[tor-commits] [metrics-lib/master] Parse v1 directories and contained server descriptors.

karsten at torproject.org karsten at torproject.org
Sat May 19 18:19:28 UTC 2012


commit 466725e2ea478420fddd4a8bc6d6e199d5d31b8e
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Sat May 19 19:30:28 2012 +0200

    Parse v1 directories and contained server descriptors.
---
 .../torproject/descriptor/NetworkStatusEntry.java  |    2 +
 src/org/torproject/descriptor/RelayDirectory.java  |   41 ++
 .../torproject/descriptor/RouterStatusEntry.java   |   20 +
 .../torproject/descriptor/ServerDescriptor.java    |    4 +-
 .../descriptor/impl/BandwidthHistoryImpl.java      |   38 +-
 .../torproject/descriptor/impl/DescriptorImpl.java |    5 +
 .../descriptor/impl/RelayDirectoryImpl.java        |  513 ++++++++++++++++++++
 .../descriptor/impl/RouterStatusEntryImpl.java     |   37 ++
 .../descriptor/impl/ServerDescriptorImpl.java      |   28 +-
 9 files changed, 671 insertions(+), 17 deletions(-)

diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java
index ba5939c..3bda176 100644
--- a/src/org/torproject/descriptor/NetworkStatusEntry.java
+++ b/src/org/torproject/descriptor/NetworkStatusEntry.java
@@ -5,6 +5,8 @@ package org.torproject.descriptor;
 import java.util.List;
 import java.util.SortedSet;
 
+/* Status entry contained in a network status with version 2 or higher or
+ * in a bridge network status. */
 public interface NetworkStatusEntry {
 
   /* Return the raw status entry bytes. */
diff --git a/src/org/torproject/descriptor/RelayDirectory.java b/src/org/torproject/descriptor/RelayDirectory.java
new file mode 100644
index 0000000..b8a9472
--- /dev/null
+++ b/src/org/torproject/descriptor/RelayDirectory.java
@@ -0,0 +1,41 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+import java.util.List;
+
+/* Contains a v1 signed directory. */
+public interface RelayDirectory extends Descriptor {
+
+  /* Return the published time in milliseconds. */
+  public long getPublishedMillis();
+
+  /* Return the directory signing key digest. */
+  public String getDirSigningKey();
+
+  /* Return recommended software versions or null if the directory doesn't
+   * list recommended software. */
+  public List<String> getRecommendedSoftware();
+
+  /* Return the directory signature. */
+  public String getDirectorySignature();
+
+  /* Return router status entries, one for each contained relay. */
+  public List<RouterStatusEntry> getRouterStatusEntries();
+
+  /* Return a list of server descriptors contained in the signed
+   * directory. */
+  public List<ServerDescriptor> getServerDescriptors();
+
+  /* Return a (very likely empty) list of exceptions from parsing the
+   * contained server descriptors. */
+  public List<Exception> getServerDescriptorParseExceptions();
+
+  /* Return the directory nickname. */
+  public String getNickname();
+
+  /* Return the directory digest that the directory authority used to sign
+   * the directory. */
+  public String getDirectoryDigest();
+}
+
diff --git a/src/org/torproject/descriptor/RouterStatusEntry.java b/src/org/torproject/descriptor/RouterStatusEntry.java
new file mode 100644
index 0000000..6652e94
--- /dev/null
+++ b/src/org/torproject/descriptor/RouterStatusEntry.java
@@ -0,0 +1,20 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+/* Status entry contained in a v1 signed directory. */
+public interface RouterStatusEntry {
+
+  /* Return the relay nickname, or null if the relay is unverified. */
+  public String getNickname();
+
+  /* Return the relay fingerprint. */
+  public String getFingerprint();
+
+  /* Return whether the relay is verified. */
+  public boolean isVerified();
+
+  /* Return whether the relay is live. */
+  public boolean isLive();
+}
+
diff --git a/src/org/torproject/descriptor/ServerDescriptor.java b/src/org/torproject/descriptor/ServerDescriptor.java
index bcc4913..a7f76c7 100644
--- a/src/org/torproject/descriptor/ServerDescriptor.java
+++ b/src/org/torproject/descriptor/ServerDescriptor.java
@@ -40,7 +40,9 @@ public interface ServerDescriptor extends Descriptor {
   public int getBandwidthBurst();
 
   /* Return the observed bandwidth in bytes per second as an estimate of
-   * the capacity that the relay can handle. */
+   * the capacity that the relay can handle, or -1 if the descriptor
+   * doesn't contain an observed bandwidth value (which is the case for
+   * Tor versions 0.0.8 or older). */
   public int getBandwidthObserved();
 
   /* Return the platform string containing the Tor software version and
diff --git a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java
index 56ae512..65def52 100644
--- a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java
+++ b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java
@@ -12,32 +12,42 @@ public class BandwidthHistoryImpl implements BandwidthHistory {
   protected BandwidthHistoryImpl(String line, String lineNoOpt,
       String[] partsNoOpt) throws DescriptorParseException {
     boolean isValid = false;
-    if (partsNoOpt.length >= 5) {
+    this.line = line;
+    if (lineNoOpt.startsWith("read-history  ")) {
+      lineNoOpt = "read-history "
+          + lineNoOpt.substring("read-history  ".length());
+      partsNoOpt = lineNoOpt.split(" ");
+    }
+    if (partsNoOpt.length == 5 || partsNoOpt.length == 6) {
       try {
-        this.line = line;
-        if (lineNoOpt.startsWith("read-history  ")) {
-          lineNoOpt = "read-history "
-              + lineNoOpt.substring("read-history  ".length());
-          partsNoOpt = lineNoOpt.split(" ");
-        }
         this.historyEndMillis = ParseHelper.parseTimestampAtIndex(line,
             partsNoOpt, 1, 2);
         if (partsNoOpt[3].startsWith("(") &&
-            partsNoOpt[4].equals("s)")) {
+            partsNoOpt[4].startsWith("s)")) {
           this.intervalLength = Long.parseLong(partsNoOpt[3].
               substring(1));
           if (this.intervalLength <= 0L) {
             throw new DescriptorParseException("Only positive interval "
                 + "lengths are allowed in line '" + line + "'.");
           }
-          if (partsNoOpt.length > 6) {
-            /* Invalid line, handle below. */
-          } else if (partsNoOpt.length == 5) {
-            /* No bandwidth values to parse. */
+          String[] values = null;
+          if (partsNoOpt.length == 5 &&
+              partsNoOpt[4].equals("s)")) {
+            /* There are no bandwidth values to parse. */
             isValid = true;
-          } else {
+          } else if (partsNoOpt.length == 6) {
+            /* There are bandwidth values to parse. */
+            values = partsNoOpt[5].split(",", -1);
+          } else if (partsNoOpt[4].length() > 2) {
+            /* There are bandwidth values to parse, but there is no space
+             * between "s)" and "0,0,0,0".  Very old Tor versions around
+             * Tor 0.0.8 wrote such history lines, and even though
+             * dir-spec.txt implies a space here, the old format isn't
+             * totally broken.  Let's pretend there's a space. */
+            values = partsNoOpt[4].substring(2).split(",", -1);
+          }
+          if (values != null) {
             long endMillis = this.historyEndMillis;
-            String[] values = partsNoOpt[5].split(",", -1);
             for (int i = values.length - 1; i >= 0; i--) {
               long bandwidthValue = Long.parseLong(values[i]);
               if (bandwidthValue < 0L) {
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java
index afaacc7..27e5153 100644
--- a/src/org/torproject/descriptor/impl/DescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java
@@ -78,6 +78,11 @@ public abstract class DescriptorImpl implements Descriptor {
         firstLines.contains("\nnetwork-status-version 2\n")) {
       parsedDescriptors.add(new RelayNetworkStatusImpl(rawDescriptorBytes,
           failUnrecognizedDescriptorLines));
+    } else if (firstLines.startsWith("@type directory 1.0\n") ||
+        firstLines.startsWith("signed-directory\n") ||
+        firstLines.contains("\nsigned-directory\n")) {
+      parsedDescriptors.add(new RelayDirectoryImpl(rawDescriptorBytes,
+          failUnrecognizedDescriptorLines));
     } else {
       throw new DescriptorParseException("Could not detect descriptor "
           + "type in descriptor starting with '" + firstLines + "'.");
diff --git a/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java b/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java
new file mode 100644
index 0000000..03d727e
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java
@@ -0,0 +1,513 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.torproject.descriptor.RelayDirectory;
+import org.torproject.descriptor.RouterStatusEntry;
+import org.torproject.descriptor.ServerDescriptor;
+
+/* TODO Write unit tests. */
+
+public class RelayDirectoryImpl extends DescriptorImpl
+    implements RelayDirectory {
+
+  protected static List<RelayDirectory> parseDirectories(
+      byte[] directoriesBytes, boolean failUnrecognizedDescriptorLines)
+      throws DescriptorParseException {
+    List<RelayDirectory> parsedDirectories =
+        new ArrayList<RelayDirectory>();
+    List<byte[]> splitDirectoriesBytes =
+        DescriptorImpl.splitRawDescriptorBytes(directoriesBytes,
+        "signed-directory\n");
+    for (byte[] directoryBytes : splitDirectoriesBytes) {
+      RelayDirectory parsedDirectory =
+          new RelayDirectoryImpl(directoryBytes,
+          failUnrecognizedDescriptorLines);
+      parsedDirectories.add(parsedDirectory);
+    }
+    return parsedDirectories;
+  }
+
+  protected RelayDirectoryImpl(byte[] directoryBytes,
+      boolean failUnrecognizedDescriptorLines)
+      throws DescriptorParseException {
+    super(directoryBytes, failUnrecognizedDescriptorLines, true);
+    this.splitAndParseParts(rawDescriptorBytes);
+    this.calculateDigest();
+    Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
+        "signed-directory,recommended-software,"
+        + "directory-signature").split(",")));
+    this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+    Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList(
+        "dir-signing-key,running-routers,router-status".split(",")));
+    this.checkAtMostOnceKeywords(atMostOnceKeywords);
+    this.checkFirstKeyword("signed-directory");
+  }
+
+  private void calculateDigest() throws DescriptorParseException {
+    try {
+      String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
+      String startToken = "signed-directory\n";
+      String sigToken = "\ndirectory-signature ";
+      if (!ascii.contains(sigToken)) {
+        return;
+      }
+      int start = ascii.indexOf(startToken);
+      int sig = ascii.indexOf(sigToken) + sigToken.length();
+      sig = ascii.indexOf("\n", sig) + 1;
+      if (start >= 0 && sig >= 0 && sig > start) {
+        byte[] forDigest = new byte[sig - start];
+        System.arraycopy(this.getRawDescriptorBytes(), start,
+            forDigest, 0, sig - start);
+        this.directoryDigest = DigestUtils.shaHex(forDigest);
+      }
+    } catch (UnsupportedEncodingException e) {
+      /* Handle below. */
+    }
+    if (this.directoryDigest == null) {
+      throw new DescriptorParseException("Could not calculate v1 "
+          + "directory digest.");
+    }
+  }
+
+  private void splitAndParseParts(byte[] rawDescriptorBytes)
+      throws DescriptorParseException {
+    if (this.rawDescriptorBytes.length == 0) {
+      throw new DescriptorParseException("Descriptor is empty.");
+    }
+    String descriptorString = new String(rawDescriptorBytes);
+    int startIndex = 0;
+    int firstRouterIndex = this.findFirstIndexOfKeyword(descriptorString,
+        "router");
+    int directorySignatureIndex = this.findFirstIndexOfKeyword(
+        descriptorString, "directory-signature");
+    int endIndex = descriptorString.length();
+    if (directorySignatureIndex < 0) {
+      directorySignatureIndex = endIndex;
+    }
+    if (firstRouterIndex < 0) {
+      firstRouterIndex = directorySignatureIndex;
+    }
+    if (firstRouterIndex > startIndex) {
+      this.parseHeaderBytes(descriptorString, startIndex,
+          firstRouterIndex);
+    }
+    if (directorySignatureIndex > firstRouterIndex) {
+      this.parseServerDescriptorBytes(descriptorString, firstRouterIndex,
+          directorySignatureIndex);
+    }
+    if (endIndex > directorySignatureIndex) {
+      this.parseDirectorySignatureBytes(descriptorString,
+          directorySignatureIndex, endIndex);
+    }
+  }
+
+  private int findFirstIndexOfKeyword(String descriptorString,
+      String keyword) {
+    if (descriptorString.startsWith(keyword)) {
+      return 0;
+    } else if (descriptorString.contains("\n" + keyword + " ")) {
+      return descriptorString.indexOf("\n" + keyword + " ") + 1;
+    } else if (descriptorString.contains("\n" + keyword + "\n")) {
+      return descriptorString.indexOf("\n" + keyword + "\n") + 1;
+    } else {
+      return -1;
+    }
+  }
+
+  private void parseHeaderBytes(String descriptorString, int start,
+      int end) throws DescriptorParseException {
+    byte[] headerBytes = new byte[end - start];
+    System.arraycopy(this.rawDescriptorBytes, start,
+        headerBytes, 0, end - start);
+    this.parseHeader(headerBytes);
+  }
+
+  private void parseServerDescriptorBytes(String descriptorString,
+      int start, int end) throws DescriptorParseException {
+    List<byte[]> splitServerDescriptorBytes =
+        this.splitByKeyword(descriptorString, "router", start, end);
+    for (byte[] statusEntryBytes : splitServerDescriptorBytes) {
+      this.parseServerDescriptor(statusEntryBytes);
+    }
+  }
+
+  private void parseDirectorySignatureBytes(String descriptorString,
+      int start, int end) throws DescriptorParseException {
+    List<byte[]> splitDirectorySignatureBytes = this.splitByKeyword(
+        descriptorString, "directory-signature", start, end);
+    for (byte[] directorySignatureBytes : splitDirectorySignatureBytes) {
+      this.parseDirectorySignature(directorySignatureBytes);
+    }
+  }
+
+  private List<byte[]> splitByKeyword(String descriptorString,
+      String keyword, int start, int end) {
+    List<byte[]> splitParts = new ArrayList<byte[]>();
+    int from = start;
+    while (from < end) {
+      int to = descriptorString.indexOf("\n" + keyword + " ", from);
+      if (to < 0) {
+        to = descriptorString.indexOf("\n" + keyword + "\n", from);
+      }
+      if (to < 0) {
+        to = end;
+      } else {
+        to += 1;
+      }
+      int toNoNewline = to;
+      while (toNoNewline > from &&
+          descriptorString.charAt(toNoNewline - 1) == '\n') {
+        toNoNewline--;
+      }
+      byte[] part = new byte[toNoNewline - from];
+      System.arraycopy(this.rawDescriptorBytes, from, part, 0,
+          toNoNewline - from);
+      from = to;
+      splitParts.add(part);
+    }
+    return splitParts;
+  }
+
+  private void parseHeader(byte[] headerBytes)
+      throws DescriptorParseException {
+    Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n");
+    String publishedLine = null, nextCrypto = null,
+        runningRoutersLine = null, routerStatusLine = null;
+    StringBuilder crypto = null;
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.isEmpty() || line.startsWith("@")) {
+        continue;
+      }
+      String lineNoOpt = line.startsWith("opt ") ?
+          line.substring("opt ".length()) : line;
+      String[] partsNoOpt = lineNoOpt.split(" ");
+      String keyword = partsNoOpt[0];
+      if (keyword.equals("signed-directory")) {
+        this.parseSignedDirectoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("published")) {
+        if (publishedLine != null) {
+          throw new DescriptorParseException("Keyword 'published' is "
+              + "contained more than once, but must be contained exactly "
+              + "once.");
+        } else {
+          publishedLine = line;
+        }
+      } else if (keyword.equals("dir-signing-key")) {
+        this.parseDirSigningKeyLine(line, lineNoOpt, partsNoOpt);
+        nextCrypto = "dir-signing-key";
+      } else if (keyword.equals("recommended-software")) {
+        this.parseRecommendedSoftwareLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("running-routers")) {
+        runningRoutersLine = line;
+      } else if (keyword.equals("router-status")) {
+        routerStatusLine = line;
+      } else if (line.startsWith("-----BEGIN")) {
+        crypto = new StringBuilder();
+        crypto.append(line + "\n");
+      } else if (line.startsWith("-----END")) {
+        crypto.append(line + "\n");
+        String cryptoString = crypto.toString();
+        crypto = null;
+        if (nextCrypto.equals("dir-signing-key") &&
+            this.dirSigningKey == null) {
+          this.dirSigningKey = cryptoString;
+        } else {
+          throw new DescriptorParseException("Unrecognized crypto "
+              + "block in v1 directory.");
+        }
+        nextCrypto = null;
+      } else if (crypto != null) {
+        crypto.append(line + "\n");
+      } else {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in v1 directory.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
+          }
+          this.unrecognizedLines.add(line);
+        }
+      }
+    }
+    if (publishedLine == null) {
+      throw new DescriptorParseException("Keyword 'published' is "
+          + "contained 0 times, but must be contained exactly once.");
+    } else {
+      String publishedLineNoOpt = publishedLine.startsWith("opt ") ?
+          publishedLine.substring("opt ".length()) : publishedLine;
+      String[] publishedPartsNoOpt = publishedLineNoOpt.split(" ");
+      this.parsePublishedLine(publishedLine, publishedLineNoOpt,
+          publishedPartsNoOpt);
+    }
+    if (routerStatusLine != null) {
+      String routerStatusLineNoOpt = routerStatusLine.startsWith("opt ") ?
+          routerStatusLine.substring("opt ".length()) : routerStatusLine;
+      String[] routerStatusPartsNoOpt = routerStatusLineNoOpt.split(" ");
+      this.parseRouterStatusLine(routerStatusLine, routerStatusLineNoOpt,
+          routerStatusPartsNoOpt);
+    } else if (runningRoutersLine != null) {
+      String runningRoutersLineNoOpt =
+          runningRoutersLine.startsWith("opt ") ?
+          runningRoutersLine.substring("opt ".length()) :
+          runningRoutersLine;
+      String[] runningRoutersPartsNoOpt =
+          runningRoutersLineNoOpt.split(" ");
+      this.parseRunningRoutersLine(runningRoutersLine,
+          runningRoutersLineNoOpt, runningRoutersPartsNoOpt);
+    } else {
+      throw new DescriptorParseException("Either running-routers or "
+          + "router-status line must be given.");
+    }
+  }
+
+  protected void parseServerDescriptor(byte[] serverDescriptorBytes) {
+    try {
+      ServerDescriptorImpl serverDescriptor = new ServerDescriptorImpl(
+          serverDescriptorBytes, this.failUnrecognizedDescriptorLines);
+      this.serverDescriptors.add(serverDescriptor);
+    } catch (DescriptorParseException e) {
+      this.serverDescriptorParseExceptions.add(e);
+    }
+  }
+
+  private void parseDirectorySignature(byte[] directorySignatureBytes)
+      throws DescriptorParseException {
+    Scanner s = new Scanner(new String(directorySignatureBytes)).
+        useDelimiter("\n");
+    String nextCrypto = null;
+    StringBuilder crypto = null;
+    while (s.hasNext()) {
+      String line = s.next();
+      String lineNoOpt = line.startsWith("opt ") ?
+          line.substring("opt ".length()) : line;
+      String[] partsNoOpt = lineNoOpt.split(" ");
+      String keyword = partsNoOpt[0];
+      if (keyword.equals("directory-signature")) {
+        this.parseDirectorySignatureLine(line, lineNoOpt, partsNoOpt);
+        nextCrypto = "directory-signature";
+      } else if (line.startsWith("-----BEGIN")) {
+        crypto = new StringBuilder();
+        crypto.append(line + "\n");
+      } else if (line.startsWith("-----END")) {
+        crypto.append(line + "\n");
+        String cryptoString = crypto.toString();
+        crypto = null;
+        if (nextCrypto.equals("directory-signature")) {
+          this.directorySignature = cryptoString;
+        } else {
+          throw new DescriptorParseException("Unrecognized crypto "
+              + "block in v2 network status.");
+        }
+        nextCrypto = null;
+      } else if (crypto != null) {
+        crypto.append(line + "\n");
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in v2 network status.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
+        }
+        this.unrecognizedLines.add(line);
+      }
+    }
+  }
+
+  private void parseSignedDirectoryLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    if (!lineNoOpt.equals("signed-directory")) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+  }
+
+  private void parsePublishedLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    this.publishedMillis = ParseHelper.parseTimestampAtIndex(line,
+        partsNoOpt, 1, 2);
+  }
+
+  private void parseDirSigningKeyLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    if (partsNoOpt.length > 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    } else if (partsNoOpt.length == 2) {
+      /* Early directories didn't have a crypto object following the
+       * "dir-signing-key" line, but had the key base64-encoded in the
+       * same line. */
+      StringBuilder sb = new StringBuilder();
+      sb.append("-----BEGIN RSA PUBLIC KEY-----\n");
+      String keyString = partsNoOpt[1];
+      while (keyString.length() > 64) {
+        sb.append(keyString.substring(0, 64) + "\n");
+        keyString = keyString.substring(64);
+      }
+      if (keyString.length() > 0) {
+        sb.append(keyString + "\n");
+      }
+      sb.append("-----END RSA PUBLIC KEY-----\n");
+      this.dirSigningKey = sb.toString();
+    }
+  }
+
+  private void parseRecommendedSoftwareLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    List<String> result = new ArrayList<String>();
+    if (partsNoOpt.length > 2) {
+      throw new DescriptorParseException("Illegal versions line '" + line
+          + "'.");
+    } else if (partsNoOpt.length == 2) {
+      String[] versions = partsNoOpt[1].split(",", -1);
+      for (int i = 0; i < versions.length; i++) {
+        String version = versions[i];
+        if (version.length() < 1) {
+          throw new DescriptorParseException("Illegal versions line '"
+              + line + "'.");
+        }
+        result.add(version);
+      }
+    }
+    this.recommendedSoftware = result;
+  }
+
+  private void parseRunningRoutersLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    for (int i = 1; i < partsNoOpt.length; i++) {
+      String part = partsNoOpt[i];
+      String debugLine = "running-routers [...] " + part + " [...]";
+      boolean isLive = true;
+      if (part.startsWith("!")) {
+        isLive = false;
+        part = part.substring(1);
+      }
+      boolean isVerified;
+      String fingerprint = null, nickname = null;
+      if (part.startsWith("$")) {
+        isVerified = false;
+        fingerprint = ParseHelper.parseTwentyByteHexString(debugLine,
+            part.substring(1));
+      } else {
+        isVerified = true;
+        nickname = ParseHelper.parseNickname(debugLine, part);
+      }
+      this.statusEntries.add(new RouterStatusEntryImpl(fingerprint,
+          nickname, isLive, isVerified));
+    }
+  }
+
+  private void parseRouterStatusLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    for (int i = 1; i < partsNoOpt.length; i++) {
+      String part = partsNoOpt[i];
+      String debugLine = "router-status [...] " + part + " [...]";
+      RouterStatusEntry entry = null;
+      if (part.contains("=")) {
+        String[] partParts = part.split("=");
+        if (partParts.length == 2) {
+          boolean isVerified = true, isLive;
+          String nickname;
+          if (partParts[0].startsWith("!")) {
+            isLive = false;
+            nickname = ParseHelper.parseNickname(debugLine,
+                partParts[0].substring(1));
+          } else {
+            isLive = true;
+            nickname = ParseHelper.parseNickname(debugLine, partParts[0]);
+          }
+          String fingerprint = ParseHelper.parseTwentyByteHexString(
+              debugLine, partParts[1].substring(1));
+          entry = new RouterStatusEntryImpl(fingerprint, nickname, isLive,
+              isVerified);
+        }
+      } else {
+        boolean isVerified = false, isLive;
+        String nickname = null, fingerprint;
+        if (part.startsWith("!")) {
+          isLive = false;
+          fingerprint = ParseHelper.parseTwentyByteHexString(
+              debugLine, part.substring(2));
+        } else {
+          isLive = true;
+          fingerprint = ParseHelper.parseTwentyByteHexString(
+              debugLine, part.substring(1));;
+        }
+        entry = new RouterStatusEntryImpl(fingerprint, nickname, isLive,
+            isVerified);
+      }
+      if (entry == null) {
+        throw new DescriptorParseException("Illegal router-status entry '"
+            + part + "' in v1 directory.");
+      }
+      this.statusEntries.add(entry);
+    }
+  }
+
+  private void parseDirectorySignatureLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    if (partsNoOpt.length < 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]);
+  }
+
+  private long publishedMillis;
+  public long getPublishedMillis() {
+    return this.publishedMillis;
+  }
+
+  private String dirSigningKey;
+  public String getDirSigningKey() {
+    return this.dirSigningKey;
+  }
+
+  private List<String> recommendedSoftware;
+  public List<String> getRecommendedSoftware() {
+    return this.recommendedSoftware == null ? null :
+        new ArrayList<String>(this.recommendedSoftware);
+  }
+
+  private String directorySignature;
+  public String getDirectorySignature() {
+    return this.directorySignature;
+  }
+
+  private List<RouterStatusEntry> statusEntries =
+      new ArrayList<RouterStatusEntry>();
+  public List<RouterStatusEntry> getRouterStatusEntries() {
+    return new ArrayList<RouterStatusEntry>(this.statusEntries);
+  }
+
+  private List<ServerDescriptor> serverDescriptors =
+      new ArrayList<ServerDescriptor>();
+  public List<ServerDescriptor> getServerDescriptors() {
+    return new ArrayList<ServerDescriptor>(this.serverDescriptors);
+  }
+
+  private List<Exception> serverDescriptorParseExceptions =
+      new ArrayList<Exception>();
+  public List<Exception> getServerDescriptorParseExceptions() {
+    return new ArrayList<Exception>(this.serverDescriptorParseExceptions);
+  }
+
+  private String nickname;
+  public String getNickname() {
+    return this.nickname;
+  }
+
+  private String directoryDigest;
+  public String getDirectoryDigest() {
+    return this.directoryDigest;
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java b/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java
new file mode 100644
index 0000000..68f549e
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java
@@ -0,0 +1,37 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import org.torproject.descriptor.RouterStatusEntry;
+
+public class RouterStatusEntryImpl implements RouterStatusEntry {
+
+  protected RouterStatusEntryImpl(String fingerprint, String nickname,
+      boolean isLive, boolean isVerified) {
+    this.fingerprint = fingerprint;
+    this.nickname = nickname;
+    this.isLive = isLive;
+    this.isVerified = isVerified;
+  }
+
+  private String nickname;
+  public String getNickname() {
+    return this.nickname;
+  }
+
+  private String fingerprint;
+  public String getFingerprint() {
+    return this.fingerprint;
+  }
+
+  private boolean isLive;
+  public boolean isLive() {
+    return this.isLive;
+  }
+
+  private boolean isVerified;
+  public boolean isVerified() {
+    return this.isVerified;
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
index 77b06dd..5237348 100644
--- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
@@ -123,6 +123,8 @@ public class ServerDescriptorImpl extends DescriptorImpl
         this.parseProtocolsLine(line, lineNoOpt, partsNoOpt);
       } else if (keyword.equals("allow-single-hop-exits")) {
         this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dircacheport")) {
+        this.parseDircacheportLine(line, lineNoOpt, partsNoOpt);
       } else if (line.startsWith("-----BEGIN")) {
         crypto = new StringBuilder();
         crypto.append(line + "\n");
@@ -183,7 +185,7 @@ public class ServerDescriptorImpl extends DescriptorImpl
 
   private void parseBandwidthLine(String line, String lineNoOpt,
       String[] partsNoOpt) throws DescriptorParseException {
-    if (partsNoOpt.length != 4) {
+    if (partsNoOpt.length < 3 || partsNoOpt.length > 4) {
       throw new DescriptorParseException("Wrong number of values in line "
           + "'" + line + "'.");
     }
@@ -191,11 +193,18 @@ public class ServerDescriptorImpl extends DescriptorImpl
     try {
       this.bandwidthRate = Integer.parseInt(partsNoOpt[1]);
       this.bandwidthBurst = Integer.parseInt(partsNoOpt[2]);
-      this.bandwidthObserved = Integer.parseInt(partsNoOpt[3]);
+      if (partsNoOpt.length == 4) {
+        this.bandwidthObserved = Integer.parseInt(partsNoOpt[3]);
+      }
       if (this.bandwidthRate >= 0 && this.bandwidthBurst >= 0 &&
           this.bandwidthObserved >= 0) {
         isValid = true;
       }
+      if (partsNoOpt.length < 4) {
+        /* Tor versions 0.0.8 and older only wrote bandwidth lines with
+         * rate and burst values, but no observed value. */
+        this.bandwidthObserved = -1;
+      }
     } catch (NumberFormatException e) {
       /* Handle below. */
     }
@@ -435,6 +444,21 @@ public class ServerDescriptorImpl extends DescriptorImpl
     this.allowSingleHopExits = true;
   }
 
+  private void parseDircacheportLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    /* The dircacheport line was only contained in server descriptors
+     * published by Tor 0.0.8 and before.  It's only specified in old
+     * tor-spec.txt versions. */
+    if (partsNoOpt.length != 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    if (this.dirPort != 0) {
+      throw new DescriptorParseException("At most one of dircacheport "
+          + "and the directory port in the router line may be non-zero.");
+    }
+    this.dirPort = ParseHelper.parsePort(line, partsNoOpt[1]);
+  }
+
   private void calculateDigest() throws DescriptorParseException {
     try {
       String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");



More information about the tor-commits mailing list