rx/
execution.rs

1use crate::event::TimedEvent;
2
3use std::collections::VecDeque;
4use std::fmt;
5use std::fs::File;
6use std::io;
7use std::io::{BufRead, Write};
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use std::time;
11
12use digest::generic_array::{sequence::*, typenum::consts::*, GenericArray};
13use digest::Digest;
14use meowhash::MeowHasher;
15use rgx::core::{Bgra8, Rgba8};
16
17#[derive(Debug, Clone, Eq, PartialEq)]
18pub enum GifMode {
19    Ignore,
20    Record,
21}
22
23/// Determines whether frame digests are recorded, verified or ignored.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DigestMode {
26    /// Verify digests.
27    Verify,
28    /// Record digests.
29    Record,
30    /// Ignore digest.
31    Ignore,
32}
33
34pub struct DigestState {
35    pub mode: DigestMode,
36    pub path: Option<PathBuf>,
37}
38
39impl DigestState {
40    pub fn from<P: AsRef<Path>>(mode: DigestMode, path: P) -> io::Result<Self> {
41        match mode {
42            DigestMode::Verify => Self::verify(path),
43            DigestMode::Record => Self::record(path),
44            DigestMode::Ignore => Self::ignore(),
45        }
46    }
47
48    pub fn verify<P: AsRef<Path>>(path: P) -> io::Result<Self> {
49        let mut frames = Vec::new();
50        let path = path.as_ref();
51
52        match File::open(&path) {
53            Ok(f) => {
54                let r = io::BufReader::new(f);
55                for line in r.lines() {
56                    let line = line?;
57                    let hash = Hash::from_str(line.as_str())
58                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
59                    frames.push(hash);
60                }
61            }
62            Err(e) => {
63                return Err(io::Error::new(
64                    e.kind(),
65                    format!("{}: {}", path.display(), e),
66                ));
67            }
68        }
69
70        Ok(Self {
71            mode: DigestMode::Verify,
72            path: Some(path.into()),
73        })
74    }
75
76    pub fn record<P: AsRef<Path>>(path: P) -> io::Result<Self> {
77        Ok(Self {
78            mode: DigestMode::Record,
79            path: Some(path.as_ref().into()),
80        })
81    }
82
83    pub fn ignore() -> io::Result<Self> {
84        Ok(Self {
85            mode: DigestMode::Ignore,
86            path: None,
87        })
88    }
89}
90
91#[derive(Debug, Clone)]
92pub enum ExecutionMode {
93    Normal,
94    Record(PathBuf, DigestMode, GifMode),
95    Replay(PathBuf, DigestMode),
96}
97
98/// Execution mode. Controls whether the session is playing or recording
99/// commands.
100// TODO: Make this a `struct` and have `ExecutionMode`.
101pub enum Execution {
102    /// Normal execution. User inputs are processed normally.
103    Normal,
104    /// Recording user inputs to log.
105    Recording {
106        /// Events being recorded.
107        events: Vec<TimedEvent>,
108        /// Start time of recording.
109        start: time::Instant,
110        /// Path to save recording to.
111        path: PathBuf,
112        /// Digest mode.
113        digest: DigestState,
114        /// Frame recorder.
115        recorder: FrameRecorder,
116    },
117    /// Replaying inputs from log.
118    Replaying {
119        /// Events being replayed.
120        events: VecDeque<TimedEvent>,
121        /// Start time of the playback.
122        start: time::Instant,
123        /// Path to read events from.
124        path: PathBuf,
125        /// Digest mode.
126        digest: DigestState,
127        /// Replay result.
128        result: ReplayResult,
129        /// Frame recorder.
130        recorder: FrameRecorder,
131    },
132}
133
134impl Execution {
135    /// Create a normal execution.
136    pub fn normal() -> io::Result<Self> {
137        Ok(Self::Normal)
138    }
139
140    /// Create a recording.
141    pub fn recording<P: AsRef<Path>>(
142        path: P,
143        digest_mode: DigestMode,
144        w: u16,
145        h: u16,
146        gif_mode: GifMode,
147    ) -> io::Result<Self> {
148        use io::{Error, ErrorKind};
149
150        let path = path.as_ref();
151        let file_name: &Path = path
152            .file_name()
153            .ok_or(Error::new(
154                ErrorKind::InvalidInput,
155                format!("invalid path {:?}", path),
156            ))?
157            .as_ref();
158
159        std::fs::create_dir_all(path)?;
160
161        let digest = DigestState::from(digest_mode, path.join(file_name).with_extension("digest"))?;
162        let gif_recorder = if gif_mode == GifMode::Record {
163            GifRecorder::new(path.join(file_name).with_extension("gif"), w, h)?
164        } else {
165            GifRecorder::dummy()
166        };
167        let recorder = FrameRecorder::new(gif_recorder, gif_mode, digest_mode);
168
169        Ok(Self::Recording {
170            events: Vec::new(),
171            start: time::Instant::now(),
172            path: path.to_path_buf(),
173            digest,
174            recorder,
175        })
176    }
177
178    /// Create a replay.
179    pub fn replaying<P: AsRef<Path>>(path: P, mode: DigestMode) -> io::Result<Self> {
180        use io::{Error, ErrorKind};
181
182        let mut events = VecDeque::new();
183        let path = path.as_ref();
184
185        let file_name: &Path = path
186            .file_name()
187            .ok_or(Error::new(
188                ErrorKind::InvalidInput,
189                format!("invalid path {:?}", path),
190            ))?
191            .as_ref();
192
193        let digest = DigestState::from(mode, path.join(file_name).with_extension("digest"))?;
194
195        let recorder = match &digest {
196            DigestState {
197                path: Some(path),
198                mode: DigestMode::Verify,
199            } => {
200                let mut frames = Vec::new();
201
202                match File::open(&path) {
203                    Ok(f) => {
204                        let r = io::BufReader::new(f);
205                        for line in r.lines() {
206                            let line = line?;
207                            let hash = Hash::from_str(line.as_str())
208                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
209                            frames.push(hash);
210                        }
211                    }
212                    Err(e) => {
213                        return Err(io::Error::new(
214                            e.kind(),
215                            format!("{}: {}", path.display(), e),
216                        ));
217                    }
218                }
219                FrameRecorder::from(frames, mode)
220            }
221            _ => FrameRecorder::new(GifRecorder::dummy(), GifMode::Ignore, mode),
222        };
223
224        let events_path = path.join(file_name).with_extension("events");
225        match File::open(&events_path) {
226            Ok(f) => {
227                let r = io::BufReader::new(f);
228                for (i, line) in r.lines().enumerate() {
229                    let line = line?;
230                    let ev = TimedEvent::from_str(&line).map_err(|e| {
231                        io::Error::new(
232                            io::ErrorKind::InvalidInput,
233                            format!("{}:{}: {}", events_path.display(), i + 1, e),
234                        )
235                    })?;
236                    events.push_back(ev);
237                }
238                Ok(Self::Replaying {
239                    events,
240                    start: time::Instant::now(),
241                    path: path.to_path_buf(),
242                    digest,
243                    result: ReplayResult::new(),
244                    recorder,
245                })
246            }
247            Err(e) => Err(io::Error::new(
248                e.kind(),
249                format!("{}: {}", events_path.display(), e),
250            )),
251        }
252    }
253
254    pub fn is_normal(&self) -> bool {
255        if let Execution::Normal = self {
256            true
257        } else {
258            false
259        }
260    }
261
262    pub fn record(&mut self, data: &[Bgra8]) {
263        match self {
264            // Replaying and verifying digests.
265            Self::Replaying {
266                digest:
267                    DigestState {
268                        mode: DigestMode::Verify,
269                        ..
270                    },
271                result,
272                recorder,
273                ..
274            } => {
275                result.record(recorder.verify_frame(data));
276            }
277            // Replaying/Recording and recording digests.
278            Self::Replaying { recorder, .. } | Self::Recording { recorder, .. } => {
279                recorder.record_frame(data);
280            }
281            _ => {}
282        }
283    }
284
285    ////////////////////////////////////////////////////////////////////////////
286
287    pub fn stop_recording(&mut self) -> io::Result<PathBuf> {
288        use io::{Error, ErrorKind};
289
290        let result = if let Execution::Recording {
291            events,
292            path,
293            digest,
294            recorder,
295            ..
296        } = self
297        {
298            recorder.finish()?;
299
300            let file_name: &Path = path
301                .file_name()
302                .ok_or(Error::new(
303                    ErrorKind::InvalidInput,
304                    format!("invalid path {:?}", path),
305                ))?
306                .as_ref();
307
308            let mut f = File::create(path.join(file_name.with_extension("events")))?;
309            for ev in events.clone() {
310                writeln!(&mut f, "{}", String::from(ev))?;
311            }
312
313            if let DigestState {
314                mode: DigestMode::Record,
315                path: Some(path),
316                ..
317            } = digest
318            {
319                Execution::write_digest(recorder, path)?;
320            }
321
322            Ok(path.clone())
323        } else {
324            panic!("record finalizer called outside of recording context")
325        };
326
327        if result.is_ok() {
328            *self = Execution::Normal;
329        }
330        result
331    }
332
333    pub fn finalize_replaying(&self) -> io::Result<PathBuf> {
334        if let Execution::Replaying {
335            digest:
336                DigestState {
337                    mode: DigestMode::Record,
338                    path: Some(path),
339                    ..
340                },
341            recorder,
342            ..
343        } = &self
344        {
345            Execution::write_digest(recorder, path)?;
346            Ok(path.clone())
347        } else {
348            panic!("replay finalizer called outside of replay context")
349        }
350    }
351
352    ////////////////////////////////////////////////////////////////////////////
353
354    fn write_digest<P: AsRef<Path>>(recorder: &FrameRecorder, path: P) -> io::Result<()> {
355        let path = path.as_ref();
356
357        if let Some(parent) = path.parent() {
358            std::fs::create_dir_all(parent)?;
359        }
360        let mut f = File::create(path)?;
361
362        for frame in &recorder.frames {
363            writeln!(&mut f, "{}", frame)?;
364        }
365        Ok(())
366    }
367}
368
369impl Default for Execution {
370    fn default() -> Self {
371        Execution::Normal
372    }
373}
374
375pub struct GifRecorder {
376    width: u16,
377    height: u16,
378    encoder: Option<gif::Encoder<Box<File>>>,
379    frames: Vec<(time::Instant, Vec<u8>)>,
380}
381
382impl GifRecorder {
383    const GIF_ENCODING_SPEED: i32 = 30;
384
385    pub fn new<P: AsRef<Path>>(path: P, width: u16, height: u16) -> io::Result<Self> {
386        let file = Box::new(File::create(path.as_ref())?);
387        let encoder = Some(gif::Encoder::new(file, width, height, &[])?);
388
389        Ok(Self {
390            width,
391            height,
392            encoder,
393            frames: Vec::new(),
394        })
395    }
396
397    fn dummy() -> Self {
398        Self {
399            width: 0,
400            height: 0,
401            encoder: None,
402            frames: Vec::new(),
403        }
404    }
405
406    fn is_dummy(&self) -> bool {
407        self.width == 0 && self.height == 0
408    }
409
410    fn record(&mut self, data: &[Bgra8]) {
411        if self.is_dummy() {
412            return;
413        }
414        let now = time::Instant::now();
415        let mut gif_data: Vec<u8> = Vec::with_capacity(data.len());
416        for bgra in data.iter().cloned() {
417            let rgba: Rgba8 = bgra.into();
418            gif_data.extend_from_slice(&[rgba.r, rgba.g, rgba.b]);
419        }
420        self.frames.push((now, gif_data));
421    }
422
423    fn finish(&mut self) -> io::Result<()> {
424        use std::convert::TryInto;
425
426        if let Some(encoder) = &mut self.encoder {
427            for (i, (t1, gif_data)) in self.frames.iter().enumerate() {
428                let delay = if let Some((t2, _)) = self.frames.get(i + 1) {
429                    *t2 - *t1
430                } else {
431                    // Let the last frame linger for a second...
432                    time::Duration::from_secs(1)
433                };
434
435                let mut frame = gif::Frame::from_rgb_speed(
436                    self.width,
437                    self.height,
438                    &gif_data,
439                    Self::GIF_ENCODING_SPEED,
440                );
441                frame.dispose = gif::DisposalMethod::Background;
442                frame.delay = (delay.as_millis() / 10)
443                    .try_into()
444                    .expect("`delay` is not an unreasonably large number");
445
446                encoder.write_frame(&frame)?;
447            }
448        }
449        Ok(())
450    }
451}
452
453/// Records and verifies frames being replayed.
454pub struct FrameRecorder {
455    frames: VecDeque<Hash>,
456    last_verified: Option<Hash>,
457    gif_recorder: GifRecorder,
458    gif_mode: GifMode,
459    digest_mode: DigestMode,
460}
461
462impl FrameRecorder {
463    fn new(gif_recorder: GifRecorder, gif_mode: GifMode, digest_mode: DigestMode) -> Self {
464        Self {
465            frames: VecDeque::new(),
466            last_verified: None,
467            gif_recorder,
468            gif_mode,
469            digest_mode,
470        }
471    }
472
473    fn from(frames: Vec<Hash>, digest_mode: DigestMode) -> Self {
474        Self {
475            frames: frames.into(),
476            last_verified: None,
477            gif_recorder: GifRecorder::dummy(),
478            gif_mode: GifMode::Ignore,
479            digest_mode,
480        }
481    }
482
483    fn record_frame(&mut self, data: &[Bgra8]) {
484        let hash = Self::hash(data);
485
486        if self.frames.back().map(|h| h != &hash).unwrap_or(true) {
487            debug!("frame: {}", hash);
488
489            if self.digest_mode == DigestMode::Record || self.gif_mode == GifMode::Record {
490                self.frames.push_back(hash);
491            }
492
493            self.gif_recorder.record(data);
494        }
495    }
496
497    fn verify_frame(&mut self, data: &[Bgra8]) -> VerifyResult {
498        let actual = Self::hash(data);
499
500        if self.frames.is_empty() {
501            return VerifyResult::EOF;
502        }
503        if Some(actual.clone()) == self.last_verified {
504            return VerifyResult::Stale(actual);
505        }
506        self.last_verified = Some(actual.clone());
507
508        if let Some(expected) = self.frames.pop_front() {
509            if actual == expected {
510                VerifyResult::Okay(actual)
511            } else {
512                VerifyResult::Failed(actual, expected)
513            }
514        } else {
515            VerifyResult::EOF
516        }
517    }
518
519    fn finish(&mut self) -> io::Result<()> {
520        self.gif_recorder.finish()
521    }
522
523    fn hash(data: &[Bgra8]) -> Hash {
524        let (_, data, _) = unsafe { data.align_to::<u8>() };
525        let bytes: GenericArray<u8, U64> = MeowHasher::digest(data);
526        let (prefix, _): (GenericArray<u8, U4>, _) = bytes.split();
527
528        Hash(prefix.into())
529    }
530}
531
532#[derive(Debug, Clone)]
533pub struct ReplayResult {
534    verify_results: Vec<VerifyResult>,
535    eof: bool,
536    okay_count: u32,
537    failed_count: u32,
538    stale_count: u32,
539}
540
541impl ReplayResult {
542    pub fn is_ok(&self) -> bool {
543        self.failed_count == 0
544    }
545
546    pub fn is_err(&self) -> bool {
547        !self.is_ok()
548    }
549
550    pub fn is_done(&self) -> bool {
551        self.eof
552    }
553
554    pub fn summary(&self) -> String {
555        let total = (self.okay_count + self.failed_count) as f32;
556
557        format!(
558            "{:.1}% OK, {:.1}% FAILED",
559            self.okay_count as f32 / total * 100.,
560            self.failed_count as f32 / total * 100.
561        )
562    }
563
564    ////////////////////////////////////////////////////////////////////////////
565
566    fn new() -> Self {
567        ReplayResult {
568            verify_results: Vec::new(),
569            okay_count: 0,
570            failed_count: 0,
571            stale_count: 0,
572            eof: false,
573        }
574    }
575
576    fn record(&mut self, result: VerifyResult) {
577        match &result {
578            VerifyResult::Okay(actual) => {
579                info!("verify: {} OK", actual);
580                self.okay_count += 1;
581            }
582            VerifyResult::Failed(actual, expected) => {
583                error!("verify: {} != {}", actual, expected);
584                self.failed_count += 1;
585                // TODO: Stop replaying
586            }
587            VerifyResult::EOF => {
588                self.eof = true;
589            }
590            VerifyResult::Stale { .. } => {
591                self.stale_count += 1;
592            }
593        }
594        self.verify_results.push(result);
595    }
596}
597
598#[derive(Debug, Clone)]
599pub enum VerifyResult {
600    /// The hash has already been verified.
601    Stale(Hash),
602    /// The actual and expected hashes match.
603    Okay(Hash),
604    /// The actual and expected hashes don't match.
605    Failed(Hash, Hash),
606    /// There are no further expected hashes.
607    EOF,
608}
609
610#[derive(PartialEq, Eq, Clone, Debug)]
611pub struct Hash([u8; 4]);
612
613impl fmt::Display for Hash {
614    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
615        for byte in self.0.iter() {
616            write!(f, "{:02x}", byte)?;
617        }
618        Ok(())
619    }
620}
621
622impl FromStr for Hash {
623    type Err = String;
624
625    fn from_str(input: &str) -> Result<Self, Self::Err> {
626        let val = |c: u8| match c {
627            b'a'..=b'f' => Ok(c - b'a' + 10),
628            b'0'..=b'9' => Ok(c - b'0'),
629            _ => Err(format!("invalid hex character {:?}", c)),
630        };
631
632        let mut hash: Vec<u8> = Vec::new();
633        for pair in input.bytes().collect::<Vec<u8>>().chunks(2) {
634            match pair {
635                [l, r] => {
636                    let left = val(*l)? << 4;
637                    let right = val(*r)?;
638
639                    hash.push(left | right);
640                }
641                _ => return Err(format!("invalid hex string: {:?}", input)),
642            }
643        }
644
645        let mut array = [0; 4];
646        array.copy_from_slice(hash.as_slice());
647
648        Ok(Hash(array))
649    }
650}