1use std::io;
16
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use thiserror::Error;
20use tokio::io::{AsyncRead, AsyncReadExt};
21
22use crate::frame;
23
24pub const STATE_V2_MAX_FRAME: usize = 1024 * 1024;
27
28pub const STATE_WIRE_VERSION: u16 = 4;
48
49#[derive(Debug, Error)]
50pub enum StateError {
51 #[error("json error: {0}")]
52 Json(#[from] serde_json::Error),
53 #[error("msgpack encode error: {0}")]
54 Encode(#[from] rmp_serde::encode::Error),
55 #[error("msgpack decode error: {0}")]
56 Decode(#[from] rmp_serde::decode::Error),
57 #[error("framing error: {0}")]
58 Frame(#[from] frame::FrameError),
59 #[error("state wire version mismatch: got {got}, expected {ours}")]
60 Version { got: u16, ours: u16 },
61}
62
63pub fn encode_v1(state: &Value) -> Result<Vec<u8>, StateError> {
66 let mut buf = serde_json::to_vec(state)?;
67 buf.push(b'\n');
68 Ok(buf)
69}
70
71pub fn decode_v1_line(line: &[u8]) -> Result<Value, StateError> {
74 let trimmed = line.strip_suffix(b"\n").unwrap_or(line);
75 Ok(serde_json::from_slice(trimmed)?)
76}
77
78#[derive(Debug, Serialize, Deserialize)]
85struct StateFrameV2 {
86 #[serde(rename = "v")]
87 v: u16,
88 #[serde(rename = "s")]
89 s: Value,
90}
91
92#[derive(Serialize)]
95struct StateFrameV2Ref<'a> {
96 #[serde(rename = "v")]
97 v: u16,
98 #[serde(rename = "s")]
99 s: &'a Value,
100}
101
102pub fn encode_v2(state: &Value) -> Result<Vec<u8>, StateError> {
105 let body = rmp_serde::to_vec_named(&StateFrameV2Ref {
106 v: STATE_WIRE_VERSION,
107 s: state,
108 })?;
109 Ok(frame::encode_frame(&body, STATE_V2_MAX_FRAME)?)
110}
111
112pub fn decode_v2(body: &[u8]) -> Result<Value, StateError> {
117 let frame: StateFrameV2 = rmp_serde::from_slice(body)?;
118 if frame.v != STATE_WIRE_VERSION {
119 return Err(StateError::Version {
120 got: frame.v,
121 ours: STATE_WIRE_VERSION,
122 });
123 }
124 Ok(frame.s)
125}
126
127enum StateFrame {
131 Value(Value),
132 Skip,
133 VersionMismatch { got: u16 },
134}
135
136pub const MAX_CONSECUTIVE_VERSION_MISMATCHES: u32 = 8;
142
143static VERSION_MISMATCH_SKIPS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
145
146pub fn version_mismatch_skips() -> u64 {
149 VERSION_MISMATCH_SKIPS.load(std::sync::atomic::Ordering::Relaxed)
150}
151
152struct MismatchRun(u32);
155
156impl MismatchRun {
157 fn record(&mut self, got: u16) -> io::Result<()> {
158 VERSION_MISMATCH_SKIPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
159 self.0 += 1;
160 if self.0 < MAX_CONSECUTIVE_VERSION_MISMATCHES {
161 return Ok(());
162 }
163 tracing::warn!(
166 got,
167 ours = STATE_WIRE_VERSION,
168 skipped = self.0,
169 "state format mismatch: the producer speaks a different state wire version"
170 );
171 Err(io::Error::new(
172 io::ErrorKind::InvalidData,
173 format!(
174 "state format mismatch: producer sends state wire version {got}, \
175 this reader expects {STATE_WIRE_VERSION}"
176 ),
177 ))
178 }
179}
180
181fn v2_body_len(header: [u8; frame::HEADER_SIZE]) -> io::Result<usize> {
185 let len = u32::from_be_bytes(header) as usize;
186 if len == 0 || len > STATE_V2_MAX_FRAME {
187 return Err(io::Error::new(
188 io::ErrorKind::InvalidData,
189 "state v2 frame length out of range",
190 ));
191 }
192 Ok(len)
193}
194
195fn decode_state_body(is_v2: bool, body: &[u8]) -> StateFrame {
201 if is_v2 {
202 match decode_v2(body) {
203 Ok(v) => StateFrame::Value(v),
204 Err(StateError::Version { got, .. }) => StateFrame::VersionMismatch { got },
205 Err(_) => StateFrame::Skip,
206 }
207 } else {
208 decode_v1_line(body).map_or(StateFrame::Skip, StateFrame::Value)
209 }
210}
211
212pub async fn read_state_value<R>(reader: &mut R) -> io::Result<Option<Value>>
237where
238 R: AsyncRead + Unpin,
239{
240 read_state_value_into(reader, &mut Vec::new()).await
241}
242
243pub async fn read_state_value_into<R>(
247 reader: &mut R,
248 buf: &mut Vec<u8>,
249) -> io::Result<Option<Value>>
250where
251 R: AsyncRead + Unpin,
252{
253 let mut mismatches = MismatchRun(0);
254 loop {
255 match read_state_frame_into(reader, buf).await? {
256 Some(StateFrame::Value(v)) => return Ok(Some(v)),
257 Some(StateFrame::Skip) => continue,
258 Some(StateFrame::VersionMismatch { got }) => mismatches.record(got)?,
259 None => return Ok(None),
260 }
261 }
262}
263
264async fn read_state_frame_into<R>(
267 reader: &mut R,
268 buf: &mut Vec<u8>,
269) -> io::Result<Option<StateFrame>>
270where
271 R: AsyncRead + Unpin,
272{
273 let mut first = [0u8; 1];
274 match reader.read_exact(&mut first).await {
275 Ok(_) => {}
276 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
277 Err(e) => return Err(e),
278 }
279
280 buf.clear();
281 if first[0] == 0x00 {
282 let mut rest = [0u8; frame::HEADER_SIZE - 1];
283 reader.read_exact(&mut rest).await?;
284 let len = v2_body_len([first[0], rest[0], rest[1], rest[2]])?;
285 buf.resize(len, 0);
286 reader.read_exact(buf.as_mut_slice()).await?;
287 Ok(Some(decode_state_body(true, buf)))
288 } else {
289 buf.push(first[0]);
290 let mut byte = [0u8; 1];
291 loop {
292 match reader.read_exact(&mut byte).await {
293 Ok(_) => {}
294 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
297 Err(e) => return Err(e),
298 }
299 if byte[0] == b'\n' {
300 break;
301 }
302 if buf.len() >= STATE_V2_MAX_FRAME {
303 return Err(io::Error::new(
304 io::ErrorKind::InvalidData,
305 "state v1 line exceeded the frame cap without a newline",
306 ));
307 }
308 buf.push(byte[0]);
309 }
310 Ok(Some(decode_state_body(false, buf)))
311 }
312}
313
314pub fn read_state_value_blocking<R>(reader: &mut R) -> io::Result<Option<Value>>
319where
320 R: io::Read,
321{
322 let mut mismatches = MismatchRun(0);
323 loop {
324 match read_state_frame_blocking(reader)? {
325 Some(StateFrame::Value(v)) => return Ok(Some(v)),
326 Some(StateFrame::Skip) => continue,
327 Some(StateFrame::VersionMismatch { got }) => mismatches.record(got)?,
328 None => return Ok(None),
329 }
330 }
331}
332
333fn read_state_frame_blocking<R>(reader: &mut R) -> io::Result<Option<StateFrame>>
334where
335 R: io::Read,
336{
337 let mut first = [0u8; 1];
338 match reader.read_exact(&mut first) {
339 Ok(_) => {}
340 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
341 Err(e) => return Err(e),
342 }
343
344 if first[0] == 0x00 {
345 let mut rest = [0u8; frame::HEADER_SIZE - 1];
346 reader.read_exact(&mut rest)?;
347 let len = v2_body_len([first[0], rest[0], rest[1], rest[2]])?;
348 let mut body = vec![0u8; len];
349 reader.read_exact(&mut body)?;
350 Ok(Some(decode_state_body(true, &body)))
351 } else {
352 let mut line = vec![first[0]];
353 let mut byte = [0u8; 1];
354 loop {
355 match reader.read_exact(&mut byte) {
356 Ok(_) => {}
357 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
358 Err(e) => return Err(e),
359 }
360 if byte[0] == b'\n' {
361 break;
362 }
363 if line.len() >= STATE_V2_MAX_FRAME {
364 return Err(io::Error::new(
365 io::ErrorKind::InvalidData,
366 "state v1 line exceeded the frame cap without a newline",
367 ));
368 }
369 line.push(byte[0]);
370 }
371 Ok(Some(decode_state_body(false, &line)))
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use serde_json::json;
379
380 fn sample() -> Value {
381 json!({
382 "armed": false,
383 "mode": "STABILIZE",
384 "battery": {"voltage": 16.4, "remaining": 87},
385 "gps": {"fix": 3, "sats": 14, "lat": 12.9716, "lon": 77.5946},
386 "attitude": {"roll": 0.01, "pitch": -0.02, "yaw": 1.57},
387 "link": {"rssi_dbm": -48, "valid_rx_packets_per_s": 630}
388 })
389 }
390
391 #[test]
392 fn v1_round_trip_with_newline() {
393 let state = sample();
394 let wire = encode_v1(&state).unwrap();
395 assert_eq!(*wire.last().unwrap(), b'\n');
396 let back = decode_v1_line(&wire).unwrap();
397 assert_eq!(state, back);
398 }
399
400 #[test]
401 fn v1_decode_tolerates_missing_newline() {
402 let state = sample();
403 let json = serde_json::to_vec(&state).unwrap();
404 assert_eq!(decode_v1_line(&json).unwrap(), state);
405 }
406
407 #[test]
408 fn v1_decode_rejects_malformed_json() {
409 assert!(decode_v1_line(b"{not json}\n").is_err());
410 }
411
412 #[test]
413 fn v2_round_trip() {
414 let state = sample();
415 let frame_bytes = encode_v2(&state).unwrap();
416 let body = &frame_bytes[frame::HEADER_SIZE..];
418 let raw: StateFrameV2 = rmp_serde::from_slice(body).unwrap();
421 assert_eq!(raw.v, STATE_WIRE_VERSION);
422 assert_eq!(raw.s, state);
423 let back = decode_v2(body).unwrap();
424 assert_eq!(state, back);
425 }
426
427 #[test]
428 fn version_matches_registry() {
429 assert_eq!(
432 STATE_WIRE_VERSION,
433 crate::contracts::contract_version("state.v2").unwrap()
434 );
435 }
436
437 #[test]
438 fn decode_v2_rejects_a_future_version() {
439 let body = rmp_serde::to_vec_named(&StateFrameV2Ref {
442 v: STATE_WIRE_VERSION + 1,
443 s: &sample(),
444 })
445 .unwrap();
446 match decode_v2(&body) {
447 Err(StateError::Version { got, ours }) => {
448 assert_eq!(got, STATE_WIRE_VERSION + 1);
449 assert_eq!(ours, STATE_WIRE_VERSION);
450 }
451 other => panic!("expected a Version error, got {other:?}"),
452 }
453 }
454
455 #[test]
456 fn v1_and_v2_preserve_the_same_fields() {
457 let state = sample();
458 let via_v1 = decode_v1_line(&encode_v1(&state).unwrap()).unwrap();
459 let frame_bytes = encode_v2(&state).unwrap();
460 let via_v2 = decode_v2(&frame_bytes[frame::HEADER_SIZE..]).unwrap();
461 assert_eq!(via_v1, via_v2);
462 }
463
464 #[test]
465 fn leading_byte_discriminates_the_two_wire_forms() {
466 assert_eq!(encode_v2(&sample()).unwrap()[0], 0x00);
470 assert_eq!(encode_v1(&sample()).unwrap()[0], b'{');
471 }
472
473 #[tokio::test]
474 async fn read_state_value_decodes_v1_and_v2_identically() {
475 let state = sample();
476 let mut v1 = std::io::Cursor::new(encode_v1(&state).unwrap());
477 let mut v2 = std::io::Cursor::new(encode_v2(&state).unwrap());
478 let from_v1 = read_state_value(&mut v1).await.unwrap().unwrap();
479 let from_v2 = read_state_value(&mut v2).await.unwrap().unwrap();
480 assert_eq!(from_v1, state);
481 assert_eq!(from_v2, state);
482 }
483
484 #[tokio::test]
485 async fn read_state_value_reads_a_mixed_v1_v2_stream_in_order() {
486 let a = json!({"mode": "STABILIZE"});
489 let b = json!({"mode": "GUIDED"});
490 let mut wire = encode_v1(&a).unwrap();
491 wire.extend(encode_v2(&b).unwrap());
492 let mut r = std::io::Cursor::new(wire);
493 assert_eq!(read_state_value(&mut r).await.unwrap().unwrap(), a);
494 assert_eq!(read_state_value(&mut r).await.unwrap().unwrap(), b);
495 assert!(read_state_value(&mut r).await.unwrap().is_none());
496 }
497
498 #[tokio::test]
499 async fn read_state_value_into_reuses_one_buffer_across_frames() {
500 let a = json!({"mode": "STABILIZE", "pad": "x".repeat(512)});
501 let b = json!({"mode": "GUIDED"});
502 let mut wire = encode_v2(&a).unwrap();
503 wire.extend(encode_v1(&b).unwrap());
504 wire.extend(encode_v2(&b).unwrap());
505 let mut r = std::io::Cursor::new(wire);
506 let mut buf = Vec::new();
507 assert_eq!(
508 read_state_value_into(&mut r, &mut buf)
509 .await
510 .unwrap()
511 .unwrap(),
512 a
513 );
514 let grown = buf.capacity();
515 assert!(grown >= 512);
516 assert_eq!(
517 read_state_value_into(&mut r, &mut buf)
518 .await
519 .unwrap()
520 .unwrap(),
521 b
522 );
523 assert_eq!(
524 read_state_value_into(&mut r, &mut buf)
525 .await
526 .unwrap()
527 .unwrap(),
528 b
529 );
530 assert_eq!(
531 buf.capacity(),
532 grown,
533 "the smaller frames reused the allocation"
534 );
535 assert!(read_state_value_into(&mut r, &mut buf)
536 .await
537 .unwrap()
538 .is_none());
539 }
540
541 #[tokio::test]
542 async fn read_state_value_skips_a_malformed_frame_then_reads_the_next() {
543 let good = sample();
544 let mut wire = b"{ not json }\n".to_vec();
545 wire.extend(encode_v2(&good).unwrap());
546 let mut r = std::io::Cursor::new(wire);
547 assert_eq!(read_state_value(&mut r).await.unwrap().unwrap(), good);
549 }
550
551 #[tokio::test]
552 async fn read_state_value_returns_none_on_clean_eof() {
553 let mut r = std::io::Cursor::new(Vec::new());
554 assert!(read_state_value(&mut r).await.unwrap().is_none());
555 }
556
557 #[test]
558 fn read_state_value_blocking_decodes_both_wire_forms() {
559 let state = sample();
560 let mut v1 = std::io::Cursor::new(encode_v1(&state).unwrap());
561 let mut v2 = std::io::Cursor::new(encode_v2(&state).unwrap());
562 assert_eq!(read_state_value_blocking(&mut v1).unwrap().unwrap(), state);
563 assert_eq!(read_state_value_blocking(&mut v2).unwrap().unwrap(), state);
564 }
565
566 fn foreign_version_frame() -> Vec<u8> {
567 let body = rmp_serde::to_vec_named(&StateFrameV2Ref {
568 v: STATE_WIRE_VERSION + 1,
569 s: &sample(),
570 })
571 .unwrap();
572 frame::encode_frame(&body, STATE_V2_MAX_FRAME).unwrap()
573 }
574
575 #[tokio::test]
576 async fn a_stream_of_foreign_versions_is_an_error_not_a_silent_stall() {
577 let mut wire = Vec::new();
582 for _ in 0..MAX_CONSECUTIVE_VERSION_MISMATCHES {
583 wire.extend(foreign_version_frame());
584 }
585 wire.extend(encode_v2(&sample()).unwrap());
586 let before = version_mismatch_skips();
587 let mut r = std::io::Cursor::new(wire.clone());
588 let err = read_state_value(&mut r).await.unwrap_err();
589 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
590 assert!(err.to_string().contains("state format mismatch"), "{err}");
591 assert!(version_mismatch_skips() >= before + MAX_CONSECUTIVE_VERSION_MISMATCHES as u64);
592
593 let mut r = std::io::Cursor::new(wire);
594 assert!(read_state_value_blocking(&mut r).is_err());
595 }
596
597 #[tokio::test]
598 async fn a_single_foreign_version_frame_is_skipped() {
599 let mut wire = foreign_version_frame();
600 wire.extend(encode_v2(&sample()).unwrap());
601 let mut r = std::io::Cursor::new(wire);
602 assert_eq!(read_state_value(&mut r).await.unwrap().unwrap(), sample());
603 }
604}