tinydtls_sys/lib.rs
1// SPDX-License-Identifier: EPL-1.0 OR BSD-3-CLAUSE
2/*
3 * lib.rs - Main library file for raw TinyDTLS Rust bindings.
4 * Copyright (c) 2021 The NAMIB Project Developers, all rights reserved.
5 * See the README as well as the LICENSE file for more information.
6 */
7
8// Bindgen translates the C headers, clippy's and rustfmt's recommendations are not applicable here.
9#![allow(clippy::all)]
10#![allow(non_camel_case_types)]
11#![allow(non_snake_case)]
12#![allow(non_upper_case_globals)]
13#![allow(deref_nullptr)]
14
15use std::fmt;
16
17use libc::{sockaddr, sockaddr_in, sockaddr_in6, sockaddr_storage, socklen_t};
18
19#[cfg(target_family = "windows")]
20include!(concat!(env!("OUT_DIR"), "\\bindings.rs"));
21#[cfg(not(target_family = "windows"))]
22include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
23
24// The dtls_set_handler function in tinydtls is an inline function, which is why it is not included
25// in the bindgen-generated bindings.
26// There is a workaround that could be applied to the vendored version (by setting a compiler flag
27// that forces inclusion of inline functions in generated binaries), but this would only work for
28// the vendored version and would also prevent inlining, reducing performance.
29// Therefore, we just re-implement it here.
30//
31// See https://rust-lang.github.io/rust-bindgen/faq.html#why-isnt-bindgen-generating-bindings-to-inline-functions
32#[inline]
33pub unsafe fn dtls_set_handler(ctx: *mut dtls_context_t, h: *mut dtls_handler_t) {
34 (*ctx).h = h;
35}
36
37// For backwards-compatibility, we add a Debug implementation of dtls_hello_verify_t.
38// (Automatic derive stopped working with https://github.com/rust-lang/rust/pull/104429.)
39impl fmt::Debug for dtls_hello_verify_t {
40 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
41 let Self { version, cookie_length, cookie } = self;
42 fmt.debug_struct("dtls_hello_verify_t")
43 .field("version", version)
44 .field("cookie_length", cookie_length)
45 .field("cookie", cookie)
46 .finish()
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use lazy_static::lazy_static;
54 use libc::{c_int, c_uchar, c_ushort, in6_addr, in_addr, size_t};
55 use std::collections::HashMap;
56 use std::ffi::c_void;
57 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket};
58 use std::time::Duration;
59
60 const AF_INET: u16 = libc::AF_INET as u16;
61 const AF_INET6: u16 = libc::AF_INET6 as u16;
62
63 /// Message that the UDP echo server listens for (unencrypted) in order to determine whether it
64 /// should shut down.
65 const TERMINATE_SERVER_MESSAGE: &str = "TERMINATE SERVER";
66 /// Message that should be sent over the encrypted channel for the UDP echo client/server test
67 const ENC_MESSAGE: &str = "Encrypted Example Message";
68 /// Name of the key used for the UDP echo client/server test.
69 #[cfg(feature = "psk")]
70 const PSK_IDENTITY: &str = "testkey";
71
72 #[cfg(feature = "psk")]
73 lazy_static! {
74 /// Map for DTLS keys used in tests.
75 static ref DTLS_KEYS: HashMap<&'static str, [u8; 16]> = {
76 let mut map: HashMap<&'static str, [u8; 16]> = HashMap::new();
77 map.insert(
78 PSK_IDENTITY,
79 [
80 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
81 ],
82 );
83 map
84 };
85 }
86
87 struct EchoTestClientState {
88 socket: UdpSocket,
89 finished: bool,
90 }
91
92 /// Converts a session pointer into a SocketAddr instance.
93 ///
94 /// # Safety
95 /// The session is assumed to be a valid pointer to a session_t struct, whose addr-field
96 /// contains a valid sockaddr value.
97 unsafe fn session_to_socketaddr(session: *mut session_t) -> SocketAddr {
98 let raw_target_addr = &session.as_ref().unwrap().addr;
99
100 // SAFETY: Value is always some kind of sockaddr, so sa_family will be set.
101 let family = raw_target_addr.sa.as_ref().sa_family;
102
103 match family {
104 AF_INET => {
105 // SAFETY: We checked the value of sa_family to ensure that this value should actually be a sockaddr_in
106 let raw_target_addr = raw_target_addr.sin.as_ref();
107 // Sockaddr fields are in network byte order, so calling to_ne_bytes() will give us the value in network byte order, no matter the system endianness.
108 let target_addr = Ipv4Addr::from(raw_target_addr.sin_addr.s_addr.to_ne_bytes());
109 SocketAddr::V4(SocketAddrV4::new(target_addr, u16::from_be(raw_target_addr.sin_port)))
110 }
111 AF_INET6 => {
112 // SAFETY: We checked the value of sa_family to ensure that this value should actually be a sockaddr_in6
113 let raw_target_addr = raw_target_addr.sin6.as_ref();
114 // Sockaddr fields are in network byte order, so calling to_ne_bytes() will give us the value in network byte order, no matter the system endianness.
115 let target_addr = Ipv6Addr::from(raw_target_addr.sin6_addr.s6_addr);
116 SocketAddr::V6(SocketAddrV6::new(
117 target_addr,
118 u16::from_be(raw_target_addr.sin6_port),
119 raw_target_addr.sin6_flowinfo,
120 raw_target_addr.sin6_scope_id,
121 ))
122 }
123 // session.addr value is either sockaddr_in or sockaddr_in6, so sa_family has to either be AF_INET or AF_INET6
124 _ => panic!("Invalid session address family"),
125 }
126 }
127
128 /// Converts a SocketAddr instance into a freshly allocated session_t struct and returns a
129 /// pointer to it.
130 ///
131 /// NOTE: The session_t is created using dtls_new_session() and has to be freed manually using
132 /// dtls_free_session().
133 /// This function returns the return value of dtls_new_session directly, therefore the pointer
134 /// can be a null pointer(!).
135 fn session_from_socketaddr(addr: &SocketAddr) -> *mut session_t {
136 match addr {
137 SocketAddr::V4(addr) => {
138 let mut raw_addr = sockaddr_in {
139 sin_family: AF_INET,
140 sin_port: addr.port().to_be(),
141 sin_addr: in_addr {
142 s_addr: u32::from_ne_bytes(addr.ip().octets()),
143 },
144 sin_zero: [0; 8],
145 };
146 // SAFETY: All pointers are valid, the supplied size is the size of the supplied struct.
147 unsafe {
148 dtls_new_session(
149 &mut raw_addr as *mut sockaddr_in as *mut sockaddr,
150 std::mem::size_of::<sockaddr_in>() as socklen_t,
151 )
152 }
153 }
154 SocketAddr::V6(addr) => {
155 let mut raw_addr = sockaddr_in6 {
156 sin6_family: AF_INET6,
157 sin6_port: addr.port().to_be(),
158 sin6_flowinfo: addr.flowinfo(),
159 sin6_addr: in6_addr {
160 s6_addr: addr.ip().octets(),
161 },
162 sin6_scope_id: addr.scope_id(),
163 };
164 // SAFETY: All pointers are valid, the supplied size is the size of the supplied struct.
165 unsafe {
166 dtls_new_session(
167 &mut raw_addr as *mut sockaddr_in6 as *mut sockaddr,
168 std::mem::size_of::<sockaddr_in6>() as socklen_t,
169 )
170 }
171 }
172 }
173 }
174
175 /// Send callback for the UDP Echo Server Test (server side)
176 ///
177 /// # Safety
178 /// This function is intended to be set as the write/send callback for a dtls context, and
179 /// therefore expects the function arguments to match the values that tinydtls would set.
180 /// It assumes that all supplied pointers are valid, that `data` points to a memory area of at
181 /// least size `len`, and that the [app](dtls_context_t.app) field in the ctx argument is set to
182 /// a *mut UdpSocket of the socket that should be used for the server.
183 unsafe extern "C" fn echo_server_send_callback(
184 ctx: *mut dtls_context_t,
185 session: *mut session_t,
186 data: *mut uint8,
187 len: size_t,
188 ) -> i32 {
189 // SAFETY: Pointers are assumed to be valid, and data should point to a memory area of at least size len.
190 let data = std::slice::from_raw_parts(data, len);
191 let target_addr = session_to_socketaddr(session);
192
193 println!("[ECHO SEND] sending {} bytes to {}: {:?}", len, &target_addr, data);
194
195 // SAFETY: app_data is assumed to be set to a pointer to Rc<UdpSocket> by us on context creation.
196 let socket = (ctx.as_ref().unwrap().app as *mut UdpSocket).as_ref().unwrap();
197 match socket.send_to(data, target_addr) {
198 Ok(num_bytes) => num_bytes as i32,
199 Err(e) => e.raw_os_error().unwrap_or(-1),
200 }
201 }
202
203 /// Send callback for the UDP Echo Server Test (client side)
204 ///
205 /// # Safety
206 /// This function is intended to be set as the write/send callback for a dtls context, and
207 /// therefore expects the function arguments to match the values that tinydtls would set.
208 /// It assumes that all supplied pointers are valid, that `data` points to a memory area of at
209 /// least size `len`, and that the [app](dtls_context_t.app) field in the ctx argument is set to
210 /// a *mut EchoTestClientState representing the echo test client.
211 unsafe extern "C" fn echo_client_send_callback(
212 ctx: *mut dtls_context_t,
213 session: *mut session_t,
214 data: *mut uint8,
215 len: size_t,
216 ) -> i32 {
217 let data = std::slice::from_raw_parts(data, len);
218 let target_addr = session_to_socketaddr(session);
219
220 println!("[ECHO SEND] sending {} bytes to {}: {:?}", len, &target_addr, data);
221
222 let socket = &(ctx.as_ref().unwrap().app as *mut EchoTestClientState)
223 .as_ref()
224 .unwrap()
225 .socket;
226 match socket.send_to(data, target_addr) {
227 Ok(num_bytes) => num_bytes as i32,
228 Err(e) => e.raw_os_error().unwrap_or(-1),
229 }
230 }
231
232 /// PSK information callback for the UDP Echo Server Test
233 ///
234 /// # Safety
235 /// This function is intended to be set as the get_psk_info callback for a dtls context, and
236 /// therefore expects the function arguments to match the values that tinydtls would set.
237 /// It assumes that all supplied pointers are valid and that `desc`/`result` point to memory
238 /// areas of at least size `desc_len`/`result_length`.
239 #[cfg(feature = "psk")]
240 unsafe extern "C" fn echo_get_psk_info(
241 _ctx: *mut dtls_context_t,
242 _session: *const session_t,
243 type_: dtls_credentials_type_t,
244 desc: *const c_uchar,
245 desc_len: size_t,
246 result: *mut c_uchar,
247 result_length: size_t,
248 ) -> i32 {
249 let result = std::slice::from_raw_parts_mut(result, result_length);
250 let desc = std::slice::from_raw_parts(desc, desc_len);
251 match type_ {
252 dtls_credentials_type_t::DTLS_PSK_HINT | dtls_credentials_type_t::DTLS_PSK_IDENTITY => {
253 if result_length < PSK_IDENTITY.len() {
254 panic!("Result field too small to provide PSK identity/hint value")
255 }
256 result[..PSK_IDENTITY.len()].clone_from_slice(&PSK_IDENTITY.as_bytes());
257 PSK_IDENTITY.len() as i32
258 }
259 dtls_credentials_type_t::DTLS_PSK_KEY => {
260 if result_length < DTLS_KEYS.get(PSK_IDENTITY).unwrap().len() {
261 panic!("Result field too small to provide PSK key value")
262 }
263 result[..DTLS_KEYS
264 .get(std::str::from_utf8(desc).expect("Invalid PSK Identity"))
265 .unwrap()
266 .len()]
267 .clone_from_slice(
268 DTLS_KEYS
269 .get(std::str::from_utf8(desc).expect("Invalid PSK Identity"))
270 .unwrap(),
271 );
272 DTLS_KEYS
273 .get(std::str::from_utf8(desc).expect("Invalid PSK Identity"))
274 .unwrap()
275 .len() as i32
276 }
277 }
278 }
279
280 /// Read callback for the UDP Echo Server Test (server-side).
281 ///
282 /// # Safety
283 /// This function is intended to be set as the read callback for a dtls context, and
284 /// therefore expects the function arguments to match the values that tinydtls would set.
285 /// It assumes that all supplied pointers are valid and that `data` points to a memory area of at
286 /// least size `len` containing the received and decrypted data.
287 unsafe extern "C" fn echo_server_read_callback(
288 ctx: *mut dtls_context_t,
289 session: *mut session_t,
290 data: *mut uint8,
291 len: size_t,
292 ) -> i32 {
293 println!(
294 "[ECHO SERVER] received {} bytes from {}: {:?}",
295 len,
296 session_to_socketaddr(session),
297 data
298 );
299 dtls_write(ctx, session, data, len)
300 }
301
302 /// Read callback for the UDP Echo Server Test (client-side).
303 ///
304 /// # Safety
305 /// This function is intended to be set as the read callback for a dtls context, and
306 /// therefore expects the function arguments to match the values that tinydtls would set.
307 /// It assumes that all supplied pointers are valid, that `data` points to a memory area of at
308 /// least size `len` containing the received and decrypted data, and that the [app](dtls_context_t.app)
309 /// field in the ctx argument is set to a *mut EchoTestClientState representing the echo test
310 /// client.
311 unsafe extern "C" fn echo_client_read_callback(
312 ctx: *mut dtls_context_t,
313 session: *mut session_t,
314 data: *mut uint8,
315 len: size_t,
316 ) -> i32 {
317 let data = std::slice::from_raw_parts(data, len);
318 println!(
319 "[ECHO CLIENT] received {} bytes from {}: {:?}",
320 len,
321 session_to_socketaddr(session),
322 data
323 );
324 assert_eq!(data, ENC_MESSAGE.as_bytes());
325
326 (ctx.as_ref().unwrap().app as *mut EchoTestClientState)
327 .as_mut()
328 .unwrap()
329 .finished = true;
330 let socket = &(ctx.as_ref().unwrap().app as *mut EchoTestClientState)
331 .as_ref()
332 .unwrap()
333 .socket;
334 match socket.send_to(TERMINATE_SERVER_MESSAGE.as_bytes(), session_to_socketaddr(session)) {
335 Ok(count) => count as i32,
336 Err(e) => e.raw_os_error().unwrap_or(-1),
337 }
338 }
339
340 /// Event callback for the UDP Echo Server Test (client-side)
341 ///
342 /// # Safety
343 /// This function is intended to be set as the event callback for a dtls context, and
344 /// therefore expects the function arguments to match the values that tinydtls would set.
345 /// It assumes that all supplied pointers are valid.
346 unsafe extern "C" fn echo_client_event_callback(
347 ctx: *mut dtls_context_t,
348 session: *mut session_t,
349 level: dtls_alert_level_t,
350 code: c_ushort,
351 ) -> i32 {
352 println!(
353 "[ECHO CLIENT] received Event from {} (level {:?}): {}",
354 session_to_socketaddr(session),
355 level,
356 code
357 );
358 if level == dtls_alert_level_t::DTLS_ALERT_LEVEL_FATAL && u32::from(code) != DTLS_EVENT_CONNECTED {
359 panic!("Fatal error in DTLS session")
360 }
361 match code as u32 {
362 DTLS_EVENT_CONNECTED => {
363 let mut buf = [0; ENC_MESSAGE.len()];
364 buf.clone_from_slice(ENC_MESSAGE.as_bytes());
365 dtls_write(ctx, session, buf.as_mut_ptr(), buf.len())
366 }
367 _ => 0,
368 }
369 }
370
371 /// Event callback for the UDP Echo Server Test (server-side)
372 ///
373 /// # Safety
374 /// This function is intended to be set as the event callback for a dtls context, and
375 /// therefore expects the function arguments to match the values that tinydtls would set.
376 /// It assumes that all supplied pointers are valid.
377 unsafe extern "C" fn echo_server_event_callback(
378 _ctx: *mut dtls_context_t,
379 session: *mut session_t,
380 level: dtls_alert_level_t,
381 code: c_ushort,
382 ) -> i32 {
383 println!(
384 "[ECHO SERVER] received Event from {} (level {:?}): {}",
385 session_to_socketaddr(session),
386 level,
387 code
388 );
389 if level == dtls_alert_level_t::DTLS_ALERT_LEVEL_FATAL && u32::from(code) != DTLS_EVENT_CONNECTED {
390 panic!("Fatal error in DTLS session")
391 }
392 0
393 }
394
395 /// Run the UDP DTLS echo server used for the [test_dtls_echo_client_server()] test.
396 #[cfg(feature = "psk")]
397 fn run_dtls_echo_server(mut socket: UdpSocket) {
398 let mut dtls_handlers = dtls_handler_t {
399 write: Some(echo_server_send_callback),
400 read: Some(echo_server_read_callback),
401 event: Some(echo_server_event_callback),
402 get_psk_info: Some(echo_get_psk_info),
403 get_ecdsa_key: None,
404 verify_ecdsa_key: None,
405 get_user_parameters: None,
406 };
407
408 // SAFETY: Supplied pointer is valid, dtls_new_context does not do anything with it except
409 // storing it in its app field.
410 let server_context = unsafe { dtls_new_context(&mut socket as *mut UdpSocket as *mut c_void) };
411 assert!(!server_context.is_null());
412 // SAFETY: Supplied pointers are valid (we just checked server_context, and dtls_handlers is a reference, so it must be valid).
413 unsafe { dtls_set_handler(server_context, &mut dtls_handlers) };
414
415 let mut buf: [u8; 512] = [0; 512];
416 loop {
417 let (read_bytes, peer) = socket.recv_from(&mut buf).expect("Error reading from socket");
418 if read_bytes == TERMINATE_SERVER_MESSAGE.len()
419 && &buf[0..read_bytes] == TERMINATE_SERVER_MESSAGE.as_bytes()
420 {
421 break;
422 }
423 let session = session_from_socketaddr(&peer);
424 assert!(!session.is_null());
425 // SAFETY: server_context has already been checked and is not invalidated by any called methods up to this point.
426 // We just checked that session is not null. msg and msglen are set correctly to our buffer and the length of the read data.
427 // dtls_handle_message() does not modify the session, neither do our handlers, therefore the call to dtls_free_session is valid.
428 unsafe {
429 dtls_handle_message(server_context, session, buf.as_mut_ptr(), read_bytes as c_int);
430 dtls_free_session(session);
431 }
432 }
433 // SAFETY: We have not called anything that would invalidate our context up to this point, so
434 // this pointer should be valid up until here.
435 unsafe {
436 dtls_free_context(server_context);
437 }
438 }
439
440 /// Test case that creates a basic UDP echo server over an encrypted DTLS socket and then sends
441 /// a message to it.
442 /// Based on the example described on the main page of the tinydtls documentation
443 /// (https://github.com/obgm/tinydtls/blob/develop/dtls.h#L416)
444 #[test]
445 #[cfg(feature = "psk")]
446 fn test_dtls_echo_client_server() {
447 // Binding to port 0 gives us any available free port.
448 let server_socket = UdpSocket::bind("localhost:0").expect("Could not bind UDP socket");
449 server_socket
450 .set_read_timeout(Some(Duration::from_secs(10)))
451 .expect("Could not set socket timeout");
452 server_socket
453 .set_write_timeout(Some(Duration::from_secs(10)))
454 .expect("Could not set socket timeout");
455 let server_addr = server_socket.local_addr().unwrap();
456 let server_thread = std::thread::spawn(move || run_dtls_echo_server(server_socket));
457
458 let mut dtls_handlers = dtls_handler_t {
459 write: Some(echo_client_send_callback),
460 read: Some(echo_client_read_callback),
461 event: Some(echo_client_event_callback),
462 get_psk_info: Some(echo_get_psk_info),
463 get_ecdsa_key: None,
464 verify_ecdsa_key: None,
465 get_user_parameters: None,
466 };
467
468 let client_socket = UdpSocket::bind("localhost:0").expect("Could not bind UDP socket");
469 client_socket
470 .set_read_timeout(Some(Duration::from_secs(10)))
471 .expect("Could not set socket timeout");
472 client_socket
473 .set_write_timeout(Some(Duration::from_secs(10)))
474 .expect("Could not set socket timeout");
475 let mut client_state = EchoTestClientState {
476 socket: client_socket,
477 finished: false,
478 };
479 // SAFETY: Supplied pointer is valid, dtls_new_context does not do anything with it except
480 // storing it in its app field.
481 let client_context = unsafe { dtls_new_context(&mut client_state as *mut EchoTestClientState as *mut c_void) };
482 assert!(!client_context.is_null());
483 // SAFETY: Supplied pointers are valid (we just checked client_context, and dtls_handlers is a reference, so it must be valid).
484 unsafe {
485 dtls_set_handler(client_context, &mut dtls_handlers);
486 };
487 let session = session_from_socketaddr(&server_addr);
488 assert!(!session.is_null());
489 // SAFETY: We just checked that session is not a null pointer, and we checked client_context before.
490 // dtls_set_handler also does not do anything to invalidate client_context.
491 unsafe {
492 dtls_connect(client_context, session);
493 }
494
495 let mut buf: [u8; 512] = [0; 512];
496 while !client_state.finished {
497 let (read_bytes, peer) = client_state
498 .socket
499 .recv_from(&mut buf)
500 .expect("Error reading from socket");
501 let session = session_from_socketaddr(&peer);
502 assert!(!session.is_null());
503 // SAFETY: client_context has already been checked and is not invalidated by any called methods up to this point.
504 // We just checked that session is not null. msg and msglen are set correctly to our buffer and the length of the read data.
505 // dtls_handle_message() does not modify the session, neither do our handlers, therefore the call to dtls_free_session is valid.
506 unsafe {
507 dtls_handle_message(client_context, session, buf.as_mut_ptr(), read_bytes as c_int);
508 dtls_free_session(session);
509 }
510 }
511 // SAFETY: We have not called anything that would invalidate our context up to this point, so
512 // this pointer should be valid up until here.
513 unsafe {
514 dtls_free_context(client_context);
515 }
516 server_thread.join().unwrap();
517 }
518}