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