1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * tests/common/mod.rs - Common code for integration tests.
4
 * This file is part of the libcoap-rs crate, see the README and LICENSE files for
5
 * more information and terms of use.
6
 * Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
7
 * See the README as well as the LICENSE file for more information.
8
 */
9

            
10
#[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
11
pub mod dtls;
12

            
13
use std::net::{SocketAddr, UdpSocket};
14
use std::rc::Rc;
15
use std::sync::{Arc, Condvar, Mutex};
16
use std::sync::atomic::{AtomicBool, Ordering};
17
use std::thread::JoinHandle;
18
use std::time::Duration;
19

            
20
use libcoap_rs::{CoapContext, CoapRequestHandler, CoapResource};
21
use libcoap_rs::message::{CoapMessageCommon, CoapRequest, CoapResponse};
22
use libcoap_rs::protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode};
23
use libcoap_rs::session::CoapSessionCommon;
24
use libcoap_sys::{coap_dtls_set_log_level, coap_log_t, coap_set_log_level};
25

            
26
25
pub(crate) fn get_unused_server_addr() -> SocketAddr {
27
25
    // This will give us a SocketAddress with a port in the local port range automatically
28
25
    // assigned by the operating system.
29
25
    // Because the UdpSocket goes out of scope, the Port will be free for usage by libcoap.
30
25
    // This seems to be the only portable way to get a port number assigned from the operating
31
25
    // system, which is necessary here because of potential parallelisation (we can't just use
32
25
    // the default CoAP port if multiple tests are run in parallel).
33
25
    // It is assumed here that after unbinding the temporary socket, the OS will not reassign
34
25
    // this port until we bind it again. This should work in most cases (unless we run on a
35
25
    // system with very few free ports), because at least Linux will not reuse port numbers
36
25
    // unless necessary, see https://unix.stackexchange.com/a/132524.
37
25
    UdpSocket::bind("localhost:0")
38
25
        .expect("Failed to bind server socket")
39
25
        .local_addr()
40
25
        .expect("Failed to get server socket address")
41
25
}
42

            
43
/// Spawns a test server in a new thread and waits for context_configurator to complete before
44
/// returning.
45
/// As the context_configurator closure is responsible for binding to sockets, this can be used to
46
/// spawn a test server and wait for it to be ready to accept requests before returning (avoiding
47
/// test failure due to "Connection Refused" errors).
48
25
pub(crate) fn spawn_test_server<F: FnOnce(CoapContext<'static>) -> CoapContext<'static> + Send + 'static>(
49
25
    context_configurator: F,
50
25
) -> JoinHandle<()> {
51
25
    let ready_condition = Arc::new((Mutex::new(false), Condvar::new()));
52
25
    let ready_condition2 = Arc::clone(&ready_condition);
53
25

            
54
25
    let server_handle = std::thread::spawn(move || {
55
25
        let (ready_var, ready_cond) = &*ready_condition2;
56
25
        run_test_server(|context| {
57
25
            let context = context_configurator(context);
58
25
            let mut ready_var = ready_var.lock().expect("ready condition mutex is poisoned");
59
25
            *ready_var = true;
60
25
            ready_cond.notify_all();
61
25
            context
62
25
        });
63
25
    });
64
25

            
65
25
    let (ready_var, ready_cond) = &*ready_condition;
66
25
    drop(
67
25
        ready_cond
68
50
            .wait_while(ready_var.lock().expect("ready condition mutex is poisoned"), |ready| {
69
50
                !*ready
70
50
            })
71
25
            .expect("ready condition mutex is poisoned"),
72
25
    );
73
25
    server_handle
74
25
}
75

            
76
/// Configures and starts a test server in the current thread.
77
25
pub(crate) fn run_test_server<F: FnOnce(CoapContext<'static>) -> CoapContext<'static>>(context_configurator: F) {
78
25
    unsafe {
79
25
        libcoap_sys::coap_startup_with_feature_checks();
80
25
        coap_dtls_set_log_level(coap_log_t::COAP_LOG_DEBUG);
81
25
        coap_set_log_level(coap_log_t::COAP_LOG_DEBUG);
82
25
    }
83
25
    let mut context = CoapContext::new().unwrap();
84
25
    context = context_configurator(context);
85
25
    let request_completed = Rc::new(AtomicBool::new(false));
86
25
    let resource = CoapResource::new("test1", request_completed.clone(), false);
87
25
    resource.set_method_handler(
88
25
        CoapRequestCode::Get,
89
25
        Some(CoapRequestHandler::new(
90
25
            |completed: &mut Rc<AtomicBool>, sess, _req, mut rsp: CoapResponse| {
91
25
                let data = Vec::<u8>::from("Hello World!".as_bytes());
92
25
                rsp.set_data(Some(data));
93
25
                rsp.set_code(CoapMessageCode::Response(CoapResponseCode::Content));
94
25
                sess.send(rsp).unwrap();
95
25
                completed.store(true, Ordering::Relaxed);
96
25
            },
97
25
        )),
98
25
    );
99
25

            
100
25
    context.add_resource(resource);
101
    loop {
102
108
        assert!(
103
108
            context.do_io(Some(Duration::from_secs(10))).unwrap() < Duration::from_secs(10),
104
            "timeout while waiting for test client request"
105
        );
106
108
        if request_completed.load(Ordering::Relaxed) {
107
25
            break;
108
83
        }
109
    }
110
25
    context.shutdown(Some(Duration::from_secs(0))).unwrap();
111
25
}
112

            
113
25
pub(crate) fn gen_test_request() -> CoapRequest {
114
25
    let uri = "/test1".parse().expect("unable to parse request URI");
115
25

            
116
25
    CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get, uri).unwrap()
117
25
}