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}