sim-lib-stream-core 0.1.0

Core stream metadata, packets, envelopes, and buffer values.
Documentation
use sim_kernel::{ContentId, Coordinate, Expr, Ref, Result, Symbol, Tick};

use crate::{
    ClockDomain, StreamDiagnostic, StreamEnvelope, StreamMetadata, StreamPacket,
    StreamSecurityPolicy,
};

pub(super) fn redact_metadata(metadata: &StreamMetadata) -> StreamMetadata {
    StreamMetadata::new(
        redact_symbol(metadata.id()),
        metadata.media(),
        metadata.direction(),
        redact_clock_symbol(metadata.clock()),
        metadata.buffer().clone(),
    )
}

pub(super) fn redact_envelope(envelope: &StreamEnvelope) -> Result<StreamEnvelope> {
    StreamEnvelope::new_with_clock_domains(
        redact_symbol(envelope.stream_id()),
        redact_symbol(envelope.packet_id()),
        envelope.media(),
        envelope.direction(),
        envelope.sequence(),
        envelope.ticks().iter().map(redact_tick).collect(),
        envelope.clock_domain(),
        envelope.clock_domains().to_vec(),
        envelope.profile().clone(),
        envelope.diagnostics().iter().map(redact_symbol).collect(),
        redact_packet(envelope.packet()),
    )
}

pub(super) fn metadata_has_host_device(metadata: &StreamMetadata) -> bool {
    is_host_device_symbol(metadata.id()) || is_host_device_symbol(metadata.clock())
}

pub(super) fn envelope_has_host_device(envelope: &StreamEnvelope) -> bool {
    is_host_device_symbol(envelope.stream_id())
        || is_host_device_symbol(envelope.packet_id())
        || envelope.diagnostics().iter().any(is_host_device_symbol)
        || envelope.ticks().iter().any(tick_has_host_device)
        || packet_has_host_device(envelope.packet())
}

pub(super) fn redact_symbol(symbol: &Symbol) -> Symbol {
    if is_host_device_symbol(symbol) {
        Symbol::qualified("stream/redacted", "device")
    } else if StreamSecurityPolicy::default()
        .finding_for_text(&symbol.as_qualified_str())
        .is_some()
    {
        Symbol::qualified("stream/redacted", "security")
    } else {
        symbol.clone()
    }
}

pub(super) fn packet_has_private_payload(packet: &StreamPacket) -> bool {
    match packet {
        StreamPacket::Data(data) => {
            is_private_text(&data.kind.as_qualified_str())
                || expr_has_private_marker(&data.payload)
                || StreamSecurityPolicy::default()
                    .finding_for_expr(&data.payload)
                    .is_some()
        }
        StreamPacket::Diagnostic(diagnostic) => {
            is_private_text(&diagnostic.kind().as_qualified_str())
                || StreamSecurityPolicy::default()
                    .finding_for_text(diagnostic.message())
                    .is_some()
        }
        _ => false,
    }
}

pub(super) fn packet_has_host_device(packet: &StreamPacket) -> bool {
    match packet {
        StreamPacket::Data(data) => {
            is_host_device_symbol(&data.kind) || expr_has_host_device(&data.payload)
        }
        StreamPacket::Diagnostic(diagnostic) => {
            is_host_device_symbol(diagnostic.kind()) || is_host_device_text(diagnostic.message())
        }
        _ => false,
    }
}

pub(super) fn is_host_device_symbol(symbol: &Symbol) -> bool {
    is_host_device_text(&symbol.as_qualified_str())
}

fn redact_clock_symbol(symbol: &Symbol) -> Symbol {
    if is_host_device_symbol(symbol) {
        ClockDomain::ServerFrame.symbol()
    } else {
        symbol.clone()
    }
}

fn redact_packet(packet: &StreamPacket) -> StreamPacket {
    match packet {
        StreamPacket::Data(_) if packet_has_private_payload(packet) => StreamPacket::data(
            Symbol::qualified("stream/data", "redacted"),
            Expr::String("[redacted stream payload]".to_owned()),
        ),
        StreamPacket::Data(data) => {
            StreamPacket::data(redact_symbol(&data.kind), redact_expr(&data.payload))
        }
        StreamPacket::Diagnostic(diagnostic) => StreamPacket::Diagnostic(StreamDiagnostic::new(
            redact_symbol(diagnostic.kind()),
            redact_host_string(diagnostic.message()),
        )),
        other => other.clone(),
    }
}

fn redact_tick(tick: &Tick) -> Tick {
    Tick::new(redact_clock_symbol(&tick.clock), redact_ref(&tick.index))
}

fn redact_ref(reference: &Ref) -> Ref {
    match reference {
        Ref::Symbol(symbol) => Ref::Symbol(redact_symbol(symbol)),
        Ref::Content(content) => Ref::Content(redact_content_id(content)),
        Ref::Handle(handle) => Ref::Handle(*handle),
        Ref::Coord(coordinate) => Ref::Coord(Coordinate {
            space: redact_symbol(&coordinate.space),
            ordinal: redact_content_id(&coordinate.ordinal),
        }),
    }
}

fn redact_content_id(content: &ContentId) -> ContentId {
    ContentId::from_bytes(redact_symbol(&content.algorithm), content.bytes)
}

fn redact_expr(expr: &Expr) -> Expr {
    match expr {
        Expr::Symbol(symbol) => Expr::Symbol(redact_symbol(symbol)),
        Expr::Local(symbol) => Expr::Local(redact_symbol(symbol)),
        Expr::String(value) => Expr::String(redact_host_string(value)),
        Expr::List(items) => Expr::List(items.iter().map(redact_expr).collect()),
        Expr::Vector(items) => Expr::Vector(items.iter().map(redact_expr).collect()),
        Expr::Set(items) => Expr::Set(items.iter().map(redact_expr).collect()),
        Expr::Map(entries) => Expr::Map(
            entries
                .iter()
                .map(|(key, value)| (redact_expr(key), redact_expr(value)))
                .collect(),
        ),
        Expr::Call { operator, args } => Expr::Call {
            operator: Box::new(redact_expr(operator)),
            args: args.iter().map(redact_expr).collect(),
        },
        Expr::Infix {
            operator,
            left,
            right,
        } => Expr::Infix {
            operator: redact_symbol(operator),
            left: Box::new(redact_expr(left)),
            right: Box::new(redact_expr(right)),
        },
        Expr::Prefix { operator, arg } => Expr::Prefix {
            operator: redact_symbol(operator),
            arg: Box::new(redact_expr(arg)),
        },
        Expr::Postfix { operator, arg } => Expr::Postfix {
            operator: redact_symbol(operator),
            arg: Box::new(redact_expr(arg)),
        },
        Expr::Block(items) => Expr::Block(items.iter().map(redact_expr).collect()),
        Expr::Quote { mode, expr } => Expr::Quote {
            mode: *mode,
            expr: Box::new(redact_expr(expr)),
        },
        Expr::Annotated { expr, annotations } => Expr::Annotated {
            expr: Box::new(redact_expr(expr)),
            annotations: annotations
                .iter()
                .map(|(key, value)| (redact_symbol(key), redact_expr(value)))
                .collect(),
        },
        Expr::Extension { tag, payload } => Expr::Extension {
            tag: redact_symbol(tag),
            payload: Box::new(redact_expr(payload)),
        },
        other => other.clone(),
    }
}

fn redact_host_string(value: &str) -> String {
    if StreamSecurityPolicy::default()
        .finding_for_text(value)
        .is_some()
        || is_host_device_text(value)
    {
        "[redacted stream metadata]".to_owned()
    } else {
        value.to_owned()
    }
}

fn expr_has_private_marker(expr: &Expr) -> bool {
    match expr {
        Expr::Map(entries) => entries.iter().any(|(key, value)| {
            matches!(key, Expr::Symbol(symbol) if symbol.name.as_ref() == "private")
                && matches!(value, Expr::Bool(true))
        }),
        _ => false,
    }
}

fn expr_has_host_device(expr: &Expr) -> bool {
    match expr {
        Expr::Symbol(symbol) | Expr::Local(symbol) => is_host_device_symbol(symbol),
        Expr::String(value) => is_host_device_text(value),
        Expr::List(items) | Expr::Vector(items) | Expr::Set(items) | Expr::Block(items) => {
            items.iter().any(expr_has_host_device)
        }
        Expr::Map(entries) => entries
            .iter()
            .any(|(key, value)| expr_has_host_device(key) || expr_has_host_device(value)),
        Expr::Call { operator, args } => {
            expr_has_host_device(operator) || args.iter().any(expr_has_host_device)
        }
        Expr::Infix { left, right, .. } => {
            expr_has_host_device(left) || expr_has_host_device(right)
        }
        Expr::Prefix { arg, .. } | Expr::Postfix { arg, .. } => expr_has_host_device(arg),
        Expr::Quote { expr, .. } => expr_has_host_device(expr),
        Expr::Annotated { expr, annotations } => {
            expr_has_host_device(expr)
                || annotations
                    .iter()
                    .any(|(key, value)| is_host_device_symbol(key) || expr_has_host_device(value))
        }
        Expr::Extension { tag, payload } => {
            is_host_device_symbol(tag) || expr_has_host_device(payload)
        }
        _ => false,
    }
}

fn tick_has_host_device(tick: &Tick) -> bool {
    is_host_device_symbol(&tick.clock) || ref_has_host_device(&tick.index)
}

fn ref_has_host_device(reference: &Ref) -> bool {
    match reference {
        Ref::Symbol(symbol) => is_host_device_symbol(symbol),
        Ref::Content(content) => is_host_device_symbol(&content.algorithm),
        Ref::Handle(_) => false,
        Ref::Coord(coordinate) => {
            is_host_device_symbol(&coordinate.space)
                || is_host_device_symbol(&coordinate.ordinal.algorithm)
        }
    }
}

fn is_private_text(value: &str) -> bool {
    value.contains("private")
        || StreamSecurityPolicy::default()
            .finding_for_text(value)
            .is_some()
}

fn is_host_device_text(value: &str) -> bool {
    value.contains("/dev/")
        || value.contains("hw:")
        || value.contains("CoreAudio")
        || value.contains("ALSA")
        || value.contains("host-device")
        || value.starts_with("device/")
        || value.starts_with("host/device")
}