iris-chat 0.1.31

Iris Chat command line client and shared encrypted chat core
Documentation
use super::invites::parse_public_invite_input;
use super::*;

impl AppCore {
    #[cfg(test)]
    pub(super) fn apply_known_app_keys_snapshot(
        &mut self,
        owner: PublicKey,
        incoming_app_keys: &AppKeys,
        incoming_created_at: u64,
    ) -> Option<(AppKeys, u64)> {
        let owner_hex = owner.to_hex();
        let current = self.app_keys.get(&owner_hex).cloned();
        let current_app_keys = current.as_ref().and_then(known_app_keys_to_ndr);
        let current_created_at = current
            .as_ref()
            .map(|known| known.created_at_secs)
            .unwrap_or_default();
        let required_device = self
            .logged_in
            .as_ref()
            .filter(|logged_in| {
                self.defer_owner_app_keys_publish
                    && logged_in.owner_keys.is_some()
                    && logged_in.owner_pubkey == owner
            })
            .map(|logged_in| {
                DeviceEntry::new(logged_in.device_keys.public_key(), unix_now().get())
            });
        let applied = apply_app_keys_snapshot_with_required_device(
            current_app_keys.as_ref(),
            current_created_at,
            incoming_app_keys,
            incoming_created_at,
            required_device,
        );
        let known = known_app_keys_from_ndr(owner, &applied.app_keys, applied.created_at);
        if current.as_ref() == Some(&known) {
            return None;
        }
        self.app_keys.insert(owner_hex, known);
        Some((applied.app_keys, applied.created_at))
    }
}

pub(super) fn parse_link_device_invite_input(
    input: &str,
    owner_pubkey: PublicKey,
) -> anyhow::Result<Invite> {
    let invite = parse_public_invite_input(input)?;
    if invite.purpose.as_deref() != Some("link") {
        return Err(anyhow::anyhow!("Invalid link code."));
    }
    if invite
        .owner_public_key
        .is_some_and(|invite_owner| invite_owner != owner_pubkey)
    {
        return Err(anyhow::anyhow!("This code is for a different profile."));
    }
    Ok(invite)
}

pub(super) fn next_app_keys_created_at(now: u64, current: u64) -> u64 {
    if now <= current {
        current.saturating_add(1)
    } else {
        now
    }
}

pub(super) fn next_removed_app_keys_created_at(now: u64, current: u64, latest_device: u64) -> u64 {
    now.max(current).max(latest_device).saturating_add(2)
}

pub(super) fn normalize_device_label(label: &str) -> Option<String> {
    let normalized = label
        .chars()
        .map(|ch| if ch.is_control() { ' ' } else { ch })
        .collect::<String>()
        .split_whitespace()
        .collect::<Vec<_>>()
        .join(" ");
    let trimmed = normalized.trim();
    if trimmed.is_empty() {
        return None;
    }
    Some(truncate_chars(trimmed, 160))
}

fn truncate_chars(value: &str, max_chars: usize) -> String {
    let mut out = String::new();
    for ch in value.chars().take(max_chars) {
        out.push(ch);
    }
    out
}

pub(super) fn known_app_keys_to_ndr(known: &KnownAppKeys) -> Option<AppKeys> {
    let mut app_keys = AppKeys::new(
        known
            .devices
            .iter()
            .filter_map(|device| {
                PublicKey::parse(&device.identity_pubkey_hex)
                    .ok()
                    .map(|pubkey| DeviceEntry::new(pubkey, device.created_at_secs))
            })
            .collect(),
    );
    for device in &known.devices {
        if device.device_label.is_none() && device.client_label.is_none() {
            continue;
        }
        let Ok(pubkey) = PublicKey::parse(&device.identity_pubkey_hex) else {
            continue;
        };
        app_keys.set_device_labels(
            pubkey,
            device.device_label.clone(),
            device.client_label.clone(),
            Some(device.label_updated_at_secs),
        );
    }
    Some(app_keys)
}

pub(super) fn known_app_keys_from_ndr(
    owner: PublicKey,
    app_keys: &AppKeys,
    created_at_secs: u64,
) -> KnownAppKeys {
    let mut devices = app_keys
        .get_all_devices()
        .into_iter()
        .map(|device| KnownAppKeyDevice {
            identity_pubkey_hex: device.identity_pubkey.to_hex(),
            created_at_secs: device.created_at,
            device_label: app_keys
                .get_device_labels(&device.identity_pubkey)
                .and_then(|labels| labels.device_label.clone()),
            client_label: app_keys
                .get_device_labels(&device.identity_pubkey)
                .and_then(|labels| labels.client_label.clone()),
            label_updated_at_secs: app_keys
                .get_device_labels(&device.identity_pubkey)
                .map(|labels| labels.updated_at)
                .unwrap_or_default(),
        })
        .collect::<Vec<_>>();
    devices.sort_by(|left, right| left.identity_pubkey_hex.cmp(&right.identity_pubkey_hex));
    KnownAppKeys {
        owner_pubkey_hex: owner.to_hex(),
        created_at_secs,
        devices,
    }
}