chiark / gitweb /
utils: Tidy up and move DigestRead and DigestWrite
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 2 May 2022 10:24:13 +0000 (11:24 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 2 May 2022 10:24:13 +0000 (11:24 +0100)
Now this file is more navigable.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/bundles.rs
src/digestrw.rs [new file with mode: 0644]
src/lib.rs
src/prelude.rs
src/utils.rs

index d59d1276182fc8460e6d567f56733140d9466a82..be2cdd5c68c6a08f76e5e0d6c5d879c91eb0e672 100644 (file)
@@ -7,7 +7,7 @@ use crate::prelude::*;
 //---------- public types ----------
 
 pub use crate::prelude::Sha512_256 as Digester;
-pub type DigestWrite<W> = crate::utils::DigestWrite<Digester, W>;
+pub type DigestWrite<W> = digestrw::DigestWrite<Digester, W>;
 
 #[derive(Copy,Clone,Hash,Eq,PartialEq,Serialize,Deserialize)]
 pub struct Hash(pub [u8; 32]);
diff --git a/src/digestrw.rs b/src/digestrw.rs
new file mode 100644 (file)
index 0000000..eb8a14a
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright 2020-2021 Ian Jackson and contributors to Otter
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// There is NO WARRANTY.
+
+use crate::imports::*;
+use crate::prelude::*;
+
+#[derive(Debug,Copy,Clone)]
+pub struct DigestRead<D: Digest, R: Read> {
+  d: D,
+  r: R,
+}
+
+impl<D: Digest, R: Read> DigestRead<D, R> {
+  pub fn new(r: R) -> Self { DigestRead { r, d: D::new() } }
+  pub fn into_inner(self) -> (D, R) { (self.d, self.r) }
+  pub fn finish(self) -> digest::Output<D> {
+    self.d.finalize()
+  }
+}
+
+impl<D: Digest, R: Read> Read for DigestRead<D, R> {
+  #[throws(io::Error)]
+  fn read(&mut self, buf: &mut [u8]) -> usize {
+    let count = self.r.read(buf)?;
+    self.d.update(&buf[0..count]);
+    count
+  }
+}
+
+#[test]
+#[cfg(not(miri))]
+fn test_digest_read() {
+  let ibuffer = b"abc";
+  let exp = Sha512_256::digest(&ibuffer[..]);
+  let inner = &ibuffer[..];
+  let mut dr = DigestRead::<Sha512_256,_>::new(inner);
+  let mut obuffer = [0;4];
+  assert_eq!( dr.read(&mut obuffer).unwrap(), 3 );
+  assert_eq!( &obuffer, b"abc\0" );
+  let got = dr.finish();
+  assert_eq!( got, exp );
+}
+
+#[derive(Debug,Copy,Clone)]
+pub struct DigestWrite<D: Digest, W: Write> {
+  d: D,
+  w: W,
+}
+
+impl<D: Digest, W: Write> DigestWrite<D, W> {
+  pub fn new(w: W) -> Self { DigestWrite { w, d: D::new() } }
+  pub fn into_inner(self) -> (D, W) { (self.d, self.w) }
+  pub fn finish(self) -> (digest::Output<D>, W) {
+    (self.d.finalize(), self.w)
+  }
+}
+impl<D: Digest> DigestWrite<D, io::Sink> {
+  pub fn sink() -> Self { DigestWrite::new(io::sink()) }
+
+  #[throws(io::Error)]
+  pub fn of<R>(r: &mut R) -> digest::Output<D> where R: Read {
+    let mut dw = DigestWrite::<D,_>::sink();
+    io::copy(r, &mut dw)?;
+    dw.finish().0
+  }
+}
+
+impl<D: Digest, W: Write> Write for DigestWrite<D, W> {
+  #[throws(io::Error)]
+  fn write(&mut self, buf: &[u8]) -> usize {
+    let count = self.w.write(buf)?;
+    self.d.update(&buf[0..count]);
+    count
+  }
+  #[throws(io::Error)]
+  fn flush(&mut self) { self.w.flush()? }
+}
+
+#[test]
+#[cfg(not(miri))]
+fn test_digest_write() {
+  let ibuffer = b"xyz";
+  let exp = Sha512_256::digest(&ibuffer[..]);
+  let mut obuffer = [0;4];
+  let inner = &mut obuffer[..];
+  let mut dw = bundles::DigestWrite::new(inner);
+  assert_eq!( dw.write(&ibuffer[..]).unwrap(), 3);
+  let (got, recov) = dw.finish();
+  assert_eq!( recov, b"\0" );
+  assert_eq!( got, exp );
+  assert_eq!( &obuffer, b"xyz\0" );
+}
index 38ec79cf5d97bfd86c435393c3c27a30ef81cdcc..af864b19702b3c84c66fed63e2dea01d62f78a86 100644 (file)
@@ -32,6 +32,7 @@ pub mod config;
 pub mod currency;
 pub mod deck;
 pub mod dice;
+pub mod digestrw;
 pub mod debugmutex;
 pub mod debugreader;
 pub mod error;
index a669c99c3140f489e2781ce59e3724327d3e6a57..661c03a00cbde43fc3a2edca66fdce3fd0f0d954 100644 (file)
@@ -154,6 +154,7 @@ pub use crate::commands::{ProgressUpdateMode};
 pub use crate::config::*;
 pub use crate::debugmutex::DebugIdentify;
 pub use crate::debugreader::DebugReader;
+pub use crate::digestrw::{self, *};
 pub use crate::error::*;
 pub use crate::fake_rng::*;
 pub use crate::fake_time::*;
index b9e1b15126aa77f2e9d89848123cb8a992bb7faf..f10e8418adaba0e05220e621635b243b89feb6f0 100644 (file)
@@ -5,22 +5,44 @@
 use crate::imports::*;
 use crate::prelude::*;
 
-#[macro_export]
-macro_rules! ensure_eq {
-  ($v1:expr, $v2:expr) => {
-    ({
-      let v1 = &$v1;
-      let v2 = &$v2;
-      if v1 != v2 {
-        Err(anyhow!("ensure_eq failed: {} != {}: {:?} != {:?}",
-                    stringify!($v1), stringify!($v2),
-                    v1, v2))
-      } else {
-        Ok(())
-      }
-    }?)
+/*
+put trait OptionExt {
+  type Output;
+  fn get_or_try_insert_with<
+      E: Error,
+      F: FnOnce() -> Result<Output,E>,
+    >(&mut self, f: F) -> Result<&mut Output, E>;
+}
+
+impl<T> OptionExt for Option<T> {
+  type Output = T;
+  fn get_or_try_insert_with<E,F>
+    (&mut self, f: F) -> Result<&mut Output, E>
+    where E: Error, F: FnOnce() -> Result<Output,E>,
+  {
+    if self.is_none() {
+      *self = Some(f()?);
+    }
+    Ok(self.as_mut().unwrap())
   }
 }
+*/
+
+//========== miscellany ==========
+// (roughly in order of implementation length)
+
+pub fn is_default<T: ConstDefault + Eq>(t: &T) -> bool { t == &T::DEFAULT }
+
+// TODO: this is not used anywhere!
+#[derive(Error,Clone,Copy,Debug,Eq,PartialEq,Serialize,Deserialize)]
+#[error("error parsing Z coordinate")]
+pub struct FooParseError;
+
+#[ext(pub, name=SeekExt)]
+impl<T: io::Seek> T {
+  #[throws(io::Error)]
+  fn rewind(&mut self) { self.seek(io::SeekFrom::Start(0))? }
+}
 
 #[ext(pub, name=OrdExt)]
 impl<T: Ord + Sized + Clone> T {
@@ -39,6 +61,148 @@ impl str {
   }
 }
 
+#[derive(Debug,Clone)]
+pub struct JsonString<T:Serialize>(pub T);
+impl<T> Serialize for JsonString<T> where T:Serialize {
+  #[throws(S::Error)]
+  fn serialize<S>(&self, s: S) -> S::Ok where S:Serializer {
+    let json = serde_json::to_string(&self.0)
+      .map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
+    Serialize::serialize(&json, s)?
+  }
+}
+
+#[throws(Either<io::Error, io::Error>)]
+pub fn io_copy_interactive<R,W>(read: &mut BufReader<R>, write: &mut W)
+where R: Read, W: Write {
+  loop {
+    let buf = read.fill_buf().map_err(Either::Left)?;
+    if buf.len() == 0 { break }
+
+    let did = (||{
+      let did = write.write(buf)?;
+      if did == 0 { throw!(ErrorKind::WriteZero) }
+      Ok::<_,io::Error>(did)
+    })().map_err(Either::Right)?;
+        
+    read.consume(did);
+    write.flush().map_err(Either::Right)?;
+  }
+}
+
+/// Allows the use of serde for a compat struct
+///
+/// Ideally we would have
+/// ```rust ignore
+/// #[derive(Deserialize)]
+/// #[serde(try_from=Compat)]
+/// struct Main { /* new definition */ }
+///
+/// #[derive(Deserialize)]
+/// #[serde(untagged)]
+/// enum Compat { V1(Main), V2(Old) }
+///
+/// #[derive(Deserialize)]
+/// struct Old { /* old version we still want to read */ }
+///
+/// impl TryFrom<Compat> for Main { /* ... */ }
+/// ```
+///
+/// But the impl for `Compat` ends up honouring the `try_from` on `Main`
+/// so is recursive.  We solve that abusing serde's remote feature.
+///
+/// For an example, see `IOccultIlk`.
+///
+/// The name of the main structure must be passed twice, once as an
+/// identifier and once as a literal, because `stringify!` doesn't work
+/// in the serde attribute.
+#[macro_export]
+macro_rules! serde_with_compat { {
+  [ $( #[ $($attrs:meta)* ] )* ] [ $vis:vis ] [ $($intro:tt)* ]
+    $main:ident=$main_s:literal $new:ident $compat_s:literal
+  [ $($body:tt)* ]
+} => {
+  $(#[ $($attrs)* ])* 
+  #[serde(try_from=$compat_s)]
+  $vis $($intro)* $main $($body)*
+
+  #[allow(non_camel_case_types)]
+  $(#[ $($attrs)* ])* 
+  #[serde(remote=$main_s)]
+  $($intro)* $new $($body)*
+} }
+
+//---------- Timespec (for serde) ----------
+
+pub mod timespec_serde {
+  use super::*;
+
+  #[derive(Serialize, Deserialize)]
+  struct Timespec(i64, u32);
+
+  #[throws(S::Error)]
+  pub fn serialize<S:Serializer>(v: &TimeSpec, s: S) -> S::Ok {
+    let v = Timespec(v.tv_sec(), v.tv_nsec().try_into().unwrap());
+    Serialize::serialize(&v, s)?
+  }
+  #[throws(D::Error)]
+  pub fn deserialize<'de, D:Deserializer<'de>>(d: D) -> TimeSpec {
+    let Timespec(sec, nsec) = Deserialize::deserialize(d)?;
+    libc::timespec { tv_sec: sec, tv_nsec: nsec.into() }.into()
+  }
+}
+
+//---------- emptytype ----------
+
+// TODO: replace with Void
+
+pub trait EmptyType { fn diverge<T>(self) -> T; }
+
+impl EmptyType for Infallible {
+  fn diverge<T>(self) -> T { match self { } }
+}
+
+//---------- IpAddress ----------
+
+pub trait IpAddress: Debug {
+  fn with_port(&self, port: u16) -> SocketAddr;
+}
+
+impl<A> IpAddress for A where A: Into<IpAddr> + Debug + Clone {
+  fn with_port(&self, port: u16) -> SocketAddr {
+    match (self.clone().into(), port)
+      .to_socket_addrs()
+      .map(|i| i.at_most_one()) {
+        Ok(Ok(Some(addr))) => addr,
+        x => panic!("{:?},{} gave {:?}", self, port, x),
+      }
+  }
+}
+
+//---------- get_or_extend_with ----------
+
+
+#[ext(pub)]
+impl<T> Vec<T> {
+  fn get_or_extend_with<F>(&mut self, i: usize, f: F) -> &mut T
+  where F: FnMut() -> T {
+    if self.get(i).is_none() {
+      self.resize_with(i+1, f);
+    }
+    &mut self[i]
+  }
+}
+
+#[ext(pub)]
+impl<I,T> IndexVec<I,T> where I: index_vec::Idx {
+  fn get_or_extend_with<F>(&mut self, i: I, f: F) -> &mut T
+  where F: FnMut() -> T {
+    self.raw.get_or_extend_with(i.index(), f)
+  }
+}
+
+//========== OldNew ==========
+
 #[derive(Copy,Clone,Debug,From,Into)]
 #[derive(Hash,Eq,PartialEq,Serialize,Deserialize)]
 #[serde(transparent)]
@@ -86,49 +250,7 @@ impl<T> Index<OldNewIndex> for OldNew<T> {
   fn index(&self, i: OldNewIndex) -> &T { &self.0[i as usize] }
 }
 
-/*
-put trait OptionExt {
-  type Output;
-  fn get_or_try_insert_with<
-      E: Error,
-      F: FnOnce() -> Result<Output,E>,
-    >(&mut self, f: F) -> Result<&mut Output, E>;
-}
-
-impl<T> OptionExt for Option<T> {
-  type Output = T;
-  fn get_or_try_insert_with<E,F>
-    (&mut self, f: F) -> Result<&mut Output, E>
-    where E: Error, F: FnOnce() -> Result<Output,E>,
-  {
-    if self.is_none() {
-      *self = Some(f()?);
-    }
-    Ok(self.as_mut().unwrap())
-  }
-}
-*/
-
-// https://github.com/rust-lang/rust/issues/32255 :-(
-
-#[ext(pub, name=LocalFileExt, supertraits=Sized)]
-impl fs::File {
-  #[throws(io::Error)]
-  fn close(self) {
-    let r = unsafe {
-      let fd = self.into_raw_fd();
-      libc::close(fd)
-    };
-    if r == 0 {
-      ()
-    } else if r == -1 {
-      throw!(io::Error::last_os_error())
-    } else {
-      panic!("close(2) returned {}", r)
-    }
-  }
-}
-
+//========== Thunk ==========
 
 // todo #[derive(Clone)]
 pub struct Thunk<U: Sync, F: Sync + FnOnce() -> U> (
@@ -177,27 +299,7 @@ impl<Y: Sync, E: Sync, F: Sync + FnOnce() -> Result<Y,E>>
 
 // todo: DerefMut
 
-#[derive(Error,Clone,Copy,Debug,Eq,PartialEq,Serialize,Deserialize)]
-#[error("error parsing Z coordinate")]
-pub struct FooParseError;
-
-pub mod timespec_serde {
-  use super::*;
-
-  #[derive(Serialize, Deserialize)]
-  struct Timespec(i64, u32);
-
-  #[throws(S::Error)]
-  pub fn serialize<S:Serializer>(v: &TimeSpec, s: S) -> S::Ok {
-    let v = Timespec(v.tv_sec(), v.tv_nsec().try_into().unwrap());
-    Serialize::serialize(&v, s)?
-  }
-  #[throws(D::Error)]
-  pub fn deserialize<'de, D:Deserializer<'de>>(d: D) -> TimeSpec {
-    let Timespec(sec, nsec) = Deserialize::deserialize(d)?;
-    libc::timespec { tv_sec: sec, tv_nsec: nsec.into() }.into()
-  }
-}
+//========== toml_merge ====================
 
 pub fn toml_merge<'u,
                   S: 'u + AsRef<str>,
@@ -238,216 +340,7 @@ pub fn toml_merge<'u,
   }
 }
 
-#[derive(Debug,Clone)]
-pub struct JsonString<T:Serialize>(pub T);
-impl<T> Serialize for JsonString<T> where T:Serialize {
-  #[throws(S::Error)]
-  fn serialize<S>(&self, s: S) -> S::Ok where S:Serializer {
-    let json = serde_json::to_string(&self.0)
-      .map_err(|e| <S::Error as serde::ser::Error>::custom(e))?;
-    Serialize::serialize(&json, s)?
-  }
-}
-
-#[macro_export]
-macro_rules! deref_to_field {
-  {$({ $($gen:tt)* })? $outer:ty, $inner:ty, $($field:tt)*} => {
-    impl $(< $($gen)* >)? Deref for $outer {
-      type Target = $inner;
-      fn deref(&self) -> &$inner { &self.$($field)* }
-    }
-  }
-}
-#[macro_export]
-macro_rules! deref_to_field_mut {
-  {$({ $($gen:tt)* })? $outer:ty, $inner:ty, $($field:tt)*} => {
-    deref_to_field!{ $({ $($gen)* })? $outer, $inner, $($field)*}
-    impl $(< $($gen)* >)? DerefMut for $outer {
-      fn deref_mut(&mut self) -> &mut $inner { &mut self.$($field)* }
-    }
-  }
-}
-
-pub trait EmptyType { fn diverge<T>(self) -> T; }
-
-impl EmptyType for Infallible {
-  fn diverge<T>(self) -> T { match self { } }
-}
-
-#[macro_export] // <- otherwise bogus warning `unused_macros`
-macro_rules! matches_doesnot_yn2bool {
-  (=) => (true);
-  (!) => (false);
-}
-
-#[macro_export]
-macro_rules! matches_doesnot {
-  ($v:expr,
-   $(
-     $yn:tt $p:pat
-   ),* $(,)?
-  ) => {
-    match $v {
-      $(
-        $p => $crate::matches_doesnot_yn2bool!($yn),
-      )*
-    }
-  }
-}
-
-#[test]
-fn matches_doesnot_test() {
-  assert!(
-    matches_doesnot!(
-      Some(42),
-      = Some(_),
-      ! None
-    )
-  );
-  assert!(
-    matches_doesnot!(
-      Some(42),
-      ! None,
-      ! Some(3),
-      = Some(_),
-    )
-  );
-  assert!(
-    matches_doesnot!(
-      Some(1),
-      = Some(1) | Some(2),
-      ! Some(_) | None
-    )
-  );
-  assert!(
-    ! matches_doesnot!(
-      Some(1),
-      ! Some(1) | Some(2),
-      = Some(_) | None
-    )
-  );
-}
-
-#[macro_export]
-macro_rules! trace_dbg {
-  ($msg:expr $(,$val:expr)*) => {
-    if log_enabled!(log::Level::Trace) {
-      #[allow(unused_mut)]
-      let mut buf = format!("{}", &$msg);
-      $( write!(&mut buf, " {}={:?}", stringify!($val), &$val).unwrap(); )*
-      trace!("{}", buf);
-    }
-  }
-
-}
-
-#[macro_export]
-macro_rules! want_failed_internal {
-  { $variant:ident($binding:pat) = $input:expr, $x:expr, $($d:expr),* } => {
-    InternalLogicError::new({
-      #[allow(unused_mut)]
-      let mut s = format!("wanted {}({}) = {}, but got {:?}",
-                         stringify!($variant), stringify!($binding),
-                          stringify!($input), $x);
-      $(
-        write!(&mut s, " {}={:?}", stringify!($d), &$d).unwrap();
-      )*
-      s
-    }).tolerate()
-  }
-}
-
-#[macro_export]
-macro_rules! want {
-  { $variant:ident = $input:expr,
-    ? $($d:expr),*
-  } => (
-    match $input {
-      $variant(y) => Some(y),
-      x => {
-        want_failed_internal!{ $variant(_)=$input, x, $($d),* }
-        None
-      },
-    }
-  );
-  { $variant:ident = $input:expr } => {
-    want!( $variant = $input,
-           ? )
-  };
-}
-
-#[macro_export]
-macro_rules! wants {
-  { $($d:tt)* } => { want!(Some = $($d)*) }
-}
-#[macro_export]
-macro_rules! wantok {
-  { $($d:tt)* } => { want!(Ok = $($d)*) }
-}
-
-#[macro_export]
-macro_rules! want_let {
-  { $($variant:ident)::+($binding:pat) = $input:expr;
-    else ? $($d:expr),*; $($otherwise:tt)*
-  } => {
-    let $binding = match $input {
-      $($variant(y))::+ => y,
-      x => {
-        want_failed_internal!{
-          $($variant)::+($binding)=$input, x, $($d),*
-        }
-        $($otherwise)*
-      },
-    };
-  };
-  { $($variant:ident)::+($binding:pat) = $input:expr;
-    else $($otherwise:tt)*
-  } => {
-    want_let!{ $($variant($binding))::+ = $input; else ?; $($otherwise)* }
-  };
-}
-
-/// Allows the use of serde for a compat struct
-///
-/// Ideally we would have
-/// ```rust ignore
-/// #[derive(Deserialize)]
-/// #[serde(try_from=Compat)]
-/// struct Main { /* new definition */ }
-///
-/// #[derive(Deserialize)]
-/// #[serde(untagged)]
-/// enum Compat { V1(Main), V2(Old) }
-///
-/// #[derive(Deserialize)]
-/// struct Old { /* old version we still want to read */ }
-///
-/// impl TryFrom<Compat> for Main { /* ... */ }
-/// ```
-///
-/// But the impl for `Compat` ends up honouring the `try_from` on `Main`
-/// so is recursive.  We solve that abusing serde's remote feature.
-///
-/// For an example, see `IOccultIlk`.
-///
-/// The name of the main structure must be passed twice, once as an
-/// identifier and once as a literal, because `stringify!` doesn't work
-/// in the serde attribute.
-#[macro_export]
-macro_rules! serde_with_compat { {
-  [ $( #[ $($attrs:meta)* ] )* ] [ $vis:vis ] [ $($intro:tt)* ]
-    $main:ident=$main_s:literal $new:ident $compat_s:literal
-  [ $($body:tt)* ]
-} => {
-  $(#[ $($attrs)* ])* 
-  #[serde(try_from=$compat_s)]
-  $vis $($intro)* $main $($body)*
-
-  #[allow(non_camel_case_types)]
-  $(#[ $($attrs)* ])* 
-  #[serde(remote=$main_s)]
-  $($intro)* $new $($body)*
-} }
+//========== .insert() and .remove() on various Entry ==========
 
 macro_rules! entry_define_insert_remove {
   { $name:ident, $name_mod:ident, $entry:path, $into_key:ident } =>
@@ -484,146 +377,35 @@ entry_define_insert_remove!{
   key
 }
 
-#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
-#[derive(From,Into)]
-#[derive(Serialize, Deserialize)]
-#[serde(into="Duration", try_from="Duration")]
-pub struct FutureInstant(pub Instant);
-
-impl Into<Duration> for FutureInstant {
-  fn into(self) -> Duration {
-    let now = config().global_clock.now();
-    Instant::from(self).checked_duration_since(now).unwrap_or_default()
-  }
-}
-
-#[derive(Error,Debug)]
-#[error("Duration (eg during load) implies out-of-range FutureInstant")]
-pub struct FutureInstantOutOfRange;
-
-impl TryFrom<Duration> for FutureInstant {
-  type Error = FutureInstantOutOfRange;
-  #[throws(FutureInstantOutOfRange)]
-  fn try_from(duration: Duration) -> FutureInstant {
-    let now = config().global_clock.now();
-    now.checked_add(duration).ok_or(FutureInstantOutOfRange)?.into()
-  }
-}      
-
-
-#[derive(Debug,Copy,Clone)]
-pub struct DigestRead<D: Digest, R: Read> {
-  d: D,
-  r: R,
-}
-
-impl<D: Digest, R: Read> DigestRead<D, R> {
-  pub fn new(r: R) -> Self { DigestRead { r, d: D::new() } }
-  pub fn into_inner(self) -> (D, R) { (self.d, self.r) }
-  pub fn finish(self) -> digest::Output<D> {
-    self.d.finalize()
-  }
-}
-
-impl<D: Digest, R: Read> Read for DigestRead<D, R> {
-  #[throws(io::Error)]
-  fn read(&mut self, buf: &mut [u8]) -> usize {
-    let count = self.r.read(buf)?;
-    self.d.update(&buf[0..count]);
-    count
-  }
-}
-
-#[test]
-#[cfg(not(miri))]
-fn test_digest_read() {
-  let ibuffer = b"abc";
-  let exp = Sha512_256::digest(&ibuffer[..]);
-  let inner = &ibuffer[..];
-  let mut dr = DigestRead::<Sha512_256,_>::new(inner);
-  let mut obuffer = [0;4];
-  assert_eq!( dr.read(&mut obuffer).unwrap(), 3 );
-  assert_eq!( &obuffer, b"abc\0" );
-  let got = dr.finish();
-  assert_eq!( got, exp );
-}
-
-#[derive(Debug,Copy,Clone)]
-pub struct DigestWrite<D: Digest, W: Write> {
-  d: D,
-  w: W,
-}
-
-impl<D: Digest, W: Write> DigestWrite<D, W> {
-  pub fn new(w: W) -> Self { DigestWrite { w, d: D::new() } }
-  pub fn into_inner(self) -> (D, W) { (self.d, self.w) }
-  pub fn finish(self) -> (digest::Output<D>, W) {
-    (self.d.finalize(), self.w)
-  }
-}
-impl<D: Digest> DigestWrite<D, io::Sink> {
-  pub fn sink() -> Self { DigestWrite::new(io::sink()) }
-
-  #[throws(io::Error)]
-  pub fn of<R>(r: &mut R) -> digest::Output<D> where R: Read {
-    let mut dw = DigestWrite::<D,_>::sink();
-    io::copy(r, &mut dw)?;
-    dw.finish().0
-  }
-}
-
-impl<D: Digest, W: Write> Write for DigestWrite<D, W> {
-  #[throws(io::Error)]
-  fn write(&mut self, buf: &[u8]) -> usize {
-    let count = self.w.write(buf)?;
-    self.d.update(&buf[0..count]);
-    count
-  }
-  #[throws(io::Error)]
-  fn flush(&mut self) { self.w.flush()? }
-}
-
-#[test]
-#[cfg(not(miri))]
-fn test_digest_write() {
-  let ibuffer = b"xyz";
-  let exp = Sha512_256::digest(&ibuffer[..]);
-  let mut obuffer = [0;4];
-  let inner = &mut obuffer[..];
-  let mut dw = bundles::DigestWrite::new(inner);
-  assert_eq!( dw.write(&ibuffer[..]).unwrap(), 3);
-  let (got, recov) = dw.finish();
-  assert_eq!( recov, b"\0" );
-  assert_eq!( got, exp );
-  assert_eq!( &obuffer, b"xyz\0" );
-}
-
-#[ext(pub, name=SeekExt)]
-impl<T: io::Seek> T {
-  #[throws(io::Error)]
-  fn rewind(&mut self) { self.seek(io::SeekFrom::Start(0))? }
-}
-
-#[ext(pub)]
-impl<T> Vec<T> {
-  fn get_or_extend_with<F>(&mut self, i: usize, f: F) -> &mut T
-  where F: FnMut() -> T {
-    if self.get(i).is_none() {
-      self.resize_with(i+1, f);
-    }
-    &mut self[i]
+//========== FutureInstant ==========
+
+#[derive(Debug,Copy,Clone,Eq,PartialEq,Ord,PartialOrd)]
+#[derive(From,Into)]
+#[derive(Serialize, Deserialize)]
+#[serde(into="Duration", try_from="Duration")]
+pub struct FutureInstant(pub Instant);
+
+impl Into<Duration> for FutureInstant {
+  fn into(self) -> Duration {
+    let now = config().global_clock.now();
+    Instant::from(self).checked_duration_since(now).unwrap_or_default()
   }
 }
 
-#[ext(pub)]
-impl<I,T> IndexVec<I,T> where I: index_vec::Idx {
-  fn get_or_extend_with<F>(&mut self, i: I, f: F) -> &mut T
-  where F: FnMut() -> T {
-    self.raw.get_or_extend_with(i.index(), f)
+#[derive(Error,Debug)]
+#[error("Duration (eg during load) implies out-of-range FutureInstant")]
+pub struct FutureInstantOutOfRange;
+
+impl TryFrom<Duration> for FutureInstant {
+  type Error = FutureInstantOutOfRange;
+  #[throws(FutureInstantOutOfRange)]
+  fn try_from(duration: Duration) -> FutureInstant {
+    let now = config().global_clock.now();
+    now.checked_add(duration).ok_or(FutureInstantOutOfRange)?.into()
   }
-}
+}      
 
-pub fn is_default<T: ConstDefault + Eq>(t: &T) -> bool { t == &T::DEFAULT }
+//========== Error handling ==========
 
 #[derive(Debug)]
 pub struct AnyhowDisplay<'a>(pub &'a anyhow::Error);
@@ -681,24 +463,31 @@ impl anyhow::Error {
   }
 }
 
-#[throws(Either<io::Error, io::Error>)]
-pub fn io_copy_interactive<R,W>(read: &mut BufReader<R>, write: &mut W)
-where R: Read, W: Write {
-  loop {
-    let buf = read.fill_buf().map_err(Either::Left)?;
-    if buf.len() == 0 { break }
+//========== IO - File::close ==========
 
-    let did = (||{
-      let did = write.write(buf)?;
-      if did == 0 { throw!(ErrorKind::WriteZero) }
-      Ok::<_,io::Error>(did)
-    })().map_err(Either::Right)?;
-        
-    read.consume(did);
-    write.flush().map_err(Either::Right)?;
+// https://github.com/rust-lang/rust/issues/32255 :-(
+
+#[ext(pub, name=LocalFileExt, supertraits=Sized)]
+impl fs::File {
+  #[throws(io::Error)]
+  fn close(self) {
+    let r = unsafe {
+      let fd = self.into_raw_fd();
+      libc::close(fd)
+    };
+    if r == 0 {
+      ()
+    } else if r == -1 {
+      throw!(io::Error::last_os_error())
+    } else {
+      panic!("close(2) returned {}", r)
+    }
   }
 }
 
+
+//========== IO - SigPipeWriter and RawStdout/CookedStdout ==========
+
 pub struct SigPipeWriter<W>(pub W);
 
 impl<W:Write> SigPipeWriter<W> {
@@ -762,20 +551,7 @@ impl Drop for CookedStdout {
   fn drop(&mut self) { self.must_flush() }
 }
 
-pub trait IpAddress: Debug {
-  fn with_port(&self, port: u16) -> SocketAddr;
-}
-
-impl<A> IpAddress for A where A: Into<IpAddr> + Debug + Clone {
-  fn with_port(&self, port: u16) -> SocketAddr {
-    match (self.clone().into(), port)
-      .to_socket_addrs()
-      .map(|i| i.at_most_one()) {
-        Ok(Ok(Some(addr))) => addr,
-        x => panic!("{:?},{} gave {:?}", self, port, x),
-      }
-  }
-}
+//========== hex ==========
 
 #[throws(fmt::Error)]
 pub fn fmt_hex(f: &mut Formatter, buf: &[u8]) {
@@ -832,3 +608,178 @@ fn test_parse_hex(){
   assert_eq!( parse_fixed_hex("1"   ), None::<[_;1]>      );
   assert_eq!( parse_fixed_hex("xy"  ), None::<[_;1]>      );
 }
+
+//========== matches_doesnot ==========
+
+#[macro_export] // <- otherwise bogus warning `unused_macros`
+macro_rules! matches_doesnot_yn2bool {
+  (=) => (true);
+  (!) => (false);
+}
+
+#[macro_export]
+macro_rules! matches_doesnot {
+  ($v:expr,
+   $(
+     $yn:tt $p:pat
+   ),* $(,)?
+  ) => {
+    match $v {
+      $(
+        $p => $crate::matches_doesnot_yn2bool!($yn),
+      )*
+    }
+  }
+}
+
+#[test]
+fn matches_doesnot_test() {
+  assert!(
+    matches_doesnot!(
+      Some(42),
+      = Some(_),
+      ! None
+    )
+  );
+  assert!(
+    matches_doesnot!(
+      Some(42),
+      ! None,
+      ! Some(3),
+      = Some(_),
+    )
+  );
+  assert!(
+    matches_doesnot!(
+      Some(1),
+      = Some(1) | Some(2),
+      ! Some(_) | None
+    )
+  );
+  assert!(
+    ! matches_doesnot!(
+      Some(1),
+      ! Some(1) | Some(2),
+      = Some(_) | None
+    )
+  );
+}
+
+//========== want* macros ==========
+
+#[macro_export]
+macro_rules! want_failed_internal {
+  { $variant:ident($binding:pat) = $input:expr, $x:expr, $($d:expr),* } => {
+    InternalLogicError::new({
+      #[allow(unused_mut)]
+      let mut s = format!("wanted {}({}) = {}, but got {:?}",
+                         stringify!($variant), stringify!($binding),
+                          stringify!($input), $x);
+      $(
+        write!(&mut s, " {}={:?}", stringify!($d), &$d).unwrap();
+      )*
+      s
+    }).tolerate()
+  }
+}
+
+#[macro_export]
+macro_rules! want {
+  { $variant:ident = $input:expr,
+    ? $($d:expr),*
+  } => (
+    match $input {
+      $variant(y) => Some(y),
+      x => {
+        want_failed_internal!{ $variant(_)=$input, x, $($d),* }
+        None
+      },
+    }
+  );
+  { $variant:ident = $input:expr } => {
+    want!( $variant = $input,
+           ? )
+  };
+}
+
+#[macro_export]
+macro_rules! wants {
+  { $($d:tt)* } => { want!(Some = $($d)*) }
+}
+#[macro_export]
+macro_rules! wantok {
+  { $($d:tt)* } => { want!(Ok = $($d)*) }
+}
+
+#[macro_export]
+macro_rules! want_let {
+  { $($variant:ident)::+($binding:pat) = $input:expr;
+    else ? $($d:expr),*; $($otherwise:tt)*
+  } => {
+    let $binding = match $input {
+      $($variant(y))::+ => y,
+      x => {
+        want_failed_internal!{
+          $($variant)::+($binding)=$input, x, $($d),*
+        }
+        $($otherwise)*
+      },
+    };
+  };
+  { $($variant:ident)::+($binding:pat) = $input:expr;
+    else $($otherwise:tt)*
+  } => {
+    want_let!{ $($variant($binding))::+ = $input; else ?; $($otherwise)* }
+  };
+}
+
+//========== miscellaneous macros ==========
+
+#[macro_export]
+macro_rules! trace_dbg {
+  ($msg:expr $(,$val:expr)*) => {
+    if log_enabled!(log::Level::Trace) {
+      #[allow(unused_mut)]
+      let mut buf = format!("{}", &$msg);
+      $( write!(&mut buf, " {}={:?}", stringify!($val), &$val).unwrap(); )*
+      trace!("{}", buf);
+    }
+  }
+
+}
+
+#[macro_export]
+macro_rules! ensure_eq {
+  ($v1:expr, $v2:expr) => {
+    ({
+      let v1 = &$v1;
+      let v2 = &$v2;
+      if v1 != v2 {
+        Err(anyhow!("ensure_eq failed: {} != {}: {:?} != {:?}",
+                    stringify!($v1), stringify!($v2),
+                    v1, v2))
+      } else {
+        Ok(())
+      }
+    }?)
+  }
+}
+
+#[macro_export]
+macro_rules! deref_to_field {
+  {$({ $($gen:tt)* })? $outer:ty, $inner:ty, $($field:tt)*} => {
+    impl $(< $($gen)* >)? Deref for $outer {
+      type Target = $inner;
+      fn deref(&self) -> &$inner { &self.$($field)* }
+    }
+  }
+}
+#[macro_export]
+macro_rules! deref_to_field_mut {
+  {$({ $($gen:tt)* })? $outer:ty, $inner:ty, $($field:tt)*} => {
+    deref_to_field!{ $({ $($gen)* })? $outer, $inner, $($field)*}
+    impl $(< $($gen)* >)? DerefMut for $outer {
+      fn deref_mut(&mut self) -> &mut $inner { &mut self.$($field)* }
+    }
+  }
+}