diff --git a/.nanpa/send-data-nonce.kdl b/.nanpa/send-data-nonce.kdl new file mode 100644 index 000000000..b4d2a4f0e --- /dev/null +++ b/.nanpa/send-data-nonce.kdl @@ -0,0 +1 @@ +patch package="livekit-api" type="added" "Update protocol and add SendDataRequest nonce" diff --git a/Cargo.lock b/Cargo.lock index b0532c222..37b881351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1064,10 +1064,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.0", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1521,9 +1533,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -1537,7 +1549,7 @@ dependencies = [ [[package]] name = "libwebrtc" -version = "0.3.8" +version = "0.3.9" dependencies = [ "cxx", "env_logger", @@ -1593,7 +1605,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "livekit" -version = "0.7.2" +version = "0.7.3" dependencies = [ "chrono", "futures-util", @@ -1628,6 +1640,7 @@ dependencies = [ "parking_lot", "pbjson-types", "prost 0.12.3", + "rand 0.9.0", "reqwest", "scopeguard", "serde", @@ -1641,7 +1654,7 @@ dependencies = [ [[package]] name = "livekit-ffi" -version = "0.12.6" +version = "0.12.8" dependencies = [ "console-subscriber", "dashmap", @@ -1666,7 +1679,7 @@ dependencies = [ [[package]] name = "livekit-protocol" -version = "0.3.6" +version = "0.3.7" dependencies = [ "futures-util", "livekit-runtime", @@ -1760,7 +1773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -1925,7 +1938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2212,8 +2225,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy", ] [[package]] @@ -2223,7 +2247,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -2232,7 +2266,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy", ] [[package]] @@ -2359,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin", "untrusted", @@ -2971,7 +3015,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -3070,7 +3114,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "rustls", "sha1", "thiserror", @@ -3091,7 +3135,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "sha1", "thiserror", "url", @@ -3209,6 +3253,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.89" @@ -3576,6 +3629,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.4.1", +] + [[package]] name = "yuv-sys" version = "0.3.7" @@ -3586,6 +3648,26 @@ dependencies = [ "regex", ] +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/livekit-api/Cargo.toml b/livekit-api/Cargo.toml index 7a843f78c..ec94be6d3 100644 --- a/livekit-api/Cargo.toml +++ b/livekit-api/Cargo.toml @@ -95,3 +95,4 @@ reqwest = { version = "0.11", default-features = false, features = [ "json" ], o isahc = { version = "1.7.2", default-features = false, features = [ "json", "text-decoding" ], optional = true } scopeguard = "1.2.0" +rand = "0.9.0" diff --git a/livekit-api/src/services/room.rs b/livekit-api/src/services/room.rs index 0bdae45b2..006e54ee4 100644 --- a/livekit-api/src/services/room.rs +++ b/livekit-api/src/services/room.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE}; use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient}; +use rand::Rng; const SVC: &str = "RoomService"; @@ -288,6 +289,8 @@ impl RoomClient { data: Vec, options: SendDataOptions, ) -> ServiceResult<()> { + let mut rng = rand::rng(); + let nonce: Vec = (0..16).map(|_| rng.random::()).collect(); #[allow(deprecated)] self.client .request( @@ -300,6 +303,7 @@ impl RoomClient { topic: options.topic, kind: options.kind as i32, destination_identities: options.destination_identities, + nonce, }, self.base.auth_header( VideoGrants { room_admin: true, room: room.to_owned(), ..Default::default() }, diff --git a/livekit-ffi/protocol/video_frame.proto b/livekit-ffi/protocol/video_frame.proto index 528b14f43..6d41dc824 100644 --- a/livekit-ffi/protocol/video_frame.proto +++ b/livekit-ffi/protocol/video_frame.proto @@ -124,7 +124,7 @@ message VideoBufferInfo { required uint32 width = 2; required uint32 height = 3; required uint64 data_ptr = 4; - required uint32 stride = 6; // only for packed formats + optional uint32 stride = 6; // only for packed formats repeated ComponentInfo components = 7; } diff --git a/livekit-protocol/protocol b/livekit-protocol/protocol index afe463fe8..2e0a35efa 160000 --- a/livekit-protocol/protocol +++ b/livekit-protocol/protocol @@ -1 +1 @@ -Subproject commit afe463fe84852471ee403efba3c32451162cbffd +Subproject commit 2e0a35efa9382b8b3eabaaf6c867c3f487431dd5 diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs index 9b6eaec82..c1f96e776 100644 --- a/livekit-protocol/src/livekit.rs +++ b/livekit-protocol/src/livekit.rs @@ -214,6 +214,8 @@ pub struct Room { pub max_participants: u32, #[prost(int64, tag="5")] pub creation_time: i64, + #[prost(int64, tag="15")] + pub creation_time_ms: i64, #[prost(string, tag="6")] pub turn_password: ::prost::alloc::string::String, #[prost(message, repeated, tag="7")] @@ -298,6 +300,9 @@ pub struct ParticipantInfo { /// timestamp when participant joined room, in seconds #[prost(int64, tag="6")] pub joined_at: i64, + /// timestamp when participant joined room, in milliseconds + #[prost(int64, tag="17")] + pub joined_at_ms: i64, #[prost(string, tag="9")] pub name: ::prost::alloc::string::String, #[prost(uint32, tag="10")] @@ -634,6 +639,9 @@ pub struct UserPacket { pub start_time: ::core::option::Option, #[prost(uint64, optional, tag="10")] pub end_time: ::core::option::Option, + /// added by SDK to enable de-duping of messages, for INTERNAL USE ONLY + #[prost(bytes="vec", tag="11")] + pub nonce: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -4044,6 +4052,9 @@ pub struct SendDataRequest { pub destination_identities: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(string, optional, tag="5")] pub topic: ::core::option::Option<::prost::alloc::string::String>, + /// added by SDK to enable de-duping of messages, for INTERNAL USE ONLY + #[prost(bytes="vec", tag="7")] + pub nonce: ::prost::alloc::vec::Vec, } /// #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/livekit-protocol/src/livekit.serde.rs b/livekit-protocol/src/livekit.serde.rs index b908afb0c..7dc86d65a 100644 --- a/livekit-protocol/src/livekit.serde.rs +++ b/livekit-protocol/src/livekit.serde.rs @@ -17006,6 +17006,9 @@ impl serde::Serialize for ParticipantInfo { if self.joined_at != 0 { len += 1; } + if self.joined_at_ms != 0 { + len += 1; + } if !self.name.is_empty() { len += 1; } @@ -17053,6 +17056,11 @@ impl serde::Serialize for ParticipantInfo { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("joinedAt", ToString::to_string(&self.joined_at).as_str())?; } + if self.joined_at_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("joinedAtMs", ToString::to_string(&self.joined_at_ms).as_str())?; + } if !self.name.is_empty() { struct_ser.serialize_field("name", &self.name)?; } @@ -17098,6 +17106,8 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "metadata", "joined_at", "joinedAt", + "joined_at_ms", + "joinedAtMs", "name", "version", "permission", @@ -17118,6 +17128,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { Tracks, Metadata, JoinedAt, + JoinedAtMs, Name, Version, Permission, @@ -17154,6 +17165,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { "tracks" => Ok(GeneratedField::Tracks), "metadata" => Ok(GeneratedField::Metadata), "joinedAt" | "joined_at" => Ok(GeneratedField::JoinedAt), + "joinedAtMs" | "joined_at_ms" => Ok(GeneratedField::JoinedAtMs), "name" => Ok(GeneratedField::Name), "version" => Ok(GeneratedField::Version), "permission" => Ok(GeneratedField::Permission), @@ -17187,6 +17199,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { let mut tracks__ = None; let mut metadata__ = None; let mut joined_at__ = None; + let mut joined_at_ms__ = None; let mut name__ = None; let mut version__ = None; let mut permission__ = None; @@ -17235,6 +17248,14 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::JoinedAtMs => { + if joined_at_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("joinedAtMs")); + } + joined_at_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::Name => { if name__.is_some() { return Err(serde::de::Error::duplicate_field("name")); @@ -17299,6 +17320,7 @@ impl<'de> serde::Deserialize<'de> for ParticipantInfo { tracks: tracks__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), joined_at: joined_at__.unwrap_or_default(), + joined_at_ms: joined_at_ms__.unwrap_or_default(), name: name__.unwrap_or_default(), version: version__.unwrap_or_default(), permission: permission__, @@ -21346,6 +21368,9 @@ impl serde::Serialize for Room { if self.creation_time != 0 { len += 1; } + if self.creation_time_ms != 0 { + len += 1; + } if !self.turn_password.is_empty() { len += 1; } @@ -21388,6 +21413,11 @@ impl serde::Serialize for Room { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("creationTime", ToString::to_string(&self.creation_time).as_str())?; } + if self.creation_time_ms != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("creationTimeMs", ToString::to_string(&self.creation_time_ms).as_str())?; + } if !self.turn_password.is_empty() { struct_ser.serialize_field("turnPassword", &self.turn_password)?; } @@ -21429,6 +21459,8 @@ impl<'de> serde::Deserialize<'de> for Room { "maxParticipants", "creation_time", "creationTime", + "creation_time_ms", + "creationTimeMs", "turn_password", "turnPassword", "enabled_codecs", @@ -21451,6 +21483,7 @@ impl<'de> serde::Deserialize<'de> for Room { DepartureTimeout, MaxParticipants, CreationTime, + CreationTimeMs, TurnPassword, EnabledCodecs, Metadata, @@ -21486,6 +21519,7 @@ impl<'de> serde::Deserialize<'de> for Room { "departureTimeout" | "departure_timeout" => Ok(GeneratedField::DepartureTimeout), "maxParticipants" | "max_participants" => Ok(GeneratedField::MaxParticipants), "creationTime" | "creation_time" => Ok(GeneratedField::CreationTime), + "creationTimeMs" | "creation_time_ms" => Ok(GeneratedField::CreationTimeMs), "turnPassword" | "turn_password" => Ok(GeneratedField::TurnPassword), "enabledCodecs" | "enabled_codecs" => Ok(GeneratedField::EnabledCodecs), "metadata" => Ok(GeneratedField::Metadata), @@ -21518,6 +21552,7 @@ impl<'de> serde::Deserialize<'de> for Room { let mut departure_timeout__ = None; let mut max_participants__ = None; let mut creation_time__ = None; + let mut creation_time_ms__ = None; let mut turn_password__ = None; let mut enabled_codecs__ = None; let mut metadata__ = None; @@ -21571,6 +21606,14 @@ impl<'de> serde::Deserialize<'de> for Room { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::CreationTimeMs => { + if creation_time_ms__.is_some() { + return Err(serde::de::Error::duplicate_field("creationTimeMs")); + } + creation_time_ms__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } GeneratedField::TurnPassword => { if turn_password__.is_some() { return Err(serde::de::Error::duplicate_field("turnPassword")); @@ -21629,6 +21672,7 @@ impl<'de> serde::Deserialize<'de> for Room { departure_timeout: departure_timeout__.unwrap_or_default(), max_participants: max_participants__.unwrap_or_default(), creation_time: creation_time__.unwrap_or_default(), + creation_time_ms: creation_time_ms__.unwrap_or_default(), turn_password: turn_password__.unwrap_or_default(), enabled_codecs: enabled_codecs__.unwrap_or_default(), metadata: metadata__.unwrap_or_default(), @@ -27257,6 +27301,9 @@ impl serde::Serialize for SendDataRequest { if self.topic.is_some() { len += 1; } + if !self.nonce.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.SendDataRequest", len)?; if !self.room.is_empty() { struct_ser.serialize_field("room", &self.room)?; @@ -27280,6 +27327,11 @@ impl serde::Serialize for SendDataRequest { if let Some(v) = self.topic.as_ref() { struct_ser.serialize_field("topic", v)?; } + if !self.nonce.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("nonce", pbjson::private::base64::encode(&self.nonce).as_str())?; + } struct_ser.end() } } @@ -27298,6 +27350,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { "destination_identities", "destinationIdentities", "topic", + "nonce", ]; #[allow(clippy::enum_variant_names)] @@ -27308,6 +27361,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { DestinationSids, DestinationIdentities, Topic, + Nonce, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -27336,6 +27390,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { "destinationSids" | "destination_sids" => Ok(GeneratedField::DestinationSids), "destinationIdentities" | "destination_identities" => Ok(GeneratedField::DestinationIdentities), "topic" => Ok(GeneratedField::Topic), + "nonce" => Ok(GeneratedField::Nonce), _ => Ok(GeneratedField::__SkipField__), } } @@ -27361,6 +27416,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { let mut destination_sids__ = None; let mut destination_identities__ = None; let mut topic__ = None; + let mut nonce__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Room => { @@ -27401,6 +27457,14 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { } topic__ = map_.next_value()?; } + GeneratedField::Nonce => { + if nonce__.is_some() { + return Err(serde::de::Error::duplicate_field("nonce")); + } + nonce__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -27413,6 +27477,7 @@ impl<'de> serde::Deserialize<'de> for SendDataRequest { destination_sids: destination_sids__.unwrap_or_default(), destination_identities: destination_identities__.unwrap_or_default(), topic: topic__, + nonce: nonce__.unwrap_or_default(), }) } } @@ -36250,6 +36315,9 @@ impl serde::Serialize for UserPacket { if self.end_time.is_some() { len += 1; } + if !self.nonce.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("livekit.UserPacket", len)?; if !self.participant_sid.is_empty() { struct_ser.serialize_field("participantSid", &self.participant_sid)?; @@ -36284,6 +36352,11 @@ impl serde::Serialize for UserPacket { #[allow(clippy::needless_borrows_for_generic_args)] struct_ser.serialize_field("endTime", ToString::to_string(&v).as_str())?; } + if !self.nonce.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("nonce", pbjson::private::base64::encode(&self.nonce).as_str())?; + } struct_ser.end() } } @@ -36309,6 +36382,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { "startTime", "end_time", "endTime", + "nonce", ]; #[allow(clippy::enum_variant_names)] @@ -36322,6 +36396,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { Id, StartTime, EndTime, + Nonce, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -36353,6 +36428,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { "id" => Ok(GeneratedField::Id), "startTime" | "start_time" => Ok(GeneratedField::StartTime), "endTime" | "end_time" => Ok(GeneratedField::EndTime), + "nonce" => Ok(GeneratedField::Nonce), _ => Ok(GeneratedField::__SkipField__), } } @@ -36381,6 +36457,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { let mut id__ = None; let mut start_time__ = None; let mut end_time__ = None; + let mut nonce__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::ParticipantSid => { @@ -36443,6 +36520,14 @@ impl<'de> serde::Deserialize<'de> for UserPacket { map_.next_value::<::std::option::Option<::pbjson::private::NumberDeserialize<_>>>()?.map(|x| x.0) ; } + GeneratedField::Nonce => { + if nonce__.is_some() { + return Err(serde::de::Error::duplicate_field("nonce")); + } + nonce__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -36458,6 +36543,7 @@ impl<'de> serde::Deserialize<'de> for UserPacket { id: id__, start_time: start_time__, end_time: end_time__, + nonce: nonce__.unwrap_or_default(), }) } }