X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/8eabb4ff13562f3550499ee599297f7e97fa8754..HEAD:/peers.scala diff --git a/peers.scala b/peers.scala index 931c8f4..c487a9e 100644 --- a/peers.scala +++ b/peers.scala @@ -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 . + */ + 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 -------------------------------------------------*/ + }