Skip to content

Commit

Permalink
feat(transport): Implement support for CoAP over tcp (#12)
Browse files Browse the repository at this point in the history
* Implement support for CoAP over tcp

Implement the missing structures.
Add tests.

* Tests for CoAP over TCP.

* Fix the license comment to point to the tcp module.

* Update comments

Fix copy/paste comments. Everything should reffer to the TCP
functionality.
  • Loading branch information
petardimitrijevic authored Jul 3, 2023
1 parent 600470b commit 0e17941
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 11 deletions.
18 changes: 12 additions & 6 deletions libcoap/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::{
error::{ContextCreationError, EndpointCreationError, IoProcessError},
resource::{CoapResource, UntypedCoapResource},
session::session_response_handler,
transport::{CoapEndpoint, CoapUdpEndpoint},
transport::{CoapEndpoint, CoapUdpEndpoint, CoapTcpEndpoint},
};

#[derive(Debug)]
Expand Down Expand Up @@ -239,10 +239,16 @@ impl CoapContext<'_> {
Ok(())
}

/// TODO
/// Creates a new TCP endpoint that is bound to the given address.
#[cfg(feature = "tcp")]
pub fn add_endpoint_tcp(&mut self, _addr: SocketAddr) -> Result<(), EndpointCreationError> {
todo!()
pub fn add_endpoint_tcp(&mut self, addr: SocketAddr) -> Result<(), EndpointCreationError> {
// SAFETY: Because we never return an owned reference to the endpoint, it cannot outlive the
// context it is bound to (i.e. this one).
let endpoint = unsafe { CoapTcpEndpoint::new(self, addr)? }.into();
let mut inner_ref = self.inner.borrow_mut();
inner_ref.endpoints.push(endpoint);
// Cannot fail, we just pushed to the Vec.
Ok(())
}

/// Creates a new DTLS endpoint that is bound to the given address.
Expand Down Expand Up @@ -607,7 +613,7 @@ impl CoapContext<'_> {
/// (will cause an abort on drop)
/// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
/// cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
/// use anything related to the context again, but why would you do that?)
/// use anything related to the context again, but why would you do that?)
// Kept here for consistency, even though it is unused.
#[allow(unused)]
pub(crate) unsafe fn as_raw_context(&self) -> &coap_context_t {
Expand All @@ -630,7 +636,7 @@ impl CoapContext<'_> {
/// (will cause an abort on drop)
/// - Calling `coap_free_context()` on this context (for obvious reasons, this will probably
/// cause a segfault if you don't immediately [std::mem::forget()] the CoapContext and never
/// use anything related to the context again, but why would you do that?)
/// use anything related to the context again, but why would you do that?)
pub(crate) unsafe fn as_mut_raw_context(&mut self) -> &mut coap_context_t {
// SAFETY: raw_context is checked to be a valid pointer on struct instantiation, cannot be
// freed by anything outside of here (assuming the contract of this function is kept), and
Expand Down
9 changes: 8 additions & 1 deletion libcoap/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use std::fmt::Debug;

use libcoap_sys::{coap_event_t, coap_session_get_context, coap_session_t};
use libcoap_sys::{coap_session_get_type, coap_session_type_t};

use crate::context::CoapContext;
use crate::session::CoapSession;
Expand Down Expand Up @@ -110,11 +111,17 @@ pub trait CoapEventHandler: Debug {
// This should be fine as we don't provide this type to a FFI function, we only read from it.
#[allow(improper_ctypes_definitions)]
pub(crate) unsafe extern "C" fn event_handler_callback(raw_session: *mut coap_session_t, event: coap_event_t) -> i32 {
let session: CoapSession = if event == coap_event_t::COAP_EVENT_SERVER_SESSION_NEW {
let raw_session_type = coap_session_get_type(raw_session);

let session: CoapSession = if event == coap_event_t::COAP_EVENT_SERVER_SESSION_NEW
|| (event == coap_event_t::COAP_EVENT_TCP_CONNECTED
&& raw_session_type == coap_session_type_t::COAP_SESSION_TYPE_SERVER)
{
CoapServerSession::initialize_raw(raw_session).into()
} else {
CoapSession::from_raw(raw_session)
};

// SAFETY: Pointer is always valid as long as there is no bug in libcoap.
let context = CoapContext::from_raw(coap_session_get_context(raw_session));
context.handle_event(session, event);
Expand Down
35 changes: 35 additions & 0 deletions libcoap/src/session/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,41 @@ impl CoapClientSession<'_> {
})
}


/// Create a new unencrypted session with the given peer over TCP.
///
/// # Errors
/// Will return a [SessionCreationError] if libcoap was unable to create a session (most likely
/// because it was not possible to bind to a port).
pub fn connect_tcp<'a>(
ctx: &mut CoapContext<'a>,
addr: SocketAddr,
) -> Result<CoapClientSession<'a>, SessionCreationError> {
// SAFETY: self.raw_context is guaranteed to be valid, local_if can be null.
let session = unsafe {
coap_new_client_session(
ctx.as_mut_raw_context(),
std::ptr::null(),
CoapAddress::from(addr).as_raw_address(),
coap_proto_t::COAP_PROTO_TCP,
)
};
if session.is_null() {
return Err(SessionCreationError::Unknown);
}
// SAFETY: Session was just checked for validity, no crypto info was provided to
// coap_new_client_session().
Ok(unsafe {
CoapClientSession::new(
session as *mut coap_session_t,
#[cfg(feature = "dtls")]
None,
#[cfg(feature = "dtls")]
None,
)
})
}

/// Initializes a new CoapClientSession from its raw counterpart with the provided initial
/// information.
///
Expand Down
16 changes: 16 additions & 0 deletions libcoap/src/transport/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use libcoap_sys::{coap_endpoint_set_default_mtu, coap_endpoint_t};
pub use dtls::CoapDtlsEndpoint;
pub use udp::CoapUdpEndpoint;

#[cfg(feature = "tcp")]
pub use tcp::CoapTcpEndpoint;

#[cfg(feature = "dtls")]
mod dtls;
#[cfg(feature = "tcp")]
Expand Down Expand Up @@ -63,6 +66,8 @@ pub trait EndpointCommon {
#[derive(Debug)]
pub enum CoapEndpoint {
Udp(CoapUdpEndpoint),
#[cfg(feature = "tcp")]
Tcp(CoapTcpEndpoint),
#[cfg(feature = "dtls")]
Dtls(CoapDtlsEndpoint),
}
Expand All @@ -73,6 +78,13 @@ impl From<CoapUdpEndpoint> for CoapEndpoint {
}
}

#[cfg(feature = "tcp")]
impl From<CoapTcpEndpoint> for CoapEndpoint {
fn from(ep: CoapTcpEndpoint) -> Self {
CoapEndpoint::Tcp(ep)
}
}

#[cfg(feature = "dtls")]
impl From<CoapDtlsEndpoint> for CoapEndpoint {
fn from(ep: CoapDtlsEndpoint) -> Self {
Expand All @@ -84,6 +96,8 @@ impl EndpointCommon for CoapEndpoint {
unsafe fn as_raw_endpoint(&self) -> &coap_endpoint_t {
match self {
CoapEndpoint::Udp(ep) => ep.as_raw_endpoint(),
#[cfg(feature = "tcp")]
CoapEndpoint::Tcp(ep) => ep.as_raw_endpoint(),
#[cfg(feature = "dtls")]
CoapEndpoint::Dtls(ep) => ep.as_raw_endpoint(),
}
Expand All @@ -92,6 +106,8 @@ impl EndpointCommon for CoapEndpoint {
unsafe fn as_mut_raw_endpoint(&mut self) -> &mut coap_endpoint_t {
match self {
CoapEndpoint::Udp(ep) => ep.as_mut_raw_endpoint(),
#[cfg(feature = "tcp")]
CoapEndpoint::Tcp(ep) => ep.as_mut_raw_endpoint(),
#[cfg(feature = "dtls")]
CoapEndpoint::Dtls(ep) => ep.as_mut_raw_endpoint(),
}
Expand Down
81 changes: 77 additions & 4 deletions libcoap/src/transport/tcp.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,86 @@
// SPDX-License-Identifier: BSD-2-Clause
/*
* transport/dtls.rs - transport-specific code for TCP.
* transport/tcp.rs - transport-specific code for TCP.
* This file is part of the libcoap-rs crate, see the README and LICENSE files for
* more information and terms of use.
* Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
* See the README as well as the LICENSE file for more information.
*/

/// TODO
#[allow(dead_code)]
use libcoap_sys::{coap_endpoint_t, coap_free_endpoint, coap_new_endpoint, coap_proto_t::COAP_PROTO_TCP};

use crate::{context::CoapContext, error::EndpointCreationError, transport::EndpointCommon, types::CoapAddress};

use std::net::SocketAddr;

#[cfg(feature = "tcp")]
pub struct CoapTcpEndpoint {}
#[derive(Debug)]
pub struct CoapTcpEndpoint {
raw_endpoint: *mut coap_endpoint_t,
}

impl CoapTcpEndpoint {
/// Creates a new CoapTcpEndpoint and binds it to the supplied SocketAddr.
///
/// This is an unsafe function (see #Safety for an explanation of why) used internally by
/// libcoap-rs to instantiate new endpoints. You should most likely not use this function, and
/// use one of the following alternatives instead:
/// - If you just want to add an endpoint to the coap context, use [CoapContext::add_endpoint_tcp()].
/// - If you need to modify the underlying [coap_endpoint_t] directly (in an unsafe manner), use
/// [CoapContext::add_endpoint_tcp()] to instantiate the endpoint and then [as_mut_raw_endpoint()]
/// to access the underlying struct.
///
/// # Safety
/// All endpoint types defined in this crate contain a [coap_endpoint_t] instance,
/// which is the representation of endpoints used by the underlying libcoap C library.
///
/// On instantiation, these [coap_endpoint_t] instances are bound to a context, which includes
/// adding them to a list maintained by the [CoapContext] (or – to be more specific – the
/// underlying [libcoap_sys::coap_context_t].
///
/// When the context that this endpoint is bound to is dropped, the context calls [libcoap_sys::coap_free_context()],
/// which will not only free the context, but also all [coap_endpoint_t] instances associated
/// with it, including the one this struct points to.
///
/// Therefore, if you decide to use this function anyway, you have to ensure that the
/// CoapContext lives at least as long as this struct does.
/// Also note that unlike [CoapContext::add_endpoint_tcp()], this function does not add the
/// endpoint to the [CoapContext::endpoints] vector, while the underlying [coap_endpoint_t] is
/// added to the underlying [libcoap_sys::coap_context_t]
pub(crate) unsafe fn new(context: &mut CoapContext, addr: SocketAddr) -> Result<Self, EndpointCreationError> {
let endpoint = coap_new_endpoint(
context.as_mut_raw_context(),
CoapAddress::from(addr).as_raw_address(),
COAP_PROTO_TCP,
);
if endpoint.is_null() {
return Err(EndpointCreationError::Unknown);
}
Ok(Self { raw_endpoint: endpoint })
}
}

impl EndpointCommon for CoapTcpEndpoint {
unsafe fn as_raw_endpoint(&self) -> &coap_endpoint_t {
// SAFETY: raw_endpoint is checked to be a valid pointer on struct instantiation, cannot be
// freed by anything outside of here (assuming the contract of this function is kept), and
// the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
// is).
&*self.raw_endpoint
}

unsafe fn as_mut_raw_endpoint(&mut self) -> &mut coap_endpoint_t {
// SAFETY: raw_endpoint is checked to be a valid pointer on struct instantiation, is not
// freed by anything outside of here (assuming the contract of this function is kept), and
// the default (elided) lifetimes are correct (the pointer is valid as long as the endpoint
// is).
&mut *self.raw_endpoint
}
}

impl Drop for CoapTcpEndpoint {
fn drop(&mut self) {
// SAFETY: Raw endpoint is guaranteed to exist for as long as the container exists.
unsafe { coap_free_endpoint(self.raw_endpoint) }
}
}
41 changes: 41 additions & 0 deletions libcoap/tests/tcp_client_server_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-2-Clause
/*
* tcp_client_server_test.rs - Tests for TCP clients+servers.
* This file is part of the libcoap-rs crate, see the README and LICENSE files for
* more information and terms of use.
* Copyright © 2021-2023 The NAMIB Project Developers, all rights reserved.
* See the README as well as the LICENSE file for more information.
*/

use libcoap_rs::session::CoapClientSession;
use libcoap_rs::{
message::CoapMessageCommon,
protocol::{CoapMessageCode, CoapResponseCode},
session::CoapSessionCommon,
CoapContext,
};
use std::time::Duration;

mod common;

#[test]
pub fn basic_client_server_request() {
let server_address = common::get_unused_server_addr();

let server_handle = common::spawn_test_server(move |context| context.add_endpoint_tcp(server_address).unwrap());

let mut context = CoapContext::new().unwrap();
let session = CoapClientSession::connect_tcp(&mut context, server_address).unwrap();

let request = common::gen_test_request(server_address);
let req_handle = session.send_request(request).unwrap();
loop {
assert!(context.do_io(Some(Duration::from_secs(10))).expect("error during IO") <= Duration::from_secs(10));
for response in session.poll_handle(&req_handle) {
assert_eq!(response.code(), CoapMessageCode::Response(CoapResponseCode::Content));
assert_eq!(response.data().unwrap().as_ref(), "Hello World!".as_bytes());
server_handle.join().unwrap();
return;
}
}
}

0 comments on commit 0e17941

Please sign in to comment.