#![allow(clippy::all)]
#![allow(non_camel_case_types)]
#![allow(deref_nullptr)]
#![allow(non_snake_case)]
use libc::{epoll_event, fd_set, sockaddr, sockaddr_in, sockaddr_in6, socklen_t, time_t};
use crate::coap_pdu_type_t::COAP_MESSAGE_RST;
#[cfg(feature = "dtls_backend_gnutls")]
use gnutls_sys as _;
#[cfg(feature = "dtls_backend_mbedtls")]
use mbedtls_sys as _;
#[cfg(feature = "dtls_backend_openssl")]
use openssl_sys as _;
#[cfg(feature = "dtls_backend_tinydtls")]
use tinydtls_sys as _;
#[cfg(target_family = "windows")]
include!(concat!(env!("OUT_DIR"), "\\bindings.rs"));
#[cfg(not(target_family = "windows"))]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[inline]
pub unsafe fn coap_send_rst(
session: *mut coap_session_t,
request: *const coap_pdu_t,
_type_: coap_pdu_type_t,
) -> coap_mid_t {
coap_send_message_type(session, request, COAP_MESSAGE_RST)
}
#[cfg(test)]
mod tests {
use std::{
ffi::c_void,
net::{SocketAddr, UdpSocket},
os::raw::c_int,
sync::{Arc, Barrier},
};
use libc::{in6_addr, in_addr, sa_family_t, size_t, AF_INET, AF_INET6};
use super::*;
use crate::{
coap_pdu_code_t::{COAP_REQUEST_CODE_GET, COAP_RESPONSE_CODE_CONTENT},
coap_proto_t::COAP_PROTO_UDP,
coap_request_t::COAP_REQUEST_GET,
coap_response_t::COAP_RESPONSE_OK,
};
const COAP_TEST_RESOURCE_URI: &str = "test";
const COAP_TEST_RESOURCE_RESPONSE: &str = "Hello World!";
fn coap_address_from_socketaddr(addr: &SocketAddr) -> coap_address_t {
match addr {
SocketAddr::V4(addr) => {
unsafe {
let mut coap_addr = coap_address_t {
size: std::mem::size_of::<sockaddr_in>() as socklen_t,
addr: std::mem::zeroed(),
};
*coap_addr.addr.sin.as_mut() = sockaddr_in {
sin_family: AF_INET as sa_family_t,
sin_port: addr.port().to_be(),
sin_addr: in_addr {
s_addr: u32::from_ne_bytes(addr.ip().octets()),
},
sin_zero: Default::default(),
};
coap_addr
}
},
SocketAddr::V6(addr) => {
unsafe {
let mut coap_addr = coap_address_t {
size: std::mem::size_of::<sockaddr_in6>() as socklen_t,
addr: std::mem::zeroed(),
};
*coap_addr.addr.sin6.as_mut() = sockaddr_in6 {
sin6_family: AF_INET6 as sa_family_t,
sin6_port: addr.port().to_be(),
sin6_addr: in6_addr {
s6_addr: addr.ip().octets(),
},
sin6_flowinfo: addr.flowinfo(),
sin6_scope_id: addr.scope_id(),
};
coap_addr
}
},
}
}
unsafe extern "C" fn test_resource_handler(
_resource: *mut coap_resource_t,
session: *mut coap_session_t,
_incoming_pdu: *const coap_pdu_t,
_query: *const coap_string_t,
response_pdu: *mut coap_pdu_t,
) {
let mut buf: [u8; 3] = [0; 3];
coap_add_option(
response_pdu,
COAP_OPTION_CONTENT_TYPE as coap_option_num_t,
coap_encode_var_safe(buf.as_mut_ptr(), buf.len(), COAP_MEDIATYPE_TEXT_PLAIN) as usize,
buf.as_ptr(),
);
coap_add_data(
response_pdu,
COAP_TEST_RESOURCE_RESPONSE.len(),
COAP_TEST_RESOURCE_RESPONSE.as_ptr(),
);
coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void);
coap_pdu_set_code(response_pdu, COAP_RESPONSE_CODE_CONTENT);
}
unsafe extern "C" fn test_response_handler(
session: *mut coap_session_t,
_sent: *const coap_pdu_t,
received: *const coap_pdu_t,
_mid: coap_mid_t,
) -> coap_response_t {
assert_eq!(coap_pdu_get_code(received), COAP_RESPONSE_CODE_CONTENT);
let mut len: size_t = 0;
let mut data: *const u8 = std::ptr::null();
assert_ne!(coap_get_data(received, &mut len, &mut data), 0);
let data = std::slice::from_raw_parts(data, len);
assert_eq!(data, COAP_TEST_RESOURCE_RESPONSE.as_bytes());
coap_set_app_data(coap_session_get_context(session), (&true) as *const bool as *mut c_void);
return COAP_RESPONSE_OK;
}
fn run_coap_test_server(addr: &SocketAddr, barrier: Arc<Barrier>) {
let context = unsafe { coap_new_context(std::ptr::null()) };
assert!(!context.is_null());
let address: coap_address_t = coap_address_from_socketaddr(addr);
let endpoint = unsafe { coap_new_endpoint(context, &address, coap_proto_t::COAP_PROTO_UDP) };
assert!(!endpoint.is_null());
let uri = unsafe { coap_new_str_const(COAP_TEST_RESOURCE_URI.as_ptr(), COAP_TEST_RESOURCE_URI.len()) };
assert!(!uri.is_null());
let test_resource = unsafe { coap_resource_init(uri, COAP_RESOURCE_FLAGS_RELEASE_URI as c_int) };
assert!(!test_resource.is_null());
unsafe {
coap_register_request_handler(test_resource, COAP_REQUEST_GET, Some(test_resource_handler));
coap_add_resource(context, test_resource);
coap_set_app_data(context, (&false) as *const bool as *mut c_void);
}
barrier.wait();
loop {
let time_spent_millis = unsafe { coap_io_process(context, 10000) };
if unsafe { *(coap_get_app_data(context) as *const bool).as_ref().unwrap() } {
break;
}
if time_spent_millis == -1 || time_spent_millis >= 10000 {
panic!("Test timeout exceeded");
}
}
unsafe {
coap_free_context(context);
std::mem::drop(context);
std::mem::drop(test_resource);
}
}
#[test]
fn test_coap_client_server_basic() {
let server_address = UdpSocket::bind("localhost:0")
.expect("Failed to bind server socket")
.local_addr()
.expect("Failed to get server socket address");
let preparation_barrier = Arc::new(Barrier::new(2));
let server_address_clone = server_address.clone();
let preparation_barrier_clone = preparation_barrier.clone();
let server_thread_handle =
std::thread::spawn(move || run_coap_test_server(&server_address_clone, preparation_barrier_clone));
let context = unsafe { coap_new_context(std::ptr::null()) };
assert!(!context.is_null());
preparation_barrier.wait();
let server_address = coap_address_from_socketaddr(&server_address);
let client_session =
unsafe { coap_new_client_session(context, std::ptr::null(), &server_address, COAP_PROTO_UDP) };
unsafe {
coap_register_response_handler(context, Some(test_response_handler));
coap_set_app_data(context, (&false) as *const bool as *mut c_void);
let coap_request_pdu =
coap_new_pdu(coap_pdu_type_t::COAP_MESSAGE_NON, COAP_REQUEST_CODE_GET, client_session);
assert!(!coap_request_pdu.is_null());
assert_ne!(
coap_add_option(
coap_request_pdu,
COAP_OPTION_URI_PATH as coap_option_num_t,
COAP_TEST_RESOURCE_URI.len(),
COAP_TEST_RESOURCE_URI.as_ptr(),
),
0
);
assert_ne!(coap_send(client_session, coap_request_pdu), COAP_INVALID_MID);
}
loop {
let time_spent_millis = unsafe { coap_io_process(context, 10000) };
if unsafe { *(coap_get_app_data(context) as *const bool).as_ref().unwrap() } {
break;
}
if time_spent_millis == -1 || time_spent_millis >= 10000 {
panic!("Test timeout exceeded");
}
}
unsafe {
coap_free_context(context);
std::mem::drop(context);
std::mem::drop(client_session)
}
server_thread_handle.join().expect("Error waiting for server thread");
}
}