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 use proptest::prelude::*;
169
170 proptest! {
171 #[test]
178 fn roundtrip_arbitrary_f64(values in prop::collection::vec(any::<f64>(), 1..300)) {
179 let decoded = PcoDecoder::decode_f64(&PcoEncoder::encode_f64(&values).unwrap()).unwrap();
180 prop_assert_eq!(decoded.len(), values.len());
181 for (a, b) in values.iter().zip(&decoded) {
182 prop_assert_eq!(a.to_bits(), b.to_bits());
183 }
184 }
185
186 #[test]
187 fn roundtrip_arbitrary_i64(values in prop::collection::vec(any::<i64>(), 1..300)) {
188 let decoded = PcoDecoder::decode_i64(&PcoEncoder::encode_i64(&values).unwrap()).unwrap();
189 prop_assert_eq!(decoded, values);
190 }
191
192 #[test]
193 fn roundtrip_arbitrary_u64(values in prop::collection::vec(any::<u64>(), 1..300)) {
194 let decoded = PcoDecoder::decode_u64(&PcoEncoder::encode_u64(&values).unwrap()).unwrap();
195 prop_assert_eq!(decoded, values);
196 }
197
198 #[test]
201 fn roundtrip_arbitrary_timestamps(values in prop::collection::vec(any::<i64>(), 1..300)) {
202 let decoded =
203 PcoDecoder::decode_i64(&PcoEncoder::encode_timestamps(&values).unwrap()).unwrap();
204 prop_assert_eq!(decoded, values);
205 }
206
207 #[test]
210 fn arbitrary_bytes_do_not_decode(data in prop::collection::vec(any::<u8>(), 0..512)) {
211 let _ = PcoDecoder::decode_f64(&data);
212 let _ = PcoDecoder::decode_i64(&data);
213 let _ = PcoDecoder::decode_u64(&data);
214 }
215 }
216
217 #[test]
218 fn round_trips_all_three_types() {
219 let f: Vec<f64> = (0..5000)
220 .map(|i| 230.0 + f64::from(i % 400) / 100.0)
221 .collect();
222 assert_eq!(
223 PcoDecoder::decode_f64(&PcoEncoder::encode_f64(&f).unwrap()).unwrap(),
224 f
225 );
226 let i: Vec<i64> = (0..5000).map(|i| 42_000 + i64::from(i) * 3).collect();
227 assert_eq!(
228 PcoDecoder::decode_i64(&PcoEncoder::encode_i64(&i).unwrap()).unwrap(),
229 i
230 );
231 let u: Vec<u64> = (0..5000u64).map(|i| i * 7).collect();
232 assert_eq!(
233 PcoDecoder::decode_u64(&PcoEncoder::encode_u64(&u).unwrap()).unwrap(),
234 u
235 );
236 let specials = vec![f64::NAN, f64::INFINITY, -0.0, f64::MIN_POSITIVE, 1e300];
237 let back = PcoDecoder::decode_f64(&PcoEncoder::encode_f64(&specials).unwrap()).unwrap();
238 for (a, b) in specials.iter().zip(&back) {
239 assert_eq!(a.to_bits(), b.to_bits());
240 }
241 }
242
243 #[test]
244 fn beats_alp_on_decimal_data() {
245 let f: Vec<f64> = (0..8192)
246 .map(|i| 230.0 + f64::from((i * 37) % 4000) / 100.0)
247 .collect();
248 let pco = PcoEncoder::encode_f64(&f).unwrap().len();
249 let alp = crate::alp::AlpEncoder::encode(&f).unwrap().len();
250 assert!(pco * 4 < alp, "pco {pco} B vs ALP {alp} B");
251 }
252
253 #[test]
254 fn a_corrupt_header_cannot_allocate_and_a_wrong_count_is_an_error() {
255 let f: Vec<f64> = (0..100).map(f64::from).collect();
256 let mut enc = PcoEncoder::encode_f64(&f).unwrap();
257 enc[..4].copy_from_slice(&u32::MAX.to_le_bytes());
259 assert!(matches!(
260 PcoDecoder::decode_f64(&enc),
261 Err(EncodingError::CorruptData { .. })
262 ));
263 enc[..4].copy_from_slice(&50u32.to_le_bytes());
265 assert!(PcoDecoder::decode_f64(&enc).is_err());
266 enc[..4].copy_from_slice(&150u32.to_le_bytes());
268 assert!(PcoDecoder::decode_f64(&enc).is_err());
269 assert!(PcoDecoder::decode_f64(&[7, 0, 0, 0, 1, 2, 3]).is_err());
271 assert!(PcoDecoder::decode_f64(&[1, 0]).is_err());
272 }
273}