1use crate::abend::Abend;
10use crate::files::{Dds, Open};
11use crate::oo::ClassCode;
12use crate::storage::Loc;
13use crate::taint::Taint;
14use crate::vocab::{OpenMode, Pos};
15use numeric::Dialect;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::io::{BufRead, Write};
18use std::path::{Path, PathBuf};
19use std::rc::Rc;
20
21pub const ADDRESS_BASE: u32 = 0x0001_0000;
24pub const RETURN_CODE: usize = 0;
26const RESERVED: usize = 8;
27const ALIGNMENT: usize = 8;
28
29pub struct Loaded<H> {
30 pub compiled: Option<H>,
32 pub name: String,
33 pub base: usize,
34 pub size: usize,
35 pub placed: bool,
38 pub files: Vec<Option<Open>>,
39 pub locked: Vec<bool>,
41 pub initialized: bool,
42 pub active: bool,
43 pub dynamic: bool,
45 pub entry: Option<usize>,
48 pub altered: Vec<Option<usize>>,
50 pub source: Option<PathBuf>,
52}
53
54impl<H> Loaded<H> {
55 pub fn new(compiled: Option<H>, name: String, base: usize, size: usize, files: usize) -> Self {
57 let (locked, files) = (vec![false; files], (0..files).map(|_| None).collect());
58 Self { compiled, name, base, size, placed: true, files, locked, initialized: false, active: false, dynamic: false, entry: None, altered: Vec::new(), source: None }
59 }
60
61 fn set_aside(&mut self) -> Held {
64 let files = self.files.iter_mut().map(Option::take).collect();
65 let locked = std::mem::replace(&mut self.locked, vec![false; self.files.len()]);
66 let held = Held { base: self.base, placed: self.placed, files, locked, initialized: self.initialized, active: self.active, dynamic: self.dynamic, altered: std::mem::take(&mut self.altered) };
67 (self.placed, self.initialized, self.active, self.dynamic) = (false, false, false, false);
68 held
69 }
70
71 fn restore(&mut self, held: Held) {
72 (self.base, self.placed, self.files, self.locked, self.altered) = (held.base, held.placed, held.files, held.locked, held.altered);
73 (self.initialized, self.active, self.dynamic) = (held.initialized, held.active, held.dynamic);
74 }
75}
76
77struct Enclave {
82 programs: Vec<Held>,
83 externals: Externals,
84 connectors: HashMap<(usize, usize), Connector>,
85 heap: Vec<(usize, usize, bool)>,
86 random: Option<u32>,
87 return_code: ([u8; 2], bool),
89}
90
91struct Held {
93 base: usize,
94 placed: bool,
95 files: Vec<Option<Open>>,
96 locked: Vec<bool>,
97 initialized: bool,
98 active: bool,
99 dynamic: bool,
100 altered: Vec<Option<usize>>,
101}
102
103pub enum LoadError {
104 NotFound,
105 Compile(String),
107}
108
109pub struct LoadedProgram<H> {
111 pub compiled: H,
112 pub name: String,
114 pub files: usize,
115 pub size: usize,
116 pub source: Option<PathBuf>,
118 pub recorded: Vec<(String, Option<crate::module::SourceFile>)>,
121}
122
123pub struct FoundClass<C> {
126 pub code: C,
127 pub sources: Vec<String>,
128}
129
130pub trait Loader<H> {
132 fn program(&mut self, name: &str) -> Result<LoadedProgram<H>, LoadError>;
134
135 fn holder(&self, entry: &str) -> Option<String>;
137
138 fn entry(program: &H, name: &str) -> Option<usize>;
140
141 fn shape(program: &H) -> (usize, usize);
143
144 fn nested(program: &H) -> &[String];
146
147 fn source(program: &H, file: usize) -> Option<String>;
149
150 fn class(&mut self, external: &str) -> Result<Option<FoundClass<Rc<ClassCode<H>>>>, String>;
153
154 fn mapset(&mut self, name: &str) -> Option<Result<crate::bms::Mapset, String>>;
157}
158
159pub trait UnitHost<'w> {
162 type Program: Clone;
163 type Loader: Loader<Self::Program>;
164 fn unit(&mut self) -> &mut RunUnit<'w, Self::Program, Self::Loader>;
165}
166
167#[derive(Clone, Copy, Debug)]
168pub enum Clock {
169 System,
170 Fixed(i64, u32),
172}
173
174pub enum Event<'a> {
178 Open { dd: &'a str, mode: OpenMode, path: &'a Path },
179 Close { dd: &'a str, path: &'a Path },
180 Load { program: &'a str, source: Option<&'a Path>, recorded: &'a [(String, Option<crate::module::SourceFile>)] },
182 Paragraph { program: &'a str, name: &'a str, index: usize },
184 Sink { kind: &'static str, file: &'a str, line: u32, operand: &'a str, input: Option<bool> },
189 Statement { file: &'a str, line: u32 },
191}
192
193pub const SINK_KINDS: [&str; 16] = [
196 "cics-dynamic-transfer",
197 "cics-sysid",
198 "connection-target",
199 "dynamic-file-path",
200 "dynamic-program-load",
201 "dynamic-sql",
202 "http-header",
203 "log",
204 "os-command",
205 "outbound-host",
206 "outbound-http",
207 "queue-name",
208 "record-key",
209 "record-update",
210 "screen",
211 "web-response",
212];
213
214#[derive(Clone, Debug, PartialEq, Eq)]
217pub enum StatementFilter {
218 All,
219 Lines(HashSet<u32>),
220}
221
222pub type Observer<'w> = Box<dyn FnMut(Event<'_>) + 'w>;
223
224pub const MAX_DEPTH: usize = 100;
226
227#[derive(Default)]
231pub struct Externals {
232 storage: HashMap<(bool, String), (usize, usize)>,
235 files: Vec<Option<Open>>,
236 locked: Vec<bool>,
238 file_names: HashMap<String, usize>,
239}
240
241pub fn switch_record(n: u8) -> String {
245 format!("UPSI-{n} SWITCH")
246}
247
248pub fn switch_of_record(name: &str) -> Option<u8> {
250 match name.strip_prefix("UPSI-")?.strip_suffix(" SWITCH")?.as_bytes() {
251 [d @ b'0'..=b'7'] => Some(d - b'0'),
252 _ => None,
253 }
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub enum Connector {
259 External(usize),
261 Program(usize, usize),
263}
264
265pub const OS_COMMAND_ROUTINES: &[&str] = &["SYSTEM", "C$SYSTEM", "CBL_EXEC_RUN_UNIT", "CBL_GC_HOSTED", "BXPSYSTM"];
268
269pub struct RunUnit<'w, H, L: Loader<H>> {
270 pub mem: Vec<u8>,
271 pub depth: usize,
273 pub programs: Vec<Loaded<H>>,
274 names: HashMap<String, usize>,
275 pub library: L,
276 pub dds: Dds,
277 pub sysin: Option<Box<dyn BufRead + 'w>>,
278 pub clock: Clock,
279 pub out: &'w mut dyn Write,
280 pub err: &'w mut dyn Write,
281 pub cics: Option<crate::cics::Task>,
283 pub eib: usize,
284 pub cics_files: HashMap<String, Open>,
285 pub sql: Option<crate::sql::Session<'w>>,
287 pub le: crate::le::State,
289 pub oo: crate::oo::Objects<Rc<ClassCode<H>>>,
291 pub observer: Option<Observer<'w>>,
293 pub random: Option<u32>,
295 pub arguments: crate::le::parm::Arguments,
298 externals: Externals,
299 connectors: HashMap<(usize, usize), Connector>,
301 pub entries: Vec<crate::set::Entry>,
303 pub statements: Option<StatementFilter>,
305 pub taint: Option<Taint>,
307 pub statement_limit: Option<u64>,
309 recent: VecDeque<Started>,
312 overrun: Option<Overrun>,
313 pub(crate) sysin_ended: HashSet<(u16, u32, u32)>,
316 set_aside: Vec<Enclave>,
318}
319
320fn end_file(f: Open, unclosed: bool) -> std::io::Result<()> {
321 if unclosed { f.abandon() } else { f.close() }
322}
323
324const LOOP_WINDOW: usize = 4096;
327
328#[derive(Clone, Copy, PartialEq, Eq)]
330struct Started {
331 program: usize,
332 depth: usize,
333 pos: Pos,
334}
335
336struct Overrun {
339 head: Started,
340 lines: Vec<u32>,
341 grace: usize,
342}
343
344fn loop_of(recent: &VecDeque<Started>, now: Started) -> Overrun {
350 let key = |s: &Started| (s.program, s.depth, s.pos.file, s.pos.line, s.pos.col);
351 let mut seen: HashMap<_, usize> = HashMap::new();
352 for s in recent {
353 *seen.entry(key(s)).or_default() += 1;
354 }
355 let recurring: Vec<&Started> = recent.iter().filter(|s| seen[&key(s)] > 1).collect();
356 let Some(outer) = recurring.iter().map(|s| s.depth).min() else { return Overrun { head: now, lines: Vec::new(), grace: 0 } };
357 let program = recurring.iter().rev().find(|s| s.depth == outer).map_or(now.program, |s| s.program);
358 let frame: Vec<&Started> = recurring.into_iter().filter(|s| s.depth == outer && s.program == program).collect();
359 let file = frame.iter().map(|s| s.pos.file).min().unwrap_or(now.pos.file);
360 let head = frame.iter().filter(|s| s.pos.file == file).min_by_key(|s| (s.pos.line, s.pos.col)).map_or(now, |s| **s);
361 let mut lines: Vec<u32> = frame.iter().filter(|s| s.pos.file == file).map(|s| s.pos.line).collect();
362 lines.sort_unstable();
363 lines.dedup();
364 Overrun { head, lines, grace: LOOP_WINDOW }
365}
366
367impl<H, L: Loader<H>> RunUnit<'_, H, L> {
368 pub fn write(&mut self, offset: usize, bytes: &[u8]) {
372 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
373 self.mark(offset, bytes.len());
374 }
375
376 pub fn write_input(&mut self, offset: usize, bytes: &[u8]) {
378 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
379 self.mark_input(offset, bytes.len(), true);
380 }
381
382 pub fn mark(&mut self, offset: usize, len: usize) {
384 if let Some(t) = self.taint.as_mut() {
385 let pending = t.pending();
386 t.set(offset, len, pending);
387 }
388 }
389
390 pub fn mark_input(&mut self, offset: usize, len: usize, input: bool) {
392 if let Some(t) = self.taint.as_mut() {
393 t.set(offset, len, input);
394 }
395 }
396
397 pub fn taint_read(&mut self, loc: Loc) {
399 if let Some(t) = self.taint.as_mut() {
400 t.read(loc.offset, loc.len);
401 }
402 }
403
404 pub fn writing(&mut self, on: bool) -> bool {
406 self.taint.as_mut().is_some_and(|t| t.writing(on))
407 }
408
409 pub fn statement_starts(&mut self) {
411 if let Some(t) = self.taint.as_mut() {
412 t.start_statement();
413 }
414 }
415
416 pub fn take_input(&mut self) {
418 if let Some(t) = self.taint.as_mut() {
419 t.take_input();
420 }
421 }
422
423 pub fn holds_input(&self, offset: usize, len: usize) -> bool {
425 self.taint.as_ref().is_some_and(|t| t.any(offset, len))
426 }
427
428 pub fn pending(&self) -> bool {
430 self.taint.as_ref().is_some_and(Taint::pending)
431 }
432
433 pub fn resume_statement(&mut self, read_before: bool) {
435 if let Some(t) = self.taint.as_mut() {
436 t.resume_statement(read_before);
437 }
438 }
439
440 pub fn unfollowed(&mut self, what: &'static str) {
442 if let Some(t) = self.taint.as_mut() {
443 t.unfollowed(what);
444 }
445 }
446
447 pub fn input_at_sink(&self) -> Option<bool> {
449 self.taint.as_ref().and_then(Taint::at_sink)
450 }
451}
452
453impl<'w, H: Clone, L: Loader<H>> RunUnit<'w, H, L> {
454 pub fn new(library: L, dds: Dds, sysin: Option<Box<dyn BufRead + 'w>>, clock: Clock, out: &'w mut dyn Write, err: &'w mut dyn Write) -> Self {
455 Self {
456 mem: vec![0; RESERVED],
457 depth: 0,
458 programs: Vec::new(),
459 names: HashMap::new(),
460 library,
461 dds,
462 sysin,
463 clock,
464 out,
465 err,
466 cics: None,
467 eib: 0,
468 cics_files: HashMap::new(),
469 sql: None,
470 le: crate::le::State::default(),
471 oo: Default::default(),
472 observer: None,
473 random: None,
474 arguments: Default::default(),
475 externals: Externals::default(),
476 connectors: HashMap::new(),
477 entries: Vec::new(),
478 statements: None,
479 taint: None,
480 statement_limit: None,
481 recent: VecDeque::new(),
482 overrun: None,
483 sysin_ended: HashSet::new(),
484 set_aside: Vec::new(),
485 }
486 }
487
488 fn allocate(&mut self, size: usize) -> usize {
489 let base = self.mem.len().div_ceil(ALIGNMENT) * ALIGNMENT;
490 self.mem.resize(base + size, 0);
491 base
492 }
493
494 pub fn add_named(&mut self, compiled: Option<H>, name: String, files: usize, size: usize) -> usize {
496 let base = self.allocate(size);
497 let index = self.programs.len();
498 self.names.insert(name.clone(), index);
499 self.programs.push(Loaded::new(compiled, name, base, size, files));
500 index
501 }
502
503 pub fn begin_cics_run_unit(&mut self) {
508 let programs = self.programs.iter_mut().map(Loaded::set_aside).collect();
509 let (externals, connectors) = (std::mem::take(&mut self.externals), std::mem::take(&mut self.connectors));
510 let return_code = ([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]], self.holds_input(RETURN_CODE, 2));
511 self.mem[RETURN_CODE..RETURN_CODE + 2].fill(0);
512 self.mark_input(RETURN_CODE, 2, false);
513 let (heap, random) = (std::mem::take(&mut self.le.heap), self.random.take());
514 self.set_aside.push(Enclave { programs, externals, connectors, heap, random, return_code });
515 }
516
517 pub fn end_cics_run_unit(&mut self, xctl: bool) -> Result<(), String> {
523 let mut closed = Ok(());
524 for program in &mut self.programs {
525 for f in program.files.iter_mut().filter_map(Option::take) {
526 if let Err(e) = f.close() {
527 closed = closed.and(Err(format!("closing a file of {}: {e}", program.name)));
528 }
529 }
530 drop(program.set_aside());
531 }
532 closed = closed.and(self.close_external_files(false));
533 let Some(enclave) = self.set_aside.pop() else { return closed };
534 for (program, held) in self.programs.iter_mut().zip(enclave.programs) {
535 program.restore(held);
536 }
537 (self.externals, self.connectors, self.le.heap, self.random) = (enclave.externals, enclave.connectors, enclave.heap, enclave.random);
538 if !xctl {
539 let (bytes, input) = enclave.return_code;
540 self.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&bytes);
541 self.mark_input(RETURN_CODE, 2, input);
542 }
543 closed
544 }
545
546 pub fn load_entry(&mut self, name: &str, copy: bool) -> Result<(usize, Option<usize>), LoadError> {
550 if let Some(i) = self.find(name) {
551 return Ok((i, self.programs[i].entry));
552 }
553 let name = name.to_ascii_uppercase();
554 let index = match self.programs.iter().position(|p| p.compiled.as_ref().is_some_and(|c| L::entry(c, &name).is_some())) {
555 Some(i) => i,
556 None => {
557 let holder = self.library.holder(&name);
558 self.load(holder.as_deref().unwrap_or(&name))?
559 }
560 };
561 let Some(compiled) = self.programs[index].compiled.clone() else { return Ok((index, None)) };
562 let Some(entry) = L::entry(&compiled, &name) else { return Ok((index, None)) };
563 if !copy {
564 return Ok((index, Some(entry)));
565 }
566 let (files, size) = L::shape(&compiled);
567 let copy = self.add_named(Some(compiled), name, files, size);
568 self.programs[copy].entry = Some(entry);
569 self.programs[copy].source = self.programs[index].source.clone();
570 Ok((copy, Some(entry)))
571 }
572
573 pub fn push_temporary(&mut self, bytes: &[u8]) -> usize {
576 let at = self.allocate(bytes.len());
577 self.write(at, bytes);
578 at
579 }
580
581 pub fn release_temporaries(&mut self, mark: usize) {
584 let external = self.externals.storage.values().all(|&(at, _)| at < mark);
585 if self.programs.iter().all(|p| !p.placed || p.base + p.size <= mark) && self.le.heap_end() <= mark && external {
586 self.mem.truncate(mark.max(RESERVED));
587 if let Some(t) = self.taint.as_mut() {
588 t.truncate(self.mem.len());
589 }
590 }
591 }
592
593 pub fn enter(&mut self, pos: Pos) -> Result<(), Abend> {
595 if self.depth >= MAX_DEPTH {
596 return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {MAX_DEPTH}"), pos));
597 }
598 self.depth += 1;
599 Ok(())
600 }
601
602 pub fn activate(&mut self, me: usize, initial: bool) -> (usize, bool) {
606 if !self.programs[me].placed {
607 let base = self.allocate(self.programs[me].size);
608 (self.programs[me].base, self.programs[me].placed) = (base, true);
609 }
610 let program = &mut self.programs[me];
611 program.active = true;
612 (program.base, !program.initialized || initial)
613 }
614
615 pub fn initialized(&mut self, me: usize) {
617 self.programs[me].initialized = true;
618 self.programs[me].altered.clear();
619 }
620
621 pub fn find(&self, name: &str) -> Option<usize> {
622 if name.bytes().any(|b| b.is_ascii_lowercase()) {
623 return self.names.get(&name.to_ascii_uppercase()).copied();
624 }
625 self.names.get(name).copied()
626 }
627
628 pub fn load(&mut self, name: &str) -> Result<usize, LoadError> {
630 let name = name.to_ascii_uppercase();
631 if let Some(i) = self.find(&name) {
632 return Ok(i);
633 }
634 let loaded = self.library.program(&name)?;
635 self.notify(Event::Load { program: &name, source: loaded.source.as_deref(), recorded: &loaded.recorded });
636 let index = self.add_named(Some(loaded.compiled), loaded.name, loaded.files, loaded.size);
637 self.programs[index].source = loaded.source;
638 Ok(index)
639 }
640
641 pub const fn observed(&self) -> bool {
642 self.observer.is_some()
643 }
644
645 pub fn start_statement(&mut self, program: usize, pos: Pos) -> Result<(), Abend> {
651 let Some(left) = self.statement_limit.as_mut() else { return Ok(()) };
652 let now = Started { program, depth: self.depth, pos };
653 if *left > 0 {
654 *left -= 1;
655 if self.recent.len() == LOOP_WINDOW {
656 self.recent.pop_front();
657 }
658 self.recent.push_back(now);
659 return Ok(());
660 }
661 let overrun = self.overrun.get_or_insert_with(|| loop_of(&self.recent, now));
662 let message = "the run reached its statement limit, as a step past its TIME= ends";
663 if now == overrun.head && !overrun.lines.is_empty() {
664 const SHOWN: usize = 24;
665 let mut lines = overrun.lines.iter().take(SHOWN).map(u32::to_string).collect::<Vec<_>>().join(", ");
666 if overrun.lines.len() > SHOWN {
667 lines += &format!(" and {} more", overrun.lines.len() - SHOWN);
668 }
669 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: format!("{message}, in the loop over lines {lines}"), pos, file: None });
670 }
671 if overrun.grace == 0 {
672 return Err(Abend { code: crate::abend::AbendCode::TimeLimit, message: message.into(), pos, file: None });
673 }
674 overrun.grace -= 1;
675 Ok(())
676 }
677
678 pub fn traces(&self, line: u32) -> bool {
679 match &self.statements {
680 None => false,
681 Some(StatementFilter::All) => self.observer.is_some(),
682 Some(StatementFilter::Lines(lines)) => self.observer.is_some() && lines.contains(&line),
683 }
684 }
685
686 pub fn notify(&mut self, event: Event<'_>) {
687 if let Event::Sink { kind, .. } = &event {
688 debug_assert!(SINK_KINDS.contains(kind), "the sink kind {kind} is not in rt::unit::SINK_KINDS");
689 }
690 if let Some(observer) = self.observer.as_mut() {
691 observer(event);
692 }
693 }
694
695 pub fn close_all(&mut self, unclosed: bool) -> Result<(), String> {
700 for program in &mut self.programs {
701 for f in program.files.iter_mut().filter_map(Option::take) {
702 end_file(f, unclosed).map_err(|e| format!("closing a file of {}: {e}", program.name))?;
703 }
704 }
705 self.close_external_files(unclosed)
706 }
707
708 fn close_external_files(&mut self, unclosed: bool) -> Result<(), String> {
710 let mut closed = Ok(());
711 for (name, &k) in &self.externals.file_names {
712 if let Some(f) = self.externals.files[k].take()
713 && let Err(e) = end_file(f, unclosed)
714 {
715 closed = closed.and(Err(format!("closing EXTERNAL file {name}: {e}")));
716 }
717 }
718 closed
719 }
720
721 pub fn external(&mut self, name: &str, file: bool, size: usize, dialect: Dialect) -> Result<usize, String> {
726 let key = (file, name.to_owned());
727 if let Some(&(at, had)) = self.externals.storage.get(&key) {
728 return if had == size {
729 Ok(at)
730 } else if size < had && !file && dialect == Dialect::Gnucobol {
731 let _ = writeln!(self.err, "ironwork: EXTERNAL record {name} has {had} bytes in the run unit, and this program describes {size}");
732 Ok(at)
733 } else {
734 let what = if file { "the record area of EXTERNAL file" } else { "EXTERNAL record" };
735 Err(format!("{what} {name} has {had} bytes in the run unit, and this program describes {size}"))
736 };
737 }
738 let at = self.allocate(size);
739 self.externals.storage.insert(key, (at, size));
740 Ok(at)
741 }
742
743 pub fn set_switches(&mut self, on: [bool; 8]) {
746 for (n, on) in (0..).zip(on) {
747 let at = self.push_temporary(&[u8::from(on)]);
748 self.externals.storage.insert((false, switch_record(n)), (at, 1));
749 self.mark_input(at, 1, true);
750 }
751 }
752
753 pub fn external_file(&mut self, name: &str) -> Connector {
755 let (files, locked) = (&mut self.externals.files, &mut self.externals.locked);
756 let k = *self.externals.file_names.entry(name.to_owned()).or_insert_with(|| {
757 files.push(None);
758 locked.push(false);
759 files.len() - 1
760 });
761 Connector::External(k)
762 }
763
764 pub fn connect(&mut self, me: usize, k: usize, to: Connector) {
766 self.connectors.insert((me, k), to);
767 }
768
769 fn connector(&self, mut me: usize, mut k: usize) -> Option<Connector> {
770 let mut to = None;
771 while let Some(&c) = self.connectors.get(&(me, k)) {
772 to = Some(c);
773 match c {
774 Connector::External(_) => break,
775 Connector::Program(p, j) => (me, k) = (p, j),
776 }
777 }
778 to
779 }
780
781 pub fn file(&mut self, me: usize, k: usize) -> &mut Option<Open> {
783 match self.connector(me, k) {
784 None => &mut self.programs[me].files[k],
785 Some(Connector::External(e)) => &mut self.externals.files[e],
786 Some(Connector::Program(p, j)) => &mut self.programs[p].files[j],
787 }
788 }
789
790 pub fn locked(&mut self, me: usize, k: usize) -> &mut bool {
792 match self.connector(me, k) {
793 None => &mut self.programs[me].locked[k],
794 Some(Connector::External(e)) => &mut self.externals.locked[e],
795 Some(Connector::Program(p, j)) => &mut self.programs[p].locked[j],
796 }
797 }
798
799 pub fn file_ref(&self, me: usize, k: usize) -> &Option<Open> {
800 match self.connector(me, k) {
801 None => &self.programs[me].files[k],
802 Some(Connector::External(e)) => &self.externals.files[e],
803 Some(Connector::Program(p, j)) => &self.programs[p].files[j],
804 }
805 }
806
807 pub fn return_code(&self) -> i16 {
808 i16::from_be_bytes([self.mem[RETURN_CODE], self.mem[RETURN_CODE + 1]])
809 }
810
811 pub fn now(&self) -> (i64, u32) {
813 match self.clock {
814 Clock::Fixed(s, h) => (s, h),
815 Clock::System => {
816 let d = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
817 (d.as_secs() as i64, d.subsec_millis() / 10)
818 }
819 }
820 }
821}