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