[tor-commits] [metrics-tasks/master] Add bridge stability report draft (#4255).

karsten at torproject.org karsten at torproject.org
Sun Oct 30 06:18:18 UTC 2011


commit 85a545e66a8ba85d512e30b955c28c1b90e915ea
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Sun Oct 30 07:11:56 2011 +0100

    Add bridge stability report draft (#4255).
---
 task-4255/.gitignore                   |   11 +
 task-4255/README                       |   29 ++
 task-4255/SimulateBridgeStability.java |  770 ++++++++++++++++++++++++++++++++
 task-4255/report.bib                   |   26 ++
 task-4255/report.tex                   |  414 +++++++++++++++++
 task-4255/stability.R                  |  220 +++++++++
 task-4255/stale-bridge-tarballs.csv    |  760 +++++++++++++++++++++++++++++++
 7 files changed, 2230 insertions(+), 0 deletions(-)

diff --git a/task-4255/.gitignore b/task-4255/.gitignore
new file mode 100644
index 0000000..4251d3d
--- /dev/null
+++ b/task-4255/.gitignore
@@ -0,0 +1,11 @@
+*.pdf
+*.log
+stability.csv
+stable-fingerprints-and-addresses
+*.class
+*.aux
+*.bbl
+*.blg
+bridge-statuses/
+future-stability/
+
diff --git a/task-4255/README b/task-4255/README
new file mode 100644
index 0000000..7ff10c9
--- /dev/null
+++ b/task-4255/README
@@ -0,0 +1,29 @@
+Extract sanitized bridge network statuses (not server descriptors or
+extra-info descriptors) to bridge-statuses/, e.g.
+bridge-statuses/bridge-descriptors-2010-01/statuses/.  Leaving server or
+extra-info descriptors in the directory may lead to errors or at least
+delay the evaluation significantly.
+
+Compile the Java class:
+
+  $ javac SimulateBridgeStability.java
+
+Run the Java class:
+
+  $ java SimulateBridgeStability
+
+Before re-running, delete the following files/directories to re-generate
+them:
+
+  - stable-fingerprints-and-addresses
+  - future-stability/
+  - stability.csv
+
+Plot the results:
+
+  $ R --slave -f stability.R
+
+Compile the report:
+
+  $ pdflatex report.tex
+
diff --git a/task-4255/SimulateBridgeStability.java b/task-4255/SimulateBridgeStability.java
new file mode 100644
index 0000000..6deb318
--- /dev/null
+++ b/task-4255/SimulateBridgeStability.java
@@ -0,0 +1,770 @@
+import java.io.*;
+import java.text.*;
+import java.util.*;
+
+/**
+ * Simulate different requirements for stable bridges that BridgeDB can
+ * use to include at least one such stable bridge in its responses to
+ * bridge users.
+ *
+ * The two bridge stability metrics used here are weighted mean time
+ * between address changes (WMTBAC) and weighted fractional uptime (WFU).
+ * Different requirements based on these two metrics are simulated by
+ * comparing the time on same address (TOSA) and weighted fractional
+ * uptime in the future (WFU).
+ */
+public class SimulateBridgeStability {
+
+  /* Bridge history entry for the third step.  Once we teach BridgeDB
+   * how to keep track of bridge stability, it's going to keep records
+   * similar to this one. */
+  private static class BridgeHistoryElement {
+    /* Weighted uptime in seconds that is used for WFU calculation. */
+    public long weightedUptime;
+    /* Weighted time in seconds that is used for WFU calculation. */
+    public long weightedTime;
+    /* Weighted run length of previously used addresses or ports in
+     * seconds. */
+    public double weightedRunLength;
+    /* Total run weights of previously used addresses or ports. */
+    public double totalRunWeights;
+    /* Currently known address. */
+    public String address;
+    /* Month string (YYYY-MM) that was used as input to the bridge
+     * descriptor sanitizer. */
+    public String month;
+    /* Currently known port. */
+    public int port;
+    /* Timestamp in milliseconds when this bridge was last seen with a
+     * different address or port.  When adding a history entry, this
+     * timestamp is initialized with the publication time of the previous
+     * status. */
+    public long lastSeenWithDifferentAddressAndPort;
+    /* Timestamp in milliseconds when this bridge was last seen with this
+     * address and port. */
+    public long lastSeenWithThisAddressAndPort;
+  }
+
+  /* Run the analysis in three steps:
+   *
+   * In the first step, we parse sanitized bridge network statuses from
+   * first to last to determine stable addresses that have changed by the
+   * sanitizing process only.  In the second step, we parse statuses from
+   * last to first to calculate TOSA and future WFU, and write future
+   * stability metrics to disk as one file per network status in
+   * future-stability/$filename.  In the third step, we parse the statuses
+   * again from first to last, calculate past stability metrics for all
+   * bridges, select stable bridges, look up future stability of these
+   * bridges, and write results to stability.csv.
+   */
+  public static void main(String[] args) throws Exception {
+
+    /* Measure how long this execution takes. */
+    long started = System.currentTimeMillis();
+
+    /* Prepare timestamp parsing. */
+    SimpleDateFormat isoFormat = new SimpleDateFormat(
+        "yyyy-MM-dd HH:mm:ss");
+    isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    SimpleDateFormat statusFileNameFormat = new SimpleDateFormat(
+        "yyyyMMdd-HHmmss");
+    statusFileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    SimpleDateFormat futureStabilityFileNameFormat = new SimpleDateFormat(
+        "yyyy-MM-dd-HH-mm-ss");
+    futureStabilityFileNameFormat.setTimeZone(TimeZone.getTimeZone(
+        "UTC"));
+
+    /* Define analysis interval. */
+    String analyzeFrom = "2010-07-01 00:00:00",
+        analyzeTo = "2011-06-30 23:00:00";
+    long analyzeFromMillis = isoFormat.parse(analyzeFrom).getTime(),
+        analyzeToMillis = isoFormat.parse(analyzeTo).getTime();
+
+    /* Scan existing status files. */
+    SortedSet<File> allStatuses = new TreeSet<File>();
+    Stack<File> files = new Stack<File>();
+    files.add(new File("bridge-statuses"));
+    while (!files.isEmpty()) {
+      File file = files.pop();
+      if (file.isDirectory()) {
+        files.addAll(Arrays.asList(file.listFiles()));
+      } else {
+        if (file.getName().endsWith(
+            "-4A0CCD2DDC7995083D73F5D667100C8A5831F16D")) {
+          allStatuses.add(file);
+        }
+      }
+    }
+    System.out.println("Scanning " + allStatuses.size() + " bridge "
+        + "network statuses.");
+
+    /* Parse statuses in forward order to detect stable fingerprint/
+     * address combinations to correct some of the IP address changes from
+     * one month to the next. */
+    SortedSet<String> stableFingerprintsAndAddresses =
+        new TreeSet<String>();
+    File stableFingerprintsAndAddressesFile =
+        new File("stable-fingerprints-and-addresses");
+    if (stableFingerprintsAndAddressesFile.exists()) {
+      System.out.println("Reading stable fingerprints and addresses from "
+          + "disk...");
+      BufferedReader br = new BufferedReader(new FileReader(
+          stableFingerprintsAndAddressesFile));
+      String line;
+      while ((line = br.readLine()) != null) {
+        stableFingerprintsAndAddresses.add(line);
+      }
+      br.close();
+    } else {
+      System.out.println("Parsing bridge network statuses and writing "
+          + "stable fingerprints and addresses to disk...");
+      Map<String, Long> firstSeenFingerprintAndAddress =
+          new HashMap<String, Long>();
+      for (File status : allStatuses) {
+        Set<String> fingerprints = new HashSet<String>();
+        BufferedReader br = new BufferedReader(new FileReader(status));
+        String line, rLine = null;
+        while ((line = br.readLine()) != null) {
+          if (line.startsWith("r ")) {
+            String[] parts = line.split(" ");
+            if (parts.length < 8) {
+              System.out.println("Illegal line '" + rLine + "' in "
+                  + status + ". Skipping status.");
+              break;
+            }
+            String fingerprint = parts[2];
+            String address = parts[6];
+            String fingerprintAndAddress = fingerprint + " " + address;
+            if (stableFingerprintsAndAddresses.contains(
+                fingerprintAndAddress)) {
+              continue;
+            } else {
+              String date = parts[4];
+              String time = parts[5];
+              long published = isoFormat.parse(date + " " + time).
+                  getTime();
+              if (!firstSeenFingerprintAndAddress.containsKey(
+                  fingerprintAndAddress)) {
+                firstSeenFingerprintAndAddress.put(fingerprintAndAddress,
+                    published);
+              } else if (published - firstSeenFingerprintAndAddress.get(
+                  fingerprintAndAddress) > 36L * 60L * 60L * 1000L) {
+                stableFingerprintsAndAddresses.add(
+                    fingerprintAndAddress);
+              }
+            }
+          }
+        }
+        br.close();
+      }
+      BufferedWriter bw = new BufferedWriter(new FileWriter(
+          stableFingerprintsAndAddressesFile));
+      for (String stableFingerprintAndAddress :
+          stableFingerprintsAndAddresses) {
+        bw.write(stableFingerprintAndAddress + "\n");
+      }
+      bw.close();
+    }
+    System.out.println("We know about "
+        + stableFingerprintsAndAddresses.size() + " stable fingerprints "
+        + "and addresses.");
+
+    /* Now parse statuses in reverse direction to calculate time until
+     * next address change and weighted fractional uptime for all bridges.
+     * Whenever we find a bridge published in a month for the first time,
+     * we look if we identified the bridge fingerprint and address (either
+     * new or old) as stable before.  If we did, ignore this address
+     * change. */
+    File futureStabilityDirectory = new File("future-stability");
+    if (futureStabilityDirectory.exists()) {
+      System.out.println("Not overwriting files in "
+          + futureStabilityDirectory.getAbsolutePath());
+    } else {
+
+      /* Track weighted uptime and total weighted time in a long[2]. */
+      SortedMap<String, long[]> wfuHistory =
+          new TreeMap<String, long[]>();
+
+      /* Track timestamps of next address changes in a long. */
+      SortedMap<String, Long> nacHistory = new TreeMap<String, Long>();
+
+      /* Store the last known r lines by fingerprint to be able to decide
+       * whether a bridge has changed its IP address. */
+      Map<String, String> lastKnownRLines = new HashMap<String, String>();
+
+      /* Parse bridge network statuses in reverse order. */
+      SortedSet<File> allStatusesReverseOrder =
+          new TreeSet<File>(Collections.reverseOrder());
+      allStatusesReverseOrder.addAll(allStatuses);
+      long nextWeightingInterval = -1L, lastStatusPublicationMillis = -1L;
+      for (File status : allStatusesReverseOrder) {
+
+        /* Parse status publication time from file name. */
+        long statusPublicationMillis = statusFileNameFormat.parse(
+            status.getName().substring(0, "yyyyMMdd-HHmmss".length())).
+            getTime();
+
+        /* We're just looking at the first status outside the analysis
+         * interval.  Stop parsing here. */
+        if (statusPublicationMillis < analyzeFromMillis) {
+          break;
+        }
+
+        /* Calculate the seconds since the last parsed status.  If this is
+         * the first status or we haven't seen a status for more than 60
+         * minutes, assume 60 minutes. */
+        long secondsSinceLastStatusPublication =
+            lastStatusPublicationMillis < 0L ||
+            lastStatusPublicationMillis - statusPublicationMillis
+            > 60L * 60L * 1000L ? 60L * 60L
+            : (lastStatusPublicationMillis - statusPublicationMillis)
+            / 1000L;
+        lastStatusPublicationMillis = statusPublicationMillis;
+
+        /* Before parsing the next bridge network status, see if 12 hours
+         * have passed since we last discounted wfu history values.  If
+         * so, discount variables for all known bridges by factor 0.95 (or
+         * 19/20 since these are long integers) and remove those bridges
+         * with a weighted fractional uptime below 1/10000 from the
+         * history. */
+        long weightingInterval = statusPublicationMillis
+            / (12L * 60L * 60L * 1000L);
+        if (nextWeightingInterval < 0L) {
+          nextWeightingInterval = weightingInterval;
+        }
+        while (weightingInterval < nextWeightingInterval) {
+          Set<String> bridgesToRemove = new HashSet<String>();
+          for (Map.Entry<String, long[]> e : wfuHistory.entrySet()) {
+            long[] w = e.getValue();
+            w[0] *= 19L;
+            w[0] /= 20L;
+            w[1] *= 19L;
+            w[1] /= 20L;
+            if (((10000L * w[0]) / w[1]) < 1L) {
+              bridgesToRemove.add(e.getKey());
+            }
+          }
+          for (String fingerprint : bridgesToRemove) {
+            wfuHistory.remove(fingerprint);
+          }
+          nextWeightingInterval -= 1L;
+        }
+
+        /* Increment total weighted time for all bridges by seconds
+         * since the last status was published. */
+        for (long[] w : wfuHistory.values()) {
+          w[1] += secondsSinceLastStatusPublication;
+        }
+
+        /* If the status falls within our analysis interval, write future
+         * WFUs and TOSAs for all running bridges to disk. */
+        BufferedWriter bw = null;
+        if (statusPublicationMillis <= analyzeToMillis) {
+          File futureStabilityFile = new File("future-stability",
+              futureStabilityFileNameFormat.format(
+              statusPublicationMillis));
+          futureStabilityFile.getParentFile().mkdirs();
+          bw = new BufferedWriter(new FileWriter(futureStabilityFile));
+        }
+
+
+        /* Parse r lines of all bridges with the Running flag from the
+         * current bridge network status. */
+        BufferedReader br = new BufferedReader(new FileReader(status));
+        String line, rLine = null;
+        SortedMap<String, String> runningBridges =
+            new TreeMap<String, String>();
+        while ((line = br.readLine()) != null) {
+          if (line.startsWith("r ")) {
+            rLine = line;
+          } else if (line.startsWith("s ") && line.contains(" Running")) {
+            String[] parts = rLine.split(" ");
+            if (parts.length < 8) {
+              System.out.println("Illegal line '" + rLine + "' in "
+                  + status + ". Skipping line.");
+              continue;
+            }
+            String fingerprint = parts[2];
+            runningBridges.put(fingerprint, rLine);
+          }
+        }
+        br.close();
+
+        /* If this status doesn't contain a single bridge with the Running
+         * flag, ignore it.  This is a problem with the bridge authority
+         * and doesn't mean we should consider all bridges as down. */
+        if (runningBridges.isEmpty()) {
+          continue;
+        }
+
+        /* Find out if a bridge changed its IP address or port. */
+        for (Map.Entry<String, String> e : runningBridges.entrySet()) {
+          String fingerprint = e.getKey();
+          String brLine = e.getValue();
+          String[] brParts = brLine.split(" ");
+
+          /* Increment weighted uptime by seconds since last status
+           * publication time. */
+          if (!wfuHistory.containsKey(fingerprint)) {
+            wfuHistory.put(fingerprint, new long[] {
+                secondsSinceLastStatusPublication,
+                secondsSinceLastStatusPublication });
+          } else {
+            wfuHistory.get(fingerprint)[0] +=
+                secondsSinceLastStatusPublication;
+          }
+
+          /* Check for address or port change. */
+          String address = brParts[6];
+          String port = brParts[7];
+          boolean sameAddressAndPort = false;
+          if (lastKnownRLines.containsKey(fingerprint)) {
+            String[] lastKnownRLineParts =
+                lastKnownRLines.get(fingerprint).split(" ");
+            String lastAddress = lastKnownRLineParts[6];
+            String lastPort = lastKnownRLineParts[7];
+            if (!port.equals(lastPort)) {
+              /* The port changed.  It doesn't matter whether the
+               * address changed or not. */
+            } else if (address.equals(lastAddress)) {
+              /* The bridge's address and port are still the same. */
+              sameAddressAndPort = true;
+            } else {
+              String month = brParts[4].substring(0, "yyyy-MM".length());
+              String lastMonth = lastKnownRLineParts[4].substring(0,
+                  "yyyy-MM".length());
+              if (!lastMonth.equals(month) &&
+                  stableFingerprintsAndAddresses.contains(
+                  fingerprint + " " + address) &&
+                  stableFingerprintsAndAddresses.contains(
+                  fingerprint + " " + lastAddress)) {
+                /* The last time we saw this bridge was in a different
+                 * month.  This bridge was seen with both addresses in
+                 * intervals of 36 hours or more.  Consider this
+                 * address change an artifact from the sanitizing
+                 * process. */
+                sameAddressAndPort = true;
+              } else {
+                /* Either the month did not change or the address or
+                 * port did change. */
+              }
+            }
+          } else {
+            /* We're seeing this bridge for the first time. */
+          }
+          if (!sameAddressAndPort) {
+            nacHistory.put(fingerprint, statusPublicationMillis);
+          }
+          lastKnownRLines.put(fingerprint, brLine);
+
+          /* Write WFU and TOSA to disk. */
+          if (bw != null) {
+            long[] wfu = wfuHistory.get(fingerprint);
+            long tosa = (nacHistory.get(fingerprint)
+                - statusPublicationMillis) / 1000L;
+            bw.write(fingerprint + " " + tosa + " "
+                + ((10000L * wfu[0]) / wfu[1]) + " " + "\n");
+          }
+        }
+        br.close();
+        if (bw != null) {
+          bw.close();
+        }
+      }
+    }
+
+    /* Finally, parse statuses in forward order to calculate weighted mean
+     * time between address change (WMTBAC) and weighted fractional uptime
+     * (WFU) and simulate how stable bridges meeting given requirements
+     * are. */
+    File stabilityFile = new File("stability.csv");
+    if (stabilityFile.exists()) {
+      System.out.println("Not overwriting output file "
+          + stabilityFile.getAbsolutePath());
+    } else {
+
+      /* Run the simulation for the following WFU and WMTBAC
+       * percentiles: */
+      List<Integer> wfuPercentiles = new ArrayList<Integer>();
+      for (int l : new int[] { 0, 30, 40, 50, 60, 70 }) {
+        wfuPercentiles.add(l);
+      }
+      List<Integer> wmtbacPercentiles = new ArrayList<Integer>();
+      for (int l : new int[] { 0, 30, 40, 50, 60, 70 }) {
+        wmtbacPercentiles.add(l);
+      }
+
+      /* Add column headers to output file. */
+      SortedSet<String> columns = new TreeSet<String>();
+      columns.add("running");
+      columns.add("minwta");
+      columns.add("minwtb");
+      for (int wfuPercentile : wfuPercentiles) {
+        columns.add("minwfua" + wfuPercentile + "wfu");
+        columns.add("minwfub" + wfuPercentile + "wfu");
+        for (int wmtbacPercentile : wmtbacPercentiles) {
+          String simulation = wfuPercentile + "wfu" + wmtbacPercentile
+              + "wmtbac";
+          columns.add("stablebridge" + simulation);
+          columns.add("perc15wfu" + simulation);
+          columns.add("perc10wfu" + simulation);
+          columns.add("perc5wfu" + simulation);
+          columns.add("perc15tosa" + simulation);
+          columns.add("perc10tosa" + simulation);
+          columns.add("perc5tosa" + simulation);
+        }
+      }
+      for (int wmtbacPercentile : wmtbacPercentiles) {
+        columns.add("minwmtbaca" + wmtbacPercentile + "wmtbac");
+        columns.add("minwmtbacb" + wmtbacPercentile + "wmtbac");
+      }
+      BufferedWriter bw = new BufferedWriter(new FileWriter(
+          stabilityFile));
+      bw.write("time");
+      for (String column : columns) {
+        bw.write("," + column);
+      }
+      bw.write("\n");
+
+      SortedMap<String, BridgeHistoryElement> history =
+          new TreeMap<String, BridgeHistoryElement>();
+
+      /* Parse previously exported network status entries again, but this
+       * time in forward order. */
+      long nextWeightingInterval = -1L, lastStatusPublicationMillis = -1L;
+      for (File status : allStatuses) {
+
+        /* Parse status publication time from file name. */
+        long statusPublicationMillis = statusFileNameFormat.parse(
+            status.getName().substring(0, "yyyyMMdd-HHmmss".length())).
+            getTime();
+
+        /* Calculate the seconds since the last parsed status.  If this is
+         * the first status or we haven't seen a status for more than 60
+         * minutes, assume 60 minutes. */
+        long secondsSinceLastStatusPublication =
+            lastStatusPublicationMillis < 0L ||
+            statusPublicationMillis - lastStatusPublicationMillis
+            > 60L * 60L * 1000L ? 60L * 60L
+            : (statusPublicationMillis - lastStatusPublicationMillis)
+            / 1000L;
+
+        /* Before parsing the next bridge network status, see if 12 hours
+         * have passed since we last discounted wfu and wmtbac history
+         * values.  If so, discount variables for all known bridges by
+         * factor 0.95 (or 19/20 since these are long integers) and remove
+         * those bridges with a weighted fractional uptime below 1/10000
+         * from the history.  Also, discount weighted run length and total
+         * run weights for all known relays by factor 0.95. */
+        long weightingInterval = statusPublicationMillis
+            / (12L * 60L * 60L * 1000L);
+        if (nextWeightingInterval < 0L) {
+          nextWeightingInterval = weightingInterval;
+        }
+        while (weightingInterval > nextWeightingInterval) {
+          Set<String> bridgesToRemove = new HashSet<String>();
+          for (Map.Entry<String, BridgeHistoryElement> e :
+              history.entrySet()) {
+            BridgeHistoryElement historyElement = e.getValue();
+            historyElement.weightedUptime =
+                (historyElement.weightedUptime * 19L) / 20L;
+            historyElement.weightedTime =
+                (historyElement.weightedTime * 19L) / 20L;
+            if (((10000L * historyElement.weightedUptime)
+                / historyElement.weightedTime) < 1L) {
+              String fingerprint = e.getKey();
+              bridgesToRemove.add(fingerprint);
+            }
+            historyElement.weightedRunLength *= 0.95;
+            historyElement.totalRunWeights *= 0.95;
+          }
+          for (String fingerprint : bridgesToRemove) {
+            history.remove(fingerprint);
+          }
+          nextWeightingInterval += 1L;
+        }
+
+        /* Parse r lines of all bridges with the Running flag from the
+         * current bridge network status. */
+        BufferedReader br = new BufferedReader(new FileReader(status));
+        String line, rLine = null;
+        SortedMap<String, String> runningBridges =
+            new TreeMap<String, String>();
+        while ((line = br.readLine()) != null) {
+          if (line.startsWith("r ")) {
+            rLine = line;
+          } else if (line.startsWith("s ") && line.contains(" Running")) {
+            String[] parts = rLine.split(" ");
+            if (parts.length < 8) {
+              System.out.println("Illegal line '" + rLine + "' in "
+                  + status + ". Skipping line.");
+              continue;
+            }
+            String fingerprint = parts[2];
+            runningBridges.put(fingerprint, rLine);
+          }
+        }
+        br.close();
+
+        /* If this status doesn't contain a single bridge with the Running
+         * flag, ignore it.  This is a problem with the bridge authority
+         * and doesn't mean we should consider all bridges as down. */
+        if (runningBridges.isEmpty()) {
+          continue;
+        }
+
+        /* Add new bridges to history or update history if it already
+         * contains a bridge. */
+        for (Map.Entry<String, String> e : runningBridges.entrySet()) {
+          String fingerprint = e.getKey();
+          String brLine = e.getValue();
+          String[] brParts = brLine.split(" ");
+          String address = brParts[6];
+          int port = Integer.parseInt(brParts[7]);
+          String month = brParts[4].substring(0, "yyyy-MM".length());
+          BridgeHistoryElement bhe = null;
+          if (!history.containsKey(fingerprint)) {
+
+            /* Add new bridge to history. */
+            bhe = new BridgeHistoryElement();
+            bhe.lastSeenWithDifferentAddressAndPort =
+                lastStatusPublicationMillis;
+            history.put(fingerprint, bhe);
+          } else {
+
+            /* Update bridge in history. */
+            bhe = history.get(fingerprint);
+
+            /* If the port changed, ... */
+            if (port != bhe.port ||
+
+                /* ... or the address changed and ... */
+                (!address.equals(bhe.address) &&
+
+                /* ... either the month is the same ... */
+                (month.equals(bhe.month) ||
+
+                /* ... or this address is not stable ... */
+                !stableFingerprintsAndAddresses.contains(
+                fingerprint + " " + address) ||
+
+                /* ... or the last address is not stable, ... */
+                !stableFingerprintsAndAddresses.contains(
+                fingerprint + " " + bhe.address)))) {
+
+              /* ... assume that the bridge changed its address or
+               * port. */
+              bhe.weightedRunLength += (double)
+                  ((bhe.lastSeenWithThisAddressAndPort -
+                  bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+              bhe.totalRunWeights += 1.0;
+              bhe.lastSeenWithDifferentAddressAndPort =
+                  bhe.lastSeenWithThisAddressAndPort;
+            }
+          }
+
+          /* Regardless of whether the bridge is new, kept or changed
+           * its address and port, raise its WFU times and note its
+           * current address, month, and port, and that we saw it using
+           * them. */
+          bhe.weightedUptime += secondsSinceLastStatusPublication;
+          bhe.weightedTime += secondsSinceLastStatusPublication;
+          bhe.lastSeenWithThisAddressAndPort = statusPublicationMillis;
+          bhe.address = address;
+          bhe.month = month;
+          bhe.port = port;
+        }
+
+        /* Update weighted times (not uptimes) of non-running bridges. */
+        for (Map.Entry<String, BridgeHistoryElement> e :
+            history.entrySet()) {
+          String fingerprint = e.getKey();
+          if (!runningBridges.containsKey(fingerprint)) {
+            BridgeHistoryElement bhe = e.getValue();
+            bhe.weightedTime += secondsSinceLastStatusPublication;
+          }
+        }
+
+        /* Prepare writing results. */
+        Map<String, String> results = new HashMap<String, String>();
+        results.put("running", "" + runningBridges.size());
+
+        /* If we reached the analysis interval, read previously calculated
+         * future WFUs and TOSAs from disk and run the simulations. */
+        Map<String, Long> fwfus = new HashMap<String, Long>(),
+            tosas = new HashMap<String, Long>();
+        File futureStabilityFile = new File("future-stability",
+            futureStabilityFileNameFormat.format(
+            statusPublicationMillis));
+        if (statusPublicationMillis < analyzeFromMillis ||
+            statusPublicationMillis > analyzeToMillis) {
+          /* Outside of our analysis interval.  Skip simulation. */
+        } else if (!futureStabilityFile.exists()) {
+          System.out.println("Could not find file "
+              + futureStabilityFile + ". Skipping simulation!");
+        } else {
+          BufferedReader fsBr = new BufferedReader(new FileReader(
+              futureStabilityFile));
+          String fsLine;
+          while ((fsLine = fsBr.readLine()) != null) {
+            String[] fsParts = fsLine.split(" ");
+            tosas.put(fsParts[0], Long.parseLong(fsParts[1]));
+            fwfus.put(fsParts[0], Long.parseLong(fsParts[2]));
+          }
+          fsBr.close();
+
+          /* Prepare calculating thresholds for selecting stable bridges
+           * in simulations. */
+          List<Long> totalWeightedTimes = new ArrayList<Long>();
+          for (String fingerprint : runningBridges.keySet()) {
+            totalWeightedTimes.add(history.get(fingerprint).weightedTime);
+          }
+          Collections.sort(totalWeightedTimes);
+          long minimumTotalWeightedTime = totalWeightedTimes.get(
+              totalWeightedTimes.size() / 8);
+          results.put("minwta", String.valueOf(minimumTotalWeightedTime));
+          if (minimumTotalWeightedTime > 8L * 24L * 60L * 60L) {
+            minimumTotalWeightedTime = 8L * 24L * 60L * 60L;
+          }
+          results.put("minwtb", String.valueOf(minimumTotalWeightedTime));
+          List<Long> weightedFractionalUptimesFamiliar =
+              new ArrayList<Long>();
+          for (String fingerprint : runningBridges.keySet()) {
+            BridgeHistoryElement bhe = history.get(fingerprint);
+            if (bhe.weightedTime >= minimumTotalWeightedTime) {
+              long weightedFractionalUptime =
+                  (10000L * bhe.weightedUptime) / bhe.weightedTime;
+              weightedFractionalUptimesFamiliar.add(
+                  weightedFractionalUptime);
+            }
+          }
+          Collections.sort(weightedFractionalUptimesFamiliar);
+          List<Long> wmtbacs = new ArrayList<Long>();
+          for (String fingerprint : runningBridges.keySet()) {
+            BridgeHistoryElement bhe = history.get(fingerprint);
+            double totalRunLength = bhe.weightedRunLength + (double)
+                ((bhe.lastSeenWithThisAddressAndPort -
+                bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+            double totalWeights = bhe.totalRunWeights + 1.0;
+            long wmtbac = totalWeights < 0.0001 ? 0L
+                : (long) (totalRunLength / totalWeights);
+            wmtbacs.add(wmtbac);
+          }
+          Collections.sort(wmtbacs);
+
+          /* Run simulation for the bridges in the current status for
+           * various WFU and WMTBAC percentiles. */
+          for (int wmtbacPercentile : wmtbacPercentiles) {
+            for (int wfuPercentile : wfuPercentiles) {
+              String simulation = wfuPercentile + "wfu" + wmtbacPercentile
+                  + "wmtbac";
+              long minimumWeightedMeanTimeBetweenAddressChange =
+                  wmtbacs.get((wmtbacPercentile * wmtbacs.size()) / 100);
+              results.put("minwmtbaca" + wmtbacPercentile + "wmtbac",
+                  String.valueOf(
+                  minimumWeightedMeanTimeBetweenAddressChange));
+              if (minimumWeightedMeanTimeBetweenAddressChange >
+                  30L * 24L * 60L * 60L) {
+                minimumWeightedMeanTimeBetweenAddressChange =
+                    30L * 24L * 60L * 60L;
+              }
+              results.put("minwmtbacb" + wmtbacPercentile + "wmtbac",
+                  String.valueOf(
+                  minimumWeightedMeanTimeBetweenAddressChange));
+              long minimumWeightedFractionalUptime =
+                  weightedFractionalUptimesFamiliar.get((wfuPercentile
+                  * weightedFractionalUptimesFamiliar.size()) / 100);
+              results.put("minwfua" + wfuPercentile + "wfu",
+                  String.valueOf(minimumWeightedFractionalUptime));
+              if (minimumWeightedFractionalUptime > 9800) {
+                minimumWeightedFractionalUptime = 9800;
+              }
+              results.put("minwfub" + wfuPercentile + "wfu",
+                  String.valueOf(minimumWeightedFractionalUptime));
+              List<Long> fwfuList = new ArrayList<Long>(),
+                  tosaList = new ArrayList<Long>();
+              Set<String> selectedBridges = new HashSet<String>();
+              for (String fingerprint : runningBridges.keySet()) {
+                BridgeHistoryElement bhe = history.get(fingerprint);
+                double totalRunLength = bhe.weightedRunLength + (double)
+                    ((bhe.lastSeenWithThisAddressAndPort -
+                    bhe.lastSeenWithDifferentAddressAndPort) / 1000L);
+                double totalWeights = bhe.totalRunWeights + 1.0;
+                long wmtbac = totalWeights < 0.0001 ? 0L
+                    : (long) (totalRunLength / totalWeights);
+                if (wmtbac < minimumWeightedMeanTimeBetweenAddressChange) {
+                  continue;
+                }
+                if (wfuPercentile > 0 &&
+                    bhe.weightedTime < minimumTotalWeightedTime) {
+                  continue;
+                }
+                long weightedFractionalUptime =
+                    (10000L * bhe.weightedUptime) / bhe.weightedTime;
+                if (weightedFractionalUptime <
+                    minimumWeightedFractionalUptime) {
+                  continue;
+                }
+                long fwfu = fwfus.get(fingerprint);
+                fwfuList.add(fwfu);
+                long tosa = tosas.get(fingerprint);
+                tosaList.add(tosa);
+                selectedBridges.add(fingerprint);
+              }
+              /* Calculate percentiles of future WFU and of TOSA as the
+               * simulation results. */
+              results.put("stablebridge" + simulation,
+                  String.valueOf(selectedBridges.size()));
+              if (tosaList.size() > 0L) {
+                Collections.sort(tosaList);
+                results.put("perc15tosa" + simulation,
+                    "" + tosaList.get((15 * tosaList.size()) / 100));
+                results.put("perc10tosa" + simulation,
+                    "" + tosaList.get((10 * tosaList.size()) / 100));
+                results.put("perc5tosa" + simulation,
+                    "" + tosaList.get((5 * tosaList.size()) / 100));
+              }
+              if (fwfuList.size() > 0) {
+                Collections.sort(fwfuList);
+                results.put("perc15wfu" + simulation,
+                    "" + fwfuList.get((15 * fwfuList.size()) / 100));
+                results.put("perc10wfu" + simulation,
+                    "" + fwfuList.get((10 * fwfuList.size()) / 100));
+                results.put("perc5wfu" + simulation,
+                    "" + fwfuList.get((5 * fwfuList.size()) / 100));
+              }
+            }
+          }
+        }
+
+        /* Write results, regardless of whether we ran simulations or
+         * not. */
+        SortedSet<String> missingColumns = new TreeSet<String>();
+        for (String resultColumn : results.keySet()) {
+          if (!columns.contains(resultColumn)) {
+            missingColumns.add(resultColumn);
+          }
+        }
+        if (!missingColumns.isEmpty()) {
+          System.out.println("We are missing the following columns:");
+          for (String missingColumn : missingColumns) {
+            System.out.print(" " + missingColumn);
+          }
+          System.out.println(".  Ignoring.");
+        }
+        bw.write(isoFormat.format(statusPublicationMillis));
+        for (String column : columns) {
+          if (results.containsKey(column)) {
+            bw.write("," + results.get(column));
+          } else {
+            bw.write(",NA");
+          }
+        }
+        bw.write("\n");
+        lastStatusPublicationMillis = statusPublicationMillis;
+      }
+      bw.close();
+    }
+  }
+}
+
diff --git a/task-4255/report.bib b/task-4255/report.bib
new file mode 100644
index 0000000..427e27c
--- /dev/null
+++ b/task-4255/report.bib
@@ -0,0 +1,26 @@
+ at misc{dirspec,
+  author = {Roger Dingledine and Nick Mathewson},
+  title = {Tor directory protocol, version 3},
+  note = {\url{https://gitweb.torproject.org/tor.git/blob_plain/HEAD:/doc/spec/dir-spec.txt}},
+}
+
+ at techreport{loesing2011analysis,
+  title = {An Analysis of {Tor} Relay Stability},
+  author = {Karsten Loesing},
+  institution = {The Tor Project},
+  year = {2011},
+  month = {June},
+  type = {Technical Report},
+  note = {\url{https://metrics.torproject.org/papers/relay-stability-2011-06-30.pdf}},
+}
+
+ at techreport{loesing2011overview,
+  title = {Overview of Statistical Data in the {Tor} Network},
+  author = {Karsten Loesing},
+  institution = {The Tor Project},
+  year = {2011},
+  month = {March},
+  type = {Technical Report},
+  note = {\url{https://metrics.torproject.org/papers/data-2011-03-14.pdf}},
+}
+
diff --git a/task-4255/report.tex b/task-4255/report.tex
new file mode 100644
index 0000000..0dfd437
--- /dev/null
+++ b/task-4255/report.tex
@@ -0,0 +1,414 @@
+\documentclass{article}
+\usepackage{url}
+\usepackage[pdftex]{graphicx}
+\usepackage{graphics}
+\usepackage{color}
+\begin{document}
+\title{An Analysis of Tor Bridge Stability\\
+{\large --- Making BridgeDB give out at least one stable bridge per
+user ---}}
+\author{Karsten Loesing\\{\tt karsten at torproject.org}}
+
+\maketitle
+
+\section{Introducing the instable bridges problem}
+
+As of October 2011, the Tor network consists of a few hundred thousand
+clients, 2\,400 public relays, and about 600 non-public bridge relays.
+Bridge relays (in the following: bridges) are entry points which are not
+publicly listed to prevent censors from blocking access to the Tor
+network.
+Censored users request a small number of typically three bridge addresses
+from the BridgeDB service via email or http and then connect to the Tor
+network only via these bridges.
+If all bridges that a user knows about suddenly stop working, the user
+needs to request a new set of bridge addresses from BridgeDB.
+However, BridgeDB memorizes the user's email or IP address and only gives
+out new bridges every 24 hours to slow down enumeration attempts.
+The result is that a user who is unlucky enough to receive only unreliable
+bridges from BridgeDB won't be able to connect to the Tor network for up
+to 24 hours before requesting a new set of bridges.
+
+In this report we propose that BridgeDB keeps bridge stability records,
+similar to how the directory authorities keep relay stability records, and
+includes at least one stable bridge in its responses to users.
+In fact, BridgeDB currently attempts to do this by including at least one
+bridge with the Stable flag assigned by the bridge authority in its
+results.
+This approach is broken for two reasons:
+The first reason is that the algorithm that the bridge authority uses to
+assign the Stable flag is broken to the extent that almost every bridge
+has the Stable flag assigned.
+The second reason is that the Stable flag was designed for clients to
+select relays for long-running streams, not for BridgeDB to select
+reliable entry points into the Tor network.
+A better metric for stable bridges would be based on bridge uptime and on
+the frequency of IP address changes.
+We propose such a metric and evaluate its effectiveness for selecting
+stable bridges based on archived bridge directories.
+
+\section{Defining a new bridge stability metric}
+\label{sec:defining}
+
+The directory authorities implement a few relay stability metrics to
+decide which of the relays to assign the Guard and Stable
+flag~\cite{dirspec, loesing2011analysis}.
+The requirements for stable bridges that we propose here are similar to
+the entry guard requirements.
+That is, stable bridges should have a higher fractional uptime than
+non-stable ones.
+Further, a stable bridge should be available under the same IP address and
+TCP port.
+Otherwise, bridge users who only know a bridge address won't be able to
+connect to the bridge once it changes its address or port.
+We propose the following requirements for a bridge to be considered
+stable in the style of the Guard and Stable flag definition:
+
+\label{def:bridgestability}
+\begin{quote}
+A bridge is considered stable if its \emph{Weighted Mean Time Between
+Address Change} is at least the median for known active bridges or at
+least 30~days, if it is `familiar', and if its \emph{Weighted Fractional
+Uptime} is at least the median for `familiar' active bridges or at least
+98~\%.
+A bridge is `familiar' if 1/8 of all active bridges have appeared more
+recently than it, or if it has been around for a \emph{Weighted Time} of
+8~days.
+\end{quote}
+
+This bridge stability definition contains three main requirements:
+
+\begin{itemize}
+\item The \emph{Weighted Mean Time Between Address Change (WMTBAC)}
+metric is used to track the time that a bridge typically uses the same IP
+address and TCP port.
+The (unweighted) MTBAC measures the average time between last using
+address and port $a_0$ to last using address and port $a_1$.
+This metric is weighted to put more emphasis on recent events than on past
+events.
+Therefore, past address sessions are discounted by factor $0.95$ every
+12~hours.
+The current session is not discounted, so that a WMTBAC value of 30 days
+can be reached after 30 days at the earliest.
+\item The \emph{Weighted Fractional Uptime (WFU)} metric measures the
+fraction of bridge uptime in the past.
+Similar to WMTBAC, WFU values are discounted by factor $0.95$ every
+12~hours, but in this case including the current uptime session.
+\item The \emph{Weighted Time (WT)} metric is used to calculate a bridge's
+WFU and to decide whether a bridge is around long enough to be considered
+`familiar.'
+WT is discounted similar to WMTBAC and WFU, so that a WT of 8 days can be
+reached after around 16 days at the earliest.
+\end{itemize}
+
+All three requirements consist of a dynamic part that depends on the
+stability of other bridges (e.g., ``A bridge is familiar if 1/8 of all
+active bridges have appeared more recently than it, \ldots'') and a static
+part that is independent of other bridges (e.g., ``\ldots or if it has
+been around for a Weighted Time of 8 days.'').
+The dynamic parts ensure that a certain fraction of bridges is considered
+stable even in a rather instable network.
+The static parts ensures that rather stable bridges are not excluded even
+when most other bridges in the network are stable.
+
+\section{Extending BridgeDB to track bridge stability}
+
+There are at least two code bases that could be extended to track bridge
+stability and include at least one stable bridge in BridgeDB results: the
+bridge authority and BridgeDB.
+The decision for extending either code base affects the available data
+for tracking bridge stability and is therefore discussed here.
+
+The bridge authority maintains a list of all active bridges.
+Bridges register at the bridge authority when joining the network, and the
+bridge authority periodically performs reachability tests to confirm that
+a bridge is still active.
+The bridge authority takes snapshots of the list of active bridges every
+30~minutes and copies these snapshots to BridgeDB.
+BridgeDB parses these half-hourly snapshots and gives out bridges to users
+based on the most recently known snapshot.
+
+The bridge stability history can be implemented either in the bridge
+authority code or in BridgeDB.
+On the one hand, an implementation in BridgeDB has the disadvantage that
+bridge reachability data has a resolution of 30 minutes whereas the bridge
+authority would learn about bridges joining or leaving the network
+immediately.
+On the other hand, the bridge stability information is not used by
+anything in the Tor software, but only by BridgeDB.
+Implementing this feature in BridgeDB makes more sense from a software
+architecture point of view.
+In the following we assume that BridgeDB will track bridge stability based
+on half-hourly snapshots of active bridge lists, the bridge network
+statuses.
+
+\section{Simulating bridge stability using archived data}
+
+We can analyze how BridgeDB would track bridge stability and give out
+stable bridges by using archived bridge descriptors.
+These archives contain the same descriptors that BridgeDB uses, but they
+are public and don't contain any IP addresses or sensitive pieces of
+information.
+In Section~\ref{sec:missingdata} we look at the problem of missing data
+due to either the bridge authority or BridgeDB failing and at the effect
+on tracking bridge stability.
+We then touch the topic of how bridge descriptors are sanitized and how we
+can glue them back together for our analysis in
+Section~\ref{sec:sanitizing}.
+Next, we examine typical bridge stability values as requirements for
+considering a bridge as stable in Section~\ref{sec:requirements}.
+In Section~\ref{sec:fractions} we estimate what fraction of bridges would
+be considered as stable depending on the chosen stability requirements.
+Finally, in Section~\ref{sec:selectedstability} we evaluate how effective
+different requirement combinations are for selecting stable bridges.
+Result metrics are how soon selected bridges change their address or what
+fractional uptime selected bridges have in the future.
+
+\subsection{Handling missing bridge status data}
+\label{sec:missingdata}
+
+The bridge status data that we use in this analysis and that would also be
+used by BridgeDB to track bridge stability is generated by the bridge
+authority and copied over to BridgeDB every 30~minutes.
+Figure~\ref{fig:runningbridge} shows the number of running bridges
+contained in these snapshots from July 2010 to June 2011.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{runningbridge.pdf}
+\caption{Median number of running bridges as reported by the bridge
+authority}
+\label{fig:runningbridge}
+\end{figure}
+
+For most of the time the number of bridges is relatively stable.
+But there are at least two irregularities, one in July 2010 and another
+one in February 2011, resulting from problems with the bridge authority or
+the data transfer to the BridgeDB host.
+Figure~\ref{fig:runningbridge-detail} shows these two intervals in more
+detail.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{runningbridge-detail.pdf}
+\caption{Number of Running bridges during phases when either the bridge
+authority or the BridgeDB host were broken}
+\label{fig:runningbridge-detail}
+\end{figure}
+
+The missing data from July 14 to 27, 2010 comes from BridgeDB host not
+accepting new descriptors from the bridge authority because of an
+operating system upgrade of the BridgeDB host.
+During this time, the bridge authority continued to work, but BridgeDB was
+unable to learn about new bridge descriptors from it.
+
+During the time from January 31 to February 16, 2011, the \verb+tor+
+process running the bridge authority silently died, but the script to copy
+descriptors to BridgeDB kept running.
+In this case, BridgeDB received fresh tarballs containing stale
+descriptors with a constant number of 687 relays, visualized in light
+gray.
+These stale descriptors have been excluded from the sanitized descriptors
+and the subsequent analysis.
+The bridge authority was restarted on February 16, 2011, resulting in the
+number of running bridges slowly stabilizing throughout the day.
+
+Both this analysis and a later implementation in BridgeDB need to take
+extended phases of missing or stale data into account.
+
+\subsection{Detecting address changes in sanitized descriptors}
+\label{sec:sanitizing}
+
+The bridge descriptor archives that we use in this analysis have been
+sanitized to remove all addresses and otherwise sensitive
+parts~\cite{loesing2011overview}.
+Part of this sanitizing process is that bridge IP addresses are replaced
+with keyed hashes using a fresh key every month.
+More precisely, every bridge IP address is replaced with the private IP
+address \verb+10.x.x.x+ with \verb+x.x.x+ being the 3 most significant
+bytes of \verb+SHA-256(IP address | bridge identity | secret)+.
+
+A side-effect of this sanitizing step is that a bridge's sanitized IP
+address changes at least once per month, even if the bridge's real IP
+address stays the same.
+We need to detect these artificial address changes and distinguish them
+from real IP address changes.
+
+In this analysis we use a simple heuristic to distinguish between real IP
+address changes and artifacts from the sanitizing process:
+Whenever we find that a bridge has changed its IP address from one month
+to the next, we look up how long both IP addresses were in use in either
+month.
+If both addresses were contained in bridge descriptors that were published
+at least 36~hours apart, we consider them stable IP addresses and
+attribute the apparent IP address change to the sanitizing process.
+Otherwise, we assume the bridge has really changed its IP address.
+Obviously, this simple heuristic might lead us to false conclusions in
+some cases.
+But it helps us handle cases when bridges rarely or never change their IP
+address which would otherwise suffer from monthly address changes in this
+analysis.
+
+\subsection{Examining typical stability metric values}
+\label{sec:requirements}
+
+The definition of bridge stability on page~\pageref{sec:defining} contains
+three different metrics, each of which having a dynamic and a static part.
+The dynamic parts compares the value of a bridge's stability metric to the
+whole set of running bridges.
+Only those bridges are considered as stable that exceed the median value
+(or the 12.5th percentile) of all running bridges.
+The static requirement parts are fixed values for all stability metrics
+that don't rely on the stability of other bridges.
+
+Figure~\ref{fig:requirements} visualizes the dynamic (solid lines) and
+static parts (dashed lines) of all three requirements.
+The dynamic WMTBAC requirements are higher than previously expected.
+A value of 60 means that, on average, bridges keep their IP address and
+port for 60 days.
+The dynamic values are cut off at 30 days by the static requirement which
+should be a high enough value.
+The goal here is to give blocked users a stable enough set of bridges so
+that they don't have to wait another 24~hours before receiving new ones.
+
+We can further see that the dynamic requirements are relatively stable
+over time except for the two phases of missing bridge status data.
+The first phase in July 2010 mostly affects WT, but neither WMTBAC nor
+WFU.
+The second phase in February 2011 affects all three metrics.
+We can expect the selection of stable bridges during February 2010 to be
+more random than at other times.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{requirements.pdf}
+\caption{Dynamic requirements for considering a bridge as stable}
+\label{fig:requirements}
+\end{figure}
+
+\subsection{Estimating fractions of bridges considered as stable}
+\label{sec:fractions}
+
+Requiring a bridge to meet or exceed either or both WMTBF or WFU metric
+results in considering only a subset of all bridges as stable.
+The first result of this analysis is to outline what fraction of bridges
+would be considered as stable if BridgeDB used either or both
+requirements.
+In theory, all parameters in the bridge stability definition on
+page~\pageref{def:bridgestability} could be adjusted to change the set of
+stable bridges or focus more on address changes or on fractional uptime.
+We're leaving the fine-tuning for future work when specifying and
+implementing the BridgeDB extension.
+
+Figure~\ref{fig:stablebridge} shows the fraction of stable bridges over
+time.
+If we only require bridges to meet or exceed the median WMTBAC or the
+fixed value of 30 days, roughly 55~\% of the bridges are considered as
+stable.
+If bridges are only required to meet or exceed the WT and WFU values,
+about $7/8 \times 1/2 = 43.75~\%$ of bridges are considered as stable.
+Requiring both WFU and WMTBAC leads to a fraction of roughly 35~\% stable
+bridges.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{stablebridge.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the fraction of running bridges considered as stable}
+\label{fig:stablebridge}
+\end{figure}
+
+The fraction of 33~\% stable bridges seems appropriate if 1 out of
+3~bridges in the BridgeDB results is supposed to be a stable bridge.
+If more than 1~bridge should be a stable bridge, the requirements need to
+be lowered, so that a higher fraction of bridges is considered stable.
+Otherwise, the load on stable bridges might become too high.
+
+\subsection{Evaluating different requirements on stable bridges}
+\label{sec:selectedstability}
+
+The main purpose of this analysis is to compare the quality of certain
+requirements and requirement combinations on the stability of selected
+bridges.
+Similar to the previous section, we only compare whether or not the WMTBAC
+or WFU requirement is used, but don't change their parameters.
+
+The first result is the future uptime that we can expect from a bridge
+that we consider stable.
+We calculate future uptime similar to past uptime by weighting events in
+the near future more than those happening later.
+We are particularly interested in the almost worst-case scenario here,
+which is why we're looking at the 10th percentile weighted fractional
+uptime in the future.
+This number means that 10~\% of bridges have a weighted fractional uptime
+at most this high and 90~\% of bridges have a value at least this high.
+
+Figure~\ref{fig:fwfu-sim} visualizes the four possible combinations of
+using or not using the WMTBAC and WFU requirements.
+In this plot, the ``WFU \& WMTBAC'' and ``WFU'' lines almost entirely
+overlap, meaning that the WMTBAC requirement doesn't add anything to
+future uptime of selected bridges.
+If the WFU requirement is not used, requiring bridges to meet the WMTBAC
+requirement increases future uptime from roughly 35~\% to maybe 55~\%.
+That means that there is a slight correlation between the two metrics,
+which is plausible.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{fwfu-sim.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the 10th percentile weighted fractional uptime in the
+future}
+\label{fig:fwfu-sim}
+\end{figure}
+
+The second result is the time that a selected bridge stays on the same
+address and port.
+We simply measure the time that the bridge will keep using its current
+address in days.
+Again, we look at the 10th percentile.
+90~\% of selected bridges keep their address longer than this time.
+
+Figure~\ref{fig:tosa-sim} shows for how long bridges keep their address
+and port.
+Bridges meeting both WFU and WTMBAC requirements keep their address for 2
+to 5~weeks.
+This value decreases to 1 to 3~weeks when taking away the WFU requirement,
+which is also a result of the two metrics beeing correlated.
+The bridges that only meet the WFU requirement and not the WMTBAC
+requirement change their address within the first week.
+If we don't use any requirement at all, which is what BridgeDB does today,
+10~\% of all bridges change their address within a single day.
+
+\begin{figure}[t]
+\includegraphics[width=\textwidth]{tosa-sim.pdf}
+\caption{Impact of requiring stable bridges to meet or exceed the median
+WFU and/or WMTBAC on the 10th percentile time on the same address}
+\label{fig:tosa-sim}
+\end{figure}
+
+\section{Concluding the bridge stability analysis}
+
+In this report we propose to extend BridgeDB to make it give out at least
+one stable bridge per user.
+Bridge stability can be calculated based on bridge status information over
+time, similar to how the directory authorities calculate relay stability.
+The bridge stability metric proposed here is based on a bridge's past
+uptime and the frequency of changing its address and/or port.
+Requiring at least 1 bridge of the 3 to be given out to users greatly
+reduces the worst case probability of all bridges being offline or
+changing their addresses or ports.
+The price for this increase in stability is that stable bridges will be
+given out more often than non-stable bridges and will therefore see more
+usage.
+
+We suggest to implement the described bridge stability metric in BridgeDB
+and make it configurable to tweak the requirement parameters if needed.
+Maybe it turns out to be more useful to lower the requirements for a
+bridge to become stable and give out two stable bridges per response.
+It's also possible that the requirement for a bridge to keep its address
+becomes less important in the future when bridge clients can request a
+bridge's current address from the bridge authority.
+All these scenarios can be analyzed before deploying them using archived
+data as done in this report.
+
+\bibliography{report}
+\bibliographystyle{plain}
+
+\end{document}
+
diff --git a/task-4255/stability.R b/task-4255/stability.R
new file mode 100644
index 0000000..b2cd487
--- /dev/null
+++ b/task-4255/stability.R
@@ -0,0 +1,220 @@
+library(ggplot2)
+stability <- read.csv("stability.csv", stringsAsFactors = FALSE)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "running")]
+d <- na.omit(d)
+d_mean <- aggregate(list(running = d$running),
+  by = list(date = as.Date(d$time)), quantile, probs = 0.5)
+d_max <- aggregate(list(running = d$running),
+  by = list(date = as.Date(d$time)), quantile, probs = 0.75)
+d_min <- aggregate(list(running = d$running),
+  by = list(date = as.Date(d$time)), quantile, probs = 0.25)
+d <- data.frame(x = d_mean$date, y = d_mean$running, ymin = d_min$running,
+  ymax = d_max$running)
+d <- rbind(d,
+  data.frame(x = as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+  to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01"),
+  y = NA, ymin = NA, ymax = NA))
+ggplot(d, aes(x = as.Date(x), y = y, ymin = ymin, ymax = ymax)) +
+geom_line() +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+  format = "%b %Y") +
+scale_y_continuous(name = "Running    \nbridges    ",
+  limits = c(0, max(d_mean$running, na.rm = TRUE))) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1))
+ggsave(filename = "runningbridge.pdf", width = 7, height = 3, dpi = 100)
+
+pdf("runningbridge-detail.pdf", width = 7, height = 4)
+grid.newpage()
+pushViewport(viewport(layout = grid.layout(2, 1)))
+d <- stability[stability$time > '2010-07-10' &
+  stability$time < '2010-07-31', ]
+a <- ggplot(d, aes(x = as.POSIXct(time), y = running)) +
+geom_point(size = 0.75) +
+scale_x_datetime(name = "", major = "1 week", minor = "1 day",
+  format = "%b %d, %Y") +
+scale_y_continuous(name = "Running    \nbridges    ",
+  limits = c(0, max(d$running, na.rm = TRUE))) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1),
+  legend.position = "none")
+d <- stability[stability$time > '2011-01-29' &
+  stability$time < '2011-02-19', ]
+e <- read.csv("stale-bridge-tarballs.csv", stringsAsFactors = FALSE,
+  col.names = c("time"))
+d <- rbind(
+  data.frame(time = d$time, running = d$running, colour = "black"),
+  data.frame(time = e$time, running = 687, colour = "grey"))
+b <- ggplot(d, aes(x = as.POSIXct(time), y = running, colour = colour)) +
+geom_point(size = 0.75) +
+scale_x_datetime(name = "", major = "1 week", minor = "1 day",
+  format = "%b %d, %Y") +
+scale_y_continuous(name = "Running    \nbridges    ",
+  limits = c(0, max(d$running, na.rm = TRUE))) +
+scale_colour_manual(values = c("black", "grey60")) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1),
+  legend.position = "none")
+print(a, vp = viewport(layout.pos.row = 1, layout.pos.col = 1))
+print(b, vp = viewport(layout.pos.row = 2, layout.pos.col = 1))
+dev.off()
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "minwmtbaca50wmtbac", "minwta", "minwfua50wfu")]
+d <- na.omit(d)
+d_mean <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+  quantile, probs = 0.5)
+d_max <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+  quantile, probs = 0.75)
+d_min <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+  quantile, probs = 0.25)
+d <- rbind(
+  data.frame(x = d_mean$date,
+    y = d_mean$minwmtbaca50wmtbac / (24 * 60 * 60),
+    ymin = d_min$minwmtbaca50wmtbac / (24 * 60 * 60),
+    ymax = d_max$minwmtbaca50wmtbac / (24 * 60 * 60),
+    var = "Median WMTBAC"),
+  data.frame(x = d_mean$date, y = d_mean$minwta / (24 * 60 * 60),
+    ymin = d_min$minwta / (24 * 60 * 60),
+    ymax = d_max$minwta / (24 * 60 * 60),
+    var = "12.5th perc. WT"),
+  data.frame(x = d_mean$date, y = d_mean$minwfua50wfu / 10000,
+    ymin = d_min$minwfua50wfu / 10000,
+    ymax = d_max$minwfua50wfu / 10000,
+    var = "Median WFU"))
+missing_dates <- as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+  to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01")
+d <- rbind(d,
+  data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+    var = "Median WMTBAC"),
+  data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+    var = "12.5th perc. WT"),
+  data.frame(x = missing_dates, y = NA, ymin = NA, ymax = NA,
+    var = "Median WFU"))
+e <- data.frame(
+  yintercept = c(30, 8, 0.98),
+  var = c("Median WMTBAC", "12.5th perc. WT", "Median WFU"))
+ggplot(d, aes(x = as.Date(x), y = y, ymin = ymin, ymax = ymax)) +
+geom_line() +#colour = "grey30") +
+#geom_ribbon(alpha = 0.3) +
+geom_hline(data = e, aes(yintercept = yintercept), colour = "gray40",
+  linetype = 2) +
+facet_grid(var ~ ., scales = "free_y") +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+  format = "%b %Y") +
+scale_y_continuous(name = "") +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1))
+ggsave(filename = "requirements.pdf", width = 7, height = 5, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "perc10wfu0wfu0wmtbac", "perc10wfu0wfu50wmtbac",
+  "perc10wfu50wfu0wmtbac", "perc10wfu50wfu50wmtbac")]
+d <- na.omit(d)
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+  quantile, probs = 0.5)
+d <- rbind(d,
+  data.frame(date = as.Date(setdiff(seq(from = min(d$date),
+  to = max(d$date), by="1 day"), d$date), origin = "1970-01-01"),
+  perc10wfu0wfu0wmtbac = NA, perc10wfu0wfu50wmtbac = NA,
+  perc10wfu50wfu0wmtbac = NA, perc10wfu50wfu50wmtbac = NA))
+d <- melt(d, id = "date")
+ggplot(d, aes(x = date, y = value / 10000, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = paste("10th perc.   \nWFU in   \n",
+  "the future   ", sep = ""), formatter = "percent", limits = c(0, 1)) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+  format = "%b %Y") +
+scale_linetype_manual(name = paste("Requirements for\nconsidering",
+  "a\nbridge as stable\n"), breaks = c("perc10wfu50wfu50wmtbac",
+  "perc10wfu50wfu0wmtbac", "perc10wfu0wfu50wmtbac",
+  "perc10wfu0wfu0wmtbac"), labels = c("WFU & WMTBAC", "WFU", "WMTBAC",
+  "None"), values = c(1, 3, 2, 4)) +
+opts(plot.title = theme_text(size = 14 * 0.8, face = "bold"),
+  axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1))
+ggsave(filename = "fwfu-sim.pdf", width = 7, height = 3, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "perc10tosa0wfu0wmtbac", "perc10tosa0wfu50wmtbac",
+  "perc10tosa50wfu0wmtbac", "perc10tosa50wfu50wmtbac")]
+d <- na.omit(d)
+d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+  quantile, probs = 0.5)
+d <- rbind(d,
+  data.frame(date = as.Date(setdiff(seq(from = min(d$date),
+  to = max(d$date), by="1 day"), d$date), origin = "1970-01-01"),
+  perc10tosa0wfu0wmtbac = NA, perc10tosa0wfu50wmtbac = NA,
+  perc10tosa50wfu0wmtbac = NA, perc10tosa50wfu50wmtbac = NA))
+d <- melt(d, id = "date")
+ggplot(d, aes(x = date, y = value / 86400, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = paste("10th perc.   \ntime on   \nthe same   \n",
+  "address   \nin days   ", sep = ""),
+  breaks = seq(0, max(d$value / 86400, na.rm = TRUE), 7),
+  minor = seq(0, max(d$value / 86400, na.rm = TRUE), 1),
+  limits = c(0, max(d$value / 86400, na.rm = TRUE))) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+  format = "%b %Y") +
+scale_linetype_manual(name = paste("Requirements for\nconsidering",
+  "a\nbridge as stable\n"), breaks = c("perc10tosa50wfu50wmtbac",
+  "perc10tosa0wfu50wmtbac", "perc10tosa50wfu0wmtbac",
+  "perc10tosa0wfu0wmtbac"), labels = c("WFU & WMTBAC", "WMTBAC", "WFU",
+  "None"), values = c(1, 3, 2, 4)) +
+opts(plot.title = theme_text(size = 14 * 0.8, face = "bold"),
+  axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1))
+ggsave(filename = "tosa-sim.pdf", width = 7, height = 3, dpi = 100)
+
+d <- stability[stability$time > '2010-07' & stability$time < '2011-07', ]
+d <- d[, c("time", "stablebridge0wfu50wmtbac", "stablebridge50wfu0wmtbac",
+  "stablebridge50wfu50wmtbac", "running")]
+d <- na.omit(d)
+#d <- aggregate(d[, 2:length(d)], by = list(date = as.Date(d$time)),
+#  quantile, probs = 0.5)
+d <- rbind(
+  data.frame(time = d$time, y = d$stablebridge0wfu50wmtbac / d$running,
+    variable = "WMTBAC"),
+  data.frame(time = d$time, y = d$stablebridge50wfu0wmtbac / d$running,
+    variable = "WFU"),
+  data.frame(time = d$time, y = d$stablebridge50wfu50wmtbac / d$running,
+    variable = "WFU & WMTBAC"))
+d <- aggregate(list(y = d$y), by = list(x = as.Date(d$time),
+  variable = d$variable), quantile, probs = 0.5)
+missing_dates <- as.Date(setdiff(seq(from = min(d$x, na.rm = TRUE),
+  to = max(d$x, na.rm = TRUE), by="1 day"), d$x), origin = "1970-01-01")
+d <- rbind(d,
+  data.frame(x = missing_dates, y = NA,
+    variable = "WMTBAC"),
+  data.frame(x = missing_dates, y = NA,
+    variable = "WFU"),
+  data.frame(x = missing_dates, y = NA,
+    variable = "WFU & WMTBAC"))
+ggplot(d, aes(x = x, y = y, linetype = variable)) +
+geom_line() +
+scale_y_continuous(name = "Fraction of    \nRunning    \nbridges    ",
+  formatter = "percent", limits = c(0, max(d$y, na.rm = TRUE))) +
+scale_x_date(name = "", major = "3 months", minor = "1 month",
+  format = "%b %Y") +
+scale_linetype_manual(name = paste("\nRequirements for\nconsidering",
+  "a\nbridge as stable\n"), values = c(3, 2, 4)) +
+opts(axis.title.x = theme_text(size = 12 * 0.8, face = "bold",
+  hjust = 0.5),
+  axis.title.y = theme_text(size = 12 * 0.8, face = "bold", vjust = 0.5,
+  hjust = 1))
+ggsave(filename = "stablebridge.pdf", width = 7, height = 3, dpi = 100)
+
diff --git a/task-4255/stale-bridge-tarballs.csv b/task-4255/stale-bridge-tarballs.csv
new file mode 100644
index 0000000..ff430a4
--- /dev/null
+++ b/task-4255/stale-bridge-tarballs.csv
@@ -0,0 +1,760 @@
+2011-01-31 06:07:03
+2011-01-31 06:37:03
+2011-01-31 07:07:03
+2011-01-31 07:37:02
+2011-01-31 08:07:02
+2011-01-31 08:37:03
+2011-01-31 09:07:03
+2011-01-31 09:37:04
+2011-01-31 10:07:03
+2011-01-31 10:37:03
+2011-01-31 11:07:03
+2011-01-31 11:37:03
+2011-01-31 12:07:02
+2011-01-31 12:37:03
+2011-01-31 13:07:03
+2011-01-31 13:37:03
+2011-01-31 14:07:02
+2011-01-31 14:37:03
+2011-01-31 15:07:02
+2011-01-31 15:37:03
+2011-01-31 16:07:02
+2011-01-31 16:37:03
+2011-01-31 17:07:03
+2011-01-31 17:37:02
+2011-01-31 18:07:04
+2011-01-31 18:37:03
+2011-01-31 19:07:03
+2011-01-31 19:37:03
+2011-01-31 20:07:03
+2011-01-31 20:37:02
+2011-01-31 21:07:03
+2011-01-31 21:37:02
+2011-01-31 22:07:02
+2011-01-31 22:37:03
+2011-01-31 23:07:02
+2011-01-31 23:37:03
+2011-02-01 00:07:02
+2011-02-01 00:37:02
+2011-02-01 01:07:02
+2011-02-01 01:37:03
+2011-02-01 02:07:02
+2011-02-01 02:37:02
+2011-02-01 03:07:02
+2011-02-01 03:37:03
+2011-02-01 04:07:02
+2011-02-01 04:37:02
+2011-02-01 05:07:03
+2011-02-01 05:37:03
+2011-02-01 06:07:03
+2011-02-01 06:37:03
+2011-02-01 07:07:03
+2011-02-01 07:37:03
+2011-02-01 08:07:02
+2011-02-01 08:37:03
+2011-02-01 09:07:02
+2011-02-01 09:37:03
+2011-02-01 10:07:02
+2011-02-01 10:37:03
+2011-02-01 11:07:02
+2011-02-01 11:37:02
+2011-02-01 12:07:03
+2011-02-01 12:37:03
+2011-02-01 13:07:03
+2011-02-01 13:37:03
+2011-02-01 14:07:03
+2011-02-01 14:37:03
+2011-02-01 15:07:03
+2011-02-01 15:37:02
+2011-02-01 16:07:02
+2011-02-01 16:37:03
+2011-02-01 17:07:03
+2011-02-01 17:37:03
+2011-02-01 18:07:02
+2011-02-01 18:37:03
+2011-02-01 19:07:03
+2011-02-01 19:37:03
+2011-02-01 20:07:03
+2011-02-01 20:37:02
+2011-02-01 21:07:08
+2011-02-01 21:37:03
+2011-02-01 22:07:03
+2011-02-01 22:37:03
+2011-02-01 23:07:02
+2011-02-01 23:37:03
+2011-02-02 00:07:02
+2011-02-02 00:37:03
+2011-02-02 01:07:02
+2011-02-02 01:37:02
+2011-02-02 02:07:03
+2011-02-02 02:37:02
+2011-02-02 03:07:03
+2011-02-02 03:37:03
+2011-02-02 04:07:02
+2011-02-02 04:37:03
+2011-02-02 05:07:02
+2011-02-02 05:37:03
+2011-02-02 06:07:03
+2011-02-02 06:37:02
+2011-02-02 07:07:03
+2011-02-02 07:37:03
+2011-02-02 08:07:03
+2011-02-02 08:37:02
+2011-02-02 09:07:03
+2011-02-02 09:37:03
+2011-02-02 10:07:03
+2011-02-02 10:37:02
+2011-02-02 11:07:02
+2011-02-02 11:37:02
+2011-02-02 12:07:03
+2011-02-02 12:37:03
+2011-02-02 13:07:02
+2011-02-02 13:37:03
+2011-02-02 14:07:02
+2011-02-02 14:37:03
+2011-02-02 15:07:03
+2011-02-02 15:37:02
+2011-02-02 16:07:03
+2011-02-02 16:37:03
+2011-02-02 17:07:03
+2011-02-02 17:37:03
+2011-02-02 18:07:03
+2011-02-02 18:37:02
+2011-02-02 19:07:03
+2011-02-02 19:37:03
+2011-02-02 20:07:03
+2011-02-02 20:37:03
+2011-02-02 21:07:03
+2011-02-02 21:37:03
+2011-02-02 22:07:03
+2011-02-02 22:37:03
+2011-02-02 23:07:03
+2011-02-02 23:37:03
+2011-02-03 00:07:03
+2011-02-03 00:37:02
+2011-02-03 01:07:03
+2011-02-03 01:37:02
+2011-02-03 02:07:03
+2011-02-03 02:37:02
+2011-02-03 03:07:02
+2011-02-03 03:37:02
+2011-02-03 04:07:03
+2011-02-03 04:37:02
+2011-02-03 05:07:02
+2011-02-03 05:37:03
+2011-02-03 06:07:03
+2011-02-03 06:37:03
+2011-02-03 07:07:02
+2011-02-03 07:37:02
+2011-02-03 08:07:02
+2011-02-03 08:37:02
+2011-02-03 09:07:03
+2011-02-03 09:37:03
+2011-02-03 10:07:02
+2011-02-03 10:37:03
+2011-02-03 11:07:02
+2011-02-03 11:37:03
+2011-02-03 12:07:02
+2011-02-03 12:37:02
+2011-02-03 13:07:03
+2011-02-03 13:37:03
+2011-02-03 14:07:02
+2011-02-03 14:37:03
+2011-02-03 15:07:02
+2011-02-03 15:37:02
+2011-02-03 16:07:03
+2011-02-03 16:37:03
+2011-02-03 17:07:02
+2011-02-03 17:37:03
+2011-02-03 18:07:03
+2011-02-03 18:37:03
+2011-02-03 19:07:02
+2011-02-03 19:37:03
+2011-02-03 20:07:03
+2011-02-03 20:37:03
+2011-02-03 21:07:03
+2011-02-03 21:37:03
+2011-02-03 22:07:03
+2011-02-03 22:37:03
+2011-02-03 23:07:02
+2011-02-03 23:37:03
+2011-02-04 00:07:02
+2011-02-04 00:37:03
+2011-02-04 01:07:03
+2011-02-04 01:37:02
+2011-02-04 02:07:03
+2011-02-04 02:37:03
+2011-02-04 03:07:03
+2011-02-04 03:37:02
+2011-02-04 04:07:02
+2011-02-04 04:37:03
+2011-02-04 05:07:02
+2011-02-04 05:37:03
+2011-02-04 06:07:03
+2011-02-04 06:37:03
+2011-02-04 07:07:03
+2011-02-04 07:37:03
+2011-02-04 08:07:03
+2011-02-04 08:37:03
+2011-02-04 09:07:02
+2011-02-04 09:37:03
+2011-02-04 10:07:03
+2011-02-04 10:37:03
+2011-02-04 11:07:03
+2011-02-04 11:37:02
+2011-02-04 12:07:03
+2011-02-04 12:37:02
+2011-02-04 13:07:03
+2011-02-04 13:37:03
+2011-02-04 14:07:03
+2011-02-04 14:37:02
+2011-02-04 15:07:03
+2011-02-04 15:37:03
+2011-02-04 16:07:03
+2011-02-04 16:37:02
+2011-02-04 17:07:03
+2011-02-04 17:37:03
+2011-02-04 18:07:03
+2011-02-04 18:37:03
+2011-02-04 19:07:03
+2011-02-04 19:37:03
+2011-02-04 20:07:03
+2011-02-04 20:37:03
+2011-02-04 21:07:02
+2011-02-04 21:37:02
+2011-02-04 22:07:02
+2011-02-04 22:37:03
+2011-02-04 23:07:03
+2011-02-04 23:37:03
+2011-02-05 00:07:03
+2011-02-05 00:37:02
+2011-02-05 01:07:03
+2011-02-05 01:37:03
+2011-02-05 02:07:03
+2011-02-05 02:37:02
+2011-02-05 03:07:03
+2011-02-05 03:37:02
+2011-02-05 04:07:03
+2011-02-05 04:37:03
+2011-02-05 05:07:03
+2011-02-05 05:37:05
+2011-02-05 06:07:02
+2011-02-05 06:37:02
+2011-02-05 07:07:02
+2011-02-05 07:37:03
+2011-02-05 08:07:03
+2011-02-05 08:37:03
+2011-02-05 09:07:03
+2011-02-05 09:37:02
+2011-02-05 10:07:03
+2011-02-05 10:37:03
+2011-02-05 11:07:02
+2011-02-05 11:37:02
+2011-02-05 12:07:03
+2011-02-05 12:37:03
+2011-02-05 13:07:02
+2011-02-05 13:37:03
+2011-02-05 14:07:04
+2011-02-05 14:37:03
+2011-02-05 15:07:03
+2011-02-05 15:37:03
+2011-02-05 16:07:02
+2011-02-05 16:37:03
+2011-02-05 17:07:03
+2011-02-05 17:37:03
+2011-02-05 18:07:03
+2011-02-05 18:37:03
+2011-02-05 19:07:02
+2011-02-05 19:37:03
+2011-02-05 20:07:03
+2011-02-05 20:37:03
+2011-02-05 21:07:02
+2011-02-05 21:37:03
+2011-02-05 22:07:03
+2011-02-05 22:37:03
+2011-02-05 23:07:03
+2011-02-05 23:37:02
+2011-02-06 00:07:03
+2011-02-06 00:37:02
+2011-02-06 01:07:03
+2011-02-06 01:37:02
+2011-02-06 02:07:03
+2011-02-06 02:37:03
+2011-02-06 03:07:03
+2011-02-06 03:37:06
+2011-02-06 04:07:03
+2011-02-06 04:37:03
+2011-02-06 05:07:03
+2011-02-06 05:37:03
+2011-02-06 06:07:03
+2011-02-06 06:37:03
+2011-02-06 07:07:02
+2011-02-06 07:37:02
+2011-02-06 08:07:02
+2011-02-06 08:37:03
+2011-02-06 09:07:02
+2011-02-06 09:37:02
+2011-02-06 10:07:02
+2011-02-06 10:37:02
+2011-02-06 11:07:02
+2011-02-06 11:37:02
+2011-02-06 12:07:03
+2011-02-06 12:37:02
+2011-02-06 13:07:02
+2011-02-06 13:37:03
+2011-02-06 14:07:03
+2011-02-06 14:37:02
+2011-02-06 15:07:02
+2011-02-06 15:37:03
+2011-02-06 16:07:03
+2011-02-06 16:37:03
+2011-02-06 17:07:03
+2011-02-06 17:37:02
+2011-02-06 18:07:03
+2011-02-06 18:37:02
+2011-02-06 19:07:02
+2011-02-06 19:37:02
+2011-02-06 20:07:03
+2011-02-06 20:37:02
+2011-02-06 21:07:03
+2011-02-06 21:37:03
+2011-02-06 22:07:03
+2011-02-06 22:37:02
+2011-02-06 23:07:02
+2011-02-06 23:37:03
+2011-02-07 00:07:03
+2011-02-07 00:37:02
+2011-02-07 01:07:03
+2011-02-07 01:37:02
+2011-02-07 02:07:02
+2011-02-07 02:37:02
+2011-02-07 03:07:02
+2011-02-07 03:37:03
+2011-02-07 04:07:02
+2011-02-07 04:37:02
+2011-02-07 05:07:02
+2011-02-07 05:37:03
+2011-02-07 06:07:03
+2011-02-07 06:37:02
+2011-02-07 07:07:03
+2011-02-07 07:37:03
+2011-02-07 08:07:03
+2011-02-07 08:37:03
+2011-02-07 09:07:03
+2011-02-07 09:37:02
+2011-02-07 10:07:02
+2011-02-07 10:37:02
+2011-02-07 11:07:02
+2011-02-07 11:37:02
+2011-02-07 12:07:03
+2011-02-07 12:37:03
+2011-02-07 13:07:03
+2011-02-07 13:37:02
+2011-02-07 14:07:03
+2011-02-07 14:37:03
+2011-02-07 15:07:03
+2011-02-07 15:37:03
+2011-02-07 16:07:02
+2011-02-07 16:37:03
+2011-02-07 17:07:02
+2011-02-07 17:37:02
+2011-02-07 18:07:03
+2011-02-07 18:37:03
+2011-02-07 19:07:02
+2011-02-07 19:37:02
+2011-02-07 20:07:03
+2011-02-07 20:37:03
+2011-02-07 21:07:06
+2011-02-07 21:37:02
+2011-02-07 22:07:02
+2011-02-07 22:37:03
+2011-02-07 23:07:03
+2011-02-07 23:37:02
+2011-02-08 00:07:02
+2011-02-08 00:37:02
+2011-02-08 01:07:03
+2011-02-08 01:37:03
+2011-02-08 02:07:02
+2011-02-08 02:37:03
+2011-02-08 03:07:03
+2011-02-08 03:37:03
+2011-02-08 04:07:02
+2011-02-08 04:37:03
+2011-02-08 05:07:02
+2011-02-08 05:37:03
+2011-02-08 06:07:03
+2011-02-08 06:37:02
+2011-02-08 07:07:03
+2011-02-08 07:37:03
+2011-02-08 08:07:02
+2011-02-08 08:37:03
+2011-02-08 09:07:03
+2011-02-08 09:37:02
+2011-02-08 10:07:02
+2011-02-08 10:37:03
+2011-02-08 11:07:02
+2011-02-08 11:37:02
+2011-02-08 12:07:02
+2011-02-08 12:37:02
+2011-02-08 13:07:03
+2011-02-08 13:37:02
+2011-02-08 14:07:03
+2011-02-08 14:37:02
+2011-02-08 15:07:03
+2011-02-08 15:37:02
+2011-02-08 16:07:03
+2011-02-08 16:37:03
+2011-02-08 17:07:03
+2011-02-08 17:37:03
+2011-02-08 18:07:03
+2011-02-08 18:37:02
+2011-02-08 19:07:03
+2011-02-08 19:37:03
+2011-02-08 20:07:03
+2011-02-08 20:37:03
+2011-02-08 21:07:03
+2011-02-08 21:37:03
+2011-02-08 22:07:03
+2011-02-08 22:37:05
+2011-02-08 23:07:03
+2011-02-08 23:37:02
+2011-02-09 00:07:02
+2011-02-09 00:37:02
+2011-02-09 01:07:02
+2011-02-09 01:37:03
+2011-02-09 02:07:02
+2011-02-09 02:37:03
+2011-02-09 03:07:03
+2011-02-09 03:37:03
+2011-02-09 04:07:03
+2011-02-09 04:37:03
+2011-02-09 05:07:02
+2011-02-09 05:37:03
+2011-02-09 06:07:03
+2011-02-09 06:37:03
+2011-02-09 07:07:03
+2011-02-09 07:37:03
+2011-02-09 08:07:02
+2011-02-09 08:37:02
+2011-02-09 09:07:03
+2011-02-09 09:37:03
+2011-02-09 10:07:03
+2011-02-09 10:37:02
+2011-02-09 11:07:02
+2011-02-09 11:37:03
+2011-02-09 12:07:03
+2011-02-09 12:37:03
+2011-02-09 13:07:02
+2011-02-09 13:37:02
+2011-02-09 14:07:03
+2011-02-09 14:37:02
+2011-02-09 15:07:03
+2011-02-09 15:37:02
+2011-02-09 16:07:03
+2011-02-09 16:37:02
+2011-02-09 17:07:02
+2011-02-09 17:37:03
+2011-02-09 18:07:03
+2011-02-09 18:37:03
+2011-02-09 19:07:03
+2011-02-09 19:37:03
+2011-02-09 20:07:03
+2011-02-09 20:37:02
+2011-02-09 21:07:02
+2011-02-09 21:37:03
+2011-02-09 22:07:03
+2011-02-09 22:37:03
+2011-02-09 23:07:02
+2011-02-09 23:37:03
+2011-02-10 00:07:03
+2011-02-10 00:37:03
+2011-02-10 01:07:02
+2011-02-10 01:37:02
+2011-02-10 02:07:03
+2011-02-10 02:37:02
+2011-02-10 03:07:02
+2011-02-10 03:37:03
+2011-02-10 04:07:03
+2011-02-10 04:37:02
+2011-02-10 05:07:03
+2011-02-10 05:37:03
+2011-02-10 06:07:03
+2011-02-10 06:37:02
+2011-02-10 07:07:02
+2011-02-10 07:37:03
+2011-02-10 08:07:03
+2011-02-10 08:37:03
+2011-02-10 09:07:02
+2011-02-10 09:37:02
+2011-02-10 10:07:02
+2011-02-10 10:37:03
+2011-02-10 11:07:03
+2011-02-10 11:37:03
+2011-02-10 12:07:03
+2011-02-10 12:37:02
+2011-02-10 13:07:03
+2011-02-10 13:37:02
+2011-02-10 14:07:03
+2011-02-10 14:37:03
+2011-02-10 15:07:02
+2011-02-10 15:37:03
+2011-02-10 16:07:03
+2011-02-10 16:37:03
+2011-02-10 17:07:02
+2011-02-10 17:37:03
+2011-02-10 18:07:02
+2011-02-10 18:37:03
+2011-02-10 19:07:02
+2011-02-10 19:37:02
+2011-02-10 20:07:03
+2011-02-10 20:37:03
+2011-02-10 21:07:03
+2011-02-10 21:37:03
+2011-02-10 22:07:03
+2011-02-10 22:37:03
+2011-02-10 23:07:03
+2011-02-10 23:37:05
+2011-02-11 00:07:03
+2011-02-11 00:37:03
+2011-02-11 01:07:03
+2011-02-11 01:37:03
+2011-02-11 02:07:03
+2011-02-11 02:37:02
+2011-02-11 03:07:02
+2011-02-11 03:37:03
+2011-02-11 04:07:03
+2011-02-11 04:37:03
+2011-02-11 05:07:02
+2011-02-11 05:37:02
+2011-02-11 06:07:03
+2011-02-11 06:37:03
+2011-02-11 07:07:03
+2011-02-11 07:37:03
+2011-02-11 08:07:03
+2011-02-11 08:37:03
+2011-02-11 09:07:03
+2011-02-11 09:37:03
+2011-02-11 10:07:03
+2011-02-11 10:37:02
+2011-02-11 11:07:02
+2011-02-11 11:37:03
+2011-02-11 12:07:03
+2011-02-11 12:37:03
+2011-02-11 13:07:03
+2011-02-11 13:37:02
+2011-02-11 14:07:03
+2011-02-11 14:37:03
+2011-02-11 15:07:03
+2011-02-11 15:37:04
+2011-02-11 16:07:03
+2011-02-11 16:37:03
+2011-02-11 17:07:03
+2011-02-11 17:37:03
+2011-02-11 18:07:03
+2011-02-11 18:37:03
+2011-02-11 19:07:03
+2011-02-11 19:37:03
+2011-02-11 20:07:03
+2011-02-11 20:37:03
+2011-02-11 21:07:03
+2011-02-11 21:37:03
+2011-02-11 22:07:02
+2011-02-11 22:37:02
+2011-02-11 23:07:02
+2011-02-11 23:37:03
+2011-02-12 00:07:02
+2011-02-12 00:37:02
+2011-02-12 01:07:03
+2011-02-12 01:37:03
+2011-02-12 02:07:02
+2011-02-12 02:37:02
+2011-02-12 03:07:02
+2011-02-12 03:37:03
+2011-02-12 04:07:02
+2011-02-12 04:37:03
+2011-02-12 05:07:02
+2011-02-12 05:37:02
+2011-02-12 06:07:03
+2011-02-12 06:37:03
+2011-02-12 07:07:02
+2011-02-12 07:37:03
+2011-02-12 08:07:03
+2011-02-12 08:37:03
+2011-02-12 09:07:03
+2011-02-12 09:37:03
+2011-02-12 10:07:02
+2011-02-12 10:37:03
+2011-02-12 11:07:03
+2011-02-12 11:37:03
+2011-02-12 12:07:03
+2011-02-12 12:37:03
+2011-02-12 13:07:03
+2011-02-12 13:37:03
+2011-02-12 14:07:03
+2011-02-12 14:37:02
+2011-02-12 15:07:02
+2011-02-12 15:37:03
+2011-02-12 16:07:03
+2011-02-12 16:37:03
+2011-02-12 17:07:03
+2011-02-12 17:37:03
+2011-02-12 18:07:03
+2011-02-12 18:37:02
+2011-02-12 19:07:02
+2011-02-12 19:37:03
+2011-02-12 20:07:03
+2011-02-12 20:37:03
+2011-02-12 21:07:03
+2011-02-12 21:37:03
+2011-02-12 22:07:03
+2011-02-12 22:37:03
+2011-02-12 23:07:03
+2011-02-12 23:37:03
+2011-02-13 00:07:02
+2011-02-13 00:37:03
+2011-02-13 01:07:03
+2011-02-13 01:37:02
+2011-02-13 02:07:02
+2011-02-13 02:37:03
+2011-02-13 03:07:02
+2011-02-13 03:37:03
+2011-02-13 04:07:03
+2011-02-13 04:37:03
+2011-02-13 05:07:03
+2011-02-13 05:37:03
+2011-02-13 06:07:03
+2011-02-13 06:37:03
+2011-02-13 07:07:02
+2011-02-13 07:37:02
+2011-02-13 08:07:03
+2011-02-13 08:37:03
+2011-02-13 09:07:03
+2011-02-13 09:37:03
+2011-02-13 10:07:03
+2011-02-13 10:37:02
+2011-02-13 11:07:03
+2011-02-13 11:37:03
+2011-02-13 12:07:03
+2011-02-13 12:37:02
+2011-02-13 13:07:02
+2011-02-13 13:37:03
+2011-02-13 14:07:03
+2011-02-13 14:37:02
+2011-02-13 15:07:02
+2011-02-13 15:37:03
+2011-02-13 16:07:04
+2011-02-13 16:37:03
+2011-02-13 17:07:03
+2011-02-13 17:37:03
+2011-02-13 18:07:02
+2011-02-13 18:37:03
+2011-02-13 19:07:03
+2011-02-13 19:37:03
+2011-02-13 20:07:03
+2011-02-13 20:37:03
+2011-02-13 21:07:03
+2011-02-13 21:37:02
+2011-02-13 22:07:03
+2011-02-13 22:37:03
+2011-02-13 23:07:02
+2011-02-13 23:37:02
+2011-02-14 00:07:03
+2011-02-14 00:37:02
+2011-02-14 01:07:03
+2011-02-14 01:37:02
+2011-02-14 02:07:03
+2011-02-14 02:37:03
+2011-02-14 03:07:03
+2011-02-14 03:37:03
+2011-02-14 04:07:02
+2011-02-14 04:37:02
+2011-02-14 05:07:02
+2011-02-14 05:37:03
+2011-02-14 06:07:03
+2011-02-14 06:37:02
+2011-02-14 07:07:02
+2011-02-14 07:37:02
+2011-02-14 08:07:02
+2011-02-14 08:37:03
+2011-02-14 09:07:02
+2011-02-14 09:37:02
+2011-02-14 10:07:03
+2011-02-14 10:37:03
+2011-02-14 11:07:03
+2011-02-14 11:37:02
+2011-02-14 12:07:03
+2011-02-14 12:37:03
+2011-02-14 13:07:03
+2011-02-14 13:37:02
+2011-02-14 14:07:02
+2011-02-14 14:37:03
+2011-02-14 15:07:03
+2011-02-14 15:37:02
+2011-02-14 16:07:02
+2011-02-14 16:37:03
+2011-02-14 17:07:02
+2011-02-14 17:37:03
+2011-02-14 18:07:03
+2011-02-14 18:37:03
+2011-02-14 19:07:02
+2011-02-14 19:37:03
+2011-02-14 20:07:03
+2011-02-14 20:37:03
+2011-02-14 21:07:03
+2011-02-14 21:37:03
+2011-02-14 22:07:03
+2011-02-14 22:37:03
+2011-02-14 23:07:03
+2011-02-14 23:37:02
+2011-02-15 00:07:03
+2011-02-15 00:37:02
+2011-02-15 01:07:03
+2011-02-15 01:37:03
+2011-02-15 02:07:03
+2011-02-15 02:37:03
+2011-02-15 03:07:03
+2011-02-15 03:37:02
+2011-02-15 04:07:03
+2011-02-15 04:37:03
+2011-02-15 05:07:02
+2011-02-15 05:37:02
+2011-02-15 06:07:02
+2011-02-15 06:37:02
+2011-02-15 07:07:03
+2011-02-15 07:37:02
+2011-02-15 08:07:03
+2011-02-15 08:37:02
+2011-02-15 09:07:02
+2011-02-15 09:37:03
+2011-02-15 10:07:02
+2011-02-15 10:37:02
+2011-02-15 11:07:03
+2011-02-15 11:37:03
+2011-02-15 12:07:03
+2011-02-15 12:37:02
+2011-02-15 13:07:02
+2011-02-15 13:37:02
+2011-02-15 14:07:03
+2011-02-15 14:37:02
+2011-02-15 15:07:02
+2011-02-15 15:37:02
+2011-02-15 16:07:02
+2011-02-15 16:37:02
+2011-02-15 17:07:03
+2011-02-15 17:37:03
+2011-02-15 18:07:03
+2011-02-15 18:37:03
+2011-02-15 19:07:03
+2011-02-15 19:37:02
+2011-02-15 20:07:03
+2011-02-15 20:37:03
+2011-02-15 21:07:03
+2011-02-15 21:37:03
+2011-02-15 22:07:03
+2011-02-15 22:37:03
+2011-02-15 23:07:02
+2011-02-15 23:37:02
+2011-02-16 00:07:03
+2011-02-16 00:37:02
+2011-02-16 01:07:04
+2011-02-16 01:37:02



More information about the tor-commits mailing list