1use crate::protocol::{ClientInfo, InputConfig, OutputConfig};
23
24const WIRE_VARINT: u8 = 0;
26const WIRE_LEN: u8 = 2;
27
28fn tag(out: &mut Vec<u8>, field: u32, wire: u8) {
29 put_varint(out, u64::from(field) << 3 | u64::from(wire));
30}
31
32fn put_varint(out: &mut Vec<u8>, mut v: u64) {
33 loop {
34 let byte = (v & 0x7f) as u8;
35 v >>= 7;
36 if v == 0 {
37 out.push(byte);
38 return;
39 }
40 out.push(byte | 0x80);
41 }
42}
43
44fn put_string(out: &mut Vec<u8>, field: u32, v: &str) {
45 tag(out, field, WIRE_LEN);
46 put_varint(out, v.len() as u64);
47 out.extend_from_slice(v.as_bytes());
48}
49
50fn put_message(out: &mut Vec<u8>, field: u32, body: &[u8]) {
51 tag(out, field, WIRE_LEN);
52 put_varint(out, body.len() as u64);
53 out.extend_from_slice(body);
54}
55
56fn put_u32(out: &mut Vec<u8>, field: u32, v: u32) {
57 tag(out, field, WIRE_VARINT);
58 put_varint(out, u64::from(v));
59}
60
61fn encode_client_info(ci: &ClientInfo) -> Vec<u8> {
62 let mut out = Vec::new();
63 if let Some(v) = &ci.language {
64 put_string(&mut out, 1, v);
65 }
66 if let Some(v) = &ci.version {
67 put_string(&mut out, 2, v);
68 }
69 if let Some(v) = &ci.language_version {
70 put_string(&mut out, 3, v);
71 }
72 if let Some(v) = &ci.os {
73 put_string(&mut out, 4, v);
74 }
75 if let Some(v) = &ci.os_version {
76 put_string(&mut out, 5, v);
77 }
78 out
79}
80
81pub fn encode_input_config(cfg: &InputConfig) -> Vec<u8> {
83 let mut out = Vec::new();
84 if let Some(v) = &cfg.storage_directory {
85 put_string(&mut out, 1, v);
86 }
87 if let Some(v) = cfg.port {
88 put_u32(&mut out, 2, v);
89 }
90 if let Some(v) = &cfg.bind_address {
91 put_string(&mut out, 3, v);
92 }
93 if let Some(ci) = &cfg.client_info {
94 put_message(&mut out, 4, &encode_client_info(ci));
95 }
96 let mut env: Vec<_> = cfg.env.iter().collect();
99 env.sort_by(|a, b| a.0.cmp(b.0));
100 for (k, v) in env {
101 let mut entry = Vec::new();
102 put_string(&mut entry, 1, k);
103 put_string(&mut entry, 2, v);
104 put_message(&mut out, 5, &entry);
105 }
106 out
107}
108
109pub fn frame(body: &[u8]) -> Vec<u8> {
111 let mut out = Vec::with_capacity(body.len() + 4);
112 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
113 out.extend_from_slice(body);
114 out
115}
116
117#[derive(Debug, thiserror::Error)]
119pub enum DecodeError {
120 #[error("truncated protobuf message at byte {0}")]
122 Truncated(usize),
123 #[error("malformed varint at byte {0}")]
125 BadVarint(usize),
126 #[error("unsupported wire type {wire} for field {field}")]
128 UnsupportedWireType {
129 field: u32,
131 wire: u8,
133 },
134 #[error("field {0} is not valid UTF-8")]
136 NotUtf8(u32),
137}
138
139struct Reader<'a> {
140 buf: &'a [u8],
141 pos: usize,
142}
143
144impl<'a> Reader<'a> {
145 fn varint(&mut self) -> Result<u64, DecodeError> {
146 let mut value = 0u64;
147 let mut shift = 0;
148 loop {
149 let byte = *self
150 .buf
151 .get(self.pos)
152 .ok_or(DecodeError::Truncated(self.pos))?;
153 self.pos += 1;
154 value |= u64::from(byte & 0x7f) << shift;
155 if byte & 0x80 == 0 {
156 return Ok(value);
157 }
158 shift += 7;
159 if shift >= 64 {
160 return Err(DecodeError::BadVarint(self.pos));
161 }
162 }
163 }
164
165 fn bytes(&mut self) -> Result<&'a [u8], DecodeError> {
166 let len = self.varint()? as usize;
167 let end = self
168 .pos
169 .checked_add(len)
170 .ok_or(DecodeError::Truncated(self.pos))?;
171 let slice = self
172 .buf
173 .get(self.pos..end)
174 .ok_or(DecodeError::Truncated(self.pos))?;
175 self.pos = end;
176 Ok(slice)
177 }
178
179 fn skip(&mut self, field: u32, wire: u8) -> Result<(), DecodeError> {
181 match wire {
182 WIRE_VARINT => {
183 self.varint()?;
184 }
185 WIRE_LEN => {
186 self.bytes()?;
187 }
188 1 => self.pos += 8,
189 5 => self.pos += 4,
190 _ => return Err(DecodeError::UnsupportedWireType { field, wire }),
191 }
192 Ok(())
193 }
194}
195
196pub fn decode_output_config(buf: &[u8]) -> Result<OutputConfig, DecodeError> {
201 let mut r = Reader { buf, pos: 0 };
202 let mut cfg = OutputConfig::default();
203 while r.pos < buf.len() {
204 let key = r.varint()?;
205 let field = (key >> 3) as u32;
206 let wire = (key & 7) as u8;
207 match (field, wire) {
208 (1, WIRE_VARINT) => cfg.port = Some(r.varint()? as i32),
209 (2, WIRE_LEN) => {
210 let raw = r.bytes()?;
211 cfg.api_key = Some(
212 std::str::from_utf8(raw)
213 .map_err(|_| DecodeError::NotUtf8(2))?
214 .to_string(),
215 );
216 }
217 _ => r.skip(field, wire)?,
218 }
219 }
220 Ok(cfg)
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 const GOLDEN_INPUT: &str = "0a0c2f746d702f616773746f7265221e0a0472757374120\
2296302e312e31301a04312e383522056c696e75782a0136";
230 const GOLDEN_OUTPUT: &str = "08e5bc021220373130643035656337373862323462363338\
2313663636632396530316536613031";
232
233 fn unhex(s: &str) -> Vec<u8> {
234 (0..s.len())
235 .step_by(2)
236 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
237 .collect()
238 }
239
240 #[test]
241 fn encodes_byte_for_byte_like_the_reference_client() {
242 let cfg = InputConfig {
243 storage_directory: Some("/tmp/agstore".into()),
244 client_info: Some(ClientInfo {
245 language: Some("rust".into()),
246 version: Some("0.1.10".into()),
247 language_version: Some("1.85".into()),
248 os: Some("linux".into()),
249 os_version: Some("6".into()),
250 }),
251 ..Default::default()
252 };
253 assert_eq!(encode_input_config(&cfg), unhex(GOLDEN_INPUT));
254 }
255
256 #[test]
257 fn decodes_a_captured_output_config() {
258 let cfg = decode_output_config(&unhex(GOLDEN_OUTPUT)).unwrap();
259 assert_eq!(cfg.port, Some(40549));
260 assert_eq!(
261 cfg.api_key.as_deref(),
262 Some("710d05ec778b24b6386ccf29e01e6a01")
263 );
264 }
265
266 #[test]
267 fn skips_fields_a_newer_harness_might_add() {
268 let mut buf = unhex(GOLDEN_OUTPUT);
269 put_string(&mut buf, 9, "something-new");
270 put_u32(&mut buf, 10, 42);
271 let cfg = decode_output_config(&buf).unwrap();
272 assert_eq!(cfg.port, Some(40549));
273 }
274
275 #[test]
276 fn rejects_a_truncated_message() {
277 let buf = unhex(GOLDEN_OUTPUT);
278 assert!(decode_output_config(&buf[..buf.len() - 4]).is_err());
279 }
280
281 #[test]
282 fn frames_with_a_little_endian_length() {
283 assert_eq!(frame(&[1, 2, 3]), vec![3, 0, 0, 0, 1, 2, 3]);
284 }
285
286 #[test]
287 fn encodes_env_entries_deterministically() {
288 let cfg = InputConfig {
289 env: [
290 ("B".to_string(), "2".to_string()),
291 ("A".to_string(), "1".to_string()),
292 ]
293 .into_iter()
294 .collect(),
295 ..Default::default()
296 };
297 let once = encode_input_config(&cfg);
298 assert_eq!(once, encode_input_config(&cfg));
299 assert_eq!(&once[..2], &[0x2a, 0x06]);
301 }
302}