[tor-commits] [metrics-lib/master] Parse micodesc consensuses and microdescriptors.

karsten at torproject.org karsten at torproject.org
Wed Jan 22 08:05:46 UTC 2014


commit 38c48ddd0c49978bbfa5e0a987cfd3a890692a5c
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Jan 9 14:24:03 2014 +0100

    Parse micodesc consensuses and microdescriptors.
    
    Required for implementing #2785.
---
 .../torproject/descriptor/DirectorySignature.java  |    3 +
 src/org/torproject/descriptor/Microdescriptor.java |   47 ++++
 .../torproject/descriptor/NetworkStatusEntry.java  |   11 +-
 .../descriptor/RelayNetworkStatusConsensus.java    |    5 +-
 .../torproject/descriptor/impl/DescriptorImpl.java |   12 +-
 .../descriptor/impl/DirectorySignatureImpl.java    |   15 +-
 .../descriptor/impl/MicrodescriptorImpl.java       |  261 ++++++++++++++++++++
 .../descriptor/impl/NetworkStatusEntryImpl.java    |   51 +++-
 .../descriptor/impl/NetworkStatusImpl.java         |    8 +-
 .../torproject/descriptor/impl/ParseHelper.java    |   17 +-
 .../impl/RelayNetworkStatusConsensusImpl.java      |   37 ++-
 .../descriptor/impl/ServerDescriptorImpl.java      |    2 +-
 12 files changed, 440 insertions(+), 29 deletions(-)

diff --git a/src/org/torproject/descriptor/DirectorySignature.java b/src/org/torproject/descriptor/DirectorySignature.java
index 29eb055..012b770 100644
--- a/src/org/torproject/descriptor/DirectorySignature.java
+++ b/src/org/torproject/descriptor/DirectorySignature.java
@@ -4,6 +4,9 @@ package org.torproject.descriptor;
 
 public interface DirectorySignature {
 
+  /* Return the digest algorithm, which is "sha1" by default. */
+  public String getAlgorithm();
+
   /* Return the directory identity fingerprint. */
   public String getIdentity();
 
diff --git a/src/org/torproject/descriptor/Microdescriptor.java b/src/org/torproject/descriptor/Microdescriptor.java
new file mode 100644
index 0000000..f106f83
--- /dev/null
+++ b/src/org/torproject/descriptor/Microdescriptor.java
@@ -0,0 +1,47 @@
+/* Copyright 2014 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+import java.util.List;
+
+/* Contains a relay microdescriptor. */
+public interface Microdescriptor extends Descriptor {
+
+  /* Return the descriptor digest that is used to reference this
+   * microdescriptor in a network status. */
+  public String getMicrodescriptorDigest();
+
+  /* Return the onion key in PEM format. */
+  public String getOnionKey();
+
+  /* Return the ntor onion key base64 string with padding omitted, or null
+   * if the microdescriptor didn't contain an ntor onion key line. */
+  public String getNtorOnionKey();
+
+  /* Return the relay's additional OR addresses and ports contained in
+   * or-address lines, or an empty list if the microdescriptor doesn't
+   * contain such lines. */
+  public List<String> getOrAddresses();
+
+  /* Return nicknames, ($-prefixed) fingerprints, $fingerprint=nickname,
+   * or $fingerprint~nickname tuples contained in the family line of this
+   * relay, or null if the descriptor does not contain a family line. */
+  public List<String> getFamilyEntries();
+
+  /* Return the default policy of the port summary or null if the
+   * microdescriptor didn't contain a port summary line. */
+  public String getDefaultPolicy();
+
+  /* Return the port list of the port summary or null if the
+   * microdescriptor didn't contain a port summary line. */
+  public String getPortList();
+
+  /* Return the default policy of the IPv6 port summary or null if the
+   * microdescriptor didn't contain an IPv6 port summary line. */
+  public String getIpv6DefaultPolicy();
+
+  /* Return the port list of the IPv6 port summary or null if the
+   * microdescriptor didn't contain an IPv6 port summary line. */
+  public String getIpv6PortList();
+}
+
diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java
index 2f0bb89..cb15ce2 100644
--- a/src/org/torproject/descriptor/NetworkStatusEntry.java
+++ b/src/org/torproject/descriptor/NetworkStatusEntry.java
@@ -1,8 +1,9 @@
-/* Copyright 2011, 2012 The Tor Project
+/* Copyright 2011--2014 The Tor Project
  * See LICENSE for licensing information */
 package org.torproject.descriptor;
 
 import java.util.List;
+import java.util.Set;
 import java.util.SortedSet;
 
 /* Status entry contained in a network status with version 2 or higher or
@@ -18,7 +19,8 @@ public interface NetworkStatusEntry {
   /* Return the relay fingerprint. */
   public String getFingerprint();
 
-  /* Return the descriptor identity. */
+  /* Return the descriptor identity or null if the containing status is a
+   * microdesc consensus. */
   public String getDescriptor();
 
   /* Return the publication timestamp. */
@@ -33,6 +35,11 @@ public interface NetworkStatusEntry {
   /* Return the DirPort. */
   public int getDirPort();
 
+  /* Return the (possibly empty) set of microdescriptor digest(s) if the
+   * containing status is a vote or microdesc consensus, or null
+   * otherwise. */
+  public Set<String> getMicrodescriptorDigests();
+
   /* Return the relay's additional OR addresses and ports contained in
    * or-address lines, or an empty list if the network status doesn't
    * contain such lines. */
diff --git a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
index 4a1634f..53eaf6b 100644
--- a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
+++ b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java
@@ -1,4 +1,4 @@
-/* Copyright 2011, 2012 The Tor Project
+/* Copyright 2011--2014 The Tor Project
  * See LICENSE for licensing information */
 package org.torproject.descriptor;
 
@@ -12,6 +12,9 @@ public interface RelayNetworkStatusConsensus extends Descriptor {
   /* Return the network status version. */
   public int getNetworkStatusVersion();
 
+  /* Return the flavor name, or null if this consensus is unflavored. */
+  public String getConsensusFlavor();
+
   /* Return the consensus method. */
   public int getConsensusMethod();
 
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java
index b1e4408..770ee4f 100644
--- a/src/org/torproject/descriptor/impl/DescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java
@@ -27,8 +27,10 @@ public abstract class DescriptorImpl implements Descriptor {
         first100Chars.length);
     String firstLines = new String(first100Chars);
     if (firstLines.startsWith("@type network-status-consensus-3 1.") ||
-        ((firstLines.startsWith("network-status-version 3\n") ||
-        firstLines.contains("\nnetwork-status-version 3\n")) &&
+        firstLines.startsWith("@type network-status-microdesc-"
+            + "consensus-3 1.") ||
+        ((firstLines.startsWith("network-status-version 3") ||
+        firstLines.contains("\nnetwork-status-version 3")) &&
         firstLines.contains("\nvote-status consensus\n"))) {
       parsedDescriptors.addAll(RelayNetworkStatusConsensusImpl.
           parseConsensuses(rawDescriptorBytes,
@@ -58,6 +60,12 @@ public abstract class DescriptorImpl implements Descriptor {
       parsedDescriptors.addAll(ExtraInfoDescriptorImpl.
           parseDescriptors(rawDescriptorBytes,
           failUnrecognizedDescriptorLines));
+    } else if (firstLines.startsWith("@type microdescriptor 1.") ||
+        firstLines.startsWith("onion-key\n") ||
+        firstLines.contains("\nonion-key\n")) {
+      parsedDescriptors.addAll(MicrodescriptorImpl.
+          parseDescriptors(rawDescriptorBytes,
+          failUnrecognizedDescriptorLines));
     } else if (firstLines.startsWith("@type bridge-pool-assignment 1.") ||
         firstLines.startsWith("bridge-pool-assignment ") ||
         firstLines.contains("\nbridge-pool-assignment ")) {
diff --git a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java
index d205345..d8548ec 100644
--- a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java
+++ b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java
@@ -41,14 +41,18 @@ public class DirectorySignatureImpl implements DirectorySignature {
       String line = s.next();
       if (line.startsWith("directory-signature ")) {
         String[] parts = line.split(" ", -1);
-        if (parts.length != 3) {
+        int algorithmOffset = 0;
+        if (parts.length == 4) {
+          this.algorithm = parts[1];
+          algorithmOffset = 1;
+        } else if (parts.length != 3) {
           throw new DescriptorParseException("Illegal line '" + line
               + "'.");
         }
         this.identity = ParseHelper.parseTwentyByteHexString(line,
-            parts[1]);
+            parts[1 + algorithmOffset]);
         this.signingKeyDigest = ParseHelper.parseTwentyByteHexString(
-            line, parts[2]);
+            line, parts[2 + algorithmOffset]);
       } else if (line.startsWith("-----BEGIN")) {
         crypto = new StringBuilder();
         crypto.append(line + "\n");
@@ -73,6 +77,11 @@ public class DirectorySignatureImpl implements DirectorySignature {
     }
   }
 
+  private String algorithm = "sha1";
+  public String getAlgorithm() {
+    return this.algorithm;
+  }
+
   private String identity;
   public String getIdentity() {
     return this.identity;
diff --git a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java
new file mode 100644
index 0000000..15fb91e
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java
@@ -0,0 +1,261 @@
+/* Copyright 2014 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.torproject.descriptor.Microdescriptor;
+
+/* Contains a microdescriptor. */
+public class MicrodescriptorImpl extends DescriptorImpl
+    implements Microdescriptor {
+
+  protected static List<Microdescriptor> parseDescriptors(
+      byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines)
+      throws DescriptorParseException {
+    List<Microdescriptor> parsedDescriptors =
+        new ArrayList<Microdescriptor>();
+    List<byte[]> splitDescriptorsBytes =
+        DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes,
+        "onion-key\n");
+    for (byte[] descriptorBytes : splitDescriptorsBytes) {
+      Microdescriptor parsedDescriptor =
+          new MicrodescriptorImpl(descriptorBytes,
+          failUnrecognizedDescriptorLines);
+      parsedDescriptors.add(parsedDescriptor);
+    }
+    return parsedDescriptors;
+  }
+
+  protected MicrodescriptorImpl(byte[] descriptorBytes,
+      boolean failUnrecognizedDescriptorLines)
+      throws DescriptorParseException {
+    super(descriptorBytes, failUnrecognizedDescriptorLines, false);
+    this.parseDescriptorBytes();
+    this.calculateDigest();
+    Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList(
+        "onion-key".split(",")));
+    this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+    Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
+        "ntor-onion-key,family,p,p6").split(",")));
+    this.checkAtMostOnceKeywords(atMostOnceKeywords);
+    this.checkFirstKeyword("onion-key");
+    return;
+  }
+
+  private void parseDescriptorBytes() throws DescriptorParseException {
+    Scanner s = new Scanner(new String(this.rawDescriptorBytes)).
+        useDelimiter("\n");
+    String nextCrypto = null;
+    StringBuilder crypto = null;
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("@")) {
+        continue;
+      }
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("onion-key")) {
+        this.parseOnionKeyLine(line, parts);
+        nextCrypto = "onion-key";
+      } else if (keyword.equals("ntor-onion-key")) {
+        this.parseNtorOnionKeyLine(line, parts);
+      } else if (keyword.equals("a")) {
+        this.parseALine(line, parts);
+      } else if (keyword.equals("family")) {
+        this.parseFamilyLine(line, parts);
+      } else if (keyword.equals("p")) {
+        this.parsePLine(line, parts);
+      } else if (keyword.equals("p6")) {
+        this.parseP6Line(line, parts);
+      } else if (line.startsWith("-----BEGIN")) {
+        crypto = new StringBuilder();
+        crypto.append(line + "\n");
+      } else if (line.startsWith("-----END")) {
+        crypto.append(line + "\n");
+        String cryptoString = crypto.toString();
+        crypto = null;
+        if (nextCrypto.equals("onion-key")) {
+          this.onionKey = cryptoString;
+        } else {
+          throw new DescriptorParseException("Unrecognized crypto "
+              + "block in microdescriptor.");
+        }
+        nextCrypto = null;
+      } else if (crypto != null) {
+        crypto.append(line + "\n");
+      } else {
+        ParseHelper.parseKeyword(line, parts[0]);
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in microdescriptor.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
+          }
+          this.unrecognizedLines.add(line);
+        }
+      }
+    }
+  }
+
+  private void parseOnionKeyLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (!line.equals("onion-key")) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+  }
+
+  private void parseNtorOnionKeyLine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length != 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    this.ntorOnionKey = parts[1].replaceAll("=", "");
+  }
+
+  private void parseALine(String line, String[] parts)
+      throws DescriptorParseException {
+    if (parts.length != 2) {
+      throw new DescriptorParseException("Wrong number of values in line "
+          + "'" + line + "'.");
+    }
+    /* TODO Add more checks. */
+    /* TODO Add tests. */
+    this.orAddresses.add(parts[1]);
+  }
+
+  private void parseFamilyLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.familyEntries = new ArrayList<String>();
+    for (int i = 1; i < parts.length; i++) {
+      if (parts[i].startsWith("$")) {
+        if (parts[i].contains("=") ^ parts[i].contains("~")) {
+          String separator = parts[i].contains("=") ? "=" : "~";
+          String fingerprint = ParseHelper.parseTwentyByteHexString(line,
+              parts[i].substring(1, parts[i].indexOf(separator)));
+          String nickname = ParseHelper.parseNickname(line,
+              parts[i].substring(parts[i].indexOf(
+              separator) + 1));
+          this.familyEntries.add("$" + fingerprint + separator
+              + nickname);
+        } else {
+          this.familyEntries.add("$"
+              + ParseHelper.parseTwentyByteHexString(line,
+              parts[i].substring(1)));
+        }
+      } else {
+        this.familyEntries.add(ParseHelper.parseNickname(line, parts[i]));
+      }
+    }
+  }
+
+  private void parsePLine(String line, String[] parts)
+      throws DescriptorParseException {
+    this.validatePOrP6Line(line, parts);
+    this.defaultPolicy = parts[1];
+    this.portList = parts[2];
+  }
+
+  private void parseP6Line(String line, String[] parts)
+      throws DescriptorParseException {
+    this.validatePOrP6Line(line, parts);
+    this.ipv6DefaultPolicy = parts[1];
+    this.ipv6PortList = parts[2];
+  }
+
+  private void validatePOrP6Line(String line, String[] parts)
+      throws DescriptorParseException {
+    boolean isValid = true;
+    if (parts.length != 3) {
+      isValid = false;
+    } else if (!parts[1].equals("accept") && !parts[1].equals("reject")) {
+      isValid = false;
+    } else {
+      String[] ports = parts[2].split(",", -1);
+      for (int i = 0; i < ports.length; i++) {
+        if (ports[i].length() < 1) {
+          isValid = false;
+          break;
+        }
+      }
+    }
+    if (!isValid) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+  }
+
+  private void calculateDigest() throws DescriptorParseException {
+    try {
+      String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
+      String startToken = "onion-key\n";
+      int start = ascii.indexOf(startToken);
+      int end = ascii.length();
+      if (start >= 0 && end > start) {
+        byte[] forDigest = new byte[end - start];
+        System.arraycopy(this.getRawDescriptorBytes(), start,
+            forDigest, 0, end - start);
+        this.microdescriptorDigest = DigestUtils.sha256Hex(forDigest);
+      }
+    } catch (UnsupportedEncodingException e) {
+      /* Handle below. */
+    }
+    if (this.microdescriptorDigest == null) {
+      throw new DescriptorParseException("Could not calculate "
+          + "microdescriptor digest.");
+    }
+  }
+
+  private String microdescriptorDigest;
+  public String getMicrodescriptorDigest() {
+    return this.microdescriptorDigest;
+  }
+
+  private String onionKey;
+  public String getOnionKey() {
+    return this.onionKey;
+  }
+
+  private String ntorOnionKey;
+  public String getNtorOnionKey() {
+    return this.ntorOnionKey;
+  }
+
+  private List<String> orAddresses = new ArrayList<String>();
+  public List<String> getOrAddresses() {
+    return new ArrayList<String>(this.orAddresses);
+  }
+
+  private List<String> familyEntries;
+  public List<String> getFamilyEntries() {
+    return this.familyEntries == null ? null :
+        new ArrayList<String>(this.familyEntries);
+  }
+  private String defaultPolicy;
+  public String getDefaultPolicy() {
+    return this.defaultPolicy;
+  }
+
+  private String portList;
+  public String getPortList() {
+    return this.portList;
+  }
+
+  private String ipv6DefaultPolicy;
+  public String getIpv6DefaultPolicy() {
+    return this.ipv6DefaultPolicy;
+  }
+
+  private String ipv6PortList;
+  public String getIpv6PortList() {
+    return this.ipv6PortList;
+  }
+}
+
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
index 337484e..19f951e 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
@@ -1,10 +1,12 @@
-/* Copyright 2011, 2012 The Tor Project
+/* Copyright 2011--2014 The Tor Project
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Scanner;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -18,6 +20,8 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
     return this.statusEntryBytes;
   }
 
+  private boolean microdescConsensus;
+
   private boolean failUnrecognizedDescriptorLines;
   private List<String> unrecognizedLines;
   protected List<String> getAndClearUnrecognizedLines() {
@@ -27,9 +31,10 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
   }
 
   protected NetworkStatusEntryImpl(byte[] statusEntryBytes,
-      boolean failUnrecognizedDescriptorLines)
+      boolean microdescConsensus, boolean failUnrecognizedDescriptorLines)
       throws DescriptorParseException {
     this.statusEntryBytes = statusEntryBytes;
+    this.microdescConsensus = microdescConsensus;
     this.failUnrecognizedDescriptorLines =
         failUnrecognizedDescriptorLines;
     this.initializeKeywords();
@@ -95,20 +100,28 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
 
   private void parseRLine(String line, String[] parts)
       throws DescriptorParseException {
-    if (parts.length < 9) {
+    if ((!this.microdescConsensus && parts.length != 9) ||
+        (this.microdescConsensus && parts.length != 8)) {
       throw new DescriptorParseException("r line '" + line + "' has "
           + "fewer space-separated elements than expected.");
     }
     this.nickname = ParseHelper.parseNickname(line, parts[1]);
     this.fingerprint = ParseHelper.parseTwentyByteBase64String(line,
         parts[2]);
-    this.descriptor = ParseHelper.parseTwentyByteBase64String(line,
-        parts[3]);
+    int descriptorOffset = 0;
+    if (!this.microdescConsensus) {
+      this.descriptor = ParseHelper.parseTwentyByteBase64String(line,
+          parts[3]);
+      descriptorOffset = 1;
+    }
     this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts,
-        4, 5);
-    this.address = ParseHelper.parseIpv4Address(line, parts[6]);
-    this.orPort = ParseHelper.parsePort(line, parts[7]);
-    this.dirPort = ParseHelper.parsePort(line, parts[8]);
+        3 + descriptorOffset, 4 + descriptorOffset);
+    this.address = ParseHelper.parseIpv4Address(line,
+        parts[5 + descriptorOffset]);
+    this.orPort = ParseHelper.parsePort(line,
+        parts[6 + descriptorOffset]);
+    this.dirPort = ParseHelper.parsePort(line,
+        parts[7 + descriptorOffset]);
   }
 
   private void parseALine(String line, String[] parts)
@@ -194,8 +207,18 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
 
   private void parseMLine(String line, String[] parts)
       throws DescriptorParseException {
-    /* TODO Implement parsing of m lines in votes as specified in
-     * dir-spec.txt. */
+    if (this.microdescriptorDigests == null) {
+      this.microdescriptorDigests = new HashSet<String>();
+    }
+    if (parts.length == 2) {
+      this.microdescriptorDigests.add(
+          ParseHelper.parseThirtyTwoByteBase64String(line, parts[1]));
+    } else if (parts.length == 3 && parts[2].length() > 7) {
+      /* 7 == "sha256=".length() */
+      this.microdescriptorDigests.add(
+          ParseHelper.parseThirtyTwoByteBase64String(line,
+          parts[2].substring(7)));
+    }
   }
 
   private String nickname;
@@ -233,6 +256,12 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
     return this.dirPort;
   }
 
+  private Set<String> microdescriptorDigests;
+  public Set<String> getMicrodescriptorDigests() {
+    return this.microdescriptorDigests == null ? null :
+        new HashSet<String>(this.microdescriptorDigests);
+  }
+
   private List<String> orAddresses = new ArrayList<String>();
   public List<String> getOrAddresses() {
     return new ArrayList<String>(this.orAddresses);
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index 94a76bf..6358ca3 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -176,7 +176,7 @@ public abstract class NetworkStatusImpl extends DescriptorImpl {
   protected void parseStatusEntry(byte[] statusEntryBytes)
       throws DescriptorParseException {
     NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl(
-        statusEntryBytes, this.failUnrecognizedDescriptorLines);
+        statusEntryBytes, false, this.failUnrecognizedDescriptorLines);
     this.statusEntries.put(statusEntry.getFingerprint(), statusEntry);
     List<String> unrecognizedStatusEntryLines = statusEntry.
         getAndClearUnrecognizedLines();
@@ -210,13 +210,13 @@ public abstract class NetworkStatusImpl extends DescriptorImpl {
     }
   }
 
-  private SortedMap<String, DirSourceEntry> dirSourceEntries =
+  protected 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 =
+  protected SortedMap<String, NetworkStatusEntry> statusEntries =
       new TreeMap<String, NetworkStatusEntry>();
   public SortedMap<String, NetworkStatusEntry> getStatusEntries() {
     return new TreeMap<String, NetworkStatusEntry>(this.statusEntries);
@@ -228,7 +228,7 @@ public abstract class NetworkStatusImpl extends DescriptorImpl {
     return this.statusEntries.get(fingerprint);
   }
 
-  private SortedMap<String, DirectorySignature> directorySignatures;
+  protected SortedMap<String, DirectorySignature> directorySignatures;
   public SortedMap<String, DirectorySignature> getDirectorySignatures() {
     return this.directorySignatures == null ? null :
         new TreeMap<String, DirectorySignature>(this.directorySignatures);
diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java
index ebb8bc6..fd38eb0 100644
--- a/src/org/torproject/descriptor/impl/ParseHelper.java
+++ b/src/org/torproject/descriptor/impl/ParseHelper.java
@@ -239,11 +239,11 @@ public class ParseHelper {
     return nickname;
   }
 
-  private static Pattern base64Pattern =
+  private static Pattern twentyByteBase64Pattern =
       Pattern.compile("^[0-9a-zA-Z+/]{27}$");
   public static String parseTwentyByteBase64String(String line,
       String base64String) throws DescriptorParseException {
-    if (!base64Pattern.matcher(base64String).matches()) {
+    if (!twentyByteBase64Pattern.matcher(base64String).matches()) {
       throw new DescriptorParseException("'" + base64String
           + "' in line '" + line + "' is not a valid base64-encoded "
           + "20-byte value.");
@@ -252,6 +252,19 @@ public class ParseHelper {
         toUpperCase();
   }
 
+  private static Pattern thirtyTwoByteBase64Pattern =
+      Pattern.compile("^[0-9a-zA-Z+/]{43}$");
+  public static String parseThirtyTwoByteBase64String(String line,
+      String base64String) throws DescriptorParseException {
+    if (!thirtyTwoByteBase64Pattern.matcher(base64String).matches()) {
+      throw new DescriptorParseException("'" + base64String
+          + "' in line '" + line + "' is not a valid base64-encoded "
+          + "32-byte value.");
+    }
+    return Hex.encodeHexString(Base64.decodeBase64(base64String + "=")).
+        toUpperCase();
+  }
+
   public static SortedMap<String, Integer>
       parseCommaSeparatedKeyIntegerValueList(String line,
       String[] partsNoOpt, int index, int keyLength)
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
index dad5bc3..d6babee 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
@@ -1,4 +1,4 @@
-/* Copyright 2011, 2012 The Tor Project
+/* Copyright 2011--2014 The Tor Project
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
@@ -17,7 +17,7 @@ import java.util.TreeSet;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.torproject.descriptor.RelayNetworkStatusConsensus;
 
-/* Contains a network status consensus. */
+/* Contains a network status consensus or microdesc consensus. */
 public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
     implements RelayNetworkStatusConsensus {
 
@@ -120,6 +120,23 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
     }
   }
 
+  private boolean microdescConsensus = false;
+  protected void parseStatusEntry(byte[] statusEntryBytes)
+      throws DescriptorParseException {
+    NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl(
+        statusEntryBytes, this.microdescConsensus,
+        this.failUnrecognizedDescriptorLines);
+    this.statusEntries.put(statusEntry.getFingerprint(), statusEntry);
+    List<String> unrecognizedStatusEntryLines = statusEntry.
+        getAndClearUnrecognizedLines();
+    if (unrecognizedStatusEntryLines != null) {
+      if (this.unrecognizedLines == null) {
+        this.unrecognizedLines = new ArrayList<String>();
+      }
+      this.unrecognizedLines.addAll(unrecognizedStatusEntryLines);
+    }
+  }
+
   protected void parseFooter(byte[] footerBytes)
       throws DescriptorParseException {
     Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n");
@@ -144,11 +161,20 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
 
   private void parseNetworkStatusVersionLine(String line, String[] parts)
       throws DescriptorParseException {
-    if (!line.equals("network-status-version 3")) {
+    if (!line.startsWith("network-status-version 3")) {
       throw new DescriptorParseException("Illegal network status version "
           + "number in line '" + line + "'.");
     }
     this.networkStatusVersion = 3;
+    if (parts.length == 3) {
+      this.consensusFlavor = parts[2];
+      if (this.consensusFlavor.equals("microdesc")) {
+        this.microdescConsensus = true;
+      }
+    } else if (parts.length != 2) {
+      throw new DescriptorParseException("Illegal network status version "
+          + "line '" + line + "'.");
+    }
   }
 
   private void parseVoteStatusLine(String line, String[] parts)
@@ -277,6 +303,11 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
     return this.networkStatusVersion;
   }
 
+  private String consensusFlavor;
+  public String getConsensusFlavor() {
+    return this.consensusFlavor;
+  }
+
   private int consensusMethod;
   public int getConsensusMethod() {
     return this.consensusMethod;
diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
index baf4456..d1fcd28 100644
--- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
@@ -508,7 +508,7 @@ public class ServerDescriptorImpl extends DescriptorImpl
     if (partsNoOpt.length != 2) {
       throw new DescriptorParseException("Illegal line '" + line + "'.");
     }
-    this.ntorOnionKey = partsNoOpt[1].replaceAll("=",  "");
+    this.ntorOnionKey = partsNoOpt[1].replaceAll("=", "");
   }
 
   private void calculateDigest() throws DescriptorParseException {



More information about the tor-commits mailing list