kdeconnect-proto 0.1.0

A pure Rust modular implementation of the KDE Connect protocol
Documentation
//! `kdeconnect-proto` is a pure Rust modular implementation of the
//! [KDE Connect protocol](https://kdeconnect.kde.org).
//!
//! It implements the transport layer as well as full serializing/deserializing of all
//! packet types according to
//! [the specification](https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md).
//! It's up to the user of this library to decide what to do when a packet of a specific type is
//! received.
//!
//! It's written using an abstraction over all IO usage, hence it supports a wide range of
//! hardware. A backend using [tokio](https://tokio.rs) is provided by default as well as a
//! backend for embedded devices using [Embassy](https://embassy.dev) in the
//! `kdeconnect-embassy` crate.
//!
//! The entrypoint of the API is the [`Device`](`device::Device`) structure which represents the
//! host device KDE Connect client.
//!
//! This crate has the `embedded` feature which enables the use of a crypto implementation for
//! embedded devices, used in TLS connections. You should enable it if you develop for embedded
//! devices, don't use the default features of this crate, or use `kdeconnect-embassy`.
//!
//! ### Getting started
//!
//! Add to your `Cargo.toml` file `kdeconnect-proto`, `uuid` (with the feature `v4`), `tokio`
//! (with the feature `rt-multi-thread`) and `rcgen`
//! (optionnal, read the documentation of [`DeviceConfig`](`config::DeviceConfig`) for
//! more information).
//!
//! ```no_run
//! use std::{fs, collections::HashMap, path::{Path, PathBuf}, sync::Arc};
//! use kdeconnect_proto::{
//!     config::DeviceConfig,
//!     device::{Device, DeviceType, Link},
//!     trust::TrustHandler,
//!     io::TokioIoImpl,
//!     packet::{NetworkPacket, NetworkPacketBody, NetworkPacketType},
//!     plugin::Plugin,
//! };
//! use rcgen::{CertificateParams, DistinguishedName, DnType, IsCa, KeyPair};
//!
//! // 1. Generate a self-signed certificate and a private key using rcgen
//!
//! pub fn gen_certificate() {
//!     let device_id = uuid::Uuid::new_v4().to_string().replace('-', "");
//!     let mut params = CertificateParams::default();
//!     params.is_ca = IsCa::ExplicitNoCa;
//!
//!     let mut dn = DistinguishedName::new();
//!     dn.push(DnType::CommonName, device_id);
//!     dn.push(DnType::OrganizationName, "KDE");
//!     dn.push(DnType::OrganizationalUnitName, "KDE Connect");
//!     params.distinguished_name = dn;
//!
//!     let key_pair = KeyPair::generate().unwrap();
//!     let cert = params.self_signed(&key_pair).unwrap();
//!
//!     fs::write("private_key.pem", key_pair.serialize_pem()).unwrap();
//!     fs::write("cert.pem", cert.pem()).unwrap();
//! }
//!
//! // 2. Manage device trust by making a structure implementing the TrustHandler trait
//!
//! struct TrustHandlerImpl {
//!    path: PathBuf,
//!    trusted_devices: HashMap<String, Vec<u8>>,
//! }
//!
//! impl TrustHandlerImpl {
//!    pub fn new<P: AsRef<Path>>(path: P) -> Self {
//!        let path = path.as_ref();
//!
//!        let trusted_devices = if path.exists() {
//!             HashMap::from_iter(fs::read_dir(path).unwrap().filter_map(Result::ok).map(|f| {
//!                 let device_id = f.path().file_stem().unwrap().to_string_lossy().to_string();
//!                 let cert = fs::read(f.path()).expect("failed to read certificate");
//!                 (device_id, cert)
//!             }))
//!         } else {
//!             fs::create_dir_all(path).expect("failed to create directory for trusted devices");
//!             HashMap::new()
//!         };
//!
//!        Self {
//!            path: path.to_path_buf(),
//!            trusted_devices,
//!        }
//!    }
//! }
//!
//! #[kdeconnect_proto::async_trait]
//! impl TrustHandler for TrustHandlerImpl {
//!     async fn trust_device(&mut self, device_id: String, cert: Vec<u8>) {
//!        fs::write(self.path.join(device_id.clone() + ".pem"), &cert).unwrap();
//!        self.trusted_devices.insert(device_id, cert);
//!    }
//!
//!    async fn untrust_device(&mut self, device_id: &str) {
//!        fs::remove_file(self.path.join(device_id.to_string() + ".pem")).unwrap();
//!        self.trusted_devices.remove(device_id);
//!    }
//!
//!    async fn get_certificate(&mut self, device_id: &str) -> Option<&[u8]> {
//!        self.trusted_devices.get(device_id).map(|v| &**v)
//!    }
//! }
//!
//! // 3. Make as many plugins as you need by making structures implementing the Plugin trait
//!
//! struct PingPlugin;
//!
//! #[kdeconnect_proto::async_trait]
//! impl Plugin for PingPlugin {
//!     fn supported_incoming_packets(&self) -> Vec<NetworkPacketType> {
//!         vec![NetworkPacketType::Ping]
//!     }
//!
//!     fn supported_outgoing_packets(&self) -> Vec<NetworkPacketType> {
//!         vec![NetworkPacketType::Ping]
//!     }
//!
//!     async fn on_packet_received(
//!         &self,
//!         packet: &NetworkPacket,
//!         link: &Link,
//!     ) -> kdeconnect_proto::error::Result<()> {
//!         match &packet.body {
//!             NetworkPacketBody::Ping(packet) => {
//!                 let device_name = link
//!                     .info
//!                     .device_name
//!                     .as_ref()
//!                     .unwrap_or(&link.info.device_id);
//!                 let msg = if let Some(msg) = &packet.message {
//!                     format!(" \"{msg}\"")
//!                 } else {
//!                     String::new()
//!                 };
//!                 println!("PING{msg} from {device_name}!",);
//!             }
//!             _ => unreachable!(),
//!         }
//!         Ok(())
//!     }
//!
//!     async fn on_start(&self, link: &Link) -> kdeconnect_proto::error::Result<()> {
//!         link.send(NetworkPacket::ping("Connected")).await;
//!         Ok(())
//!     }
//! }
//!
//! #[tokio::main]
//! async fn main() {
//!     if !Path::new("cert.pem").exists() {
//!         gen_certificate();
//!     }
//!
//!     // 4. Make a device configuration for the host using the DeviceConfig structure
//!
//!     let config = DeviceConfig {
//!         name: String::from("kdeconnect client"),
//!         device_type: DeviceType::Desktop,
//!         cert: fs::read("cert.pem").expect("failed to read certificate"),
//!         private_key: fs::read("private_key.pem").expect("failed to read private key"),
//!     };
//!
//!     // 5. Start discovering other devices by calling Device::start or Device::start_arced
//!
//!     let device = Device::new(
//!         config,
//!         vec![Box::new(PingPlugin)],
//!         TrustHandlerImpl::new("trusted_devices"),
//!         TokioIoImpl,
//!     );
//!     let device = Arc::new(device);
//!     {
//!         let device = Arc::clone(&device);
//!         std::thread::spawn(move || {
//!             device.start_arced();
//!         });
//!     }
//!
//!     // 6. When another device connects, pair with it immediately by calling Device::pair_with
//!
//!     println!("Started discovering devices");
//!     loop {
//!         let link_id = device.wait_for_connection().await;
//!         println!("Device {link_id} found");
//!         device.pair_with(&link_id).await;
//!     }
//! }
//! ```
#![forbid(unsafe_code)]
#![warn(
    missing_docs,
    missing_debug_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unstable_features,
    unused_import_braces,
    unused_qualifications,
    unreachable_pub,
    rustdoc::private_doc_tests,
    rustdoc::broken_intra_doc_links,
    rustdoc::private_intra_doc_links,
    clippy::unnecessary_wraps,
    clippy::too_many_lines,
    clippy::implicit_clone,
    clippy::explicit_iter_loop,
    clippy::unnecessary_cast,
    clippy::missing_errors_doc,
    clippy::pedantic,
    clippy::clone_on_ref_ptr,
    clippy::non_ascii_literal,
    clippy::dbg_macro,
    clippy::use_debug,
    clippy::map_err_ignore,
    clippy::useless_let_if_seq,
    clippy::verbose_file_reads,
    clippy::panic,
    clippy::unimplemented,
    clippy::todo
)]
#![allow(clippy::must_use_candidate)]

#![cfg_attr(not(feature = "std"), no_std)]

pub mod transport;
pub mod packet;
pub mod device;
pub mod plugin;
pub mod io;
pub mod config;
pub mod trust;
pub mod error;

#[cfg(not(feature = "std"))]
extern crate alloc;

pub use async_trait::async_trait;