Skip to main content

kdeconnect_proto/packet/
mod.rs

1//! Definition of all the KDE Connect core packets.
2//!
3//! ### Status
4//!
5//! - [x] `kdeconnect.identity`
6//! - [x] `kdeconnect.battery`
7//! - [x] `kdeconnect.clipboard`
8//! - [x] `kdeconnect.clipboard.connect`
9//! - [x] `kdeconnect.connectivity_report`
10//! - [x] `kdeconnect.contacts.request_all_uids_timestamps`
11//! - [x] `kdeconnect.contacts.request_vcards_by_uid`
12//! - [x] `kdeconnect.contacts.response_uids_timestamps`
13//! - [x] `kdeconnect.contacts.response_vcards`
14//! - [x] `kdeconnect.digitizer.session`
15//! - [x] `kdeconnect.digitizer`
16//! - [x] `kdeconnect.findmyphone.request`
17//! - [x] `kdeconnect.lock`
18//! - [x] `kdeconnect.lock.request`
19//! - [x] `kdeconnect.mousepad.echo`
20//! - [x] `kdeconnect.mousepad.keyboardstate`
21//! - [x] `kdeconnect.mousepad.request`
22//! - [x] `kdeconnect.mpris`
23//! - [x] `kdeconnect.mpris.request`
24//! - [x] `kdeconnect.notification`
25//! - [x] `kdeconnect.notification.action`
26//! - [x] `kdeconnect.notification.reply`
27//! - [x] `kdeconnect.notification.request`
28//! - [x] `kdeconnect.ping`
29//! - [x] `kdeconnect.presenter`
30//! - [x] `kdeconnect.runcommand`
31//! - [x] `kdeconnect.runcommand.request`
32//! - [x] `kdeconnect.sftp`
33//! - [x] `kdeconnect.sftp.request`
34//! - [x] `kdeconnect.share.request`
35//! - [x] `kdeconnect.share.request.update`
36//! - [x] `kdeconnect.sms.attachment_file`
37//! - [x] `kdeconnect.sms.messages`
38//! - [x] `kdeconnect.sms.request`
39//! - [x] `kdeconnect.sms.request_attachment`
40//! - [x] `kdeconnect.sms.request_conversation`
41//! - [x] `kdeconnect.sms.request_conversations`
42//! - [x] `kdeconnect.systemvolume`
43//! - [x] `kdeconnect.systemvolume.request`
44//! - [x] `kdeconnect.telephony`
45//! - [x] `kdeconnect.telephony.request_mute`
46//!
47//! Most of the documentation of this module is copy-pasted from the original
48//! [KDE Connection specification](https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md).
49use hashbrown::HashMap;
50
51#[cfg(not(feature = "std"))]
52use alloc::{string::{String, ToString}, format};
53
54use serde::{Deserialize, Serialize, de::Error};
55use serde_json::Value;
56
57use crate::io::{TcpStreamImpl, TlsStreamImpl};
58
59pub mod battery;
60pub mod clipboard;
61pub mod connectivity_report;
62pub mod contacts;
63pub mod digitizer;
64pub mod identity;
65pub mod lock;
66pub mod mousepad;
67pub mod mpris;
68pub mod notification;
69pub mod pair;
70pub mod ping;
71pub mod presenter;
72pub mod runcommand;
73pub mod sftp;
74pub mod share;
75pub mod sms;
76pub mod system_volume;
77pub mod telephony;
78
79/// Custom serde deserializer to handle both integer or string values in certain packet
80/// fields.
81///
82/// It's needed because some KDE Connect clients send some fields (e.g `id`, `timestamp`)
83/// as string instead of integer.
84pub(super) fn deserialize_number_or_string<'de, D: serde::Deserializer<'de>>(
85    de: D,
86) -> Result<u64, D::Error> {
87    match Value::deserialize(de)? {
88        Value::Number(n) => n
89            .as_u64()
90            .ok_or_else(|| Error::custom("JSON number cannot be converted to u64")),
91        Value::String(s) => s
92            .parse::<u64>()
93            .map_err(|e| Error::custom(format!("String cannot be parsed as u64: {e}"))),
94        _ => Err(Error::custom("unknown type, expected number or string")),
95    }
96}
97
98/// Custom serde deserializer to handle both integer or string values in certain packet
99/// fields which are optional.
100///
101/// It's needed because some KDE Connect clients send some fields (e.g `id`, `timestamp`)
102/// as string instead of integer.
103pub(super) fn deserialize_number_or_string_in_option<'de, D: serde::Deserializer<'de>>(
104    de: D,
105) -> Result<Option<u64>, D::Error> {
106    match Value::deserialize(de)? {
107        Value::Number(n) => {
108            Ok(Some(n.as_u64().ok_or_else(|| {
109                Error::custom("JSON number cannot be converted to u64")
110            })?))
111        }
112        Value::String(s) => {
113            Ok(Some(s.parse::<u64>().map_err(|e| {
114                Error::custom(format!("String cannot be parsed as u64: {e}"))
115            })?))
116        }
117        _ => Err(Error::custom("unknown type, expected number or string")),
118    }
119}
120
121/// The payload of a [`NetworkPacket`].
122#[derive(Serialize, Deserialize, Debug, Clone)]
123#[serde(tag = "type", content = "body")]
124pub enum NetworkPacketBody {
125    /// The KDE Connect identity packet is used to identify devices and their capabilities.
126    #[serde(rename = "kdeconnect.identity")]
127    Identity(identity::IdentityPacket),
128
129    /// The KDE Connect pair packet is used to negotiate pairing between devices.
130    #[serde(rename = "kdeconnect.pair")]
131    Pair(pair::PairPacket),
132
133    /// This packet is a battery status update.
134    #[serde(rename = "kdeconnect.battery")]
135    Battery(battery::BatteryPacket),
136
137    /// This packet is sent when the clipboard content changes. In other words, it is typically sent when the selection owner changes.
138    #[serde(rename = "kdeconnect.clipboard")]
139    Clipboard(clipboard::ClipboardPacket),
140
141    /// This packet is only sent when a device connects.
142    #[serde(rename = "kdeconnect.clipboard.connect")]
143    ClipboardConnect(clipboard::ClipboardConnectPacket),
144
145    /// This packet is a connectivity report.
146    #[serde(rename = "kdeconnect.connectivity_report")]
147    ConnectivityReport(connectivity_report::ConnectivityReportPacket),
148
149    /// This packet is a request for a list of contact UIDs with modification timestamps.
150    #[serde(rename = "kdeconnect.contacts.request_all_uids_timestamps")]
151    ContactsRequestAllUidsTimestamps(()),
152
153    /// This packet is a request for a list of contact UIDs with vCard data.
154    #[serde(rename = "kdeconnect.contacts.request_vcards_by_uid")]
155    ContactsRequestVcardByUid(contacts::ContactsRequestVcardsByUid),
156
157    /// This packet is a list of contact UIDs with modification timestamps.
158    #[serde(rename = "kdeconnect.contacts.response_uids_timestamps")]
159    ContactsResponseUidsTimestamps(contacts::ContactsResponseUidsTimestamp),
160
161    /// This packet is a list of contact UIDs with vCard data.
162    #[serde(rename = "kdeconnect.contacts.response_vcards")]
163    ContactsResponseVcards(contacts::ContactsResponseVcards),
164
165    /// This packet either starts or stops a drawing tablet session.
166    /// The session is started when this packet is sent with a start action.
167    /// When it is started, it includes the information necessary to create a fake drawing tablet
168    /// device on the receiver. The session is ended when this packet is sent with an end action,
169    /// or the device disconnects. It is valid to send both a start or an end packet regardless
170    /// whether a session exists or not. If a session exists and a start packet is sent, the session
171    /// will be first ended, and then another session will be started. If no session exists and an
172    /// end packet is sent, nothing will happen.
173    #[serde(rename = "kdeconnect.digitizer.session")]
174    DigitizerSession(digitizer::DigitizerSessionPacket),
175
176    /// This packet is a stylus (or finger) event. `kdeconnect.digitizer` packets must not be sent
177    /// until a session has been started. You may start a session by sending the
178    /// `kdeconnect.digitizer.session` packet with the field action set to start, and filling
179    /// the appropriate fields.
180    #[serde(rename = "kdeconnect.digitizer")]
181    Digitizer(digitizer::DigitizerPacket),
182
183    /// This packet is a request for a device to announce its location.
184    /// By convention, sending a second packet cancels the request.
185    #[serde(rename = "kdeconnect.findmyphone.request")]
186    FindmyphoneRequest(()),
187
188    /// This packet is a lock status update.
189    #[serde(rename = "kdeconnect.lock")]
190    Lock(lock::LockPacket),
191
192    /// This packet is a request for a lock status update or change.
193    #[serde(rename = "kdeconnect.lock.request")]
194    LockRequest(lock::LockRequestPacket),
195
196    /// This packet is an echo for a `kdeconnect.mousepad.request` packet.
197    #[serde(rename = "kdeconnect.mousepad.echo")]
198    MousepadEcho(mousepad::MousepadEchoPacket),
199
200    /// This packet is a keyboard status update.
201    #[serde(rename = "kdeconnect.mousepad.keyboardstate")]
202    MousepadKeyboardState(mousepad::MousepadKeyboardStatePacket),
203
204    /// This packet is a request for a pointer or keyboard event.
205    #[serde(rename = "kdeconnect.mousepad.request")]
206    MousepadRequest(mousepad::MousepadRequestPacket),
207
208    /// This packet is used for two things: if it contains a playerList field, it is used to
209    /// enumerate the available media players to a remote device; if it contains a player field,
210    /// it is used to report the status of a specific media player. Note that player packets can be
211    /// incremental, ie: only contain the fields that changed since the last update.
212    #[serde(rename = "kdeconnect.mpris")]
213    Mpris(mpris::MprisPacket),
214
215    /// This packet is used to request the status of remote media players, issue commands, and
216    /// request the transfer of album art payloads.
217    #[serde(rename = "kdeconnect.mpris.request")]
218    MprisRequest(mpris::MprisRequestPacket),
219
220    /// This packet is a notification.
221    #[serde(rename = "kdeconnect.notification")]
222    Notification(notification::NotificationPacket),
223
224    /// This packet is an activation of a notification action.
225    #[serde(rename = "kdeconnect.notification.action")]
226    NotificationAction(notification::NotificationActionPacket),
227
228    /// This packet is a reply for a repliable notification.
229    #[serde(rename = "kdeconnect.notification.reply")]
230    NotificationReply(notification::NotificationReplyPacket),
231
232    /// This packet is a request for notifications.
233    #[serde(rename = "kdeconnect.notification.request")]
234    NotificationRequest(notification::NotificationRequestPacket),
235
236    /// This packet is a ping request.
237    #[serde(rename = "kdeconnect.ping")]
238    Ping(ping::PingPacket),
239
240    /// This packet is a presentation remote event.
241    #[serde(rename = "kdeconnect.presenter")]
242    Presenter(presenter::PresenterPacket),
243
244    /// This packet is a list of available commands.
245    #[serde(rename = "kdeconnect.runcommand")]
246    RunCommand(runcommand::RunCommandPacket),
247
248    /// This packet is a runcommand status update.
249    #[serde(rename = "kdeconnect.runcommand.request")]
250    RunCommandRequest(runcommand::RunCommandRequestPacket),
251
252    /// This packet contains SFTP login information.
253    #[serde(rename = "kdeconnect.sftp")]
254    Sftp(sftp::SftpPacket),
255
256    /// This packet is a request to start SFTP.
257    #[serde(rename = "kdeconnect.sftp.request")]
258    SftpRequest(sftp::SftpRequestPacket),
259
260    /// This packet is an upload request.
261    #[serde(rename = "kdeconnect.share.request")]
262    ShareRequest(share::ShareRequestPacket),
263
264    /// This packet is the metadata for a multi-file transfer.
265    /// By convention it is sent in advance of the packets containing the payload, which will
266    /// include the same fields, potentially with updated totals.
267    #[serde(rename = "kdeconnect.share.request.update")]
268    ShareRequestUpdate(share::ShareRequestUpdatePacket),
269
270    /// This packet is a message attachment transfer.
271    #[serde(rename = "kdeconnect.sms.attachment_file")]
272    SmsAttachmentFile(sms::SmsAttachmentFilePacket),
273
274    /// This packet is a list of messages.
275    #[serde(rename = "kdeconnect.sms.messages")]
276    SmsMessages(sms::SmsMessagesPacket),
277
278    /// This packet is a request to send an SMS/MMS message.
279    #[serde(rename = "kdeconnect.sms.request")]
280    SmsRequest(sms::SmsRequestPacket),
281
282    /// This packet is a request for a message attachment.
283    #[serde(rename = "kdeconnect.sms.request_attachment")]
284    SmsRequestAttachment(sms::SmsRequestAttachmentPacket),
285
286    /// This packet is a request for messages from a thread.
287    #[serde(rename = "kdeconnect.sms.request_conversation")]
288    SmsRequestConversation(sms::SmsRequestConversationPacket),
289
290    /// This packet is a request for the latest message in each thread.
291    #[serde(rename = "kdeconnect.sms.request_conversations")]
292    SmsRequestConversations(()),
293
294    /// This packet is a mixer stream state update.
295    #[serde(rename = "kdeconnect.systemvolume")]
296    SystemVolume(system_volume::SystemVolumePacket),
297
298    /// This packet is a audio stream request. It is used to request both the list of streams and
299    /// changes to those streams.
300    #[serde(rename = "kdeconnect.systemvolume.request")]
301    SystemVolumeRequest(system_volume::SystemVolumeRequestPacket),
302
303    /// This packet is a telephony event, such as the phone ringing.
304    #[serde(rename = "kdeconnect.telephony")]
305    Telephony(telephony::TelephonyPacket),
306
307    /// This packet is sent to request the ringer be muted.
308    #[serde(rename = "kdeconnect.telephony.request_mute")]
309    TelephonyRequestMute(()),
310
311    /// Another type of packet that a library user can implement to handle special custom actions.
312    #[serde(untagged)]
313    Other(Value),
314}
315
316impl NetworkPacketBody {
317    /// Get the [`NetworkPacketType`] of a [`NetworkPacketBody`].
318    pub fn get_type(&self) -> NetworkPacketType {
319        match self {
320            NetworkPacketBody::Identity(_) => NetworkPacketType::Identity,
321            NetworkPacketBody::Pair(_) => NetworkPacketType::Pair,
322            NetworkPacketBody::Ping(_) => NetworkPacketType::Ping,
323            NetworkPacketBody::Battery(_) => NetworkPacketType::Battery,
324            NetworkPacketBody::Clipboard(_) => NetworkPacketType::Clipboard,
325            NetworkPacketBody::ClipboardConnect(_) => NetworkPacketType::ClipboardConnect,
326            NetworkPacketBody::ConnectivityReport(_) => NetworkPacketType::ConnectivityReport,
327            NetworkPacketBody::ContactsRequestAllUidsTimestamps(()) => {
328                NetworkPacketType::ContactsRequestAllUidsTimestamps
329            }
330            NetworkPacketBody::ContactsRequestVcardByUid(_) => {
331                NetworkPacketType::ContactsRequestVcardByUid
332            }
333            NetworkPacketBody::ContactsResponseUidsTimestamps(_) => {
334                NetworkPacketType::ContactsResponseUidsTimestamps
335            }
336            NetworkPacketBody::ContactsResponseVcards(_) => {
337                NetworkPacketType::ContactsResponseVcards
338            }
339            NetworkPacketBody::DigitizerSession(_) => NetworkPacketType::DigitizerSession,
340            NetworkPacketBody::Digitizer(_) => NetworkPacketType::Digitizer,
341            NetworkPacketBody::FindmyphoneRequest(()) => NetworkPacketType::FindmyphoneRequest,
342            NetworkPacketBody::Lock(_) => NetworkPacketType::Lock,
343            NetworkPacketBody::LockRequest(_) => NetworkPacketType::LockRequest,
344            NetworkPacketBody::MousepadEcho(_) => NetworkPacketType::MousepadEcho,
345            NetworkPacketBody::MousepadKeyboardState(_) => NetworkPacketType::MousepadKeyboardState,
346            NetworkPacketBody::MousepadRequest(_) => NetworkPacketType::MousepadRequest,
347            NetworkPacketBody::Mpris(_) => NetworkPacketType::Mpris,
348            NetworkPacketBody::MprisRequest(_) => NetworkPacketType::MprisRequest,
349            NetworkPacketBody::Notification(_) => NetworkPacketType::Notification,
350            NetworkPacketBody::NotificationAction(_) => NetworkPacketType::NotificationAction,
351            NetworkPacketBody::NotificationReply(_) => NetworkPacketType::NotificationReply,
352            NetworkPacketBody::NotificationRequest(_) => NetworkPacketType::NotificationRequest,
353            NetworkPacketBody::Presenter(_) => NetworkPacketType::Presenter,
354            NetworkPacketBody::RunCommand(_) => NetworkPacketType::RunCommand,
355            NetworkPacketBody::RunCommandRequest(_) => NetworkPacketType::RunCommandRequest,
356            NetworkPacketBody::Sftp(_) => NetworkPacketType::Sftp,
357            NetworkPacketBody::SftpRequest(_) => NetworkPacketType::SftpRequest,
358            NetworkPacketBody::ShareRequest(_) => NetworkPacketType::ShareRequest,
359            NetworkPacketBody::ShareRequestUpdate(_) => NetworkPacketType::ShareRequestUpdate,
360            NetworkPacketBody::SmsAttachmentFile(_) => NetworkPacketType::SmsAttachmentFile,
361            NetworkPacketBody::SmsMessages(_) => NetworkPacketType::SmsMessages,
362            NetworkPacketBody::SmsRequest(_) => NetworkPacketType::SmsRequest,
363            NetworkPacketBody::SmsRequestAttachment(_) => NetworkPacketType::SmsRequestAttachment,
364            NetworkPacketBody::SmsRequestConversation(_) => {
365                NetworkPacketType::SmsRequestConversation
366            }
367            NetworkPacketBody::SmsRequestConversations(()) => {
368                NetworkPacketType::SmsRequestConversations
369            }
370            NetworkPacketBody::SystemVolume(_) => NetworkPacketType::SystemVolume,
371            NetworkPacketBody::SystemVolumeRequest(_) => NetworkPacketType::SystemVolumeRequest,
372            NetworkPacketBody::Telephony(_) => NetworkPacketType::Telephony,
373            NetworkPacketBody::TelephonyRequestMute(()) => NetworkPacketType::TelephonyRequestMute,
374            NetworkPacketBody::Other(value) => NetworkPacketType::Other(
375                value
376                    .get("type")
377                    .unwrap_or(&Value::String("Unknown".into()))
378                    .as_str()
379                    .unwrap_or("Unknown")
380                    .to_string(),
381            ),
382        }
383    }
384}
385
386/// The type of a [`NetworkPacket`].
387///
388/// It's used to define which packet types are supported by a plugin, see
389/// [`Plugin::supported_incoming_packets`](`crate::plugin::Plugin::supported_incoming_packets`) and
390/// [`Plugin::supported_outgoing_packets`](`crate::plugin::Plugin::supported_outgoing_packets`).
391#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
392pub enum NetworkPacketType {
393    /// The KDE Connect identity packet is used to identify devices and their capabilities.
394    #[serde(rename = "kdeconnect.identity")]
395    Identity,
396
397    /// The KDE Connect pair packet is used to negotiate pairing between devices.
398    #[serde(rename = "kdeconnect.pair")]
399    Pair,
400
401    /// This packet is a battery status update.
402    #[serde(rename = "kdeconnect.battery")]
403    Battery,
404
405    /// This packet is sent when the clipboard content changes. In other words, it is typically sent when the selection owner changes.
406    #[serde(rename = "kdeconnect.clipboard")]
407    Clipboard,
408
409    /// This packet is only sent when a device connects and allows syncing clipboard text content between devices.
410    #[serde(rename = "kdeconnect.clipboard.connect")]
411    ClipboardConnect,
412
413    /// This packet allows a device to expose the status of its connectivity.
414    #[serde(rename = "kdeconnect.connectivity_report")]
415    ConnectivityReport,
416
417    /// This packet is a request for a list of contact UIDs with modification timestamps.
418    #[serde(rename = "kdeconnect.contacts.request_all_uids_timestamps")]
419    ContactsRequestAllUidsTimestamps,
420
421    /// This packet is a request for a list of contact UIDs with vCard data.
422    #[serde(rename = "kdeconnect.contacts.request_vcards_by_uid")]
423    ContactsRequestVcardByUid,
424
425    /// This packet is a list of contact UIDs with modification timestamps.
426    #[serde(rename = "kdeconnect.contacts.response_uids_timestamps")]
427    ContactsResponseUidsTimestamps,
428
429    /// This packet is a list of contact UIDs with vCard data.
430    #[serde(rename = "kdeconnect.contacts.response_vcards")]
431    ContactsResponseVcards,
432
433    /// This packet either starts or stops a drawing tablet session.
434    /// The session is started when this packet is sent with a start action.
435    /// When it is started, it includes the information necessary to create a fake drawing tablet
436    /// device on the receiver. The session is ended when this packet is sent with an end action,
437    /// or the device disconnects. It is valid to send both a start or an end packet regardless
438    /// whether a session exists or not. If a session exists and a start packet is sent, the session
439    /// will be first ended, and then another session will be started. If no session exists and an
440    /// end packet is sent, nothing will happen.
441    #[serde(rename = "kdeconnect.digitizer.session")]
442    DigitizerSession,
443
444    /// This packet is a stylus (or finger) event. `kdeconnect.digitizer` packets must not be sent
445    /// until a session has been started. You may start a session by sending the
446    /// `kdeconnect.digitizer.session` packet with the field action set to start, and filling
447    /// the appropriate fields.
448    #[serde(rename = "kdeconnect.digitizer")]
449    Digitizer,
450
451    /// This packet is a request for a device to announce its location.
452    /// By convention, sending a second packet cancels the request.
453    #[serde(rename = "kdeconnect.findmyphone.request")]
454    FindmyphoneRequest,
455
456    /// This packet is a lock status update.
457    #[serde(rename = "kdeconnect.lock")]
458    Lock,
459
460    /// This packet is a request for a lock status update or change.
461    #[serde(rename = "kdeconnect.lock.request")]
462    LockRequest,
463
464    /// This packet is an echo for a `kdeconnect.mousepad.request` packet.
465    #[serde(rename = "kdeconnect.mousepad.echo")]
466    MousepadEcho,
467
468    /// This packet is a keyboard status update.
469    #[serde(rename = "kdeconnect.mousepad.keyboardstate")]
470    MousepadKeyboardState,
471
472    /// This packet is a request for a pointer or keyboard event.
473    #[serde(rename = "kdeconnect.mousepad.request")]
474    MousepadRequest,
475
476    /// This packet is used for two things: if it contains a playerList field, it is used to
477    /// enumerate the available media players to a remote device; if it contains a player field,
478    /// it is used to report the status of a specific media player. Note that player packets can be
479    /// incremental, ie: only contain the fields that changed since the last update.
480    #[serde(rename = "kdeconnect.mpris")]
481    Mpris,
482
483    /// This packet is used to request the status of remote media players, issue commands, and
484    /// request the transfer of album art payloads.
485    #[serde(rename = "kdeconnect.mpris.request")]
486    MprisRequest,
487
488    /// This packet is a notification.
489    #[serde(rename = "kdeconnect.notification")]
490    Notification,
491
492    /// This packet is an activation of a notification action.
493    #[serde(rename = "kdeconnect.notification.action")]
494    NotificationAction,
495
496    /// This packet is a reply for a repliable notification.
497    #[serde(rename = "kdeconnect.notification.reply")]
498    NotificationReply,
499
500    /// This packet is a request for notifications.
501    #[serde(rename = "kdeconnect.notification.request")]
502    NotificationRequest,
503
504    /// This packet is a ping request.
505    #[serde(rename = "kdeconnect.ping")]
506    Ping,
507
508    /// This packet is a presentation remote event.
509    #[serde(rename = "kdeconnect.presenter")]
510    Presenter,
511
512    /// This packet is a list of available commands.
513    #[serde(rename = "kdeconnect.runcommand")]
514    RunCommand,
515
516    /// This packet is a runcommand status update.
517    #[serde(rename = "kdeconnect.runcommand.request")]
518    RunCommandRequest,
519
520    /// This packet contains SFTP login information.
521    #[serde(rename = "kdeconnect.sftp")]
522    Sftp,
523
524    /// This packet is a request to start SFTP.
525    #[serde(rename = "kdeconnect.sftp.request")]
526    SftpRequest,
527
528    /// This packet is an upload request.
529    #[serde(rename = "kdeconnect.share.request")]
530    ShareRequest,
531
532    /// This packet is the metadata for a multi-file transfer.
533    /// By convention it is sent in advance of the packets containing the payload, which will
534    /// include the same fields, potentially with updated totals.
535    #[serde(rename = "kdeconnect.share.request.update")]
536    ShareRequestUpdate,
537
538    /// This packet is a message attachment transfer.
539    #[serde(rename = "kdeconnect.sms.attachment_file")]
540    SmsAttachmentFile,
541
542    /// This packet is a list of messages.
543    #[serde(rename = "kdeconnect.sms.messages")]
544    SmsMessages,
545
546    /// This packet is a request to send an SMS/MMS message.
547    #[serde(rename = "kdeconnect.sms.request")]
548    SmsRequest,
549
550    /// This packet is a request for a message attachment.
551    #[serde(rename = "kdeconnect.sms.request_attachment")]
552    SmsRequestAttachment,
553
554    /// This packet is a request for messages from a thread.
555    #[serde(rename = "kdeconnect.sms.request_conversation")]
556    SmsRequestConversation,
557
558    /// This packet is a request for the latest message in each thread.
559    #[serde(rename = "kdeconnect.sms.request_conversations")]
560    SmsRequestConversations,
561
562    /// This packet is a mixer stream state update.
563    #[serde(rename = "kdeconnect.systemvolume")]
564    SystemVolume,
565
566    /// This packet is a audio stream request. It is used to request both the list of streams and
567    /// changes to those streams.
568    #[serde(rename = "kdeconnect.systemvolume.request")]
569    SystemVolumeRequest,
570
571    /// This packet is a telephony event, such as the phone ringing.
572    #[serde(rename = "kdeconnect.telephony")]
573    Telephony,
574
575    /// This packet is sent to request the ringer be muted.
576    #[serde(rename = "kdeconnect.telephony.request_mute")]
577    TelephonyRequestMute,
578
579    /// Another type of packet that a library user can implement to handle special custom actions.
580    #[serde(untagged)]
581    Other(String),
582}
583
584/// A root packet structured as defined in the KDE Connection specification.
585///
586/// <https://invent.kde.org/network/kdeconnect-meta/blob/master/protocol.md#kdeconnect>
587#[derive(Serialize, Deserialize, Debug, Clone)]
588pub struct NetworkPacket {
589    #[serde(deserialize_with = "deserialize_number_or_string")]
590    id: u64,
591
592    /// A dictionary of parameters appropriate for the packet type.
593    #[serde(flatten)]
594    pub body: NetworkPacketBody,
595
596    /// The size of the payload to expect. There is a currently unused convention of using `-1` to declare a stream of indefinite size.
597    #[serde(skip_serializing_if = "Option::is_none")]
598    #[serde(default)]
599    pub range: Option<i64>,
600
601    /// A dictionary of properties necessary for clients to negotiate a transfer channel.
602    #[serde(skip_serializing_if = "Option::is_none")]
603    #[serde(default)]
604    pub payload_transfer_info: Option<HashMap<String, Value>>,
605}
606
607impl NetworkPacket {
608    /// Make a new [`NetworkPacket`] with the corresponding body.
609    ///
610    /// The type will be inferred from the body variant.
611    pub fn new(body: NetworkPacketBody) -> Self {
612        Self {
613            // This field is deprecated and can be safely populated with a constant value
614            id: 0,
615            body,
616            range: None,
617            payload_transfer_info: None,
618        }
619    }
620
621    pub(crate) fn try_read_from(buf: &[u8]) -> Result<Self, serde_json::Error> {
622        serde_json::from_slice::<NetworkPacket>(buf)
623    }
624
625    pub(crate) async fn write_to_socket_unencrypted<T: TcpStreamImpl + Unpin>(self, socket: &mut T) {
626        let mut serialized_packet = serde_json::to_string(&self).unwrap();
627        serialized_packet.push('\n');
628
629        socket
630            .write_all(serialized_packet.as_bytes())
631            .await
632            .unwrap();
633    }
634
635    pub(crate) async fn write_to_socket<T: TlsStreamImpl + Unpin>(self, socket: &mut T) {
636        let mut serialized_packet = serde_json::to_string(&self).unwrap();
637        serialized_packet.push('\n');
638
639        socket
640            .write_all(serialized_packet.as_bytes())
641            .await
642            .unwrap();
643    }
644}