1use std::fmt;
9use std::time::{SystemTime, UNIX_EPOCH};
10use std::vec;
11
12use serde::{Deserialize, Serialize};
13
14use crate::failure::{Doing, Failure};
15use bash_strings::{emit_array, parse_array};
16
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
18pub struct Micros(pub u64);
19
20impl Micros {
21 pub(crate) fn now() -> Result<Self, Failure> {
23 SystemTime::now()
24 .duration_since(UNIX_EPOCH)
25 .map(|since| Self(since.as_micros() as u64))
26 .doing(|| "reading the run's clock".into())
27 }
28
29 fn parse_epoch(text: &str) -> Option<Self> {
31 let (seconds, micros) = text.split_once(['.', ','])?;
32 if micros.len() != 6 {
33 return None;
34 }
35
36 Some(Self(
37 seconds.parse::<u64>().ok()? * 1_000_000 + micros.parse::<u64>().ok()?,
38 ))
39 }
40}
41
42#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
43pub struct Pid(pub u32);
44
45impl fmt::Display for Pid {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(f, "{}", self.0)
48 }
49}
50
51#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize)]
55#[serde(rename_all = "UPPERCASE")]
56pub enum Verb {
57 Say,
58 Ask,
59}
60
61#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
63pub struct Stamp {
64 pub sent_at: Micros,
66
67 pub heard_at: Micros,
69}
70
71#[derive(Debug, Clone, Serialize)]
73pub struct Message {
74 pub verb: Verb,
75 pub stamp: Stamp,
76
77 pub words: Vec<String>,
79}
80
81impl Message {
82 pub fn behind(&self, lead: &str) -> Option<&[String]> {
85 match self.words.split_first() {
86 Some((first, rest)) if first == lead => Some(rest),
87 _ => None,
88 }
89 }
90
91 pub(crate) fn read(line: Line) -> Result<Self, Failure> {
93 let refused = |why: &str| {
94 Failure::new(
95 format!("reading the line {:?}", line.text),
96 why,
97 )
98 };
99 let mut ahead = Ahead::over(&line.text).map_err(|why| refused(&why))?;
100
101 let verb = match ahead.word().map_err(refused)?.as_str() {
102 "SAY" => Verb::Say,
103 "ASK" => Verb::Ask,
104 other => {
105 return Err(refused(&format!(
106 "{other} is not a verb"
107 )));
108 }
109 };
110 let sent_at = ahead.clock().map_err(refused)?;
111
112 Ok(Self {
113 verb,
114 stamp: Stamp {
115 sent_at,
116 heard_at: line.heard_at,
117 },
118 words: ahead.rest(),
119 })
120 }
121}
122
123#[derive(Debug)]
126pub(crate) struct Account {
127 pub stamp: Stamp,
128 pub words: Vec<String>,
129}
130
131impl Account {
132 pub(crate) fn read(text: &str, heard_at: Micros) -> Result<Self, Failure> {
133 let refused = |why: &str| {
134 Failure::new(
135 format!("reading the account {text:?}"),
136 why,
137 )
138 };
139 let mut ahead = Ahead::over(text).map_err(|why| refused(&why))?;
140 let sent_at = ahead.clock().map_err(refused)?;
141
142 Ok(Self {
143 stamp: Stamp { sent_at, heard_at },
144 words: ahead.rest(),
145 })
146 }
147}
148
149#[derive(Debug)]
151pub(crate) struct Line {
152 pub text: String,
153 pub heard_at: Micros,
154}
155
156struct Ahead(vec::IntoIter<String>);
158
159impl Ahead {
160 fn over(text: &str) -> Result<Self, String> {
161 parse_array(text)
162 .map(|words| Self(words.into_iter()))
163 .map_err(|why| why.to_string())
164 }
165
166 fn word(&mut self) -> Result<String, &'static str> {
167 self.0.next().ok_or("the line ended early")
168 }
169
170 fn clock(&mut self) -> Result<Micros, &'static str> {
172 match self.word()?.split_once('=') {
173 Some(("at", value)) => Micros::parse_epoch(value).ok_or("bad at"),
174 _ => Err("no at= header"),
175 }
176 }
177
178 fn rest(self) -> Vec<String> {
179 self.0.collect()
180 }
181}
182
183pub fn field<'a>(words: &'a [String], key: &str) -> Option<&'a str> {
187 words
188 .chunks_exact(2)
189 .find(|pair| pair[0] == key)
190 .map(|pair| pair[1].as_str())
191}
192
193#[derive(Debug)]
196pub struct Answer(Vec<String>);
197
198impl Answer {
199 pub fn of(command: impl Into<String>, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
202 let mut words = vec![command.into()];
203 words.extend(args.into_iter().map(Into::into));
204
205 Self(words)
206 }
207
208 pub fn status(code: u8) -> Self {
210 Self::of("__bc_status", [code.to_string()])
211 }
212
213 pub fn unknown() -> Self {
215 Self::status(127)
216 }
217
218 pub fn returning(code: u8) -> Self {
222 Self::of("return", [code.to_string()])
223 }
224}
225
226impl fmt::Display for Answer {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 write!(f, "{}", emit_array(&self.0))
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn words(items: &[&str]) -> Vec<String> {
239 items.iter().map(|item| item.to_string()).collect()
240 }
241
242 fn line(kind: &str, payload: &[&str]) -> Line {
243 let mut all = words(&[kind, "at=1.000002"]);
244 all.extend(words(payload));
245
246 Line {
247 text: emit_array(&all),
248 heard_at: Micros(50),
249 }
250 }
251
252 fn spoke(payload: &[&str]) -> Message {
253 Message::read(line("SAY", payload)).expect("a message")
254 }
255
256 #[test]
257 fn the_protocols_words_come_off_and_the_clients_remain() {
258 let message = spoke(&["REC", "a space", ""]);
259
260 assert_eq!(message.verb, Verb::Say);
261 assert_eq!(message.stamp.sent_at, Micros(1_000_002));
262 assert_eq!(
263 message.stamp.heard_at,
264 Micros(50),
265 "the run's clock, from the read"
266 );
267 assert_eq!(
268 message.words,
269 words(&["REC", "a space", ""]),
270 "the payload alone"
271 );
272 assert_eq!(
273 message.behind("REC"),
274 Some(words(&["a space", ""]).as_slice())
275 );
276 }
277
278 #[test]
279 fn a_message_may_carry_nothing_of_its_own() {
280 let message = spoke(&[]);
281
282 assert!(message.words.is_empty());
283 assert_eq!(message.behind("REC"), None);
284 }
285
286 #[test]
288 fn an_account_is_the_clock_and_the_pairs() {
289 let text = emit_array(&words(&[
290 "at=1.000002",
291 "zero",
292 "x.bash",
293 "flags",
294 "hB",
295 ]));
296 let account = Account::read(&text, Micros(50)).unwrap();
297
298 assert_eq!(
299 account.stamp,
300 Stamp {
301 sent_at: Micros(1_000_002),
302 heard_at: Micros(50)
303 }
304 );
305 assert_eq!(
306 field(&account.words, "zero"),
307 Some("x.bash")
308 );
309
310 let verbed = emit_array(&words(&["JOIN", "at=1.000002"]));
311 assert!(
312 Account::read(&verbed, Micros(0)).is_err(),
313 "a verb where the clock goes"
314 );
315 }
316
317 #[test]
318 fn a_header_the_protocol_did_not_write_is_an_error() {
319 let bad = [
320 emit_array(&words(&["MUMBLE", "at=1.000002"])),
321 emit_array(&words(&["JOIN", "at=1.000002"])),
322 emit_array(&words(&["SAY", "when=1.000002"])),
323 emit_array(&words(&["SAY", "at=1.0"])),
324 emit_array(&words(&["SAY"])),
325 "(unquoted".to_string(),
326 ];
327 for text in bad {
328 let refused = Message::read(Line {
329 text: text.clone(),
330 heard_at: Micros(0),
331 });
332 assert!(
333 refused.is_err(),
334 "{text} should not read"
335 );
336 }
337 }
338
339 #[test]
343 fn an_answer_is_one_line_whatever_it_carries() {
344 let carried = ["%s", "two\nlines", "a\ttab", "\u{ff}", "it's", ""];
345 let message = Answer::of("printf", carried).to_string();
346
347 assert!(
348 !message.contains('\n'),
349 "a raw newline would truncate the read: {message}"
350 );
351
352 let mut expected = vec!["printf".to_string()];
353 expected.extend(carried.iter().map(|word| word.to_string()));
354 assert_eq!(
355 parse_array(&message).unwrap(),
356 expected,
357 "and it still reads back"
358 );
359 }
360
361 #[test]
363 fn messages_round_trip() {
364 let nested = emit_array(&words(&["INNER", "x y"]));
365 let message = spoke(&["TAG", "quote'inside", "two\nlines", &nested]);
366
367 assert_eq!(
368 message.words,
369 words(&["TAG", "quote'inside", "two\nlines", &nested])
370 );
371 assert_eq!(
372 parse_array(&nested).unwrap(),
373 words(&["INNER", "x y"])
374 );
375 }
376}