Skip to main content

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;