kdeconnect-proto 0.2.0

A pure Rust modular implementation of the KDE Connect protocol
Documentation
//! TCP connection response implementation.
use core::net::{IpAddr, Ipv6Addr, SocketAddr};

#[cfg(feature = "std")]
use std::sync::Arc;

#[cfg(not(feature = "std"))]
use alloc::sync::Arc;

use crate::{
    device::Device,
    io::{IoImpl, KnownFunctionName, TcpListenerImpl, TcpStreamImpl, TlsStreamImpl, UdpSocketImpl},
    packet::{NetworkPacket, NetworkPacketBody},
};

/// Start a TCP listener on a port in the range 1716-1764.
///
/// As a library user, you should ignore this function as it's only useful to develop other IO
/// backends.
///
/// If a TCP connection is made with this device, it's upgraded to a TLS connection and used to
/// send application packets.
pub async fn setup_tcp<
    Io: IoImpl<UdpSocket, TcpStream, TcpListener, TlsStream> + Unpin + 'static,
    UdpSocket: UdpSocketImpl + Unpin + 'static,
    TcpStream: TcpStreamImpl + Unpin + 'static,
    TcpListener: TcpListenerImpl<TcpStream> + Unpin + 'static,
    TlsStream: TlsStreamImpl + Unpin + 'static,
>(
    device: Arc<Device<Io, UdpSocket, TcpStream, TcpListener, TlsStream>>,
) {
    #[allow(unused_mut)]
    let (mut tcp_listener, tcp_port) = {
        let mut port = crate::config::MIN_TCP_PORT;

        loop {
            if port > crate::config::MAX_TCP_PORT {
                log::error!(
                    "No port available in {}..{}",
                    crate::config::MIN_TCP_PORT,
                    crate::config::MAX_TCP_PORT
                );
                return;
            }

            if let Ok(tcp_listener) = device
                .io_impl
                .listen_tcp(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port))
                .await
            {
                break (tcp_listener, port);
            }

            port += 1;
        }
    };

    let _ = device.my_tcp_port.set(tcp_port).await;

    // The TCP server is initialized, launch discovery mechanisms
    super::start_discovering(Arc::clone(&device));

    loop {
        let Ok(socket) = tcp_listener.accept().await else {
            #[cfg(feature = "embedded")]
            device.io_impl.sleep(core::time::Duration::from_millis(500)).await;
            continue;
        };

        // A task is spawned per TCP-stream which will be further converted to a TLS stream
        Arc::clone(&device)
            .io_impl
            .spawn(KnownFunctionName::PerTcpStream(socket), Arc::clone(&device));

        #[cfg(feature = "embedded")]
        {
            tcp_listener = device
                .io_impl
                .listen_tcp(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), tcp_port))
                .await
                .unwrap();
        }
    }
}

// Function which should be called when a new TCP stream is made.
///
/// As a library user, you should ignore this function as it's only useful to develop other IO
/// backends.
///
/// It upgrades the connection to a TLS connection and use it to
/// send application packets.
pub async fn per_tcp_stream<
    Io: IoImpl<UdpSocket, TcpStream, TcpListener, TlsStream> + Unpin + 'static,
    UdpSocket: UdpSocketImpl + Unpin + 'static,
    TcpStream: TcpStreamImpl + Unpin + 'static,
    TcpListener: TcpListenerImpl<TcpStream> + Unpin + 'static,
    TlsStream: TlsStreamImpl + Unpin + 'static,
>(
    mut socket: TcpStream,
    device: Arc<Device<Io, UdpSocket, TcpStream, TcpListener, TlsStream>>,
) {
    let mut i = 0;
    let mut buf = [0; crate::config::TCP_BUFFER_SIZE];

    loop {
        let Ok(bytes_read) = socket.read(&mut buf[i..]).await else {
            log::warn!(
                "TCP packet is too large, consider reading the documentation of config::TCP_BUFFER_SIZE"
            );
            i = 0;
            continue;
        };
        i += bytes_read;

        if let Ok(NetworkPacket {
            body: NetworkPacketBody::Identity(packet),
            ..
        }) = NetworkPacket::try_read_from(&buf[..i])
        {
            if device.links.lock().await.contains_key(&packet.device_id) {
                log::debug!(
                    "Device {} has already established connection, ignore the MDNS request",
                    packet.device_id
                );
                continue;
            }

            log::debug!("TCP Identity packet received, upgrading connection");

            // Upgrade to TLS as SSL client
            super::tls::upgrade_tcp_connection(packet, socket, device, true).await;

            break;
        }

        if bytes_read == 0 {
            log::warn!("tcp: Failed to parse the received JSON");
            break;
        }
    }
}