Skip to main content

hpr_io/grib2/
complex.rs

1//! Complex packing, with and without spatial differencing: data representation templates 5.2
2//! and 5.3, and the data they pack (templates 7.2 and 7.3).
3//!
4//! The values are split into groups. Each group has a reference `X1`, a width `W` in bits and a
5//! length `L`; its values are `X1 + X2` with `X2` an integer of `W` bits (none when `W = 0`, so the
6//! group is `X1` throughout). Section 7 holds, in order: for 5.3, the extra descriptors (the first
7//! one or two values and the overall minimum of the differences, each a fixed number of bytes,
8//! [`SpatialDifferencing::octets`], the minimum signed); every group's reference, then every
9//! group's width above the reference width, then every group's length as a scaled number `l`,
10//! each list padded to a whole byte; then the groups' values, one after another. A group's
11//! length is `L = L_ref + l · increment`, except the last's, which is given as it is (WMO-No. 306, Volume I.2, templates 5.2, 5.3, 7.2, 7.3 and Regulation 92.9.4).
12//!
13//! **Missing values** (code table 5.5): with primary missing values, a group of width `W > 0`
14//! marks a missing value by `X2 = 2^W − 1`, and a group of width 0 is missing throughout when its
15//! reference is all ones; with secondary ones too, `2^W − 2` (or a reference of all ones but the
16//! last bit) marks the secondary kind. Both read as no value.
17//!
18//! **Spatial differencing** (5.3) packs differences of the values, taken over the values that are
19//! not missing, in grid order. With `h` the reconstructed integers, `z` the packed ones and `m`
20//! the overall minimum:
21//!
22//! - first order: `h₁` is given, and `hₙ = zₙ + m + hₙ₋₁`;
23//! - second order: `h₁` and `h₂` are given, and `hₙ = zₙ + m + 2 hₙ₋₁ − hₙ₋₂`.
24//!
25//! The packed integers in the first one or two places are not used. Each `h` then unpacks as simple
26//! packing does, `Y = (R + h · 2^E) / 10^D`.
27
28use serde::{Deserialize, Serialize};
29
30use super::{Grib2Error, be_i16, be_u32, malformed, need, truncated};
31
32/// Complex packing's parameters: data representation templates 5.2 and 5.3.
33#[non_exhaustive]
34#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
35pub struct ComplexPacking {
36    /// The reference value `R`, as written (a 32-bit float).
37    pub reference: f32,
38    /// The binary scale factor `E`.
39    pub binary_scale: i16,
40    /// The decimal scale factor `D`.
41    pub decimal_scale: i16,
42    /// How many values are packed: the grid points the bitmap marks, or all of them.
43    pub count: u32,
44    /// Bits per group reference.
45    pub group_reference_bits: u8,
46    /// How missing values are marked (code table 5.5): 0 not at all, 1 primary, 2 primary and
47    /// secondary.
48    pub missing_values: u8,
49    /// The number of groups.
50    pub groups: u32,
51    /// The reference for group widths, bits.
52    pub width_reference: u8,
53    /// Bits per group width.
54    pub width_bits: u8,
55    /// The reference for group lengths.
56    pub length_reference: u32,
57    /// The increment for group lengths.
58    pub length_increment: u8,
59    /// The last group's length, as it is.
60    pub last_length: u32,
61    /// Bits per scaled group length.
62    pub length_bits: u8,
63    /// Template 5.3's spatial differencing; `None` for 5.2.
64    pub spatial_differencing: Option<SpatialDifferencing>,
65}
66
67/// Template 5.3's spatial differencing.
68#[non_exhaustive]
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70pub struct SpatialDifferencing {
71    /// The order, 1 or 2 (code table 5.6).
72    pub order: u8,
73    /// Bytes per extra descriptor in section 7, 1 to 8.
74    pub octets: u8,
75}
76
77impl ComplexPacking {
78    /// `Y = (R + h · 2^E) / 10^D` for a reconstructed integer `h`.
79    #[must_use]
80    #[allow(
81        clippy::cast_precision_loss,
82        reason = "a valid field's integers are far below 2^53; a hostile one's only lose digits"
83    )]
84    pub fn unpack(&self, h: i64) -> f64 {
85        super::unpack(
86            self.reference,
87            self.binary_scale,
88            self.decimal_scale,
89            h as f64,
90        )
91    }
92}
93
94/// Section 5, template 5.2 or 5.3, from the section's bytes.
95pub(super) fn read(s: &[u8], template: u16, message: usize) -> Result<ComplexPacking, Grib2Error> {
96    let s = need(s, 47, "data template 5.2", message)?;
97    // Table 5.1: 0 floating point, 1 integer. Either unpacks the same way.
98    if s[20] > 1 {
99        return Err(Grib2Error::Unsupported {
100            message,
101            what: "type of original field values",
102            value: s[20].into(),
103        });
104    }
105    let reference = f32::from_bits(be_u32(&s[11..15]));
106    if !reference.is_finite() {
107        return Err(malformed(message, "the reference value is not finite"));
108    }
109    // Code table 5.4: 1 is general group splitting. With 0, row by row, the group lengths
110    // have no meaning (note 1 to template 5.2).
111    if s[21] != 1 {
112        return Err(Grib2Error::Unsupported {
113            message,
114            what: "group splitting method",
115            value: s[21].into(),
116        });
117    }
118    let missing_values = s[22];
119    if missing_values > 2 {
120        return Err(Grib2Error::Unsupported {
121            message,
122            what: "missing value management",
123            value: missing_values.into(),
124        });
125    }
126    let spatial_differencing = if template == 3 {
127        let s = need(s, 49, "data template 5.3", message)?;
128        let (order, octets) = (s[47], s[48]);
129        if !(1..=2).contains(&order) {
130            return Err(Grib2Error::Unsupported {
131                message,
132                what: "order of spatial differencing",
133                value: order.into(),
134            });
135        }
136        if !(1..=8).contains(&octets) {
137            return Err(Grib2Error::Unsupported {
138                message,
139                what: "bytes per extra descriptor",
140                value: octets.into(),
141            });
142        }
143        Some(SpatialDifferencing { order, octets })
144    } else {
145        None
146    };
147    let packing = ComplexPacking {
148        reference,
149        binary_scale: be_i16(&s[15..17]),
150        decimal_scale: be_i16(&s[17..19]),
151        count: be_u32(&s[5..9]),
152        group_reference_bits: s[19],
153        missing_values,
154        groups: be_u32(&s[31..35]),
155        width_reference: s[35],
156        width_bits: s[36],
157        length_reference: be_u32(&s[37..41]),
158        length_increment: s[41],
159        last_length: be_u32(&s[42..46]),
160        length_bits: s[46],
161        spatial_differencing,
162    };
163    for (what, bits) in [
164        ("bits per group reference", packing.group_reference_bits),
165        ("bits per group width", packing.width_bits),
166        ("bits per group length", packing.length_bits),
167    ] {
168        if bits > 32 {
169            return Err(Grib2Error::Unsupported {
170                message,
171                what,
172                value: bits.into(),
173            });
174        }
175    }
176    if missing_values == 2 && packing.group_reference_bits == 0 {
177        // A 0-bit reference is all ones, so every group of width 0 is missing (ecCodes writes a
178        // field with no values that way); all ones less one, the secondary code, has no bits.
179        return Err(Grib2Error::Unsupported {
180            message,
181            what: "secondary missing values with 0-bit group references, management",
182            value: missing_values.into(),
183        });
184    }
185    Ok(packing)
186}
187
188/// Where each part of section 7 starts, in bits from the start of its data, found by
189/// [`layout`], with the packing it was checked against: the field's public copy may be changed,
190/// this one not.
191#[derive(Debug, Clone, Copy, PartialEq)]
192pub(super) struct Layout {
193    packing: ComplexPacking,
194    /// The first one or two values, and the overall minimum of the differences.
195    first: [i64; 2],
196    minimum: i64,
197    references: u64,
198    widths: u64,
199    lengths: u64,
200    values: u64,
201}
202
203/// Checks section 7 holds what the packing says, reading each group's width and length once,
204/// and gives where its parts start.
205///
206/// The groups' lengths must add up to the packed count and their values fit the data; a group
207/// wider than 32 bits is refused. The work is bounded by the file's size: the lists of widths and
208/// lengths hold a group each, and when both are 0 bits wide every group is alike and is counted
209/// at once.
210pub(super) fn layout(
211    p: &ComplexPacking,
212    data: &[u8],
213    message: usize,
214) -> Result<Layout, Grib2Error> {
215    let have = data.len() as u64 * 8;
216    let groups = u64::from(p.groups);
217    let count = u64::from(p.count);
218    if groups == 0 && count > 0 {
219        // ecCodes reads this as the reference everywhere (its issue ECC-2095); the regulation
220        // doesn't say.
221        return Err(Grib2Error::Unsupported {
222            message,
223            what: "complex packing with no groups, values",
224            value: count,
225        });
226    }
227    if groups > count {
228        return Err(malformed(
229            message,
230            format!("{groups} groups for {count} packed values"),
231        ));
232    }
233    let (mut first, mut minimum, mut at) = ([0; 2], 0, 0_u64);
234    if let Some(d) = p.spatial_differencing {
235        let octets = usize::from(d.octets);
236        let len = octets * (usize::from(d.order) + 1);
237        if data.len() < len {
238            return Err(truncated(message, "the extra descriptors", len, data.len()));
239        }
240        let field = |k: usize| &data[k * octets..(k + 1) * octets];
241        for (k, slot) in first.iter_mut().take(usize::from(d.order)).enumerate() {
242            // The first values are scaled values less the reference, so not negative. ecCodes
243            // reads them unsigned and NCEP's g2clib as sign and magnitude, so a top bit set
244            // would be read two ways.
245            if field(k)[0] & 0x80 != 0 {
246                return Err(malformed(
247                    message,
248                    "a first value has its top bit set, which decoders read two ways",
249                ));
250            }
251            *slot = sign_magnitude(field(k));
252        }
253        minimum = sign_magnitude(field(usize::from(d.order)));
254        at = len as u64 * 8;
255    }
256    let list = |at: u64, bits: u8| at + (groups * u64::from(bits)).div_ceil(8) * 8;
257    let references = at;
258    let widths = list(references, p.group_reference_bits);
259    let lengths = list(widths, p.width_bits);
260    let values = list(lengths, p.length_bits);
261    if values > have {
262        return Err(truncated(
263            message,
264            "the group descriptors",
265            values.div_ceil(8),
266            data.len(),
267        ));
268    }
269    let wide = |width: u64| Grib2Error::Unsupported {
270        message,
271        what: "group width, bits",
272        value: width,
273    };
274    let (mut total, mut bits) = (0_u64, 0_u64);
275    if p.width_bits == 0 && p.length_bits == 0 {
276        // Every group but the last is `length_reference` long, and all `width_reference` wide.
277        let width = u64::from(p.width_reference);
278        if width > 32 {
279            return Err(wide(width));
280        }
281        // Below 2^64 (`groups`, `length_reference` and `last_length` are 32 bits); `bits` only
282        // counts when the lengths add up. No groups hold no values (the count is then 0 too).
283        total = groups.checked_sub(1).map_or(0, |g| {
284            g * u64::from(p.length_reference) + u64::from(p.last_length)
285        });
286        bits = if total <= count { total * width } else { 0 };
287    } else {
288        for k in 0..groups {
289            let (width, length) = group(p, data, widths, lengths, k);
290            if width > 32 {
291                return Err(wide(width));
292            }
293            total += length;
294            // A group is at most 2^40 long and 32 bits wide, and `total ≤ count ≤ 2^32` before
295            // it, so neither sum can overflow.
296            bits += length * width;
297            if total > count {
298                break;
299            }
300        }
301    }
302    if total != count {
303        return Err(malformed(
304            message,
305            format!("the groups hold {total} values but section 5 packs {count}"),
306        ));
307    }
308    if values + bits > have {
309        return Err(truncated(
310            message,
311            "the packed values",
312            (values + bits).div_ceil(8),
313            data.len(),
314        ));
315    }
316    Ok(Layout {
317        packing: *p,
318        first,
319        minimum,
320        references,
321        widths,
322        lengths,
323        values,
324    })
325}
326
327/// Group `k`'s width and length.
328fn group(p: &ComplexPacking, data: &[u8], widths: u64, lengths: u64, k: u64) -> (u64, u64) {
329    let wb = u64::from(p.width_bits);
330    let width = u64::from(p.width_reference) + bits(data, widths + k * wb, p.width_bits);
331    let length = if k + 1 == u64::from(p.groups) {
332        u64::from(p.last_length)
333    } else {
334        let lb = u64::from(p.length_bits);
335        u64::from(p.length_reference)
336            + bits(data, lengths + k * lb, p.length_bits) * u64::from(p.length_increment)
337    };
338    (width, length)
339}
340
341/// `n ≤ 32` bits from bit `start`, first bit high; bits past the data read as 0 (`layout` checked
342/// the data holds every bit read).
343pub(super) fn bits(data: &[u8], start: u64, n: u8) -> u64 {
344    if n == 0 {
345        return 0;
346    }
347    let byte = usize::try_from(start / 8).unwrap_or(usize::MAX);
348    let mut word = [0_u8; 8];
349    let rest = data.get(byte..).unwrap_or(&[]);
350    let take = rest.len().min(8);
351    word[..take].copy_from_slice(&rest[..take]);
352    // `start % 8 ≤ 7` and `n ≤ 32`, so the bits wanted are inside the 64 read.
353    (u64::from_be_bytes(word) << (start % 8)) >> (64 - u32::from(n))
354}
355
356/// A sign-and-magnitude integer of 1 to 8 bytes.
357fn sign_magnitude(b: &[u8]) -> i64 {
358    let mut raw: u64 = 0;
359    for &byte in b {
360        raw = (raw << 8) | u64::from(byte);
361    }
362    let top = 8 * b.len() as u32 - 1;
363    // Below 2^63 once the sign bit is cleared.
364    let magnitude = i64::try_from(raw & ((1_u64 << top) - 1)).unwrap_or(i64::MAX);
365    if raw >> top & 1 == 1 {
366        -magnitude
367    } else {
368        magnitude
369    }
370}
371
372/// Decodes the packed values in order, up to (not including) position `end`, calling `sink`
373/// with each position and its value, or `None` for a missing value.
374///
375/// # Errors
376/// [`Grib2Error::Malformed`] when reconstructing the differences overflows 64 bits, which only a
377/// broken file does.
378pub(super) fn decode(
379    layout: &Layout,
380    data: &[u8],
381    end: u64,
382    message: usize,
383    mut sink: impl FnMut(u64, Option<f64>),
384) -> Result<(), Grib2Error> {
385    let p = &layout.packing;
386    let order = p.spatial_differencing.map_or(0, |d| d.order);
387    let rb = p.group_reference_bits;
388    let all_ones = |bits: u64| (1_u64 << bits) - 1;
389    let overflow = || malformed(message, "the spatial differences overflow 64 bits");
390    let (mut position, mut at, mut present) = (0_u64, layout.values, 0_u64);
391    let (mut previous, mut before) = (0_i64, 0_i64);
392    for k in 0..u64::from(p.groups) {
393        if position >= end {
394            break;
395        }
396        let (width, length) = group(p, data, layout.widths, layout.lengths, k);
397        let reference = bits(data, layout.references + k * u64::from(rb), rb);
398        for _ in 0..length {
399            if position >= end {
400                return Ok(());
401            }
402            // `layout` refused a group wider than 32 bits.
403            let w = u8::try_from(width).unwrap_or(32);
404            let (x, missing) = if width == 0 {
405                let r = u64::from(rb);
406                (
407                    reference,
408                    (p.missing_values >= 1 && reference == all_ones(r))
409                        || (p.missing_values == 2 && reference == all_ones(r) - 1),
410                )
411            } else {
412                let x2 = bits(data, at, w);
413                at += width;
414                (
415                    reference + x2,
416                    (p.missing_values >= 1 && x2 == all_ones(width))
417                        || (p.missing_values == 2 && x2 == all_ones(width) - 1),
418                )
419            };
420            if missing {
421                sink(position, None);
422                position += 1;
423                continue;
424            }
425            // `reference + x2 < 2^33`.
426            let z = i64::try_from(x).unwrap_or(i64::MAX);
427            let h = match (order, present) {
428                (0, _) => z,
429                (_, 0) => layout.first[0],
430                (2, 1) => layout.first[1],
431                (1, _) => z
432                    .checked_add(layout.minimum)
433                    .and_then(|v| v.checked_add(previous))
434                    .ok_or_else(overflow)?,
435                _ => z
436                    .checked_add(layout.minimum)
437                    .and_then(|v| v.checked_add(previous.checked_mul(2)?))
438                    .and_then(|v| v.checked_sub(before))
439                    .ok_or_else(overflow)?,
440            };
441            before = previous;
442            previous = h;
443            present += 1;
444            sink(position, Some(p.unpack(h)));
445            position += 1;
446        }
447    }
448    Ok(())
449}