├── .circleci └── config.yml ├── .github └── workflows │ └── coverage.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── benches └── server.rs ├── examples ├── client.rs ├── echo.rs ├── echo_with_dtls.rs └── server.rs ├── rustfmt.toml ├── src ├── client.rs ├── dtls.rs ├── lib.rs ├── observer.rs ├── request.rs └── server.rs └── tests └── test_certs ├── README.md ├── coap_client.csr ├── coap_client.pem ├── coap_client.pub.pem ├── coap_server.csr ├── coap_server.pem ├── coap_server.pub.pem └── extfile.conf /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/2.0/configuration-reference 3 | version: 2.1 4 | 5 | # Define a job to be invoked later in a workflow. 6 | # See: https://circleci.com/docs/2.0/configuration-reference/#jobs 7 | jobs: 8 | test: 9 | # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. 10 | # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor 11 | docker: 12 | - image: rustlang/rust:nightly-slim 13 | # Add steps to the job 14 | # See: https://circleci.com/docs/2.0/configuration-reference/#steps 15 | steps: 16 | - checkout 17 | - run: cargo --version 18 | - run: 19 | name: Run Tests 20 | command: "cargo test" 21 | - run: 22 | name: Run doc 23 | command: "cargo doc" 24 | 25 | 26 | # Invoke jobs via workflows 27 | # See: https://circleci.com/docs/2.0/configuration-reference/#workflows 28 | workflows: 29 | test-workflow: 30 | jobs: 31 | - test 32 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Install stable toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | override: true 25 | 26 | - name: Run tarpaulin 27 | uses: actions-rs/tarpaulin@v0.1 28 | with: 29 | out-type: Lcov 30 | args: '--avoid-cfg-tarpaulin' 31 | version: '0.22.0' 32 | 33 | 34 | - name: Coveralls Upload 35 | uses: coverallsapp/github-action@1.1.3 36 | with: 37 | github-token: ${{ secrets.GITHUB_TOKEN }} 38 | path-to-lcov: ./lcov.info 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coap" 3 | version = "0.21.0" 4 | description = "A CoAP library" 5 | readme = "README.md" 6 | documentation = "https://docs.rs/coap/" 7 | repository = "https://github.com/covertness/coap-rs" 8 | license = "MIT" 9 | authors = ["Yang Zhang "] 10 | keywords = ["CoAP"] 11 | edition = "2021" 12 | 13 | [dependencies] 14 | url = "^2.5" 15 | log = "^0.4" 16 | regex = "^1.5" 17 | tokio = {version = "^1.32", features = ["full"]} 18 | tokio-util = {version = "^0.6", features = ["codec","net"]} 19 | tokio-stream = {version = "^0.1", features = ["time"]} 20 | futures = "^0.3" 21 | coap-lite = "0.13.2" 22 | async-trait = "0.1.74" 23 | 24 | # dependencies for dtls 25 | webrtc-dtls = {version = "0.8.0", optional = true} 26 | webrtc-util = {version = "0.8.0", optional = true} 27 | rustls = {version = "^0.21.1", optional = true} 28 | rustls-pemfile = {version = "2.0.0", optional = true} 29 | rcgen = {version = "^0.11.0", optional = true} 30 | pkcs8 = {version = "0.10.2", optional = true} 31 | sec1 = { version = "0.7.3", features = ["pem", "pkcs8", "std"], optional = true} 32 | rand = "0.8.5" 33 | 34 | [features] 35 | default = ["dtls"] 36 | dtls = ["dep:webrtc-dtls", "dep:webrtc-util", "dep:rustls", "dep:rustls-pemfile", "dep:rcgen", "dep:pkcs8", "dep:sec1"] 37 | 38 | 39 | [dev-dependencies] 40 | quickcheck = "0.8.2" 41 | socket2 = "0.5" 42 | tokio-test = "0.4.4" 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2019 Yang Zhang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coap-rs 2 | 3 | [![CircleCI](https://circleci.com/gh/Covertness/coap-rs.svg?style=svg)](https://circleci.com/gh/Covertness/coap-rs) 4 | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/ic36jdu4xy6doc59?svg=true)](https://ci.appveyor.com/project/Covertness/coap-rs) 5 | ![Downloads](https://img.shields.io/crates/d/coap.svg?style=flat) 6 | [![Coverage Status](https://coveralls.io/repos/github/Covertness/coap-rs/badge.svg?branch=master)](https://coveralls.io/github/Covertness/coap-rs?branch=master) 7 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 8 | 9 | A fast and stable [Constrained Application Protocol(CoAP)](https://tools.ietf.org/html/rfc7252) library implemented in Rust. 10 | 11 | Features: 12 | - CoAP core protocol [RFC 7252](https://tools.ietf.org/rfc/rfc7252.txt) 13 | - CoAP Observe option [RFC 7641](https://tools.ietf.org/rfc/rfc7641.txt) 14 | - *Too Many Requests* Response Code [RFC 8516](https://tools.ietf.org/html/rfc8516) 15 | - Block-Wise Transfers [RFC 7959](https://tools.ietf.org/html/rfc7959) 16 | - DTLS support via [webrtc-rs](https://github.com/webrtc-rs/webrtc) 17 | - Option to provide custom transports for client and server 18 | 19 | [Documentation](https://docs.rs/coap/) 20 | 21 | ## Installation 22 | 23 | First add this to your `Cargo.toml`: 24 | 25 | ```toml 26 | [dependencies] 27 | coap = "0.21" 28 | coap-lite = "0.13.1" 29 | tokio = {version = "^1.32", features = ["full"]} 30 | ``` 31 | 32 | ## Example 33 | 34 | ### Server: 35 | ```rust 36 | use coap_lite::{RequestType as Method, CoapRequest}; 37 | use coap::Server; 38 | use tokio::runtime::Runtime; 39 | use std::net::SocketAddr; 40 | fn main() { 41 | let addr = "127.0.0.1:5683"; 42 | Runtime::new().unwrap().block_on(async move { 43 | let mut server = Server::new_udp(addr).unwrap(); 44 | println!("Server up on {}", addr); 45 | 46 | server.run(|mut request: Box>| async { 47 | match request.get_method() { 48 | &Method::Get => println!("request by get {}", request.get_path()), 49 | &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload.clone()).unwrap()), 50 | &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload.clone()).unwrap()), 51 | _ => println!("request by other method"), 52 | }; 53 | 54 | match request.response { 55 | Some(ref mut message) => { 56 | message.message.payload = b"OK".to_vec(); 57 | 58 | }, 59 | _ => {} 60 | }; 61 | return request 62 | }).await.unwrap(); 63 | }); 64 | } 65 | ``` 66 | 67 | ### Client: 68 | ```rust 69 | use coap_lite::{RequestType as Method, CoapRequest}; 70 | use coap::{UdpCoAPClient}; 71 | use tokio::main; 72 | #[tokio::main] 73 | async fn main() { 74 | let url = "coap://127.0.0.1:5683/Rust"; 75 | println!("Client request: {}", url); 76 | 77 | let response = UdpCoAPClient::get(url).await.unwrap(); 78 | println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); 79 | } 80 | ``` 81 | 82 | ## Benchmark 83 | ```bash 84 | $ cargo bench 85 | ``` 86 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 3 | - rustup-init -yv --default-toolchain nightly --default-host x86_64-pc-windows-msvc 4 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 5 | - rustc -vV 6 | - cargo -vV 7 | 8 | build: false 9 | 10 | environment: 11 | RUST_TEST_THREADS: 1 12 | 13 | test_script: 14 | - cargo test --verbose 15 | -------------------------------------------------------------------------------- /benches/server.rs: -------------------------------------------------------------------------------- 1 | #![feature(test, async_closure)] 2 | extern crate test; 3 | 4 | use std::net::SocketAddr; 5 | 6 | use coap::{server::UdpCoapListener, Server, UdpCoAPClient}; 7 | use coap_lite::{CoapOption, CoapRequest, MessageType}; 8 | use tokio::{net::UdpSocket, runtime::Runtime}; 9 | 10 | #[bench] 11 | fn bench_server_with_request(b: &mut test::Bencher) { 12 | let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); 13 | 14 | let rt = Runtime::new().unwrap(); 15 | rt.spawn(async move { 16 | let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 17 | let addr = sock.local_addr().unwrap(); 18 | let listener = Box::new(UdpCoapListener::from_socket(sock)); 19 | let server = Server::from_listeners(vec![listener]); 20 | 21 | tx.send(addr.port()).unwrap(); 22 | 23 | server 24 | .run(move |mut request: Box>| async { 25 | let uri_path = request.get_path().to_string(); 26 | 27 | match request.response { 28 | Some(ref mut response) => { 29 | response.message.payload = uri_path.as_bytes().to_vec(); 30 | } 31 | _ => {} 32 | }; 33 | return request; 34 | }) 35 | .await 36 | .unwrap(); 37 | }); 38 | 39 | let server_port = rx.blocking_recv().unwrap(); 40 | let client = rt.block_on(async { 41 | UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 42 | .await 43 | .unwrap() 44 | }); 45 | 46 | let mut request = CoapRequest::new(); 47 | request.message.header.set_version(1); 48 | request.message.header.set_type(MessageType::Confirmable); 49 | request.message.header.set_code("0.01"); 50 | request.message.header.message_id = 1; 51 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 52 | request 53 | .message 54 | .add_option(CoapOption::UriPath, "test".to_string().into_bytes()); 55 | 56 | b.iter(|| { 57 | rt.block_on(async { 58 | let recv_packet = client.send(request.clone()).await.unwrap(); 59 | assert_eq!(recv_packet.message.payload, b"test".to_vec()); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | extern crate coap; 2 | 3 | use coap::client::ObserveMessage; 4 | use coap::UdpCoAPClient; 5 | use std::io; 6 | use std::io::ErrorKind; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | println!("GET url:"); 11 | example_get().await; 12 | 13 | println!("POST data to url:"); 14 | example_post().await; 15 | 16 | println!("PUT data to url:"); 17 | example_put().await; 18 | 19 | println!("DELETE url:"); 20 | example_delete().await; 21 | 22 | println!("Observing:"); 23 | example_observe().await; 24 | } 25 | 26 | async fn example_get() { 27 | let url = "coap://127.0.0.1:5683/hello/get"; 28 | println!("Client request: {}", url); 29 | 30 | match UdpCoAPClient::get(url).await { 31 | Ok(response) => { 32 | println!( 33 | "Server reply: {}", 34 | String::from_utf8(response.message.payload).unwrap() 35 | ); 36 | } 37 | Err(e) => { 38 | match e.kind() { 39 | ErrorKind::WouldBlock => println!("Request timeout"), // Unix 40 | ErrorKind::TimedOut => println!("Request timeout"), // Windows 41 | _ => println!("Request error: {:?}", e), 42 | } 43 | } 44 | } 45 | } 46 | 47 | async fn example_post() { 48 | let url = "coap://127.0.0.1:5683/hello/post"; 49 | let data = b"data".to_vec(); 50 | println!("Client request: {}", url); 51 | 52 | match UdpCoAPClient::post(url, data).await { 53 | Ok(response) => { 54 | println!( 55 | "Server reply: {}", 56 | String::from_utf8(response.message.payload).unwrap() 57 | ); 58 | } 59 | Err(e) => { 60 | match e.kind() { 61 | ErrorKind::WouldBlock => println!("Request timeout"), // Unix 62 | ErrorKind::TimedOut => println!("Request timeout"), // Windows 63 | _ => println!("Request error: {:?}", e), 64 | } 65 | } 66 | } 67 | } 68 | 69 | async fn example_put() { 70 | let url = "coap://127.0.0.1:5683/hello/put"; 71 | let data = b"data".to_vec(); 72 | println!("Client request: {}", url); 73 | 74 | match UdpCoAPClient::put(url, data).await { 75 | Ok(response) => { 76 | println!( 77 | "Server reply: {}", 78 | String::from_utf8(response.message.payload).unwrap() 79 | ); 80 | } 81 | Err(e) => { 82 | match e.kind() { 83 | ErrorKind::WouldBlock => println!("Request timeout"), // Unix 84 | ErrorKind::TimedOut => println!("Request timeout"), // Windows 85 | _ => println!("Request error: {:?}", e), 86 | } 87 | } 88 | } 89 | } 90 | 91 | async fn example_delete() { 92 | let url = "coap://127.0.0.1:5683/hello/delete"; 93 | println!("Client request: {}", url); 94 | 95 | match UdpCoAPClient::delete(url).await { 96 | Ok(response) => { 97 | println!( 98 | "Server reply: {}", 99 | String::from_utf8(response.message.payload).unwrap() 100 | ); 101 | } 102 | Err(e) => { 103 | match e.kind() { 104 | ErrorKind::WouldBlock => println!("Request timeout"), // Unix 105 | ErrorKind::TimedOut => println!("Request timeout"), // Windows 106 | _ => println!("Request error: {:?}", e), 107 | } 108 | } 109 | } 110 | } 111 | 112 | async fn example_observe() { 113 | let client = UdpCoAPClient::new_udp("127.0.0.1:5683").await.unwrap(); 114 | let observe_channel = client 115 | .observe("/hello/put", |msg| { 116 | println!( 117 | "resource changed {}", 118 | String::from_utf8(msg.payload).unwrap() 119 | ); 120 | }) 121 | .await 122 | .unwrap(); 123 | 124 | println!("Enter any key to stop..."); 125 | 126 | io::stdin().read_line(&mut String::new()).unwrap(); 127 | observe_channel.send(ObserveMessage::Terminate).unwrap(); 128 | } 129 | -------------------------------------------------------------------------------- /examples/echo.rs: -------------------------------------------------------------------------------- 1 | extern crate coap; 2 | 3 | use std::net::SocketAddr; 4 | 5 | use coap::{Server, UdpCoAPClient}; 6 | use coap_lite::CoapRequest; 7 | #[tokio::main] 8 | async fn main() { 9 | let _server_task = tokio::spawn(async move { 10 | let server = Server::new_udp("127.0.0.1:5683").unwrap(); 11 | server 12 | .run(|mut request: Box>| async { 13 | let uri_path = request.get_path().to_string(); 14 | 15 | match request.response { 16 | Some(ref mut response) => { 17 | response.message.payload = uri_path.as_bytes().to_vec(); 18 | } 19 | _ => {} 20 | }; 21 | return request; 22 | }) 23 | .await 24 | .unwrap(); 25 | }); 26 | 27 | let url = "coap://127.0.0.1:5683/Rust"; 28 | println!("Client request: {}", url); 29 | 30 | // Maybe need sleep seconds before start client on some OS: https://github.com/Covertness/coap-rs/issues/75 31 | let response = UdpCoAPClient::get(url).await.unwrap(); 32 | println!( 33 | "Server reply: {}", 34 | String::from_utf8(response.message.payload).unwrap() 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /examples/echo_with_dtls.rs: -------------------------------------------------------------------------------- 1 | /// This example shows how to use DTLS with coap-rs. If you want to use PKI, please take 2 | /// a look at the test in dtls.rs 3 | extern crate coap; 4 | use coap::client::CoAPClient; 5 | use coap::dtls::UdpDtlsConfig; 6 | use coap::request::RequestBuilder; 7 | use coap::Server; 8 | use coap_lite::{CoapRequest, RequestType as Method}; 9 | use std::future::Future; 10 | use std::net::{SocketAddr, ToSocketAddrs}; 11 | use std::sync::Arc; 12 | use tokio::sync::mpsc; 13 | use webrtc_dtls::cipher_suite::CipherSuiteId; 14 | use webrtc_dtls::config::Config; 15 | use webrtc_dtls::listener::listen; 16 | use webrtc_util::conn::Listener as WebRtcListener; 17 | 18 | pub fn spawn_dtls_server< 19 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 20 | HandlerRet, 21 | >( 22 | ip: &'static str, 23 | request_handler: F, 24 | config: webrtc_dtls::config::Config, 25 | ) -> mpsc::UnboundedReceiver 26 | where 27 | HandlerRet: Future>> + Send, 28 | { 29 | let (tx, rx) = mpsc::unbounded_channel(); 30 | tokio::spawn(async move { 31 | let listener = listen(ip, config).await.unwrap(); 32 | let listen_port = listener.addr().await.unwrap().port(); 33 | let listener = Box::new(listener); 34 | let server = Server::from_listeners(vec![listener]); 35 | tx.send(listen_port).unwrap(); 36 | server.run(request_handler).await.unwrap(); 37 | }); 38 | 39 | rx 40 | } 41 | 42 | #[tokio::main] 43 | async fn main() { 44 | let config = Config { 45 | psk: Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))), 46 | psk_identity_hint: Some("webrtc-rs DTLS Server".as_bytes().to_vec()), 47 | cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], 48 | server_name: "localhost".to_string(), 49 | ..Default::default() 50 | }; 51 | let server_port = spawn_dtls_server( 52 | "127.0.0.1:0", 53 | |mut req| async { 54 | req.response.as_mut().unwrap().message.payload = b"hello dtls".to_vec(); 55 | req 56 | }, 57 | config.clone(), 58 | ) 59 | .recv() 60 | .await 61 | .unwrap(); 62 | 63 | let dtls_config = UdpDtlsConfig { 64 | config, 65 | dest_addr: ("127.0.0.1", server_port) 66 | .to_socket_addrs() 67 | .unwrap() 68 | .next() 69 | .unwrap(), 70 | }; 71 | 72 | let client = CoAPClient::from_udp_dtls_config(dtls_config) 73 | .await 74 | .expect("could not create client"); 75 | let domain = format!("127.0.0.1:{}", server_port); 76 | 77 | let request = RequestBuilder::new("/hello", Method::Get) 78 | .domain(domain.to_string()) 79 | .build(); 80 | let resp = client.send(request).await.unwrap(); 81 | println!( 82 | "receive on client: {}", 83 | std::str::from_utf8(&resp.message.payload).unwrap() 84 | ); 85 | assert_eq!(resp.message.payload, b"hello dtls".to_vec()); 86 | } 87 | -------------------------------------------------------------------------------- /examples/server.rs: -------------------------------------------------------------------------------- 1 | extern crate coap; 2 | 3 | use std::net::SocketAddr; 4 | 5 | use coap::Server; 6 | use coap_lite::CoapRequest; 7 | use coap_lite::RequestType as Method; 8 | use tokio::runtime::Runtime; 9 | 10 | fn main() { 11 | let addr = "127.0.0.1:5683"; 12 | 13 | Runtime::new().unwrap().block_on(async move { 14 | let server = Server::new_udp(addr).unwrap(); 15 | println!("Server up on {}", addr); 16 | 17 | server 18 | .run(|mut request: Box>| async move { 19 | match request.get_method() { 20 | &Method::Get => println!("request by get {}", request.get_path()), 21 | &Method::Post => println!( 22 | "request by post {}", 23 | String::from_utf8(request.message.payload.clone()).unwrap() 24 | ), 25 | &Method::Put => println!( 26 | "request by put {}", 27 | String::from_utf8(request.message.payload.clone()).unwrap() 28 | ), 29 | _ => println!("request by other method"), 30 | }; 31 | 32 | match request.response { 33 | Some(ref mut message) => { 34 | message.message.payload = b"OK".to_vec(); 35 | } 36 | _ => {} 37 | }; 38 | return request; 39 | }) 40 | .await 41 | .unwrap(); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | indent_style = "Block" 2 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "dtls")] 2 | use crate::dtls::{DtlsConnection, UdpDtlsConfig}; 3 | use crate::request::RequestBuilder; 4 | use coap_lite::{ 5 | block_handler::{extending_splice, BlockValue}, 6 | error::HandlingError, 7 | CoapOption, CoapRequest, CoapResponse, MessageClass, MessageType, ObserveOption, Packet as Message, 8 | RequestType as Method, ResponseType as Status, 9 | }; 10 | use core::mem; 11 | 12 | use futures::Future; 13 | use log::*; 14 | 15 | use regex::Regex; 16 | use std::{ 17 | collections::BTreeMap, 18 | net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, 19 | sync::{atomic::AtomicU16, Weak}, 20 | }; 21 | use std::{ 22 | io::{Error, ErrorKind, Result as IoResult}, 23 | pin::Pin, 24 | }; 25 | use std::{sync::Arc, time::Duration}; 26 | use tokio::sync::{ 27 | mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, 28 | oneshot, Mutex, 29 | }; 30 | use tokio::time::timeout; 31 | use tokio::{ 32 | net::{lookup_host, ToSocketAddrs, UdpSocket}, 33 | sync::RwLock, 34 | }; 35 | use url::Url; 36 | const DEFAULT_RECEIVE_TIMEOUT_SECONDS: u64 = 2; // 2s 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct Packet { 40 | pub address: Option, 41 | pub message: Message, 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum ObserveMessage { 46 | Terminate, 47 | } 48 | use async_trait::async_trait; 49 | 50 | #[async_trait] 51 | /// A basic interface for a transport on the client 52 | /// representing a one-to-one connection between a client and server 53 | /// timeouts and retries do not need to be implemented by the transport 54 | /// if confirmable messages are sent 55 | pub trait ClientTransport: Send + Sync { 56 | async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, Option)>; 57 | async fn send(&self, buf: &[u8]) -> std::io::Result; 58 | } 59 | 60 | trait TransportExt { 61 | async fn receive_packet(&self) -> IoResult>; 62 | } 63 | 64 | impl TransportExt for T { 65 | async fn receive_packet(&self) -> IoResult> { 66 | let mut buf = [0; 1500]; 67 | let (nread, address) = self.recv(&mut buf).await?; 68 | return match Message::from_bytes(&buf[..nread]).ok() { 69 | Some(message) => Ok(Some(Packet {address, message})), 70 | None => Ok(None), 71 | } 72 | } 73 | } 74 | 75 | /// we only use the token as the identifier, and an empty token to represent empty requests 76 | type Token = Vec; 77 | type PacketRegistry = BTreeMap>>; 78 | 79 | #[derive(Clone)] 80 | pub struct TransportSynchronizer { 81 | pub(crate) outgoing: Arc>, 82 | fail_error: Arc>>, 83 | } 84 | 85 | impl TransportSynchronizer { 86 | pub fn new() -> Self { 87 | Self { 88 | outgoing: Arc::new(Mutex::new(PacketRegistry::new())), 89 | fail_error: Arc::new(RwLock::new(None)), 90 | } 91 | } 92 | 93 | async fn check_for_error(&self, sender: &UnboundedSender>) -> Option<()> { 94 | self.fail_error.read().await.as_ref(); 95 | if let Some(err) = self.fail_error.read().await.as_ref() { 96 | let _ = sender.send(Err(Self::clone_err(err))); 97 | return None; 98 | } 99 | Some(()) 100 | } 101 | 102 | fn clone_err(error: &std::io::Error) -> std::io::Error { 103 | let s = error.to_string(); 104 | let k = error.kind(); 105 | std::io::Error::new(k, s) 106 | } 107 | 108 | pub async fn fail(self, error: std::io::Error) { 109 | let error_clone = Self::clone_err(&error); 110 | let _ = self.fail_error.write().await.insert(error_clone); 111 | let mut mutex = self.outgoing.lock().await; 112 | 113 | let keys: Vec> = mutex.keys().cloned().collect(); 114 | for k in keys { 115 | let error_clone = Self::clone_err(&error); 116 | let _ = mutex.remove(&k).map(|resp| resp.send(Err(error_clone))); 117 | } 118 | } 119 | 120 | pub async fn get_sender(&self, key: &[u8]) -> Option>> { 121 | self.outgoing 122 | .lock() 123 | .await 124 | .get(key) 125 | .map(UnboundedSender::clone) 126 | } 127 | /// Sets the sender of a given key, 128 | /// returns the previous key if it was set 129 | pub async fn set_sender( 130 | &self, 131 | key: Vec, 132 | sender: UnboundedSender>, 133 | ) -> Option>> { 134 | self.check_for_error(&sender).await?; 135 | self.outgoing.lock().await.insert(key, sender) 136 | } 137 | pub async fn remove_sender(&self, key: &[u8]) -> Option>> { 138 | self.outgoing.lock().await.remove(key) 139 | } 140 | } 141 | 142 | async fn receive_loop( 143 | transport: Weak, 144 | transport_sync: TransportSynchronizer, 145 | ) -> std::io::Result<()> { 146 | let err = loop { 147 | let Some(transport_instance) = transport.upgrade() else { 148 | // nobody else is listening so we can drop our reference 149 | return Ok(()); 150 | }; 151 | // we do a timeout here to ensure that we do not block forever 152 | let Ok(recv_res) = timeout( 153 | Duration::from_millis(300), 154 | transport_instance.receive_packet(), 155 | ) 156 | .await 157 | else { 158 | continue; 159 | }; 160 | let option_packet = match recv_res { 161 | Err(e) => break e, 162 | Ok(o) => o, 163 | }; 164 | let Some(packet) = option_packet else { 165 | trace!("unexpected malformed packet received"); 166 | continue; 167 | }; 168 | if let Some(ack) = parse_for_ack(&packet) { 169 | transport_instance.send(&ack).await?; 170 | } 171 | 172 | match packet.message.header.code { 173 | MessageClass::Response(_) => {} 174 | m => { 175 | debug!("unknown message type {}", m); 176 | continue; 177 | } 178 | }; 179 | 180 | let token = packet.message.get_token(); 181 | let Some(sender) = transport_sync.get_sender(token).await else { 182 | info!("received unexpected response for token {:?}", &token); 183 | continue; 184 | }; 185 | let Ok(_) = sender.send(Ok(packet)) else { 186 | debug!("unexpected drop of sender"); 187 | continue; 188 | }; 189 | }; 190 | 191 | let e = Err(Error::new(err.kind(), err.to_string())); 192 | transport_sync.fail(err).await; 193 | return e; 194 | } 195 | 196 | pub fn parse_for_ack(packet: &Packet) -> Option> { 197 | match (packet.message.header.get_type(), packet.message.header.code) { 198 | (MessageType::Confirmable, MessageClass::Response(_)) => Some(make_ack(packet)), 199 | _ => None, 200 | } 201 | } 202 | 203 | pub fn make_ack(packet: &Packet) -> Vec { 204 | let mut ack = Message::new(); 205 | ack.header.set_type(MessageType::Acknowledgement); 206 | ack.header.message_id = packet.message.header.message_id; 207 | ack.header.code = MessageClass::Empty; 208 | return ack.to_bytes().unwrap(); 209 | } 210 | 211 | /// a wrapper for transports responsible for retries and timeouts 212 | struct CoapClientTransport { 213 | pub(crate) transport: Arc, 214 | pub(crate) synchronizer: TransportSynchronizer, 215 | pub(crate) retries: usize, 216 | pub(crate) timeout: Duration, 217 | } 218 | 219 | impl Clone for CoapClientTransport { 220 | fn clone(&self) -> Self { 221 | Self { 222 | transport: self.transport.clone(), 223 | synchronizer: self.synchronizer.clone(), 224 | retries: self.retries.clone(), 225 | timeout: self.timeout.clone(), 226 | } 227 | } 228 | } 229 | 230 | impl CoapClientTransport { 231 | pub const DEFAULT_NUM_RETRIES: usize = 5; 232 | async fn establish_receiver_for(&self, packet: &Packet) -> UnboundedReceiver> { 233 | let (tx, rx) = unbounded_channel(); 234 | let token = packet.message.get_token().to_owned(); 235 | self.synchronizer.set_sender(token, tx).await; 236 | return rx; 237 | } 238 | 239 | /// tries to send a confirmable message with retries and timeouts 240 | async fn try_send_confirmable_message( 241 | &self, 242 | msg: &Packet, 243 | receiver: &mut UnboundedReceiver>, 244 | ) -> IoResult { 245 | let mut res = Err(Error::new(ErrorKind::InvalidData, "not enough retries")); 246 | for _ in 0..self.retries { 247 | res = self.try_send_non_confirmable_message(&msg, receiver).await; 248 | if res.is_ok() { 249 | return res; 250 | } 251 | } 252 | return res; 253 | } 254 | 255 | fn encode_message(message: &Message) -> IoResult> { 256 | message 257 | .to_bytes() 258 | .map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e.to_string())) 259 | } 260 | 261 | async fn try_send_non_confirmable_message( 262 | &self, 263 | msg: &Packet, 264 | receiver: &mut UnboundedReceiver>, 265 | ) -> IoResult { 266 | let bytes = Self::encode_message(&msg.message)?; 267 | self.transport.send(&bytes).await?; 268 | let try_receive: Result>, tokio::time::error::Elapsed> = 269 | timeout(self.timeout, receiver.recv()).await; 270 | if let Ok(Some(res)) = try_receive { 271 | return res; 272 | } 273 | Err(Error::new(ErrorKind::TimedOut, "send timeout")) 274 | } 275 | 276 | async fn do_request_response_for_packet_inner( 277 | &self, 278 | packet: &Packet, 279 | receiver: &mut UnboundedReceiver>, 280 | ) -> IoResult { 281 | if packet.message.header.get_type() == MessageType::Confirmable { 282 | return self.try_send_confirmable_message(&packet, receiver).await; 283 | } else { 284 | return self 285 | .try_send_non_confirmable_message(&packet, receiver) 286 | .await; 287 | } 288 | } 289 | 290 | pub async fn do_request_response_for_packet(&self, packet: &Packet) -> IoResult { 291 | let mut receiver = self.establish_receiver_for(packet).await; 292 | let result = self 293 | .do_request_response_for_packet_inner(packet, &mut receiver) 294 | .await; 295 | self.synchronizer.remove_sender(packet.message.get_token()).await; 296 | result 297 | } 298 | 299 | pub fn from_transport(transport: Arc, synchronizer: TransportSynchronizer) -> Self { 300 | return Self { 301 | transport, 302 | synchronizer, 303 | retries: Self::DEFAULT_NUM_RETRIES, 304 | timeout: Duration::from_secs(DEFAULT_RECEIVE_TIMEOUT_SECONDS), 305 | }; 306 | } 307 | } 308 | 309 | pub struct UdpTransport { 310 | pub socket: UdpSocket, 311 | pub peer_addr: SocketAddr, 312 | } 313 | #[async_trait] 314 | impl ClientTransport for UdpTransport { 315 | async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, Option)> { 316 | let (read, addr) = self.socket 317 | .recv_from(buf) 318 | .await?; 319 | return Ok((read, Some(addr))); 320 | } 321 | async fn send(&self, buf: &[u8]) -> std::io::Result { 322 | self.socket.send_to(buf, self.peer_addr).await 323 | } 324 | } 325 | 326 | /// A CoAP client over UDP. This client can send multicast and broadcasts 327 | pub type UdpCoAPClient = CoAPClient; 328 | 329 | pub struct CoAPClient { 330 | transport: CoapClientTransport, 331 | block1_size: usize, 332 | message_id: Arc, 333 | } 334 | 335 | impl Clone for CoAPClient { 336 | fn clone(&self) -> Self { 337 | Self { 338 | transport: self.transport.clone(), 339 | block1_size: self.block1_size.clone(), 340 | message_id: self.message_id.clone(), 341 | } 342 | } 343 | } 344 | 345 | /// a receiver used whenever you have a use case involving multiple responses to a single request 346 | pub struct MessageReceiver { 347 | synchronizer: TransportSynchronizer, 348 | receiver: UnboundedReceiver>, 349 | token: Vec, 350 | } 351 | 352 | impl MessageReceiver { 353 | pub async fn receive(&mut self) -> IoResult { 354 | match self.receiver.recv().await { 355 | Some(Ok(packet)) => Ok(packet), 356 | Some(Err(e)) => Err(e), 357 | None => Err(Error::new( 358 | ErrorKind::Other, 359 | "sender dropped by synchronizer", 360 | )), 361 | } 362 | } 363 | pub fn new( 364 | synchronizer: TransportSynchronizer, 365 | receiver: UnboundedReceiver>, 366 | token: &[u8], 367 | ) -> Self { 368 | Self { 369 | synchronizer, 370 | receiver, 371 | token: token.to_vec(), 372 | } 373 | } 374 | } 375 | 376 | impl Drop for MessageReceiver { 377 | fn drop(&mut self) { 378 | let sync = self.synchronizer.clone(); 379 | let tok = std::mem::take(&mut self.token); 380 | tokio::spawn(async move { sync.remove_sender(&tok).await }); 381 | } 382 | } 383 | 384 | impl UdpCoAPClient { 385 | pub async fn new_with_specific_source( 386 | bind_addr: A, 387 | peer_addr: B, 388 | ) -> IoResult { 389 | let socket = UdpSocket::bind(bind_addr).await?; 390 | Self::new_with_tokio_socket(socket, peer_addr).await 391 | } 392 | 393 | pub async fn new_udp(addr: A) -> IoResult { 394 | let sock_addr = lookup_host(addr).await?.next().ok_or(Error::new( 395 | ErrorKind::InvalidInput, 396 | "could not get socket address", 397 | ))?; 398 | Ok(match &sock_addr { 399 | SocketAddr::V4(_) => Self::new_with_specific_source("0.0.0.0:0", sock_addr).await?, 400 | SocketAddr::V6(_) => Self::new_with_specific_source(":::0", sock_addr).await?, 401 | }) 402 | } 403 | 404 | /// Create a client with a `std::net` socket 405 | /// 406 | /// Using a standard socket is useful to get advanced features from socket2 crate 407 | /// 408 | /// # Examples 409 | /// 410 | /// ``` 411 | /// # tokio_test::block_on(async { 412 | /// use socket2::{Socket, Domain, Type}; 413 | /// use coap::UdpCoAPClient; 414 | /// 415 | /// let socket = Socket::new(Domain::IPV6, Type::DGRAM, None).expect("Standard socket creation failed"); 416 | /// socket.set_multicast_hops_v6(16).expect("Setting multicast hops failed"); 417 | /// let client = UdpCoAPClient::new_with_std_socket(socket.into(), "[::1]:5683").await.expect("Client creation failed"); 418 | /// # }) 419 | /// ``` 420 | pub async fn new_with_std_socket( 421 | socket: std::net::UdpSocket, 422 | peer_addr: A, 423 | ) -> IoResult { 424 | socket.set_nonblocking(true)?; 425 | let socket = UdpSocket::from_std(socket)?; 426 | Self::new_with_tokio_socket(socket, peer_addr).await 427 | } 428 | 429 | async fn new_with_tokio_socket( 430 | socket: UdpSocket, 431 | peer_addr: A, 432 | ) -> IoResult { 433 | let peer_addr = lookup_host(peer_addr).await?.next().ok_or(Error::new( 434 | ErrorKind::InvalidInput, 435 | "could not get socket address", 436 | ))?; 437 | 438 | let transport = UdpTransport { socket, peer_addr }; 439 | Ok(UdpCoAPClient::from_transport(transport)) 440 | } 441 | 442 | /// Send a request to all CoAP devices. 443 | /// - IPv4 AllCoAP multicast address is '224.0.1.187' 444 | /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' 445 | /// Parameter segment is used with IPv6 to determine the first octet. 446 | /// It's value can be between 0x0 and 0xf. To address multiple segments, 447 | /// you have to call send_all_coap for each of the segments. 448 | pub async fn send_all_coap( 449 | &self, 450 | request: &mut CoapRequest, 451 | segment: u8, 452 | ) -> IoResult<()> { 453 | assert!(segment <= 0xf); 454 | let addr = match self.transport.transport.peer_addr { 455 | SocketAddr::V4(val) => { 456 | SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), val.port()) 457 | } 458 | SocketAddr::V6(val) => SocketAddr::new( 459 | IpAddr::V6(Ipv6Addr::new( 460 | 0xff00 + segment as u16, 461 | 0, 462 | 0, 463 | 0, 464 | 0, 465 | 0, 466 | 0, 467 | 0xfd, 468 | )), 469 | val.port(), 470 | ), 471 | }; 472 | 473 | self.send_multicast(request, &addr).await 474 | } 475 | 476 | /// Send a multicast request to multiple devices. 477 | pub async fn send_multicast( 478 | &self, 479 | request: &mut CoapRequest, 480 | addr: &SocketAddr, 481 | ) -> IoResult<()> { 482 | if 0 == request.message.header.message_id { 483 | request.message.header.message_id = self.gen_message_id(); 484 | } 485 | match request.message.to_bytes() { 486 | Ok(bytes) => { 487 | let size = self 488 | .transport 489 | .transport 490 | .socket 491 | .send_to(&bytes[..], addr) 492 | .await?; 493 | if size == bytes.len() { 494 | Ok(()) 495 | } else { 496 | Err(Error::new(ErrorKind::Other, "send length error")) 497 | } 498 | } 499 | Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")), 500 | } 501 | } 502 | 503 | pub fn set_broadcast(&self, value: bool) -> IoResult<()> { 504 | self.transport.transport.socket.set_broadcast(value) 505 | } 506 | 507 | /// creates a receiver based on a specific request 508 | /// this method can be used if you send a multicast request and 509 | /// expect multiple responses. 510 | /// only use this method if you know what you are doing 511 | /// ``` 512 | /// 513 | /// use coap_lite::{ 514 | /// RequestType 515 | /// }; 516 | /// use coap::request::RequestBuilder; 517 | /// use coap::client::UdpCoAPClient; 518 | /// 519 | /// async fn foo() { 520 | /// let segment = 0x0; 521 | /// let client = UdpCoAPClient::new_udp("127.0.0.1:5683") 522 | /// .await 523 | /// .unwrap(); 524 | /// let mut request = RequestBuilder::new("test-echo", RequestType::Get) 525 | /// .data(Some(vec![0x51, 0x55, 0x77, 0xE8])) 526 | /// .confirmable(true) 527 | /// .build(); 528 | /// 529 | /// let mut receiver = client.create_receiver_for(&request).await; 530 | /// client.send_all_coap(&mut request, segment).await.unwrap(); 531 | /// loop { 532 | /// let recv_packet = receiver.receive().await.unwrap(); 533 | /// assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 534 | /// } 535 | /// } 536 | /// ``` 537 | 538 | pub async fn create_receiver_for(&self, request: &CoapRequest) -> MessageReceiver { 539 | let (tx, rx) = unbounded_channel(); 540 | let key = request.message.get_token().to_vec(); 541 | self.transport.synchronizer.set_sender(key, tx).await; 542 | return MessageReceiver::new( 543 | self.transport.synchronizer.clone(), 544 | rx, 545 | request.message.get_token(), 546 | ); 547 | } 548 | } 549 | 550 | #[cfg(feature = "dtls")] 551 | impl CoAPClient { 552 | pub async fn from_udp_dtls_config(config: UdpDtlsConfig) -> IoResult { 553 | Ok(CoAPClient::from_transport( 554 | DtlsConnection::try_new(config).await?, 555 | )) 556 | } 557 | } 558 | 559 | impl CoAPClient { 560 | const MAX_PAYLOAD_BLOCK: usize = 1024; 561 | /// Create a CoAP client with a chosen transport type 562 | 563 | pub fn from_transport(transport: T) -> Self { 564 | let synchronizer = TransportSynchronizer::new(); 565 | let transport_arc = Arc::new(transport); 566 | let message_id: u16 = rand::random(); 567 | // spawn receive loop to handle responses 568 | tokio::spawn(receive_loop( 569 | Arc::downgrade(&transport_arc), 570 | synchronizer.clone(), 571 | )); 572 | CoAPClient { 573 | transport: CoapClientTransport::from_transport(transport_arc.clone(), synchronizer), 574 | block1_size: Self::MAX_PAYLOAD_BLOCK, 575 | message_id: Arc::new(AtomicU16::new(message_id)), 576 | } 577 | } 578 | /// Execute a single get request with a coap url 579 | pub async fn get(url: &str) -> IoResult { 580 | Self::request(url, Method::Get, None).await 581 | } 582 | 583 | /// Execute a single get request with a coap url and a specific timeout. 584 | pub async fn get_with_timeout(url: &str, timeout: Duration) -> IoResult { 585 | Self::request_with_timeout(url, Method::Get, None, timeout).await 586 | } 587 | 588 | /// Execute a single post request with a coap url using udp 589 | pub async fn post(url: &str, data: Vec) -> IoResult { 590 | Self::request(url, Method::Post, Some(data)).await 591 | } 592 | 593 | /// Execute a single post request with a coap url using udp 594 | pub async fn post_with_timeout( 595 | url: &str, 596 | data: Vec, 597 | timeout: Duration, 598 | ) -> IoResult { 599 | Self::request_with_timeout(url, Method::Post, Some(data), timeout).await 600 | } 601 | 602 | /// Execute a put request with a coap url using udp 603 | pub async fn put(url: &str, data: Vec) -> IoResult { 604 | Self::request(url, Method::Put, Some(data)).await 605 | } 606 | 607 | /// Execute a single put request with a coap url using udp 608 | pub async fn put_with_timeout( 609 | url: &str, 610 | data: Vec, 611 | timeout: Duration, 612 | ) -> IoResult { 613 | Self::request_with_timeout(url, Method::Put, Some(data), timeout).await 614 | } 615 | 616 | /// Execute a single delete request with a coap url using udp 617 | pub async fn delete(url: &str) -> IoResult { 618 | Self::request(url, Method::Delete, None).await 619 | } 620 | 621 | /// Execute a single delete request with a coap url using udp 622 | pub async fn delete_with_timeout(url: &str, timeout: Duration) -> IoResult { 623 | Self::request_with_timeout(url, Method::Delete, None, timeout).await 624 | } 625 | 626 | /// Execute a single request (GET, POST, PUT, DELETE) with a coap url using udp 627 | pub async fn request( 628 | url: &str, 629 | method: Method, 630 | data: Option>, 631 | ) -> IoResult { 632 | let (domain, port, path, queries) = Self::parse_coap_url(url)?; 633 | let client = UdpCoAPClient::new_udp((domain.as_str(), port)).await?; 634 | let request = RequestBuilder::new(&path, method) 635 | .queries(queries) 636 | .domain(domain) 637 | .data(data) 638 | .build(); 639 | client.send(request).await 640 | } 641 | 642 | /// Execute a single request (GET, POST, PUT, DELETE) with a coap url and a specfic timeout 643 | /// using udp 644 | pub async fn request_with_timeout( 645 | url: &str, 646 | method: Method, 647 | data: Option>, 648 | timeout: Duration, 649 | ) -> IoResult { 650 | let (domain, port, path, queries) = Self::parse_coap_url(url)?; 651 | let mut client = UdpCoAPClient::new_udp((domain.as_str(), port)).await?; 652 | client.set_receive_timeout(timeout); 653 | let request = RequestBuilder::new(&path, method) 654 | .queries(queries) 655 | .domain(domain) 656 | .data(data) 657 | .build(); 658 | 659 | client.send(request).await 660 | } 661 | 662 | /// Send a Request via the given transport, and receive a response. 663 | /// users are responsible for filling meaningful fields in the request 664 | /// this method supports blockwise requests 665 | pub async fn send(&self, mut request: CoapRequest) -> IoResult { 666 | let first_response = self.send_request(&mut request).await?; 667 | request.response = Some(first_response); 668 | self.receive(&mut request).await 669 | } 670 | 671 | pub async fn observe( 672 | &self, 673 | resource_path: &str, 674 | handler: H, 675 | ) -> IoResult> 676 | where 677 | T: 'static + Send + Sync, 678 | { 679 | let register_packet = RequestBuilder::new(resource_path, Method::Get).build(); 680 | self.observe_with(register_packet, handler).await 681 | } 682 | 683 | /// Observe a resource with the handler and specified timeout using the given transport. 684 | /// Use the oneshot sender to cancel observation. If this sender is dropped without explicitly 685 | /// cancelling it, the observation will continue forever. 686 | pub async fn observe_with_timeout( 687 | &mut self, 688 | resource_path: &str, 689 | handler: H, 690 | timeout: Duration, 691 | ) -> IoResult> 692 | where 693 | T: 'static + Send + Sync, 694 | { 695 | self.set_receive_timeout(timeout); 696 | self.observe(resource_path, handler).await 697 | } 698 | 699 | /// observe a resource with a given transport using your own request 700 | /// Use this method if you need to set some specific options in your 701 | /// requests. This method will add observe flags and a message id as a fallback 702 | /// Use this method if you plan on re-using the same client for requests 703 | pub async fn observe_with( 704 | &self, 705 | request: CoapRequest, 706 | mut handler: H, 707 | ) -> IoResult> { 708 | let this = self.clone(); 709 | let mut register_packet = request; 710 | if 0 == register_packet.message.header.message_id { 711 | register_packet.message.header.message_id = self.gen_message_id(); 712 | } 713 | register_packet.set_observe_flag(ObserveOption::Register); 714 | 715 | let req_token = register_packet.message.get_token().to_vec(); 716 | let resource_path = register_packet.get_path(); 717 | let response = self.send(register_packet).await?; 718 | if *response.get_status() != Status::Content { 719 | return Err(Error::new(ErrorKind::NotFound, "the resource not found")); 720 | } 721 | let (tx_observe, mut rx_observe) = unbounded_channel(); 722 | self.transport 723 | .synchronizer 724 | .set_sender(req_token.clone(), tx_observe) 725 | .await; 726 | 727 | handler(response.message); 728 | 729 | let (tx, rx) = oneshot::channel(); 730 | let observe_path = String::from(resource_path); 731 | 732 | tokio::spawn(async move { 733 | let mut rx_pinned: Pin< 734 | Box< 735 | dyn Future< 736 | Output = std::result::Result, 737 | > + Send, 738 | >, 739 | > = Box::pin(rx); 740 | loop { 741 | tokio::select! { 742 | sock_rx = rx_observe.recv() => { 743 | Self::receive_and_handle_message_observe(sock_rx?, &mut handler).await; 744 | } 745 | observe = &mut rx_pinned => { 746 | match observe { 747 | Ok(ObserveMessage::Terminate) => { 748 | this.terminate_observe(&observe_path, req_token).await; 749 | break; 750 | } 751 | // if the receiver is dropped, we change the future to wait forever 752 | Err(_) => { 753 | debug!("observe continuing forever"); 754 | rx_pinned = Box::pin(futures::future::pending()) 755 | }, 756 | } 757 | } 758 | 759 | } 760 | } 761 | Some(()) 762 | }); 763 | return Ok(tx); 764 | } 765 | 766 | async fn terminate_observe(&self, observe_path: &str, req_token: Vec) { 767 | let mut deregister_packet = CoapRequest::::new(); 768 | deregister_packet.message.header.message_id = self.gen_message_id(); 769 | deregister_packet.set_observe_flag(ObserveOption::Deregister); 770 | deregister_packet.set_path(observe_path); 771 | deregister_packet.message.set_token(req_token); 772 | 773 | let _ = self 774 | .transport 775 | .do_request_response_for_packet(&Packet {address:None, message: deregister_packet.message}) 776 | .await; 777 | } 778 | 779 | async fn receive_and_handle_message_observe( 780 | socket_result: IoResult, 781 | handler: &mut H, 782 | ) { 783 | match socket_result { 784 | Ok(packet) => { 785 | handler(packet.message); 786 | } 787 | Err(e) => match e.kind() { 788 | ErrorKind::WouldBlock => { 789 | info!("Observe timeout"); 790 | } 791 | _ => warn!("observe failed {:?}", e), 792 | }, 793 | }; 794 | } 795 | 796 | /// sends a request through the transport. If a request is confirmable, it will attempt 797 | /// retries until receiving a response. requests sent using a multicast-address should be non-confirmable 798 | /// the user is responsible for setting meaningful fields in the request 799 | /// Do not use this method unless you need low-level control over the protocol (e.g., 800 | /// multicast), instead use send for client applications. 801 | pub async fn send_single_request( 802 | &self, 803 | request: &CoapRequest, 804 | ) -> IoResult { 805 | let response = self 806 | .transport 807 | .do_request_response_for_packet(&Packet {address:None, message:request.message.to_owned()}) 808 | .await?; 809 | Ok(CoapResponse { message: response.message }) 810 | } 811 | 812 | /// low-level method to send a a request supporting block1 option based on 813 | /// the block size set in the client 814 | async fn send_request(&self, request: &mut CoapRequest) -> IoResult { 815 | let request_length = request.message.payload.len(); 816 | if request_length <= self.block1_size { 817 | if 0 == request.message.header.message_id { 818 | request.message.header.message_id = self.gen_message_id(); 819 | } 820 | return self.send_single_request(request).await; 821 | } 822 | let payload = std::mem::take(&mut request.message.payload); 823 | let mut it = payload.chunks(self.block1_size).enumerate().peekable(); 824 | let mut result = Err(Error::new(ErrorKind::Other, "unknown error occurred")); 825 | 826 | while let Some((idx, elem)) = it.next() { 827 | let more_blocks = it.peek().is_some(); 828 | let block = BlockValue::new(idx, more_blocks, self.block1_size) 829 | .map_err(|_| Error::new(ErrorKind::Other, "could not set block size"))?; 830 | 831 | request.message.clear_option(CoapOption::Block1); 832 | request 833 | .message 834 | .add_option_as::(CoapOption::Block1, block.clone()); 835 | request.message.payload = elem.to_vec(); 836 | 837 | request.message.header.message_id = self.gen_message_id(); 838 | let resp = self.send_single_request(request).await?; 839 | // continue receiving responses until last element 840 | if it.peek().is_some() { 841 | let maybe_block1 = resp 842 | .message 843 | .get_first_option_as::(CoapOption::Block1) 844 | .ok_or(Error::new( 845 | ErrorKind::Unsupported, 846 | "endpoint does not support blockwise transfers. Try setting block1_size to a larger value", 847 | ))?; 848 | let block1_resp = maybe_block1.map_err(|_| { 849 | Error::new( 850 | ErrorKind::InvalidData, 851 | "endpoint responded with invalid block", 852 | ) 853 | })?; 854 | //TODO: negotiate smaller block size 855 | if block1_resp.size_exponent != block.size_exponent { 856 | return Err(Error::new( 857 | ErrorKind::Unsupported, 858 | "negotiating block size is currently unsupported", 859 | )); 860 | } 861 | } 862 | result = Ok(resp); 863 | } 864 | return result; 865 | } 866 | 867 | /// Receive a response support block-wise. 868 | async fn receive(&self, request: &mut CoapRequest) -> IoResult { 869 | let mut block2_state = BlockState::default(); 870 | loop { 871 | match Self::intercept_response(request, &mut block2_state) { 872 | Ok(true) => { 873 | request.message.header.message_id = self.gen_message_id(); 874 | let resp = self.send_single_request(request).await?; 875 | request.response = Some(resp); 876 | } 877 | Err(err) => { 878 | error!("intercept response error: {:?}", err); 879 | return Err(Error::new(ErrorKind::Interrupted, "packet error")); 880 | } 881 | Ok(false) => { 882 | break; 883 | } 884 | } 885 | } 886 | Ok(CoapResponse { 887 | message: request.response.as_ref().unwrap().message.clone(), 888 | }) 889 | } 890 | 891 | /// Set the receive timeout. 892 | pub fn set_receive_timeout(&mut self, dur: Duration) { 893 | self.transport.timeout = dur; 894 | } 895 | 896 | pub fn set_transport_retries(&mut self, num_retries: usize) { 897 | self.transport.retries = num_retries; 898 | } 899 | 900 | /// Set the maximum size for a block1 request. Default is 1024 bytes 901 | pub fn set_block1_size(&mut self, block1_max_bytes: usize) { 902 | self.block1_size = block1_max_bytes; 903 | } 904 | 905 | fn parse_coap_url(url: &str) -> IoResult<(String, u16, String, Option>)> { 906 | let url_params = match Url::parse(url) { 907 | Ok(url_params) => url_params, 908 | Err(_) => return Err(Error::new(ErrorKind::InvalidInput, "url error")), 909 | }; 910 | 911 | let host = match url_params.host_str() { 912 | Some("") => return Err(Error::new(ErrorKind::InvalidInput, "host error")), 913 | Some(h) => h, 914 | None => return Err(Error::new(ErrorKind::InvalidInput, "host error")), 915 | }; 916 | let host = Regex::new(r"^\[(.*?)]$") 917 | .unwrap() 918 | .replace(&host, "$1") 919 | .to_string(); 920 | 921 | let port = match url_params.port() { 922 | Some(p) => p, 923 | None => 5683, 924 | }; 925 | 926 | let path = url_params.path().to_string(); 927 | 928 | let queries = url_params.query().map(|q| q.as_bytes().to_vec()); 929 | 930 | return Ok((host.to_string(), port, path, queries)); 931 | } 932 | 933 | fn gen_message_id(&self) -> u16 { 934 | self.message_id 935 | .fetch_add(1, std::sync::atomic::Ordering::Relaxed) 936 | } 937 | 938 | fn intercept_response( 939 | request: &mut CoapRequest, 940 | state: &mut BlockState, 941 | ) -> std::result::Result { 942 | let block2_handled = Self::maybe_handle_response_block2(request, state)?; 943 | if block2_handled { 944 | return Ok(true); 945 | } 946 | 947 | Ok(false) 948 | } 949 | 950 | fn maybe_handle_response_block2( 951 | request: &mut CoapRequest, 952 | state: &mut BlockState, 953 | ) -> std::result::Result { 954 | let response = request.response.as_ref().unwrap(); 955 | let maybe_block2 = response 956 | .message 957 | .get_first_option_as::(CoapOption::Block2) 958 | .and_then(|x| x.ok()); 959 | 960 | if let Some(block2) = maybe_block2 { 961 | if state.cached_payload.is_none() { 962 | state.cached_payload = Some(Vec::new()); 963 | } 964 | let cached_payload = state.cached_payload.as_mut().unwrap(); 965 | 966 | let payload_offset = usize::from(block2.num) * block2.size(); 967 | extending_splice( 968 | cached_payload, 969 | payload_offset..payload_offset + block2.size(), 970 | response.message.payload.iter().copied(), 971 | 16 * 1024, 972 | ) 973 | .map_err(HandlingError::internal)?; 974 | 975 | if block2.more { 976 | request.message.clear_option(CoapOption::Block2); 977 | let mut next_block2 = block2.clone(); 978 | next_block2.num += 1; 979 | next_block2.more = false; 980 | request 981 | .message 982 | .add_option_as::(CoapOption::Block2, next_block2); 983 | return Ok(true); 984 | } else { 985 | let cached_payload = mem::take(&mut state.cached_payload).unwrap(); 986 | request.response.as_mut().unwrap().message.payload = cached_payload; 987 | } 988 | } 989 | 990 | Ok(false) 991 | } 992 | } 993 | 994 | #[derive(Debug, Clone, Default)] 995 | pub struct BlockState { 996 | cached_payload: Option>, 997 | } 998 | 999 | #[cfg(test)] 1000 | mod test { 1001 | 1002 | use tokio::time; 1003 | 1004 | use crate::server::test::spawn_server; 1005 | 1006 | use super::super::*; 1007 | use super::*; 1008 | use std::ops::DerefMut; 1009 | use std::str; 1010 | use std::sync::atomic::{AtomicU32, Ordering}; 1011 | #[test] 1012 | fn test_parse_coap_url_good_url() { 1013 | assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1").is_ok()); 1014 | assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1:5683").is_ok()); 1015 | assert!(UdpCoAPClient::parse_coap_url("coap://[::1]").is_ok()); 1016 | assert!(UdpCoAPClient::parse_coap_url("coap://[::1]:5683").is_ok()); 1017 | assert!(UdpCoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]").is_ok()); 1018 | assert!(UdpCoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]:5683").is_ok()); 1019 | assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world").is_ok()); 1020 | } 1021 | 1022 | #[test] 1023 | fn test_parse_coap_url_bad_url() { 1024 | assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1:65536").is_err()); 1025 | assert!(UdpCoAPClient::parse_coap_url("coap://").is_err()); 1026 | assert!(UdpCoAPClient::parse_coap_url("coap://:5683").is_err()); 1027 | assert!(UdpCoAPClient::parse_coap_url("127.0.0.1").is_err()); 1028 | } 1029 | 1030 | async fn request_handler(req: Box>) -> Box> { 1031 | tokio::time::sleep(Duration::from_secs(1)).await; 1032 | req 1033 | } 1034 | 1035 | #[test] 1036 | fn test_parse_queries() { 1037 | if let Ok((_, _, _, Some(queries))) = 1038 | UdpCoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world&test1=test2") 1039 | { 1040 | assert_eq!("hello=world&test1=test2".as_bytes().to_vec(), queries); 1041 | } else { 1042 | error!("Parse Queries failed"); 1043 | } 1044 | } 1045 | 1046 | #[tokio::test] 1047 | async fn test_get_url() { 1048 | let resp = UdpCoAPClient::get("coap://coap.me:5683/hello") 1049 | .await 1050 | .unwrap(); 1051 | assert_eq!(resp.message.payload, b"world".to_vec()); 1052 | } 1053 | 1054 | #[tokio::test] 1055 | async fn test_get_url_timeout() { 1056 | let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) 1057 | .recv() 1058 | .await 1059 | .unwrap(); 1060 | 1061 | let error = UdpCoAPClient::get_with_timeout( 1062 | &format!("coap://127.0.0.1:{}/Rust", server_port), 1063 | Duration::new(0, 0), 1064 | ) 1065 | .await 1066 | .unwrap_err(); 1067 | assert_eq!(error.kind(), ErrorKind::TimedOut); 1068 | } 1069 | 1070 | #[tokio::test] 1071 | async fn test_get() { 1072 | let domain = "coap.me"; 1073 | let client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); 1074 | let resp = client 1075 | .send( 1076 | RequestBuilder::request_path( 1077 | "/hello", 1078 | Method::Get, 1079 | None, 1080 | None, 1081 | Some(domain.to_string()), 1082 | ) 1083 | .build(), 1084 | ) 1085 | .await 1086 | .unwrap(); 1087 | assert_eq!(resp.message.payload, b"world".to_vec()); 1088 | } 1089 | #[tokio::test] 1090 | async fn test_post_url() { 1091 | let resp = UdpCoAPClient::post("coap://coap.me:5683/validate", b"world".to_vec()) 1092 | .await 1093 | .unwrap(); 1094 | assert_eq!(resp.message.payload, b"POST OK".to_vec()); 1095 | let resp = UdpCoAPClient::post("coap://coap.me:5683/validate", b"test".to_vec()) 1096 | .await 1097 | .unwrap(); 1098 | assert_eq!(resp.message.payload, b"POST OK".to_vec()); 1099 | } 1100 | 1101 | #[tokio::test] 1102 | async fn test_post() { 1103 | let domain = "coap.me"; 1104 | let client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); 1105 | let resp = client 1106 | .send( 1107 | RequestBuilder::request_path( 1108 | "/validate", 1109 | Method::Post, 1110 | Some(b"world".to_vec()), 1111 | None, 1112 | Some(domain.to_string()), 1113 | ) 1114 | .build(), 1115 | ) 1116 | .await 1117 | .unwrap(); 1118 | assert_eq!(resp.message.payload, b"POST OK".to_vec()); 1119 | } 1120 | 1121 | #[tokio::test] 1122 | async fn test_put_url() { 1123 | let resp = UdpCoAPClient::put("coap://coap.me:5683/create1", b"world".to_vec()) 1124 | .await 1125 | .unwrap(); 1126 | assert_eq!(resp.message.payload, b"Created".to_vec()); 1127 | let resp = UdpCoAPClient::put("coap://coap.me:5683/create1", b"test".to_vec()) 1128 | .await 1129 | .unwrap(); 1130 | assert_eq!(resp.message.payload, b"Created".to_vec()); 1131 | } 1132 | 1133 | #[tokio::test] 1134 | async fn test_put() { 1135 | let domain = "coap.me"; 1136 | let client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); 1137 | let resp = client 1138 | .send( 1139 | RequestBuilder::new("/create1", Method::Put) 1140 | .data(Some(b"world".to_vec())) 1141 | .domain(domain.to_string()) 1142 | .build(), 1143 | ) 1144 | .await 1145 | .unwrap(); 1146 | assert_eq!(resp.message.payload, b"Created".to_vec()); 1147 | } 1148 | 1149 | #[tokio::test] 1150 | async fn test_delete_url() { 1151 | let resp = UdpCoAPClient::delete("coap://coap.me:5683/validate") 1152 | .await 1153 | .unwrap(); 1154 | assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); 1155 | let resp = UdpCoAPClient::delete("coap://coap.me:5683/validate") 1156 | .await 1157 | .unwrap(); 1158 | assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); 1159 | } 1160 | 1161 | #[tokio::test] 1162 | async fn test_delete() { 1163 | let domain = "coap.me"; 1164 | let client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); 1165 | let resp = client 1166 | .send( 1167 | RequestBuilder::new("/validate", Method::Delete) 1168 | .domain(domain.to_string()) 1169 | .build(), 1170 | ) 1171 | .await 1172 | .unwrap(); 1173 | assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); 1174 | } 1175 | 1176 | #[tokio::test] 1177 | async fn test_set_broadcast() { 1178 | let client = UdpCoAPClient::new_udp(("127.0.0.1", 5683)).await.unwrap(); 1179 | assert!(client.set_broadcast(true).is_ok()); 1180 | assert!(client.set_broadcast(false).is_ok()); 1181 | } 1182 | 1183 | #[tokio::test] 1184 | #[ignore] 1185 | async fn test_set_broadcast_v6() { 1186 | let client = UdpCoAPClient::new_udp(("::1", 5683)).await.unwrap(); 1187 | assert!(client.set_broadcast(true).is_ok()); 1188 | assert!(client.set_broadcast(false).is_ok()); 1189 | } 1190 | 1191 | #[tokio::test] 1192 | async fn test_send_all_coap() { 1193 | // prepare the Non-confirmable request with the broadcast message 1194 | let mut request: CoapRequest = CoapRequest::new(); 1195 | request.set_method(Method::Get); 1196 | request.set_path("/"); 1197 | request 1198 | .message 1199 | .header 1200 | .set_type(coap_lite::MessageType::NonConfirmable); 1201 | request.message.payload = b"Discovery".to_vec(); 1202 | 1203 | let client = UdpCoAPClient::new_udp(("127.0.0.1", 5683)).await.unwrap(); 1204 | client.send_all_coap(&mut request, 0).await.unwrap(); 1205 | } 1206 | #[tokio::test] 1207 | async fn test_change_block_option() { 1208 | // this test is a little finnicky because it relies on the configuration 1209 | // of the reception endpoint. It tries to send a payload larger than the 1210 | // default using a block option, this request is expected to fail because 1211 | // the endpoint does not support block requests. Afterwards, we change the 1212 | // maximum block size and thus expect the request to work. 1213 | const PAYLOAD_STR: &str = "this is a payload"; 1214 | let mut large_payload = vec![]; 1215 | while large_payload.len() < 1024 { 1216 | large_payload.extend_from_slice(PAYLOAD_STR.as_bytes()); 1217 | } 1218 | let domain = "coap.me"; 1219 | let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); 1220 | let resp = client 1221 | .send( 1222 | RequestBuilder::new("/large-create", Method::Put) 1223 | .domain(domain.to_string()) 1224 | .data(Some(large_payload.clone())) 1225 | .build(), 1226 | ) 1227 | .await; 1228 | let err = resp.unwrap_err(); 1229 | assert_eq!(err.kind(), ErrorKind::Unsupported); 1230 | //we now set the block size to make sure it is sent in a single request 1231 | client.set_block1_size(10_000_000); 1232 | 1233 | let resp = client 1234 | .send( 1235 | RequestBuilder::new("/large-create", Method::Post) 1236 | .data(Some(large_payload.clone())) 1237 | .domain(domain.to_string()) 1238 | .build(), 1239 | ) 1240 | .await 1241 | .unwrap(); 1242 | assert_eq!(*resp.get_status(), Status::Created); 1243 | } 1244 | #[tokio::test] 1245 | #[ignore] 1246 | async fn test_send_all_coap_v6() { 1247 | // prepare the Non-confirmable request with the broadcast message 1248 | let mut request: CoapRequest = CoapRequest::new(); 1249 | request.set_method(Method::Get); 1250 | request.set_path("/"); 1251 | request 1252 | .message 1253 | .header 1254 | .set_type(coap_lite::MessageType::NonConfirmable); 1255 | request.message.payload = b"Discovery".to_vec(); 1256 | 1257 | let client = UdpCoAPClient::new_udp(("::1", 5683)).await.unwrap(); 1258 | client.send_all_coap(&mut request, 0x4).await.unwrap(); 1259 | } 1260 | 1261 | struct FaultyUdp { 1262 | pub udp: UdpTransport, 1263 | pub num_fails: u32, 1264 | pub current_fails: AtomicU32, 1265 | } 1266 | 1267 | #[async_trait] 1268 | impl ClientTransport for FaultyUdp { 1269 | async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, Option)> { 1270 | self.udp.recv(buf).await 1271 | } 1272 | 1273 | async fn send(&self, buf: &[u8]) -> std::io::Result { 1274 | self.current_fails.fetch_add(1, Ordering::Relaxed); 1275 | self.current_fails 1276 | .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |n| { 1277 | Some(n % self.num_fails) 1278 | }) 1279 | .unwrap(); 1280 | if self.current_fails.load(Ordering::Relaxed) == 0 { 1281 | return self.udp.send(buf).await; 1282 | } 1283 | Err(Error::new(ErrorKind::Other, "fails this time")) 1284 | } 1285 | } 1286 | 1287 | async fn get_faulty_client(server_addr: &str, num_fails: u32) -> CoAPClient { 1288 | let peer_addr = lookup_host(server_addr) 1289 | .await 1290 | .unwrap() 1291 | .next() 1292 | .ok_or(Error::new( 1293 | ErrorKind::InvalidInput, 1294 | "could not get socket address", 1295 | )) 1296 | .unwrap(); 1297 | let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 1298 | let transport = UdpTransport { socket, peer_addr }; 1299 | let transport = FaultyUdp { 1300 | udp: transport, 1301 | num_fails, 1302 | current_fails: 0.into(), 1303 | }; 1304 | 1305 | return CoAPClient::from_transport(transport); 1306 | } 1307 | #[tokio::test] 1308 | async fn test_retries() { 1309 | let server_port = server::test::spawn_server("127.0.0.1:0", |mut req| async { 1310 | req.response.as_mut().unwrap().message.payload = b"Rust".to_vec(); 1311 | return req; 1312 | }) 1313 | .recv() 1314 | .await 1315 | .unwrap(); 1316 | 1317 | let server_addr = format!("127.0.0.1:{}", server_port); 1318 | let mut client = get_faulty_client( 1319 | &server_addr, 1320 | CoapClientTransport::::DEFAULT_NUM_RETRIES as u32 + 1, 1321 | ) 1322 | .await; 1323 | let request_gen = || { 1324 | RequestBuilder::new("/Rust", Method::Get) 1325 | .domain(server_addr.clone()) 1326 | .build() 1327 | }; 1328 | let error = client.send(request_gen()).await.unwrap_err(); 1329 | assert_eq!(error.kind(), ErrorKind::Other); 1330 | //this request will work, we do this to reset the state of the faulty udp 1331 | client.send(request_gen()).await.unwrap(); 1332 | 1333 | client.set_transport_retries(CoapClientTransport::::DEFAULT_NUM_RETRIES + 2); 1334 | let resp = client.send(request_gen()).await.unwrap(); 1335 | 1336 | assert_eq!(resp.message.payload, b"Rust".to_vec()); 1337 | } 1338 | #[tokio::test] 1339 | async fn test_non_confirmable_no_retries() { 1340 | let server_port = server::test::spawn_server("127.0.0.1:0", |mut req| async { 1341 | req.response.as_mut().unwrap().message.payload = b"Rust".to_vec(); 1342 | return req; 1343 | }) 1344 | .recv() 1345 | .await 1346 | .unwrap(); 1347 | 1348 | let server_addr = format!("127.0.0.1:{}", server_port); 1349 | let client = get_faulty_client(&server_addr, 2).await; 1350 | let mut request = CoapRequest::new(); 1351 | request.set_method(Method::Get); 1352 | request.set_path("/Rust"); 1353 | request.message.header.message_id = 123; 1354 | request.message.header.set_type(MessageType::NonConfirmable); 1355 | 1356 | let req = client.send(request).await; 1357 | assert!(req.is_err()); 1358 | } 1359 | 1360 | async fn do_wait_request( 1361 | client: Arc>, 1362 | path: &str, 1363 | token: Vec, 1364 | wait_ms: u64, 1365 | ) -> IoResult { 1366 | let mut request = CoapRequest::new(); 1367 | request.message.header.set_version(1); 1368 | request 1369 | .message 1370 | .header 1371 | .set_type(coap_lite::MessageType::Confirmable); 1372 | request.message.header.set_code("0.01"); 1373 | request.message.header.message_id = 1; 1374 | request.message.set_token(token); 1375 | request 1376 | .message 1377 | .add_option(CoapOption::UriPath, path.as_bytes().to_vec()); 1378 | request.message.payload = wait_ms.to_string().into(); 1379 | 1380 | return client.send(request).await; 1381 | } 1382 | 1383 | async fn wait_handler(mut req: Box>) -> Box> { 1384 | let uri_path_list = req.message.get_option(CoapOption::UriPath).unwrap().clone(); 1385 | let payload = str::from_utf8(&req.message.payload).unwrap(); 1386 | let to_wait_ms: u64 = payload.parse().unwrap(); 1387 | time::sleep(Duration::from_millis(to_wait_ms)).await; 1388 | 1389 | match req.response { 1390 | Some(ref mut response) => { 1391 | response.message.payload = uri_path_list.front().unwrap().clone(); 1392 | } 1393 | _ => {} 1394 | } 1395 | return req; 1396 | } 1397 | /// run 2 clients using the same transport and receive an answer 1398 | /// in the expected order without interference 1399 | #[tokio::test] 1400 | async fn test_multiple_clients_same_socket() { 1401 | let server_port = spawn_server("127.0.0.1:0", wait_handler) 1402 | .recv() 1403 | .await 1404 | .unwrap(); 1405 | 1406 | let client = Arc::new( 1407 | UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 1408 | .await 1409 | .unwrap(), 1410 | ); 1411 | let mut b = tokio::spawn(do_wait_request(client.clone(), "/bar", vec![1], 500)); 1412 | let a = tokio::spawn(do_wait_request(client.clone(), "/foo", vec![2], 50)); 1413 | 1414 | tokio::select! { 1415 | a_first = a => { 1416 | let a_first = a_first.unwrap().unwrap(); 1417 | assert_eq!(a_first.message.payload, b"/foo".to_vec()); 1418 | assert_eq!(a_first.message.get_token(), vec![2]); 1419 | }, 1420 | _b_first = &mut b => { 1421 | panic!("should not happen"); 1422 | 1423 | } 1424 | } 1425 | let b_end = b.await.unwrap().expect("should receive a response"); 1426 | assert_eq!(b_end.message.payload, b"/bar".to_vec()); 1427 | assert_eq!(b_end.message.get_token(), vec![1]); 1428 | } 1429 | 1430 | struct FaultyReceiver { 1431 | pub udp: UdpTransport, 1432 | pub should_fail: Mutex>, 1433 | } 1434 | #[async_trait] 1435 | impl ClientTransport for FaultyReceiver { 1436 | async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, Option)> { 1437 | let mut mutex = self.should_fail.lock().await; 1438 | tokio::select! { 1439 | e = mutex.deref_mut() => { 1440 | return Err(e.unwrap()); 1441 | } 1442 | result = self.udp.recv(buf) => { 1443 | return result; 1444 | } 1445 | } 1446 | } 1447 | 1448 | async fn send(&self, buf: &[u8]) -> std::io::Result { 1449 | self.udp.send(buf).await 1450 | } 1451 | } 1452 | 1453 | async fn get_faulty_receiver_client( 1454 | server_addr: &str, 1455 | ) -> (oneshot::Sender, CoAPClient) { 1456 | let (tx, rx) = oneshot::channel(); 1457 | let peer_addr = lookup_host(server_addr) 1458 | .await 1459 | .unwrap() 1460 | .next() 1461 | .ok_or(Error::new( 1462 | ErrorKind::InvalidInput, 1463 | "could not get socket address", 1464 | )) 1465 | .unwrap(); 1466 | let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 1467 | let transport = UdpTransport { socket, peer_addr }; 1468 | let transport = FaultyReceiver { 1469 | udp: transport, 1470 | should_fail: Mutex::new(rx), 1471 | }; 1472 | 1473 | return (tx, CoAPClient::from_transport(transport)); 1474 | } 1475 | #[tokio::test(flavor = "multi_thread")] 1476 | async fn test_synchronizer_receive_error() { 1477 | let server_port = server::test::spawn_server("127.0.0.1:0", wait_handler) 1478 | .recv() 1479 | .await 1480 | .unwrap(); 1481 | 1482 | let server_addr = format!("127.0.0.1:{}", server_port); 1483 | let (flag, client) = get_faulty_receiver_client(&server_addr).await; 1484 | let mut handles = vec![]; 1485 | let arc_client = Arc::new(client); 1486 | for i in 0..10 { 1487 | let c_clone = arc_client.clone(); 1488 | handles.push(tokio::spawn(async move { 1489 | do_wait_request(c_clone, &format!("/{}", i), vec![i], 2000).await 1490 | })); 1491 | } 1492 | //wait for all futures to advance 1493 | tokio::time::sleep(Duration::from_millis(200)).await; 1494 | flag.send(Error::new(ErrorKind::Other, "fail")).unwrap(); 1495 | 1496 | //all handles should fail now because of the error 1497 | for h in handles { 1498 | assert!(h.await.unwrap().is_err()); 1499 | } 1500 | 1501 | assert!( 1502 | do_wait_request(arc_client.clone(), "/foo", vec![254], 1) 1503 | .await 1504 | .is_err(), 1505 | "failed transport should make all other requests fail" 1506 | ) 1507 | } 1508 | } 1509 | -------------------------------------------------------------------------------- /src/dtls.rs: -------------------------------------------------------------------------------- 1 | //! this file is included by enabling the "dtls" feature. It provides a default DTLS backend using 2 | //! webrtc-rs's dtls implementation. 3 | use crate::client::ClientTransport; 4 | use crate::server::{Listener, Responder, TransportRequestSender}; 5 | use async_trait::async_trait; 6 | use std::net::SocketAddr; 7 | use std::time::Duration; 8 | use std::{ 9 | io::{Error, ErrorKind, Result as IoResult}, 10 | sync::Arc, 11 | }; 12 | use tokio::net::UdpSocket; 13 | use tokio::task::JoinHandle; 14 | use tokio::time::timeout; 15 | use webrtc_dtls::conn::DTLSConn; 16 | use webrtc_dtls::state::State; 17 | use webrtc_util::conn::{Conn, Listener as WebRtcListener}; 18 | 19 | #[async_trait] 20 | impl Listener for L { 21 | async fn listen( 22 | self: Box, 23 | sender: TransportRequestSender, 24 | ) -> IoResult>> { 25 | Ok(tokio::spawn(async move { 26 | loop { 27 | let res = self.accept().await; 28 | if let Ok((dtls_conn, remote_addr)) = res { 29 | tokio::spawn(spawn_webrtc_conn(dtls_conn, remote_addr, sender.clone())); 30 | } else { 31 | return Err(std::io::Error::new(ErrorKind::Other, "could not accept")); 32 | } 33 | } 34 | })) 35 | } 36 | } 37 | 38 | pub struct DtlsResponse { 39 | pub conn: Arc, 40 | pub remote_addr: SocketAddr, 41 | } 42 | 43 | #[async_trait] 44 | impl ClientTransport for DtlsConnection { 45 | async fn recv(&self, buf: &mut [u8]) -> IoResult<(usize, Option)> { 46 | let read = self 47 | .conn 48 | .read(buf, None) 49 | .await 50 | .map_err(|e| Error::new(ErrorKind::Other, e))?; 51 | return Ok((read, self.conn.remote_addr())); 52 | } 53 | 54 | async fn send(&self, buf: &[u8]) -> IoResult { 55 | self.conn 56 | .write(buf, None) 57 | .await 58 | .map_err(|e| Error::new(ErrorKind::Other, e)) 59 | } 60 | } 61 | #[async_trait] 62 | impl Responder for DtlsResponse { 63 | /// responds to a request by creating a new task. This ensures we do not 64 | /// block the main server handler task 65 | async fn respond(&self, response: Vec) { 66 | let self_clone = self.conn.clone(); 67 | tokio::spawn(async move { self_clone.send(&response).await }); 68 | } 69 | fn address(&self) -> SocketAddr { 70 | self.remote_addr 71 | } 72 | } 73 | 74 | pub async fn spawn_webrtc_conn( 75 | conn: Arc, 76 | remote_addr: SocketAddr, 77 | sender: TransportRequestSender, 78 | ) { 79 | const VECTOR_LENGTH: usize = 1600; 80 | loop { 81 | let mut vec_buf = Vec::with_capacity(VECTOR_LENGTH); 82 | unsafe { vec_buf.set_len(VECTOR_LENGTH) }; 83 | let Ok(rx) = conn.recv(&mut vec_buf).await else { 84 | break; 85 | }; 86 | if rx == 0 || rx > VECTOR_LENGTH { 87 | break; 88 | } 89 | unsafe { vec_buf.set_len(rx) } 90 | let response = Arc::new(DtlsResponse { 91 | conn: conn.clone(), 92 | remote_addr, 93 | }); 94 | let Ok(_) = sender.send((vec_buf, response)) else { 95 | break; 96 | }; 97 | } 98 | } 99 | #[async_trait] 100 | /// This trait is used to implement a hook that is called when a DTLS connection is dropped 101 | /// Only use this in case you need to save your connection 102 | pub trait DtlsDropHook: Send + Sync { 103 | async fn on_drop(&self, conn: Arc); 104 | } 105 | pub struct DtlsConnection { 106 | conn: Arc, 107 | on_drop: Option>, 108 | } 109 | 110 | impl DtlsConnection { 111 | /// Creates a new DTLS connection from a given connection. This connection can be 112 | /// a tokio UDP socket or a user-created struct implementing Conn, Send, and Sync 113 | /// 114 | /// 115 | /// # Errors 116 | /// 117 | /// This function will return an error if the handshake fails or if it times out 118 | pub async fn try_from_connection( 119 | connection: Arc, 120 | dtls_config: webrtc_dtls::config::Config, 121 | handshake_timeout: Duration, 122 | state: Option, 123 | on_drop: Option>, 124 | ) -> IoResult { 125 | let dtls_conn = timeout( 126 | handshake_timeout, 127 | DTLSConn::new(connection, dtls_config, true, state), 128 | ) 129 | .await 130 | .map_err(|_| { 131 | Error::new( 132 | ErrorKind::TimedOut, 133 | "Received no response on DTLS handshake", 134 | ) 135 | })? 136 | .map_err(|e| Error::new(ErrorKind::Other, e))?; 137 | return Ok(DtlsConnection { 138 | conn: Arc::new(dtls_conn), 139 | on_drop, 140 | }); 141 | } 142 | 143 | pub async fn try_new(dtls_config: UdpDtlsConfig) -> IoResult { 144 | let conn = UdpSocket::bind("0.0.0.0:0") 145 | .await 146 | .map_err(|e| Error::new(ErrorKind::Other, e))?; 147 | conn.connect(dtls_config.dest_addr).await?; 148 | return Self::try_from_connection( 149 | Arc::new(conn), 150 | dtls_config.config, 151 | Duration::new(30, 0), 152 | None, 153 | None, 154 | ) 155 | .await; 156 | } 157 | } 158 | pub struct UdpDtlsConfig { 159 | pub config: webrtc_dtls::config::Config, 160 | pub dest_addr: SocketAddr, 161 | } 162 | 163 | impl Drop for DtlsConnection { 164 | fn drop(&mut self) { 165 | if let Some(drop_hook) = self.on_drop.take() { 166 | println!("dropping"); 167 | //this is a nasty hack necessary to call async methods inside the drop method without 168 | //transferring ownership 169 | let conn = self.conn.clone(); 170 | tokio::spawn(async move { 171 | drop_hook.on_drop(conn).await; 172 | }); 173 | } 174 | } 175 | } 176 | 177 | #[cfg(test)] 178 | mod test { 179 | use super::*; 180 | use crate::client::CoAPClient; 181 | use crate::request::RequestBuilder; 182 | use crate::server::UdpCoapListener; 183 | use crate::{Server, UdpCoAPClient}; 184 | use coap_lite::{CoapOption, CoapRequest, RequestType as Method}; 185 | use futures::Future; 186 | use pkcs8::{LineEnding, SecretDocument}; 187 | use rcgen::KeyPair; 188 | use std::fs::File; 189 | use std::io::{BufReader, Read}; 190 | use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs}; 191 | use std::sync::atomic::AtomicBool; 192 | use tokio::sync::mpsc; 193 | use tokio::time::sleep; 194 | use webrtc_dtls::cipher_suite::CipherSuiteId; 195 | use webrtc_dtls::config::{ClientAuthType, Config, ExtendedMasterSecretType}; 196 | use webrtc_dtls::crypto::{Certificate, CryptoPrivateKey}; 197 | use webrtc_dtls::listener::listen; 198 | 199 | const SERVER_CERTIFICATE_PRIVATE_KEY: &'static str = "tests/test_certs/coap_server.pem"; 200 | const SERVER_CERTIFICATE: &'static str = "tests/test_certs/coap_server.pub.pem"; 201 | const CLIENT_CERTIFICATE_PRIVATE_KEY: &'static str = "tests/test_certs/coap_client.pem"; 202 | const CLIENT_CERTIFICATE: &'static str = "tests/test_certs/coap_client.pub.pem"; 203 | 204 | async fn request_handler( 205 | mut req: Box>, 206 | ) -> Box> { 207 | let uri_path_list = req.message.get_option(CoapOption::UriPath).unwrap().clone(); 208 | assert_eq!(uri_path_list.len(), 1); 209 | 210 | match req.response { 211 | Some(ref mut response) => { 212 | response.message.payload = uri_path_list.front().unwrap().clone(); 213 | } 214 | _ => {} 215 | } 216 | return req; 217 | } 218 | pub fn spawn_dtls_server< 219 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 220 | HandlerRet, 221 | >( 222 | ip: &'static str, 223 | request_handler: F, 224 | config: webrtc_dtls::config::Config, 225 | ) -> mpsc::UnboundedReceiver 226 | where 227 | HandlerRet: Future>> + Send, 228 | { 229 | let (tx, rx) = mpsc::unbounded_channel(); 230 | tokio::spawn(async move { 231 | let listener = listen(ip, config).await.unwrap(); 232 | let listen_port = listener.addr().await.unwrap().port(); 233 | let listener = Box::new(listener); 234 | let server = Server::from_listeners(vec![listener]); 235 | tx.send(listen_port).unwrap(); 236 | server.run(request_handler).await.unwrap(); 237 | }); 238 | 239 | rx 240 | } 241 | 242 | pub fn get_certificate(name: &str) -> rustls::Certificate { 243 | let mut f = File::open(name).unwrap(); 244 | let mut reader = BufReader::new(&mut f); 245 | let mut cert_iter = rustls_pemfile::certs(&mut reader); 246 | let cert = cert_iter 247 | .next() 248 | .unwrap() 249 | .expect("could not get certificate"); 250 | assert!( 251 | cert_iter.next().is_none(), 252 | "there should only be 1 certificate in this file" 253 | ); 254 | return rustls::Certificate(cert.to_vec()); 255 | } 256 | 257 | pub fn server_certificate() -> rustls::Certificate { 258 | return get_certificate(SERVER_CERTIFICATE); 259 | } 260 | 261 | pub fn client_certificate() -> rustls::Certificate { 262 | return get_certificate(CLIENT_CERTIFICATE); 263 | } 264 | pub fn convert_to_pkcs8(s: &str) -> String { 265 | let pkdoc: SecretDocument = 266 | sec1::DecodeEcPrivateKey::from_sec1_pem(s).expect("could not parse ec key"); 267 | 268 | let pkcs8_pem = pkdoc 269 | .to_pem("PRIVATE_KEY", LineEnding::LF) 270 | .expect("could not encode ec key to PEM"); 271 | return pkcs8_pem.to_string(); 272 | } 273 | 274 | pub fn get_private_key(name: &str) -> CryptoPrivateKey { 275 | let f = File::open(name).unwrap(); 276 | let mut reader = BufReader::new(f); 277 | let mut buf = vec![]; 278 | reader.read_to_end(&mut buf).unwrap(); 279 | let s = String::from_utf8(buf).expect("utf8 of file"); 280 | // convert key to pkcs8 281 | let s = convert_to_pkcs8(&s); 282 | 283 | let key_pair = KeyPair::from_pem(s.as_str()).expect("key pair in file"); 284 | CryptoPrivateKey::from_key_pair(&key_pair).expect("crypto key pair") 285 | } 286 | 287 | pub fn server_key() -> CryptoPrivateKey { 288 | return get_private_key(SERVER_CERTIFICATE_PRIVATE_KEY); 289 | } 290 | 291 | pub fn client_key() -> CryptoPrivateKey { 292 | return get_private_key(CLIENT_CERTIFICATE_PRIVATE_KEY); 293 | } 294 | 295 | pub fn get_psk_config() -> Config { 296 | Config { 297 | psk: Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))), 298 | psk_identity_hint: Some("webrtc-rs DTLS Server".as_bytes().to_vec()), 299 | cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], 300 | server_name: "localhost".to_string(), 301 | ..Default::default() 302 | } 303 | } 304 | 305 | #[tokio::test] 306 | async fn test_dtls_pki() { 307 | let server_cfg = { 308 | let mut server_cert_pool = rustls::RootCertStore::empty(); 309 | let server_cert = server_certificate(); 310 | server_cert_pool 311 | .add(&server_cert) 312 | .expect("could not add certificate"); 313 | 314 | let server_private_key = server_key(); 315 | let certificate = Certificate { 316 | certificate: vec![server_cert], 317 | private_key: server_private_key, 318 | }; 319 | 320 | Config { 321 | certificates: vec![certificate], 322 | extended_master_secret: ExtendedMasterSecretType::Require, 323 | client_auth: ClientAuthType::RequireAndVerifyClientCert, //RequireAnyClientCert, // 324 | client_cas: server_cert_pool, 325 | ..Default::default() 326 | } 327 | }; 328 | 329 | let client_cfg = { 330 | let mut client_cert_pool = rustls::RootCertStore::empty(); 331 | let client_cert = client_certificate(); 332 | let server_cert = server_certificate(); 333 | client_cert_pool 334 | .add(&server_cert) 335 | .expect("could not add certificate"); 336 | 337 | let client_private_key = client_key(); 338 | let certificate = Certificate { 339 | certificate: vec![client_cert], 340 | private_key: client_private_key, 341 | }; 342 | 343 | Config { 344 | certificates: vec![certificate], 345 | extended_master_secret: ExtendedMasterSecretType::Require, 346 | roots_cas: client_cert_pool, 347 | server_name: "coap.rs".to_owned(), 348 | ..Default::default() 349 | } 350 | }; 351 | 352 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, server_cfg.clone()) 353 | .recv() 354 | .await 355 | .unwrap(); 356 | 357 | let dtls_config = UdpDtlsConfig { 358 | config: client_cfg, 359 | dest_addr: ("127.0.0.1", server_port) 360 | .to_socket_addrs() 361 | .unwrap() 362 | .next() 363 | .unwrap(), 364 | }; 365 | 366 | let client = CoAPClient::from_udp_dtls_config(dtls_config) 367 | .await 368 | .expect("could not create client"); 369 | let domain = format!("127.0.0.1:{}", server_port); 370 | let resp = client 371 | .send( 372 | RequestBuilder::request_path( 373 | "/hello", 374 | Method::Get, 375 | None, 376 | None, 377 | Some(domain.to_string()), 378 | ) 379 | .build(), 380 | ) 381 | .await 382 | .unwrap(); 383 | assert_eq!(resp.message.payload, b"hello".to_vec()); 384 | } 385 | #[tokio::test] 386 | async fn test_dtls_psk() { 387 | let config = get_psk_config(); 388 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) 389 | .recv() 390 | .await 391 | .unwrap(); 392 | 393 | let dtls_config = UdpDtlsConfig { 394 | config, 395 | dest_addr: ("127.0.0.1", server_port) 396 | .to_socket_addrs() 397 | .unwrap() 398 | .next() 399 | .unwrap(), 400 | }; 401 | 402 | let client = CoAPClient::from_udp_dtls_config(dtls_config) 403 | .await 404 | .expect("could not create client"); 405 | let domain = format!("127.0.0.1:{}", server_port); 406 | let resp = client 407 | .send( 408 | RequestBuilder::request_path( 409 | "/hello", 410 | Method::Get, 411 | None, 412 | None, 413 | Some(domain.to_string()), 414 | ) 415 | .build(), 416 | ) 417 | .await 418 | .unwrap(); 419 | assert_eq!(resp.message.payload, b"hello".to_vec()); 420 | } 421 | #[tokio::test] 422 | async fn test_dtls_false_psk() { 423 | let mut config = get_psk_config(); 424 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) 425 | .recv() 426 | .await 427 | .unwrap(); 428 | // make the psk fail 429 | config.psk = Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 9]))); 430 | 431 | let dtls_config = UdpDtlsConfig { 432 | config, 433 | dest_addr: ("127.0.0.1", server_port) 434 | .to_socket_addrs() 435 | .unwrap() 436 | .next() 437 | .unwrap(), 438 | }; 439 | assert!( 440 | CoAPClient::from_udp_dtls_config(dtls_config).await.is_err(), 441 | "should not have connected" 442 | ); 443 | } 444 | 445 | #[tokio::test] 446 | async fn test_wrong_protocol() { 447 | let config = get_psk_config(); 448 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) 449 | .recv() 450 | .await 451 | .unwrap(); 452 | // make the psk fail 453 | 454 | let get = UdpCoAPClient::get_with_timeout( 455 | &format!("coap://127.0.0.1:{}/hello", server_port), 456 | Duration::from_millis(100), 457 | ) 458 | .await; 459 | let get_error = get.unwrap_err(); 460 | assert!(get_error.kind() == ErrorKind::TimedOut); 461 | 462 | let dtls_config = UdpDtlsConfig { 463 | config, 464 | dest_addr: ("127.0.0.1", server_port) 465 | .to_socket_addrs() 466 | .unwrap() 467 | .next() 468 | .unwrap(), 469 | }; 470 | 471 | let client = CoAPClient::from_udp_dtls_config(dtls_config) 472 | .await 473 | .expect("could not create client"); 474 | let domain = format!("127.0.0.1:{}", server_port); 475 | let resp = client 476 | .send( 477 | RequestBuilder::request_path( 478 | "/hello", 479 | Method::Get, 480 | None, 481 | None, 482 | Some(domain.to_string()), 483 | ) 484 | .build(), 485 | ) 486 | .await 487 | .unwrap(); 488 | assert_eq!(resp.message.payload, b"hello".to_vec()); 489 | } 490 | 491 | #[tokio::test] 492 | async fn test_multiple_listeners() { 493 | let config = get_psk_config(); 494 | // spawn a server with 2 listeners 495 | let (tx, mut rx) = mpsc::unbounded_channel(); 496 | tokio::spawn(async move { 497 | let config = get_psk_config(); 498 | let listener_dtls = listen("127.0.0.1:0", config).await.unwrap(); 499 | let port_dtls = listener_dtls.addr().await.unwrap().port(); 500 | let listener_dtls = Box::new(listener_dtls); 501 | 502 | let sock_udp = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 503 | let port_udp = sock_udp.local_addr().unwrap().port(); 504 | let listener_udp = Box::new(UdpCoapListener::from_socket(sock_udp)); 505 | 506 | let server = Server::from_listeners(vec![listener_dtls, listener_udp]); 507 | tx.send((port_udp, port_dtls)).unwrap(); 508 | server.run(request_handler).await.unwrap(); 509 | }); 510 | 511 | let (udp, dtls) = rx.recv().await.unwrap(); 512 | 513 | let get = UdpCoAPClient::get(&format!("coap://127.0.0.1:{}/hello_udp", udp)) 514 | .await 515 | .unwrap(); 516 | assert_eq!(get.message.payload, b"hello_udp".to_vec()); 517 | 518 | let dtls_config = UdpDtlsConfig { 519 | config, 520 | dest_addr: ("127.0.0.1", dtls) 521 | .to_socket_addrs() 522 | .unwrap() 523 | .next() 524 | .unwrap(), 525 | }; 526 | 527 | let client = CoAPClient::from_udp_dtls_config(dtls_config) 528 | .await 529 | .expect("could not create client"); 530 | let domain = format!("127.0.0.1:{}", dtls); 531 | let resp = client 532 | .send( 533 | RequestBuilder::request_path( 534 | "/hello_dtls", 535 | Method::Get, 536 | None, 537 | None, 538 | Some(domain.to_string()), 539 | ) 540 | .build(), 541 | ) 542 | .await 543 | .unwrap(); 544 | assert_eq!(resp.message.payload, b"hello_dtls".to_vec()); 545 | } 546 | 547 | struct OnDropFlag(pub Arc); 548 | 549 | #[async_trait] 550 | impl DtlsDropHook for OnDropFlag { 551 | async fn on_drop(&self, conn: Arc) { 552 | let _state = conn.connection_state().await; 553 | self.0.store(true, std::sync::atomic::Ordering::Relaxed); 554 | } 555 | } 556 | 557 | #[tokio::test] 558 | async fn test_drop_hook() { 559 | let config = get_psk_config(); 560 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) 561 | .recv() 562 | .await 563 | .unwrap(); 564 | let dtls_config = UdpDtlsConfig { 565 | config, 566 | dest_addr: ("127.0.0.1", server_port) 567 | .to_socket_addrs() 568 | .unwrap() 569 | .next() 570 | .unwrap(), 571 | }; 572 | 573 | let flag = Arc::new(AtomicBool::new(false)); 574 | { 575 | let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); 576 | socket.connect(dtls_config.dest_addr).await.unwrap(); 577 | let on_drop = OnDropFlag(flag.clone()); 578 | 579 | let transport = DtlsConnection::try_from_connection( 580 | socket, 581 | dtls_config.config, 582 | Duration::from_secs(1), 583 | None, 584 | Some(Box::new(on_drop)), 585 | ) 586 | .await 587 | .expect("could not create client"); 588 | let client = CoAPClient::from_transport(transport); 589 | let domain = format!("127.0.0.1:{}", server_port); 590 | let resp = client 591 | .send( 592 | RequestBuilder::request_path( 593 | "/hello", 594 | Method::Get, 595 | None, 596 | None, 597 | Some(domain.to_string()), 598 | ) 599 | .build(), 600 | ) 601 | .await 602 | .unwrap(); 603 | assert_eq!(resp.message.payload, b"hello".to_vec()); 604 | drop(client); 605 | } 606 | sleep(Duration::from_millis(500)).await; 607 | assert!( 608 | flag.load(std::sync::atomic::Ordering::Relaxed), 609 | "flag not called" 610 | ); 611 | } 612 | 613 | struct SocketWrapper(pub UdpSocket); 614 | 615 | type WebrtcResult = std::result::Result; 616 | #[async_trait] 617 | impl Conn for SocketWrapper { 618 | async fn connect(&self, addr: SocketAddr) -> WebrtcResult<()> { 619 | let _ = self.0.connect(addr).await; 620 | Ok(()) 621 | } 622 | async fn recv(&self, buf: &mut [u8]) -> WebrtcResult { 623 | Ok(self.0.recv(buf).await?) 624 | } 625 | async fn recv_from(&self, _buf: &mut [u8]) -> WebrtcResult<(usize, SocketAddr)> { 626 | todo!("not needed") 627 | } 628 | async fn send(&self, buf: &[u8]) -> WebrtcResult { 629 | Ok(self.0.send(buf).await?) 630 | } 631 | async fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> WebrtcResult { 632 | todo!("not needed"); 633 | } 634 | fn local_addr(&self) -> WebrtcResult { 635 | todo!("not needed"); 636 | } 637 | fn remote_addr(&self) -> Option { 638 | Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0)) 639 | } 640 | async fn close(&self) -> WebrtcResult<()> { 641 | Ok(self.0.close().await?) 642 | } 643 | } 644 | #[tokio::test] 645 | async fn test_own_connection() { 646 | let config = get_psk_config(); 647 | let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) 648 | .recv() 649 | .await 650 | .unwrap(); 651 | let dtls_config = UdpDtlsConfig { 652 | config, 653 | dest_addr: ("127.0.0.1", server_port) 654 | .to_socket_addrs() 655 | .unwrap() 656 | .next() 657 | .unwrap(), 658 | }; 659 | let socket = Arc::new(SocketWrapper(UdpSocket::bind("0.0.0.0:0").await.unwrap())); 660 | socket.connect(dtls_config.dest_addr).await.unwrap(); 661 | 662 | let transport = DtlsConnection::try_from_connection( 663 | socket, 664 | dtls_config.config, 665 | Duration::from_secs(1), 666 | None, 667 | None, 668 | ) 669 | .await 670 | .expect("could not create client"); 671 | let client = CoAPClient::from_transport(transport); 672 | let domain = format!("127.0.0.1:{}", server_port); 673 | let resp = client 674 | .send( 675 | RequestBuilder::request_path( 676 | "/hello", 677 | Method::Get, 678 | None, 679 | None, 680 | Some(domain.to_string()), 681 | ) 682 | .build(), 683 | ) 684 | .await 685 | .unwrap(); 686 | assert_eq!(resp.message.payload, b"hello".to_vec()); 687 | drop(client); 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the [CoAP Protocol][spec]. 2 | //! 3 | //! This library provides both a client interface (`CoAPClient`) 4 | //! and a server interface (`CoAPServer`). 5 | //! 6 | //! Features: 7 | //! - CoAP core protocol [RFC 7252](https://tools.ietf.org/rfc/rfc7252.txt) 8 | //! - CoAP Observe option [RFC 7641](https://tools.ietf.org/rfc/rfc7641.txt) 9 | //! - *Too Many Requests* Response Code [RFC 8516](https://tools.ietf.org/html/rfc8516) 10 | //! - Block-Wise Transfers [RFC 7959](https://tools.ietf.org/html/rfc7959) 11 | //! - DTLS support via [webrtc-rs](https://github.com/webrtc-rs/webrtc) 12 | //! - Option to provide custom transports for client and server 13 | //! - Client can perform multiple concurrent requests, like observing and sending requests using 14 | //! the same underlying socket 15 | //! 16 | //! 17 | //! # Installation 18 | //! 19 | //! First add this to your `Cargo.toml`: 20 | //! 21 | //! ```toml 22 | //! [dependencies] 23 | //! coap = "0.21" 24 | //! coap-lite = "0.13.1" 25 | //! tokio = {version = "^1.32", features = ["full"]} 26 | //! ``` 27 | //! 28 | //! Then, add this to your crate root: 29 | //! 30 | //! ``` 31 | //! extern crate coap; 32 | //! ``` 33 | //! 34 | //! # Example 35 | //! 36 | //! ## Server: 37 | //! ```no_run 38 | //! extern crate coap; 39 | //! 40 | //! use coap_lite::{RequestType as Method, CoapRequest}; 41 | //! use coap::Server; 42 | //! use tokio::runtime::Runtime; 43 | //! use std::net::SocketAddr; 44 | //! fn main() { 45 | //! let addr = "127.0.0.1:5683"; 46 | //! Runtime::new().unwrap().block_on(async move { 47 | //! let mut server = Server::new_udp(addr).unwrap(); 48 | //! println!("Server up on {}", addr); 49 | //! 50 | //! server.run(|mut request: Box>| async { 51 | //! match request.get_method() { 52 | //! &Method::Get => println!("request by get {}", request.get_path()), 53 | //! &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload.clone()).unwrap()), 54 | //! &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload.clone()).unwrap()), 55 | //! _ => println!("request by other method"), 56 | //! }; 57 | 58 | //! match request.response { 59 | //! Some(ref mut message) => { 60 | //! message.message.payload = b"OK".to_vec(); 61 | 62 | //! }, 63 | //! _ => {} 64 | //! }; 65 | //! return request 66 | //! }).await.unwrap(); 67 | //! }); 68 | //! } 69 | //! ``` 70 | //! 71 | //! ## Client: 72 | //! ```no_run 73 | //! extern crate coap; 74 | //! 75 | //! use coap_lite::{RequestType as Method, CoapRequest}; 76 | //! use coap::{UdpCoAPClient}; 77 | //! use tokio::main; 78 | //! #[tokio::main] 79 | //! async fn main() { 80 | //! let url = "coap://127.0.0.1:5683/Rust"; 81 | //! println!("Client request: {}", url); 82 | //! 83 | //! let response = UdpCoAPClient::get(url).await.unwrap(); 84 | //! println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); 85 | //! } 86 | 87 | #[macro_use] 88 | extern crate alloc; 89 | 90 | #[cfg(test)] 91 | extern crate quickcheck; 92 | 93 | pub use self::client::UdpCoAPClient; 94 | pub use self::observer::Observer; 95 | pub use self::server::Server; 96 | pub mod client; 97 | #[cfg(feature = "dtls")] 98 | pub mod dtls; 99 | mod observer; 100 | pub mod request; 101 | pub mod server; 102 | -------------------------------------------------------------------------------- /src/observer.rs: -------------------------------------------------------------------------------- 1 | use coap_lite::{ 2 | CoapRequest, MessageClass, MessageType, ObserveOption, Packet, RequestType as Method, 3 | ResponseType as Status, 4 | }; 5 | use futures::{ 6 | stream::{Fuse, SelectNextSome}, 7 | StreamExt, 8 | }; 9 | use log::{debug, warn}; 10 | use std::{ 11 | collections::{hash_map::Entry, HashMap, HashSet}, 12 | net::SocketAddr, 13 | sync::Arc, 14 | time::Duration, 15 | }; 16 | use tokio::time::interval; 17 | use tokio_stream::wrappers::IntervalStream; 18 | 19 | use crate::server::Responder; 20 | 21 | const DEFAULT_UNACKNOWLEDGE_MESSAGE_TRY_TIMES: usize = 10; 22 | 23 | pub struct Observer { 24 | registers: HashMap, 25 | resources: HashMap, 26 | register_resources: HashMap, 27 | unacknowledge_messages: HashMap, 28 | current_message_id: u16, 29 | timer: Fuse, 30 | } 31 | 32 | #[derive(Debug)] 33 | struct RegisterItem { 34 | register_resources: HashSet, 35 | } 36 | 37 | #[derive(Debug)] 38 | struct ResourceItem { 39 | payload: Vec, 40 | register_resources: HashSet, 41 | sequence: u32, 42 | } 43 | 44 | struct RegisterResourceItem { 45 | pub(crate) registered_responder: Arc, 46 | pub(crate) resource: String, 47 | pub(crate) token: Vec, 48 | pub(crate) unacknowledge_message: Option, 49 | } 50 | 51 | #[derive(Debug)] 52 | struct UnacknowledgeMessageItem { 53 | register_resource: String, 54 | try_times: usize, 55 | } 56 | 57 | impl Observer { 58 | /// Creates an observer with channel to send message. 59 | pub fn new() -> Self { 60 | Self { 61 | registers: HashMap::new(), 62 | resources: HashMap::new(), 63 | register_resources: HashMap::new(), 64 | unacknowledge_messages: HashMap::new(), 65 | current_message_id: 0, 66 | timer: IntervalStream::new(interval(Duration::from_secs(1))).fuse(), 67 | } 68 | } 69 | 70 | /// poll the observer's timer. 71 | pub fn select_next_some(&mut self) -> SelectNextSome> { 72 | self.timer.select_next_some() 73 | } 74 | 75 | /// filter the requests belong to the observer. store the responder in case it is needed 76 | /// returns whether the request should be forwarded to the handler 77 | pub async fn request_handler( 78 | &mut self, 79 | request: &mut CoapRequest, 80 | responder: Arc, 81 | ) -> bool { 82 | if request.message.header.get_type() == MessageType::Acknowledgement { 83 | self.acknowledge(request); 84 | return false; 85 | } 86 | 87 | match (request.get_method(), request.get_observe_flag()) { 88 | (&Method::Get, Some(observe_option)) => match observe_option { 89 | Ok(ObserveOption::Register) => { 90 | self.register(request, responder).await; 91 | return false; 92 | } 93 | Ok(ObserveOption::Deregister) => { 94 | self.deregister(request); 95 | return true; 96 | } 97 | _ => return true, 98 | }, 99 | (&Method::Put, _) => { 100 | self.resource_changed(request).await; 101 | return true; 102 | } 103 | _ => return true, 104 | } 105 | } 106 | 107 | /// trigger send the unacknowledge messages. 108 | pub async fn timer_handler(&mut self) { 109 | let register_resource_keys: Vec; 110 | { 111 | register_resource_keys = self 112 | .unacknowledge_messages 113 | .iter() 114 | .map(|(_, msg)| msg.register_resource.clone()) 115 | .collect(); 116 | } 117 | 118 | for register_resource_key in register_resource_keys { 119 | if self.try_unacknowledge_message(®ister_resource_key) { 120 | self.notify_register_with_newest_resource(®ister_resource_key) 121 | .await; 122 | } 123 | } 124 | } 125 | 126 | async fn register( 127 | &mut self, 128 | request: &mut CoapRequest, 129 | responder: Arc, 130 | ) { 131 | let register_address = responder.address(); 132 | let resource_path = request.get_path(); 133 | 134 | debug!("register {} {}", register_address, resource_path); 135 | 136 | // reply NotFound if resource doesn't exist 137 | if !self.resources.contains_key(&resource_path) { 138 | if let Some(ref response) = request.response.take() { 139 | let mut response2 = response.clone(); 140 | response2.set_status(Status::NotFound); 141 | let msg_serial = response2.message.to_bytes(); 142 | if let Ok(b) = msg_serial { 143 | responder.respond(b).await; 144 | } 145 | } 146 | return; 147 | } 148 | 149 | self.record_register_resource( 150 | responder.clone(), 151 | &resource_path, 152 | &request.message.get_token(), 153 | ); 154 | 155 | let resource = self.resources.get(&resource_path).unwrap(); 156 | 157 | if let Some(response) = request.response.take() { 158 | let mut response2 = response.clone(); 159 | response2.message.payload = resource.payload.clone(); 160 | response2.message.set_observe_value(resource.sequence); 161 | response2 162 | .message 163 | .header 164 | .set_type(MessageType::NonConfirmable); 165 | if let Ok(b) = response2.message.to_bytes() { 166 | responder.respond(b).await; 167 | } 168 | } 169 | } 170 | 171 | fn deregister(&mut self, request: &CoapRequest) { 172 | let register_address = request.source.unwrap(); 173 | let resource_path = request.get_path(); 174 | 175 | debug!("deregister {} {}", register_address, resource_path); 176 | 177 | self.remove_register_resource( 178 | ®ister_address, 179 | &resource_path, 180 | &request.message.get_token(), 181 | ); 182 | } 183 | 184 | async fn resource_changed(&mut self, request: &CoapRequest) { 185 | let resource_path = request.get_path(); 186 | let ref resource_payload = request.message.payload; 187 | 188 | debug!("resource_changed {} {:?}", resource_path, resource_payload); 189 | 190 | let register_resource_keys: Vec; 191 | { 192 | let resource = self.record_resource(&resource_path, &resource_payload); 193 | register_resource_keys = resource 194 | .register_resources 195 | .iter() 196 | .map(|k| k.clone()) 197 | .collect(); 198 | } 199 | 200 | for register_resource_key in register_resource_keys { 201 | self.gen_message_id(); 202 | self.notify_register_with_newest_resource(®ister_resource_key) 203 | .await; 204 | self.record_unacknowledge_message(®ister_resource_key); 205 | } 206 | } 207 | 208 | fn acknowledge(&mut self, request: &CoapRequest) { 209 | self.remove_unacknowledge_message( 210 | &request.message.header.message_id, 211 | &request.message.get_token(), 212 | ); 213 | } 214 | 215 | fn record_register_resource( 216 | &mut self, 217 | responder: Arc, 218 | path: &String, 219 | token: &[u8], 220 | ) { 221 | let resource = self.resources.get_mut(path).unwrap(); 222 | let register_key = responder; 223 | 224 | let register_resource_key = Self::format_register_resource(®ister_key.address(), path); 225 | 226 | self.register_resources 227 | .entry(register_resource_key.clone()) 228 | .or_insert(RegisterResourceItem { 229 | registered_responder: register_key.clone(), 230 | resource: path.clone(), 231 | token: token.into(), 232 | unacknowledge_message: None, 233 | }); 234 | resource 235 | .register_resources 236 | .replace(register_resource_key.clone()); 237 | match self.registers.entry(register_key.address().to_string()) { 238 | Entry::Occupied(register) => { 239 | register 240 | .into_mut() 241 | .register_resources 242 | .replace(register_resource_key); 243 | } 244 | Entry::Vacant(v) => { 245 | let mut register = RegisterItem { 246 | register_resources: HashSet::new(), 247 | }; 248 | register.register_resources.insert(register_resource_key); 249 | 250 | v.insert(register); 251 | } 252 | }; 253 | } 254 | 255 | fn remove_register_resource( 256 | &mut self, 257 | address: &SocketAddr, 258 | path: &String, 259 | token: &[u8], 260 | ) -> bool { 261 | let register_resource_key = Self::format_register_resource(&address, path); 262 | 263 | if let Some(register_resource) = self.register_resources.get(®ister_resource_key) { 264 | if register_resource.token != *token { 265 | return false; 266 | } 267 | 268 | if let Some(unacknowledge_message) = register_resource.unacknowledge_message { 269 | self.unacknowledge_messages 270 | .remove(&unacknowledge_message) 271 | .unwrap(); 272 | } 273 | 274 | assert_eq!( 275 | self.resources 276 | .get_mut(path) 277 | .unwrap() 278 | .register_resources 279 | .remove(®ister_resource_key), 280 | true 281 | ); 282 | 283 | let remove_register; 284 | { 285 | let register = self 286 | .registers 287 | .get_mut(®ister_resource.registered_responder.address().to_string()) 288 | .unwrap(); 289 | assert_eq!( 290 | register.register_resources.remove(®ister_resource_key), 291 | true 292 | ); 293 | remove_register = register.register_resources.len() == 0; 294 | } 295 | 296 | if remove_register { 297 | self.registers 298 | .remove(®ister_resource.registered_responder.address().to_string()); 299 | } 300 | } 301 | 302 | self.register_resources.remove(®ister_resource_key); 303 | return true; 304 | } 305 | 306 | fn record_resource(&mut self, path: &String, payload: &Vec) -> &ResourceItem { 307 | match self.resources.entry(path.clone()) { 308 | Entry::Occupied(resource) => { 309 | let r = resource.into_mut(); 310 | r.sequence += 1; 311 | r.payload = payload.clone(); 312 | return r; 313 | } 314 | Entry::Vacant(v) => { 315 | return v.insert(ResourceItem { 316 | payload: payload.clone(), 317 | register_resources: HashSet::new(), 318 | sequence: 0, 319 | }); 320 | } 321 | } 322 | } 323 | 324 | fn record_unacknowledge_message(&mut self, register_resource_key: &String) { 325 | let message_id = self.current_message_id; 326 | 327 | let register_resource = self 328 | .register_resources 329 | .get_mut(register_resource_key) 330 | .unwrap(); 331 | if let Some(old_message_id) = register_resource.unacknowledge_message { 332 | self.unacknowledge_messages.remove(&old_message_id); 333 | } 334 | 335 | register_resource.unacknowledge_message = Some(message_id); 336 | self.unacknowledge_messages.insert( 337 | message_id, 338 | UnacknowledgeMessageItem { 339 | register_resource: register_resource_key.clone(), 340 | try_times: 1, 341 | }, 342 | ); 343 | } 344 | 345 | fn try_unacknowledge_message(&mut self, register_resource_key: &String) -> bool { 346 | let register_resource = self 347 | .register_resources 348 | .get_mut(register_resource_key) 349 | .unwrap(); 350 | let ref message_id = register_resource.unacknowledge_message.unwrap(); 351 | 352 | let try_again; 353 | { 354 | let unacknowledge_message = self.unacknowledge_messages.get_mut(message_id).unwrap(); 355 | if unacknowledge_message.try_times > DEFAULT_UNACKNOWLEDGE_MESSAGE_TRY_TIMES { 356 | try_again = false; 357 | } else { 358 | unacknowledge_message.try_times += 1; 359 | try_again = true; 360 | } 361 | } 362 | 363 | if !try_again { 364 | warn!( 365 | "unacknowledge_message try times exceeded {}", 366 | register_resource_key 367 | ); 368 | 369 | register_resource.unacknowledge_message = None; 370 | self.unacknowledge_messages.remove(message_id); 371 | } 372 | 373 | return try_again; 374 | } 375 | 376 | fn remove_unacknowledge_message(&mut self, message_id: &u16, token: &[u8]) { 377 | if let Some(message) = self.unacknowledge_messages.get_mut(message_id) { 378 | let register_resource = self 379 | .register_resources 380 | .get_mut(&message.register_resource) 381 | .unwrap(); 382 | if register_resource.token != *token { 383 | return; 384 | } 385 | 386 | register_resource.unacknowledge_message = None; 387 | } 388 | 389 | self.unacknowledge_messages.remove(message_id); 390 | } 391 | 392 | async fn notify_register_with_newest_resource(&mut self, register_resource_key: &String) { 393 | let message_id = self.current_message_id; 394 | 395 | debug!("notify {} {}", register_resource_key, message_id); 396 | 397 | let ref mut message = Packet::new(); 398 | message.header.set_type(MessageType::Confirmable); 399 | message.header.code = MessageClass::Response(Status::Content); 400 | 401 | let register_resource = self.register_resources.get(register_resource_key).unwrap(); 402 | let resource = self.resources.get(®ister_resource.resource).unwrap(); 403 | 404 | message.set_token(register_resource.token.clone()); 405 | message.set_observe_value(resource.sequence); 406 | message.header.message_id = message_id; 407 | message.payload = resource.payload.clone(); 408 | if let Ok(b) = message.to_bytes() { 409 | debug!("notify register with newest resource {:?}", &b); 410 | register_resource.registered_responder.respond(b).await; 411 | } 412 | } 413 | 414 | fn gen_message_id(&mut self) -> u16 { 415 | self.current_message_id += 1; 416 | return self.current_message_id; 417 | } 418 | 419 | fn format_register_resource(address: &SocketAddr, path: &String) -> String { 420 | format!("{}${}", address, path) 421 | } 422 | } 423 | 424 | #[cfg(test)] 425 | mod test { 426 | 427 | use crate::request::RequestBuilder; 428 | 429 | use super::super::*; 430 | use super::*; 431 | use std::io::ErrorKind; 432 | use tokio::sync::mpsc; 433 | 434 | async fn request_handler( 435 | mut req: Box>, 436 | ) -> Box> { 437 | match req.get_method() { 438 | &coap_lite::RequestType::Get => { 439 | let observe_option = req.get_observe_flag().unwrap().unwrap(); 440 | assert_eq!(observe_option, ObserveOption::Deregister); 441 | } 442 | &coap_lite::RequestType::Put => {} 443 | _ => panic!("unexpected request"), 444 | } 445 | 446 | match req.response { 447 | Some(ref mut response) => { 448 | response.message.payload = b"OK".to_vec(); 449 | } 450 | _ => {} 451 | }; 452 | return req; 453 | } 454 | 455 | #[tokio::test] 456 | async fn test_observe() { 457 | let path = "/test"; 458 | let payload1 = b"data1".to_vec(); 459 | let payload2 = b"data2".to_vec(); 460 | let (tx, mut rx) = mpsc::unbounded_channel(); 461 | let (tx2, mut rx2) = mpsc::unbounded_channel(); 462 | let mut step = 1; 463 | 464 | let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) 465 | .recv() 466 | .await 467 | .unwrap(); 468 | 469 | let server_address = &format!("127.0.0.1:{}", server_port); 470 | 471 | let client = UdpCoAPClient::new_udp(server_address).await.unwrap(); 472 | 473 | tx.send(step).unwrap(); 474 | let mut request = CoapRequest::new(); 475 | 476 | request.set_method(coap_lite::RequestType::Put); 477 | request.set_path(path); 478 | request.message.set_token(vec![1]); 479 | 480 | request.message.payload = payload1.clone(); 481 | let _ = client.send(request.clone()).await.unwrap(); 482 | 483 | let payload1_clone = payload1.clone(); 484 | let payload2_clone = payload2.clone(); 485 | 486 | let client2 = client.clone(); 487 | 488 | let mut receive_step = 1; 489 | client 490 | .observe(path, move |msg| { 491 | match rx.try_recv() { 492 | Ok(n) => receive_step = n, 493 | _ => debug!("receive_step rx error"), 494 | } 495 | debug!("receive on client: {:?}", &msg); 496 | 497 | match receive_step { 498 | 1 => assert_eq!(msg.payload, payload1_clone), 499 | 2 => { 500 | assert_eq!(msg.payload, payload2_clone); 501 | tx2.send(()).unwrap(); 502 | } 503 | _ => panic!("unexpected step"), 504 | } 505 | }) 506 | .await 507 | .unwrap(); 508 | 509 | step = 2; 510 | debug!("on step 2"); 511 | tx.send(step).unwrap(); 512 | 513 | request.message.payload = payload2.clone(); 514 | request.message.set_token(vec![2]); 515 | 516 | let _ = client2.send(request).await.unwrap(); 517 | assert_eq!( 518 | tokio::time::timeout(Duration::new(5, 0), rx2.recv()) 519 | .await 520 | .unwrap(), 521 | Some(()) 522 | ); 523 | } 524 | #[tokio::test] 525 | async fn test_unobserve() { 526 | let path = "/test"; 527 | let payload1 = b"data1".to_vec(); 528 | let payload2 = b"data2".to_vec(); 529 | 530 | let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) 531 | .recv() 532 | .await 533 | .unwrap(); 534 | 535 | let server_address = &format!("127.0.0.1:{}", server_port); 536 | 537 | let client = UdpCoAPClient::new_udp(server_address).await.unwrap(); 538 | 539 | let client3 = client.clone(); 540 | 541 | let mut request = RequestBuilder::new(path, coap_lite::RequestType::Put) 542 | .token(Some(vec![1])) 543 | .data(Some(payload1.clone())) 544 | .build(); 545 | let _ = client.send(request.clone()).await.unwrap(); 546 | 547 | let payload1_clone = payload1.clone(); 548 | let unobserve = client 549 | .observe(path, move |msg| { 550 | assert_eq!(msg.payload, payload1_clone); 551 | }) 552 | .await 553 | .unwrap(); 554 | 555 | unobserve.send(client::ObserveMessage::Terminate).unwrap(); 556 | request.message.payload = payload2.clone(); 557 | 558 | let _ = client3.send(request).await.unwrap(); 559 | } 560 | 561 | #[tokio::test] 562 | async fn test_observe_without_resource() { 563 | let path = "/test"; 564 | 565 | let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) 566 | .recv() 567 | .await 568 | .unwrap(); 569 | 570 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 571 | .await 572 | .unwrap(); 573 | let error = client.observe(path, |_msg| {}).await.unwrap_err(); 574 | assert_eq!(error.kind(), ErrorKind::NotFound); 575 | } 576 | } 577 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | pub use coap_lite::{ 4 | CoapOption, CoapRequest, MessageClass, MessageType, ObserveOption, Packet, 5 | RequestType as Method, ResponseType as Status, 6 | }; 7 | 8 | /// A builder for creating CoAP requests using coap_lite 9 | pub struct RequestBuilder<'a> { 10 | path: &'a str, 11 | method: Method, 12 | data: Option>, 13 | queries: Option>, 14 | domain: String, 15 | confirmable: bool, 16 | token: Option>, 17 | options: Vec<(CoapOption, Vec)>, 18 | } 19 | impl<'a> RequestBuilder<'a> { 20 | pub fn new(path: &'a str, method: Method) -> Self { 21 | Self { 22 | path, 23 | method, 24 | data: None, 25 | queries: None, 26 | token: None, 27 | domain: "".to_string(), 28 | confirmable: true, 29 | options: vec![], 30 | } 31 | } 32 | 33 | /// Create a new request with the given path, method, optional payload, optional query, and 34 | /// domain. 35 | pub fn request_path( 36 | path: &'a str, 37 | method: Method, 38 | payload: Option>, 39 | query: Option>, 40 | domain: Option, 41 | ) -> Self { 42 | let new_self = Self::new(path, method); 43 | Self { 44 | data: payload, 45 | queries: query, 46 | domain: domain.unwrap_or_else(|| "".to_string()), 47 | ..new_self 48 | } 49 | } 50 | 51 | /// Set the payload of the request. 52 | pub fn data(mut self, data: Option>) -> Self { 53 | self.data = data; 54 | self 55 | } 56 | /// set the queries of the request. 57 | pub fn queries(mut self, queries: Option>) -> Self { 58 | self.queries = queries; 59 | self 60 | } 61 | /// set the domain of the request. 62 | pub fn domain(mut self, domain: String) -> Self { 63 | self.domain = domain; 64 | self 65 | } 66 | /// set whether the request is confirmable. 67 | pub fn confirmable(mut self, confirmable: bool) -> Self { 68 | self.confirmable = confirmable; 69 | self 70 | } 71 | /// set the token of the request 72 | pub fn token(mut self, token: Option>) -> Self { 73 | self.token = token; 74 | self 75 | } 76 | /// set the options of the request 77 | pub fn options(mut self, options: Vec<(CoapOption, Vec)>) -> Self { 78 | self.options = options; 79 | self 80 | } 81 | 82 | pub fn build(self) -> CoapRequest { 83 | let mut request = CoapRequest::new(); 84 | request.set_method(self.method); 85 | request.set_path(self.path); 86 | if let Some(queries) = self.queries { 87 | request.message.add_option(CoapOption::UriQuery, queries); 88 | } 89 | for (opt, opt_data) in self.options { 90 | assert_ne!(opt, CoapOption::UriQuery, "Use queries instead"); 91 | request.message.add_option(opt, opt_data); 92 | } 93 | if self.domain.len() != 0 { 94 | request.message.add_option( 95 | CoapOption::UriHost, 96 | self.domain.as_str().as_bytes().to_vec(), 97 | ); 98 | } 99 | if let Some(data) = self.data { 100 | request.message.payload = data; 101 | } 102 | match self.confirmable { 103 | true => request.message.header.set_type(MessageType::Confirmable), 104 | false => request.message.header.set_type(MessageType::NonConfirmable), 105 | }; 106 | if let Some(tok) = self.token { 107 | request.message.set_token(tok); 108 | } 109 | return request; 110 | } 111 | } 112 | #[cfg(test)] 113 | pub mod test { 114 | pub use super::*; 115 | 116 | #[test] 117 | fn test_request_has_payload() { 118 | let build = RequestBuilder::request_path( 119 | "/", 120 | Method::Put, 121 | Some(b"hello, world!".to_vec()), 122 | None, 123 | None, 124 | ) 125 | .build(); 126 | assert_eq!(build.message.payload.as_slice(), b"hello, world!"); 127 | } 128 | #[test] 129 | fn test_domain() { 130 | let build = RequestBuilder::request_path( 131 | "/", 132 | Method::Put, 133 | None, 134 | None, 135 | Some("example.com".to_string()), 136 | ) 137 | .build(); 138 | assert_eq!( 139 | build.message.get_first_option(CoapOption::UriHost).unwrap(), 140 | b"example.com" 141 | ); 142 | } 143 | 144 | #[test] 145 | fn test_domain_and_other_options() { 146 | let options = vec![ 147 | (CoapOption::ProxyUri, b"coap://foo.com".to_vec()), 148 | (CoapOption::Block2, b"fake".to_vec()), 149 | ]; 150 | let build = RequestBuilder::request_path( 151 | "/", 152 | Method::Put, 153 | None, 154 | b"query=hello".to_vec().into(), 155 | Some("example.com".to_string()), 156 | ) 157 | .options(options) 158 | .build(); 159 | assert_eq!( 160 | build.message.get_first_option(CoapOption::UriHost).unwrap(), 161 | b"example.com" 162 | ); 163 | assert_eq!( 164 | build 165 | .message 166 | .get_first_option(CoapOption::UriQuery) 167 | .unwrap(), 168 | b"query=hello" 169 | ); 170 | assert_eq!( 171 | build 172 | .message 173 | .get_first_option(CoapOption::ProxyUri) 174 | .unwrap(), 175 | b"coap://foo.com" 176 | ); 177 | assert_eq!( 178 | build.message.get_first_option(CoapOption::Block2).unwrap(), 179 | b"fake" 180 | ) 181 | } 182 | 183 | #[test] 184 | fn test_request_token() { 185 | let build = RequestBuilder::new("/", Method::Put) 186 | .token(Some(b"token".to_vec())) 187 | .build(); 188 | assert_eq!(build.message.get_token(), b"token"); 189 | } 190 | #[test] 191 | fn test_confirmable_request() { 192 | let build = RequestBuilder::new("/", Method::Put) 193 | .confirmable(true) 194 | .build(); 195 | assert_eq!(build.message.header.get_type(), MessageType::Confirmable); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use coap_lite::{BlockHandler, BlockHandlerConfig, CoapRequest, CoapResponse, Packet}; 3 | use log::debug; 4 | use std::{ 5 | future::Future, 6 | io::ErrorKind, 7 | net::{self, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}, 8 | sync::Arc, 9 | }; 10 | use tokio::{ 11 | io, 12 | net::UdpSocket, 13 | select, 14 | sync::{ 15 | mpsc::{self, UnboundedReceiver, UnboundedSender}, 16 | Mutex, 17 | }, 18 | task::JoinHandle, 19 | }; 20 | 21 | use crate::observer::Observer; 22 | 23 | #[derive(Debug)] 24 | pub enum CoAPServerError { 25 | NetworkError, 26 | EventLoopError, 27 | AnotherHandlerIsRunning, 28 | EventSendError, 29 | } 30 | 31 | use tokio::io::Error; 32 | 33 | #[async_trait] 34 | pub trait Dispatcher: Send + Sync { 35 | async fn dispatch(&self, request: CoapRequest) -> Option; 36 | } 37 | 38 | #[async_trait] 39 | /// This trait represents a generic way to respond to a listener. If you want to implement your own 40 | /// listener, you have to implement this trait to be able to send responses back through the 41 | /// correct transport 42 | pub trait Responder: Sync + Send { 43 | async fn respond(&self, response: Vec); 44 | fn address(&self) -> SocketAddr; 45 | } 46 | 47 | /// channel to send new requests from a transport to the CoAP server 48 | pub type TransportRequestSender = UnboundedSender<(Vec, Arc)>; 49 | 50 | /// channel used by CoAP server to receive new requests 51 | pub type TransportRequestReceiver = UnboundedReceiver<(Vec, Arc)>; 52 | 53 | type UdpResponseReceiver = UnboundedReceiver<(Vec, SocketAddr)>; 54 | type UdpResponseSender = UnboundedSender<(Vec, SocketAddr)>; 55 | 56 | // listeners receive new connections 57 | #[async_trait] 58 | pub trait Listener: Send { 59 | async fn listen( 60 | self: Box, 61 | sender: TransportRequestSender, 62 | ) -> std::io::Result>>; 63 | } 64 | /// listener for a UDP socket 65 | pub struct UdpCoapListener { 66 | socket: UdpSocket, 67 | multicast_addresses: Vec, 68 | response_receiver: UdpResponseReceiver, 69 | response_sender: UdpResponseSender, 70 | } 71 | 72 | #[async_trait] 73 | /// A trait for handling incoming requests. Use this instead of a closure 74 | /// if you want to modify some external state 75 | pub trait RequestHandler: Send + Sync + 'static { 76 | async fn handle_request( 77 | &self, 78 | mut request: Box>, 79 | ) -> Box>; 80 | } 81 | 82 | #[async_trait] 83 | impl RequestHandler for F 84 | where 85 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 86 | HandlerRet: Future>> + Send, 87 | { 88 | async fn handle_request( 89 | &self, 90 | request: Box>, 91 | ) -> Box> { 92 | self(request).await 93 | } 94 | } 95 | 96 | /// A listener for UDP packets. This listener can also subscribe to multicast addresses 97 | impl UdpCoapListener { 98 | pub fn new(addr: A) -> Result { 99 | let std_socket = net::UdpSocket::bind(addr)?; 100 | std_socket.set_nonblocking(true)?; 101 | let socket = UdpSocket::from_std(std_socket)?; 102 | Ok(Self::from_socket(socket)) 103 | } 104 | 105 | pub fn from_socket(socket: tokio::net::UdpSocket) -> Self { 106 | let (tx, rx) = mpsc::unbounded_channel(); 107 | Self { 108 | socket, 109 | multicast_addresses: Vec::new(), 110 | response_receiver: rx, 111 | response_sender: tx, 112 | } 113 | } 114 | 115 | /// join multicast - adds the multicast addresses to the unicast listener 116 | /// - IPv4 multicast address range is '224.0.0.0/4' 117 | /// - IPv6 AllCoAp multicast addresses are 'ff00::/8' 118 | /// 119 | /// Parameter segment is used with IPv6 to determine the first octet. 120 | /// - It's value can be between 0x0 and 0xf. 121 | /// - To join multiple segments, you have to call enable_discovery for each of the segments. 122 | /// 123 | /// Some Multicast address scope 124 | /// IPv6 IPv4 equivalent[16] Scope Purpose 125 | /// ffx1::/16 127.0.0.0/8 Interface-local Packets with this destination address may not be sent over any network link, but must remain within the current node; this is the multicast equivalent of the unicast loopback address. 126 | /// ffx2::/16 224.0.0.0/24 Link-local Packets with this destination address may not be routed anywhere. 127 | /// ffx3::/16 239.255.0.0/16 IPv4 local scope 128 | /// ffx4::/16 Admin-local The smallest scope that must be administratively configured. 129 | /// ffx5::/16 Site-local Restricted to the local physical network. 130 | /// ffx8::/16 239.192.0.0/14 Organization-local Restricted to networks used by the organization administering the local network. (For example, these addresses might be used over VPNs; when packets for this group are routed over the public internet (where these addresses are not valid), they would have to be encapsulated in some other protocol.) 131 | /// ffxe::/16 224.0.1.0-238.255.255.255 Global scope Eligible to be routed over the public internet. 132 | /// 133 | /// Notable addresses: 134 | /// ff02::1 All nodes on the local network segment 135 | /// ff0x::c Simple Service Discovery Protocol 136 | /// ff0x::fb Multicast DNS 137 | /// ff0x::fb Multicast CoAP 138 | /// ff0x::114 Used for experiments 139 | // pub fn join_multicast(&mut self, addr: IpAddr) { 140 | // self.udp_server.join_multicast(addr); 141 | // } 142 | pub fn join_multicast(&mut self, addr: IpAddr) { 143 | assert!(addr.is_multicast()); 144 | // determine wether IPv4 or IPv6 and 145 | // join the appropriate multicast address 146 | match self.socket.local_addr().unwrap() { 147 | SocketAddr::V4(val) => { 148 | match addr { 149 | IpAddr::V4(ipv4) => { 150 | let i = val.ip().clone(); 151 | self.socket.join_multicast_v4(ipv4, i).unwrap(); 152 | self.multicast_addresses.push(addr); 153 | } 154 | IpAddr::V6(_ipv6) => { /* handle IPv6 */ } 155 | } 156 | } 157 | SocketAddr::V6(_val) => { 158 | match addr { 159 | IpAddr::V4(_ipv4) => { /* handle IPv4 */ } 160 | IpAddr::V6(ipv6) => { 161 | self.socket.join_multicast_v6(&ipv6, 0).unwrap(); 162 | self.multicast_addresses.push(addr); 163 | //self.socket.set_only_v6(true)?; 164 | } 165 | } 166 | } 167 | } 168 | } 169 | 170 | /// leave multicast - remove the multicast address from the listener 171 | pub fn leave_multicast(&mut self, addr: IpAddr) { 172 | assert!(addr.is_multicast()); 173 | // determine wether IPv4 or IPv6 and 174 | // leave the appropriate multicast address 175 | match self.socket.local_addr().unwrap() { 176 | SocketAddr::V4(val) => { 177 | match addr { 178 | IpAddr::V4(ipv4) => { 179 | let i = val.ip().clone(); 180 | self.socket.leave_multicast_v4(ipv4, i).unwrap(); 181 | let index = self 182 | .multicast_addresses 183 | .iter() 184 | .position(|&item| item == addr) 185 | .unwrap(); 186 | self.multicast_addresses.remove(index); 187 | } 188 | IpAddr::V6(_ipv6) => { /* handle IPv6 */ } 189 | } 190 | } 191 | SocketAddr::V6(_val) => { 192 | match addr { 193 | IpAddr::V4(_ipv4) => { /* handle IPv4 */ } 194 | IpAddr::V6(ipv6) => { 195 | self.socket.leave_multicast_v6(&ipv6, 0).unwrap(); 196 | let index = self 197 | .multicast_addresses 198 | .iter() 199 | .position(|&item| item == addr) 200 | .unwrap(); 201 | self.multicast_addresses.remove(index); 202 | } 203 | } 204 | } 205 | } 206 | } 207 | /// enable AllCoAP multicasts - adds the AllCoap addresses to the listener 208 | /// - IPv4 AllCoAP multicast address is '224.0.1.187' 209 | /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' 210 | /// 211 | /// Parameter segment is used with IPv6 to determine the first octet. 212 | /// - It's value can be between 0x0 and 0xf. 213 | /// - To join multiple segments, you have to call enable_discovery for each of the segments. 214 | /// 215 | /// For further details see method join_multicast 216 | pub fn enable_all_coap(&mut self, segment: u8) { 217 | assert!(segment <= 0xf); 218 | let m = match self.socket.local_addr().unwrap() { 219 | SocketAddr::V4(_val) => IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), 220 | SocketAddr::V6(_val) => IpAddr::V6(Ipv6Addr::new( 221 | 0xff00 + segment as u16, 222 | 0, 223 | 0, 224 | 0, 225 | 0, 226 | 0, 227 | 0, 228 | 0xfd, 229 | )), 230 | }; 231 | self.join_multicast(m); 232 | } 233 | } 234 | #[async_trait] 235 | impl Listener for UdpCoapListener { 236 | async fn listen( 237 | mut self: Box, 238 | sender: TransportRequestSender, 239 | ) -> std::io::Result>> { 240 | return Ok(tokio::spawn(self.receive_loop(sender))); 241 | } 242 | } 243 | 244 | #[derive(Clone)] 245 | struct UdpResponder { 246 | address: SocketAddr, // this is the address we are sending to 247 | tx: UdpResponseSender, 248 | } 249 | 250 | #[async_trait] 251 | impl Responder for UdpResponder { 252 | async fn respond(&self, response: Vec) { 253 | let _ = self.tx.send((response, self.address)); 254 | } 255 | fn address(&self) -> SocketAddr { 256 | self.address 257 | } 258 | } 259 | 260 | impl UdpCoapListener { 261 | pub async fn receive_loop(mut self, sender: TransportRequestSender) -> std::io::Result<()> { 262 | loop { 263 | let mut recv_vec = Vec::with_capacity(u16::MAX as usize); 264 | select! { 265 | message =self.socket.recv_buf_from(&mut recv_vec)=> { 266 | match message { 267 | Ok((_size, from)) => { 268 | 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"))?; 269 | } 270 | Err(e) => { 271 | return Err(e); 272 | } 273 | } 274 | }, 275 | response = self.response_receiver.recv() => { 276 | if let Some((bytes, to)) = response{ 277 | debug!("sending {:?} to {:?}", &bytes, &to); 278 | self.socket.send_to(&bytes, to).await?; 279 | } 280 | else { 281 | // in case nobody is listening to us, we can just terminate, though this 282 | // should never happen for UDP 283 | return Ok(()); 284 | } 285 | 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | #[derive(Debug)] 293 | pub struct QueuedMessage { 294 | pub address: SocketAddr, 295 | pub message: Packet, 296 | } 297 | 298 | struct ServerCoapState { 299 | observer: Observer, 300 | block_handler: BlockHandler, 301 | disable_observe: bool, 302 | } 303 | 304 | pub enum ShouldForwardToHandler { 305 | True, 306 | False, 307 | } 308 | 309 | impl ServerCoapState { 310 | pub async fn intercept_request( 311 | &mut self, 312 | request: &mut CoapRequest, 313 | responder: Arc, 314 | ) -> ShouldForwardToHandler { 315 | match self.block_handler.intercept_request(request) { 316 | Ok(true) => return ShouldForwardToHandler::False, 317 | Err(_err) => return ShouldForwardToHandler::False, 318 | Ok(false) => {} 319 | }; 320 | 321 | if self.disable_observe { 322 | return ShouldForwardToHandler::True; 323 | } 324 | 325 | let should_be_forwarded = self.observer.request_handler(request, responder).await; 326 | if should_be_forwarded { 327 | return ShouldForwardToHandler::True; 328 | } else { 329 | return ShouldForwardToHandler::False; 330 | } 331 | } 332 | 333 | pub async fn intercept_response(&mut self, request: &mut CoapRequest) { 334 | match self.block_handler.intercept_response(request) { 335 | Err(err) => { 336 | let _ = request.apply_from_error(err); 337 | } 338 | _ => {} 339 | } 340 | } 341 | pub fn new() -> Self { 342 | Self { 343 | observer: Observer::new(), 344 | block_handler: BlockHandler::new(BlockHandlerConfig::default()), 345 | disable_observe: false, 346 | } 347 | } 348 | pub fn disable_observe_handling(&mut self, value: bool) { 349 | self.disable_observe = value 350 | } 351 | } 352 | 353 | pub struct Server { 354 | listeners: Vec>, 355 | coap_state: Arc>, 356 | new_packet_receiver: TransportRequestReceiver, 357 | new_packet_sender: TransportRequestSender, 358 | } 359 | 360 | impl Server { 361 | /// Creates a CoAP server listening on the given address. 362 | pub fn new_udp(addr: A) -> Result { 363 | let listener: Vec> = vec![Box::new(UdpCoapListener::new(addr)?)]; 364 | Ok(Self::from_listeners(listener)) 365 | } 366 | 367 | pub fn from_listeners(listeners: Vec>) -> Self { 368 | let (tx, rx) = mpsc::unbounded_channel(); 369 | Server { 370 | listeners, 371 | coap_state: Arc::new(Mutex::new(ServerCoapState::new())), 372 | new_packet_receiver: rx, 373 | new_packet_sender: tx, 374 | } 375 | } 376 | 377 | async fn spawn_handles( 378 | listeners: Vec>, 379 | sender: TransportRequestSender, 380 | ) -> std::io::Result>>> { 381 | let mut handles = vec![]; 382 | for listener in listeners.into_iter() { 383 | let handle = listener.listen(sender.clone()).await?; 384 | handles.push(handle); 385 | } 386 | return Ok(handles); 387 | } 388 | 389 | /// run the server. 390 | pub async fn run(mut self, handler: Handler) -> Result<(), io::Error> { 391 | let _handles = Self::spawn_handles(self.listeners, self.new_packet_sender.clone()).await?; 392 | 393 | let handler_arc = Arc::new(handler); 394 | // receive an input, sync our cache / states, then call custom handler 395 | loop { 396 | let (bytes, respond) = 397 | self.new_packet_receiver.recv().await.ok_or_else(|| { 398 | std::io::Error::new(ErrorKind::Other, "listen channel closed") 399 | })?; 400 | if let Ok(packet) = Packet::from_bytes(&bytes) { 401 | let mut request = Box::new(CoapRequest::::from_packet( 402 | packet, 403 | respond.address(), 404 | )); 405 | let mut coap_state = self.coap_state.lock().await; 406 | let should_forward = coap_state 407 | .intercept_request(&mut request, respond.clone()) 408 | .await; 409 | 410 | match should_forward { 411 | ShouldForwardToHandler::True => { 412 | let handler_clone = handler_arc.clone(); 413 | let coap_state_clone = self.coap_state.clone(); 414 | tokio::spawn(async move { 415 | request = handler_clone.handle_request(request).await; 416 | coap_state_clone 417 | .lock() 418 | .await 419 | .intercept_response(request.as_mut()) 420 | .await; 421 | 422 | Self::respond_to_request(request, respond).await; 423 | }); 424 | } 425 | ShouldForwardToHandler::False => { 426 | Self::respond_to_request(request, respond).await; 427 | } 428 | } 429 | } 430 | } 431 | } 432 | async fn respond_to_request(req: Box>, responder: Arc) { 433 | // if we have some reponse to send, send it 434 | if let Some(Ok(b)) = req.response.map(|resp| resp.message.to_bytes()) { 435 | responder.respond(b).await; 436 | } 437 | } 438 | #[deprecated(since = "0.21.0", note = "Use 'coap::Server::automatic_observe_handling' instead.")] 439 | /// disable auto-observe handling in server 440 | pub async fn disable_observe_handling(&mut self, value: bool) { 441 | self.automatic_observe_handling(value).await 442 | } 443 | /// set auto-observe handling in server, defaults to enabled 444 | pub async fn automatic_observe_handling(&mut self, value: bool) { 445 | let mut coap_state = self.coap_state.lock().await; 446 | coap_state.disable_observe_handling(value) 447 | } 448 | } 449 | 450 | #[cfg(test)] 451 | pub mod test { 452 | use crate::request::RequestBuilder; 453 | 454 | use super::super::*; 455 | use super::*; 456 | use coap_lite::{block_handler::BlockValue, CoapOption, RequestType}; 457 | use std::str; 458 | use std::time::Duration; 459 | 460 | pub fn spawn_server< 461 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 462 | HandlerRet, 463 | >( 464 | ip: &'static str, 465 | request_handler: F, 466 | ) -> mpsc::UnboundedReceiver 467 | where 468 | HandlerRet: Future>> + Send, 469 | { 470 | let (tx, rx) = mpsc::unbounded_channel(); 471 | let _task = tokio::spawn(async move { 472 | let sock = UdpSocket::bind(ip).await.unwrap(); 473 | let addr = sock.local_addr().unwrap(); 474 | let listener = Box::new(UdpCoapListener::from_socket(sock)); 475 | let server = Server::from_listeners(vec![listener]); 476 | tx.send(addr.port()).unwrap(); 477 | server.run(request_handler).await.unwrap(); 478 | }); 479 | 480 | rx 481 | } 482 | 483 | async fn request_handler( 484 | mut req: Box>, 485 | ) -> Box> { 486 | let uri_path_list = req.message.get_option(CoapOption::UriPath).unwrap().clone(); 487 | assert_eq!(uri_path_list.len(), 1); 488 | 489 | match req.response { 490 | Some(ref mut response) => { 491 | response.message.payload = uri_path_list.front().unwrap().clone(); 492 | } 493 | _ => {} 494 | } 495 | return req; 496 | } 497 | 498 | pub fn spawn_server_with_all_coap< 499 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 500 | HandlerRet, 501 | >( 502 | ip: &'static str, 503 | request_handler: F, 504 | segment: u8, 505 | ) -> mpsc::UnboundedReceiver 506 | where 507 | HandlerRet: Future>> + Send, 508 | { 509 | let (tx, rx) = mpsc::unbounded_channel(); 510 | 511 | std::thread::Builder::new() 512 | .name(String::from("v4-server")) 513 | .spawn(move || { 514 | tokio::runtime::Runtime::new() 515 | .unwrap() 516 | .block_on(async move { 517 | // multicast needs a server on a real interface 518 | let sock = UdpSocket::bind((ip, 0)).await.unwrap(); 519 | let addr = sock.local_addr().unwrap(); 520 | let mut listener = Box::new(UdpCoapListener::from_socket(sock)); 521 | listener.enable_all_coap(segment); 522 | let server = Server::from_listeners(vec![listener]); 523 | tx.send(addr.port()).unwrap(); 524 | server.run(request_handler).await.unwrap(); 525 | }) 526 | }) 527 | .unwrap(); 528 | 529 | rx 530 | } 531 | 532 | pub fn spawn_server_disable_observe< 533 | F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, 534 | HandlerRet, 535 | >( 536 | ip: &'static str, 537 | request_handler: F, 538 | ) -> mpsc::UnboundedReceiver 539 | where 540 | HandlerRet: Future>> + Send, 541 | { 542 | let (tx, rx) = mpsc::unbounded_channel(); 543 | let _task = tokio::spawn(async move { 544 | let sock = UdpSocket::bind(ip).await.unwrap(); 545 | let addr = sock.local_addr().unwrap(); 546 | let listener = Box::new(UdpCoapListener::from_socket(sock)); 547 | let mut server = Server::from_listeners(vec![listener]); 548 | server.disable_observe_handling(true).await; 549 | tx.send(addr.port()).unwrap(); 550 | server.run(request_handler).await.unwrap(); 551 | }); 552 | 553 | rx 554 | } 555 | 556 | #[tokio::test] 557 | async fn test_echo_server() { 558 | let server_port = spawn_server("127.0.0.1:0", request_handler) 559 | .recv() 560 | .await 561 | .unwrap(); 562 | 563 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 564 | .await 565 | .unwrap(); 566 | let mut request = CoapRequest::new(); 567 | request.message.header.set_version(1); 568 | request 569 | .message 570 | .header 571 | .set_type(coap_lite::MessageType::Confirmable); 572 | request.message.header.set_code("0.01"); 573 | request.message.header.message_id = 1; 574 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 575 | request 576 | .message 577 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 578 | client.send_single_request(&request).await.unwrap(); 579 | 580 | let recv_packet = client.send(request).await.unwrap(); 581 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 582 | } 583 | 584 | #[tokio::test] 585 | async fn test_put_block() { 586 | let server_port = spawn_server("127.0.0.1:0", request_handler) 587 | .recv() 588 | .await 589 | .unwrap(); 590 | let data = "hello this is a payload"; 591 | let mut v = Vec::new(); 592 | for _ in 0..1024 { 593 | v.extend_from_slice(data.as_bytes()); 594 | } 595 | let payload_size = v.len(); 596 | let server_string = format!("127.0.0.1:{}", server_port); 597 | let client = UdpCoAPClient::new_udp(server_string.clone()).await.unwrap(); 598 | 599 | let request = RequestBuilder::new("/large", RequestType::Put) 600 | .data(Some(v)) 601 | .domain(server_string.clone()) 602 | .build(); 603 | 604 | let resp = client.send(request).await.unwrap(); 605 | let block_opt = resp 606 | .message 607 | .get_first_option_as::(CoapOption::Block1) 608 | .expect("expected block opt in response") 609 | .expect("could not decode block1 option"); 610 | let expected_number = (payload_size as f32 / 1024.0).ceil() as u16 - 1; 611 | assert_eq!( 612 | block_opt.num, expected_number, 613 | "block not completely received!" 614 | ); 615 | 616 | assert_eq!(resp.message.payload, b"large".to_vec()); 617 | } 618 | 619 | #[tokio::test] 620 | #[ignore] 621 | async fn test_echo_server_v6() { 622 | let server_port = spawn_server("::1:0", request_handler).recv().await.unwrap(); 623 | 624 | let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) 625 | .await 626 | .unwrap(); 627 | let mut request = CoapRequest::new(); 628 | request.message.header.set_version(1); 629 | request 630 | .message 631 | .header 632 | .set_type(coap_lite::MessageType::Confirmable); 633 | request.message.header.set_code("0.01"); 634 | request.message.header.message_id = 1; 635 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 636 | request 637 | .message 638 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 639 | 640 | let recv_packet = client.send(request).await.unwrap(); 641 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 642 | } 643 | 644 | #[tokio::test] 645 | async fn test_echo_server_no_token() { 646 | let server_port = spawn_server("127.0.0.1:0", request_handler) 647 | .recv() 648 | .await 649 | .unwrap(); 650 | 651 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 652 | .await 653 | .unwrap(); 654 | let mut packet = CoapRequest::new(); 655 | packet.message.header.set_version(1); 656 | packet 657 | .message 658 | .header 659 | .set_type(coap_lite::MessageType::Confirmable); 660 | packet.message.header.set_code("0.01"); 661 | packet.message.header.message_id = 1; 662 | packet 663 | .message 664 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 665 | let recv_packet = client.send(packet).await.unwrap(); 666 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 667 | } 668 | 669 | #[tokio::test] 670 | #[ignore] 671 | async fn test_echo_server_no_token_v6() { 672 | let server_port = spawn_server("::1:0", request_handler).recv().await.unwrap(); 673 | 674 | let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) 675 | .await 676 | .unwrap(); 677 | let mut packet = CoapRequest::new(); 678 | packet.message.header.set_version(1); 679 | packet 680 | .message 681 | .header 682 | .set_type(coap_lite::MessageType::Confirmable); 683 | packet.message.header.set_code("0.01"); 684 | packet.message.header.message_id = 1; 685 | packet 686 | .message 687 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 688 | 689 | let recv_packet = client.send(packet).await.unwrap(); 690 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 691 | } 692 | 693 | #[tokio::test] 694 | async fn test_update_resource() { 695 | let path = "/test"; 696 | let payload1 = b"data1".to_vec(); 697 | let payload2 = b"data2".to_vec(); 698 | let (tx, mut rx) = mpsc::unbounded_channel(); 699 | let (tx2, mut rx2) = mpsc::unbounded_channel(); 700 | let mut step = 1; 701 | 702 | let server_port = spawn_server("127.0.0.1:0", request_handler) 703 | .recv() 704 | .await 705 | .unwrap(); 706 | 707 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 708 | .await 709 | .unwrap(); 710 | 711 | tx.send(step).unwrap(); 712 | let mut request = CoapRequest::new(); 713 | request.set_method(RequestType::Put); 714 | request.set_path(path); 715 | request.message.payload = payload1.clone(); 716 | client.send(request.clone()).await.unwrap(); 717 | 718 | let mut receive_step = 1; 719 | let payload1_clone = payload1.clone(); 720 | let payload2_clone = payload2.clone(); 721 | client 722 | .observe(path, move |msg| { 723 | match rx.try_recv() { 724 | Ok(n) => receive_step = n, 725 | _ => (), 726 | } 727 | 728 | match receive_step { 729 | 1 => assert_eq!(msg.payload, payload1_clone), 730 | 2 => { 731 | assert_eq!(msg.payload, payload2_clone); 732 | tx2.send(()).unwrap(); 733 | } 734 | _ => panic!("unexpected step"), 735 | } 736 | }) 737 | .await 738 | .unwrap(); 739 | 740 | step = 2; 741 | tx.send(step).unwrap(); 742 | request.message.payload = payload2.clone(); 743 | let client2 = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 744 | .await 745 | .unwrap(); 746 | let _ = client2.send(request).await.unwrap(); 747 | assert_eq!( 748 | tokio::time::timeout(Duration::new(5, 0), rx2.recv()) 749 | .await 750 | .unwrap(), 751 | Some(()) 752 | ); 753 | } 754 | 755 | #[tokio::test] 756 | async fn test_observe_transparent_transmission() { 757 | let path = "/test"; 758 | let (tx, mut rx) = mpsc::unbounded_channel(); 759 | 760 | let server_port = spawn_server_disable_observe("127.0.0.1:0", request_handler) 761 | .recv() 762 | .await 763 | .unwrap(); 764 | 765 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 766 | .await 767 | .unwrap(); 768 | 769 | client 770 | .observe(path, move |msg| { 771 | assert_eq!(msg.payload, b"test".to_vec()); 772 | tx.send(()).unwrap(); 773 | }) 774 | .await 775 | .unwrap(); 776 | 777 | assert_eq!( 778 | tokio::time::timeout(Duration::new(5, 0), rx.recv()) 779 | .await 780 | .unwrap(), 781 | Some(()) 782 | ); 783 | } 784 | 785 | #[tokio::test] 786 | async fn multicast_server_all_coap() { 787 | // segment not relevant with IPv4 788 | let segment = 0x0; 789 | let server_port = spawn_server_with_all_coap("0.0.0.0", request_handler, segment) 790 | .recv() 791 | .await 792 | .unwrap(); 793 | 794 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 795 | .await 796 | .unwrap(); 797 | let mut request = CoapRequest::new(); 798 | request.message.header.set_version(1); 799 | request 800 | .message 801 | .header 802 | .set_type(coap_lite::MessageType::Confirmable); 803 | request.message.header.set_code("0.01"); 804 | request.message.header.message_id = 1; 805 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 806 | request 807 | .message 808 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 809 | let recv_packet = client.send(request).await.unwrap(); 810 | 811 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 812 | 813 | let client = UdpCoAPClient::new_udp(format!("224.0.1.187:{}", server_port)) 814 | .await 815 | .unwrap(); 816 | let mut request = RequestBuilder::new("test-echo", RequestType::Get) 817 | .data(Some(vec![0x51, 0x55, 0x77, 0xE8])) 818 | .confirmable(true) 819 | .build(); 820 | 821 | let mut receiver = client.create_receiver_for(&request).await; 822 | client.send_all_coap(&mut request, segment).await.unwrap(); 823 | let recv_packet = receiver.receive().await.unwrap(); 824 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 825 | } 826 | 827 | //This test right now does not work on windows 828 | #[cfg(unix)] 829 | #[tokio::test] 830 | #[ignore] 831 | async fn multicast_server_all_coap_v6() { 832 | // use segment 0x04 which should be the smallest administered scope 833 | 834 | let segment = 0x04; 835 | let server_port = spawn_server_with_all_coap("::0", request_handler, segment) 836 | .recv() 837 | .await 838 | .unwrap(); 839 | 840 | let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) 841 | .await 842 | .unwrap(); 843 | let mut request = CoapRequest::new(); 844 | request.message.header.set_version(1); 845 | request 846 | .message 847 | .header 848 | .set_type(coap_lite::MessageType::Confirmable); 849 | request.message.header.set_code("0.01"); 850 | request.message.header.message_id = 1; 851 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 852 | request 853 | .message 854 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 855 | client.send_single_request(&request).await.unwrap(); 856 | 857 | let recv_packet = client.send(request).await.unwrap(); 858 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 859 | 860 | // use 0xff02 to keep it within this network 861 | let client = UdpCoAPClient::new_udp(format!("ff0{}::fd:{}", segment, server_port)) 862 | .await 863 | .unwrap(); 864 | let mut request = CoapRequest::new(); 865 | request.message.header.set_version(1); 866 | request 867 | .message 868 | .header 869 | .set_type(coap_lite::MessageType::NonConfirmable); 870 | request.message.header.set_code("0.01"); 871 | request.message.header.message_id = 2; 872 | request.message.set_token(vec![0x51, 0x55, 0x77, 0xE8]); 873 | request 874 | .message 875 | .add_option(CoapOption::UriPath, b"test-echo".to_vec()); 876 | let mut receiver = client.create_receiver_for(&request).await; 877 | client.send_all_coap(&mut request, segment).await.unwrap(); 878 | let recv_packet = receiver.receive().await.unwrap(); 879 | assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); 880 | } 881 | 882 | #[test] 883 | fn multicast_join_leave() { 884 | std::thread::Builder::new() 885 | .name(String::from("v4-server")) 886 | .spawn(move || { 887 | tokio::runtime::Runtime::new() 888 | .unwrap() 889 | .block_on(async move { 890 | // multicast needs a server on a real interface 891 | let sock = UdpSocket::bind(("0.0.0.0", 0)).await.unwrap(); 892 | let mut listener = Box::new(UdpCoapListener::from_socket(sock)); 893 | listener.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); 894 | listener.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); 895 | listener.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); 896 | listener.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); 897 | let server = Server::from_listeners(vec![listener]); 898 | server.run(request_handler).await.unwrap(); 899 | }) 900 | }) 901 | .unwrap(); 902 | 903 | std::thread::sleep(std::time::Duration::from_secs(1)); 904 | } 905 | #[test] 906 | #[ignore] 907 | fn multicast_join_leave_v6() { 908 | std::thread::Builder::new() 909 | .name(String::from("v6-server")) 910 | .spawn(move || { 911 | tokio::runtime::Runtime::new() 912 | .unwrap() 913 | .block_on(async move { 914 | // multicast needs a server on a real interface 915 | let sock = UdpSocket::bind(("0.0.0.0", 0)).await.unwrap(); 916 | let mut listener = Box::new(UdpCoapListener::from_socket(sock)); 917 | listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 918 | 0xff02, 0, 0, 0, 0, 0, 1, 0x1, 919 | ))); 920 | listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 921 | 0xff02, 0, 0, 0, 0, 1, 0, 0x2, 922 | ))); 923 | listener.leave_multicast(IpAddr::V6(Ipv6Addr::new( 924 | 0xff02, 0, 0, 0, 0, 0, 1, 0x1, 925 | ))); 926 | listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 927 | 0xff02, 0, 0, 0, 0, 1, 0, 0x2, 928 | ))); 929 | let server = Server::from_listeners(vec![listener]); 930 | server.run(request_handler).await.unwrap(); 931 | }) 932 | }) 933 | .unwrap(); 934 | 935 | std::thread::sleep(std::time::Duration::from_secs(1)); 936 | } 937 | 938 | fn get_expected_response() -> Vec { 939 | let mut resp = vec![]; 940 | for c in b'a'..=b'z' { 941 | resp.extend(std::iter::repeat(c).take(1024)); 942 | } 943 | resp 944 | } 945 | async fn block2_responder( 946 | mut req: Box>, 947 | ) -> Box> { 948 | // vec should contain 'a' 1024 times, then 'b' 1024, up to ascii 'z' 949 | 950 | match req.response { 951 | Some(ref mut response) => { 952 | response.message.payload = get_expected_response(); 953 | } 954 | _ => {} 955 | } 956 | return req; 957 | } 958 | #[tokio::test] 959 | async fn test_block2_server_response() { 960 | let server_port = spawn_server("127.0.0.1:0", block2_responder) 961 | .recv() 962 | .await 963 | .unwrap(); 964 | 965 | let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) 966 | .await 967 | .unwrap(); 968 | let resp = client 969 | .send(RequestBuilder::new("/", RequestType::Get).build()) 970 | .await 971 | .unwrap(); 972 | assert_eq!( 973 | resp.message.payload, 974 | get_expected_response(), 975 | "responses do not match" 976 | ); 977 | } 978 | } 979 | -------------------------------------------------------------------------------- /tests/test_certs/README.md: -------------------------------------------------------------------------------- 1 | # Tests Certificates and Keys 2 | keys and certificates generated using openssl and adapted from (webrtc-rs)[https://github.com/webrtc-rs/webrtc/tree/master/dtls/examples/certificates] 3 | 4 | ## generate commands 5 | ```shell 6 | # Server. 7 | $ SERVER_NAME='coap_server' 8 | $ openssl ecparam -name prime256v1 -genkey -noout -out "${SERVER_NAME}.pem" 9 | $ openssl req -key "${SERVER_NAME}.pem" -new -sha256 -subj '/C=NL' -out "${SERVER_NAME}.csr" 10 | $ openssl x509 -req -in "${SERVER_NAME}.csr" -extfile "${EXTFILE}" -days 365 -signkey "${SERVER_NAME}.pem" -sha256 -out "${SERVER_NAME}.pub.pem" 11 | 12 | # Client. 13 | $ CLIENT_NAME='coap_client' 14 | $ openssl ecparam -name prime256v1 -genkey -noout -out "${CLIENT_NAME}.pem" 15 | $ openssl req -key "${CLIENT_NAME}.pem" -new -sha256 -subj '/C=NL' -out "${CLIENT_NAME}.csr" 16 | $ openssl x509 -req -in "${CLIENT_NAME}.csr" -extfile "${EXTFILE}" -days 365 -CA "${SERVER_NAME}.pub.pem" -CAkey "${SERVER_NAME}.pem" -set_serial '0xabcd' -sha256 -out "${CLIENT_NAME}.pub.pem" 17 | ``` 18 | -------------------------------------------------------------------------------- /tests/test_certs/coap_client.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIHHMG8CAQAwDTELMAkGA1UEBhMCTkwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC 3 | AASrVgK4GG07FtKloEAh3EUzrIrMGYMagaZT5ERd5mt+VsEDg4nLQFXhDA/0WZf3 4 | tzEKXLi3OlbWR3hli4fYw+uDoAAwCgYIKoZIzj0EAwIDSAAwRQIhAO2lxvfqPLEn 5 | DxsSundZgFL3ZTl+5UQJrW+4Uh8ZTyGpAiBd2dzerMa/T9P+xjMJpHPbnJKDnXQu 6 | MNPmD3CzlG+3ig== 7 | -----END CERTIFICATE REQUEST----- 8 | -------------------------------------------------------------------------------- /tests/test_certs/coap_client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEINV0o4CxrFILfGIFKvjRvtrjBgypC8kcFwFm57/g+tx2oAoGCCqGSM49 3 | AwEHoUQDQgAEq1YCuBhtOxbSpaBAIdxFM6yKzBmDGoGmU+REXeZrflbBA4OJy0BV 4 | 4QwP9FmX97cxCly4tzpW1kd4ZYuH2MPrgw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/test_certs/coap_client.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBYTCCAQegAwIBAgIDAKvNMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4X 3 | DTI1MDExNzAyNTk0OFoXDTI2MDExNzAyNTk0OFowDTELMAkGA1UEBhMCTkwwWTAT 4 | BgcqhkjOPQIBBggqhkjOPQMBBwNCAASrVgK4GG07FtKloEAh3EUzrIrMGYMagaZT 5 | 5ERd5mt+VsEDg4nLQFXhDA/0WZf3tzEKXLi3OlbWR3hli4fYw+uDo1YwVDASBgNV 6 | HREECzAJggdjb2FwLnJzMB0GA1UdDgQWBBT/iSCz0TIfoC+9BgDJsmXpJvibGTAf 7 | BgNVHSMEGDAWgBQC1dL17C0yO3OcXld2QjyWCovr9DAKBggqhkjOPQQDAgNIADBF 8 | AiEAlPIGzv/sfWzWlTEtxbL51SjlPHjBZyHqL1dVH13kRTkCIBOWBTWRx7O46+IJ 9 | Yjcq897bUzaGSxDiGwwJeeORYVP+ 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /tests/test_certs/coap_server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIHHMG8CAQAwDTELMAkGA1UEBhMCTkwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC 3 | AASZwuhnFexl4+/wA2dfc7OCEAUz407FlTDYOldeTWhv/TdKXSPh8NxipL1w+14z 4 | /eQXBqZQovHJB9j93SN8zoGkoAAwCgYIKoZIzj0EAwIDSAAwRQIhANYwr2mkwz5E 5 | UOcuw9oS0K6Py7I03WHDcskuFNNQt5B8AiB55mNz/Q8Qjvr0Cdjh83dmvrmzBz5d 6 | BbgrxQg9e4HTRQ== 7 | -----END CERTIFICATE REQUEST----- 8 | -------------------------------------------------------------------------------- /tests/test_certs/coap_server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIG35yqAr17wAautvX2D8s6F0sCUcqcFCkEIjsyKtqqOWoAoGCCqGSM49 3 | AwEHoUQDQgAEmcLoZxXsZePv8ANnX3OzghAFM+NOxZUw2DpXXk1ob/03Sl0j4fDc 4 | YqS9cPteM/3kFwamUKLxyQfY/d0jfM6BpA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/test_certs/coap_server.pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBUDCB96ADAgECAhRdXRTz/UqPpqshsS+b/fMLKouIXDAKBggqhkjOPQQDAjAN 3 | MQswCQYDVQQGEwJOTDAeFw0yNTAxMTcwMjU5MTVaFw0yNjAxMTcwMjU5MTVaMA0x 4 | CzAJBgNVBAYTAk5MMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmcLoZxXsZePv 5 | 8ANnX3OzghAFM+NOxZUw2DpXXk1ob/03Sl0j4fDcYqS9cPteM/3kFwamUKLxyQfY 6 | /d0jfM6BpKM1MDMwEgYDVR0RBAswCYIHY29hcC5yczAdBgNVHQ4EFgQUAtXS9ewt 7 | MjtznF5XdkI8lgqL6/QwCgYIKoZIzj0EAwIDSAAwRQIgfgpX5CpzdJZo0uU7DQnq 8 | ATfOSCjAlwRYCClPfnfesckCIQDIZ2Tzq6UxczggOLh/iVGLt/rufJwu76Mp+KiF 9 | WZgbyg== 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /tests/test_certs/extfile.conf: -------------------------------------------------------------------------------- 1 | subjectAltName = DNS:coap.rs 2 | --------------------------------------------------------------------------------