[tor-commits] [metrics-web/master] Remove consensus-health checker.

karsten at torproject.org karsten at torproject.org
Tue Dec 6 06:28:22 UTC 2011


commit 8af25f86effec8bb9b7a577310cc569adb2879cc
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Mon Dec 5 20:23:44 2011 +0100

    Remove consensus-health checker.
---
 build.xml                                        |   11 -
 src/org/torproject/chc/Checker.java              |  285 -------
 src/org/torproject/chc/Downloader.java           |  251 -------
 src/org/torproject/chc/Main.java                 |   46 --
 src/org/torproject/chc/MetricsWebsiteReport.java |  875 ----------------------
 src/org/torproject/chc/Parser.java               |  167 ----
 src/org/torproject/chc/Report.java               |   23 -
 src/org/torproject/chc/Status.java               |  176 -----
 src/org/torproject/chc/StatusEntry.java          |   48 --
 src/org/torproject/chc/StatusFileReport.java     |  197 -----
 src/org/torproject/chc/Warning.java              |   46 --
 11 files changed, 0 insertions(+), 2125 deletions(-)

diff --git a/build.xml b/build.xml
index 570c973..e148a13 100644
--- a/build.xml
+++ b/build.xml
@@ -21,7 +21,6 @@
     <copy file="${contextxmltemplate}" tofile="${contextxml}"/>
     <copy file="config.template" tofile="config"/>
     <mkdir dir="${classes}"/>
-    <mkdir dir="website"/>
   </target>
 
   <!-- Compile all servlets and plain Java classes. -->
@@ -60,16 +59,6 @@
     </java>
   </target>
 
-  <!-- Run consensus-health checker. -->
-  <target name="chc" depends="compile">
-    <java fork="true"
-          maxmemory="512m"
-          classname="org.torproject.chc.Main"
-          error="chc-stderr.txt">
-      <classpath refid="classpath"/>
-    </java>
-  </target>
-
   <!-- Prepare data for being displayed on the website. -->
   <target name="run" depends="compile">
     <java fork="true"
diff --git a/src/org/torproject/chc/Checker.java b/src/org/torproject/chc/Checker.java
deleted file mode 100644
index 02c70b7..0000000
--- a/src/org/torproject/chc/Checker.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.io.*;
-import java.text.*;
-import java.util.*;
-
-/* Check a given consensus and votes for irregularities and write results
- * to a warnings map consisting of warning type and details. */
-public class Checker {
-
-  /* Warning messages consisting of type and details. */
-  private SortedMap<Warning, String> warnings =
-      new TreeMap<Warning, String>();
-
-  public SortedMap<Warning, String> getWarnings() {
-    return this.warnings;
-  }
-
-  /* Date-time format to format timestamps. */
-  private static SimpleDateFormat dateTimeFormat;
-  static {
-    dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
-
-  /* Downloaded consensus and corresponding votes for processing. */
-  private SortedMap<String, Status> downloadedConsensuses;
-  private Status downloadedConsensus;
-  private SortedSet<Status> downloadedVotes;
-  public void processDownloadedConsensuses(
-      SortedMap<String, Status> downloadedConsensuses) {
-    this.downloadedConsensuses = downloadedConsensuses;
-    this.findMostRecentConsensus();
-    this.checkMissingConsensuses();
-    this.checkAllConsensusesFresh();
-    if (this.downloadedConsensus != null) {
-      if (this.isConsensusFresh(this.downloadedConsensus)) {
-        this.checkConsensusMethods();
-        this.checkRecommendedVersions();
-        this.checkConsensusParameters();
-        this.checkAuthorityKeys();
-        this.checkMissingVotes();
-        this.checkBandwidthScanners();
-      }
-    } else {
-      this.warnings.put(Warning.NoConsensusKnown, "");
-    }
-  }
-
-  /* Find most recent consensus and corresponding votes. */
-  private void findMostRecentConsensus() {
-    long mostRecentValidAfterMillis = -1L;
-    for (Status downloadedConsensus : downloadedConsensuses.values()) {
-      if (downloadedConsensus.getValidAfterMillis() >
-          mostRecentValidAfterMillis) {
-        this.downloadedConsensus = downloadedConsensus;
-        mostRecentValidAfterMillis =
-            downloadedConsensus.getValidAfterMillis();
-      }
-    }
-    if (this.downloadedConsensus != null) {
-      this.downloadedVotes = this.downloadedConsensus.getVotes();
-    }
-  }
-
-  /* Check if any directory authority didn't tell us a consensus. */
-  private void checkMissingConsensuses() {
-    SortedSet<String> missingConsensuses = new TreeSet<String>(
-        Arrays.asList(("gabelmoo,tor26,ides,maatuska,dannenberg,urras,"
-        + "moria1,dizum").split(",")));
-    missingConsensuses.removeAll(this.downloadedConsensuses.keySet());
-    if (!missingConsensuses.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String nickname : missingConsensuses) {
-        sb.append(", " + nickname);
-      }
-      this.warnings.put(Warning.ConsensusDownloadTimeout,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if all consensuses are fresh. */
-  private void checkAllConsensusesFresh() {
-    long fresh = System.currentTimeMillis() - 60L * 60L * 1000L;
-    SortedSet<String> nonFresh = new TreeSet<String>();
-    for (Map.Entry<String, Status> e : downloadedConsensuses.entrySet()) {
-      String nickname = e.getKey();
-      Status downloadedConsensus = e.getValue();
-      if (downloadedConsensus.getValidAfterMillis() < fresh) {
-        nonFresh.add(nickname);
-      }
-    }
-    if (!nonFresh.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String nickname : nonFresh) {
-        sb.append(", " + nickname);
-      }
-      this.warnings.put(Warning.ConsensusNotFresh,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if the most recent consensus is older than 1 hour. */
-  private boolean isConsensusFresh(Status consensus) {
-    return (consensus.getValidAfterMillis() >=
-        System.currentTimeMillis() - 60L * 60L * 1000L);
-  }
-
-  /* Check supported consensus methods of all votes. */
-  private void checkConsensusMethods() {
-    SortedSet<String> dirs = new TreeSet<String>();
-    for (Status vote : this.downloadedVotes) {
-      if (!vote.getConsensusMethods().contains(
-          this.downloadedConsensus.getConsensusMethods().last())) {
-        dirs.add(vote.getNickname());
-      }
-    }
-    if (!dirs.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String dir : dirs) {
-        sb.append(", " + dir);
-      }
-      this.warnings.put(Warning.ConsensusMethodNotSupported,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if the recommended versions in a vote are different from the
-   * recommended versions in the consensus. */
-  private void checkRecommendedVersions() {
-    SortedSet<String> unrecommendedClientVersions = new TreeSet<String>(),
-        unrecommendedServerVersions = new TreeSet<String>();
-    for (Status vote : this.downloadedVotes) {
-      if (vote.getRecommendedClientVersions() != null &&
-          !downloadedConsensus.getRecommendedClientVersions().equals(
-          vote.getRecommendedClientVersions())) {
-        StringBuilder message = new StringBuilder();
-        message.append(vote.getNickname());
-        for (String version : vote.getRecommendedClientVersions()) {
-          message.append(" " + version);
-        }
-        unrecommendedClientVersions.add(message.toString());
-      }
-      if (vote.getRecommendedServerVersions() != null &&
-          !downloadedConsensus.getRecommendedServerVersions().equals(
-          vote.getRecommendedServerVersions())) {
-        StringBuilder message = new StringBuilder();
-        message.append(vote.getNickname());
-        for (String version : vote.getRecommendedServerVersions()) {
-          message.append(" " + version);
-        }
-        unrecommendedServerVersions.add(message.toString());
-      }
-    }
-    if (!unrecommendedServerVersions.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String dir : unrecommendedServerVersions) {
-        sb.append(", " + dir);
-      }
-      this.warnings.put(Warning.DifferentRecommendedServerVersions,
-          sb.toString().substring(2));
-    }
-    if (!unrecommendedClientVersions.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String dir : unrecommendedClientVersions) {
-        sb.append(", " + dir);
-      }
-      this.warnings.put(Warning.DifferentRecommendedClientVersions,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if a vote contains conflicting or invalid consensus
-   * parameters. */
-  private void checkConsensusParameters() {
-    Set<String> validParameters = new HashSet<String>(Arrays.asList(
-        ("circwindow,CircuitPriorityHalflifeMsec,refuseunknownexits,"
-        + "cbtdisabled,cbtnummodes,cbtrecentcount,cbtmaxtimeouts,"
-        + "cbtmincircs,cbtquantile,cbtclosequantile,cbttestfreq,"
-        + "cbtmintimeout,cbtinitialtimeout,perconnbwburst,perconnbwrate").
-        split(",")));
-    SortedSet<String> conflicts = new TreeSet<String>();
-    for (Status vote : this.downloadedVotes) {
-      Map<String, String> voteConsensusParams =
-          vote.getConsensusParams();
-      boolean conflictOrInvalid = false;
-      if (voteConsensusParams != null) {
-        for (Map.Entry<String, String> e :
-            voteConsensusParams.entrySet()) {
-          if (!downloadedConsensus.getConsensusParams().containsKey(
-              e.getKey()) ||
-              !downloadedConsensus.getConsensusParams().get(e.getKey()).
-              equals(e.getValue()) ||
-              (!validParameters.contains(e.getKey()) &&
-              !e.getKey().startsWith("bwauth"))) {
-            StringBuilder message = new StringBuilder();
-            message.append(vote.getNickname());
-            for (Map.Entry<String, String> p :
-                voteConsensusParams.entrySet()) {
-              message.append(" " + p.getKey() + "=" + p.getValue());
-            }
-            conflicts.add(message.toString());
-            break;
-          }
-        }
-      }
-    }
-    if (!conflicts.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String dir : conflicts) {
-        sb.append(", " + dir);
-      }
-      this.warnings.put(Warning.ConflictingOrInvalidConsensusParams,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check whether any of the authority keys expire in the next 14
-   * days. */
-  private void checkAuthorityKeys() {
-    SortedMap<String, String> expiringCertificates =
-        new TreeMap<String, String>();
-    long now = System.currentTimeMillis();
-    for (Status vote : this.downloadedVotes) {
-      long voteDirKeyExpiresMillis = vote.getDirKeyExpiresMillis();
-      if (voteDirKeyExpiresMillis - 14L * 24L * 60L * 60L * 1000L < now) {
-        expiringCertificates.put(vote.getNickname(),
-            dateTimeFormat.format(voteDirKeyExpiresMillis));
-      }
-    }
-    if (!expiringCertificates.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (Map.Entry<String, String> e :
-          expiringCertificates.entrySet()) {
-        String dir = e.getKey();
-        String timestamp = e.getValue();
-        sb.append(", " + dir + " " + timestamp);
-      }
-      this.warnings.put(Warning.CertificateExpiresSoon,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if any votes are missing. */
-  private void checkMissingVotes() {
-    SortedSet<String> knownAuthorities = new TreeSet<String>(
-        Arrays.asList(("dannenberg,dizum,gabelmoo,ides,maatuska,moria1,"
-        + "tor26,urras").split(",")));
-    SortedSet<String> missingVotes =
-        new TreeSet<String>(knownAuthorities);
-    for (Status vote : this.downloadedVotes) {
-      missingVotes.remove(vote.getNickname());
-    }
-    if (!missingVotes.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String missingDir : missingVotes) {
-        sb.append(", " + missingDir);
-      }
-      this.warnings.put(Warning.VotesMissing,
-          sb.toString().substring(2));
-    }
-  }
-
-  /* Check if any bandwidth scanner results are missing. */
-  private void checkBandwidthScanners() {
-    SortedSet<String> missingBandwidthScanners = new TreeSet<String>(
-        Arrays.asList("ides,urras,moria1,gabelmoo,maatuska".split(",")));
-    for (Status vote : this.downloadedVotes) {
-      if (vote.getBandwidthWeights() > 0) {
-        missingBandwidthScanners.remove(vote.getNickname());
-      }
-    }
-    if (!missingBandwidthScanners.isEmpty()) {
-      StringBuilder sb = new StringBuilder();
-      for (String dir : missingBandwidthScanners) {
-        sb.append(", " + dir);
-      }
-      this.warnings.put(Warning.BandwidthScannerResultsMissing,
-          sb.toString().substring(2));
-    }
-  }
-}
-
diff --git a/src/org/torproject/chc/Downloader.java b/src/org/torproject/chc/Downloader.java
deleted file mode 100644
index e56d20d..0000000
--- a/src/org/torproject/chc/Downloader.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.io.*;
-import java.net.*;
-import java.text.*;
-import java.util.*;
-import java.util.zip.*;
-
-/* Download the latest network status consensus and corresponding
- * votes. */
-public class Downloader {
-
-  /* List of directory authorities to download consensuses and votes
-   * from. */
-  private SortedMap<String, String> authorities =
-      new TreeMap<String, String>();
-  public Downloader() {
-    this.authorities.put("gabelmoo", "212.112.245.170");
-    this.authorities.put("tor26", "86.59.21.38");
-    this.authorities.put("ides", "216.224.124.114:9030");
-    this.authorities.put("maatuska", "213.115.239.118:443");
-    this.authorities.put("dannenberg", "193.23.244.244");
-    this.authorities.put("urras", "208.83.223.34:443");
-    this.authorities.put("moria1", "128.31.0.34:9131");
-    this.authorities.put("dizum", "194.109.206.212");
-  }
-
-  /* Download a new consensus and corresponding votes. */
-  public void downloadFromAuthorities() {
-    this.downloadConsensus();
-    if (!this.downloadedConsensuses.isEmpty()) {
-      this.parseConsensusToFindReferencedVotes();
-      this.downloadReferencedVotes();
-    }
-  }
-
-  /* Download the most recent consensus from all authorities. */
-  private SortedMap<String, String> downloadedConsensuses =
-      new TreeMap<String, String>();
-  private void downloadConsensus() {
-    Map<String, String> urls = new HashMap<String, String>();
-    for (Map.Entry<String, String> e : this.authorities.entrySet()) {
-      String nickname = e.getKey();
-      String address = e.getValue();
-      String resource = "/tor/status-vote/current/consensus.z";
-      String fullUrl = "http://" + address + resource;
-      urls.put(nickname, fullUrl);
-    }
-    Map<String, String> responses =
-        this.downloadFromAuthority(new HashSet<String>(urls.values()));
-    for (Map.Entry<String, String> e : urls.entrySet()) {
-      String nickname = e.getKey();
-      String url = e.getValue();
-      if (responses.containsKey(url)) {
-        String response = responses.get(url);
-        this.downloadedConsensuses.put(nickname, response);
-      } else {
-        System.err.println("Could not download consensus from directory "
-            + "authority " + nickname + ".  Ignoring.");
-      }
-    }
-    if (responses.isEmpty()) {
-      System.err.println("Could not download consensus from any of the "
-          + "directory authorities.  Ignoring.");
-    }
-  }
-
-  /* Downloads a consensus or vote in a separate thread that can be
-   * interrupted after a timeout. */
-  private static class DownloadRunnable implements Runnable {
-    Thread mainThread;
-    String url;
-    String response;
-    boolean finished = false;
-    public DownloadRunnable(String url) {
-      this.mainThread = Thread.currentThread();
-      this.url = url;
-    }
-    public void run() {
-      try {
-        URL u = new URL(this.url);
-        HttpURLConnection huc = (HttpURLConnection) u.openConnection();
-        huc.setRequestMethod("GET");
-        huc.connect();
-        int responseCode = huc.getResponseCode();
-        if (responseCode == 200) {
-          BufferedInputStream in = new BufferedInputStream(
-              new InflaterInputStream(huc.getInputStream()));
-          ByteArrayOutputStream baos = new ByteArrayOutputStream();
-          int len;
-          byte[] data = new byte[1024];
-          while (!this.finished &&
-              (len = in.read(data, 0, 1024)) >= 0) {
-            baos.write(data, 0, len);
-          }
-          if (this.finished) {
-            return;
-          }
-          in.close();
-          byte[] allData = baos.toByteArray();
-          this.response = new String(allData);
-          this.finished = true;
-          this.mainThread.interrupt();
-        }
-      } catch (IOException e) {
-        /* Can't do much except leaving this.response at null. */
-      }
-      this.finished = true;
-    }
-  }
-
-  /* Download one or more consensuses or votes from one or more directory
-   * authorities using a timeout of 60 seconds. */
-  private Map<String, String> downloadFromAuthority(Set<String> urls) {
-    Set<DownloadRunnable> downloadRunnables =
-        new HashSet<DownloadRunnable>();
-    for (String url : urls) {
-      DownloadRunnable downloadRunnable = new DownloadRunnable(url);
-      downloadRunnables.add(downloadRunnable);
-      new Thread(downloadRunnable).start();
-    }
-    long started = System.currentTimeMillis(), sleep;
-    while ((sleep = started + 60L * 1000L - System.currentTimeMillis())
-        > 0L) {
-      try {
-        Thread.sleep(sleep);
-      } catch (InterruptedException e) {
-        /* Do nothing. */
-      }
-      boolean unfinished = false;
-      for (DownloadRunnable downloadRunnable : downloadRunnables) {
-        if (!downloadRunnable.finished) {
-          unfinished = true;
-          break;
-        }
-      }
-      if (!unfinished) {
-        break;
-      }
-    }
-    Map<String, String> responses = new HashMap<String, String>();
-    for (DownloadRunnable downloadRunnable : downloadRunnables) {
-      String url = downloadRunnable.url;
-      String response = downloadRunnable.response;
-      if (response != null) {
-        responses.put(url, response);
-      }
-      downloadRunnable.finished = true;
-    }
-    return responses;
-  }
-
-  /* Date-time formats to parse and format timestamps. */
-  private static SimpleDateFormat dateTimeFormat;
-  static {
-    dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
-
-  /* Parse the downloaded consensus to find fingerprints of directory
-   * authorities publishing the corresponding votes. */
-  private SortedSet<String> fingerprints = new TreeSet<String>();
-  private void parseConsensusToFindReferencedVotes() {
-    for (String downloadedConsensus :
-        this.downloadedConsensuses.values()) {
-      try {
-        BufferedReader br = new BufferedReader(new StringReader(
-            downloadedConsensus));
-        String line;
-        while ((line = br.readLine()) != null) {
-          if (line.startsWith("valid-after ")) {
-            try {
-              long validAfterMillis = dateTimeFormat.parse(line.substring(
-                  "valid-after ".length())).getTime();
-              if (validAfterMillis + 60L * 60L * 1000L <
-                  System.currentTimeMillis()) {
-                /* Consensus is more than 1 hour old.  We won't be able to
-                 * download the corresponding votes anymore. */
-                break;
-              }
-            } catch (ParseException e) {
-              System.err.println("Could not parse valid-after timestamp "
-                  + "in line '" + line + "' of a downloaded consensus.  "
-                  + "Not downloading votes.");
-              break;
-            }
-          } else if (line.startsWith("dir-source ")) {
-            String[] parts = line.split(" ");
-            if (parts.length < 3) {
-              System.err.println("Bad dir-source line '" + line
-                  + "' in downloaded consensus.  Skipping.");
-              continue;
-            }
-            String nickname = parts[1];
-            if (nickname.endsWith("-legacy")) {
-              continue;
-            }
-            String fingerprint = parts[2];
-            this.fingerprints.add(fingerprint);
-          }
-        }
-        br.close();
-      } catch (IOException e) {
-        System.err.println("Could not parse consensus to find referenced "
-            + "votes in it.  Skipping.");
-      }
-    }
-  }
-
-  /* Download the votes published by directory authorities listed in the
-   * consensus. */
-  private List<String> downloadedVotes = new ArrayList<String>();
-  private void downloadReferencedVotes() {
-    for (String fingerprint : this.fingerprints) {
-      String downloadedVote = null;
-      List<String> authorities = new ArrayList<String>(
-          this.authorities.values());
-      Collections.shuffle(authorities);
-      for (String authority : authorities) {
-        if (downloadedVote != null) {
-          break;
-        }
-        String resource = "/tor/status-vote/current/" + fingerprint
-            + ".z";
-        String fullUrl = "http://" + authority + resource;
-        Set<String> urls = new HashSet<String>();
-        urls.add(fullUrl);
-        Map<String, String> downloadedVotes =
-            this.downloadFromAuthority(urls);
-        if (downloadedVotes.containsKey(fullUrl)) {
-          downloadedVote = downloadedVotes.get(fullUrl);
-          this.downloadedVotes.add(downloadedVote);
-        }
-      }
-    }
-  }
-
-  /* Return the previously downloaded (unparsed) consensus string by
-   * authority nickname. */
-  public SortedMap<String, String> getConsensusStrings() {
-    return this.downloadedConsensuses;
-  }
-
-  /* Return the previously downloaded (unparsed) vote strings. */
-  public List<String> getVoteStrings() {
-    return this.downloadedVotes;
-  }
-}
-
diff --git a/src/org/torproject/chc/Main.java b/src/org/torproject/chc/Main.java
deleted file mode 100644
index a0a3c19..0000000
--- a/src/org/torproject/chc/Main.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.util.*;
-
-/* Coordinate the process of downloading consensus and votes to check
- * Tor's consensus health. */
-public class Main {
-  public static void main(String[] args) {
-
-    /* Initialize reports. */
-    List<Report> reports = new ArrayList<Report>();
-    reports.add(new MetricsWebsiteReport(
-        "website/consensus-health.html"));
-    reports.add(new StatusFileReport());
-
-    /* Download consensus and corresponding votes from the directory
-     * authorities. */
-    Downloader downloader = new Downloader();
-    downloader.downloadFromAuthorities();
-
-    /* Parse consensus and votes. */
-    Parser parser = new Parser();
-    SortedMap<String, Status> parsedDownloadedConsensuses = parser.parse(
-        downloader.getConsensusStrings(), downloader.getVoteStrings());
-
-    /* Check consensus and votes for possible problems. */
-    Checker checker = new Checker();
-    checker.processDownloadedConsensuses(parsedDownloadedConsensuses);
-    SortedMap<Warning, String> warnings = checker.getWarnings();
-
-    /* Pass warnings, consensuses, and votes to the reports, and finish
-     * writing them. */
-    for (Report report : reports) {
-      report.processWarnings(warnings);
-      report.processDownloadedConsensuses(parsedDownloadedConsensuses);
-      report.writeReport();
-    }
-
-    /* Terminate the program including any download threads that may still
-     * be running. */
-    System.exit(0);
-  }
-}
-
diff --git a/src/org/torproject/chc/MetricsWebsiteReport.java b/src/org/torproject/chc/MetricsWebsiteReport.java
deleted file mode 100644
index 8aa52b5..0000000
--- a/src/org/torproject/chc/MetricsWebsiteReport.java
+++ /dev/null
@@ -1,875 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.io.*;
-import java.text.*;
-import java.util.*;
-
-/* Transform the most recent consensus and corresponding votes into an
- * HTML page showing possible irregularities. */
-public class MetricsWebsiteReport implements Report {
-
-  /* Date-time format to format timestamps. */
-  private static SimpleDateFormat dateTimeFormat;
-  static {
-    dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
-
-  /* Output file to write report to. */
-  private File htmlOutputFile;
-
-  /* Initialize this report. */
-  public MetricsWebsiteReport(String htmlOutputFilename) {
-    this.htmlOutputFile = new File(htmlOutputFilename);
-  }
-
-  /* Process warnings. */
-  public void processWarnings(SortedMap<Warning, String> warnings) {
-    /* We could use these warnings instead of running all checks
-     * ourselves.  But we're not doing that yet. */
-  }
-
-  /* Store the downloaded consensus and corresponding votes for later
-   * processing. */
-  private Status downloadedConsensus;
-  private SortedSet<Status> downloadedVotes;
-  public void processDownloadedConsensuses(
-      SortedMap<String, Status> downloadedConsensuses) {
-    long mostRecentValidAfterMillis = -1L;
-    for (Status downloadedConsensus : downloadedConsensuses.values()) {
-      if (downloadedConsensus.getValidAfterMillis() >
-          mostRecentValidAfterMillis) {
-        this.downloadedConsensus = downloadedConsensus;
-        mostRecentValidAfterMillis =
-            downloadedConsensus.getValidAfterMillis();
-      }
-    }
-    if (this.downloadedConsensus != null) {
-      this.downloadedVotes = this.downloadedConsensus.getVotes();
-    }
-  }
-
-  /* Writer to write all HTML output to. */
-  private BufferedWriter bw;
-
-  /* Write HTML output file for the metrics website. */
-  public void writeReport() {
-
-    if (this.downloadedConsensus != null) {
-      try {
-        this.htmlOutputFile.getParentFile().mkdirs();
-        this.bw = new BufferedWriter(new FileWriter(this.htmlOutputFile));
-        writePageHeader();
-        writeValidAfterTime();
-        writeKnownFlags();
-        writeNumberOfRelaysVotedAbout();
-        writeConsensusMethods();
-        writeRecommendedVersions();
-        writeConsensusParameters();
-        writeAuthorityKeys();
-        writeBandwidthScannerStatus();
-        writeAuthorityVersions();
-        writeRelayFlagsTable();
-        writeRelayFlagsSummary();
-        writePageFooter();
-        this.bw.close();
-      } catch (IOException e) {
-        System.err.println("Could not write HTML output file '"
-            + this.htmlOutputFile.getAbsolutePath() + "'.  Ignoring.");
-      }
-    }
-  }
-
-  /* Write the HTML page header including the metrics website
-   * navigation. */
-  private void writePageHeader() throws IOException {
-    this.bw.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 "
-          + "Transitional//EN\">\n"
-        + "<html>\n"
-        + "  <head>\n"
-        + "    <title>Tor Metrics Portal: Consensus health</title>\n"
-        + "    <meta http-equiv=\"content-type\" content=\"text/html; "
-          + "charset=ISO-8859-1\">\n"
-        + "    <link href=\"/css/stylesheet-ltr.css\" type=\"text/css\" "
-          + "rel=\"stylesheet\">\n"
-        + "    <link href=\"/images/favicon.ico\" "
-          + "type=\"image/x-icon\" rel=\"shortcut icon\">\n"
-        + "  </head>\n"
-        + "  <body>\n"
-        + "    <div class=\"center\">\n"
-        + "      <table class=\"banner\" border=\"0\" cellpadding=\"0\" "
-          + "cellspacing=\"0\" summary=\"\">\n"
-        + "        <tr>\n"
-        + "          <td class=\"banner-left\"><a "
-          + "href=\"/index.html\"><img src=\"/images/top-left.png\" "
-          + "alt=\"Click to go to home page\" width=\"193\" "
-          + "height=\"79\"></a></td>\n"
-        + "          <td class=\"banner-middle\">\n"
-        + "            <a href=\"/\">Home</a>\n"
-        + "            <a href=\"graphs.html\">Graphs</a>\n"
-        + "            <a href=\"research.html\">Research</a>\n"
-        + "            <a href=\"status.html\">Status</a>\n"
-        + "            <br>\n"
-        + "            <font size=\"2\">\n"
-        + "              <a href=\"networkstatus.html\">Network "
-          + "Status</a>\n"
-        + "              <a href=\"exonerator.html\">ExoneraTor</a>\n"
-        + "              <a href=\"relay-search.html\">Relay Search</a>\n"
-        + "              <a class=\"current\">Consensus Health</a>\n"
-        + "            </font>\n"
-        + "          </td>\n"
-        + "          <td class=\"banner-right\"></td>\n"
-        + "        </tr>\n"
-        + "      </table>\n"
-        + "      <div class=\"main-column\">\n"
-        + "        <h2>Tor Metrics Portal: Consensus Health</h2>\n"
-        + "        <br>\n"
-        + "        <p>This page shows statistics about the current "
-          + "consensus and votes to facilitate debugging of the "
-          + "directory consensus process.</p>\n");
-  }
-
-  /* Write the valid-after time of the downloaded consensus. */
-  private void writeValidAfterTime() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"validafter\">\n"
-        + "        <h3><a href=\"#validafter\" class=\"anchor\">"
-          + "Valid-after time</a></h3>\n"
-        + "        <br>\n"
-        + "        <p>Consensus was published ");
-    if (this.downloadedConsensus.getValidAfterMillis() <
-        System.currentTimeMillis() - 3L * 60L * 60L * 1000L) {
-      this.bw.write("<font color=\"red\">"
-          + dateTimeFormat.format(
-          this.downloadedConsensus.getValidAfterMillis()) + "</font>");
-    } else {
-      this.bw.write(dateTimeFormat.format(
-          this.downloadedConsensus.getValidAfterMillis()));
-    }
-    this.bw.write(". <i>Note that it takes up to 15 to learn about new "
-        + "consensus and votes and process them.</i></p>\n");
-  }
-
-  /* Write the lists of known flags. */
-  private void writeKnownFlags() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"knownflags\">\n"
-        + "        <h3><a href=\"#knownflags\" class=\"anchor\">Known "
-          + "flags</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        this.bw.write("          <tr>\n"
-            + "            <td>" + vote.getNickname() + "</td>\n"
-            + "            <td>known-flags");
-        for (String knownFlag : vote.getKnownFlags()) {
-          this.bw.write(" " + knownFlag);
-        }
-        this.bw.write("</td>\n"
-            + "          </tr>\n");
-      }
-    }
-    this.bw.write("          <tr>\n"
-        + "            <td><font color=\"blue\">consensus</font>"
-          + "</td>\n"
-        + "            <td><font color=\"blue\">known-flags");
-    for (String knownFlag : this.downloadedConsensus.getKnownFlags()) {
-      this.bw.write(" " + knownFlag);
-    }
-    this.bw.write("</font></td>\n"
-        + "          </tr>\n"
-        + "        </table>\n");
-  }
-
-  /* Write the number of relays voted about. */
-  private void writeNumberOfRelaysVotedAbout() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"numberofrelays\">\n"
-        + "        <h3><a href=\"#numberofrelays\" class=\"anchor\">"
-          + "Number of relays voted about</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"320\">\n"
-        + "            <col width=\"320\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td><td></td>"
-            + "</tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        this.bw.write("          <tr>\n"
-            + "            <td>" + vote.getNickname() + "</td>\n"
-            + "            <td>" + vote.getStatusEntries().size()
-              + " total</td>\n"
-            + "            <td>" + vote.getRunningRelays()
-              + " Running</td>\n"
-            + "          </tr>\n");
-      }
-    }
-    this.bw.write("          <tr>\n"
-        + "            <td><font color=\"blue\">consensus</font>"
-          + "</td>\n"
-        + "            <td><font color=\"blue\">"
-          + this.downloadedConsensus.getStatusEntries().size()
-          + " total</font></td>\n"
-        + "            <td><font color=\"blue\">"
-          + this.downloadedConsensus.getRunningRelays()
-          + " Running</font></td>\n"
-        + "          </tr>\n"
-        + "        </table>\n");
-  }
-
-  /* Write the supported consensus methods of directory authorities and
-   * the resulting consensus method. */
-  private void writeConsensusMethods() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"consensusmethods\">\n"
-        + "        <h3><a href=\"#consensusmethods\" class=\"anchor\">"
-          + "Consensus methods</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        SortedSet<Integer> consensusMethods =
-            vote.getConsensusMethods();
-        if (consensusMethods.contains(
-            this.downloadedConsensus.getConsensusMethods().last())) {
-          this.bw.write("          <tr>\n"
-               + "            <td>" + vote.getNickname() + "</td>\n"
-               + "            <td>consensus-methods");
-          for (int consensusMethod : consensusMethods) {
-            this.bw.write(" " + String.valueOf(consensusMethod));
-          }
-          this.bw.write("</td>\n"
-               + "          </tr>\n");
-        } else {
-          this.bw.write("          <tr>\n"
-              + "            <td><font color=\"red\">"
-                + vote.getNickname() + "</font></td>\n"
-              + "            <td><font color=\"red\">"
-                + "consensus-methods");
-          for (int consensusMethod : consensusMethods) {
-            this.bw.write(" " + String.valueOf(consensusMethod));
-          }
-          this.bw.write("</font></td>\n"
-            + "          </tr>\n");
-        }
-      }
-    }
-    this.bw.write("          <tr>\n"
-        + "            <td><font color=\"blue\">consensus</font>"
-          + "</td>\n"
-        + "            <td><font color=\"blue\">consensus-method "
-          + this.downloadedConsensus.getConsensusMethods().last()
-          + "</font></td>\n"
-        + "          </tr>\n"
-        + "        </table>\n");
-  }
-
-  /* Write recommended versions. */
-  private void writeRecommendedVersions() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"recommendedversions\">\n"
-        + "        <h3><a href=\"#recommendedversions\" class=\"anchor\">"
-          + "Recommended versions</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        SortedSet<String> voteRecommendedClientVersions =
-            vote.getRecommendedClientVersions();
-        if (voteRecommendedClientVersions != null) {
-          if (downloadedConsensus.getRecommendedClientVersions().equals(
-              voteRecommendedClientVersions)) {
-            this.bw.write("          <tr>\n"
-                + "            <td>" + vote.getNickname() + "</td>\n"
-                + "            <td>client-versions ");
-            int i = 0;
-            for (String version : voteRecommendedClientVersions) {
-              this.bw.write((i++ > 0 ? "," : "") + version);
-            }
-            this.bw.write("</td>\n"
-                + "          </tr>\n");
-          } else {
-            this.bw.write("          <tr>\n"
-                + "            <td><font color=\"red\">"
-                  + vote.getNickname()
-                  + "</font></td>\n"
-                + "            <td><font color=\"red\">client-versions ");
-            int i = 0;
-            for (String version : voteRecommendedClientVersions) {
-              this.bw.write((i++ > 0 ? "," : "") + version);
-            }
-            this.bw.write("</font></td>\n"
-                + "          </tr>\n");
-          }
-        }
-        SortedSet<String> voteRecommendedServerVersions =
-            vote.getRecommendedServerVersions();
-        if (voteRecommendedServerVersions != null) {
-          if (downloadedConsensus.getRecommendedServerVersions().equals(
-              voteRecommendedServerVersions)) {
-            this.bw.write("          <tr>\n"
-                + "            <td></td>\n"
-                + "            <td>server-versions ");
-            int i = 0;
-            for (String version : voteRecommendedServerVersions) {
-              this.bw.write((i++ > 0 ? "," : "") + version);
-            }
-            this.bw.write("</td>\n"
-                + "          </tr>\n");
-          } else {
-            this.bw.write("          <tr>\n"
-                + "            <td></td>\n"
-                + "            <td><font color=\"red\">server-versions ");
-            int i = 0;
-            for (String version : voteRecommendedServerVersions) {
-              this.bw.write((i++ > 0 ? "," : "") + version);
-            }
-            this.bw.write("</font></td>\n"
-                + "          </tr>\n");
-          }
-        }
-      }
-    }
-    this.bw.write("          <tr>\n"
-        + "            <td><font color=\"blue\">consensus</font>"
-        + "</td>\n"
-        + "            <td><font color=\"blue\">client-versions ");
-    int i = 0;
-    for (String version :
-        downloadedConsensus.getRecommendedClientVersions()) {
-      this.bw.write((i++ > 0 ? "," : "") + version);
-    }
-    this.bw.write("</font></td>\n"
-        + "          </tr>\n"
-        + "          <tr>\n"
-        + "            <td></td>\n"
-        + "            <td><font color=\"blue\">server-versions ");
-    i = 0;
-    for (String version :
-        downloadedConsensus.getRecommendedServerVersions()) {
-      this.bw.write((i++ > 0 ? "," : "") + version);
-    }
-    this.bw.write("</font></td>\n"
-      + "          </tr>\n"
-      + "        </table>\n");
-  }
-
-  /* Write consensus parameters. */
-  private void writeConsensusParameters() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"consensusparams\">\n"
-        + "        <h3><a href=\"#consensusparams\" class=\"anchor\">"
-          + "Consensus parameters</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      Set<String> validParameters = new HashSet<String>(Arrays.asList(
-          ("circwindow,CircuitPriorityHalflifeMsec,refuseunknownexits,"
-          + "cbtdisabled,cbtnummodes,cbtrecentcount,cbtmaxtimeouts,"
-          + "cbtmincircs,cbtquantile,cbtclosequantile,cbttestfreq,"
-          + "cbtmintimeout,cbtinitialtimeout,bwauthpid").split(",")));
-      Map<String, String> consensusConsensusParams =
-          downloadedConsensus.getConsensusParams();
-      for (Status vote : this.downloadedVotes) {
-        Map<String, String> voteConsensusParams =
-            vote.getConsensusParams();
-        boolean conflictOrInvalid = false;
-        if (voteConsensusParams != null) {
-          for (Map.Entry<String, String> e :
-              voteConsensusParams.entrySet()) {
-            if (!consensusConsensusParams.containsKey(e.getKey()) ||
-                !consensusConsensusParams.get(e.getKey()).equals(
-                e.getValue()) ||
-                !validParameters.contains(e.getKey())) {
-              conflictOrInvalid = true;
-              break;
-            }
-          }
-        }
-        if (conflictOrInvalid) {
-          this.bw.write("          <tr>\n"
-              + "            <td><font color=\"red\">"
-                + vote.getNickname() + "</font></td>\n"
-              + "            <td><font color=\"red\">params");
-          for (Map.Entry<String, String> e :
-              voteConsensusParams.entrySet()) {
-            this.bw.write(" " + e.getKey() + "=" + e.getValue());
-          }
-          this.bw.write("</font></td>\n"
-              + "          </tr>\n");
-        } else {
-          this.bw.write("          <tr>\n"
-              + "            <td>" + vote.getNickname() + "</td>\n"
-              + "            <td>params");
-          for (Map.Entry<String, String> e :
-              voteConsensusParams.entrySet()) {
-            this.bw.write(" " + e.getKey() + "=" + e.getValue());
-          }
-          this.bw.write("</td>\n"
-              + "          </tr>\n");
-        }
-      }
-    }
-    this.bw.write("          <tr>\n"
-        + "            <td><font color=\"blue\">consensus</font>"
-          + "</td>\n"
-        + "            <td><font color=\"blue\">params");
-    for (Map.Entry<String, String> e :
-        this.downloadedConsensus.getConsensusParams().entrySet()) {
-      this.bw.write(" " + e.getKey() + "=" + e.getValue());
-    }
-    this.bw.write("</font></td>\n"
-        + "          </tr>\n"
-        + "        </table>\n");
-  }
-
-  /* Write authority keys and their expiration dates. */
-  private void writeAuthorityKeys() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"authoritykeys\">\n"
-        + "        <h3><a href=\"#authoritykeys\" class=\"anchor\">"
-          + "Authority keys</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        long voteDirKeyExpiresMillis = vote.getDirKeyExpiresMillis();
-        if (voteDirKeyExpiresMillis - 14L * 24L * 60L * 60L * 1000L <
-            System.currentTimeMillis()) {
-          this.bw.write("          <tr>\n"
-              + "            <td><font color=\"red\">"
-                + vote.getNickname() + "</font></td>\n"
-              + "            <td><font color=\"red\">dir-key-expires "
-                + dateTimeFormat.format(voteDirKeyExpiresMillis)
-                + "</font></td>\n"
-              + "          </tr>\n");
-        } else {
-          this.bw.write("          <tr>\n"
-              + "            <td>" + vote.getNickname() + "</td>\n"
-              + "            <td>dir-key-expires "
-                + dateTimeFormat.format(voteDirKeyExpiresMillis)
-                + "</td>\n"
-              + "          </tr>\n");
-        }
-      }
-    }
-    this.bw.write("        </table>\n"
-        + "        <br>\n"
-        + "        <p><i>Note that expiration dates of legacy keys are "
-          + "not included in votes and therefore not listed here!</i>"
-          + "</p>\n");
-  }
-
-  /* Write the status of bandwidth scanners and results being contained
-   * in votes. */
-  private void writeBandwidthScannerStatus() throws IOException {
-    this.bw.write("        <br>\n"
-         + "        <a name=\"bwauthstatus\">\n"
-         + "        <h3><a href=\"#bwauthstatus\" class=\"anchor\">"
-           + "Bandwidth scanner status</a></h3>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"640\">\n"
-        + "          </colgroup>\n");
-    if (this.downloadedVotes.size() < 1) {
-      this.bw.write("          <tr><td>(No votes.)</td><td></td></tr>\n");
-    } else {
-      for (Status vote : this.downloadedVotes) {
-        if (vote.getBandwidthWeights() > 0) {
-          this.bw.write("          <tr>\n"
-              + "            <td>" + vote.getNickname() + "</td>\n"
-              + "            <td>" + vote.getBandwidthWeights()
-                + " Measured values in w lines</td>\n"
-              + "          </tr>\n");
-        }
-      }
-    }
-    this.bw.write("        </table>\n");
-  }
-
-  /* Write directory authority versions. */
-  private void writeAuthorityVersions() throws IOException {
-    this.bw.write("        <br>\n"
-         + "        <a name=\"authorityversions\">\n"
-         + "        <h3><a href=\"#authorityversions\" class=\"anchor\">"
-           + "Authority versions</a></h3>\n"
-        + "        <br>\n");
-    Map<String, String> authorityVersions =
-        this.downloadedConsensus.getAuthorityVersions();
-    if (authorityVersions.size() < 1) {
-      this.bw.write("          <p>(No relays with Authority flag found.)"
-            + "</p>\n");
-    } else {
-      this.bw.write("        <table border=\"0\" cellpadding=\"4\" "
-            + "cellspacing=\"0\" summary=\"\">\n"
-          + "          <colgroup>\n"
-          + "            <col width=\"160\">\n"
-          + "            <col width=\"640\">\n"
-          + "          </colgroup>\n");
-      for (Map.Entry<String, String> e : authorityVersions.entrySet()) {
-        String nickname = e.getKey();
-        String versionString = e.getValue();
-        this.bw.write("          <tr>\n"
-            + "            <td>" + nickname + "</td>\n"
-            + "            <td>" + versionString + "</td>\n"
-            + "          </tr>\n");
-      }
-      this.bw.write("        </table>\n"
-          + "        <br>\n"
-          + "        <p><i>Note that this list of relays with the "
-            + "Authority flag may be different from the list of v3 "
-            + "directory authorities!</i></p>\n");
-    }
-  }
-
-  /* Write the (huge) table containing relay flags contained in votes and
-   * the consensus for each relay. */
-  private void writeRelayFlagsTable() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"relayflags\">\n"
-        + "        <h3><a href=\"#relayflags\" class=\"anchor\">Relay "
-          + "flags</a></h3>\n"
-        + "        <br>\n"
-        + "        <p>The semantics of flags written in the table is "
-          + "as follows:</p>\n"
-        + "        <ul>\n"
-        + "          <li><b>In vote and consensus:</b> Flag in vote "
-          + "matches flag in consensus, or relay is not listed in "
-          + "consensus (because it doesn't have the Running "
-          + "flag)</li>\n"
-        + "          <li><b><font color=\"red\">Only in "
-          + "vote:</font></b> Flag in vote, but missing in the "
-          + "consensus, because there was no majority for the flag or "
-          + "the flag was invalidated (e.g., Named gets invalidated by "
-          + "Unnamed)</li>\n"
-        + "          <li><b><font color=\"gray\"><s>Only in "
-          + "consensus:</s></font></b> Flag in consensus, but missing "
-          + "in a vote of a directory authority voting on this "
-          + "flag</li>\n"
-        + "          <li><b><font color=\"blue\">In "
-          + "consensus:</font></b> Flag in consensus</li>\n"
-        + "        </ul>\n"
-        + "        <br>\n"
-        + "        <p>See also the summary below the table.</p>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"120\">\n"
-        + "            <col width=\"80\">\n");
-    for (int i = 0; i < this.downloadedVotes.size(); i++) {
-      this.bw.write("            <col width=\""
-          + (640 / this.downloadedVotes.size()) + "\">\n");
-    }
-    this.bw.write("          </colgroup>\n");
-    SortedMap<String, String> allRelays = new TreeMap<String, String>();
-    for (Status vote : this.downloadedVotes) {
-      for (StatusEntry statusEntry : vote.getStatusEntries().values()) {
-        allRelays.put(statusEntry.getFingerprint(),
-            statusEntry.getNickname());
-      }
-    }
-    for (StatusEntry statusEntry :
-        this.downloadedConsensus.getStatusEntries().values()) {
-      allRelays.put(statusEntry.getFingerprint(),
-          statusEntry.getNickname());
-    }
-    int linesWritten = 0;
-    for (Map.Entry<String, String> e : allRelays.entrySet()) {
-      if (linesWritten++ % 10 == 0) {
-        this.writeRelayFlagsTableHeader();
-      }
-      String fingerprint = e.getKey();
-      String nickname = e.getValue();
-      this.writeRelayFlagsTableRow(fingerprint, nickname);
-    }
-    this.bw.write("        </table>\n");
-  }
-
-  /* Write the table header that is repeated every ten relays and that
-   * contains the directory authority names. */
-  private void writeRelayFlagsTableHeader() throws IOException {
-    this.bw.write("          <tr><td><br><b>Fingerprint</b></td>"
-        + "<td><br><b>Nickname</b></td>\n");
-    for (Status vote : this.downloadedVotes) {
-      String shortDirName = vote.getNickname().length() > 6 ?
-          vote.getNickname().substring(0, 5) + "." :
-          vote.getNickname();
-      this.bw.write("<td><br><b>" + shortDirName + "</b></td>");
-    }
-    this.bw.write("<td><br><b>consensus</b></td></tr>\n");
-  }
-
-  /* Write a single row in the table of relay flags. */
-  private void writeRelayFlagsTableRow(String fingerprint,
-      String nickname) throws IOException {
-    this.bw.write("          <tr>\n");
-    if (this.downloadedConsensus.containsStatusEntry(fingerprint) &&
-        this.downloadedConsensus.getStatusEntry(fingerprint).getFlags().
-        contains("Named") &&
-        !Character.isDigit(nickname.charAt(0))) {
-      this.bw.write("            <td id=\"" + nickname
-          + "\"><a href=\"relay.html?fingerprint="
-          + fingerprint + "\" target=\"_blank\">"
-          + fingerprint.substring(0, 8) + "</a></td>\n");
-    } else {
-      this.bw.write("            <td><a href=\"relay.html?fingerprint="
-          + fingerprint + "\" target=\"_blank\">"
-          + fingerprint.substring(0, 8) + "</a></td>\n");
-    }
-    this.bw.write("            <td>" + nickname + "</td>\n");
-    SortedSet<String> relevantFlags = new TreeSet<String>();
-    for (Status vote : this.downloadedVotes) {
-      if (vote.containsStatusEntry(fingerprint)) {
-        relevantFlags.addAll(vote.getStatusEntry(fingerprint).getFlags());
-      }
-    }
-    SortedSet<String> consensusFlags = null;
-    if (this.downloadedConsensus.containsStatusEntry(fingerprint)) {
-      consensusFlags = this.downloadedConsensus.
-          getStatusEntry(fingerprint).getFlags();
-      relevantFlags.addAll(consensusFlags);
-    }
-    for (Status vote : this.downloadedVotes) {
-      if (vote.containsStatusEntry(fingerprint)) {
-        SortedSet<String> flags = vote.getStatusEntry(fingerprint).
-            getFlags();
-        this.bw.write("            <td>");
-        int flagsWritten = 0;
-        for (String flag : relevantFlags) {
-          this.bw.write(flagsWritten++ > 0 ? "<br>" : "");
-          if (flags.contains(flag)) {
-            if (consensusFlags == null ||
-              consensusFlags.contains(flag)) {
-              this.bw.write(flag);
-            } else {
-              this.bw.write("<font color=\"red\">" + flag + "</font>");
-            }
-          } else if (consensusFlags != null &&
-              vote.getKnownFlags().contains(flag) &&
-              consensusFlags.contains(flag)) {
-            this.bw.write("<font color=\"gray\"><s>" + flag
-                + "</s></font>");
-          }
-        }
-        this.bw.write("</td>\n");
-      } else {
-        this.bw.write("            <td></td>\n");
-      }
-    }
-    if (consensusFlags != null) {
-      this.bw.write("            <td>");
-      int flagsWritten = 0;
-      for (String flag : relevantFlags) {
-        this.bw.write(flagsWritten++ > 0 ? "<br>" : "");
-        if (consensusFlags.contains(flag)) {
-          this.bw.write("<font color=\"blue\">" + flag + "</font>");
-        }
-      }
-      this.bw.write("</td>\n");
-    } else {
-      this.bw.write("            <td></td>\n");
-    }
-    this.bw.write("          </tr>\n");
-  }
-
-  /* Write the relay flag summary. */
-  private void writeRelayFlagsSummary() throws IOException {
-    this.bw.write("        <br>\n"
-        + "        <a name=\"overlap\">\n"
-        + "        <h3><a href=\"#overlap\" class=\"anchor\">Overlap "
-          + "between votes and consensus</a></h3>\n"
-        + "        <br>\n"
-        + "        <p>The semantics of columns is similar to the "
-          + "table above:</p>\n"
-        + "        <ul>\n"
-        + "          <li><b>In vote and consensus:</b> Flag in vote "
-          + "matches flag in consensus, or relay is not listed in "
-          + "consensus (because it doesn't have the Running "
-          + "flag)</li>\n"
-        + "          <li><b><font color=\"red\">Only in "
-          + "vote:</font></b> Flag in vote, but missing in the "
-          + "consensus, because there was no majority for the flag or "
-          + "the flag was invalidated (e.g., Named gets invalidated by "
-          + "Unnamed)</li>\n"
-        + "          <li><b><font color=\"gray\"><s>Only in "
-          + "consensus:</s></font></b> Flag in consensus, but missing "
-          + "in a vote of a directory authority voting on this "
-          + "flag</li>\n"
-        + "        </ul>\n"
-        + "        <br>\n"
-        + "        <table border=\"0\" cellpadding=\"4\" "
-        + "cellspacing=\"0\" summary=\"\">\n"
-        + "          <colgroup>\n"
-        + "            <col width=\"160\">\n"
-        + "            <col width=\"210\">\n"
-        + "            <col width=\"210\">\n"
-        + "            <col width=\"210\">\n"
-        + "          </colgroup>\n"
-        + "          <tr><td></td><td><b>Only in vote</b></td>"
-          + "<td><b>In vote and consensus</b></td>"
-          + "<td><b>Only in consensus</b></td>\n");
-    Set<String> allFingerprints = new HashSet<String>();
-    for (Status vote : this.downloadedVotes) {
-      allFingerprints.addAll(vote.getStatusEntries().keySet());
-    }
-    allFingerprints.addAll(this.downloadedConsensus.getStatusEntries().
-        keySet());
-    SortedMap<String, SortedMap<String, Integer>> flagsAgree =
-        new TreeMap<String, SortedMap<String, Integer>>();
-    SortedMap<String, SortedMap<String, Integer>> flagsLost =
-        new TreeMap<String, SortedMap<String, Integer>>();
-    SortedMap<String, SortedMap<String, Integer>> flagsMissing =
-        new TreeMap<String, SortedMap<String, Integer>>();
-    for (String fingerprint : allFingerprints) {
-      SortedSet<String> consensusFlags =
-          this.downloadedConsensus.containsStatusEntry(fingerprint) ?
-          this.downloadedConsensus.getStatusEntry(fingerprint).getFlags() :
-          null;
-      for (Status vote : this.downloadedVotes) {
-        String dir = vote.getNickname();
-        if (vote.containsStatusEntry(fingerprint)) {
-          SortedSet<String> flags = vote.getStatusEntry(fingerprint).
-              getFlags();
-          for (String flag : this.downloadedConsensus.getKnownFlags()) {
-            SortedMap<String, SortedMap<String, Integer>> sums = null;
-            if (flags.contains(flag)) {
-              if (consensusFlags == null ||
-                consensusFlags.contains(flag)) {
-                sums = flagsAgree;
-              } else {
-                sums = flagsLost;
-              }
-            } else if (consensusFlags != null &&
-                vote.getKnownFlags().contains(flag) &&
-                consensusFlags.contains(flag)) {
-              sums = flagsMissing;
-            }
-            if (sums != null) {
-              SortedMap<String, Integer> sum = null;
-              if (sums.containsKey(dir)) {
-                sum = sums.get(dir);
-              } else {
-                sum = new TreeMap<String, Integer>();
-                sums.put(dir, sum);
-              }
-              sum.put(flag, sum.containsKey(flag) ?
-                  sum.get(flag) + 1 : 1);
-            }
-          }
-        }
-      }
-    }
-    for (Status vote : this.downloadedVotes) {
-      String dir = vote.getNickname();
-      int i = 0;
-      for (String flag : vote.getKnownFlags()) {
-        this.bw.write("          <tr>\n"
-            + "            <td>" + (i++ == 0 ? dir : "")
-              + "</td>\n");
-        if (flagsLost.containsKey(dir) &&
-            flagsLost.get(dir).containsKey(flag)) {
-          this.bw.write("            <td><font color=\"red\"> "
-                + flagsLost.get(dir).get(flag) + " " + flag
-                + "</font></td>\n");
-        } else {
-          this.bw.write("            <td></td>\n");
-        }
-        if (flagsAgree.containsKey(dir) &&
-            flagsAgree.get(dir).containsKey(flag)) {
-          this.bw.write("            <td>" + flagsAgree.get(dir).get(flag)
-                + " " + flag + "</td>\n");
-        } else {
-          this.bw.write("            <td></td>\n");
-        }
-        if (flagsMissing.containsKey(dir) &&
-            flagsMissing.get(dir).containsKey(flag)) {
-          this.bw.write("            <td><font color=\"gray\"><s>"
-                + flagsMissing.get(dir).get(flag) + " " + flag
-                + "</s></font></td>\n");
-        } else {
-          this.bw.write("            <td></td>\n");
-        }
-        this.bw.write("          </tr>\n");
-      }
-    }
-    this.bw.write("        </table>\n");
-  }
-
-  /* Write the footer of the HTML page containing the blurb that is on
-   * every page of the metrics website. */
-  private void writePageFooter() throws IOException {
-    this.bw.write("      </div>\n"
-        + "    </div>\n"
-        + "    <div class=\"bottom\" id=\"bottom\">\n"
-        + "      <p>This material is supported in part by the "
-          + "National Science Foundation under Grant No. "
-          + "CNS-0959138. Any opinions, finding, and conclusions "
-          + "or recommendations expressed in this material are "
-          + "those of the author(s) and do not necessarily reflect "
-          + "the views of the National Science Foundation.</p>\n"
-        + "      <p>\"Tor\" and the \"Onion Logo\" are <a "
-          + "href=\"https://www.torproject.org/docs/trademark-faq.html"
-          + ".en\">"
-        + "registered trademarks</a> of The Tor Project, "
-          + "Inc.</p>\n"
-        + "      <p>Data on this site is freely available under a "
-          + "<a href=\"http://creativecommons.org/publicdomain/"
-          + "zero/1.0/\">CC0 no copyright declaration</a>: To the "
-          + "extent possible under law, the Tor Project has waived "
-          + "all copyright and related or neighboring rights in "
-          + "the data. Graphs are licensed under a <a "
-          + "href=\"http://creativecommons.org/licenses/by/3.0/"
-          + "us/\">Creative Commons Attribution 3.0 United States "
-          + "License</a>.</p>\n"
-        + "    </div>\n"
-        + "  </body>\n"
-        + "</html>");
-  }
-}
-
diff --git a/src/org/torproject/chc/Parser.java b/src/org/torproject/chc/Parser.java
deleted file mode 100644
index 053c7b4..0000000
--- a/src/org/torproject/chc/Parser.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.io.*;
-import java.text.*;
-import java.util.*;
-import org.apache.commons.codec.binary.*;
-
-/* Parse a network status consensus or vote. */
-public class Parser {
-
-  /* Parse and return a consensus and corresponding votes, or null if
-   * something goes wrong. */
-  public SortedMap<String, Status> parse(
-      SortedMap<String, String> consensusStrings,
-      List<String> voteStrings) {
-    SortedSet<Status> parsedVotes = new TreeSet<Status>();
-    for (String voteString : voteStrings) {
-      Status parsedVote = this.parseConsensusOrVote(voteString, false);
-      if (parsedVote != null) {
-        parsedVotes.add(parsedVote);
-      }
-    }
-    SortedMap<String, Status> parsedConsensuses =
-        new TreeMap<String, Status>();
-    for (Map.Entry<String, String> e : consensusStrings.entrySet()) {
-      String nickname = e.getKey();
-      String consensusString = e.getValue();
-      Status parsedConsensus = this.parseConsensusOrVote(consensusString,
-          true);
-      if (parsedConsensus != null) {
-        for (Status parsedVote : parsedVotes) {
-          if (parsedConsensus.getValidAfterMillis() ==
-              parsedVote.getValidAfterMillis()) {
-            parsedConsensus.addVote(parsedVote);
-          }
-        }
-        parsedConsensuses.put(nickname, parsedConsensus);
-      }
-    }
-    return parsedConsensuses;
-  }
-
-  /* Date-time formats to parse and format timestamps. */
-  private static SimpleDateFormat dateTimeFormat;
-  static {
-    dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
-
-  /* Parse a consensus or vote string into a Status instance. */
-  private Status parseConsensusOrVote(String statusString,
-      boolean isConsensus) {
-    if (statusString == null) {
-      return null;
-    }
-    Status status = new Status();
-    status.setUnparsedString(statusString);
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          statusString));
-      String line, rLine = null, sLine = null;
-      int totalRelays = 0, runningRelays = 0, bandwidthWeights = 0;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("consensus-method ") ||
-            line.startsWith("consensus-methods ")) {
-          SortedSet<Integer> consensusMethods = new TreeSet<Integer>();
-          String[] parts = line.split(" ");
-          for (int i = 1; i < parts.length; i++) {
-            consensusMethods.add(Integer.parseInt(parts[i]));
-          }
-          status.setConsensusMethods(consensusMethods);
-        } else if (line.startsWith("valid-after ")) {
-          try {
-            status.setValidAfterMillis(dateTimeFormat.parse(
-                line.substring("valid-after ".length())).getTime());
-          } catch (ParseException e) {
-            System.err.println("Could not parse valid-after timestamp in "
-                + "line '" + line + "' of a "
-                + (isConsensus ? "consensus" : "vote") + ".  Skipping.");
-            return null;
-          }
-        } else if (line.startsWith("client-versions ")) {
-          status.setRecommendedClientVersions(
-              new TreeSet<String>(Arrays.asList(
-              line.split(" ")[1].split(","))));
-        } else if (line.startsWith("server-versions ")) {
-          status.setRecommendedServerVersions(
-              new TreeSet<String>(Arrays.asList(
-              line.split(" ")[1].split(","))));
-        } else if (line.startsWith("known-flags ")) {
-          for (String flag : line.substring("known-flags ".length()).
-              split(" ")) {
-            status.addKnownFlag(flag);
-          }
-        } else if (line.startsWith("params ")) {
-          if (line.length() > "params ".length()) {
-            for (String param :
-                line.substring("params ".length()).split(" ")) {
-              String paramName = param.split("=")[0];
-              String paramValue = param.split("=")[1];
-              status.addConsensusParam(paramName, paramValue);
-            }
-          }
-        } else if (line.startsWith("dir-source ") && !isConsensus) {
-          status.setNickname(line.split(" ")[1]);
-          status.setFingerprint(line.split(" ")[2]);
-        } else if (line.startsWith("dir-key-expires ")) {
-          try {
-            status.setDirKeyExpiresMillis(dateTimeFormat.parse(
-                line.substring("dir-key-expires ".length())).getTime());
-          } catch (ParseException e) {
-            System.err.println("Could not parse dir-key-expires "
-                + "timestamp in line '" + line + "' of a "
-                + (isConsensus ? "consensus" : "vote") + ".  Skipping.");
-            return null;
-          }
-        } else if (line.startsWith("r ") ||
-            line.equals("directory-footer")) {
-          if (rLine != null) {
-            StatusEntry statusEntry = new StatusEntry();
-            statusEntry.setNickname(rLine.split(" ")[1]);
-            statusEntry.setFingerprint(Hex.encodeHexString(
-                Base64.decodeBase64(rLine.split(" ")[2] + "=")).
-                toUpperCase());
-            SortedSet<String> flags = new TreeSet<String>();
-            if (sLine.length() > 2) {
-              for (String flag : sLine.substring(2).split(" ")) {
-                flags.add(flag);
-              }
-            }
-            statusEntry.setFlags(flags);
-            status.addStatusEntry(statusEntry);
-          }
-          if (line.startsWith("r ")) {
-            rLine = line;
-          } else {
-            break;
-          }
-        } else if (line.startsWith("s ") || line.equals("s")) {
-          sLine = line;
-          if (line.contains(" Running")) {
-            runningRelays++;
-          }
-        } else if (line.startsWith("v ") &&
-            sLine.contains(" Authority")) {
-          String nickname = rLine.split(" ")[1];
-          String versionString = line.substring(2);
-          status.addAuthorityVersion(nickname, versionString);
-        } else if (line.startsWith("w ") && !isConsensus &&
-              line.contains(" Measured")) {
-          bandwidthWeights++;
-        }
-      }
-      br.close();
-      status.setRunningRelays(runningRelays);
-      status.setBandwidthWeights(bandwidthWeights);
-    } catch (IOException e) {
-      System.err.println("Caught an IOException while parsing a "
-          + (isConsensus ? "consensus" : "vote") + " string.  Skipping.");
-      return null;
-    }
-    return status;
-  }
-}
-
diff --git a/src/org/torproject/chc/Report.java b/src/org/torproject/chc/Report.java
deleted file mode 100644
index 48015eb..0000000
--- a/src/org/torproject/chc/Report.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.util.*;
-
-/* Transform findings from parsing consensuses and votes into a report of
- * some form. */
-public interface Report {
-
-  /* Process the downloaded current consensus and corresponding votes to
-   * find irregularities between them. */
-  public abstract void processDownloadedConsensuses(
-      SortedMap<String, Status> downloadedConsensuses);
-
-  /* Process warnings consisting of warning type and details. */
-  public abstract void processWarnings(
-      SortedMap<Warning, String> warnings);
-
-  /* Finish writing report. */
-  public abstract void writeReport();
-}
-
diff --git a/src/org/torproject/chc/Status.java b/src/org/torproject/chc/Status.java
deleted file mode 100644
index cbf7395..0000000
--- a/src/org/torproject/chc/Status.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.util.*;
-
-/* Contains the unparsed string and parsed fields from a network status
- * consensus or vote. */
-public class Status implements Comparable<Status> {
-
-  /* Helper methods to implement the Comparable interface; Status
-   * instances are compared by nickname of the publishing directory
-   * authorities. */
-  public int compareTo(Status o) {
-    return this.nickname.compareTo(o.nickname);
-  }
-  public boolean equals(Object o) {
-    return (o instanceof Status &&
-        this.nickname.equals(((Status) o).nickname));
-  }
-
-  /* Unparsed string that was downloaded or read from disk and that can
-   * be written to disk. */
-  private String unparsedString;
-  public void setUnparsedString(String unparsedString) {
-    this.unparsedString = unparsedString;
-  }
-  public String getUnparsedString() {
-    return this.unparsedString;
-  }
-
-  /* Votes published at the same time as this consensus; votes don't
-   * reference any statuses. */
-  private SortedSet<Status> votes = new TreeSet<Status>();
-  public void addVote(Status vote) {
-    this.votes.add(vote);
-  }
-  public SortedSet<Status> getVotes() {
-    return this.votes;
-  }
-
-  /* Fingerprint of the directory authority publishing this vote; left
-   * empty for consensuses. */
-  private String fingerprint;
-  public void setFingerprint(String fingerprint) {
-    this.fingerprint = fingerprint;
-  }
-  public String getFingerprint() {
-    return this.fingerprint;
-  }
-
-  /* Nickname of the directory authority publishing this vote; left empty
-   * for consensuses. */
-  private String nickname;
-  public void setNickname(String nickname) {
-    this.nickname= nickname;
-  }
-  public String getNickname() {
-    return this.nickname;
-  }
-
-  /* Valid-after time in milliseconds. */
-  private long validAfterMillis;
-  public void setValidAfterMillis(long validAfterMillis) {
-    this.validAfterMillis = validAfterMillis;
-  }
-  public long getValidAfterMillis() {
-    return this.validAfterMillis;
-  }
-
-  /* Consensus parameters. */
-  private SortedMap<String, String> consensusParams =
-      new TreeMap<String, String>();
-  public void addConsensusParam(String paramName, String paramValue) {
-    this.consensusParams.put(paramName, paramValue);
-  }
-  public SortedMap<String, String> getConsensusParams() {
-    return this.consensusParams;
-  }
-
-  /* Consensus methods supported by the directory authority sending a vote
-   * or of the produced consensus. */
-  private SortedSet<Integer> consensusMethods;
-  public void setConsensusMethods(SortedSet<Integer> consensusMethods) {
-    this.consensusMethods = consensusMethods;
-  }
-  public SortedSet<Integer> getConsensusMethods() {
-    return this.consensusMethods;
-  }
-
-  /* Recommended server versions. */
-  private SortedSet<String> recommendedServerVersions;
-  public void setRecommendedServerVersions(
-      SortedSet<String> recommendedServerVersions) {
-    this.recommendedServerVersions = recommendedServerVersions;
-  }
-  public SortedSet<String> getRecommendedServerVersions() {
-    return this.recommendedServerVersions;
-  }
-
-  /* Recommended client versions. */
-  private SortedSet<String> recommendedClientVersions;
-  public void setRecommendedClientVersions(
-      SortedSet<String> recommendedClientVersions) {
-    this.recommendedClientVersions = recommendedClientVersions;
-  }
-  public SortedSet<String> getRecommendedClientVersions() {
-    return this.recommendedClientVersions;
-  }
-
-  /* Expiration times of directory signing keys. */
-  private long dirKeyExpiresMillis;
-  public void setDirKeyExpiresMillis(long dirKeyExpiresMillis) {
-    this.dirKeyExpiresMillis = dirKeyExpiresMillis;
-  }
-  public long getDirKeyExpiresMillis() {
-    return this.dirKeyExpiresMillis;
-  }
-
-  /* Known flags by the directory authority sending a vote or of the
-   * produced consensus. */
-  private SortedSet<String> knownFlags = new TreeSet<String>();
-  public void addKnownFlag(String knownFlag) {
-    this.knownFlags.add(knownFlag);
-  }
-  public SortedSet<String> getKnownFlags() {
-    return this.knownFlags;
-  }
-
-  /* Number of status entries with the Running flag. */
-  private int runningRelays;
-  public void setRunningRelays(int runningRelays) {
-    this.runningRelays = runningRelays;
-  }
-  public int getRunningRelays() {
-    return this.runningRelays;
-  }
-
-  /* Number of status entries containing bandwidth weights (only relevant
-   * in votes). */
-  private int bandwidthWeights;
-  public void setBandwidthWeights(int bandwidthWeights) {
-    this.bandwidthWeights = bandwidthWeights;
-  }
-  public int getBandwidthWeights() {
-    return this.bandwidthWeights;
-  }
-
-  /* Status entries contained in this status. */
-  private SortedMap<String, StatusEntry> statusEntries =
-      new TreeMap<String, StatusEntry>();
-  public void addStatusEntry(StatusEntry statusEntry) {
-    this.statusEntries.put(statusEntry.getFingerprint(), statusEntry);
-  }
-  public SortedMap<String, StatusEntry> getStatusEntries() {
-    return this.statusEntries;
-  }
-  public boolean containsStatusEntry(String fingerprint) {
-    return this.statusEntries.containsKey(fingerprint);
-  }
-  public StatusEntry getStatusEntry(String fingerprint) {
-    return this.statusEntries.get(fingerprint);
-  }
-
-  /* Versions of directory authorities (only set in a consensus). */
-  private SortedMap<String, String> authorityVersions =
-      new TreeMap<String, String>();
-  public void addAuthorityVersion(String fingerprint,
-      String versionString) {
-    this.authorityVersions.put(fingerprint, versionString);
-  }
-  public SortedMap<String, String> getAuthorityVersions() {
-    return this.authorityVersions;
-  }
-}
-
diff --git a/src/org/torproject/chc/StatusEntry.java b/src/org/torproject/chc/StatusEntry.java
deleted file mode 100644
index 0377937..0000000
--- a/src/org/torproject/chc/StatusEntry.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.util.*;
-
-/* Contains the parsed data from a network status entry contained in a
- * network status consensus or vote. */
-public class StatusEntry implements Comparable<StatusEntry> {
-
-  /* Helper methods to implement the Comparable interface; StatusEntry
-   * instances are compared by fingerprint. */
-  public int compareTo(StatusEntry o) {
-    return this.fingerprint.compareTo(o.fingerprint);
-  }
-  public boolean equals(Object o) {
-    return (o instanceof StatusEntry &&
-        this.fingerprint.equals(((StatusEntry) o).fingerprint));
-  }
-
-  /* Relay fingerprint. */
-  private String fingerprint;
-  public void setFingerprint(String fingerprint) {
-    this.fingerprint = fingerprint;
-  }
-  public String getFingerprint() {
-    return this.fingerprint;
-  }
-
-  /* Relay nickname. */
-  private String nickname;
-  public void setNickname(String nickname) {
-    this.nickname = nickname;
-  }
-  public String getNickname() {
-    return this.nickname;
-  }
-
-  /* Relay flags. */
-  private SortedSet<String> flags;
-  public void setFlags(SortedSet<String> flags) {
-    this.flags = flags;
-  }
-  public SortedSet<String> getFlags() {
-    return this.flags;
-  }
-}
-
diff --git a/src/org/torproject/chc/StatusFileReport.java b/src/org/torproject/chc/StatusFileReport.java
deleted file mode 100644
index 4aef195..0000000
--- a/src/org/torproject/chc/StatusFileReport.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-import java.io.*;
-import java.text.*;
-import java.util.*;
-
-/* Check a given consensus and votes for irregularities and write results
- * to stdout while rate-limiting warnings based on severity. */
-public class StatusFileReport implements Report {
-
-  /* Date-time format to format timestamps. */
-  private static SimpleDateFormat dateTimeFormat;
-  static {
-    dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-    dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-  }
-
-  /* Downloaded consensus and corresponding votes for later
-   * processing. */
-  private SortedMap<String, Status> downloadedConsensuses;
-  private Status downloadedConsensus;
-  private SortedSet<Status> downloadedVotes;
-  public void processDownloadedConsensuses(
-      SortedMap<String, Status> downloadedConsensuses) {
-    this.downloadedConsensuses = downloadedConsensuses;
-  }
-
-  /* Warnings obtained from checking the current consensus and votes. */
-  private SortedMap<Warning, String> warnings;
-  public void processWarnings(SortedMap<Warning, String> warnings) {
-    this.warnings = warnings;
-  }
-
-  /* Check consensuses and votes for irregularities and write output to
-   * stdout. */
-  public void writeReport() {
-    this.readLastWarned();
-    this.prepareReports();
-    this.writeStatusFiles();
-    this.writeLastWarned();
-  }
-
-  /* Warning messages of the last 24 hours that is used to implement
-   * rate limiting. */
-  private Map<String, Long> lastWarned = new HashMap<String, Long>();
-
-  /* Read when we last emitted a warning to rate-limit some of them. */
-  private void readLastWarned() {
-    long now = System.currentTimeMillis();
-    File lastWarnedFile = new File("stats/chc-last-warned");
-    try {
-      if (lastWarnedFile.exists()) {
-        BufferedReader br = new BufferedReader(new FileReader(
-            lastWarnedFile));
-        String line;
-        while ((line = br.readLine()) != null) {
-          if (!line.contains(": ")) {
-            System.err.println("Bad line in stats/chc-last-warned: '" + line
-                + "'.  Ignoring this line.");
-            continue;
-          }
-          long warnedMillis = Long.parseLong(line.substring(0,
-              line.indexOf(": ")));
-          if (warnedMillis < now - 24L * 60L * 60L * 1000L) {
-            /* Remove warnings that are older than 24 hours. */
-            continue;
-          }
-          String message = line.substring(line.indexOf(": ") + 2);
-          lastWarned.put(message, warnedMillis);
-        }
-      }
-    } catch (IOException e) {
-      System.err.println("Could not read file '"
-          + lastWarnedFile.getAbsolutePath() + "' to learn which "
-          + "warnings have been sent out before.  Ignoring.");
-    }
-  }
-
-  /* Prepare a report to be written to stdout. */
-  private String allWarnings = null, newWarnings = null;
-  private void prepareReports() {
-    SortedMap<String, Long> warningStrings = new TreeMap<String, Long>();
-    for (Map.Entry<Warning, String> e : this.warnings.entrySet()) {
-      Warning type = e.getKey();
-      String details = e.getValue();
-      switch (type) {
-        case NoConsensusKnown:
-          break;
-        case ConsensusDownloadTimeout:
-          warningStrings.put("The following directory authorities did "
-              + "not return a consensus within a timeout of 60 seconds: "
-              + details, 150L * 60L * 1000L);
-          break;
-        case ConsensusNotFresh:
-          warningStrings.put("The consensuses published by the following "
-              + "directory authorities are more than 1 hour old and "
-              + "therefore not fresh anymore: " + details,
-              150L * 60L * 1000L);
-          break;
-        case ConsensusMethodNotSupported:
-          warningStrings.put("The following directory authorities do not "
-              + "support the consensus method that the consensus uses: "
-              + details, 24L * 60L * 60L * 1000L);
-          break;
-        case DifferentRecommendedClientVersions:
-          warningStrings.put("The following directory authorities "
-              + "recommend other client versions than the consensus: "
-              + details, 150L * 60L * 1000L);
-          break;
-        case DifferentRecommendedServerVersions:
-          warningStrings.put("The following directory authorities "
-              + "recommend other server versions than the consensus: "
-              + details, 150L * 60L * 1000L);
-          break;
-        case ConflictingOrInvalidConsensusParams:
-          warningStrings.put("The following directory authorities set "
-              + "conflicting or invalid consensus parameters: " + details,
-              150L * 60L * 1000L);
-          break;
-        case CertificateExpiresSoon:
-          warningStrings.put("The certificates of the following "
-              + "directory authorities expire within the next 14 days: "
-              + details, 24L * 60L * 60L * 1000L);
-          break;
-        case VotesMissing:
-          warningStrings.put("We're missing votes from the following "
-              + "directory authorities: " + details, 150L * 60L * 1000L);
-          break;
-        case BandwidthScannerResultsMissing:
-          warningStrings.put("The following directory authorities are "
-              + "not reporting bandwidth scanner results: " + details,
-              150L * 60L * 1000L);
-          break;
-      }
-    }
-    long now = System.currentTimeMillis();
-    StringBuilder allSb = new StringBuilder(),
-        newSb = new StringBuilder();
-    for (Map.Entry<String, Long> e : warningStrings.entrySet()) {
-      String message = e.getKey();
-      allSb.append(message + "\n");
-      long warnInterval = e.getValue();
-      if (!lastWarned.containsKey(message) ||
-          lastWarned.get(message) + warnInterval < now) {
-        newSb.append(message + "\n");
-      }
-    }
-    if (newSb.length() > 0) {
-      this.allWarnings = allSb.toString();
-      this.newWarnings = newSb.toString();
-      for (String message : warningStrings.keySet()) {
-        this.lastWarned.put(message, now);
-      }
-    }
-  }
-
-  /* Write report to stdout. */
-  private void writeStatusFiles() {
-    try {
-      BufferedWriter allBw = new BufferedWriter(new FileWriter(
-          "all-warnings")), newBw = new BufferedWriter(new FileWriter(
-          "new-warnings"));
-      if (this.allWarnings != null) {
-        allBw.write(this.allWarnings);
-      }
-      if (this.newWarnings != null) {
-        newBw.write(this.newWarnings);
-      }
-      allBw.close();
-      newBw.close();
-    } catch (IOException e) {
-      System.err.println("Could not write status files 'all-warnings' "
-          + "and/or 'new-warnings'.  Ignoring.");
-    }
-  }
-
-  /* Write timestamps when warnings were last sent to disk. */
-  private void writeLastWarned() {
-    File lastWarnedFile = new File("stats/chc-last-warned");
-    try {
-      lastWarnedFile.getParentFile().mkdirs();
-      BufferedWriter bw = new BufferedWriter(new FileWriter(
-          lastWarnedFile));
-      for (Map.Entry<String, Long> e : lastWarned.entrySet()) {
-        bw.write(String.valueOf(e.getValue()) + ": " + e.getKey() + "\n");
-      }
-      bw.close();
-    } catch (IOException e) {
-      System.err.println("Could not write file '"
-          + lastWarnedFile.getAbsolutePath() + "' to remember which "
-          + "warnings have been sent out before.  Ignoring.");
-    }
-  }
-}
-
diff --git a/src/org/torproject/chc/Warning.java b/src/org/torproject/chc/Warning.java
deleted file mode 100644
index 3425c6c..0000000
--- a/src/org/torproject/chc/Warning.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright 2011 The Tor Project
- * See LICENSE for licensing information */
-package org.torproject.chc;
-
-/* Warning about irregularities in parsed consensuses and votes. */
-public enum Warning {
-
-  /* No consensus is known that can be checked. */
-  NoConsensusKnown,
-
-  /* One or more directory authorities did not return a consensus within a
-   * timeout of 60 seconds. */
-  ConsensusDownloadTimeout,
-
-  /* One or more directory authorities published a consensus that is more
-   * than 1 hour old and therefore not fresh anymore. */
-  ConsensusNotFresh,
-
-  /* One or more directory authorities does not support the consensus
-   * method that the consensus uses. */
-  ConsensusMethodNotSupported,
-
-  /* One or more directory authorities recommends different client
-   * versions than the ones in the consensus. */
-  DifferentRecommendedClientVersions,
-
-  /* One or more directory authorities recommends different server
-   * versions than the ones in the consensus. */
-  DifferentRecommendedServerVersions,
-
-  /* One or more directory authorities set conflicting or invalid
-   * consensus parameters. */
-  ConflictingOrInvalidConsensusParams,
-
-  /* The certificate(s) of one or more directory authorities expire within
-   * the next 14 days. */
-  CertificateExpiresSoon,
-
-  /* The vote(s) of one or more directory authorities are missing. */
-  VotesMissing,
-
-  /* One or more directory authorities are not reporting bandwidth scanner
-   * results. */
-  BandwidthScannerResultsMissing
-}
-



More information about the tor-commits mailing list