keys.scala, etc.: Make merging public keys have a progress bar.
[tripe-android] / peers.scala
index 931c8f4..c487a9e 100644 (file)
@@ -1,5 +1,32 @@
+/* -*-scala-*-
+ *
+ * The database of known peers
+ *
+ * (c) 2018 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Trivial IP Encryption (TrIPE) Android app.
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
 package uk.org.distorted.tripe; package object peers {
 
+/*----- Imports -----------------------------------------------------------*/
+
 import java.io.{BufferedReader, File, FileReader, Reader};
 import java.net.{InetAddress, Inet4Address, Inet6Address,
                 UnknownHostException};
@@ -9,56 +36,31 @@ import scala.concurrent.Channel;
 import scala.util.control.Breaks;
 import scala.util.matching.Regex;
 
-val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
-val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
-val RX_ASSGN = """(?x) ^
+/*----- Handy regular expressions -----------------------------------------*/
+
+private final val RX_COMMENT = """(?x) ^ \s* (?: [;\#] .* )? $""".r;
+private final val RX_GRPHDR = """(?x) ^ \s* \[ (.*) \] \s* $""".r;
+private final val RX_ASSGN = """(?x) ^
        ([^\s:=] (?: [^:=]* [^\s:=])?)
        \s* [:=] \s*
        (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
        (?: \s+ (?: [;\#].*)? )? $""".r;
-val RX_CONT = """(?x) ^ \s+
+private final val RX_CONT = """(?x) ^ \s+
        (| [^\s\#;]\S* (?: \s+ [^\s\#;]\S*)*)
        (?: \s+ (?: [;\#].*)? )? $""".r;
-val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
-val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
-val RX_PARENT = """(?x) [^\s,]+""".r
-
-def with_cleaner[T](body: Cleaner => T): T = {
-  val cleaner = new Cleaner;
-  try { body(cleaner) }
-  finally { cleaner.cleanup(); }
-}
-
-class Cleaner {
-  var cleanups: List[() => Unit] = Nil;
-  def apply(cleanup: => Unit) { cleanups +:= { () => cleanup; } }
-  def cleanup() { cleanups foreach { _() } }
-}
+private final val RX_REF = """(?x) \$ \( ([^)]+) \)""".r;
+private final val RX_RESOLVE = """(?x) \$ ([46*]*) \[ ([^\]]+) \]""".r;
+private final val RX_PARENT = """(?x) [^\s,]+""".r
 
-def lines(r: Reader) = new Traversable[String] {
-  val in: BufferedReader = new BufferedReader(r);
-  override def foreach[T](f: String => T) {
-    while (true) in.readLine match {
-      case null => return;
-      case line => f(line);
-    }
-  }
-}
+/*----- Name resolution ---------------------------------------------------*/
 
-def thread(name: String, run: Boolean = true, daemon: Boolean = true)
-         (body: => Unit): Thread = {
-  val t = new Thread(new Runnable { override def run() { body } }, name);
-  t.setDaemon(daemon);
-  if (run) t.start();
-  t
+private object BulkResolver {
+  private val BREAK = new Breaks;
 }
 
-object BulkResolver {
-  val BREAK = new Breaks;
-}
+private class BulkResolver(val nthreads: Int = 8) {
+  import BulkResolver.BREAK.{breakable, break};
 
-class BulkResolver(val nthreads: Int = 8) {
-  import BulkResolver.BREAK._;
   class Host(val name: String) {
     var a4, a6: Seq[InetAddress] = Seq.empty;
 
@@ -87,20 +89,19 @@ class BulkResolver(val nthreads: Int = 8) {
       }
     }
   }
+
   val ch = new Channel[Host];
   val map = HashMap[String, Host]();
   var preparing = true;
 
   val workers = Array.tabulate(nthreads) { i =>
     thread(s"resolver worker #$i") {
-      breakable {
-       while (true) {
-         val host = ch.read; if (host == null) break;
+      loopUnit { exit =>
+       val host = ch.read; if (host == null) exit;
 println(s";; ${Thread.currentThread.getName} resolving `${host.name}'");
-         try {
-           for (a <- InetAddress.getAllByName(host.name)) host.addaddr(a);
-         } catch { case e: UnknownHostException => () }
-       }
+       try {
+         for (a <- InetAddress.getAllByName(host.name)) host.addaddr(a);
+       } catch { case e: UnknownHostException => () }
       }
 println(s";; ${Thread.currentThread.getName} done'");
       ch.write(null);
@@ -128,6 +129,8 @@ println(s";; prepare host `$name'");
     map(name).get(flags);
 }
 
+/*----- The peer configuration --------------------------------------------*/
+
 def fmtpath(path: Seq[String]) =
   path.reverse map { i => s"`$i'" } mkString " -> ";
 
@@ -135,10 +138,12 @@ class ConfigSyntaxError(val file: File, val lno: Int, val msg: String)
        extends Exception {
   override def getMessage(): String = s"$file:$lno: $msg";
 }
+
 class MissingConfigSection(val sect: String) extends Exception {
   override def getMessage(): String =
     s"missing configuration section `$sect'";
 }
+
 class MissingConfigItem(val sect: String, val key: String,
                        val path: Seq[(String)]) extends Exception {
   override def getMessage(): String = {
@@ -147,6 +152,7 @@ class MissingConfigItem(val sect: String, val key: String,
     else msg + s" (wanted while expanding ${fmtpath(path)})"
   }
 }
+
 class AmbiguousConfig(val key: String,
                      val v0: String, val p0: Seq[String],
                      val v1: String, val p1: Seq[String])
@@ -160,32 +166,33 @@ class ConfigCycle(val key: String, path: Seq[String]) extends Exception {
   override def getMessage(): String =
     s"found a cycle ${fmtpath(path)} looking up key `$key'";
 }
+
 class NoHostAddresses(val sect: String, val key: String, val host: String)
        extends Exception {
   override def getMessage(): String =
     s"no addresses found for `$host' (key `$key' in section `$sect')";
 }
 
-object Config {
-  sealed abstract class ConfigCacheEntry;
-  case object StillLooking extends ConfigCacheEntry;
-  case object NotFound extends ConfigCacheEntry;
-  case class Found(value: String, path: Seq[String])
-    extends ConfigCacheEntry;
-}
+private sealed abstract class ConfigCacheEntry;
+private case object StillLooking extends ConfigCacheEntry;
+private case object NotFound extends ConfigCacheEntry;
+private case class Found(value: String, path: Seq[String])
+       extends ConfigCacheEntry;
 
 class Config { conf =>
-  import Config._;
-  class Section(val name: String) {
-    val itemmap = HashMap[String, String]();
-    val cache = HashMap[String, ConfigCacheEntry]();
+
+  class Section private(val name: String) {
+    private val itemmap = HashMap[String, String]();
+    private[this] val cache = HashMap[String, ConfigCacheEntry]();
+
     override def toString: String = s"${getClass.getName}($name)";
+
     def parents: Seq[Section] =
       (itemmap.get("@inherit")
        map { pp => (RX_PARENT.findAllIn(pp) map { conf.section _ }).toList }
        getOrElse Nil);
 
-    def get_internal(key: String, path: Seq[String] = Nil):
+    private def get_internal(key: String, path: Seq[String] = Nil):
              Option[(String, Seq[String])] = {
       val incpath = name +: path;
 
@@ -230,8 +237,8 @@ class Config { conf =>
       expand(key, v0, resolve, path)
     }
 
-    def expand(key: String, value: String, resolve: Boolean,
-              path: Seq[String]): String = {
+    private def expand(key: String, value: String, resolve: Boolean,
+                      path: Seq[String]): String = {
       val v1 = RX_REF.replaceAllIn(value, { m =>
        Regex.quoteReplacement(get(m.group(1), resolve, path))
       });
@@ -265,16 +272,17 @@ class Config { conf =>
       b.result
     }
   }
-  val sectmap = new HashMap[String, Section];
+
+  private[this] val sectmap = new HashMap[String, Section];
   def sections: Iterator[Section] = sectmap.values.iterator;
   def section(name: String): Section =
     sectmap.getOrElse(name, throw new MissingConfigSection(name));
 
-  val resolver = new BulkResolver;
+  private[this] val resolver = new BulkResolver;
 
-  def parseFile(path: File): this.type = {
+  private[this] def parseFile(path: File): this.type = {
 println(s";; parse ${path.getPath}");
-    with_cleaner { clean =>
+    withCleaner { clean =>
       val in = new FileReader(path); clean { in.close(); }
 
       val lno = 1;
@@ -315,6 +323,7 @@ println(s";; in `${sect.name}', set `$key' to `${b.result}'");
     }
     this
   }
+
   def parse(path: File): this.type = {
     if (!path.isDirectory) parseFile(path);
     else for {
@@ -356,4 +365,6 @@ println(s";;        resolving in key `$key'...");
   }
 }
 
+/*----- That's all, folks -------------------------------------------------*/
+
 }