Skip to content

Implement support for CoAP over tcp #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
components: rust-src
toolchain: nightly
- if: matrix.dtls_backend == 'gnutls'
uses: awalsh128/cache-apt-pkgs-action@latest
with:
Expand Down
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;
}
}
}