1use pco::data_types::Number;
26use pco::standalone::{simple_compress, simple_decompress_into};
27use pco::ChunkConfig;
28
29use crate::coding::MAX_BLOCK_VALUES;
30use crate::error::{EncodingError, Result};
31
32const HEADER: usize = 4;
33
34pub struct PcoEncoder;
36
37pub struct PcoDecoder;
39
40impl PcoEncoder {
41 pub fn encode_f64(values: &[f64]) -> Result<Vec<u8>> {
47 Self::encode(values)
48 }
49
50 pub fn encode_i64(values: &[i64]) -> Result<Vec<u8>> {
56 Self::encode(values)
57 }
58
59 pub fn encode_u64(values: &[u64]) -> Result<Vec<u8>> {
65 Self::encode(values)
66 }
67
68 pub fn encode_timestamps(values: &[i64]) -> Result<Vec<u8>> {
80 Self::encode_with(values, &ChunkConfig::default())
81 }
82
83 fn encode<T: Number>(values: &[T]) -> Result<Vec<u8>> {
84 Self::encode_with(values, &ChunkConfig::default())
85 }
86
87 fn encode_with<T: Number>(values: &[T], config: &ChunkConfig) -> Result<Vec<u8>> {
88 if values.is_empty() {
89 return Err(EncodingError::EmptyInput {
90 context: "pco encode",
91 });
92 }
93 let n = u32::try_from(values.len()).map_err(|_| EncodingError::CorruptData {
94 detail: format!("pco: {} values exceed a block", values.len()),
95 })?;
96 let body = simple_compress(values, config).map_err(|e| EncodingError::CorruptData {
97 detail: format!("pco encode: {e}"),
98 })?;
99 let mut out = Vec::with_capacity(HEADER + body.len());
100 out.extend_from_slice(&n.to_le_bytes());
101 out.extend_from_slice(&body);
102 Ok(out)
103 }
104}
105
106impl PcoDecoder {
107 pub fn decode_f64(data: &[u8]) -> Result<Vec<f64>> {
115 Self::decode(data)
116 }
117
118 pub fn decode_i64(data: &[u8]) -> Result<Vec<i64>> {
124 Self::decode(data)
125 }
126
127 pub fn decode_u64(data: &[u8]) -> Result<Vec<u64>> {
133 Self::decode(data)
134 }
135
136 fn decode<T: Number + Default + Copy>(data: &[u8]) -> Result<Vec<T>> {
137 if data.len() < HEADER {
138 return Err(EncodingError::CorruptData {
139 detail: "pco: truncated header".into(),
140 });
141 }
142 let n = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
143 if n == 0 || n > MAX_BLOCK_VALUES {
144 return Err(EncodingError::CorruptData {
145 detail: format!("pco: declared {n} values, ceiling is {MAX_BLOCK_VALUES}"),
146 });
147 }
148 let mut out = vec![T::default(); n];
150 let progress = simple_decompress_into(&data[HEADER..], &mut out).map_err(|e| {
151 EncodingError::CorruptData {
152 detail: format!("pco decode: {e}"),
153 }
154 })?;
155 if progress.n_processed != n || !progress.finished {
156 return Err(EncodingError::CountMismatch {
157 expected: n,
158 actual: progress.n_processed,
159 });
160 }
161 Ok(out)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn round_trips_all_three_types() {
171 let f: Vec<f64> = (0..5000)
172 .map(|i| 230.0 + f64::from(i % 400) / 100.0)
173 .collect();
174 assert_eq!(
175 PcoDecoder::decode_f64(&PcoEncoder::encode_f64(&f).unwrap()).unwrap(),
176 f
177 );
178 let i: Vec<i64> = (0..5000).map(|i| 42_000 + i64::from(i) * 3).collect();
179 assert_eq!(
180 PcoDecoder::decode_i64(&PcoEncoder::encode_i64(&i).unwrap()).unwrap(),
181 i
182 );
183 let u: Vec<u64> = (0..5000u64).map(|i| i * 7).collect();
184 assert_eq!(
185 PcoDecoder::decode_u64(&PcoEncoder::encode_u64(&u).unwrap()).unwrap(),
186 u
187 );
188 let specials = vec![f64::NAN, f64::INFINITY, -0.0, f64::MIN_POSITIVE, 1e300];
189 let back = PcoDecoder::decode_f64(&PcoEncoder::encode_f64(&specials).unwrap()).unwrap();
190 for (a, b) in specials.iter().zip(&back) {
191 assert_eq!(a.to_bits(), b.to_bits());
192 }
193 }
194
195 #[test]
196 fn beats_alp_on_decimal_data() {
197 let f: Vec<f64> = (0..8192)
198 .map(|i| 230.0 + f64::from((i * 37) % 4000) / 100.0)
199 .collect();
200 let pco = PcoEncoder::encode_f64(&f).unwrap().len();
201 let alp = crate::alp::AlpEncoder::encode(&f).unwrap().len();
202 assert!(pco * 4 < alp, "pco {pco} B vs ALP {alp} B");
203 }
204
205 #[test]
206 fn a_corrupt_header_cannot_allocate_and_a_wrong_count_is_an_error() {
207 let f: Vec<f64> = (0..100).map(f64::from).collect();
208 let mut enc = PcoEncoder::encode_f64(&f).unwrap();
209 enc[..4].copy_from_slice(&u32::MAX.to_le_bytes());
211 assert!(matches!(
212 PcoDecoder::decode_f64(&enc),
213 Err(EncodingError::CorruptData { .. })
214 ));
215 enc[..4].copy_from_slice(&50u32.to_le_bytes());
217 assert!(PcoDecoder::decode_f64(&enc).is_err());
218 enc[..4].copy_from_slice(&150u32.to_le_bytes());
220 assert!(PcoDecoder::decode_f64(&enc).is_err());
221 assert!(PcoDecoder::decode_f64(&[7, 0, 0, 0, 1, 2, 3]).is_err());
223 assert!(PcoDecoder::decode_f64(&[1, 0]).is_err());
224 }
225}