[tor-commits] [metrics-lib/master] Make an abstract network status parser class.

karsten at torproject.org karsten at torproject.org
Thu Dec 15 14:34:24 UTC 2011


commit a7e7c7f52768891c3ba49e3e1068c03ebb40a287
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Dec 15 15:28:30 2011 +0100

    Make an abstract network status parser class.
    
    This will allow us to parse v3 consensuses, v3 votes, v3 microdesc
    consensuses, v2 statuses, and sanitized bridge network statuses without
    duplicating too much code.  v1 directories are too different, though.
---
 .../descriptor/impl/NetworkStatusImpl.java         |  357 ++++++++++++++++++++
 .../impl/RelayNetworkStatusConsensusImpl.java      |  269 ++++-----------
 .../impl/RelayNetworkStatusConsensusImplTest.java  |    7 +
 3 files changed, 434 insertions(+), 199 deletions(-)

diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
new file mode 100644
index 0000000..f7488c6
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -0,0 +1,357 @@
+/* Copyright 2011 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.torproject.descriptor.DirSourceEntry;
+import org.torproject.descriptor.NetworkStatusEntry;
+
+/* Parse the common parts of v3 consensuses, v3 votes, v3 microdesc
+ * consensuses, v2 statuses, and sanitized bridge network statuses and
+ * delegate the specific parts to the subclasses. */
+public abstract class NetworkStatusImpl {
+
+  protected static List<byte[]> splitRawDescriptorBytes(
+      byte[] rawDescriptorBytes, String startToken) {
+    List<byte[]> rawDescriptors = new ArrayList<byte[]>();
+    String splitToken = "\n" + startToken;
+    String ascii = new String(rawDescriptorBytes);
+    int length = rawDescriptorBytes.length,
+        start = ascii.indexOf(startToken);
+    while (start < length) {
+      int end = ascii.indexOf(splitToken, start);
+      if (end < 0) {
+        end = length;
+      } else {
+        end += 1;
+      }
+      byte[] rawDescriptor = new byte[end - start];
+      System.arraycopy(rawDescriptorBytes, start, rawDescriptor, 0,
+          end - start);
+      start = end;
+      rawDescriptors.add(rawDescriptor);
+    }
+    return rawDescriptors;
+  }
+
+  private byte[] rawDescriptorBytes;
+  public byte[] getRawDescriptorBytes() {
+    return this.rawDescriptorBytes;
+  }
+
+  protected NetworkStatusImpl(byte[] rawDescriptorBytes)
+      throws DescriptorParseException {
+    this.rawDescriptorBytes = rawDescriptorBytes;
+    this.splitAndParseParts(rawDescriptorBytes);
+  }
+
+  private void splitAndParseParts(byte[] rawDescriptorBytes)
+      throws DescriptorParseException {
+    if (this.rawDescriptorBytes.length == 0) {
+      throw new DescriptorParseException("Descriptor is empty.");
+    }
+    String descriptorString = new String(rawDescriptorBytes);
+    if (descriptorString.startsWith("\n") ||
+        descriptorString.contains("\n\n")) {
+      throw new DescriptorParseException("Empty lines are not allowed.");
+    }
+    int startIndex = 0;
+    int firstDirSourceIndex = this.findFirstIndexOfKeyword(
+        descriptorString, "dir-source");
+    int firstRIndex = this.findFirstIndexOfKeyword(descriptorString, "r");
+    int directoryFooterIndex = this.findFirstIndexOfKeyword(
+        descriptorString, "directory-footer");
+    int firstDirectorySignatureIndex = this.findFirstIndexOfKeyword(
+        descriptorString, "directory-signature");
+    int endIndex = descriptorString.length();
+    if (firstDirectorySignatureIndex < 0) {
+      firstDirectorySignatureIndex = endIndex;
+    }
+    if (directoryFooterIndex < 0) {
+      directoryFooterIndex = firstDirectorySignatureIndex;
+    }
+    if (firstRIndex < 0) {
+      firstRIndex = directoryFooterIndex;
+    }
+    if (firstDirSourceIndex < 0) {
+      firstDirSourceIndex = firstRIndex;
+    }
+    if (firstDirSourceIndex > startIndex) {
+      this.parseHeaderBytes(descriptorString, startIndex,
+          firstDirSourceIndex);
+    }
+    if (firstRIndex > firstDirSourceIndex) {
+      this.parseDirSourceBytes(descriptorString, firstDirSourceIndex,
+          firstRIndex);
+    }
+    if (directoryFooterIndex > firstRIndex) {
+      this.parseStatusEntryBytes(descriptorString, firstRIndex,
+          directoryFooterIndex);
+    }
+    if (firstDirectorySignatureIndex > directoryFooterIndex) {
+      this.parseDirectoryFooterBytes(descriptorString,
+          directoryFooterIndex, firstDirectorySignatureIndex);
+    }
+    if (endIndex > firstDirectorySignatureIndex) {
+      this.parseDirectorySignatureBytes(descriptorString,
+          firstDirectorySignatureIndex, 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.rememberFirstKeyword(headerBytes);
+    this.countKeywords(headerBytes);
+    this.parseHeader(headerBytes);
+  }
+
+  private void parseDirSourceBytes(String descriptorString, int start,
+      int end) throws DescriptorParseException {
+    List<byte[]> splitDirSourceBytes =
+        this.splitByKeyword(descriptorString, "dir-source", start, end);
+    for (byte[] dirSourceBytes : splitDirSourceBytes) {
+      this.parseDirSource(dirSourceBytes);
+    }
+  }
+
+  private void parseStatusEntryBytes(String descriptorString, int start,
+      int end) throws DescriptorParseException {
+    List<byte[]> splitStatusEntryBytes =
+        this.splitByKeyword(descriptorString, "r", start, end);
+    for (byte[] statusEntryBytes : splitStatusEntryBytes) {
+      this.parseStatusEntry(statusEntryBytes);
+    }
+  }
+
+  private void parseDirectoryFooterBytes(String descriptorString,
+      int start, int end) throws DescriptorParseException {
+    byte[] directoryFooterBytes = new byte[end - start];
+    System.arraycopy(this.rawDescriptorBytes, start,
+        directoryFooterBytes, 0, end - start);
+    this.countKeywords(directoryFooterBytes);
+    this.parseFooter(directoryFooterBytes);
+  }
+
+  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 = end;
+      } else {
+        to += 1;
+      }
+      byte[] part = new byte[to - from];
+      System.arraycopy(this.rawDescriptorBytes, from, part, 0,
+          to - from);
+      from = to;
+      splitParts.add(part);
+    }
+    return splitParts;
+  }
+
+  protected abstract void parseHeader(byte[] headerBytes)
+      throws DescriptorParseException;
+
+  protected void parseDirSource(byte[] dirSourceBytes)
+      throws DescriptorParseException {
+    DirSourceEntry dirSourceEntry = new DirSourceEntryImpl(
+        dirSourceBytes);
+    this.dirSourceEntries.put(dirSourceEntry.getIdentity(),
+        dirSourceEntry);
+  }
+
+  protected void parseStatusEntry(byte[] statusEntryBytes)
+      throws DescriptorParseException {
+    NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl(
+        statusEntryBytes);
+    this.statusEntries.put(statusEntry.getFingerprint(), statusEntry);
+  }
+
+  protected abstract void parseFooter(byte[] footerBytes)
+      throws DescriptorParseException;
+
+  protected void parseDirectorySignature(byte[] directorySignatureBytes)
+      throws DescriptorParseException {
+    if (this.directorySignatures == null) {
+      this.directorySignatures = new TreeMap<String, String>();
+    }
+    try {
+      BufferedReader br = new BufferedReader(new StringReader(
+          new String(directorySignatureBytes)));
+      String line;
+      while ((line = br.readLine()) != null) {
+        if (line.startsWith("directory-signature ")) {
+          String[] parts = line.split(" ", -1);
+          if (parts.length != 3) {
+            throw new DescriptorParseException("Illegal line '" + line
+                + "'.");
+          }
+          String identity = ParseHelper.parseTwentyByteHexString(line,
+              parts[1]);
+          String signingKeyDigest = ParseHelper.parseTwentyByteHexString(
+              line, parts[2]);
+          this.directorySignatures.put(identity, signingKeyDigest);
+          break;
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Internal error: Ran into an "
+          + "IOException while parsing a String in memory.  Something's "
+          + "really wrong.", e);
+    }
+  }
+
+  private String firstKeyword;
+  protected void rememberFirstKeyword(byte[] headerBytes) {
+    try {
+      BufferedReader br = new BufferedReader(new StringReader(
+          new String(headerBytes)));
+      this.firstKeyword = br.readLine().split(" ", -1)[0];
+    } catch (IOException e) {
+      throw new RuntimeException("Internal error: Ran into an "
+          + "IOException while parsing a String in memory.  Something's "
+          + "really wrong.", e);
+    }
+  }
+
+  protected void checkFirstKeyword(String keyword)
+      throws DescriptorParseException {
+    if (this.firstKeyword == null ||
+        !this.firstKeyword.equals(keyword)) {
+      throw new DescriptorParseException("Keyword '" + keyword + "' must "
+          + "be contained in the first line.");
+    }
+  }
+
+  /* Count parsed keywords in header and footer for consistency checks by
+   * subclasses. */
+  private Map<String, Integer> parsedKeywords =
+      new HashMap<String, Integer>();
+  protected void countKeywords(byte[] headerOrFooterBytes)
+      throws DescriptorParseException {
+    try {
+      BufferedReader br = new BufferedReader(new StringReader(
+          new String(headerOrFooterBytes)));
+      String line;
+      boolean skipCrypto = false;
+      while ((line = br.readLine()) != null) {
+        if (line.startsWith("-----BEGIN SIGNATURE-----")) {
+          skipCrypto = true;
+        } else if (line.equals("-----END SIGNATURE-----")) {
+          skipCrypto = false;
+        } else if (!skipCrypto) {
+          String keyword = line.split(" ", -1)[0];
+          if (keyword.equals("")) {
+            throw new DescriptorParseException("Illegal keyword in line '"
+                + line + "'.");
+          }
+          if (parsedKeywords.containsKey(keyword)) {
+            parsedKeywords.put(keyword, parsedKeywords.get(keyword) + 1);
+          } else {
+            parsedKeywords.put(keyword, 1);
+          }
+        }
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Internal error: Ran into an "
+          + "IOException while parsing a String in memory.  Something's "
+          + "really wrong.", e);
+    }
+  }
+
+  protected void checkExactlyOnceKeywords(Set<String> keywords)
+      throws DescriptorParseException {
+    for (String keyword : keywords) {
+      if (!this.parsedKeywords.containsKey(keyword) ||
+          this.parsedKeywords.get(keyword) != 1) {
+        throw new DescriptorParseException("Keyword '" + keyword + "' is "
+            + "contained " + this.parsedKeywords.get(keyword) + " times, "
+            + "but must be contained exactly once.");
+      }
+    }
+  }
+
+  protected void checkAtLeastOnceKeywords(Set<String> keywords)
+      throws DescriptorParseException {
+    for (String keyword : keywords) {
+      if (!this.parsedKeywords.containsKey(keyword)) {
+        throw new DescriptorParseException("Keyword '" + keyword + "' is "
+            + "contained 0 times, but must be contained at least once.");
+      }
+    }
+  }
+
+  protected void checkAtMostOnceKeywords(Set<String> keywords)
+      throws DescriptorParseException {
+    for (String keyword : keywords) {
+      if (this.parsedKeywords.containsKey(keyword) &&
+          this.parsedKeywords.get(keyword) > 1) {
+        throw new DescriptorParseException("Keyword '" + keyword + "' is "
+            + "contained " + this.parsedKeywords.get(keyword) + " times, "
+            + "but must be contained at most once.");
+      }
+    }
+  }
+
+  private SortedMap<String, DirSourceEntry> dirSourceEntries =
+      new TreeMap<String, DirSourceEntry>();
+  public SortedMap<String, DirSourceEntry> getDirSourceEntries() {
+    return new TreeMap<String, DirSourceEntry>(this.dirSourceEntries);
+  }
+
+  private SortedMap<String, NetworkStatusEntry> statusEntries =
+      new TreeMap<String, NetworkStatusEntry>();
+  public SortedMap<String, NetworkStatusEntry> getStatusEntries() {
+    return new TreeMap<String, NetworkStatusEntry>(this.statusEntries);
+  }
+  public boolean containsStatusEntry(String fingerprint) {
+    return this.statusEntries.containsKey(fingerprint);
+  }
+  public NetworkStatusEntry getStatusEntry(String fingerprint) {
+    return this.statusEntries.get(fingerprint);
+  }
+
+  private SortedMap<String, String> directorySignatures;
+  public SortedMap<String, String> getDirectorySignatures() {
+    return this.directorySignatures == null ? null :
+        new TreeMap<String, String>(this.directorySignatures);
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
index 6721f4d..c4dc88b 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
@@ -7,7 +7,9 @@ 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;
@@ -19,119 +21,57 @@ import org.torproject.descriptor.NetworkStatusEntry;
 import org.torproject.descriptor.RelayNetworkStatusConsensus;
 
 /* Contains a network status consensus. */
-public class RelayNetworkStatusConsensusImpl
+public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
     implements RelayNetworkStatusConsensus {
 
   protected static List<RelayNetworkStatusConsensus> parseConsensuses(
-      byte[] consensusBytes) {
+      byte[] consensusesBytes) {
     List<RelayNetworkStatusConsensus> parsedConsensuses =
         new ArrayList<RelayNetworkStatusConsensus>();
-    String startToken = "network-status-version 3";
-    String splitToken = "\n" + startToken;
-    String ascii = new String(consensusBytes);
-    int length = consensusBytes.length, start = ascii.indexOf(startToken);
-    while (start < length) {
-      int end = ascii.indexOf(splitToken, start);
-      if (end < 0) {
-        end = length;
-      } else {
-        end += 1;
-      }
-      byte[] descBytes = new byte[end - start];
-      System.arraycopy(consensusBytes, start, descBytes, 0, end - start);
-      start = end;
-      try {
+    List<byte[]> splitConsensusBytes =
+        NetworkStatusImpl.splitRawDescriptorBytes(consensusesBytes,
+        "network-status-version 3");
+    try {
+      for (byte[] consensusBytes : splitConsensusBytes) {
         RelayNetworkStatusConsensus parsedConsensus =
-            new RelayNetworkStatusConsensusImpl(descBytes);
+            new RelayNetworkStatusConsensusImpl(consensusBytes);
         parsedConsensuses.add(parsedConsensus);
-      } catch (DescriptorParseException e) {
-        /* TODO Handle this error somehow. */
-        System.err.println("Failed to parse consensus.  Skipping.");
-        e.printStackTrace();
       }
+    } catch (DescriptorParseException e) {
+      /* TODO Handle this error somehow. */
+      System.err.println("Failed to parse consensus.  Skipping.");
+      e.printStackTrace();
     }
     return parsedConsensuses;
   }
 
   protected RelayNetworkStatusConsensusImpl(byte[] consensusBytes)
       throws DescriptorParseException {
-    this.consensusBytes = consensusBytes;
-    this.initializeKeywords();
-    this.parseConsensusBytes();
-    this.checkKeywords();
-  }
-
-  private SortedSet<String> exactlyOnceKeywords, atMostOnceKeywords;
-  private void initializeKeywords() {
-    this.exactlyOnceKeywords = new TreeSet<String>();
-    this.exactlyOnceKeywords.add("vote-status");
-    this.exactlyOnceKeywords.add("consensus-method");
-    this.exactlyOnceKeywords.add("valid-after");
-    this.exactlyOnceKeywords.add("fresh-until");
-    this.exactlyOnceKeywords.add("valid-until");
-    this.exactlyOnceKeywords.add("voting-delay");
-    this.exactlyOnceKeywords.add("known-flags");
-    this.exactlyOnceKeywords.add("directory-footer");
-    this.atMostOnceKeywords = new TreeSet<String>();
-    this.atMostOnceKeywords.add("client-versions");
-    this.atMostOnceKeywords.add("server-versions");
-    this.atMostOnceKeywords.add("params");
-    this.atMostOnceKeywords.add("bandwidth-weights");
-  }
-
-  private void parsedExactlyOnceKeyword(String keyword)
+    super(consensusBytes);
+    Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
+        "vote-status,consensus-method,valid-after,fresh-until,"
+        + "valid-until,voting-delay,known-flags,"
+        + "directory-footer").split(",")));
+    this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+    Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
+        "client-versions,server-versions,params,"
+        + "bandwidth-weights").split(",")));
+    this.checkAtMostOnceKeywords(atMostOnceKeywords);
+    this.checkFirstKeyword("network-status-version");
+  }
+
+  protected void parseHeader(byte[] headerBytes)
       throws DescriptorParseException {
-    if (!this.exactlyOnceKeywords.contains(keyword)) {
-      throw new DescriptorParseException("Duplicate '" + keyword
-          + "' line in consensus.");
-    }
-    this.exactlyOnceKeywords.remove(keyword);
-  }
-
-  private void parsedAtMostOnceKeyword(String keyword)
-      throws DescriptorParseException {
-    if (!this.atMostOnceKeywords.contains(keyword)) {
-      throw new DescriptorParseException("Duplicate " + keyword + "line "
-          + "in consensus.");
-    }
-    this.atMostOnceKeywords.remove(keyword);
-  }
-
-  private void checkKeywords() throws DescriptorParseException {
-    if (!this.exactlyOnceKeywords.isEmpty()) {
-      throw new DescriptorParseException("Consensus does not contain a '"
-          + this.exactlyOnceKeywords.first() + "' line.");
-    }
-  }
-
-  private void parseConsensusBytes() throws DescriptorParseException {
     try {
       BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.consensusBytes)));
-      String line = br.readLine();
-      if (line == null || !line.equals("network-status-version 3")) {
-        throw new DescriptorParseException("Consensus must start with "
-            + "line 'network-status-version 3'.");
-      }
-      this.networkStatusVersion = 3;
-      StringBuilder dirSourceEntryLines = null, statusEntryLines = null;
-      boolean skipSignature = false;
+          new String(headerBytes)));
+      String line;
       while ((line = br.readLine()) != null) {
-        if (line.length() < 1) {
-          throw new DescriptorParseException("Empty lines are not "
-              + "allowed in a consensus.");
-        }
         String[] parts = line.split(" ");
-        if (parts.length < 1) {
-          throw new DescriptorParseException("No keyword found in line '"
-              + line + "'.");
-        }
         String keyword = parts[0];
-        if (keyword.length() < 1) {
-          throw new DescriptorParseException("Empty keyword in line '"
-              + line + "'.");
-        }
-        if (keyword.equals("vote-status")) {
+        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-method")) {
           this.parseConsensusMethodLine(line, parts);
@@ -151,46 +91,34 @@ public class RelayNetworkStatusConsensusImpl
           this.parseKnownFlagsLine(line, parts);
         } else if (keyword.equals("params")) {
           this.parseParamsLine(line, parts);
-        } else if (keyword.equals("dir-source") || keyword.equals("r") ||
-            keyword.equals("directory-footer")) {
-          if (dirSourceEntryLines != null) {
-            this.parseDirSourceEntryLines(dirSourceEntryLines.toString());
-            dirSourceEntryLines = null;
-          }
-          if (statusEntryLines != null) {
-            this.parseStatusEntryLines(statusEntryLines.toString());
-            statusEntryLines = null;
-          }
-          if (keyword.equals("dir-source")) {
-            dirSourceEntryLines = new StringBuilder(line + "\n");
-          } else if (keyword.equals("r")) {
-            statusEntryLines = new StringBuilder(line + "\n");
-          } else if (keyword.equals("directory-footer")) {
-            this.parsedExactlyOnceKeyword("directory-footer");
-          }
-        } else if (keyword.equals("contact") ||
-            keyword.equals("vote-digest")) {
-          if (dirSourceEntryLines == null) {
-            throw new DescriptorParseException(keyword + " line with no "
-                + "preceding dir-source line.");
-          }
-          dirSourceEntryLines.append(line + "\n");
-        } else if (keyword.equals("s") || keyword.equals("v") ||
-            keyword.equals("w") || keyword.equals("p")) {
-          if (statusEntryLines == null) {
-            throw new DescriptorParseException(keyword + " line with no "
-                + "preceding r line.");
-          }
-          statusEntryLines.append(line + "\n");
+        } 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);
+    }
+  }
+
+  protected void parseFooter(byte[] footerBytes)
+      throws DescriptorParseException {
+    try {
+      BufferedReader br = new BufferedReader(new StringReader(
+          new String(footerBytes)));
+      String line;
+      while ((line = br.readLine()) != null) {
+        String[] parts = line.split(" ");
+        String keyword = parts[0];
+        if (keyword.equals("directory-footer")) {
         } else if (keyword.equals("bandwidth-weights")) {
           this.parseBandwidthWeightsLine(line, parts);
-        } else if (keyword.equals("directory-signature")) {
-          this.parseDirectorySignatureLine(line, parts);
-        } else if (line.equals("-----BEGIN SIGNATURE-----")) {
-          skipSignature = true;
-        } else if (line.equals("-----END SIGNATURE-----")) {
-          skipSignature = false;
-        } else if (!skipSignature) {
+        } 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. */
@@ -205,9 +133,17 @@ public class RelayNetworkStatusConsensusImpl
     }
   }
 
+  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 {
-    this.parsedExactlyOnceKeyword("vote-status");
     if (parts.length != 2 || !parts[1].equals("consensus")) {
       throw new DescriptorParseException("Line '" + line + "' indicates "
           + "that this is not a consensus.");
@@ -216,7 +152,6 @@ public class RelayNetworkStatusConsensusImpl
 
   private void parseConsensusMethodLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("consensus-method");
     if (parts.length != 2) {
       throw new DescriptorParseException("Illegal line '" + line
           + "' in consensus.");
@@ -235,28 +170,24 @@ public class RelayNetworkStatusConsensusImpl
 
   private void parseValidAfterLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("valid-after");
     this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts,
         1, 2);
   }
 
   private void parseFreshUntilLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("fresh-until");
     this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
         1, 2);
   }
 
   private void parseValidUntilLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("valid-until");
     this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
         1, 2);
   }
 
   private void parseVotingDelayLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("voting-delay");
     if (parts.length != 3) {
       throw new DescriptorParseException("Wrong number of values in line "
           + "'" + line + "'.");
@@ -272,14 +203,12 @@ public class RelayNetworkStatusConsensusImpl
 
   private void parseClientVersionsLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedAtMostOnceKeyword("client-versions");
     this.recommendedClientVersions = this.parseClientOrServerVersions(
         line, parts);
   }
 
   private void parseServerVersionsLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedAtMostOnceKeyword("server-versions");
     this.recommendedServerVersions = this.parseClientOrServerVersions(
         line, parts);
   }
@@ -307,11 +236,11 @@ public class RelayNetworkStatusConsensusImpl
 
   private void parseKnownFlagsLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedExactlyOnceKeyword("known-flags");
     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]);
     }
@@ -319,49 +248,15 @@ public class RelayNetworkStatusConsensusImpl
 
   private void parseParamsLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedAtMostOnceKeyword("params");
     this.consensusParams = ParseHelper.parseKeyValuePairs(line, parts, 1);
   }
 
-  private void parseDirSourceEntryLines(String string)
-      throws DescriptorParseException {
-    DirSourceEntry dirSourceEntry = new DirSourceEntryImpl(
-        string.getBytes());
-    this.dirSourceEntries.put(dirSourceEntry.getIdentity(),
-        dirSourceEntry);
-  }
-
-  private void parseStatusEntryLines(String string)
-      throws DescriptorParseException {
-    NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl(
-        string.getBytes());
-    this.statusEntries.put(statusEntry.getFingerprint(), statusEntry);
-  }
-
   private void parseBandwidthWeightsLine(String line, String[] parts)
       throws DescriptorParseException {
-    this.parsedAtMostOnceKeyword("bandwidth-weights");
     this.bandwidthWeights = ParseHelper.parseKeyValuePairs(line, parts,
         1);
   }
 
-  private void parseDirectorySignatureLine(String line, String[] parts)
-      throws DescriptorParseException {
-    if (parts.length != 3) {
-      throw new DescriptorParseException("Illegal line '" + line + "'.");
-    }
-    String identity = ParseHelper.parseTwentyByteHexString(line,
-        parts[1]);
-    String signingKeyDigest = ParseHelper.parseTwentyByteHexString(line,
-        parts[2]);
-    this.directorySignatures.put(identity, signingKeyDigest);
-  }
-
-  private byte[] consensusBytes;
-  public byte[] getRawDescriptorBytes() {
-    return this.consensusBytes;
-  }
-
   private int networkStatusVersion;
   public int getNetworkStatusVersion() {
     return this.networkStatusVersion;
@@ -409,7 +304,7 @@ public class RelayNetworkStatusConsensusImpl
         new ArrayList<String>(this.recommendedServerVersions);
   }
 
-  private SortedSet<String> knownFlags = new TreeSet<String>();
+  private SortedSet<String> knownFlags;
   public SortedSet<String> getKnownFlags() {
     return new TreeSet<String>(this.knownFlags);
   }
@@ -420,30 +315,6 @@ public class RelayNetworkStatusConsensusImpl
         new TreeMap<String, Integer>(this.consensusParams);
   }
 
-  private SortedMap<String, DirSourceEntry> dirSourceEntries =
-      new TreeMap<String, DirSourceEntry>();
-  public SortedMap<String, DirSourceEntry> getDirSourceEntries() {
-    return new TreeMap<String, DirSourceEntry>(this.dirSourceEntries);
-  }
-
-  private SortedMap<String, NetworkStatusEntry> statusEntries =
-      new TreeMap<String, NetworkStatusEntry>();
-  public SortedMap<String, NetworkStatusEntry> getStatusEntries() {
-    return new TreeMap<String, NetworkStatusEntry>(this.statusEntries);
-  }
-  public boolean containsStatusEntry(String fingerprint) {
-    return this.statusEntries.containsKey(fingerprint);
-  }
-  public NetworkStatusEntry getStatusEntry(String fingerprint) {
-    return this.statusEntries.get(fingerprint);
-  }
-
-  private SortedMap<String, String> directorySignatures =
-      new TreeMap<String, String>();
-  public SortedMap<String, String> getDirectorySignatures() {
-    return new TreeMap<String, String>(this.directorySignatures);
-  }
-
   private SortedMap<String, Integer> bandwidthWeights;
   public SortedMap<String, Integer> getBandwidthWeights() {
     return this.bandwidthWeights == null ? null :
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
index 520473d..db20638 100644
--- a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
@@ -537,6 +537,13 @@ public class RelayNetworkStatusConsensusImplTest {
   }
 
   @Test(expected = DescriptorParseException.class)
+  public void testNetworkStatusVersionNewLineSpace()
+      throws DescriptorParseException {
+    ConsensusBuilder.createWithNetworkStatusVersionLine(
+        "network-status-version 3\n ");
+  }
+
+  @Test(expected = DescriptorParseException.class)
   public void testNetworkStatusVersionPrefixLineAtChar()
       throws DescriptorParseException {
     ConsensusBuilder.createWithNetworkStatusVersionLine(



More information about the tor-commits mailing list