enigma-rtc 0.1.0

WebRTC signaling and session management for Enigma Messenger
Documentation
use bytes::Bytes;
use serde_json::Value;

use crate::error::{EnigmaRtcError, RtcResult};
use crate::types::SignalingMessage;

pub fn encode_signaling(message: &SignalingMessage) -> String {
    match serde_json::to_string(message) {
        Ok(json) => json,
        Err(_) => "{\"type\":\"hangup\",\"reason\":\"serialization_error\"}".to_string(),
    }
}

pub fn decode_signaling(payload: &str) -> RtcResult<SignalingMessage> {
    let buffer = Bytes::copy_from_slice(payload.as_bytes());
    let value: Value = serde_json::from_slice(buffer.as_ref())
        .map_err(|e| EnigmaRtcError::JsonError(e.to_string()))?;
    let message_type = value
        .get("type")
        .and_then(Value::as_str)
        .ok_or_else(|| EnigmaRtcError::JsonError("missing type".into()))?;
    match message_type {
        "offer" => parse_offer(&value),
        "answer" => parse_answer(&value),
        "ice_candidate" => parse_candidate(&value),
        "hangup" => parse_hangup(&value),
        _ => Err(EnigmaRtcError::JsonError("unknown message type".into())),
    }
}

fn parse_offer(value: &Value) -> RtcResult<SignalingMessage> {
    let sdp = extract_string(value, "sdp")?;
    if sdp.trim().is_empty() {
        return Err(EnigmaRtcError::InvalidSdp);
    }
    Ok(SignalingMessage::Offer { sdp })
}

fn parse_answer(value: &Value) -> RtcResult<SignalingMessage> {
    let sdp = extract_string(value, "sdp")?;
    if sdp.trim().is_empty() {
        return Err(EnigmaRtcError::InvalidSdp);
    }
    Ok(SignalingMessage::Answer { sdp })
}

fn parse_candidate(value: &Value) -> RtcResult<SignalingMessage> {
    let candidate = extract_string(value, "candidate")?;
    if candidate.trim().is_empty() {
        return Err(EnigmaRtcError::InvalidCandidate);
    }
    let sdp_mid = value
        .get("sdp_mid")
        .and_then(Value::as_str)
        .map(|s| s.to_string());
    let sdp_mline_index = match value.get("sdp_mline_index") {
        Some(v) => Some(
            v.as_u64()
                .ok_or_else(|| EnigmaRtcError::JsonError("invalid sdp_mline_index".into()))?
                as u16,
        ),
        None => None,
    };
    Ok(SignalingMessage::IceCandidate {
        candidate,
        sdp_mid,
        sdp_mline_index,
    })
}

fn parse_hangup(value: &Value) -> RtcResult<SignalingMessage> {
    let reason = value
        .get("reason")
        .and_then(Value::as_str)
        .map(|s| s.to_string());
    Ok(SignalingMessage::Hangup { reason })
}

fn extract_string(value: &Value, key: &str) -> RtcResult<String> {
    value
        .get(key)
        .and_then(Value::as_str)
        .map(|s| s.to_string())
        .ok_or_else(|| EnigmaRtcError::JsonError(format!("missing {key}")))
}