/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * * Modified by Coffey Labs in 2026 for INBUXA. */ use common::ipc::{ BroadcastEvent, CacheInvalidation, CalendarAlert, EmailPush, PushNotification, RegistryChange, }; use registry::{ schema::prelude::ObjectType, types::{EnumImpl, id::ObjectId}, }; use std::{borrow::Borrow, io::Write}; use types::{id::Id, type_state::StateChange}; use utils::{ codec::leb128::{Leb128Iterator, Leb128Writer}, map::bitmap::Bitmap, }; pub mod publisher; pub mod subscriber; #[derive(Debug)] pub(crate) struct BroadcastBatch { messages: T, } const MAX_BATCH_SIZE: usize = 100; pub(crate) const BROADCAST_TOPIC: &str = "stwt.agora"; impl BroadcastBatch> { pub fn init() -> Self { Self { messages: Vec::with_capacity(MAX_BATCH_SIZE), } } pub fn insert(&mut self, message: BroadcastEvent) -> bool { self.messages.push(message); self.messages.len() < MAX_BATCH_SIZE } pub fn serialize(&self, node_id: u16) -> Vec { let mut serialized = Vec::with_capacity((self.messages.len() * 10) + std::mem::size_of::()); let _ = serialized.write_leb128(node_id); for message in &self.messages { match message { BroadcastEvent::PushNotification(notification) => match notification { PushNotification::StateChange(state_change) => { serialized.push(0u8); let _ = serialized.write_leb128(state_change.change_id); let _ = serialized.write_leb128(*state_change.types.as_ref()); let _ = serialized.write_leb128(state_change.account_id); } PushNotification::CalendarAlert(calendar_alert) => { serialized.push(1u8); let _ = serialized.write_leb128(calendar_alert.account_id); let _ = serialized.write_leb128(calendar_alert.event_id); let _ = serialized .write_leb128(calendar_alert.recurrence_id.unwrap_or_default() as u64); let _ = serialized.write_leb128(calendar_alert.uid.len()); let _ = serialized.write(calendar_alert.uid.as_bytes()); let _ = serialized.write_leb128(calendar_alert.alert_id.len()); let _ = serialized.write(calendar_alert.alert_id.as_bytes()); } PushNotification::EmailPush(email_push) => { serialized.push(2u8); let _ = serialized.write_leb128(email_push.account_id); let _ = serialized.write_leb128(email_push.email_id); let _ = serialized.write_leb128(email_push.change_id); } }, BroadcastEvent::PushServerUpdate(account_id) => { serialized.push(3u8); let _ = serialized.write_leb128(*account_id); } BroadcastEvent::RegistryChange(items) => match items { RegistryChange::Insert(id) => { serialized.push(4u8); let _ = serialized.write_leb128(id.object().to_id()); let _ = serialized.write_leb128(id.id().id()); } RegistryChange::Delete(id) => { serialized.push(5u8); let _ = serialized.write_leb128(id.object().to_id()); let _ = serialized.write_leb128(id.id().id()); } RegistryChange::Reload(object) => { serialized.push(6u8); let _ = serialized.write_leb128(object.to_id()); } }, BroadcastEvent::CacheInvalidate(items) => { serialized.push(7u8); let _ = serialized.write_leb128(items.len()); for item in items { let (marker, id) = match item { CacheInvalidation::AccessToken(id) => (0u8, *id), CacheInvalidation::DavResources(id) => (1u8, *id), CacheInvalidation::Domain(id) => (2u8, *id), CacheInvalidation::Account(id) => (3u8, *id), CacheInvalidation::DkimSignature(id) => (4u8, *id), CacheInvalidation::Tenant(id) => (5u8, *id), CacheInvalidation::Role(id) => (6u8, *id), CacheInvalidation::List(id) => (7u8, *id), CacheInvalidation::DomainLogo(id) => (8u8, *id), CacheInvalidation::TenantLogo(id) => (9u8, *id), CacheInvalidation::EmailNegative { domain_id, local_part_hash, } => { serialized.push(10u8); let _ = serialized.write_leb128(*domain_id); let _ = serialized.write_leb128(*local_part_hash); continue; } CacheInvalidation::DomainNegative => (11u8, 0), }; serialized.push(marker); let _ = serialized.write_leb128(id); } } BroadcastEvent::CacheInvalidateAll => { serialized.push(8u8); } BroadcastEvent::CacheInvalidateNegative => { serialized.push(9u8); } BroadcastEvent::MtaQueueStatus { is_running } => { if *is_running { serialized.push(10u8); } else { serialized.push(11u8); } } BroadcastEvent::QueueRefresh => { serialized.push(12u8); } // inbuxa: AL-3 BroadcastEvent::EndSessions(account_id) => { serialized.push(13u8); let _ = serialized.write_leb128(*account_id); } } } serialized } pub fn clear(&mut self) { self.messages.clear(); } } impl BroadcastBatch where T: Iterator + Leb128Iterator, I: Borrow, { pub fn node_id(&mut self) -> Option { self.messages.next_leb128::() } pub fn next_event(&mut self) -> Result, ()> { if let Some(id) = self.messages.next() { match id.borrow() { 0 => Ok(Some(BroadcastEvent::PushNotification( PushNotification::StateChange(StateChange { change_id: self.messages.next_leb128().ok_or(())?, types: Bitmap::from(self.messages.next_leb128::().ok_or(())?), account_id: self.messages.next_leb128().ok_or(())?, }), ))), 1 => { let account_id = self.messages.next_leb128().ok_or(())?; let event_id = self.messages.next_leb128().ok_or(())?; let recurrence_id = self.messages.next_leb128::().ok_or(())? as i64; let uid_len = self.messages.next_leb128::().ok_or(())?; let mut uid_bytes = vec![0u8; uid_len]; for byte in uid_bytes.iter_mut() { *byte = self.messages.next().ok_or(())?.borrow().to_owned(); } let uid = String::from_utf8(uid_bytes).map_err(|_| ())?; let alert_id_len = self.messages.next_leb128::().ok_or(())?; let mut alert_id_bytes = vec![0u8; alert_id_len]; for byte in alert_id_bytes.iter_mut() { *byte = self.messages.next().ok_or(())?.borrow().to_owned(); } let alert_id = String::from_utf8(alert_id_bytes).map_err(|_| ())?; Ok(Some(BroadcastEvent::PushNotification( PushNotification::CalendarAlert(CalendarAlert { account_id, event_id, recurrence_id: if recurrence_id == 0 { None } else { Some(recurrence_id) }, uid, alert_id, }), ))) } 2 => Ok(Some(BroadcastEvent::PushNotification( PushNotification::EmailPush(EmailPush { account_id: self.messages.next_leb128().ok_or(())?, email_id: self.messages.next_leb128().ok_or(())?, change_id: self.messages.next_leb128().ok_or(())?, }), ))), 3 => { let account_id = self.messages.next_leb128().ok_or(())?; Ok(Some(BroadcastEvent::PushServerUpdate(account_id))) } 4 => { let object_id = self.messages.next_leb128().ok_or(())?; let id = self.messages.next_leb128::().ok_or(())?; Ok(Some(BroadcastEvent::RegistryChange( RegistryChange::Insert(ObjectId::new( ObjectType::from_id(object_id).ok_or(())?, Id::new(id), )), ))) } 5 => { let object_id = self.messages.next_leb128().ok_or(())?; let id = self.messages.next_leb128::().ok_or(())?; Ok(Some(BroadcastEvent::RegistryChange( RegistryChange::Delete(ObjectId::new( ObjectType::from_id(object_id).ok_or(())?, Id::new(id), )), ))) } 6 => { let object_id = self.messages.next_leb128().ok_or(())?; Ok(Some(BroadcastEvent::RegistryChange( RegistryChange::Reload(ObjectType::from_id(object_id).ok_or(())?), ))) } 7 => { let count = self.messages.next_leb128::().ok_or(())?; let mut items = Vec::with_capacity(count); for _ in 0..count { let marker = self.messages.next().ok_or(())?.borrow().to_owned(); let id = self.messages.next_leb128::().ok_or(())?; items.push(match marker { 0 => CacheInvalidation::AccessToken(id), 1 => CacheInvalidation::DavResources(id), 2 => CacheInvalidation::Domain(id), 3 => CacheInvalidation::Account(id), 4 => CacheInvalidation::DkimSignature(id), 5 => CacheInvalidation::Tenant(id), 6 => CacheInvalidation::Role(id), 7 => CacheInvalidation::List(id), 8 => CacheInvalidation::DomainLogo(id), 9 => CacheInvalidation::TenantLogo(id), 10 => { let local_part_hash = self.messages.next_leb128::().ok_or(())?; CacheInvalidation::EmailNegative { domain_id: id, local_part_hash, } } 11 => CacheInvalidation::DomainNegative, _ => return Err(()), }); } Ok(Some(BroadcastEvent::CacheInvalidate(items))) } 8 => Ok(Some(BroadcastEvent::CacheInvalidateAll)), 9 => Ok(Some(BroadcastEvent::CacheInvalidateNegative)), 10 => Ok(Some(BroadcastEvent::MtaQueueStatus { is_running: true })), 11 => Ok(Some(BroadcastEvent::MtaQueueStatus { is_running: false })), 12 => Ok(Some(BroadcastEvent::QueueRefresh)), // inbuxa: AL-3 13 => { let account_id = self.messages.next_leb128().ok_or(())?; Ok(Some(BroadcastEvent::EndSessions(account_id))) } _ => Err(()), } } else { Ok(None) } } } impl BroadcastBatch { pub fn new(messages: T) -> Self { Self { messages } } }