enigma-node-types 0.0.1

Canonical node-facing types and strict codecs for Enigma (UserId hashing, identities, presence, relay envelopes).
Documentation
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::{EnigmaNodeTypesError, Result};
use crate::user_id::UserId;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayEnvelope {
    pub id: Uuid,
    pub to: UserId,
    pub from: Option<UserId>,
    pub created_at_ms: u64,
    pub expires_at_ms: Option<u64>,
    pub kind: RelayKind,
}

impl RelayEnvelope {
    pub fn validate(&self) -> Result<()> {
        if self.created_at_ms == 0 {
            return Err(EnigmaNodeTypesError::InvalidField("created_at_ms"));
        }
        if let Some(expires) = self.expires_at_ms {
            if expires <= self.created_at_ms {
                return Err(EnigmaNodeTypesError::InvalidField("expires_at_ms"));
            }
        }
        match &self.kind {
            RelayKind::OpaqueMessage(message) => {
                validate_blob(&message.blob_b64)?;
                if let Some(ct) = &message.content_type {
                    if ct.len() > 128 {
                        return Err(EnigmaNodeTypesError::InvalidField("content_type"));
                    }
                }
            }
            RelayKind::OpaqueSignaling(signaling) => {
                validate_blob(&signaling.blob_b64)?;
            }
            RelayKind::OpaqueAttachmentChunk(chunk) => {
                validate_blob(&chunk.blob_b64)?;
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RelayKind {
    OpaqueMessage(OpaqueMessage),
    OpaqueSignaling(OpaqueSignaling),
    OpaqueAttachmentChunk(OpaqueAttachmentChunk),
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct OpaqueMessage {
    pub blob_b64: String,
    pub content_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct OpaqueSignaling {
    pub blob_b64: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct OpaqueAttachmentChunk {
    pub blob_b64: String,
    pub attachment_id: Uuid,
    pub index: u32,
    pub total: Option<u32>,
}

fn validate_blob(blob_b64: &str) -> Result<()> {
    let decoded = STANDARD.decode(blob_b64.as_bytes())?;
    if decoded.is_empty() {
        return Err(EnigmaNodeTypesError::InvalidBase64);
    }
    Ok(())
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayPushRequest {
    pub envelopes: Vec<RelayEnvelope>,
}

impl RelayPushRequest {
    pub fn validate(&self) -> Result<()> {
        for envelope in &self.envelopes {
            envelope.validate()?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayPushResponse {
    pub accepted: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayPullResponse {
    pub envelopes: Vec<RelayEnvelope>,
    pub next_cursor: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayAckRequest {
    pub ids: Vec<Uuid>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct RelayAckResponse {
    pub removed: usize,
}