[tor-commits] [metrics-lib/master] Make votes use the general network-status parsing code.

karsten at torproject.org karsten at torproject.org
Thu Dec 15 19:46:30 UTC 2011


commit cb9c62a90e30052db5599bc469da95de161af087
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Dec 15 20:45:42 2011 +0100

    Make votes use the general network-status parsing code.
---
 .../descriptor/RelayNetworkStatusVote.java         |   11 +-
 .../descriptor/impl/NetworkStatusImpl.java         |   20 +-
 .../impl/RelayNetworkStatusVoteImpl.java           |  577 ++++++++++++--------
 .../impl/RelayNetworkStatusVoteImplTest.java       |  574 +++++++++++++++++++
 4 files changed, 933 insertions(+), 249 deletions(-)

diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
index 6bfdd6f..aab90bd 100644
--- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java
+++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
@@ -27,8 +27,11 @@ public interface RelayNetworkStatusVote extends Descriptor {
   /* Return the valid-until time in milliseconds. */
   public long getValidUntilMillis();
 
-  /* Return a list of the voting-delay times in seconds. */
-  public List<Long> getVotingDelay();
+  /* Return the VoteSeconds time in seconds. */
+  public long getVoteSeconds();
+
+  /* Return the DistSeconds time in seconds. */
+  public long getDistSeconds();
 
   /* Return recommended server versions or null if the authority doesn't
    * recommend server versions. */
@@ -65,6 +68,10 @@ public interface RelayNetworkStatusVote extends Descriptor {
   /* Return the directory key certificate version. */
   public int getDirKeyCertificateVersion();
 
+  /* Return the legacy key or null if the directory authority does not use
+   * a legacy key. */
+  public String getLegacyKey();
+
   /* Return the directory key publication timestamp. */
   public long getDirKeyPublishedMillis();
 
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index 6ca546e..af8b9e2 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -51,6 +51,7 @@ public abstract class NetworkStatusImpl {
   protected NetworkStatusImpl(byte[] rawDescriptorBytes)
       throws DescriptorParseException {
     this.rawDescriptorBytes = rawDescriptorBytes;
+    this.countKeywords(rawDescriptorBytes);
     this.splitAndParseParts(rawDescriptorBytes);
   }
 
@@ -126,7 +127,6 @@ public abstract class NetworkStatusImpl {
     System.arraycopy(this.rawDescriptorBytes, start,
         headerBytes, 0, end - start);
     this.rememberFirstKeyword(headerBytes);
-    this.countKeywords(headerBytes);
     this.parseHeader(headerBytes);
   }
 
@@ -153,7 +153,6 @@ public abstract class NetworkStatusImpl {
     byte[] directoryFooterBytes = new byte[end - start];
     System.arraycopy(this.rawDescriptorBytes, start,
         directoryFooterBytes, 0, end - start);
-    this.countKeywords(directoryFooterBytes);
     this.parseFooter(directoryFooterBytes);
   }
 
@@ -264,17 +263,17 @@ public abstract class NetworkStatusImpl {
    * subclasses. */
   private Map<String, Integer> parsedKeywords =
       new HashMap<String, Integer>();
-  protected void countKeywords(byte[] headerOrFooterBytes)
+  protected void countKeywords(byte[] rawDescriptorBytes)
       throws DescriptorParseException {
     try {
       BufferedReader br = new BufferedReader(new StringReader(
-          new String(headerOrFooterBytes)));
+          new String(rawDescriptorBytes)));
       String line;
       boolean skipCrypto = false;
       while ((line = br.readLine()) != null) {
         if (line.startsWith("-----BEGIN")) {
           skipCrypto = true;
-        } else if (line.equals("-----END")) {
+        } else if (line.startsWith("-----END")) {
           skipCrypto = false;
         } else if (!skipCrypto) {
           String keyword = line.split(" ", -1)[0];
@@ -299,11 +298,14 @@ public abstract class NetworkStatusImpl {
   protected void checkExactlyOnceKeywords(Set<String> keywords)
       throws DescriptorParseException {
     for (String keyword : keywords) {
-      if (!this.parsedKeywords.containsKey(keyword) ||
-          this.parsedKeywords.get(keyword) != 1) {
+      int contained = 0;
+      if (this.parsedKeywords.containsKey(keyword)) {
+        contained = this.parsedKeywords.get(keyword);
+      }
+      if (contained != 1) {
         throw new DescriptorParseException("Keyword '" + keyword + "' is "
-            + "contained " + this.parsedKeywords.get(keyword) + " times, "
-            + "but must be contained exactly once.");
+            + "contained " + contained + " times, but must be contained "
+            + "exactly once.");
       }
     }
   }
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index a7a5328..5089210 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -7,258 +7,341 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
-import java.util.TimeZone;
 import java.util.TreeMap;
 import java.util.TreeSet;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import org.torproject.descriptor.Descriptor;
-import org.torproject.descriptor.NetworkStatusEntry;
 import org.torproject.descriptor.RelayNetworkStatusVote;
 
+/* TODO Find out if all keywords in the dir-source section are required.
+ * They are not all mentioned in dir-spec.txt. */
+
 /* Contains a network status vote. */
-/* TODO This class is sharing a lot of parsing code with the consensus
- * class.  Should there be an abstract super class for the two? */
-public class RelayNetworkStatusVoteImpl
+public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
     implements RelayNetworkStatusVote {
 
   protected static List<RelayNetworkStatusVote> parseVotes(
-      byte[] voteBytes) {
+      byte[] votesBytes) {
     List<RelayNetworkStatusVote> parsedVotes =
         new ArrayList<RelayNetworkStatusVote>();
-    String startToken = "network-status-version 3";
-    String splitToken = "\n" + startToken;
-    String ascii = new String(voteBytes);
-    int length = voteBytes.length, start = ascii.indexOf(startToken);
-    while (start < length) {
-      int end = ascii.indexOf(splitToken, start);
-      if (end < 0) {
-        end = length;
-      } else {
-        end += 1;
+    List<byte[]> splitVotesBytes =
+        NetworkStatusImpl.splitRawDescriptorBytes(votesBytes,
+        "network-status-version 3");
+    try {
+      for (byte[] voteBytes : splitVotesBytes) {
+        RelayNetworkStatusVote parsedVote =
+            new RelayNetworkStatusVoteImpl(voteBytes);
+        parsedVotes.add(parsedVote);
       }
-      byte[] descBytes = new byte[end - start];
-      System.arraycopy(voteBytes, start, descBytes, 0, end - start);
-      RelayNetworkStatusVote parsedVote =
-          new RelayNetworkStatusVoteImpl(descBytes);
-      parsedVotes.add(parsedVote);
-      start = end;
+    } catch (DescriptorParseException e) {
+      /* TODO Handle this error somehow. */
+      System.err.println("Failed to parse vote.  Skipping.");
+      e.printStackTrace();
     }
     return parsedVotes;
   }
 
-  protected RelayNetworkStatusVoteImpl(byte[] voteBytes) {
-    this.voteBytes = voteBytes;
-    this.parseVoteBytes();
-    this.checkConsistency();
-    /* TODO Find a way to handle parse and consistency-check problems. */
+  protected RelayNetworkStatusVoteImpl(byte[] voteBytes)
+      throws DescriptorParseException {
+    super(voteBytes);
+    Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
+        "vote-status,consensus-methods,published,valid-after,fresh-until,"
+        + "valid-until,voting-delay,known-flags,dir-source,"
+        + "dir-key-certificate-version,fingerprint,dir-key-published,"
+        + "dir-key-expires,directory-footer").split(",")));
+    this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+    Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
+        "client-versions,server-versions,params,contact,legacy-key").
+        split(",")));
+    this.checkAtMostOnceKeywords(atMostOnceKeywords);
+    this.checkFirstKeyword("network-status-version");
   }
 
-  private void parseVoteBytes() {
-    String line = null;
+  protected void parseHeader(byte[] headerBytes)
+      throws DescriptorParseException {
     try {
       BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.voteBytes)));
-      SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
-          "yyyy-MM-dd HH:mm:ss");
-      dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-      StringBuilder dirSourceEntryLines = null, statusEntryLines = null;
-      boolean skipCrypto = false;
+          new String(headerBytes)));
+      String line;
       while ((line = br.readLine()) != null) {
-        if (line.startsWith("network-status-version ")) {
-          this.networkStatusVersion = Integer.parseInt(line.substring(
-              "network-status-version ".length()));
-        } else if (line.startsWith("vote-status ")) {
-          if (!line.equals("vote-status vote")) {
-            throw new RuntimeException("Line '" + line + "' indicates "
-                + "that this string is not a vote.  Aborting parsing.");
-          }
-        } else if (line.startsWith("consensus-methods ")) {
-          for (String consensusMethodString : line.substring(
-              "consensus-methods ".length()).split(" ")) {
-            this.consensusMethods.add(Integer.parseInt(
-            consensusMethodString));
-          }
-        } else if (line.startsWith("published ")) {
-          this.publishedMillis = dateTimeFormat.parse(
-              line.substring("published ".length())).getTime();
-        } else if (line.startsWith("valid-after ")) {
-          this.validAfterMillis = dateTimeFormat.parse(
-              line.substring("valid-after ".length())).getTime();
-        } else if (line.startsWith("fresh-until ")) {
-          this.freshUntilMillis = dateTimeFormat.parse(
-              line.substring("fresh-until ".length())).getTime();
-        } else if (line.startsWith("valid-until ")) {
-          this.validUntilMillis = dateTimeFormat.parse(
-              line.substring("valid-until ".length())).getTime();
-        } else if (line.startsWith("voting-delay ")) {
-          for (String votingDelayString : line.substring(
-              "voting-delay ".length()).split(" ")) {
-            this.votingDelay.add(Long.parseLong(votingDelayString));
-          }
-        } else if (line.startsWith("client-versions ")) {
-          this.recommendedClientVersions =
-              Arrays.asList(line.split(" ")[1].split(","));
-        } else if (line.startsWith("server-versions ")) {
-          this.recommendedServerVersions =
-              Arrays.asList(line.split(" ")[1].split(","));
-        } else if (line.startsWith("known-flags ")) {
-          for (String flag : line.substring("known-flags ".length()).
-              split(" ")) {
-            this.knownFlags.add(flag);
-          }
-        } else if (line.startsWith("params ")) {
-          if (line.length() > "params ".length()) {
-            for (String param :
-                line.substring("params ".length()).split(" ")) {
-              String paramName = param.split("=")[0];
-              int paramValue = Integer.parseInt(param.split("=")[1]);
-              this.consensusParams.put(paramName, paramValue);
-            }
-          }
-        } else if (line.startsWith("dir-source ")) {
-          String[] parts = line.split(" ");
-          this.nickname = parts[1];
-          this.identity = parts[2];
-          this.address = parts[4];
-          this.dirPort = Integer.parseInt(parts[5]);
-          this.orPort = Integer.parseInt(parts[6]);
-          /* TODO Add code for parsing legacy dir sources. */
-        } else if (line.startsWith("contact ")) {
-          this.contactLine = line.substring("contact ".length());
-        } else if (line.startsWith("dir-key-certificate-version ")) {
-          this.dirKeyCertificateVersion = Integer.parseInt(line.substring(
-              "dir-key-certificate-version ".length()));
-        } else if (line.startsWith("fingerprint ")) {
-          /* Nothing new to learn here.  We already know the fingerprint
-           * from the dir-source line. */
-        } else if (line.startsWith("dir-key-published ")) {
-          this.dirKeyPublishedMillis = dateTimeFormat.parse(
-              line.substring("dir-key-published ".length())).getTime();
-        } else if (line.startsWith("dir-key-expires ")) {
-          this.dirKeyExpiresMillis = dateTimeFormat.parse(
-              line.substring("dir-key-expires ".length())).getTime();
-        } else if (line.equals("dir-identity-key") ||
-            line.equals("dir-signing-key") ||
-            line.equals("dir-key-crosscert") ||
-            line.equals("dir-key-certification")) {
-          /* Ignore crypto parts for now. */
-        } else if (line.startsWith("r ") ||
-            line.equals("directory-footer")) {
-          if (statusEntryLines != null) {
-            try {
-              NetworkStatusEntryImpl statusEntry =
-                  new NetworkStatusEntryImpl(
-                  statusEntryLines.toString().getBytes());
-              this.statusEntries.put(statusEntry.getFingerprint(),
-                  statusEntry);
-            } catch (DescriptorParseException e) {
-              System.err.println("Could not parse status entry in vote.  "
-                  + "Skipping.");
-            }
-            statusEntryLines = null;
-          }
-          if (line.startsWith("r ")) {
-            statusEntryLines = new StringBuilder();
-            statusEntryLines.append(line + "\n");
-          }
-        } else if (line.startsWith("s ") || line.equals("s") ||
-            line.startsWith("opt v ") || line.startsWith("w ") ||
-            line.startsWith("p ") || line.startsWith("m ")) {
-          statusEntryLines.append(line + "\n");
-        } else if (line.startsWith("directory-signature ")) {
-          String[] parts = line.split(" ");
-          String identity = parts[1];
-          String signingKeyDigest = parts[2];
-          this.directorySignatures.put(identity, signingKeyDigest);
-        } else if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!skipCrypto) {
-          throw new RuntimeException("Unrecognized line '" + line + "'.");
+        String[] parts = line.split(" ");
+        String keyword = parts[0];
+        if (keyword.equals("network-status-version")) {
+          this.parseNetworkStatusVersionLine(line, parts);
+        } else if (keyword.equals("vote-status")) {
+          this.parseVoteStatusLine(line, parts);
+        } else if (keyword.equals("consensus-methods")) {
+          this.parseConsensusMethodsLine(line, parts);
+        } else if (keyword.equals("published")) {
+          this.parsePublishedLine(line, parts);
+        } else if (keyword.equals("valid-after")) {
+          this.parseValidAfterLine(line, parts);
+        } else if (keyword.equals("fresh-until")) {
+          this.parseFreshUntilLine(line, parts);
+        } else if (keyword.equals("valid-until")) {
+          this.parseValidUntilLine(line, parts);
+        } else if (keyword.equals("voting-delay")) {
+          this.parseVotingDelayLine(line, parts);
+        } else if (keyword.equals("client-versions")) {
+          this.parseClientVersionsLine(line, parts);
+        } else if (keyword.equals("server-versions")) {
+          this.parseServerVersionsLine(line, parts);
+        } else if (keyword.equals("known-flags")) {
+          this.parseKnownFlagsLine(line, parts);
+        } else if (keyword.equals("params")) {
+          this.parseParamsLine(line, parts);
+        } else {
+          /* TODO Is throwing an exception the right thing to do here?
+           * This is probably fine for development, but once the library
+           * is in production use, this seems annoying. */
+          throw new DescriptorParseException("Unrecognized line '" + line
+              + "'.");
         }
       }
     } catch (IOException e) {
       throw new RuntimeException("Internal error: Ran into an "
           + "IOException while parsing a String in memory.  Something's "
           + "really wrong.", e);
-    } catch (ParseException e) {
-      /* TODO Handle me correctly. */
-      throw new RuntimeException("Parse error in line '" + line + "'.");
+    }
+  }
+
+  private void parseNetworkStatusVersionLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (!line.equals("network-status-version 3")) {
+      throw new DescriptorParseException("Illegal network status version "
+          + "number in line '" + line + "'.");
+    }
+    this.networkStatusVersion = 3;
+  }
+
+  private void parseVoteStatusLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length != 2 || !parts[1].equals("vote")) {
+      throw new DescriptorParseException("Line '" + line + "' indicates "
+          + "that this is not a vote.");
+    }
+  }
+
+  private void parseConsensusMethodsLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length < 2) {
+      throw new DescriptorParseException("Illegal line '" + line
+          + "' in vote.");
+    }
+    this.consensusMethods = new ArrayList<Integer>();
+    for (int i = 1; i < parts.length; i++) {
+      int consensusMethod = -1;
+      try {
+        consensusMethod = Integer.parseInt(parts[i]);
+      } catch (NumberFormatException e) {
+        /* We'll notice below that consensusMethod is still -1. */
+      }
+      if (consensusMethod < 1) {
+        throw new DescriptorParseException("Illegal consensus method "
+            + "number in line '" + line + "'.");
+      }
+      this.consensusMethods.add(Integer.parseInt(parts[i]));
+    }
+  }
+
+  private void parsePublishedLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+        1, 2);
+  }
+
+  private void parseValidAfterLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+        1, 2);
+  }
+
+  private void parseFreshUntilLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+        1, 2);
+  }
+
+  private void parseValidUntilLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+        1, 2);
+  }
+
+  private void parseVotingDelayLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length != 3) {
+      throw new DescriptorParseException("Wrong number of values in line "
+          + "'" + line + "'.");
+    }
+    try {
+      this.voteSeconds = Long.parseLong(parts[1]);
+      this.distSeconds = Long.parseLong(parts[2]);
     } catch (NumberFormatException e) {
-      /* TODO Handle me.  In theory, we shouldn't catch runtime
-       * exceptions, but in this case it keeps the parsing code small. */
-    } catch (ArrayIndexOutOfBoundsException e) {
-      /* TODO Handle me.  In theory, we shouldn't catch runtime
-       * exceptions, but in this case it keeps the parsing code small. */
+      throw new DescriptorParseException("Illegal values in line '" + line
+          + "'.");
     }
   }
 
-  private byte[] voteBytes;
-  public byte[] getRawDescriptorBytes() {
-    return this.voteBytes;
+  private void parseClientVersionsLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.recommendedClientVersions = this.parseClientOrServerVersions(
+        line, parts);
   }
 
-  private int networkStatusVersion;
-  public int getNetworkStatusVersion() {
-    return this.networkStatusVersion;
+  private void parseServerVersionsLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.recommendedServerVersions = this.parseClientOrServerVersions(
+        line, parts);
   }
 
-  private List<Integer> consensusMethods = new ArrayList<Integer>();
-  public List<Integer> getConsensusMethods() {
-    return this.consensusMethods;
+  private List<String> parseClientOrServerVersions(String line,
+      String[] parts) throws DescriptorParseException {
+    List<String> result = new ArrayList<String>();
+    if (parts.length == 1) {
+      return result;
+    } else if (parts.length > 2) {
+      throw new DescriptorParseException("Illegal versions line '" + line
+          + "'.");
+    }
+    String[] versions = parts[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);
+    }
+    return result;
   }
 
-  private long publishedMillis;
-  public long getPublishedMillis() {
-    return this.publishedMillis;
+  private void parseKnownFlagsLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length < 2) {
+      throw new DescriptorParseException("No known flags in line '" + line
+          + "'.");
+    }
+    this.knownFlags = new TreeSet<String>();
+    for (int i = 1; i < parts.length; i++) {
+      this.knownFlags.add(parts[i]);
+    }
   }
 
-  private long validAfterMillis;
-  public long getValidAfterMillis() {
-    return this.validAfterMillis;
+  private void parseParamsLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.consensusParams = ParseHelper.parseKeyValuePairs(line, parts, 1);
   }
 
-  private long freshUntilMillis;
-  public long getFreshUntilMillis() {
-    return this.freshUntilMillis;
+  protected void parseDirSource(byte[] dirSourceBytes)
+      throws DescriptorParseException {
+    try {
+      BufferedReader br = new BufferedReader(new StringReader(
+          new String(dirSourceBytes)));
+      String line;
+      boolean skipCrypto = false;
+      while ((line = br.readLine()) != null) {
+        String[] parts = line.split(" ");
+        String keyword = parts[0];
+        if (keyword.equals("dir-source")) {
+          this.parseDirSourceLine(line, parts);
+        } else if (keyword.equals("contact")) {
+          this.parseContactLine(line, parts);
+        } else if (keyword.equals("dir-key-certificate-version")) {
+          this.parseDirKeyCertificateVersionLine(line, parts);
+        } else if (keyword.equals("fingerprint")) {
+          /* Nothing new to learn here.  We already know the fingerprint
+           * from the dir-source line. */
+        } else if (keyword.equals("legacy-key")) {
+          this.parseLegacyKeyLine(line, parts);
+        } else if (keyword.equals("dir-key-published")) {
+          this.parseDirKeyPublished(line, parts);
+        } else if (keyword.equals("dir-key-expires")) {
+          this.parseDirKeyExpiresLine(line, parts);
+        } else if (keyword.equals("dir-identity-key") ||
+            keyword.equals("dir-signing-key") ||
+            keyword.equals("dir-key-crosscert") ||
+            keyword.equals("dir-key-certification")) {
+        } else if (line.startsWith("-----BEGIN")) {
+          skipCrypto = true;
+        } else if (line.equals("-----END")) {
+          skipCrypto = false;
+        } else if (!skipCrypto) {
+          /* TODO Is throwing an exception the right thing to do here?
+           * This is probably fine for development, but once the library
+           * is in production use, this seems annoying. */
+          throw new DescriptorParseException("Unrecognized line '" + line
+              + "'.");
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Internal error: Ran into an "
+          + "IOException while parsing a String in memory.  Something's "
+          + "really wrong.", e);
+    }
   }
 
-  private long validUntilMillis;
-  public long getValidUntilMillis() {
-    return this.validUntilMillis;
+  private void parseDirSourceLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.nickname = ParseHelper.parseNickname(line, parts[1]);
+    this.identity = ParseHelper.parseTwentyByteHexString(line, parts[2]);
+    this.address = ParseHelper.parseIpv4Address(line, parts[4]);
+    this.dirPort = ParseHelper.parsePort(line, parts[5]);
+    this.orPort = ParseHelper.parsePort(line, parts[6]);
   }
 
-  private List<Long> votingDelay = new ArrayList<Long>();
-  public List<Long> getVotingDelay() {
-    return new ArrayList<Long>(this.votingDelay);
+  private void parseContactLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (line.length() > "contact ".length()) {
+      this.contactLine = line.substring("contact ".length());
+    } else {
+      this.contactLine = "";
+    }
   }
 
-  private List<String> recommendedClientVersions;
-  public List<String> getRecommendedClientVersions() {
-    return this.recommendedClientVersions == null ? null :
-        new ArrayList<String>(this.recommendedClientVersions);
+  private void parseDirKeyCertificateVersionLine(String line,
+      String[] parts) throws DescriptorParseException {
+    if (parts.length != 2) {
+      throw new DescriptorParseException("Illegal line '" + line
+          + "' in vote.");
+    }
+    try {
+      this.dirKeyCertificateVersion = Integer.parseInt(parts[1]);
+    } catch (NumberFormatException e) {
+      throw new DescriptorParseException("Illegal dir key certificate "
+          + "version in line '" + line + "'.");
+    }
+    if (this.dirKeyCertificateVersion < 1) {
+      throw new DescriptorParseException("Illegal dir key certificate "
+          + "version in line '" + line + "'.");
+    }
   }
 
-  private List<String> recommendedServerVersions;
-  public List<String> getRecommendedServerVersions() {
-    return this.recommendedServerVersions == null ? null :
-        new ArrayList<String>(this.recommendedServerVersions);
+  private void parseLegacyKeyLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length != 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    this.legacyKey = ParseHelper.parseTwentyByteHexString(line, parts[2]);
   }
 
-  private SortedSet<String> knownFlags = new TreeSet<String>();
-  public SortedSet<String> getKnownFlags() {
-    return new TreeSet<String>(this.knownFlags);
+  private void parseDirKeyPublished(String line, String[] parts)
+      throws DescriptorParseException {
+    this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line,
+        parts, 1, 2);
   }
 
-  private SortedMap<String, Integer> consensusParams =
-      new TreeMap<String, Integer>();
-  public SortedMap<String, Integer> getConsensusParams() {
-    return new TreeMap<String, Integer>(this.consensusParams);
+  private void parseDirKeyExpiresLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line,
+        parts, 1, 2);
+  }
+
+  protected void parseFooter(byte[] footerBytes) {
+    /* There is nothing in the footer that we'd want to parse. */
   }
 
   private String nickname;
@@ -296,6 +379,11 @@ public class RelayNetworkStatusVoteImpl
     return this.dirKeyCertificateVersion;
   }
 
+  private String legacyKey;
+  public String getLegacyKey() {
+    return this.legacyKey;
+  }
+
   private long dirKeyPublishedMillis;
   public long getDirKeyPublishedMillis() {
     return this.dirKeyPublishedMillis;
@@ -311,54 +399,67 @@ public class RelayNetworkStatusVoteImpl
     return this.signingKeyDigest;
   }
 
-  private SortedMap<String, NetworkStatusEntry> statusEntries =
-      new TreeMap<String, NetworkStatusEntry>();
-  public SortedMap<String, NetworkStatusEntry> getStatusEntries() {
-    return new TreeMap<String, NetworkStatusEntry>(this.statusEntries);
+  private int networkStatusVersion;
+  public int getNetworkStatusVersion() {
+    return this.networkStatusVersion;
   }
-  public boolean containsStatusEntry(String fingerprint) {
-    return this.statusEntries.containsKey(fingerprint);
+
+  private List<Integer> consensusMethods;
+  public List<Integer> getConsensusMethods() {
+    return new ArrayList<Integer>(this.consensusMethods);
   }
-  public NetworkStatusEntry getStatusEntry(String fingerprint) {
-    return this.statusEntries.get(fingerprint);
+
+  private long publishedMillis;
+  public long getPublishedMillis() {
+    return this.publishedMillis;
   }
 
+  private long validAfterMillis;
+  public long getValidAfterMillis() {
+    return this.validAfterMillis;
+  }
 
-  private SortedMap<String, String> directorySignatures =
-      new TreeMap<String, String>();
-  public SortedMap<String, String> getDirectorySignatures() {
-    return new TreeMap<String, String>(this.directorySignatures);
+  private long freshUntilMillis;
+  public long getFreshUntilMillis() {
+    return this.freshUntilMillis;
   }
 
-  private void checkConsistency() {
-    if (this.networkStatusVersion == 0) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'network-status-version' line.");
-    }
-    if (this.validAfterMillis == 0L) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'valid-after' line.");
-    }
-    if (this.freshUntilMillis == 0L) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'fresh-until' line.");
-    }
-    if (this.validUntilMillis == 0L) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'valid-until' line.");
-    }
-    if (this.votingDelay.isEmpty()) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'voting-delay' line.");
-    }
-    if (this.knownFlags.isEmpty()) {
-      throw new RuntimeException("Consensus doesn't contain a "
-          + "'known-flags' line.");
-    }
-    if (this.statusEntries.isEmpty()) {
-      throw new RuntimeException("Consensus doesn't contain any 'r' "
-          + "lines.");
-    }
+  private long validUntilMillis;
+  public long getValidUntilMillis() {
+    return this.validUntilMillis;
+  }
+
+  private long voteSeconds;
+  public long getVoteSeconds() {
+    return this.voteSeconds;
+  }
+
+  private long distSeconds;
+  public long getDistSeconds() {
+    return this.distSeconds;
+  }
+
+  private List<String> recommendedClientVersions;
+  public List<String> getRecommendedClientVersions() {
+    return this.recommendedClientVersions == null ? null :
+        new ArrayList<String>(this.recommendedClientVersions);
+  }
+
+  private List<String> recommendedServerVersions;
+  public List<String> getRecommendedServerVersions() {
+    return this.recommendedServerVersions == null ? null :
+        new ArrayList<String>(this.recommendedServerVersions);
+  }
+
+  private SortedSet<String> knownFlags;
+  public SortedSet<String> getKnownFlags() {
+    return new TreeSet<String>(this.knownFlags);
+  }
+
+  private SortedMap<String, Integer> consensusParams;
+  public SortedMap<String, Integer> getConsensusParams() {
+    return this.consensusParams == null ? null:
+        new TreeMap<String, Integer>(this.consensusParams);
   }
 }
 
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
new file mode 100644
index 0000000..8b62480
--- /dev/null
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
@@ -0,0 +1,574 @@
+/* Copyright 2011 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import org.torproject.descriptor.RelayNetworkStatusVote;
+import org.torproject.descriptor.impl.RelayNetworkStatusVoteImpl;
+
+import java.util.*;
+
+import org.junit.*;
+import org.junit.rules.*;
+import static org.junit.Assert.*;
+
+/* TODO Add tests (and possibly a DirSourceLineBuilder) to test the
+ * following methods:
+ * - String getNickname();
+ * - String getIdentity();
+ * - String getAddress();
+ * - int getDirport();
+ * - int getOrport();
+ * - String getContactLine();
+ * - int getDirKeyCertificateVersion();
+ * - String getLegacyKey();
+ * - long getDirKeyPublishedMillis();
+ * - long getDirKeyExpiresMillis();
+ * - String getSigningKeyDigest();
+ */
+
+/* Test parsing of network status votes.  Some of the vote-parsing code is
+ * already tested in the consensus-parsing tests.  The tests in this class
+ * focus on the differences between votes and consensuses that are mostly
+ * in the directory header. */
+public class RelayNetworkStatusVoteImplTest {
+
+  /* Helper class to build a vote based on default data and modifications
+   * requested by test methods. */
+  private static class VoteBuilder {
+    private String networkStatusVersionLine = "network-status-version 3";
+    private static RelayNetworkStatusVote
+        createWithNetworkStatusVersionLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.networkStatusVersionLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String voteStatusLine = "vote-status vote";
+    private static RelayNetworkStatusVote
+        createWithVoteStatusLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.voteStatusLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String consensusMethodsLine =
+        "consensus-methods 1 2 3 4 5 6 7 8 9 10 11";
+    private static RelayNetworkStatusVote
+        createWithConsensusMethodsLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.consensusMethodsLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String publishedLine = "published 2011-11-30 08:50:01";
+    private static RelayNetworkStatusVote
+        createWithPublishedLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.publishedLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String validAfterLine = "valid-after 2011-11-30 09:00:00";
+    private static RelayNetworkStatusVote
+        createWithValidAfterLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.validAfterLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String freshUntilLine = "fresh-until 2011-11-30 10:00:00";
+    private static RelayNetworkStatusVote
+        createWithFreshUntilLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.freshUntilLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String validUntilLine = "valid-until 2011-11-30 12:00:00";
+    private static RelayNetworkStatusVote
+        createWithValidUntilLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.validUntilLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String votingDelayLine = "voting-delay 300 300";
+    private static RelayNetworkStatusVote
+        createWithVotingDelayLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.votingDelayLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String clientVersionsLine = "client-versions 0.2.1.31,"
+        + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha";
+    private static RelayNetworkStatusVote
+        createWithClientVersionsLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.clientVersionsLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String serverVersionsLine = "server-versions 0.2.1.31,"
+        + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha";
+    private static RelayNetworkStatusVote
+        createWithServerVersionsLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.serverVersionsLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String knownFlagsLine = "known-flags Authority BadExit Exit "
+        + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid";
+    private static RelayNetworkStatusVote
+        createWithKnownFlagsLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.knownFlagsLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private String paramsLine = "params "
+        + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 "
+        + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 "
+        + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 "
+        + "cbtquantile=80 circwindow=1000 refuseunknownexits=1";
+    private static RelayNetworkStatusVote
+        createWithParamsLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.paramsLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private List<String> dirSources = new ArrayList<String>();
+    private List<String> statusEntries = new ArrayList<String>();
+    private String directoryFooterLine = "directory-footer";
+    private static RelayNetworkStatusVote
+        createWithDirectoryFooterLine(String line)
+        throws DescriptorParseException {
+      VoteBuilder vb = new VoteBuilder();
+      vb.directoryFooterLine = line;
+      return new RelayNetworkStatusVoteImpl(vb.buildVote());
+    }
+    private List<String> directorySignatures = new ArrayList<String>();
+    private VoteBuilder() {
+      this.dirSources.add("dir-source urras "
+          + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
+          + "208.83.223.34 443 80\n"
+          + "contact 4096R/E012B42D Jacob Appelbaum "
+          + "<jacob at appelbaum.net>\n"
+          + "dir-key-certificate-version 3\n"
+          + "fingerprint 80550987E1D626E3EBA5E5E75A458DE0626D088C\n"
+          + "dir-key-published 2011-04-27 05:34:37\n"
+          + "dir-key-expires 2012-04-27 05:34:37\n"
+          + "dir-identity-key\n"
+          + "-----BEGIN RSA PUBLIC KEY-----\n"
+          + "MIIBigKCAYEAtKpuLgVK25sfScjsxfVU1ljofrDygt9GP7bNJl/rghX42KUT"
+          + "975W\nrGp/fbhF7p+FcKCzNOhJFINQbRf/5E3lN8mzoamIU43QqQ9RRVf946"
+          + "88UsazVsAN\nNVT0v9J0cr387WePjenRuIE1MmiP0nmw/XdvbPTayqax7VYl"
+          + "cUMXGHl8DnWix1EN\nRwmeig+JBte0JS12oo2HG9zcSfjLJVjY6ZmvRrVycX"
+          + "iRxGc/JgNlSrV4cxUNykaB\nJ6pO6J499OZfQu7m1vAPTENrVJ4yEfRGRwFI"
+          + "Y+d/s8BkKcaiWtXAfTe31uBI6GEH\nmS3HNu1JVSuoaUiQIvVYDLMfBvMcNy"
+          + "Ax97UT1l6E0Tn6a7pgChrquGwXai1xGzk8\n58aXwdSFoFBSTCkyemopq5H2"
+          + "0p/nkPAO0pHL1kTvcaKz9CEj4XcKm+kOmzejYmIa\nkbWNcRpXPiUZ+xmwGt"
+          + "sq30xrzqiONmERkxqlmf7bVQPFvh3Kz6hGcmTBhTbHSe9h\nzDgmdaTNn3EH"
+          + "AgMBAAE=\n"
+          + "-----END RSA PUBLIC KEY-----\n"
+          + "dir-signing-key\n"
+          + "-----BEGIN RSA PUBLIC KEY-----\n"
+          + "MIGJAoGBAN05qyHFQlTqykMP8yLuD4G2UuYulD4Xs8iSX5uqF+WGsUA1E4zZ"
+          + "h48h\nDFj8+drFiCu3EqhMEmVG4ACtJK2uz6D1XohUsbPWTR6LSnWJ8q6/zf"
+          + "TSLumBGsN7\nPUXyMNjwRKL6UvrcbYk1d2mRBLO7SAP/sFW5fHhIBVeLIWrz"
+          + "Q19rAgMBAAE=\n"
+          + "-----END RSA PUBLIC KEY-----\n"
+          + "dir-key-crosscert\n"
+          + "-----BEGIN ID SIGNATURE-----\n"
+          + "rPBFn6IJ6TvAHj4pSwlg+RTn1fP89JGSVa08wuyJr5dAvZsdakQXvRjamT9o"
+          + "JUaZ\nnY5Rl/tRlGuSQ0BglTPPKoXdKERK0FUr9f0EKrQy7NDUgE2j9losiR"
+          + "uyKzhA3neZ\nK4yF8bhqAwM51u7fzAhIjNeRif9c04rhFJJCseco84w=\n"
+          + "-----END ID SIGNATURE-----\n"
+          + "dir-key-certification\n"
+          + "-----BEGIN SIGNATURE-----\n"
+          + "hPSh6FuohNF5ccjiMbkvr8cZJwGFuL11cNtwN9k0X3pUdFZVATIEkqBe7z+r"
+          + "E2PX\nPw+BGyC6wYAieoTVIhLpwKqd7DXLYjuhPZ28+7MQaDL01AqYeRp5PT"
+          + "01PxrFY0Um\nlVf95uqUitgvDT76Ne4ExWk6UvGlYB9OBgBySZz8VWe9znoM"
+          + "qb0uHn/p8IzqTApT\nAxRWXBHClntMeRqtGxaj8DcdJFn8yMxQiZG7MfDg2s"
+          + "q2ySPJyGlN+neoVDVhZiDI\n9LTNmw60gWlUp2erFeam8Mo1ZBC4DPNjQEm6"
+          + "QeHZFZMkhDuO6SwS/FL712A42+Co\nYtMaVot/p5FG2ZSBXbgl2XP5/z8ELn"
+          + "pmXqMbPAoWRo3BPNSJkIQQNog8Q5ZrK+av\nZDw5eGPltGKsXOkvuzIMM8nB"
+          + "eAnDPDgYvzrIFObEGbvY/P8mzVAZxp3Yz+sRtNel\nC1SWz/Fx+Saex5oI7D"
+          + "J3xtSD4XqKb/wYwZFT8IxDYq1t2tFXdHxd4QPRVcvc0zYC\n"
+          + "-----END SIGNATURE-----");
+      this.statusEntries.add("r right2privassy3 "
+          + "ADQ6gCT3DiFHKPDFr3rODBUI8HM lJY5Vf7kXec+VdkGW2flEsfkFC8 "
+          + "2011-11-12 00:03:40 50.63.8.215 9023 0\n"
+          + "s Exit Fast Guard Running Stable Valid\n"
+          + "opt v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)\n"
+          + "w Bandwidth=297 Measured=73\n"
+          + "p accept 80,1194,1220,1293,1500,1533,1677,1723,1863,"
+          + "2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,"
+          + "4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,"
+          + "8008,8074,8080,8087-8088,8443,8888,9418,9999-10000,19294,"
+          + "19638\n"
+          + "m 8,9,10,11 sha256=9ciEx9t0McXk9A06I7qwN7pxuNOdpCP64RV/6cx2Zkc");
+      this.directorySignatures.add("directory-signature "
+          + "80550987E1D626E3EBA5E5E75A458DE0626D088C "
+          + "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19\n"
+          + "-----BEGIN SIGNATURE-----\n"
+          + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn"
+          + "F3Yh\nqXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi"
+          + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n"
+          + "-----END SIGNATURE-----");
+    }
+    private byte[] buildVote() {
+      StringBuilder sb = new StringBuilder();
+      this.appendHeader(sb);
+      this.appendBody(sb);
+      this.appendFooter(sb);
+      return sb.toString().getBytes();
+    }
+    private void appendHeader(StringBuilder sb) {
+      if (this.networkStatusVersionLine != null) {
+        sb.append(this.networkStatusVersionLine + "\n");
+      }
+      if (this.voteStatusLine != null) {
+        sb.append(this.voteStatusLine + "\n");
+      }
+      if (this.consensusMethodsLine != null) {
+        sb.append(this.consensusMethodsLine + "\n");
+      }
+      if (this.publishedLine != null) {
+        sb.append(this.publishedLine + "\n");
+      }
+      if (this.validAfterLine != null) {
+        sb.append(this.validAfterLine + "\n");
+      }
+      if (this.freshUntilLine != null) {
+        sb.append(this.freshUntilLine + "\n");
+      }
+      if (this.validUntilLine != null) {
+        sb.append(this.validUntilLine + "\n");
+      }
+      if (this.votingDelayLine != null) {
+        sb.append(this.votingDelayLine + "\n");
+      }
+      if (this.clientVersionsLine != null) {
+        sb.append(this.clientVersionsLine + "\n");
+      }
+      if (this.serverVersionsLine != null) {
+        sb.append(this.serverVersionsLine + "\n");
+      }
+      if (this.knownFlagsLine != null) {
+        sb.append(this.knownFlagsLine + "\n");
+      }
+      if (this.paramsLine != null) {
+        sb.append(this.paramsLine + "\n");
+      }
+      for (String dirSource : this.dirSources) {
+        sb.append(dirSource + "\n");
+      }
+    }
+    private void appendBody(StringBuilder sb) {
+      for (String statusEntry : this.statusEntries) {
+        sb.append(statusEntry + "\n");
+      }
+    }
+    private void appendFooter(StringBuilder sb) {
+      if (this.directoryFooterLine != null) {
+        sb.append(this.directoryFooterLine + "\n");
+      }
+      for (String directorySignature : this.directorySignatures) {
+        sb.append(directorySignature + "\n");
+      }
+    }
+  }
+
+  @Test()
+  public void testSampleVote() throws DescriptorParseException {
+    VoteBuilder vb = new VoteBuilder();
+    RelayNetworkStatusVote vote =
+        new RelayNetworkStatusVoteImpl(vb.buildVote());
+    assertEquals(3, vote.getNetworkStatusVersion());
+    List<Integer> consensusMethods = Arrays.asList(
+        new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
+    assertEquals(vote.getConsensusMethods(), consensusMethods);
+    assertEquals(1322643001000L, vote.getPublishedMillis());
+    assertEquals(1322643600000L, vote.getValidAfterMillis());
+    assertEquals(1322647200000L, vote.getFreshUntilMillis());
+    assertEquals(1322654400000L, vote.getValidUntilMillis());
+    assertEquals(300L, vote.getVoteSeconds());
+    assertEquals(300L, vote.getDistSeconds());
+    assertTrue(vote.getKnownFlags().contains("Running"));
+    assertEquals(30000, (int) vote.getConsensusParams().get(
+        "CircuitPriorityHalflifeMsec"));
+    assertEquals("Tor 0.2.1.29 (r8e9b25e6c7a2e70c)",
+        vote.getStatusEntry("00343A8024F70E214728F0C5AF7ACE0C1508F073").
+        getVersion());
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionNoLine()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionNewLine()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version 3\n");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionNewLineSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version 3\n ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionPrefixLineAtChar()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "@vote\nnetwork-status-version 3");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionPrefixLine()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "directory-footer\nnetwork-status-version 3");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionPrefixLinePoundChar()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "#vote\nnetwork-status-version 3");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionNoSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionOneSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersion42()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version 42");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionFourtyTwo()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version FourtyTwo");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionSpaceBefore()
+      throws DescriptorParseException {
+    VoteBuilder.createWithNetworkStatusVersionLine(
+        " network-status-version 3");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusSpaceBefore() throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine(" vote-status vote");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusNoSpace() throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine("vote-status");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusOneSpace() throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine("vote-status ");
+  }
+
+  @Test()
+  public void testVoteStatusVoteOneSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine("vote-status vote ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusConsensus() throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine("vote-status consensus");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVoteStatusTheMagicVoteStatus()
+      throws DescriptorParseException {
+    VoteBuilder.createWithVoteStatusLine(
+        "vote-status TheMagicVoteStatus");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodNoLine()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodNoSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine("consensus-methods");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodOneSpace()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine("consensus-methods ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodEleven()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine(
+        "consensus-methods eleven");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodMinusOne()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine("consensus-methods -1");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodNinePeriod()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine("consensus-methods "
+        + "999999999999999999999999999999999999999999999999999999999999");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testConsensusMethodTwoLines()
+      throws DescriptorParseException {
+    VoteBuilder.createWithConsensusMethodsLine(
+        "consensus-method 1\nconsensus-method 1");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testPublishedNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithPublishedLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidAfterNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithValidAfterLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidAfterNoSpace() throws DescriptorParseException {
+    VoteBuilder.createWithValidAfterLine("valid-after");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidAfterOneSpace() throws DescriptorParseException {
+    VoteBuilder.createWithValidAfterLine("valid-after ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidAfterLongAgo() throws DescriptorParseException {
+    VoteBuilder.createWithValidAfterLine("valid-after long ago");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidAfterFeb30() throws DescriptorParseException {
+    VoteBuilder.createWithValidAfterLine(
+        "valid-after 2011-02-30 09:00:00");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testFreshUntilNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithFreshUntilLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testFreshUntilAroundTen() throws DescriptorParseException {
+    VoteBuilder.createWithFreshUntilLine(
+        "fresh-until 2011-11-30 around ten");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testValidUntilTomorrowMorning()
+      throws DescriptorParseException {
+    VoteBuilder.createWithValidUntilLine(
+        "valid-until tomorrow morning");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelayNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelayNoSpace() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine("voting-delay");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelayOneSpace() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine("voting-delay ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelayTriple() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine(
+        "voting-delay 300 300 300");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelaySingle() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine("voting-delay 300");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testVotingDelayOneTwo() throws DescriptorParseException {
+    VoteBuilder.createWithVotingDelayLine("voting-delay one two");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testClientVersionsComma() throws DescriptorParseException {
+    VoteBuilder.createWithClientVersionsLine("client-versions ,");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testClientVersionsCommaVersion()
+      throws DescriptorParseException {
+    VoteBuilder.createWithClientVersionsLine(
+        "client-versions ,0.2.2.34");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testKnownFlagsNoLine() throws DescriptorParseException {
+    VoteBuilder.createWithKnownFlagsLine(null);
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testKnownFlagsNoSpace() throws DescriptorParseException {
+    VoteBuilder.createWithKnownFlagsLine("known-flags");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testKnownFlagsOneSpace() throws DescriptorParseException {
+    VoteBuilder.createWithKnownFlagsLine("known-flags ");
+  }
+}
+



More information about the tor-commits mailing list