+/* -*-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};
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;
}
}
}
+
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);
map(name).get(flags);
}
+/*----- The peer configuration --------------------------------------------*/
+
def fmtpath(path: Seq[String]) =
path.reverse map { i => s"`$i'" } mkString " -> ";
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 = {
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])
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;
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))
});
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;
}
this
}
+
def parse(path: File): this.type = {
if (!path.isDirectory) parseFile(path);
else for {
}
}
+/*----- That's all, folks -------------------------------------------------*/
+
}