-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprotobuf_cobs.rs
More file actions
116 lines (109 loc) · 4.56 KB
/
Copy pathprotobuf_cobs.rs
File metadata and controls
116 lines (109 loc) · 4.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Framing Protocol Buffers over a serial link with COBS.
//!
//! Protobuf is a *serialization* format, not a *wire* format: it turns a message
//! into a compact byte string but says nothing about where one message ends and
//! the next begins. Push raw protobuf bytes down a UART and the receiver sees an
//! undifferentiated stream. COBS supplies the missing framing:
//!
//! 1. **Serialize** the message with protobuf (the bytes usually contain `0x00`).
//! 2. **COBS-encode** it so the payload contains no `0x00`, then append a single
//! `0x00` **delimiter**. That byte now unambiguously marks the end of a frame.
//! 3. On the far end, split the stream on `0x00`, COBS-decode each frame, and hand
//! the bytes back to protobuf.
//!
//! The payoff over length-prefixing: if line noise corrupts a packet, the receiver
//! just waits for the next `0x00` and **resynchronises on the very next message**,
//! discarding only the broken frame instead of losing sync forever. This example
//! shows exactly that.
//!
//! Run it with `cargo run --example protobuf_cobs`.
//!
//! On a microcontroller you would use the allocation-free API — [`framing::frame`]
//! to encode into a fixed `[u8; N]`, and [`framing::StreamDecoder`] to decode an
//! incoming stream into another fixed buffer — for the same result with zero heap.
//!
//! [`framing::frame`]: cobs_codec_rs::framing::frame
//! [`framing::StreamDecoder`]: cobs_codec_rs::framing::StreamDecoder
#![allow(missing_docs)]
use cobs_codec_rs::framing::{FrameDecoder, frame_to_vec};
use prost::Message;
/// A telemetry packet a sensor node might stream to a host computer.
#[derive(Clone, PartialEq, prost::Message)]
struct SensorReading {
#[prost(uint32, tag = "1")]
node_id: u32,
#[prost(float, tag = "2")]
temperature_c: f32,
#[prost(uint64, tag = "3")]
uptime_ms: u64,
#[prost(string, tag = "4")]
label: String,
}
fn main() {
let readings = [
SensorReading {
node_id: 1,
temperature_c: 21.5,
uptime_ms: 1_000,
label: "cabin".into(),
},
SensorReading {
node_id: 2,
temperature_c: 100.0,
uptime_ms: 1_256,
label: "boiler".into(),
},
SensorReading {
node_id: 7,
temperature_c: -4.25,
uptime_ms: 99_999,
label: "outside".into(),
},
];
// --- Device side: serialize each reading with protobuf, then COBS-frame it. ---
let mut wire: Vec<u8> = Vec::new();
let mut frame_starts: Vec<usize> = Vec::new();
for reading in &readings {
frame_starts.push(wire.len());
let payload = reading.encode_to_vec(); // protobuf bytes (the float alone brings 0x00s)
wire.extend_from_slice(&frame_to_vec(&payload)); // COBS-encode + trailing 0x00
}
// COBS guarantees a zero-free payload, so every 0x00 on the wire is a frame
// delimiter — provably one per frame.
println!(
"device: {} readings -> {} wire bytes; the only zeros are the {} frame delimiters",
readings.len(),
wire.len(),
readings.len(),
);
// --- Inject line noise: corrupt reading #2's leading COBS code byte. ---
// A bogus code byte makes the frame claim more data than it holds, so it fails
// to decode — a stand-in for any bit-flip a noisy UART might introduce.
wire[frame_starts[1]] = 0xFF;
println!("noise: flipped a byte inside reading #2's frame\n");
// --- Host side: reassemble across misaligned UART reads and protobuf-decode. ---
let mut rx = FrameDecoder::new().max_frame_len(256);
let mut received = 0usize;
// A UART hands you whatever bytes have arrived, never aligned to frames.
for chunk in wire.chunks(5) {
rx.push(chunk, |frame| match frame {
Ok(bytes) => match SensorReading::decode(&*bytes) {
Ok(msg) => {
received += 1;
println!(
"host <- node {:>2}: {:+6.2} C, up {:>6} ms, \"{}\"",
msg.node_id, msg.temperature_c, msg.uptime_ms, msg.label,
);
}
Err(err) => println!("host <- valid COBS frame but bad protobuf: {err}"),
},
Err(err) => println!("host <- dropped a corrupt frame ({err}); resyncing on next 0x00"),
});
}
println!(
"\nreceived {received}/{} readings: the corrupted frame was skipped and the \
stream resynced on the next delimiter.",
readings.len(),
);
assert_eq!(received, readings.len() - 1);
}