1use std::collections::HashMap;
4
5use bytes::{Buf, Bytes};
6use thiserror::Error;
7use tl_gige::gvsp::{self, ChunkRaw};
8use tracing::{debug, warn};
9
10const KNOWN_CHUNKS: &[KnownChunk] = &[
12 KnownChunk {
13 id: 0x0001,
14 kind: ChunkKind::Timestamp,
15 decoder: ValueDecoder::U64,
16 },
17 KnownChunk {
18 id: 0x1002,
19 kind: ChunkKind::ExposureTime,
20 decoder: ValueDecoder::F64,
21 },
22 KnownChunk {
23 id: 0x1003,
24 kind: ChunkKind::Gain,
25 decoder: ValueDecoder::F64,
26 },
27 KnownChunk {
28 id: 0x0201,
29 kind: ChunkKind::LineStatusAll,
30 decoder: ValueDecoder::U32,
31 },
32];
33
34#[derive(Copy, Clone)]
35struct KnownChunk {
36 id: u16,
37 kind: ChunkKind,
38 decoder: ValueDecoder,
39}
40
41#[derive(Copy, Clone)]
42enum ValueDecoder {
43 U64,
44 F64,
45 U32,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum ChunkKind {
51 Timestamp,
52 ExposureTime,
53 Gain,
54 LineStatusAll,
55 Unknown(u16),
56}
57
58#[derive(Debug, Clone, PartialEq)]
60pub enum ChunkValue {
61 U64(u64),
62 F64(f64),
63 U32(u32),
64 Bytes(Bytes),
65}
66
67pub type ChunkMap = HashMap<ChunkKind, ChunkValue>;
68
69#[derive(Debug, Error)]
71pub enum ChunkError {
72 #[error("chunk {id:#06x} payload length {actual} shorter than required {expected} bytes")]
73 InvalidLength {
74 id: u16,
75 expected: usize,
76 actual: usize,
77 },
78}
79
80fn decode_known(raw: &ChunkRaw, entry: &KnownChunk) -> Result<(ChunkKind, ChunkValue), ChunkError> {
81 let expected = match entry.decoder {
82 ValueDecoder::U64 | ValueDecoder::F64 => 8,
83 ValueDecoder::U32 => 4,
84 };
85 if raw.data.len() < expected {
86 warn!(
87 chunk_id = format_args!("{:#06x}", raw.id),
88 len = raw.data.len(),
89 expected,
90 "truncated chunk payload"
91 );
92 return Err(ChunkError::InvalidLength {
93 id: raw.id,
94 expected,
95 actual: raw.data.len(),
96 });
97 }
98 let mut cursor = raw.data.clone();
99 let value = match entry.decoder {
100 ValueDecoder::U64 => ChunkValue::U64(cursor.get_u64_le()),
101 ValueDecoder::F64 => ChunkValue::F64(cursor.get_f64_le()),
102 ValueDecoder::U32 => ChunkValue::U32(cursor.get_u32_le()),
103 };
104 debug!(
105 chunk_id = format_args!("{:#06x}", raw.id),
106 len = raw.data.len(),
107 kind = ?entry.kind,
108 "decoded known chunk"
109 );
110 Ok((entry.kind, value))
111}
112
113pub fn decode_raw_chunks(chunks: &[ChunkRaw]) -> Result<ChunkMap, ChunkError> {
115 let mut map = HashMap::new();
116 for chunk in chunks {
117 if let Some(entry) = KNOWN_CHUNKS
118 .iter()
119 .find(|candidate| candidate.id == chunk.id)
120 {
121 let (kind, value) = decode_known(chunk, entry)?;
122 map.insert(kind, value);
123 } else {
124 debug!(
125 chunk_id = format_args!("{:#06x}", chunk.id),
126 len = chunk.data.len(),
127 "storing unknown chunk"
128 );
129 map.insert(
130 ChunkKind::Unknown(chunk.id),
131 ChunkValue::Bytes(chunk.data.clone()),
132 );
133 }
134 }
135 Ok(map)
136}
137
138pub fn parse_chunk_bytes(data: &[u8]) -> Result<ChunkMap, ChunkError> {
140 let raw = gvsp::parse_chunks(data);
141 decode_raw_chunks(&raw)
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn chunk_buffer(id: u16, payload: &[u8]) -> Vec<u8> {
149 let mut buf = Vec::new();
150 buf.extend_from_slice(&id.to_be_bytes());
151 buf.extend_from_slice(&0u16.to_be_bytes());
152 buf.extend_from_slice(&(payload.len() as u32).to_be_bytes());
153 buf.extend_from_slice(payload);
154 buf
155 }
156
157 #[test]
158 fn decode_known_chunks() {
159 let mut data = Vec::new();
160 data.extend_from_slice(&chunk_buffer(
161 0x0001,
162 &0x1234_5678_9ABC_DEF0u64.to_le_bytes(),
163 ));
164 data.extend_from_slice(&chunk_buffer(0x1002, &1234.5f64.to_le_bytes()));
165 let map = parse_chunk_bytes(&data).expect("decode");
166 assert!(matches!(
167 map.get(&ChunkKind::Timestamp),
168 Some(ChunkValue::U64(0x1234_5678_9ABC_DEF0))
169 ));
170 assert!(matches!(
171 map.get(&ChunkKind::ExposureTime),
172 Some(ChunkValue::F64(v)) if (*v - 1234.5).abs() < f64::EPSILON
173 ));
174 }
175
176 #[test]
177 fn invalid_length_errors() {
178 let data = chunk_buffer(0x0001, &[0x12, 0x34]);
179 let err = parse_chunk_bytes(&data).unwrap_err();
180 assert!(matches!(err, ChunkError::InvalidLength { id: 0x0001, .. }));
181 }
182
183 #[test]
184 fn unknown_chunk_kept_as_bytes() {
185 let payload = [0xAA, 0xBB, 0xCC];
186 let data = chunk_buffer(0xDEAD, &payload);
187 let map = parse_chunk_bytes(&data).expect("decode");
188 assert!(matches!(
189 map.get(&ChunkKind::Unknown(0xDEAD)),
190 Some(ChunkValue::Bytes(bytes)) if bytes.as_ref() == payload
191 ));
192 }
193
194 const KNOWN_IDS: &[u16] = &[0x0001, 0x1002, 0x1003, 0x0201];
195
196 #[test]
197 fn random_unknown_chunks_are_stored() {
198 for _ in 0..128 {
199 let mut id = fastrand::u16(..);
200 while KNOWN_IDS.contains(&id) {
201 id = fastrand::u16(..);
202 }
203 let len = fastrand::usize(..16);
204 let mut payload = vec![0u8; len];
205 for byte in &mut payload {
206 *byte = fastrand::u8(..);
207 }
208 let mut buffer = chunk_buffer(id, &payload);
209 let padding_len = fastrand::usize(..8);
210 for _ in 0..padding_len {
211 buffer.push(fastrand::u8(..));
212 }
213 let raw = gvsp::parse_chunks(&buffer);
214 let map = decode_raw_chunks(&raw).expect("decode");
215 match map.get(&ChunkKind::Unknown(id)) {
216 Some(ChunkValue::Bytes(bytes)) => assert_eq!(bytes.as_ref(), payload.as_slice()),
217 Some(other) => panic!("expected raw bytes for unknown chunk, found {other:?}"),
218 None => panic!("unknown chunk missing from map"),
219 }
220 }
221 }
222}