1
// SPDX-License-Identifier: BSD-2-Clause
2
/*
3
 * Copyright © The libcoap-rs Contributors, all rights reserved.
4
 * This file is part of the libcoap-rs project, see the README file for
5
 * general information on this project and the NOTICE.md and LICENSE files
6
 * for information regarding copyright ownership and terms of use.
7
 *
8
 * tests/common/mod.rs - Common code for integration tests.
9
 */
10

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

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

            
25
use libcoap_rs::{
26
    message::{CoapMessageCommon, CoapRequest, CoapResponse},
27
    protocol::{CoapMessageCode, CoapMessageType, CoapRequestCode, CoapResponseCode},
28
    session::CoapSessionCommon,
29
    CoapContext, CoapRequestHandler, CoapResource,
30
};
31
use libcoap_sys::{coap_dtls_set_log_level, coap_log_t_COAP_LOG_DEBUG, coap_set_log_level};
32

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

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

            
61
25
    let server_handle = std::thread::Builder::new()
62
25
        .name(String::from("test server"))
63
25
        .spawn(move || {
64
25
            let (ready_var, ready_cond) = &*ready_condition2;
65
25
            run_test_server(|context| {
66
25
                let context = context_configurator(context);
67
25
                let mut ready_var = ready_var.lock().expect("ready condition mutex is poisoned");
68
25
                *ready_var = true;
69
25
                ready_cond.notify_all();
70
25
                context
71
25
            });
72
25
        })
73
25
        .expect("unable to spawn test server thread");
74
25

            
75
25
    let (ready_var, ready_cond) = &*ready_condition;
76
25
    {
77
25
        let (_guard, timeout_result) = ready_cond
78
25
            .wait_timeout_while(
79
25
                ready_var.lock().expect("ready condition mutex is poisoned"),
80
25
                Duration::from_secs(10),
81
50
                |ready| !*ready,
82
25
            )
83
25
            .expect("ready condition mutex is poisoned");
84
25
        if timeout_result.timed_out() && server_handle.is_finished() {
85
            if let Err(e) = server_handle.join() {
86
                std::panic::resume_unwind(e);
87
            }
88
            panic!("Test server thread is dead and has not reported readiness after 10 seconds, but has also not panicked.")
89
25
        }
90
25

            
91
25
        if timeout_result.timed_out() {
92
            panic!("Test server thread has not reported readiness after 10 seconds, but has also not died (deadlock?).")
93
25
        }
94
25
    }
95
25
    server_handle
96
25
}
97

            
98
/// Configures and starts a test server in the current thread.
99
25
pub(crate) fn run_test_server<F: FnOnce(CoapContext<'static>) -> CoapContext<'static>>(context_configurator: F) {
100
25
    unsafe {
101
25
        libcoap_sys::coap_startup_with_feature_checks();
102
25
        coap_dtls_set_log_level(coap_log_t_COAP_LOG_DEBUG);
103
25
        coap_set_log_level(coap_log_t_COAP_LOG_DEBUG);
104
25
    }
105
25
    let mut context = CoapContext::new().unwrap();
106
25
    context = context_configurator(context);
107
25
    let request_completed = Rc::new(AtomicBool::new(false));
108
25
    let resource = CoapResource::new("test1", request_completed.clone(), false);
109
25
    resource.set_method_handler(
110
25
        CoapRequestCode::Get,
111
25
        Some(CoapRequestHandler::new(
112
25
            |completed: &mut Rc<AtomicBool>, sess, _req, mut rsp: CoapResponse| {
113
25
                let data = Vec::<u8>::from("Hello World!".as_bytes());
114
25
                rsp.set_data(Some(data));
115
25
                rsp.set_code(CoapMessageCode::Response(CoapResponseCode::Content));
116
25
                sess.send(rsp).unwrap();
117
25
                completed.store(true, Ordering::Relaxed);
118
25
            },
119
25
        )),
120
25
    );
121
25

            
122
25
    context.add_resource(resource);
123
    loop {
124
108
        assert!(
125
108
            context.do_io(Some(Duration::from_secs(10))).unwrap() < Duration::from_secs(10),
126
            "timeout while waiting for test client request"
127
        );
128
108
        if request_completed.load(Ordering::Relaxed) {
129
25
            break;
130
83
        }
131
    }
132
25
    context.shutdown(Some(Duration::from_secs(0))).unwrap();
133
25
}
134

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

            
138
25
    CoapRequest::new(CoapMessageType::Con, CoapRequestCode::Get, uri).unwrap()
139
25
}