kdeconnect_proto/lib.rs
1//! `kdeconnect-proto` is a pure Rust modular implementation of the
2//! [KDE Connect protocol](https://kdeconnect.kde.org).
3//!
4//! It implements the transport layer as well as full serializing/deserializing of all
5//! packet types according to
6//! [the specification](https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md).
7//! It's up to the user of this library to decide what to do when a packet of a specific type is
8//! received.
9//!
10//! It's written using an abstraction over all IO usage, hence it supports a wide range of
11//! hardware. A backend using [tokio](https://tokio.rs) is provided by default as well as a
12//! backend for embedded devices using [Embassy](https://embassy.dev) in the
13//! `kdeconnect-embassy` crate.
14//!
15//! The entrypoint of the API is the [`Device`](`device::Device`) structure which represents the
16//! host device KDE Connect client.
17//!
18//! This crate has the `embedded` feature which enables the use of a crypto implementation for
19//! embedded devices, used in TLS connections. You should enable it if you develop for embedded
20//! devices, don't use the default features of this crate, or use `kdeconnect-embassy`.
21//!
22//! ### Getting started
23//!
24//! Add to your `Cargo.toml` file `kdeconnect-proto`, `uuid` (with the feature `v4`), `tokio`
25//! (with the feature `rt-multi-thread`) and `rcgen`
26//! (optional, read the documentation of [`DeviceConfig`](`config::DeviceConfig`) for
27//! more information).
28//!
29//! ```no_run
30//! use std::{fs, collections::HashMap, path::{Path, PathBuf}, sync::Arc};
31//! use kdeconnect_proto::{
32//! config::DeviceConfig,
33//! device::{Device, DeviceType, Link},
34//! trust::TrustHandler,
35//! io::TokioIoImpl,
36//! packet::{NetworkPacket, NetworkPacketBody, NetworkPacketType},
37//! plugin::Plugin,
38//! };
39//! use rcgen::{CertificateParams, DistinguishedName, DnType, IsCa, KeyPair};
40//!
41//! // 1. Generate a self-signed certificate and a private key using rcgen
42//!
43//! pub fn gen_certificate() {
44//! let device_id = uuid::Uuid::new_v4().to_string().replace('-', "");
45//! let mut params = CertificateParams::default();
46//! params.is_ca = IsCa::ExplicitNoCa;
47//!
48//! let mut dn = DistinguishedName::new();
49//! dn.push(DnType::CommonName, device_id);
50//! dn.push(DnType::OrganizationName, "KDE");
51//! dn.push(DnType::OrganizationalUnitName, "KDE Connect");
52//! params.distinguished_name = dn;
53//!
54//! let key_pair = KeyPair::generate().unwrap();
55//! let cert = params.self_signed(&key_pair).unwrap();
56//!
57//! fs::write("private_key.pem", key_pair.serialize_pem()).unwrap();
58//! fs::write("cert.pem", cert.pem()).unwrap();
59//! }
60//!
61//! // 2. Manage device trust by making a structure implementing the TrustHandler trait
62//!
63//! struct TrustHandlerImpl {
64//! path: PathBuf,
65//! trusted_devices: HashMap<String, Vec<u8>>,
66//! }
67//!
68//! impl TrustHandlerImpl {
69//! pub fn new<P: AsRef<Path>>(path: P) -> Self {
70//! let path = path.as_ref();
71//!
72//! let trusted_devices = if path.exists() {
73//! HashMap::from_iter(fs::read_dir(path).unwrap().filter_map(Result::ok).map(|f| {
74//! let device_id = f.path().file_stem().unwrap().to_string_lossy().to_string();
75//! let cert = fs::read(f.path()).expect("failed to read certificate");
76//! (device_id, cert)
77//! }))
78//! } else {
79//! fs::create_dir_all(path).expect("failed to create directory for trusted devices");
80//! HashMap::new()
81//! };
82//!
83//! Self {
84//! path: path.to_path_buf(),
85//! trusted_devices,
86//! }
87//! }
88//! }
89//!
90//! #[kdeconnect_proto::async_trait]
91//! impl TrustHandler for TrustHandlerImpl {
92//! async fn trust_device(&mut self, device_id: String, cert: Vec<u8>) {
93//! fs::write(self.path.join(device_id.clone() + ".pem"), &cert).unwrap();
94//! self.trusted_devices.insert(device_id, cert);
95//! }
96//!
97//! async fn untrust_device(&mut self, device_id: &str) {
98//! fs::remove_file(self.path.join(device_id.to_string() + ".pem")).unwrap();
99//! self.trusted_devices.remove(device_id);
100//! }
101//!
102//! async fn get_certificate(&mut self, device_id: &str) -> Option<&[u8]> {
103//! self.trusted_devices.get(device_id).map(|v| &**v)
104//! }
105//! }
106//!
107//! // 3. Make as many plugins as you need by making structures implementing the Plugin trait
108//!
109//! struct PingPlugin;
110//!
111//! #[kdeconnect_proto::async_trait]
112//! impl Plugin for PingPlugin {
113//! fn supported_incoming_packets(&self) -> Vec<NetworkPacketType> {
114//! vec![NetworkPacketType::Ping]
115//! }
116//!
117//! fn supported_outgoing_packets(&self) -> Vec<NetworkPacketType> {
118//! vec![NetworkPacketType::Ping]
119//! }
120//!
121//! async fn on_packet_received(
122//! &self,
123//! packet: &NetworkPacket,
124//! link: &Link,
125//! ) -> kdeconnect_proto::error::Result<()> {
126//! match &packet.body {
127//! NetworkPacketBody::Ping(packet) => {
128//! let device_name = link
129//! .info
130//! .device_name
131//! .as_ref()
132//! .unwrap_or(&link.info.device_id);
133//! let msg = if let Some(msg) = &packet.message {
134//! format!(" \"{msg}\"")
135//! } else {
136//! String::new()
137//! };
138//! println!("PING{msg} from {device_name}!",);
139//! }
140//! _ => unreachable!(),
141//! }
142//! Ok(())
143//! }
144//!
145//! async fn on_start(&self, link: &Link) -> kdeconnect_proto::error::Result<()> {
146//! link.send(NetworkPacket::ping("Connected")).await;
147//! Ok(())
148//! }
149//! }
150//!
151//! #[tokio::main]
152//! async fn main() {
153//! if !Path::new("cert.pem").exists() {
154//! gen_certificate();
155//! }
156//!
157//! // 4. Make a device configuration for the host using the DeviceConfig structure
158//!
159//! let config = DeviceConfig {
160//! name: String::from("kdeconnect client"),
161//! device_type: DeviceType::Desktop,
162//! cert: fs::read("cert.pem").expect("failed to read certificate"),
163//! private_key: fs::read("private_key.pem").expect("failed to read private key"),
164//! };
165//!
166//! // 5. Start discovering other devices by calling Device::start or Device::start_arced
167//!
168//! let device = Device::new(
169//! config,
170//! vec![Box::new(PingPlugin)],
171//! TrustHandlerImpl::new("trusted_devices"),
172//! TokioIoImpl,
173//! );
174//! let device = Arc::new(device);
175//! {
176//! let device = Arc::clone(&device);
177//! std::thread::spawn(move || {
178//! device.start_arced();
179//! });
180//! }
181//!
182//! // 6. When another device connects, pair with it immediately by calling Device::pair_with
183//!
184//! println!("Started discovering devices");
185//! loop {
186//! let link_id = device.wait_for_connection().await;
187//! println!("Device {link_id} found");
188//! device.pair_with(&link_id).await;
189//! }
190//! }
191//! ```
192#![forbid(unsafe_code)]
193#![warn(
194 missing_docs,
195 missing_debug_implementations,
196 trivial_casts,
197 trivial_numeric_casts,
198 unstable_features,
199 unused_import_braces,
200 unused_qualifications,
201 unreachable_pub,
202 rustdoc::private_doc_tests,
203 rustdoc::broken_intra_doc_links,
204 rustdoc::private_intra_doc_links,
205 clippy::unnecessary_wraps,
206 clippy::too_many_lines,
207 clippy::implicit_clone,
208 clippy::explicit_iter_loop,
209 clippy::unnecessary_cast,
210 clippy::missing_errors_doc,
211 clippy::pedantic,
212 clippy::clone_on_ref_ptr,
213 clippy::non_ascii_literal,
214 clippy::dbg_macro,
215 clippy::use_debug,
216 clippy::map_err_ignore,
217 clippy::useless_let_if_seq,
218 clippy::verbose_file_reads,
219 clippy::panic,
220 clippy::unimplemented,
221 clippy::todo
222)]
223#![allow(clippy::must_use_candidate)]
224
225#![cfg_attr(not(feature = "std"), no_std)]
226
227pub mod transport;
228pub mod packet;
229pub mod device;
230pub mod plugin;
231pub mod io;
232pub mod config;
233pub mod trust;
234pub mod error;
235
236#[cfg(not(feature = "std"))]
237extern crate alloc;
238
239pub use async_trait::async_trait;