Skip to main content

mesh_data_tile/
decoder.rs

1use std::io::Read;
2
3use crc32fast::hash as crc32;
4use flate2::read::DeflateDecoder;
5
6use crate::common::{
7    decode_no_data_field, expected_payload_length, read_numeric_value, read_u32_le, read_u64_le,
8    unpack_dtype_endian, validate_tile_id_for_mesh_kind,
9};
10use crate::consts::{
11    HEADER_CHECKSUM_INPUT_LENGTH, HEADER_CHECKSUM_OFFSET, MAGIC, OFFSET_BANDS, OFFSET_COLS,
12    OFFSET_COMPRESSED_PAYLOAD_LENGTH, OFFSET_COMPRESSION, OFFSET_DTYPE_ENDIAN, OFFSET_FORMAT_MAJOR,
13    OFFSET_MESH_KIND, OFFSET_NO_DATA_KIND, OFFSET_NO_DATA_VALUE, OFFSET_PAYLOAD_CHECKSUM,
14    OFFSET_ROWS, OFFSET_TILE_ID, OFFSET_UNCOMPRESSED_PAYLOAD_LENGTH, TILE_FIXED_HEADER_LENGTH,
15    TILE_VERSION_MAJOR,
16};
17use crate::{
18    CompressionMode, DType, DecodedTile, Endianness, MeshKind, Result, TileDimensions, TileError,
19    TileErrorCode, TileHeader,
20};
21
22#[derive(Debug)]
23struct ParsedHeader {
24    header: TileHeader,
25    compressed_payload_len: usize,
26    uncompressed_payload_len: usize,
27}
28
29pub fn inspect_tile(bytes: &[u8]) -> Result<TileHeader> {
30    let parsed = parse_header(bytes)?;
31    Ok(parsed.header)
32}
33
34pub fn decode_tile_minimal(bytes: &[u8]) -> Result<DecodedTile> {
35    let parsed = parse_header(bytes)?;
36
37    let payload_end = TILE_FIXED_HEADER_LENGTH
38        .checked_add(parsed.compressed_payload_len)
39        .ok_or_else(|| {
40            TileError::new(
41                TileErrorCode::InvalidPayloadLength,
42                "Compressed payload length overflow.",
43            )
44        })?;
45
46    let stored_payload = &bytes[TILE_FIXED_HEADER_LENGTH..payload_end];
47    let payload = decompress_payload(parsed.header.compression, stored_payload)?;
48
49    if payload.len() != parsed.uncompressed_payload_len {
50        return Err(TileError::new(
51            TileErrorCode::InvalidPayloadLength,
52            format!(
53                "Uncompressed payload length mismatch. expected={} got={}",
54                parsed.uncompressed_payload_len,
55                payload.len()
56            ),
57        ));
58    }
59
60    let payload_crc32 = crc32(&payload);
61    if payload_crc32 != parsed.header.payload_crc32 {
62        return Err(TileError::new(
63            TileErrorCode::PayloadChecksumMismatch,
64            format!(
65                "Payload checksum mismatch. expected={:08x} actual={payload_crc32:08x}",
66                parsed.header.payload_crc32
67            ),
68        ));
69    }
70
71    let expected_uncompressed_len =
72        expected_payload_length(parsed.header.dimensions, parsed.header.dtype)?;
73    if payload.len() != expected_uncompressed_len {
74        return Err(TileError::new(
75            TileErrorCode::InvalidPayloadLength,
76            format!(
77                "Decoded payload length mismatch. expected={expected_uncompressed_len} got={}",
78                payload.len()
79            ),
80        ));
81    }
82
83    Ok(DecodedTile {
84        header: parsed.header,
85        payload,
86    })
87}
88
89pub fn decode_payload_values(
90    dtype: DType,
91    endianness: Endianness,
92    payload: &[u8],
93    no_data: Option<f64>,
94) -> Result<Vec<Option<f64>>> {
95    let value_size = dtype.byte_size();
96    if !payload.len().is_multiple_of(value_size) {
97        return Err(TileError::new(
98            TileErrorCode::InvalidPayloadLength,
99            format!(
100                "Payload byte length {} is not divisible by {value_size}",
101                payload.len()
102            ),
103        ));
104    }
105
106    let mut values = Vec::with_capacity(payload.len() / value_size);
107    for chunk in payload.chunks_exact(value_size) {
108        let value = read_numeric_value(dtype, endianness, chunk)?;
109        if let Some(marker) = no_data {
110            if value.to_bits() == marker.to_bits() {
111                values.push(None);
112                continue;
113            }
114        }
115        values.push(Some(value));
116    }
117    Ok(values)
118}
119
120fn parse_header(bytes: &[u8]) -> Result<ParsedHeader> {
121    if bytes.len() < TILE_FIXED_HEADER_LENGTH {
122        return Err(TileError::new(
123            TileErrorCode::InvalidHeaderLength,
124            "File shorter than fixed header.",
125        ));
126    }
127
128    if bytes[0..4] != MAGIC {
129        return Err(TileError::new(
130            TileErrorCode::InvalidMagic,
131            "Invalid file magic.",
132        ));
133    }
134
135    let format_major = bytes[OFFSET_FORMAT_MAJOR];
136    if format_major != TILE_VERSION_MAJOR {
137        return Err(TileError::new(
138            TileErrorCode::UnsupportedVersion,
139            format!("Unsupported major version {format_major}."),
140        ));
141    }
142
143    let expected_header_crc32 = read_u32_le(bytes, HEADER_CHECKSUM_OFFSET)?;
144    let actual_header_crc32 = crc32(&bytes[..HEADER_CHECKSUM_INPUT_LENGTH]);
145    if expected_header_crc32 != actual_header_crc32 {
146        return Err(TileError::new(
147            TileErrorCode::HeaderChecksumMismatch,
148            format!(
149                "Header checksum mismatch. expected={expected_header_crc32:08x} actual={actual_header_crc32:08x}"
150            ),
151        ));
152    }
153
154    let tile_id = read_u64_le(bytes, OFFSET_TILE_ID)?;
155    let mesh_kind = MeshKind::from_code(bytes[OFFSET_MESH_KIND])?;
156    validate_tile_id_for_mesh_kind(tile_id, mesh_kind)?;
157
158    let (dtype, endianness) = unpack_dtype_endian(bytes[OFFSET_DTYPE_ENDIAN])?;
159    let compression = CompressionMode::from_code(bytes[OFFSET_COMPRESSION])?;
160
161    let dimensions = TileDimensions {
162        rows: read_u32_le(bytes, OFFSET_ROWS)?,
163        cols: read_u32_le(bytes, OFFSET_COLS)?,
164        bands: bytes[OFFSET_BANDS],
165    };
166    dimensions.validate()?;
167
168    let no_data_kind = bytes[OFFSET_NO_DATA_KIND];
169    let mut no_data_value_raw = [0_u8; 8];
170    no_data_value_raw.copy_from_slice(&bytes[OFFSET_NO_DATA_VALUE..OFFSET_NO_DATA_VALUE + 8]);
171    let no_data = decode_no_data_field(no_data_kind, no_data_value_raw, dtype, endianness)?;
172
173    let uncompressed_payload_u64 = read_u64_le(bytes, OFFSET_UNCOMPRESSED_PAYLOAD_LENGTH)?;
174    let compressed_payload_u64 = read_u64_le(bytes, OFFSET_COMPRESSED_PAYLOAD_LENGTH)?;
175    let payload_crc32 = read_u32_le(bytes, OFFSET_PAYLOAD_CHECKSUM)?;
176
177    let uncompressed_payload_len = usize::try_from(uncompressed_payload_u64).map_err(|_| {
178        TileError::new(
179            TileErrorCode::InvalidHeaderLength,
180            "uncompressed payload length exceeds platform usize.",
181        )
182    })?;
183    let compressed_payload_len = usize::try_from(compressed_payload_u64).map_err(|_| {
184        TileError::new(
185            TileErrorCode::InvalidHeaderLength,
186            "compressed payload length exceeds platform usize.",
187        )
188    })?;
189
190    let payload_end = TILE_FIXED_HEADER_LENGTH
191        .checked_add(compressed_payload_len)
192        .ok_or_else(|| {
193            TileError::new(
194                TileErrorCode::InvalidPayloadLength,
195                "Compressed payload length overflow.",
196            )
197        })?;
198
199    if bytes.len() < payload_end {
200        return Err(TileError::new(
201            TileErrorCode::InvalidPayloadLength,
202            "File shorter than declared compressed payload length.",
203        ));
204    }
205
206    let header = TileHeader {
207        format_major,
208        tile_id,
209        mesh_kind,
210        dtype,
211        endianness,
212        compression,
213        dimensions,
214        no_data_kind,
215        no_data_value_raw,
216        no_data,
217        payload_uncompressed_bytes: uncompressed_payload_u64,
218        payload_compressed_bytes: compressed_payload_u64,
219        payload_crc32,
220        header_crc32: expected_header_crc32,
221    };
222
223    Ok(ParsedHeader {
224        header,
225        compressed_payload_len,
226        uncompressed_payload_len,
227    })
228}
229
230fn decompress_payload(mode: CompressionMode, payload: &[u8]) -> Result<Vec<u8>> {
231    match mode {
232        CompressionMode::None => Ok(payload.to_vec()),
233        CompressionMode::DeflateRaw => {
234            let mut decoder = DeflateDecoder::new(payload);
235            let mut out = Vec::new();
236            decoder.read_to_end(&mut out).map_err(|err| {
237                TileError::new(
238                    TileErrorCode::DecompressionFailed,
239                    format!("Could not decompress payload using deflate-raw: {err}"),
240                )
241            })?;
242            Ok(out)
243        }
244    }
245}