diff --git a/crates/xmtp_mls/src/worker/tasks.rs b/crates/xmtp_mls/src/worker/tasks.rs index d3465a8a06..a5293050e4 100644 --- a/crates/xmtp_mls/src/worker/tasks.rs +++ b/crates/xmtp_mls/src/worker/tasks.rs @@ -392,6 +392,16 @@ where .db() .pull_in_task_deadline(&p.target_data_hash, p.not_later_than_ns)?; } + Some(xmtp_proto::xmtp::mls::database::task::Task::KpRotation(_)) + | Some(xmtp_proto::xmtp::mls::database::task::Task::KpDeletion(_)) => { + // Minimal arms: real handlers land with the KP-consumer impl. + // Nothing seeds these singletons yet, so dropping is safe. + tracing::warn!( + "KP task {} received before handler landed; dropping", + task.id + ); + context.db().delete_task(task.id)?; + } None => { tracing::error!("Task {} has no data. Deleting.", task.id); context.db().delete_task(task.id)?; diff --git a/crates/xmtp_proto/src/gen/proto_descriptor.bin b/crates/xmtp_proto/src/gen/proto_descriptor.bin index 1ff059c632..7404f40ddd 100644 Binary files a/crates/xmtp_proto/src/gen/proto_descriptor.bin and b/crates/xmtp_proto/src/gen/proto_descriptor.bin differ diff --git a/crates/xmtp_proto/src/gen/xmtp.mls.database.rs b/crates/xmtp_proto/src/gen/xmtp.mls.database.rs index 03fc595e5c..2f4adec4b5 100644 --- a/crates/xmtp_proto/src/gen/xmtp.mls.database.rs +++ b/crates/xmtp_proto/src/gen/xmtp.mls.database.rs @@ -783,7 +783,7 @@ impl PermissionPolicyOption { } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Task { - #[prost(oneof = "task::Task", tags = "1, 2, 3, 4")] + #[prost(oneof = "task::Task", tags = "1, 2, 3, 4, 5, 6")] pub task: ::core::option::Option, } /// Nested message and enum types in `Task`. @@ -798,6 +798,10 @@ pub mod task { ProcessPendingSelfRemove(super::ProcessPendingSelfRemove), #[prost(message, tag = "4")] PullInDeadline(super::PullInDeadline), + #[prost(message, tag = "5")] + KpRotation(super::KpRotation), + #[prost(message, tag = "6")] + KpDeletion(super::KpDeletion), } } impl ::prost::Name for Task { @@ -831,6 +835,34 @@ impl ::prost::Name for PullInDeadline { "/xmtp.mls.database.PullInDeadline".into() } } +/// Recurring singleton: rotate + upload a fresh key package when the identity's +/// rotation deadline is due. Empty payload => stable data_hash for pull-ins. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct KpRotation {} +impl ::prost::Name for KpRotation { + const NAME: &'static str = "KpRotation"; + const PACKAGE: &'static str = "xmtp.mls.database"; + fn full_name() -> ::prost::alloc::string::String { + "xmtp.mls.database.KpRotation".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/xmtp.mls.database.KpRotation".into() + } +} +/// Recurring singleton: delete superseded local key-package material whose +/// delete_at_ns has passed. Empty payload => stable data_hash for pull-ins. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct KpDeletion {} +impl ::prost::Name for KpDeletion { + const NAME: &'static str = "KpDeletion"; + const PACKAGE: &'static str = "xmtp.mls.database"; + fn full_name() -> ::prost::alloc::string::String { + "xmtp.mls.database.KpDeletion".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/xmtp.mls.database.KpDeletion".into() + } +} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct SendSyncArchive { #[prost(message, optional, tag = "1")] diff --git a/crates/xmtp_proto/src/gen/xmtp.mls.database.serde.rs b/crates/xmtp_proto/src/gen/xmtp.mls.database.serde.rs index fef20891e0..1e3bb9cd79 100644 --- a/crates/xmtp_proto/src/gen/xmtp.mls.database.serde.rs +++ b/crates/xmtp_proto/src/gen/xmtp.mls.database.serde.rs @@ -1002,6 +1002,150 @@ impl<'de> serde::Deserialize<'de> for InstallationIds { deserializer.deserialize_struct("xmtp.mls.database.InstallationIds", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for KpDeletion { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("xmtp.mls.database.KpDeletion", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for KpDeletion { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl serde::de::Visitor<'_> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = KpDeletion; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct xmtp.mls.database.KpDeletion") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(KpDeletion { + }) + } + } + deserializer.deserialize_struct("xmtp.mls.database.KpDeletion", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for KpRotation { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("xmtp.mls.database.KpRotation", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for KpRotation { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl serde::de::Visitor<'_> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = KpRotation; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct xmtp.mls.database.KpRotation") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(KpRotation { + }) + } + } + deserializer.deserialize_struct("xmtp.mls.database.KpRotation", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for PermissionPolicyOption { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -2958,6 +3102,12 @@ impl serde::Serialize for Task { task::Task::PullInDeadline(v) => { struct_ser.serialize_field("pull_in_deadline", v)?; } + task::Task::KpRotation(v) => { + struct_ser.serialize_field("kp_rotation", v)?; + } + task::Task::KpDeletion(v) => { + struct_ser.serialize_field("kp_deletion", v)?; + } } } struct_ser.end() @@ -2978,6 +3128,10 @@ impl<'de> serde::Deserialize<'de> for Task { "processPendingSelfRemove", "pull_in_deadline", "pullInDeadline", + "kp_rotation", + "kpRotation", + "kp_deletion", + "kpDeletion", ]; #[allow(clippy::enum_variant_names)] @@ -2986,6 +3140,8 @@ impl<'de> serde::Deserialize<'de> for Task { SendSyncArchive, ProcessPendingSelfRemove, PullInDeadline, + KpRotation, + KpDeletion, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3012,6 +3168,8 @@ impl<'de> serde::Deserialize<'de> for Task { "sendSyncArchive" | "send_sync_archive" => Ok(GeneratedField::SendSyncArchive), "processPendingSelfRemove" | "process_pending_self_remove" => Ok(GeneratedField::ProcessPendingSelfRemove), "pullInDeadline" | "pull_in_deadline" => Ok(GeneratedField::PullInDeadline), + "kpRotation" | "kp_rotation" => Ok(GeneratedField::KpRotation), + "kpDeletion" | "kp_deletion" => Ok(GeneratedField::KpDeletion), _ => Ok(GeneratedField::__SkipField__), } } @@ -3060,6 +3218,20 @@ impl<'de> serde::Deserialize<'de> for Task { return Err(serde::de::Error::duplicate_field("pullInDeadline")); } task__ = map_.next_value::<::std::option::Option<_>>()?.map(task::Task::PullInDeadline) +; + } + GeneratedField::KpRotation => { + if task__.is_some() { + return Err(serde::de::Error::duplicate_field("kpRotation")); + } + task__ = map_.next_value::<::std::option::Option<_>>()?.map(task::Task::KpRotation) +; + } + GeneratedField::KpDeletion => { + if task__.is_some() { + return Err(serde::de::Error::duplicate_field("kpDeletion")); + } + task__ = map_.next_value::<::std::option::Option<_>>()?.map(task::Task::KpDeletion) ; } GeneratedField::__SkipField__ => {