lxmf-sdk 0.7.0

High-level Rust SDK for LXMF clients and RPC-backed LXMF workflows.
Documentation
use crate::capability::EffectiveLimits;
use crate::types::Profile;

const CAP_CURSOR_REPLAY: &str = "sdk.capability.cursor_replay";
const CAP_ASYNC_EVENTS: &str = "sdk.capability.async_events";
const CAP_MANUAL_TICK: &str = "sdk.capability.manual_tick";
const CAP_TOKEN_AUTH: &str = "sdk.capability.token_auth";
const CAP_MTLS_AUTH: &str = "sdk.capability.mtls_auth";
const CAP_RECEIPT_TERMINALITY: &str = "sdk.capability.receipt_terminality";
const CAP_BATCH_SEND: &str = "sdk.capability.batch_send";
const CAP_CONFIG_REVISION_CAS: &str = "sdk.capability.config_revision_cas";
const CAP_IDEMPOTENCY_TTL: &str = "sdk.capability.idempotency_ttl";
const CAP_TOPICS: &str = "sdk.capability.topics";
const CAP_TOPIC_SUBSCRIPTIONS: &str = "sdk.capability.topic_subscriptions";
const CAP_TOPIC_FANOUT: &str = "sdk.capability.topic_fanout";
const CAP_TELEMETRY_QUERY: &str = "sdk.capability.telemetry_query";
const CAP_TELEMETRY_STREAM: &str = "sdk.capability.telemetry_stream";
const CAP_ATTACHMENTS: &str = "sdk.capability.attachments";
const CAP_ATTACHMENT_DELETE: &str = "sdk.capability.attachment_delete";
const CAP_ATTACHMENT_STREAMING: &str = "sdk.capability.attachment_streaming";
const CAP_MARKERS: &str = "sdk.capability.markers";
const CAP_IDENTITY_MULTI: &str = "sdk.capability.identity_multi";
const CAP_IDENTITY_DISCOVERY: &str = "sdk.capability.identity_discovery";
const CAP_IDENTITY_IMPORT_EXPORT: &str = "sdk.capability.identity_import_export";
const CAP_IDENTITY_HASH_RESOLUTION: &str = "sdk.capability.identity_hash_resolution";
const CAP_CONTACT_MANAGEMENT: &str = "sdk.capability.contact_management";
const CAP_PAPER_MESSAGES: &str = "sdk.capability.paper_messages";
const CAP_REMOTE_COMMANDS: &str = "sdk.capability.remote_commands";
const CAP_VOICE_SIGNALING: &str = "sdk.capability.voice_signaling";
const CAP_GROUP_DELIVERY: &str = "sdk.capability.group_delivery";
const CAP_EVENT_SINK_BRIDGE: &str = "sdk.capability.event_sink_bridge";
const CAP_SHARED_INSTANCE_RPC_AUTH: &str = "sdk.capability.shared_instance_rpc_auth";

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MemoryBudget {
    pub max_heap_bytes: usize,
    pub max_event_queue_bytes: usize,
    pub max_attachment_spool_bytes: usize,
}

const DESKTOP_FULL_REQUIRED: &[&str] = &[
    CAP_CURSOR_REPLAY,
    CAP_ASYNC_EVENTS,
    CAP_RECEIPT_TERMINALITY,
    CAP_CONFIG_REVISION_CAS,
    CAP_IDEMPOTENCY_TTL,
];

const DESKTOP_LOCAL_RUNTIME_REQUIRED: &[&str] =
    &[CAP_CURSOR_REPLAY, CAP_RECEIPT_TERMINALITY, CAP_CONFIG_REVISION_CAS, CAP_IDEMPOTENCY_TTL];

const EMBEDDED_ALLOC_REQUIRED: &[&str] =
    &[CAP_MANUAL_TICK, CAP_CONFIG_REVISION_CAS, CAP_IDEMPOTENCY_TTL];

const DESKTOP_FULL_SUPPORTED: &[&str] = &[
    CAP_CURSOR_REPLAY,
    CAP_ASYNC_EVENTS,
    CAP_MANUAL_TICK,
    CAP_TOKEN_AUTH,
    CAP_MTLS_AUTH,
    CAP_RECEIPT_TERMINALITY,
    CAP_BATCH_SEND,
    CAP_CONFIG_REVISION_CAS,
    CAP_IDEMPOTENCY_TTL,
    CAP_TOPICS,
    CAP_TOPIC_SUBSCRIPTIONS,
    CAP_TOPIC_FANOUT,
    CAP_TELEMETRY_QUERY,
    CAP_TELEMETRY_STREAM,
    CAP_ATTACHMENTS,
    CAP_ATTACHMENT_DELETE,
    CAP_ATTACHMENT_STREAMING,
    CAP_MARKERS,
    CAP_IDENTITY_MULTI,
    CAP_IDENTITY_DISCOVERY,
    CAP_IDENTITY_IMPORT_EXPORT,
    CAP_IDENTITY_HASH_RESOLUTION,
    CAP_CONTACT_MANAGEMENT,
    CAP_PAPER_MESSAGES,
    CAP_REMOTE_COMMANDS,
    CAP_VOICE_SIGNALING,
    CAP_GROUP_DELIVERY,
    CAP_EVENT_SINK_BRIDGE,
    CAP_SHARED_INSTANCE_RPC_AUTH,
];

const DESKTOP_LOCAL_RUNTIME_SUPPORTED: &[&str] = &[
    CAP_CURSOR_REPLAY,
    CAP_ASYNC_EVENTS,
    CAP_MANUAL_TICK,
    CAP_TOKEN_AUTH,
    CAP_MTLS_AUTH,
    CAP_RECEIPT_TERMINALITY,
    CAP_BATCH_SEND,
    CAP_CONFIG_REVISION_CAS,
    CAP_IDEMPOTENCY_TTL,
    CAP_TOPICS,
    CAP_TOPIC_SUBSCRIPTIONS,
    CAP_TOPIC_FANOUT,
    CAP_TELEMETRY_QUERY,
    CAP_TELEMETRY_STREAM,
    CAP_ATTACHMENTS,
    CAP_ATTACHMENT_DELETE,
    CAP_ATTACHMENT_STREAMING,
    CAP_MARKERS,
    CAP_IDENTITY_MULTI,
    CAP_IDENTITY_DISCOVERY,
    CAP_IDENTITY_IMPORT_EXPORT,
    CAP_IDENTITY_HASH_RESOLUTION,
    CAP_CONTACT_MANAGEMENT,
    CAP_PAPER_MESSAGES,
    CAP_REMOTE_COMMANDS,
    CAP_VOICE_SIGNALING,
    CAP_GROUP_DELIVERY,
    CAP_EVENT_SINK_BRIDGE,
    CAP_SHARED_INSTANCE_RPC_AUTH,
];

const EMBEDDED_ALLOC_SUPPORTED: &[&str] = &[
    CAP_CURSOR_REPLAY,
    CAP_MANUAL_TICK,
    CAP_TOKEN_AUTH,
    CAP_RECEIPT_TERMINALITY,
    CAP_BATCH_SEND,
    CAP_CONFIG_REVISION_CAS,
    CAP_IDEMPOTENCY_TTL,
    CAP_TOPICS,
    CAP_TOPIC_SUBSCRIPTIONS,
    CAP_TOPIC_FANOUT,
    CAP_TELEMETRY_QUERY,
    CAP_TELEMETRY_STREAM,
    CAP_ATTACHMENTS,
    CAP_ATTACHMENT_DELETE,
    CAP_ATTACHMENT_STREAMING,
    CAP_MARKERS,
    CAP_IDENTITY_MULTI,
    CAP_IDENTITY_DISCOVERY,
    CAP_IDENTITY_IMPORT_EXPORT,
    CAP_IDENTITY_HASH_RESOLUTION,
    CAP_CONTACT_MANAGEMENT,
    CAP_PAPER_MESSAGES,
    CAP_REMOTE_COMMANDS,
    CAP_VOICE_SIGNALING,
    CAP_GROUP_DELIVERY,
    CAP_EVENT_SINK_BRIDGE,
    CAP_SHARED_INSTANCE_RPC_AUTH,
];

pub fn default_effective_limits(profile: Profile) -> EffectiveLimits {
    match profile {
        Profile::DesktopFull => EffectiveLimits {
            max_poll_events: 256,
            max_event_bytes: 65_536,
            max_batch_bytes: 1_048_576,
            max_extension_keys: 32,
            idempotency_ttl_ms: 86_400_000,
        },
        Profile::DesktopLocalRuntime => EffectiveLimits {
            max_poll_events: 64,
            max_event_bytes: 32_768,
            max_batch_bytes: 1_048_576,
            max_extension_keys: 32,
            idempotency_ttl_ms: 43_200_000,
        },
        Profile::EmbeddedAlloc => EffectiveLimits {
            max_poll_events: 32,
            max_event_bytes: 8_192,
            max_batch_bytes: 262_144,
            max_extension_keys: 32,
            idempotency_ttl_ms: 7_200_000,
        },
    }
}

pub fn default_memory_budget(profile: Profile) -> MemoryBudget {
    match profile {
        Profile::DesktopFull => MemoryBudget {
            max_heap_bytes: 268_435_456,
            max_event_queue_bytes: 67_108_864,
            max_attachment_spool_bytes: 536_870_912,
        },
        Profile::DesktopLocalRuntime => MemoryBudget {
            max_heap_bytes: 134_217_728,
            max_event_queue_bytes: 33_554_432,
            max_attachment_spool_bytes: 268_435_456,
        },
        Profile::EmbeddedAlloc => MemoryBudget {
            max_heap_bytes: 8_388_608,
            max_event_queue_bytes: 2_097_152,
            max_attachment_spool_bytes: 16_777_216,
        },
    }
}

pub fn required_capabilities(profile: Profile) -> &'static [&'static str] {
    match profile {
        Profile::DesktopFull => DESKTOP_FULL_REQUIRED,
        Profile::DesktopLocalRuntime => DESKTOP_LOCAL_RUNTIME_REQUIRED,
        Profile::EmbeddedAlloc => EMBEDDED_ALLOC_REQUIRED,
    }
}

pub fn supports_capability(profile: Profile, capability_id: &str) -> bool {
    let supported = match profile {
        Profile::DesktopFull => DESKTOP_FULL_SUPPORTED,
        Profile::DesktopLocalRuntime => DESKTOP_LOCAL_RUNTIME_SUPPORTED,
        Profile::EmbeddedAlloc => EMBEDDED_ALLOC_SUPPORTED,
    };
    supported.contains(&capability_id)
}

pub fn is_profile_method_required(profile: Profile, method: &str) -> bool {
    match profile {
        Profile::DesktopFull => !matches!(method, "tick" | "send_group"),
        Profile::DesktopLocalRuntime => {
            !matches!(method, "tick" | "subscribe_events" | "send_group")
        }
        Profile::EmbeddedAlloc => !matches!(method, "subscribe_events" | "send_group"),
    }
}

pub fn is_profile_method_supported(profile: Profile, method: &str) -> bool {
    match profile {
        Profile::DesktopFull => true,
        Profile::DesktopLocalRuntime => true,
        Profile::EmbeddedAlloc => method != "subscribe_events",
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn embedded_alloc_limits_are_constrained() {
        let limits = default_effective_limits(Profile::EmbeddedAlloc);
        assert_eq!(limits.max_poll_events, 32);
        assert_eq!(limits.max_event_bytes, 8_192);
        let memory = default_memory_budget(Profile::EmbeddedAlloc);
        assert_eq!(memory.max_heap_bytes, 8_388_608);
        assert_eq!(memory.max_event_queue_bytes, 2_097_152);
    }

    #[test]
    fn embedded_alloc_requires_manual_tick() {
        assert!(required_capabilities(Profile::EmbeddedAlloc).contains(&CAP_MANUAL_TICK));
    }

    #[test]
    fn unknown_capability_is_not_supported() {
        assert!(!supports_capability(Profile::DesktopFull, "sdk.capability.unknown"));
        assert!(!supports_capability(Profile::DesktopLocalRuntime, "sdk.capability.unknown"));
        assert!(!supports_capability(Profile::EmbeddedAlloc, "sdk.capability.unknown"));
    }

    #[test]
    fn method_support_matrix_matches_contract() {
        assert!(is_profile_method_supported(Profile::DesktopFull, "subscribe_events"));
        assert!(is_profile_method_supported(Profile::DesktopLocalRuntime, "subscribe_events"));
        assert!(!is_profile_method_supported(Profile::EmbeddedAlloc, "subscribe_events"));
        assert!(is_profile_method_supported(Profile::DesktopFull, "tick"));
        assert!(is_profile_method_supported(Profile::DesktopLocalRuntime, "tick"));
        assert!(is_profile_method_supported(Profile::EmbeddedAlloc, "tick"));
        assert!(is_profile_method_supported(Profile::DesktopFull, "send_group"));
        assert!(is_profile_method_supported(Profile::DesktopLocalRuntime, "send_group"));
        assert!(is_profile_method_supported(Profile::EmbeddedAlloc, "send_group"));
        assert!(!is_profile_method_required(Profile::DesktopFull, "send_group"));
        assert!(!is_profile_method_required(Profile::DesktopLocalRuntime, "send_group"));
        assert!(!is_profile_method_required(Profile::EmbeddedAlloc, "send_group"));
    }

    #[test]
    fn memory_budgets_reduce_for_more_constrained_profiles() {
        let full = default_memory_budget(Profile::DesktopFull);
        let local = default_memory_budget(Profile::DesktopLocalRuntime);
        let embedded = default_memory_budget(Profile::EmbeddedAlloc);
        assert!(local.max_heap_bytes <= full.max_heap_bytes);
        assert!(embedded.max_heap_bytes <= local.max_heap_bytes);
        assert!(local.max_event_queue_bytes <= full.max_event_queue_bytes);
        assert!(embedded.max_event_queue_bytes <= local.max_event_queue_bytes);
        assert!(local.max_attachment_spool_bytes <= full.max_attachment_spool_bytes);
        assert!(embedded.max_attachment_spool_bytes <= local.max_attachment_spool_bytes);
    }
}