kdeconnect-proto 0.2.0

A pure Rust modular implementation of the KDE Connect protocol
Documentation
//! UDP discovery mechanism.
use core::net::{IpAddr, Ipv4Addr, SocketAddr};

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

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

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

async fn broadcast_udp_identity<
    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>>,
) {
    let Ok(mut socket) = device
        .io_impl
        .bind_udp(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0))
        .await
    else {
        return;
    };

    let my_identity_packet = device.get_identity_packet();
    let serialized_my_identity_packet = serde_json::to_string(&my_identity_packet).unwrap();
    socket.set_broadcast(true).unwrap();
    let _ = socket
        .send_to(
            serialized_my_identity_packet.as_bytes(),
            SocketAddr::new(IpAddr::V4(Ipv4Addr::BROADCAST), crate::config::UDP_PORT),
        )
        .await;
}

/// Start an UDP listener on the reserved udp port (1716).
///
/// As a library user, you should ignore this function as it's only useful to develop other IO
/// backends.
///
/// This function sets up a listener incoming UDP connections and regularly
/// broadcasts an identity packet over UDP to discover other devices.
///
/// If a valid identity packet is received, a TCP connection is established with the other device
/// which is then upgraded to a TLS connection used to send application packets.
pub async fn setup_udp<
    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>>,
) {
    broadcast_udp_identity(Arc::clone(&device)).await;

    let Ok(udp_listener) = device
        .io_impl
        .bind_udp_reuse_v4(SocketAddr::new(
            IpAddr::V4(Ipv4Addr::UNSPECIFIED),
            crate::config::UDP_PORT,
        ))
        .await
    else {
        return;
    };
    udp_listener.set_broadcast(true).unwrap();

    let mut i = 0;
    let mut buf = [0u8; crate::config::UDP_BUFFER_SIZE];

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

        let device = Arc::clone(&device);

        // A UDP packet received must be an Identity packet
        if let Ok(NetworkPacket {
            body: NetworkPacketBody::Identity(packet),
            ..
        }) = NetworkPacket::try_read_from(&buf[..i])
        {
            i = 0;

            if packet.device_id == device.host_device_id {
                log::trace!("Discovered myself, ignore the MDNS request");
                continue;
            }

            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!("UDP Identity packet received, upgrading connection");

            let Some(peer_tcp_port) = packet.get_tcp_port() else {
                log::warn!(
                    "Peer device identity packet does not contain its TCP port, can't connect to it"
                );
                continue;
            };
            addr.set_port(peer_tcp_port);

            // Make a TCP connection
            let Ok(mut socket) = device.io_impl.connect_tcp(addr).await else {
                log::warn!("Failed to make a TCP connection to the peer device");
                continue;
            };
            socket.writable().await.unwrap();

            device
                .get_identity_packet()
                .write_to_socket_unencrypted(&mut socket)
                .await;

            // Upgrade to TLS as SSL server
            super::tls::upgrade_tcp_connection(packet, socket, device, false).await;
        }

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