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}