coap-zero 0.3.0

CoAP protocol implementation for no_std without alloc
Documentation
// Copyright Open Logistics Foundation
//
// Licensed under the Open Logistics Foundation License 1.3.
// For details on the licensing terms, see the LICENSE file.
// SPDX-License-Identifier: OLFL-1.3

//! Connection Layer of the CoAP [Endpoint](super::CoapEndpoint)
//!
//! Handles all connect and close functionality

use core::str::FromStr;

use embedded_hal::blocking::rng;
use embedded_nal::{AddrType, Dns, IpAddr, SocketAddr, UdpClientStack};
use embedded_timers::clock::Clock;

use super::{error::Error, CoapEndpoint, ConnectionLink, Uri};

impl<
        'a,
        UDP,
        RNG,
        CLOCK,
        const MAX_OPTION_COUNT: usize,
        const MAX_OPTION_SIZE: usize,
        const INCOMING_BUFFER_SIZE: usize,
        const OUTGOING_BUFFER_SIZE: usize,
        const RECEIVE_BUFFER_SIZE: usize,
    >
    CoapEndpoint<
        'a,
        UDP,
        RNG,
        CLOCK,
        MAX_OPTION_COUNT,
        MAX_OPTION_SIZE,
        INCOMING_BUFFER_SIZE,
        OUTGOING_BUFFER_SIZE,
        RECEIVE_BUFFER_SIZE,
    >
where
    UDP: UdpClientStack,
    RNG: rng::Read,
    CLOCK: Clock,
{
    /// Stores an already initialized socket inside the CoAP Endpoint.
    ///
    /// This socket has to already be connected to a remote endpoint.
    pub fn connect_with_socket(
        &mut self,
        socket: UDP::UdpSocket,
        addr: SocketAddr,
    ) -> Result<(), Error<<UDP as UdpClientStack>::Error>> {
        if self.connection_link.is_none() {
            self.connection_link = Some(ConnectionLink { socket, addr });
            Ok(())
        } else {
            Err(Error::AlreadyConnected)
        }
    }

    /// Connects the CoAP Endpoint to a socket address and stores the socket.
    pub fn connect_to_addr<'client>(
        &mut self,
        client: &'client mut UDP,
        addr: SocketAddr,
    ) -> Result<&'client mut UDP, Error<<UDP as UdpClientStack>::Error>> {
        if self.connection_link.is_some() {
            Err(Error::AlreadyConnected)
        } else {
            let mut socket = client.socket().map_err(Error::Network)?;
            client.connect(&mut socket, addr).map_err(Error::Network)?;

            self.connection_link = Some(ConnectionLink { socket, addr });

            Ok(client)
        }
    }

    /// Closes the connection specified by the CoAP Endpoints socket.
    pub fn close<'client>(
        &mut self,
        client: &'client mut UDP,
    ) -> Result<&'client mut UDP, Error<<UDP as UdpClientStack>::Error>> {
        if self.connection_link.is_none() {
            Err(Error::NotConnected)
        } else {
            client
                .close(self.connection_link.take().unwrap().socket)
                .map_err(Error::Network)?;
            self.connection_link = None;

            Ok(client)
        }
    }
}

impl<
        'a,
        UDP,
        RNG,
        CLOCK,
        const MAX_OPTION_COUNT: usize,
        const MAX_OPTION_SIZE: usize,
        const INCOMING_BUFFER_SIZE: usize,
        const OUTGOING_BUFFER_SIZE: usize,
        const RECEIVE_BUFFER_SIZE: usize,
    >
    CoapEndpoint<
        'a,
        UDP,
        RNG,
        CLOCK,
        MAX_OPTION_COUNT,
        MAX_OPTION_SIZE,
        INCOMING_BUFFER_SIZE,
        OUTGOING_BUFFER_SIZE,
        RECEIVE_BUFFER_SIZE,
    >
where
    UDP: UdpClientStack + Dns,
    RNG: rng::Read,
    CLOCK: Clock,
{
    /// Connect the Endpoint to the specified URL string.
    ///
    /// The URL has to have the
    /// format `coap://<host>:<port>`, although the port is optional.
    /// If you specify a hostname, a DNS lookup will be made.
    /// If you specify an IP Address, no lookup has to be done.
    pub fn connect_to_url(
        &mut self,
        client: &mut UDP,
        url: &str,
    ) -> Result<(), Error<<UDP as UdpClientStack>::Error>> {
        if self.connection_link.is_some() {
            return Err(Error::AlreadyConnected);
        }

        let uri = Uri::new(url).map_err(|_| Error::Uri)?;

        let components = uri.authority_components().ok_or(Error::Uri)?;

        let host = components.host();
        let port = if let Some(port_str) = components.port() {
            port_str.parse::<u16>().map_err(|_| Error::Uri)?
        } else {
            super::DEFAULT_COAP_PORT
        };

        let ip = if let Ok(ip) = IpAddr::from_str(host) {
            ip
        } else {
            nb::block!(client.get_host_by_name(host, AddrType::Either)).unwrap()
        };

        let socket_addr = SocketAddr::new(ip, port);

        self.connect_to_addr(client, socket_addr)?;

        Ok(())
    }
}