tekhsi_rs 0.1.1

High-performance client for Tektronix TekHSI enabled oscilloscopes
Documentation
use crate::tekscope::{WaveformHeader, WfmType};

pub(crate) fn is_header_valid(header: &WaveformHeader) -> bool {
    // The reference implementation does not validate these fields, but we assume
    // real devices do not send headers with no data or zero samples. We reject
    // such headers early rather than decode partial or nonsensical data.
    if header.noofsamples == 0 || !header.hasdata {
        return false;
    }

    match header.wfmtype() {
        WfmType::WfmtypeAnalog8
        | WfmType::WfmtypeAnalog16
        | WfmType::WfmtypeAnalogFloat
        | WfmType::WfmtypeDigital8
        | WfmType::WfmtypeDigital16 => true,
        WfmType::WfmtypeAnalog16Iq | WfmType::WfmtypeAnalog32Iq => header.iq_span != 0.0,
        WfmType::WfmtypeUnspecified => false,
    }
}

pub(crate) fn expected_payload_len(header: &WaveformHeader) -> usize {
    (header.noofsamples as usize).saturating_mul(header.sourcewidth as usize)
}

#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
    use super::{expected_payload_len, is_header_valid};
    use crate::tekscope::WaveformHeader;

    fn base_header() -> WaveformHeader {
        WaveformHeader {
            sourcename: "ch1".to_string(),
            noofsamples: 1,
            hasdata: true,
            ..Default::default()
        }
    }

    #[test]
    fn header_validation_by_wfmtype() {
        let analog = WaveformHeader {
            wfmtype: 1,
            sourcewidth: 4,
            ..base_header()
        };
        assert!(is_header_valid(&analog));

        let digital = WaveformHeader {
            wfmtype: 4,
            sourcewidth: 1,
            ..base_header()
        };
        assert!(is_header_valid(&digital));

        let iq = WaveformHeader {
            wfmtype: 6,
            sourcewidth: 2,
            iq_span: 1.0,
            ..base_header()
        };
        assert!(is_header_valid(&iq));

        let unspecified = WaveformHeader {
            wfmtype: 0,
            sourcewidth: 1,
            ..base_header()
        };
        assert!(!is_header_valid(&unspecified));
    }

    #[test]
    fn header_validation_rejects_zero_iq_span() {
        let iq = WaveformHeader {
            wfmtype: 6,
            sourcewidth: 2,
            iq_span: 0.0,
            ..base_header()
        };
        assert!(!is_header_valid(&iq));
    }

    #[test]
    fn header_validation_rejects_no_data_or_samples() {
        let no_samples = WaveformHeader {
            wfmtype: 1,
            sourcewidth: 1,
            noofsamples: 0,
            hasdata: true,
            ..base_header()
        };
        assert!(!is_header_valid(&no_samples));

        let no_data = WaveformHeader {
            wfmtype: 1,
            sourcewidth: 1,
            noofsamples: 1,
            hasdata: false,
            ..base_header()
        };
        assert!(!is_header_valid(&no_data));
    }

    #[test]
    fn expected_payload_len_saturates_on_overflow() {
        let header = WaveformHeader {
            noofsamples: u64::MAX,
            sourcewidth: u32::MAX,
            ..base_header()
        };
        assert_eq!(expected_payload_len(&header), usize::MAX);
    }
}