[tor-commits] [metrics-lib/master] Implement parsing of network status votes.

karsten at torproject.org karsten at torproject.org
Tue Dec 13 09:20:11 UTC 2011


commit 276e0ef72aae65f0e69cb55bb4b1d80c537691e9
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Tue Dec 13 10:17:34 2011 +0100

    Implement parsing of network status votes.
---
 .../descriptor/RelayNetworkStatusConsensus.java    |    7 +
 .../descriptor/RelayNetworkStatusVote.java         |   80 +++++-
 .../descriptor/impl/NetworkStatusEntryImpl.java    |    7 +-
 .../impl/RelayNetworkStatusConsensusImpl.java      |    6 +
 .../impl/RelayNetworkStatusVoteImpl.java           |  315 +++++++++++++++++++-
 5 files changed, 408 insertions(+), 7 deletions(-)

diff --git a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
index 192c963..452a0ba 100755
--- a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
+++ b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
@@ -46,6 +46,13 @@ public interface RelayNetworkStatusConsensus extends Descriptor {
   /* Return status entries, one for each contained relay. */
   public SortedMap<String, NetworkStatusEntry> getStatusEntries();
 
+  /* Return whether a status entry with the given fingerprint exists. */
+  public boolean containsStatusEntry(String fingerprint);
+
+  /* Return a status entry by fingerprint or null if no such status entry
+   * exists. */
+  public NetworkStatusEntry getStatusEntry(String fingerprint);
+
   /* Return directory signatures. */
   public SortedMap<String, String> getDirectorySignatures();
 
diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
index 62d1307..7d85a0c 100755
--- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java
+++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
@@ -2,11 +2,87 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor;
 
+import java.util.List;
 import java.util.SortedMap;
 import java.util.SortedSet;
 
-/* Contains the unparsed string and parsed fields from a network status
- * vote. */
+/* Contains a network status vote. */
 public interface RelayNetworkStatusVote extends Descriptor {
+
+  /* Return the network status version. */
+  public int getNetworkStatusVersion();
+
+  /* Return the consensus method. */
+  public List<Integer> getConsensusMethods();
+
+  /* Return the publication time in milliseconds. */
+  public long getPublishedMillis();
+
+  /* Return the valid-after time in milliseconds. */
+  public long getValidAfterMillis();
+
+  /* Return the fresh-until time in milliseconds. */
+  public long getFreshUntilMillis();
+
+  /* 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 cecommended server versions. */
+  public SortedSet<String> getRecommendedServerVersions();
+
+  /* Return recommended client versions. */
+  public SortedSet<String> getRecommendedClientVersions();
+
+  /* Return known relay flags. */
+  public SortedSet<String> getKnownFlags();
+
+  /* Return consensus parameters. */
+  public SortedMap<String, String> getConsensusParams();
+
+  /* Return the directory nickname. */
+  public String getNickname();
+
+  /* Return the directory identity. */
+  public String getIdentity();
+
+  /* Return the IP address. */
+  public String getAddress();
+
+  /* Return the DiRPort. */
+  public int getDirport();
+
+  /* Return the ORPort. */
+  public int getOrport();
+
+  /* Return the contact line. */
+  public String getContactLine();
+
+  /* Return the directory key certificate version. */
+  public int getDirKeyCertificateVersion();
+
+  /* Return the directory key publication timestamp. */
+  public long getDirKeyPublishedMillis();
+
+  /* Return the directory key expiration timestamp. */
+  public long getDirKeyExpiresMillis();
+
+  /* Return the signing key digest. */
+  public String getSigningKeyDigest();
+
+  /* Return status entries, one for each contained relay. */
+  public SortedMap<String, NetworkStatusEntry> getStatusEntries();
+
+  /* Return whether a status entry with the given fingerprint exists. */
+  public boolean containsStatusEntry(String fingerprint);
+
+  /* Return a status entry by fingerprint or null if no such status entry
+   * exists. */
+  public NetworkStatusEntry getStatusEntry(String fingerprint);
+
+  /* Return directory signatures. */
+  public SortedMap<String, String> getDirectorySignatures();
 }
 
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
index d0a511d..610fb1a 100755
--- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
@@ -57,12 +57,15 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
         } else if (line.startsWith("s ")) {
           this.flags.addAll(Arrays.asList(line.substring("s ".length()).
               split(" ")));
-        } else if (line.startsWith("v ")) {
-          this.version = line.substring("v ".length());
+        } else if (line.startsWith("v ") || line.startsWith("opt v")) {
+          this.version = line.substring(
+              line.startsWith("v ") ? "v ".length() : "opt v".length());
         } else if (line.startsWith("w ")) {
           this.bandwidth = line.substring("w ".length());
         } else if (line.startsWith("p ")) {
           this.ports = line.substring(2);
+        } else if (line.startsWith("m ")) {
+          /* TODO Parse m lines in votes. */
         } else {
           throw new RuntimeException("Unknown line '" + line + "' in "
               + "status entry.");
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
index a9579a6..3d2b0a2 100755
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
@@ -291,6 +291,12 @@ public class RelayNetworkStatusConsensusImpl
   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>();
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index 84a81e2..fa5b4d5 100755
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -2,14 +2,26 @@
  * 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.Arrays;
 import java.util.List;
+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;
 
 /* Contains a network status vote. */
-/* TODO This class doesn't contain any parsing code yet, and it would be
- * sharing a lot of that code with the consensus class.  Should there be
- * an abstract super class for the two? */
+/* 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
     implements RelayNetworkStatusVote {
 
@@ -40,11 +52,308 @@ public class RelayNetworkStatusVoteImpl
 
   protected RelayNetworkStatusVoteImpl(byte[] voteBytes) {
     this.voteBytes = voteBytes;
+    this.parseVoteBytes();
+    this.checkConsistency();
+    /* TODO Find a way to handle parse and consistency-check problems. */
+  }
+
+  private void parseVoteBytes() {
+    String line = null;
+    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;
+      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.addAll(
+              Arrays.asList(line.split(" ")[1].split(",")));
+        } else if (line.startsWith("server-versions ")) {
+          this.recommendedServerVersions.addAll(
+              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];
+              String paramValue = 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) {
+            NetworkStatusEntryImpl statusEntry =
+                new NetworkStatusEntryImpl(
+                statusEntryLines.toString().getBytes());
+            this.statusEntries.put(statusEntry.getFingerprint(),
+                statusEntry);
+            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 + "'.");
+        }
+      }
+    } 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 + "'.");
+    } 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. */
+    }
   }
 
   private byte[] voteBytes;
   public byte[] getRawDescriptorBytes() {
     return this.voteBytes;
   }
+
+  private int networkStatusVersion;
+  public int getNetworkStatusVersion() {
+    return this.networkStatusVersion;
+  }
+
+  private List<Integer> consensusMethods = new ArrayList<Integer>();
+  public List<Integer> getConsensusMethods() {
+    return this.consensusMethods;
+  }
+
+  private long publishedMillis;
+  public long getPublishedMillis() {
+    return this.publishedMillis;
+  }
+
+  private long validAfterMillis;
+  public long getValidAfterMillis() {
+    return this.validAfterMillis;
+  }
+
+  private long freshUntilMillis;
+  public long getFreshUntilMillis() {
+    return this.freshUntilMillis;
+  }
+
+  private long validUntilMillis;
+  public long getValidUntilMillis() {
+    return this.validUntilMillis;
+  }
+
+  private List<Long> votingDelay = new ArrayList<Long>();
+  public List<Long> getVotingDelay() {
+    return new ArrayList<Long>(this.votingDelay);
+  }
+
+  private SortedSet<String> recommendedClientVersions =
+      new TreeSet<String>();
+  public SortedSet<String> getRecommendedClientVersions() {
+    return new TreeSet<String>(this.recommendedClientVersions);
+  }
+
+  private SortedSet<String> recommendedServerVersions =
+      new TreeSet<String>();
+  public SortedSet<String> getRecommendedServerVersions() {
+    return new TreeSet<String>(this.recommendedServerVersions);
+  }
+
+  private SortedSet<String> knownFlags = new TreeSet<String>();
+  public SortedSet<String> getKnownFlags() {
+    return new TreeSet<String>(this.knownFlags);
+  }
+
+  private SortedMap<String, String> consensusParams =
+      new TreeMap<String, String>();
+  public SortedMap<String, String> getConsensusParams() {
+    return new TreeMap<String, String>(this.consensusParams);
+  }
+
+  private String nickname;
+  public String getNickname() {
+    return this.nickname;
+  }
+
+  private String identity;
+  public String getIdentity() {
+    return this.identity;
+  }
+
+  private String address;
+  public String getAddress() {
+    return this.address;
+  }
+
+  private int dirPort;
+  public int getDirport() {
+    return this.dirPort;
+  }
+
+  private int orPort;
+  public int getOrport() {
+    return this.orPort;
+  }
+
+  private String contactLine;
+  public String getContactLine() {
+    return this.contactLine;
+  }
+
+  private int dirKeyCertificateVersion;
+  public int getDirKeyCertificateVersion() {
+    return this.dirKeyCertificateVersion;
+  }
+
+  private long dirKeyPublishedMillis;
+  public long getDirKeyPublishedMillis() {
+    return this.dirKeyPublishedMillis;
+  }
+
+  private long dirKeyExpiresMillis;
+  public long getDirKeyExpiresMillis() {
+    return this.dirKeyExpiresMillis;
+  }
+
+  private String signingKeyDigest;
+  public String getSigningKeyDigest() {
+    return this.signingKeyDigest;
+  }
+
+  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 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.");
+    }
+  }
 }
 



More information about the tor-commits mailing list