Skip to main content

ad_core_rs/
codec.rs

1use crate::ndarray::NDDataType;
2
3/// Codec names for compressed NDArray data (matching C++ `NDCodecName`).
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum CodecName {
6    None,
7    JPEG,
8    /// Zlib (deflate) compression (C++ `NDCODEC_ZLIB`).
9    Zlib,
10    LZ4,
11    Blosc,
12    BSLZ4,
13    /// LZ4 in the HDF5 framing variant (C++ `NDCODEC_LZ4HDF5`).
14    LZ4HDF5,
15}
16
17impl CodecName {
18    /// Codec name string as published in the EPICS `CODEC` parameter and the
19    /// `NDArray.codec.name` field (matching C++ `NDCodecName`).
20    pub fn as_str(self) -> &'static str {
21        match self {
22            Self::None => "",
23            Self::JPEG => "jpeg",
24            Self::Zlib => "zlib",
25            Self::LZ4 => "lz4",
26            Self::Blosc => "blosc",
27            Self::BSLZ4 => "bslz4",
28            Self::LZ4HDF5 => "lz4hdf5",
29        }
30    }
31
32    /// Value of the `COMPRESSOR` parameter that selects this codec, i.e. C
33    /// `NDCodecCompressor_t` (Codec.h:12-18): NONE=0, JPEG=1, BLOSC=2, LZ4=3,
34    /// BSLZ4=4. The Rust-only zlib/lz4hdf5 codecs take ordinals after the C set
35    /// so they never shadow a C ordinal.
36    ///
37    /// This is the single mapping between the `COMPRESSOR` wire value and the
38    /// codec, used both when the operator writes the parameter and when the
39    /// Codec plugin reports the codec it found on a decompressed array
40    /// (C `setIntegerParam(NDCodecCompressor, ...)`, NDPluginCodec.cpp:734-757).
41    pub fn ordinal(self) -> i32 {
42        match self {
43            Self::None => 0,
44            Self::JPEG => 1,
45            Self::Blosc => 2,
46            Self::LZ4 => 3,
47            Self::BSLZ4 => 4,
48            Self::Zlib => 5,
49            Self::LZ4HDF5 => 6,
50        }
51    }
52
53    /// Inverse of [`CodecName::ordinal`]. Unknown ordinals select `None`, as C
54    /// does for any compressor value outside the enum (NDPluginCodec.cpp:680-683
55    /// `case NDCODEC_NONE: default:`).
56    pub fn from_ordinal(ordinal: i32) -> Self {
57        match ordinal {
58            1 => Self::JPEG,
59            2 => Self::Blosc,
60            3 => Self::LZ4,
61            4 => Self::BSLZ4,
62            5 => Self::Zlib,
63            6 => Self::LZ4HDF5,
64            _ => Self::None,
65        }
66    }
67}
68
69/// Severity the Codec plugin reports in its `CodecStatus` parameter, i.e. C
70/// `NDCodecStatus_t` (NDPluginCodec.h:42-46).
71///
72/// C keeps three levels apart and the numeric values are the contract seen by
73/// every client of the `CodecStatus` PV:
74///
75/// - `Success` (0): the codec did its job, or the array passed through because
76///   there was nothing to do (NDPluginCodec.cpp:659, :732-735).
77/// - `Warning` (1): benign — the operation was skipped, not failed. C uses it
78///   only for "array is already compressed" (:672-675, :466-469, :361-365,
79///   :537-541), where the frame flows on unchanged.
80/// - `Error` (2): a genuine failure — unsupported input, an encoder/decoder
81///   error, a failed allocation, an unexpected codec (:141, :167, :202, :252,
82///   :279, :392-397, :760).
83///
84/// The value is carried as this enum rather than an integer so no call site can
85/// invent a level that C does not have, and so a benign skip cannot be reported
86/// with the same number as a real failure.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum CodecStatus {
89    Success = 0,
90    Warning = 1,
91    Error = 2,
92}
93
94impl CodecStatus {
95    /// The `CodecStatus` parameter value (C `(int)codecStatus`,
96    /// NDPluginCodec.cpp:776).
97    pub fn as_i32(self) -> i32 {
98        self as i32
99    }
100}
101
102/// Blosc sub-compressor names (matching C++ `NDCodecBloscCompName`).
103///
104/// Indexed by `NDCodecBloscComp_t`: `BloscLZ`, `LZ4`, `LZ4HC`, `Snappy`,
105/// `ZLIB`, `ZSTD`.
106pub const BLOSC_COMP_NAMES: [&str; 6] = ["blosclz", "lz4", "lz4hc", "snappy", "zlib", "zstd"];
107
108/// Resolve a Blosc sub-compressor index to its name, matching C++
109/// `NDCodecBloscCompName[compressor]`. Out-of-range indices return `None`.
110pub fn blosc_comp_name(compressor: i32) -> Option<&'static str> {
111    usize::try_from(compressor)
112        .ok()
113        .and_then(|i| BLOSC_COMP_NAMES.get(i).copied())
114}
115
116/// Codec information attached to a compressed NDArray.
117///
118/// `name`/`level`/`shuffle`/`compressor` mirror C++ `Codec_t` and
119/// `compressed_size` mirrors `NDArray::compressedSize`. `original_data_type`
120/// folds in C++ `NDArray::dataType`, which "holds the data type of the
121/// *uncompressed* data ... used for decompression" (NDPluginCodec.cpp:35-36).
122/// The Rust port's typed [`NDDataBuffer`](crate::ndarray::NDDataBuffer)
123/// collapses to raw bytes (`U8`) once compressed and so can no longer carry the
124/// element type; because a `Codec` exists exactly when an array is compressed,
125/// this field is the single source of truth for the original element type on
126/// every compressed array (set by the codec plugin on compress, read on
127/// decompress).
128#[derive(Debug, Clone)]
129pub struct Codec {
130    pub name: CodecName,
131    pub compressed_size: usize,
132    pub level: i32,
133    pub shuffle: i32,
134    pub compressor: i32,
135    /// Element type of the uncompressed data (C++ `NDArray::dataType`),
136    /// retained so decompression can rebuild the correctly-typed buffer.
137    pub original_data_type: NDDataType,
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_codec_clone() {
146        let c = Codec {
147            name: CodecName::LZ4,
148            compressed_size: 1024,
149            level: 0,
150            shuffle: 0,
151            compressor: 0,
152            original_data_type: NDDataType::UInt16,
153        };
154        let c2 = c.clone();
155        assert_eq!(c2.name, CodecName::LZ4);
156        assert_eq!(c2.compressed_size, 1024);
157        assert_eq!(c2.original_data_type, NDDataType::UInt16);
158    }
159
160    #[test]
161    fn test_codec_name_none() {
162        assert_eq!(CodecName::None, CodecName::None);
163        assert_ne!(CodecName::None, CodecName::JPEG);
164    }
165
166    #[test]
167    fn test_codec_name_strings() {
168        // G12: name strings must match C++ NDCodecName.
169        assert_eq!(CodecName::None.as_str(), "");
170        assert_eq!(CodecName::JPEG.as_str(), "jpeg");
171        assert_eq!(CodecName::Zlib.as_str(), "zlib");
172        assert_eq!(CodecName::LZ4.as_str(), "lz4");
173        assert_eq!(CodecName::Blosc.as_str(), "blosc");
174        assert_eq!(CodecName::BSLZ4.as_str(), "bslz4");
175        assert_eq!(CodecName::LZ4HDF5.as_str(), "lz4hdf5");
176    }
177
178    #[test]
179    fn test_blosc_comp_names() {
180        // G12: Blosc sub-compressor table must match C++ NDCodecBloscCompName.
181        assert_eq!(blosc_comp_name(0), Some("blosclz"));
182        assert_eq!(blosc_comp_name(1), Some("lz4"));
183        assert_eq!(blosc_comp_name(2), Some("lz4hc"));
184        assert_eq!(blosc_comp_name(3), Some("snappy"));
185        assert_eq!(blosc_comp_name(4), Some("zlib"));
186        assert_eq!(blosc_comp_name(5), Some("zstd"));
187        assert_eq!(blosc_comp_name(6), None);
188        assert_eq!(blosc_comp_name(-1), None);
189    }
190}