spvirit-tools 0.1.9

PVAccess client/server tools for EPICS
Documentation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SupportLevel {
    Supported,
    Rejected,
    NotApplicable,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandRole {
    ClientToServer,
    ServerToClient,
    Bidirectional,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProtocolCommandCoverage {
    pub command: u8,
    pub name: &'static str,
    pub role: CommandRole,
    pub support: SupportLevel,
    pub notes: &'static str,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NtFamilyCoverage {
    pub name: &'static str,
    pub support: SupportLevel,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecordTypeCoverage {
    pub name: &'static str,
    pub support: SupportLevel,
}

pub const COMMAND_COVERAGE: [ProtocolCommandCoverage; 23] = [
    ProtocolCommandCoverage {
        command: 0,
        name: "BEACON",
        role: CommandRole::ServerToClient,
        support: SupportLevel::NotApplicable,
        notes: "Server broadcast message; validated via encode/decode vector tests.",
    },
    ProtocolCommandCoverage {
        command: 1,
        name: "CONNECTION_VALIDATION",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Full handshake supported on TCP session setup.",
    },
    ProtocolCommandCoverage {
        command: 2,
        name: "ECHO",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Application command is echoed by server; control-plane echo is also supported.",
    },
    ProtocolCommandCoverage {
        command: 3,
        name: "SEARCH",
        role: CommandRole::ClientToServer,
        support: SupportLevel::Supported,
        notes: "UDP search request/response implemented.",
    },
    ProtocolCommandCoverage {
        command: 4,
        name: "SEARCH_RESPONSE",
        role: CommandRole::ServerToClient,
        support: SupportLevel::NotApplicable,
        notes: "Server response command; covered by codec and UDP integration tests.",
    },
    ProtocolCommandCoverage {
        command: 5,
        name: "AUTHNZ",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed and rejected with explicit status message.",
    },
    ProtocolCommandCoverage {
        command: 6,
        name: "ACL_CHANGE",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed and rejected with explicit status message.",
    },
    ProtocolCommandCoverage {
        command: 7,
        name: "CREATE_CHANNEL",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Channel create response lifecycle implemented.",
    },
    ProtocolCommandCoverage {
        command: 8,
        name: "DESTROY_CHANNEL",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Destroy request handled by SID/CID state cleanup.",
    },
    ProtocolCommandCoverage {
        command: 9,
        name: "CONNECTION_VALIDATED",
        role: CommandRole::ServerToClient,
        support: SupportLevel::NotApplicable,
        notes: "Server handshake response command.",
    },
    ProtocolCommandCoverage {
        command: 10,
        name: "GET",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Init and data paths implemented.",
    },
    ProtocolCommandCoverage {
        command: 11,
        name: "PUT",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Init/data/get-put paths implemented with access checks.",
    },
    ProtocolCommandCoverage {
        command: 12,
        name: "PUT_GET",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Init and data paths implemented.",
    },
    ProtocolCommandCoverage {
        command: 13,
        name: "MONITOR",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Init/start/stop/destroy/pipeline ack paths implemented.",
    },
    ProtocolCommandCoverage {
        command: 14,
        name: "ARRAY",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed as op and rejected with operation status.",
    },
    ProtocolCommandCoverage {
        command: 15,
        name: "DESTROY_REQUEST",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Monitor destroy request cleanup implemented.",
    },
    ProtocolCommandCoverage {
        command: 16,
        name: "PROCESS",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed as op and rejected with operation status.",
    },
    ProtocolCommandCoverage {
        command: 17,
        name: "GET_FIELD",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Channel introspection supported; list-mode enumeration is gated by server config.",
    },
    ProtocolCommandCoverage {
        command: 18,
        name: "MESSAGE",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Client-initiated MESSAGE is rejected.",
    },
    ProtocolCommandCoverage {
        command: 19,
        name: "MULTIPLE_DATA",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed and rejected with explicit status message.",
    },
    ProtocolCommandCoverage {
        command: 20,
        name: "RPC",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Supported,
        notes: "Supported for server channel list endpoint when --pvlist-mode=list; otherwise rejected.",
    },
    ProtocolCommandCoverage {
        command: 21,
        name: "CANCEL_REQUEST",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed and rejected with explicit status message.",
    },
    ProtocolCommandCoverage {
        command: 22,
        name: "ORIGIN_TAG",
        role: CommandRole::Bidirectional,
        support: SupportLevel::Rejected,
        notes: "Parsed and rejected with explicit status message.",
    },
];

pub fn command_coverage() -> &'static [ProtocolCommandCoverage] {
    &COMMAND_COVERAGE
}

pub const NT_FAMILY_COVERAGE: [NtFamilyCoverage; 3] = [
    NtFamilyCoverage {
        name: "NTScalarArray",
        support: SupportLevel::Supported,
    },
    NtFamilyCoverage {
        name: "NTTable",
        support: SupportLevel::Supported,
    },
    NtFamilyCoverage {
        name: "NTNDArray",
        support: SupportLevel::Supported,
    },
];

pub fn nt_family_coverage() -> &'static [NtFamilyCoverage] {
    &NT_FAMILY_COVERAGE
}

pub const ARRAY_RECORD_COVERAGE: [RecordTypeCoverage; 4] = [
    RecordTypeCoverage {
        name: "waveform",
        support: SupportLevel::Supported,
    },
    RecordTypeCoverage {
        name: "aai",
        support: SupportLevel::Supported,
    },
    RecordTypeCoverage {
        name: "aao",
        support: SupportLevel::Supported,
    },
    RecordTypeCoverage {
        name: "subArray",
        support: SupportLevel::Supported,
    },
];

pub fn array_record_coverage() -> &'static [RecordTypeCoverage] {
    &ARRAY_RECORD_COVERAGE
}

#[cfg(test)]
mod tests {
    use std::collections::HashSet;

    use super::{array_record_coverage, command_coverage, nt_family_coverage};

    #[test]
    fn matrix_covers_all_protocol_command_ids() {
        let entries = command_coverage();
        assert_eq!(entries.len(), 23);

        let mut ids = HashSet::new();
        for entry in entries {
            assert!(
                ids.insert(entry.command),
                "duplicate command {}",
                entry.command
            );
            assert!(entry.command <= 22);
            assert!(!entry.name.is_empty());
            assert!(!entry.notes.is_empty());
        }

        for id in 0u8..=22u8 {
            assert!(ids.contains(&id), "missing command {}", id);
        }
    }

    #[test]
    fn matrix_covers_nt_families() {
        let rows = nt_family_coverage();
        assert_eq!(rows.len(), 3);
        let names: HashSet<&str> = rows.iter().map(|r| r.name).collect();
        assert!(names.contains("NTScalarArray"));
        assert!(names.contains("NTTable"));
        assert!(names.contains("NTNDArray"));
    }

    #[test]
    fn matrix_covers_array_record_types() {
        let rows = array_record_coverage();
        assert_eq!(rows.len(), 4);
        let names: HashSet<&str> = rows.iter().map(|r| r.name).collect();
        assert!(names.contains("waveform"));
        assert!(names.contains("aai"));
        assert!(names.contains("aao"));
        assert!(names.contains("subArray"));
    }
}