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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DigestMode {
26 Verify,
28 Record,
30 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
98pub enum Execution {
102 Normal,
104 Recording {
106 events: Vec<TimedEvent>,
108 start: time::Instant,
110 path: PathBuf,
112 digest: DigestState,
114 recorder: FrameRecorder,
116 },
117 Replaying {
119 events: VecDeque<TimedEvent>,
121 start: time::Instant,
123 path: PathBuf,
125 digest: DigestState,
127 result: ReplayResult,
129 recorder: FrameRecorder,
131 },
132}
133
134impl Execution {
135 pub fn normal() -> io::Result<Self> {
137 Ok(Self::Normal)
138 }
139
140 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 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 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 Self::Replaying { recorder, .. } | Self::Recording { recorder, .. } => {
279 recorder.record_frame(data);
280 }
281 _ => {}
282 }
283 }
284
285 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 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 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
453pub 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 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 }
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 Stale(Hash),
602 Okay(Hash),
604 Failed(Hash, Hash),
606 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}