commit aecdb0f72eefa2479002e47cce3d1dce9e55868d
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Mon Jan 2 14:37:30 2012 +0100
Implement parsing relay server descriptors.
---
.../torproject/descriptor/BandwidthHistory.java | 26 +
.../descriptor/RelayServerDescriptor.java | 103 ++
.../descriptor/impl/BandwidthHistoryImpl.java | 73 ++
.../torproject/descriptor/impl/DescriptorImpl.java | 164 +++
.../descriptor/impl/NetworkStatusImpl.java | 129 +---
.../torproject/descriptor/impl/ParseHelper.java | 45 +
.../impl/RelayNetworkStatusConsensusImpl.java | 2 +-
.../impl/RelayNetworkStatusVoteImpl.java | 2 +-
.../descriptor/impl/RelayServerDescriptorImpl.java | 529 ++++++++++
.../impl/RelayNetworkStatusConsensusImplTest.java | 2 +
.../impl/RelayNetworkStatusVoteImplTest.java | 2 +
.../impl/RelayServerDescriptorImplTest.java | 1080 ++++++++++++++++++++
12 files changed, 2028 insertions(+), 129 deletions(-)
diff --git a/src/org/torproject/descriptor/BandwidthHistory.java b/src/org/torproject/descriptor/BandwidthHistory.java
new file mode 100644
index 0000000..1794ab4
--- /dev/null
+++ b/src/org/torproject/descriptor/BandwidthHistory.java
@@ -0,0 +1,26 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor;
+
+import java.util.SortedMap;
+
+/* Contains the bandwidth history of a relay or bridge. */
+public interface BandwidthHistory {
+
+ /* Return the original bandwidth history line as contained in the
+ * descriptor, possibly prefixed with "opt ". */
+ public String getLine();
+
+ /* Return the end of the most recent interval in millis. */
+ public long getHistoryEndMillis();
+
+ /* Return the interval length in seconds, which is typically 900 seconds
+ * or 15 minutes. */
+ public long getIntervalLength();
+
+ /* Return the (possibly empty) bandwidth history with map keys being
+ * interval ends in millis and map values being number of bytes used in
+ * the interval, ordered from oldest to newest interval. */
+ public SortedMap<Long, Long> getBandwidthValues();
+}
+
diff --git a/src/org/torproject/descriptor/RelayServerDescriptor.java b/src/org/torproject/descriptor/RelayServerDescriptor.java
index 1542cba..fcd4382 100644
--- a/src/org/torproject/descriptor/RelayServerDescriptor.java
+++ b/src/org/torproject/descriptor/RelayServerDescriptor.java
@@ -2,6 +2,109 @@
* See LICENSE for licensing information */
package org.torproject.descriptor;
+import java.util.List;
+
+/* Contains a relay server descriptor. */
public interface RelayServerDescriptor extends Descriptor {
+
+ /* Return the relay's nickname. */
+ public String getNickname();
+
+ /* Return the relay's IPv4 address in dotted-quad format. */
+ public String getAddress();
+
+ /* Return the relay's OR port. */
+ public int getOrPort();
+
+ /* Return the relay's SOCKS port which should always be 0. */
+ public int getSocksPort();
+
+ /* Return the relay's directory port. */
+ public int getDirPort();
+
+ /* Return the average bandwidth in bytes per second that the relay is
+ * willing to sustain over long periods. */
+ public int getBandwidthRate();
+
+ /* Return the burst bandwidth in bytes per second that the relay is
+ * willing to sustain in very short intervals. */
+ public int getBandwidthBurst();
+
+ /* Return the observed bandwidth in bytes per second as an estimate of
+ * the capacity that the relay can handle. */
+ public int getBandwidthObserved();
+
+ /* Return the platform string containing the Tor software version and
+ * the operating system. */
+ public String getPlatform();
+
+ /* Return the time when this descriptor and the corresponding extra-info
+ * document was generated. */
+ public long getPublishedMillis();
+
+ /* Return the relay fingerprint, or null if this descriptor does not
+ * contain a fingerprint line. */
+ public String getFingerprint();
+
+ /* Return whether the relay was hibernating when this descriptor was
+ * published. */
+ public boolean isHibernating();
+
+ /* Return the number of seconds that this relay has been running, or -1
+ * if the descriptor does not contain an uptime line. */
+ public int getUptime();
+
+ /* Return the relay's exit policy consisting of one or more accept or
+ * reject lines. */
+ public List<String> getExitPolicyLines();
+
+ /* Return the contact information for this relay, or null if no contact
+ * information is included in the descriptor. */
+ public String getContact();
+
+ /* Return the nicknames or ($-prefixed) fingerprints 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 relay's read history. (Current Tor versions include their
+ * bandwidth histories in their extra-info descriptors, not in their
+ * server descriptors.) */
+ public BandwidthHistory getReadHistory();
+
+ /* Return the relay's write history. (Current Tor versions include
+ * their bandwidth histories in their extra-info descriptors, not in
+ * their server descriptors.) */
+ public BandwidthHistory getWriteHistory();
+
+ /* Return true if the relay uses the enhanced DNS logic, or false if
+ * doesn't use it or doesn't include an eventdns line in its
+ * descriptor. */
+ public boolean getUsesEnhancedDnsLogic();
+
+ /* Return whether this relay is a directory cache that provides
+ * extra-info descriptors. */
+ public boolean getCachesExtraInfo();
+
+ /* Return the digest of the relay's extra-info descriptor, or null if
+ * the relay did not upload a corresponding extra-info descriptor. */
+ public String getExtraInfoDigest();
+
+ /* Return the hidden service descriptor version(s) that this relay
+ * stores and serves, or null if it doesn't store and serve any hidden
+ * service descriptors. */
+ public List<Integer> getHiddenServiceDirVersions();
+
+ /* Return the list of link protocol versions that this relay
+ * supports. */
+ public List<Integer> getLinkProtocolVersions();
+
+ /* Return the list of circuit protocol versions that this relay
+ * supports. */
+ public List<Integer> getCircuitProtocolVersions();
+
+ /* Return whether this relay allows single-hop circuits to make exit
+ * connections. */
+ public boolean getAllowSingleHopExits();
}
diff --git a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java
new file mode 100644
index 0000000..f16cc81
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java
@@ -0,0 +1,73 @@
+/* Copyright 2012 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.SortedMap;
+import java.util.TreeMap;
+import org.torproject.descriptor.BandwidthHistory;
+
+public class BandwidthHistoryImpl implements BandwidthHistory {
+
+ protected BandwidthHistoryImpl(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ boolean isValid = false;
+ if (partsNoOpt.length >= 5) {
+ try {
+ this.line = line;
+ this.historyEndMillis = ParseHelper.parseTimestampAtIndex(line,
+ partsNoOpt, 1, 2);
+ if (partsNoOpt[3].startsWith("(") &&
+ partsNoOpt[4].equals("s)")) {
+ this.intervalLength = Long.parseLong(partsNoOpt[3].
+ substring(1));
+ if (partsNoOpt.length > 6) {
+ /* Invalid line, handle below. */
+ } else if (partsNoOpt.length == 5) {
+ /* No bandwidth values to parse. */
+ isValid = true;
+ } else {
+ 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]);
+ this.bandwidthValues.put(endMillis, bandwidthValue);
+ endMillis -= this.intervalLength * 1000L;
+ }
+ isValid = true;
+ }
+ }
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ }
+ if (!isValid) {
+ throw new DescriptorParseException("Invalid bandwidth-history line "
+ + "'" + line + "'.");
+ }
+ }
+
+ private String line;
+ public String getLine() {
+ return this.line;
+ }
+
+ private long historyEndMillis;
+ public long getHistoryEndMillis() {
+ return this.historyEndMillis;
+ }
+
+ private long intervalLength;
+ public long getIntervalLength() {
+ return this.intervalLength;
+ }
+
+ private SortedMap<Long, Long> bandwidthValues =
+ new TreeMap<Long, Long>();
+ public SortedMap<Long, Long> getBandwidthValues() {
+ return new TreeMap<Long, Long>(this.bandwidthValues);
+ }
+}
+
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java
new file mode 100644
index 0000000..d337630
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java
@@ -0,0 +1,164 @@
+/* Copyright 2012 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 org.torproject.descriptor.Descriptor;
+
+public abstract class DescriptorImpl implements Descriptor {
+
+ 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;
+ }
+
+ protected byte[] rawDescriptorBytes;
+ public byte[] getRawDescriptorBytes() {
+ return this.rawDescriptorBytes;
+ }
+
+ protected DescriptorImpl(byte[] rawDescriptorBytes)
+ throws DescriptorParseException {
+ this.rawDescriptorBytes = rawDescriptorBytes;
+ this.countKeywords(rawDescriptorBytes);
+ }
+
+ /* Count parsed keywords for consistency checks by subclasses. */
+ private String firstKeyword, lastKeyword;
+ private Map<String, Integer> parsedKeywords =
+ new HashMap<String, Integer>();
+ private void countKeywords(byte[] rawDescriptorBytes)
+ throws DescriptorParseException {
+ if (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.");
+ }
+ try {
+ BufferedReader br = new BufferedReader(new StringReader(
+ descriptorString));
+ String line;
+ boolean skipCrypto = false;
+ while ((line = br.readLine()) != null) {
+ if (line.startsWith("-----BEGIN")) {
+ skipCrypto = true;
+ } else if (line.startsWith("-----END")) {
+ skipCrypto = false;
+ } else if (!skipCrypto) {
+ String lineNoOpt = line.startsWith("opt ") ?
+ line.substring("opt ".length()) : line;
+ String keyword = lineNoOpt.split(" ", -1)[0];
+ if (keyword.equals("")) {
+ throw new DescriptorParseException("Illegal keyword in line '"
+ + line + "'.");
+ }
+ if (this.firstKeyword == null) {
+ this.firstKeyword = keyword;
+ }
+ lastKeyword = keyword;
+ 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 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.");
+ }
+ }
+
+ protected void checkLastKeyword(String keyword)
+ throws DescriptorParseException {
+ if (this.lastKeyword == null ||
+ !this.lastKeyword.equals(keyword)) {
+ throw new DescriptorParseException("Keyword '" + keyword + "' must "
+ + "be contained in the last line.");
+ }
+ }
+
+ protected void checkExactlyOnceKeywords(Set<String> keywords)
+ throws DescriptorParseException {
+ for (String keyword : keywords) {
+ int contained = 0;
+ if (this.parsedKeywords.containsKey(keyword)) {
+ contained = this.parsedKeywords.get(keyword);
+ }
+ if (contained != 1) {
+ throw new DescriptorParseException("Keyword '" + keyword + "' is "
+ + "contained " + contained + " 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.");
+ }
+ }
+ }
+
+ protected int getKeywordCount(String keyword) {
+ if (!this.parsedKeywords.containsKey(keyword)) {
+ return 0;
+ } else {
+ return this.parsedKeywords.get(keyword);
+ }
+ }
+}
+
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index f478913..2878ad9 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -18,40 +18,11 @@ 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;
- }
+public abstract class NetworkStatusImpl extends DescriptorImpl {
protected NetworkStatusImpl(byte[] rawDescriptorBytes)
throws DescriptorParseException {
- this.rawDescriptorBytes = rawDescriptorBytes;
- this.countKeywords(rawDescriptorBytes);
+ super(rawDescriptorBytes);
this.splitAndParseParts(rawDescriptorBytes);
}
@@ -126,7 +97,6 @@ public abstract class NetworkStatusImpl {
byte[] headerBytes = new byte[end - start];
System.arraycopy(this.rawDescriptorBytes, start,
headerBytes, 0, end - start);
- this.rememberFirstKeyword(headerBytes);
this.parseHeader(headerBytes);
}
@@ -240,101 +210,6 @@ public abstract class NetworkStatusImpl {
}
}
- 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[] rawDescriptorBytes)
- throws DescriptorParseException {
- try {
- BufferedReader br = new BufferedReader(new StringReader(
- new String(rawDescriptorBytes)));
- String line;
- boolean skipCrypto = false;
- while ((line = br.readLine()) != null) {
- if (line.startsWith("-----BEGIN")) {
- skipCrypto = true;
- } else if (line.startsWith("-----END")) {
- 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) {
- int contained = 0;
- if (this.parsedKeywords.containsKey(keyword)) {
- contained = this.parsedKeywords.get(keyword);
- }
- if (contained != 1) {
- throw new DescriptorParseException("Keyword '" + keyword + "' is "
- + "contained " + contained + " 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() {
diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java
index 5f4a1bd..296955e 100644
--- a/src/org/torproject/descriptor/impl/ParseHelper.java
+++ b/src/org/torproject/descriptor/impl/ParseHelper.java
@@ -61,6 +61,51 @@ public class ParseHelper {
return port;
}
+ public static String parseExitPattern(String line, String exitPattern)
+ throws DescriptorParseException {
+ if (!exitPattern.contains(":")) {
+ throw new DescriptorParseException("'" + exitPattern + "' in line '"
+ + line + "' must contain address and port.");
+ }
+ String[] parts = exitPattern.split(":");
+ String addressPart = parts[0];
+ /* TODO Extend to IPv6. */
+ if (addressPart.equals("*")) {
+ /* Nothing to check. */
+ } else if (addressPart.contains("/")) {
+ String[] addressParts = addressPart.split("/");
+ String address = addressParts[0];
+ ParseHelper.parseIpv4Address(line, address);
+ String mask = addressParts[1];
+ int maskValue = -1;
+ try {
+ maskValue = Integer.parseInt(addressPart.substring(
+ addressPart.indexOf("/") + 1));
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ if (addressParts.length != 2 || maskValue < 0 || maskValue > 32) {
+ throw new DescriptorParseException("'" + addressPart + "' in "
+ + "line '" + line + "' is not a valid address part.");
+ }
+ } else {
+ ParseHelper.parseIpv4Address(line, addressPart);
+ }
+ String portPart = parts[1];
+ if (portPart.equals("*")) {
+ /* Nothing to check. */
+ } else if (portPart.contains("-")) {
+ String[] portParts = portPart.split("-");
+ String fromPort = portParts[0];
+ ParseHelper.parsePort(line, fromPort);
+ String toPort = portParts[1];
+ ParseHelper.parsePort(line, toPort);
+ } else {
+ ParseHelper.parsePort(line, portPart);
+ }
+ return exitPattern;
+ }
+
private static SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
static {
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
index c4dc88b..a5ab87c 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
@@ -29,7 +29,7 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
List<RelayNetworkStatusConsensus> parsedConsensuses =
new ArrayList<RelayNetworkStatusConsensus>();
List<byte[]> splitConsensusBytes =
- NetworkStatusImpl.splitRawDescriptorBytes(consensusesBytes,
+ DescriptorImpl.splitRawDescriptorBytes(consensusesBytes,
"network-status-version 3");
try {
for (byte[] consensusBytes : splitConsensusBytes) {
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index ab4da77..438b1df 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -25,7 +25,7 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
List<RelayNetworkStatusVote> parsedVotes =
new ArrayList<RelayNetworkStatusVote>();
List<byte[]> splitVotesBytes =
- NetworkStatusImpl.splitRawDescriptorBytes(votesBytes,
+ DescriptorImpl.splitRawDescriptorBytes(votesBytes,
"network-status-version 3");
try {
for (byte[] voteBytes : splitVotesBytes) {
diff --git a/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java
new file mode 100644
index 0000000..a5374b6
--- /dev/null
+++ b/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java
@@ -0,0 +1,529 @@
+/* Copyright 2012 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.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.torproject.descriptor.RelayServerDescriptor;
+import org.torproject.descriptor.BandwidthHistory;
+
+/* Contains a relay server descriptor. */
+public class RelayServerDescriptorImpl extends DescriptorImpl
+ implements RelayServerDescriptor {
+
+ protected static List<RelayServerDescriptor> parseDescriptors(
+ byte[] descriptorsBytes) {
+ List<RelayServerDescriptor> parsedDescriptors =
+ new ArrayList<RelayServerDescriptor>();
+ List<byte[]> splitDescriptorsBytes =
+ DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes,
+ "router ");
+ try {
+ for (byte[] descriptorBytes : splitDescriptorsBytes) {
+ RelayServerDescriptor parsedDescriptor =
+ new RelayServerDescriptorImpl(descriptorBytes);
+ parsedDescriptors.add(parsedDescriptor);
+ }
+ } catch (DescriptorParseException e) {
+ /* TODO Handle this error somehow. */
+ System.err.println("Failed to parse descriptor. Skipping.");
+ e.printStackTrace();
+ }
+ return parsedDescriptors;
+ }
+
+ protected RelayServerDescriptorImpl(byte[] descriptorBytes)
+ throws DescriptorParseException {
+ super(descriptorBytes);
+ this.parseDescriptorBytes();
+ Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
+ "router,bandwidth,published,onion-key,signing-key,"
+ + "router-signature").split(",")));
+ this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+ Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
+ "platform,fingerprint,hibernating,uptime,contact,family,"
+ + "read-history,write-history,eventdns,caches-extra-info,"
+ + "extra-info-digest,hidden-service-dir,protocols,"
+ + "allow-single-hop-exits").split(",")));
+ this.checkAtMostOnceKeywords(atMostOnceKeywords);
+ this.checkFirstKeyword("router");
+ this.checkLastKeyword("router-signature");
+ if (this.getKeywordCount("accept") == 0 &&
+ this.getKeywordCount("reject") == 0) {
+ throw new DescriptorParseException("Either keyword 'accept' or "
+ + "'reject' must be contained at least once.");
+ }
+ return;
+ }
+
+ private void parseDescriptorBytes() throws DescriptorParseException {
+ try {
+ BufferedReader br = new BufferedReader(new StringReader(
+ new String(this.rawDescriptorBytes)));
+ String line;
+ boolean skipCrypto = false;
+ while ((line = br.readLine()) != null) {
+ String lineNoOpt = line.startsWith("opt ") ?
+ line.substring("opt ".length()) : line;
+ String[] partsNoOpt = lineNoOpt.split(" ");
+ String keyword = partsNoOpt[0];
+ if (keyword.equals("router")) {
+ this.parseRouterLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("bandwidth")) {
+ this.parseBandwidthLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("platform")) {
+ this.parsePlatformLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("published")) {
+ this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("fingerprint")) {
+ this.parseFingerprintLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("hibernating")) {
+ this.parseHibernatingLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("uptime")) {
+ this.parseUptimeLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("onion-key")) {
+ this.parseOnionKeyLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("signing-key")) {
+ this.parseSigningKeyLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("accept")) {
+ this.parseAcceptLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("reject")) {
+ this.parseRejectLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("router-signature")) {
+ this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("contact")) {
+ this.parseContactLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("family")) {
+ this.parseFamilyLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("read-history")) {
+ this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("write-history")) {
+ this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("eventdns")) {
+ this.parseEventdnsLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("caches-extra-info")) {
+ this.parseCachesExtraInfoLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("extra-info-digest")) {
+ this.parseExtraInfoDigestLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("hidden-service-dir")) {
+ this.parseHiddenServiceDirLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("protocols")) {
+ this.parseProtocolsLine(line, lineNoOpt, partsNoOpt);
+ } else if (keyword.equals("allow-single-hop-exits")) {
+ this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt);
+ } else if (line.startsWith("-----BEGIN")) {
+ skipCrypto = true;
+ } else if (line.startsWith("-----END")) {
+ skipCrypto = false;
+ } else if (!skipCrypto) {
+ /* TODO Is throwing an exception the right thing to do here?
+ * This is probably fine for development, but once the library
+ * is in production use, this seems annoying. In theory,
+ * dir-spec.txt says that unknown lines should be ignored. This
+ * also applies to the other descriptors. */
+ throw new DescriptorParseException("Unrecognized line '" + line
+ + "'.");
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Internal error: Ran into an "
+ + "IOException while parsing a String in memory. Something's "
+ + "really wrong.", e);
+ }
+ }
+
+ private void parseRouterLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 6) {
+ throw new DescriptorParseException("Illegal line '" + line
+ + "' in server descriptor.");
+ }
+ this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]);
+ this.address = ParseHelper.parseIpv4Address(line, partsNoOpt[2]);
+ this.orPort = ParseHelper.parsePort(line, partsNoOpt[3]);
+ this.socksPort = ParseHelper.parsePort(line, partsNoOpt[4]);
+ this.dirPort = ParseHelper.parsePort(line, partsNoOpt[5]);
+ }
+
+ private void parseBandwidthLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 4) {
+ throw new DescriptorParseException("Wrong number of values in line "
+ + "'" + line + "'.");
+ }
+ boolean isValid = false;
+ try {
+ this.bandwidthRate = Integer.parseInt(partsNoOpt[1]);
+ this.bandwidthBurst = Integer.parseInt(partsNoOpt[2]);
+ this.bandwidthObserved = Integer.parseInt(partsNoOpt[3]);
+ if (this.bandwidthRate >= 0 && this.bandwidthBurst >= 0 &&
+ this.bandwidthObserved >= 0) {
+ isValid = true;
+ }
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ if (!isValid) {
+ throw new DescriptorParseException("Illegal values in line '" + line
+ + "'.");
+ }
+ }
+
+ private void parsePlatformLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (lineNoOpt.length() > "platform ".length()) {
+ this.platform = lineNoOpt.substring("platform ".length());
+ } else {
+ this.platform = "";
+ }
+ }
+
+ private void parsePublishedLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.publishedMillis = ParseHelper.parseTimestampAtIndex(line,
+ partsNoOpt, 1, 2);
+ }
+
+ private void parseFingerprintLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (lineNoOpt.length() != "fingerprint".length() + 5 * 10) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.fingerprint = ParseHelper.parseTwentyByteHexString(line,
+ lineNoOpt.substring("fingerprint ".length()).replaceAll(" ", ""));
+ }
+
+ private void parseHibernatingLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ if (partsNoOpt[1].equals("true")) {
+ this.hibernating = true;
+ } else if (partsNoOpt[1].equals("false")) {
+ this.hibernating = false;
+ } else {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
+ private void parseUptimeLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Wrong number of values in line "
+ + "'" + line + "'.");
+ }
+ boolean isValid = false;
+ try {
+ this.uptime = Integer.parseInt(partsNoOpt[1]);
+ if (this.uptime >= 0) {
+ isValid = true;
+ }
+ } catch (NumberFormatException e) {
+ /* Handle below. */
+ }
+ if (!isValid) {
+ throw new DescriptorParseException("Illegal value in line '" + line
+ + "'.");
+ }
+ }
+
+ private void parseOnionKeyLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ /* Not parsing crypto parts (yet). */
+ }
+
+ private void parseSigningKeyLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ /* Not parsing crypto parts (yet). */
+ }
+
+ private void parseAcceptLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt);
+ }
+
+ private void parseRejectLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt);
+ }
+
+ private void parseExitPolicyLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ ParseHelper.parseExitPattern(line, partsNoOpt[1]);
+ this.exitPolicyLines.add(lineNoOpt);
+ }
+
+ private void parseRouterSignatureLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (!lineNoOpt.equals("router-signature")) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ /* Not parsing crypto parts (yet). */
+ }
+
+ private void parseContactLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (lineNoOpt.length() > "contact ".length()) {
+ this.contact = lineNoOpt.substring("contact ".length());
+ } else {
+ this.contact = "";
+ }
+ }
+
+ private void parseFamilyLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.familyEntries = new ArrayList<String>();
+ for (int i = 1; i < partsNoOpt.length; i++) {
+ if (partsNoOpt[i].startsWith("$")) {
+ this.familyEntries.add("$"
+ + ParseHelper.parseTwentyByteHexString(line,
+ partsNoOpt[i].substring(1)));
+ } else {
+ this.familyEntries.add(ParseHelper.parseNickname(line,
+ partsNoOpt[i]));
+ }
+ }
+ }
+
+ private void parseReadHistoryLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.readHistory = new BandwidthHistoryImpl(line, lineNoOpt,
+ partsNoOpt);
+ }
+
+ private void parseWriteHistoryLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.writeHistory = new BandwidthHistoryImpl(line, lineNoOpt,
+ partsNoOpt);
+ }
+
+ private void parseEventdnsLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ if (partsNoOpt[1].equals("true")) {
+ this.usesEnhancedDnsLogic = true;
+ } else if (partsNoOpt[1].equals("false")) {
+ this.usesEnhancedDnsLogic = false;
+ } else {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
+ private void parseCachesExtraInfoLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (!lineNoOpt.equals("caches-extra-info")) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.cachesExtraInfo = true;
+ }
+
+ private void parseExtraInfoDigestLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (partsNoOpt.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line,
+ partsNoOpt[1]);
+ }
+
+ private void parseHiddenServiceDirLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ this.hiddenServiceDirVersions = new ArrayList<Integer>();
+ if (partsNoOpt.length == 1) {
+ this.hiddenServiceDirVersions.add(2);
+ } else {
+ try {
+ for (int i = 1; i < partsNoOpt.length; i++) {
+ this.hiddenServiceDirVersions.add(Integer.parseInt(
+ partsNoOpt[i]));
+ }
+ } catch (NumberFormatException e) {
+ throw new DescriptorParseException("Illegal value in line '"
+ + line + "'.");
+ }
+ }
+ }
+
+ private void parseProtocolsLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ List<String> partsList = Arrays.asList(partsNoOpt);
+ boolean isValid = true;
+ this.linkProtocolVersions = new ArrayList<Integer>();
+ this.circuitProtocolVersions = new ArrayList<Integer>();
+ List<Integer> protocolVersions = null;
+ for (int i = 1; i < partsNoOpt.length; i++) {
+ String part = partsNoOpt[i];
+ if (part.equals("Link")) {
+ protocolVersions = this.linkProtocolVersions;
+ } else if (part.equals("Circuit")) {
+ protocolVersions = this.circuitProtocolVersions;
+ } else if (protocolVersions == null) {
+ isValid = false;
+ break;
+ } else {
+ try {
+ protocolVersions.add(Integer.parseInt(part));
+ } catch (NumberFormatException e) {
+ isValid = false;
+ break;
+ }
+ }
+ }
+ if (protocolVersions != this.circuitProtocolVersions) {
+ isValid = false;
+ }
+ if (!isValid) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ }
+
+ private void parseAllowSingleHopExitsLine(String line, String lineNoOpt,
+ String[] partsNoOpt) throws DescriptorParseException {
+ if (!lineNoOpt.equals("allow-single-hop-exits")) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.allowSingleHopExits = true;
+ }
+
+ private String nickname;
+ public String getNickname() {
+ return this.nickname;
+ }
+
+ private String address;
+ public String getAddress() {
+ return this.address;
+ }
+
+ private int orPort;
+ public int getOrPort() {
+ return this.orPort;
+ }
+
+ private int socksPort;
+ public int getSocksPort() {
+ return this.socksPort;
+ }
+
+ private int dirPort;
+ public int getDirPort() {
+ return this.dirPort;
+ }
+
+ private int bandwidthRate;
+ public int getBandwidthRate() {
+ return this.bandwidthRate;
+ }
+
+ private int bandwidthBurst;
+ public int getBandwidthBurst() {
+ return this.bandwidthBurst;
+ }
+
+ private int bandwidthObserved;
+ public int getBandwidthObserved() {
+ return this.bandwidthObserved;
+ }
+
+ private String platform;
+ public String getPlatform() {
+ return this.platform;
+ }
+
+ private long publishedMillis;
+ public long getPublishedMillis() {
+ return this.publishedMillis;
+ }
+
+ private String fingerprint;
+ public String getFingerprint() {
+ return this.fingerprint;
+ }
+
+ private boolean hibernating;
+ public boolean isHibernating() {
+ return this.hibernating;
+ }
+
+ private int uptime = -1;
+ public int getUptime() {
+ return this.uptime;
+ }
+
+ private List<String> exitPolicyLines = new ArrayList<String>();
+ public List<String> getExitPolicyLines() {
+ return new ArrayList<String>(this.exitPolicyLines);
+ }
+
+ private String contact;
+ public String getContact() {
+ return this.contact;
+ }
+
+ private List<String> familyEntries;
+ public List<String> getFamilyEntries() {
+ return this.familyEntries == null ? null :
+ new ArrayList<String>(this.familyEntries);
+ }
+
+ private BandwidthHistory readHistory;
+ public BandwidthHistory getReadHistory() {
+ return this.readHistory;
+ }
+
+ private BandwidthHistory writeHistory;
+ public BandwidthHistory getWriteHistory() {
+ return this.writeHistory;
+ }
+
+ private boolean usesEnhancedDnsLogic;
+ public boolean getUsesEnhancedDnsLogic() {
+ return this.usesEnhancedDnsLogic;
+ }
+
+ private boolean cachesExtraInfo;
+ public boolean getCachesExtraInfo() {
+ return this.cachesExtraInfo;
+ }
+
+ private String extraInfoDigest;
+ public String getExtraInfoDigest() {
+ return this.extraInfoDigest;
+ }
+
+ private List<Integer> hiddenServiceDirVersions;
+ public List<Integer> getHiddenServiceDirVersions() {
+ return this.hiddenServiceDirVersions == null ? null :
+ new ArrayList<Integer>(this.hiddenServiceDirVersions);
+ }
+
+ private List<Integer> linkProtocolVersions;
+ public List<Integer> getLinkProtocolVersions() {
+ return this.linkProtocolVersions == null ? null :
+ new ArrayList<Integer>(this.linkProtocolVersions);
+ }
+
+ private List<Integer> circuitProtocolVersions;
+ public List<Integer> getCircuitProtocolVersions() {
+ return this.circuitProtocolVersions == null ? null :
+ new ArrayList<Integer>(this.circuitProtocolVersions);
+ }
+
+ private boolean allowSingleHopExits;
+ public boolean getAllowSingleHopExits() {
+ return this.allowSingleHopExits;
+ }
+}
+
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
index deadf80..d5bfd3a 100644
--- a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java
@@ -11,6 +11,8 @@ import org.junit.*;
import org.junit.rules.*;
import static org.junit.Assert.*;
+/* TODO Add test cases for all lines starting with "opt ". */
+
/* Test parsing of network status consensuses. The main focus is on
* making sure that the parser is as robust as possible and doesn't break,
* no matter what gets fed into it. A secondary focus is to ensure that
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
index cdf2a01..7618fff 100644
--- a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
@@ -11,6 +11,8 @@ import org.junit.*;
import org.junit.rules.*;
import static org.junit.Assert.*;
+/* TODO Add test cases for all lines starting with "opt ". */
+
/* Test parsing of network status votes. Some of the vote-parsing code is
* already tested in the consensus-parsing tests. The tests in this class
* focus on the differences between votes and consensuses that are mostly
diff --git a/test/org/torproject/descriptor/impl/RelayServerDescriptorImplTest.java b/test/org/torproject/descriptor/impl/RelayServerDescriptorImplTest.java
new file mode 100644
index 0000000..acc2dbe
--- /dev/null
+++ b/test/org/torproject/descriptor/impl/RelayServerDescriptorImplTest.java
@@ -0,0 +1,1080 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import org.torproject.descriptor.BandwidthHistory;
+import org.torproject.descriptor.RelayServerDescriptor;
+
+import java.util.*;
+
+import org.junit.*;
+import org.junit.rules.*;
+import static org.junit.Assert.*;
+
+/* Test parsing of relay server descriptors. */
+public class RelayServerDescriptorImplTest {
+
+ /* Helper class to build a descriptor based on default data and
+ * modifications requested by test methods. */
+ private static class DescriptorBuilder {
+ private String routerLine = "router saberrider2008 94.134.192.243 "
+ + "9001 0 0";
+ private static RelayServerDescriptor createWithRouterLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.routerLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String bandwidthLine = "bandwidth 51200 51200 53470";
+ private static RelayServerDescriptor createWithBandwidthLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.bandwidthLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String platformLine = "platform Tor 0.2.2.35 "
+ + "(git-b04388f9e7546a9f) on Linux i686";
+ private static RelayServerDescriptor createWithPlatformLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.platformLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String publishedLine = "published 2012-01-01 04:03:19";
+ private static RelayServerDescriptor createWithPublishedLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.publishedLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String fingerprintLine = "opt fingerprint D873 3048 FC8E "
+ + "C910 2466 AD8F 3098 622B F1BF 71FD";
+ private static RelayServerDescriptor createWithFingerprintLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.fingerprintLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String hibernatingLine = null;
+ private static RelayServerDescriptor createWithHibernatingLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.hibernatingLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String uptimeLine = "uptime 48";
+ private static RelayServerDescriptor createWithUptimeLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.uptimeLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String onionKeyLines = "onion-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp"
+ + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE"
+ + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg"
+ + "MBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----";
+ private static RelayServerDescriptor createWithOnionKeyLines(
+ String lines) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.onionKeyLines = lines;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String signingKeyLines = "signing-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb"
+ + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN"
+ + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg"
+ + "MBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----";
+ private static RelayServerDescriptor createWithSigningKeyLines(
+ String lines) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.signingKeyLines = lines;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String exitPolicyLines = "reject *:*";
+ private static RelayServerDescriptor createWithExitPolicyLines(
+ String lines) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.exitPolicyLines = lines;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String contactLine = "contact Random Person <nobody AT "
+ + "example dot com>";
+ private static RelayServerDescriptor createWithContactLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.contactLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String familyLine = null;
+ private static RelayServerDescriptor createWithFamilyLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.familyLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String readHistoryLine = null;
+ private static RelayServerDescriptor createWithReadHistoryLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.readHistoryLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String writeHistoryLine = null;
+ private static RelayServerDescriptor createWithWriteHistoryLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.writeHistoryLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String eventdnsLine = null;
+ private static RelayServerDescriptor createWithEventdnsLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.eventdnsLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String cachesExtraInfoLine = null;
+ private static RelayServerDescriptor createWithCachesExtraInfoLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.cachesExtraInfoLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String extraInfoDigestLine = "opt extra-info-digest "
+ + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74";
+ private static RelayServerDescriptor createWithExtraInfoDigestLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.extraInfoDigestLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String hiddenServiceDirLine = "opt hidden-service-dir";
+ private static RelayServerDescriptor createWithHiddenServiceDirLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.hiddenServiceDirLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String protocolsLine = "opt protocols Link 1 2 Circuit 1";
+ private static RelayServerDescriptor createWithProtocolsLine(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.protocolsLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String allowSingleHopExitsLine = null;
+ private static RelayServerDescriptor
+ createWithAllowSingleHopExitsLine(String line)
+ throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.allowSingleHopExitsLine = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private String routerSignatureLines = "router-signature\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT"
+ + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n"
+ + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n"
+ + "-----END SIGNATURE-----";
+ private static RelayServerDescriptor createWithRouterSignatureLines(
+ String line) throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ db.routerSignatureLines = line;
+ return new RelayServerDescriptorImpl(db.buildDescriptor());
+ }
+ private byte[] buildDescriptor() {
+ StringBuilder sb = new StringBuilder();
+ if (this.routerLine != null) {
+ sb.append(this.routerLine + "\n");
+ }
+ if (this.bandwidthLine != null) {
+ sb.append(this.bandwidthLine + "\n");
+ }
+ if (this.platformLine != null) {
+ sb.append(this.platformLine + "\n");
+ }
+ if (this.publishedLine != null) {
+ sb.append(this.publishedLine + "\n");
+ }
+ if (this.fingerprintLine != null) {
+ sb.append(this.fingerprintLine + "\n");
+ }
+ if (this.hibernatingLine != null) {
+ sb.append(this.hibernatingLine + "\n");
+ }
+ if (this.uptimeLine != null) {
+ sb.append(this.uptimeLine + "\n");
+ }
+ if (this.onionKeyLines != null) {
+ sb.append(this.onionKeyLines + "\n");
+ }
+ if (this.signingKeyLines != null) {
+ sb.append(this.signingKeyLines + "\n");
+ }
+ if (this.exitPolicyLines != null) {
+ sb.append(this.exitPolicyLines + "\n");
+ }
+ if (this.contactLine != null) {
+ sb.append(this.contactLine + "\n");
+ }
+ if (this.familyLine != null) {
+ sb.append(this.familyLine + "\n");
+ }
+ if (this.readHistoryLine != null) {
+ sb.append(this.readHistoryLine + "\n");
+ }
+ if (this.writeHistoryLine != null) {
+ sb.append(this.writeHistoryLine + "\n");
+ }
+ if (this.eventdnsLine != null) {
+ sb.append(this.eventdnsLine + "\n");
+ }
+ if (this.cachesExtraInfoLine != null) {
+ sb.append(this.cachesExtraInfoLine + "\n");
+ }
+ if (this.extraInfoDigestLine != null) {
+ sb.append(this.extraInfoDigestLine + "\n");
+ }
+ if (this.hiddenServiceDirLine != null) {
+ sb.append(this.hiddenServiceDirLine + "\n");
+ }
+ if (this.protocolsLine != null) {
+ sb.append(this.protocolsLine + "\n");
+ }
+ if (this.allowSingleHopExitsLine != null) {
+ sb.append(this.allowSingleHopExitsLine + "\n");
+ }
+ if (this.routerSignatureLines != null) {
+ sb.append(this.routerSignatureLines + "\n");
+ }
+ return sb.toString().getBytes();
+ }
+ }
+
+ @Test()
+ public void testSampleDescriptor() throws DescriptorParseException {
+ DescriptorBuilder db = new DescriptorBuilder();
+ RelayServerDescriptor descriptor =
+ new RelayServerDescriptorImpl(db.buildDescriptor());
+ assertEquals("saberrider2008", descriptor.getNickname());
+ assertEquals("94.134.192.243", descriptor.getAddress());
+ assertEquals(9001, (int) descriptor.getOrPort());
+ assertEquals(0, (int) descriptor.getSocksPort());
+ assertEquals(0, (int) descriptor.getDirPort());
+ assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686",
+ descriptor.getPlatform());
+ assertEquals(Arrays.asList(new Integer[] {1, 2}),
+ descriptor.getLinkProtocolVersions());
+ assertEquals(Arrays.asList(new Integer[] {1}),
+ descriptor.getCircuitProtocolVersions());
+ assertEquals(1325390599000L, descriptor.getPublishedMillis());
+ assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD",
+ descriptor.getFingerprint());
+ assertEquals(48, (int) descriptor.getUptime());
+ assertEquals(51200, (int) descriptor.getBandwidthRate());
+ assertEquals(51200, (int) descriptor.getBandwidthBurst());
+ assertEquals(53470, (int) descriptor.getBandwidthObserved());
+ assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74",
+ descriptor.getExtraInfoDigest());
+ assertEquals(Arrays.asList(new Integer[] {2}),
+ descriptor.getHiddenServiceDirVersions());
+ assertEquals("Random Person <nobody AT example dot com>",
+ descriptor.getContact());
+ assertEquals(Arrays.asList(new String[] {"reject *:*"}),
+ descriptor.getExitPolicyLines());
+ assertFalse(descriptor.isHibernating());
+ assertNull(descriptor.getFamilyEntries());
+ assertNull(descriptor.getReadHistory());
+ assertNull(descriptor.getWriteHistory());
+ assertFalse(descriptor.getUsesEnhancedDnsLogic());
+ assertFalse(descriptor.getCachesExtraInfo());
+ assertFalse(descriptor.getAllowSingleHopExits());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testRouterLineMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine(null);
+ }
+
+ @Test()
+ public void testRouterOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithRouterLine("opt router saberrider2008 "
+ + "94.134.192.243 9001 0 0");
+ assertEquals("saberrider2008", descriptor.getNickname());
+ assertEquals("94.134.192.243", descriptor.getAddress());
+ assertEquals(9001, (int) descriptor.getOrPort());
+ assertEquals(0, (int) descriptor.getSocksPort());
+ assertEquals(0, (int) descriptor.getDirPort());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testRouterLinePrecedingHibernatingLine()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("hibernating true\nrouter "
+ + "saberrider2008 94.134.192.243 9001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNicknameMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router 94.134.192.243 9001 "
+ + "0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNicknameInvalidChar() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router $aberrider2008 "
+ + "94.134.192.243 9001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNicknameTooLong() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router "
+ + "saberrider2008ReallyLongNickname 94.134.192.243 9001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testAddress24() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192/24 9001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testAddress294() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "294.134.192.243 9001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testAddressMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 9001 "
+ + "0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testOrPort99001() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192.243 99001 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testOrPortMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192.243 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testOrPortOne() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192.243 one 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testOrPortNewline() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192.243 0\n 0 0");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testDirPortMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterLine("router saberrider2008 "
+ + "94.134.192.243 9001 0 ");
+ }
+
+ @Test()
+ public void testPlatformMissing() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithPlatformLine(null);
+ assertNull(descriptor.getPlatform());
+ }
+
+ @Test()
+ public void testPlatformOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithPlatformLine("opt platform Tor 0.2.2.35 "
+ + "(git-b04388f9e7546a9f) on Linux i686");
+ assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686",
+ descriptor.getPlatform());
+ }
+
+ @Test()
+ public void testPlatformNoSpace() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithPlatformLine("platform");
+ assertEquals("", descriptor.getPlatform());
+ }
+
+ @Test()
+ public void testPlatformSpace() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithPlatformLine("platform ");
+ assertEquals("", descriptor.getPlatform());
+ }
+
+ @Test()
+ public void testProtocolsNoOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithProtocolsLine("protocols Link 1 2 Circuit 1");
+ assertEquals(Arrays.asList(new Integer[] {1, 2}),
+ descriptor.getLinkProtocolVersions());
+ assertEquals(Arrays.asList(new Integer[] {1}),
+ descriptor.getCircuitProtocolVersions());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testProtocolsAB() throws DescriptorParseException {
+ DescriptorBuilder.createWithProtocolsLine("opt protocols Link A B "
+ + "Circuit 1");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testProtocolsNoCircuitVersions()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithProtocolsLine("opt protocols Link 1 2");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testPublishedMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithPublishedLine(null);
+ }
+
+ @Test()
+ public void testPublishedOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithPublishedLine("opt published 2012-01-01 04:03:19");
+ assertEquals(1325390599000L, descriptor.getPublishedMillis());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testPublished3012() throws DescriptorParseException {
+ DescriptorBuilder.createWithPublishedLine("published 3012-01-01 "
+ + "04:03:19");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testPublished1912() throws DescriptorParseException {
+ DescriptorBuilder.createWithPublishedLine("published 1912-01-01 "
+ + "04:03:19");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testPublishedFeb31() throws DescriptorParseException {
+ DescriptorBuilder.createWithPublishedLine("published 2012-02-31 "
+ + "04:03:19");
+ }
+
+ @Test()
+ public void testFingerprintNoOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithFingerprintLine("fingerprint D873 3048 FC8E C910 2466 "
+ + "AD8F 3098 622B F1BF 71FD");
+ assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD",
+ descriptor.getFingerprint());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFingerprintG() throws DescriptorParseException {
+ DescriptorBuilder.createWithFingerprintLine("opt fingerprint G873 "
+ + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFingerprintTooShort() throws DescriptorParseException {
+ DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 "
+ + "3048 FC8E C910 2466 AD8F 3098 622B F1BF");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFingerprintTooLong() throws DescriptorParseException {
+ DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 "
+ + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD D873");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFingerprintNoSpaces() throws DescriptorParseException {
+ DescriptorBuilder.createWithFingerprintLine("opt fingerprint "
+ + "D8733048FC8EC9102466AD8F3098622BF1BF71FD");
+ }
+
+ @Test()
+ public void testUptimeMissing() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithUptimeLine(null);
+ assertEquals(-1, (int) descriptor.getUptime());
+ }
+
+ @Test()
+ public void testUptimeOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithUptimeLine("opt uptime 48");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testUptimeFourtyEight() throws DescriptorParseException {
+ DescriptorBuilder.createWithUptimeLine("uptime fourty-eight");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testUptimeMinusOne() throws DescriptorParseException {
+ DescriptorBuilder.createWithUptimeLine("uptime -1");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testUptimeSpace() throws DescriptorParseException {
+ DescriptorBuilder.createWithUptimeLine("uptime ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testUptimeNoSpace() throws DescriptorParseException {
+ DescriptorBuilder.createWithUptimeLine("uptime");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testUptimeFourEight() throws DescriptorParseException {
+ DescriptorBuilder.createWithUptimeLine("uptime 4 8");
+ }
+
+ @Test()
+ public void testBandwidthOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithBandwidthLine("opt bandwidth 51200 51200 53470");
+ assertEquals(51200, (int) descriptor.getBandwidthRate());
+ assertEquals(51200, (int) descriptor.getBandwidthBurst());
+ assertEquals(53470, (int) descriptor.getBandwidthObserved());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testBandwidthMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithBandwidthLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testBandwidthTwoValues() throws DescriptorParseException {
+ DescriptorBuilder.createWithBandwidthLine("bandwidth 51200 51200");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testBandwidthFourValues() throws DescriptorParseException {
+ DescriptorBuilder.createWithBandwidthLine("bandwidth 51200 51200 "
+ + "53470 53470");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testBandwidthMinusOneTwoThree()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithBandwidthLine("bandwidth -1 -2 -3");
+ }
+
+ @Test()
+ public void testExtraInfoDigestNoOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithExtraInfoDigestLine("extra-info-digest "
+ + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74");
+ assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74",
+ descriptor.getExtraInfoDigest());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExtraInfoDigestNoSpace()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithExtraInfoDigestLine("opt "
+ + "extra-info-digest");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExtraInfoDigestTooShort()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithExtraInfoDigestLine("opt "
+ + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F5");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExtraInfoDigestTooLong()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithExtraInfoDigestLine("opt "
+ + "extra-info-digest "
+ + "1469D1550738A25B1E7B47CDDBCD7B2899F51B741469");
+ }
+
+ @Test()
+ public void testExtraInfoDigestMissing()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithExtraInfoDigestLine(null);
+ assertNull(descriptor.getExtraInfoDigest());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testOnionKeyMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithOnionKeyLines(null);
+ }
+
+ @Test()
+ public void testOnionKeyOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithOnionKeyLines("opt onion-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp"
+ + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE"
+ + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg"
+ + "MBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testSigningKeyMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithSigningKeyLines(null);
+ }
+
+ @Test()
+ public void testSigningKeyOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithSigningKeyLines("opt signing-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb"
+ + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN"
+ + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg"
+ + "MBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----");
+ }
+
+ @Test()
+ public void testHiddenServiceDirMissing()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHiddenServiceDirLine(null);
+ assertNull(descriptor.getHiddenServiceDirVersions());
+ }
+
+ @Test()
+ public void testHiddenServiceDirNoOpt()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHiddenServiceDirLine("hidden-service-dir");
+ assertEquals(Arrays.asList(new Integer[] {2}),
+ descriptor.getHiddenServiceDirVersions());
+ }
+
+ @Test()
+ public void testHiddenServiceDirVersions2And3()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHiddenServiceDirLine("hidden-service-dir 2 3");
+ assertEquals(Arrays.asList(new Integer[] {2, 3}),
+ descriptor.getHiddenServiceDirVersions());
+ }
+
+ @Test()
+ public void testContactMissing() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithContactLine(null);
+ assertNull(descriptor.getContact());
+ }
+
+ @Test()
+ public void testContactOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithContactLine("opt contact Random Person");
+ assertEquals("Random Person", descriptor.getContact());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testContactDuplicate() throws DescriptorParseException {
+ DescriptorBuilder.createWithContactLine("contact Random "
+ + "Person\ncontact Random Person");
+ }
+
+ @Test()
+ public void testContactNoSpace() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithContactLine("contact");
+ assertEquals("", descriptor.getContact());
+ }
+
+ @Test()
+ public void testExitPolicyRejectAllAcceptAll()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithExitPolicyLines("reject *:*\naccept *:*");
+ assertEquals(Arrays.asList(new String[] {"reject *:*", "accept *:*"}),
+ descriptor.getExitPolicyLines());
+ }
+
+ @Test()
+ public void testExitPolicyOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithExitPolicyLines("opt reject *:*");
+ assertEquals(Arrays.asList(new String[] {"reject *:*"}),
+ descriptor.getExitPolicyLines());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExitPolicyNoPort() throws DescriptorParseException {
+ DescriptorBuilder.createWithExitPolicyLines("reject *");
+ }
+
+ @Test()
+ public void testExitPolicyAccept80RejectAll()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithExitPolicyLines("accept *:80\nreject *:*");
+ assertEquals(Arrays.asList(new String[] {"accept *:80",
+ "reject *:*"}), descriptor.getExitPolicyLines());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExitPolicyReject321() throws DescriptorParseException {
+ DescriptorBuilder.createWithExitPolicyLines("reject "
+ + "123.123.123.321:80");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExitPolicyRejectPort66666()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithExitPolicyLines("reject *:66666");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExitPolicyProjectAll() throws DescriptorParseException {
+ DescriptorBuilder.createWithExitPolicyLines("project *:*");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testExitPolicyMissing() throws DescriptorParseException {
+ DescriptorBuilder.createWithExitPolicyLines(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testRouterSignatureMissing()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterSignatureLines(null);
+ }
+
+ @Test()
+ public void testRouterSignatureOpt()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterSignatureLines("opt "
+ + "router-signature\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "crypto lines are ignored anyway\n"
+ + "-----END SIGNATURE-----");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testRouterSignatureNotLastLine()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithRouterSignatureLines("router-signature\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT"
+ + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n"
+ + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n"
+ + "-----END SIGNATURE-----\ncontact me");
+ }
+
+ @Test()
+ public void testHibernatingOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHibernatingLine("opt hibernating true");
+ assertTrue(descriptor.isHibernating());
+ }
+
+ @Test()
+ public void testHibernatingFalse() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHibernatingLine("hibernating false");
+ assertFalse(descriptor.isHibernating());
+ }
+
+ @Test()
+ public void testHibernatingTrue() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithHibernatingLine("hibernating true");
+ assertTrue(descriptor.isHibernating());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testHibernatingYep() throws DescriptorParseException {
+ DescriptorBuilder.createWithHibernatingLine("hibernating yep");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testHibernatingNoSpace() throws DescriptorParseException {
+ DescriptorBuilder.createWithHibernatingLine("hibernating");
+ }
+
+ @Test()
+ public void testFamilyOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithFamilyLine("opt family saberrider2008");
+ assertEquals(Arrays.asList(new String[] {"saberrider2008"}),
+ descriptor.getFamilyEntries());
+ }
+
+ @Test()
+ public void testFamilyFingerprint() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithFamilyLine("family "
+ + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD");
+ assertEquals(Arrays.asList(new String[] {
+ "$D8733048FC8EC9102466AD8F3098622BF1BF71FD"}),
+ descriptor.getFamilyEntries());
+ }
+
+ @Test()
+ public void testFamilyNickname() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithFamilyLine("family saberrider2008");
+ assertEquals(Arrays.asList(new String[] {"saberrider2008"}),
+ descriptor.getFamilyEntries());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFamilyDuplicate() throws DescriptorParseException {
+ DescriptorBuilder.createWithFamilyLine("family "
+ + "saberrider2008\nfamily saberrider2008");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFamilyNicknamePrefix() throws DescriptorParseException {
+ DescriptorBuilder.createWithFamilyLine("family $saberrider2008");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFamilyFingerprintNoPrefix()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithFamilyLine("family "
+ + "D8733048FC8EC9102466AD8F3098622BF1BF71FD");
+ }
+
+ @Test()
+ public void testWriteHistory() throws DescriptorParseException {
+ String writeHistoryLine = "write-history 2012-01-01 03:51:44 (900 s) "
+ + "4345856,261120,7591936,1748992";
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine(writeHistoryLine);
+ assertNotNull(descriptor.getWriteHistory());
+ BandwidthHistory parsedWriteHistory = descriptor.getWriteHistory();
+ assertEquals(writeHistoryLine, parsedWriteHistory.getLine());
+ assertEquals(1325389904000L, (long) parsedWriteHistory.
+ getHistoryEndMillis());
+ assertEquals(900L, (long) parsedWriteHistory.getIntervalLength());
+ SortedMap<Long, Long> bandwidthValues = parsedWriteHistory.
+ getBandwidthValues();
+ assertEquals(4345856L, (long) bandwidthValues.remove(1325387204000L));
+ assertEquals(261120L, (long) bandwidthValues.remove(1325388104000L));
+ assertEquals(7591936L, (long) bandwidthValues.remove(1325389004000L));
+ assertEquals(1748992L, (long) bandwidthValues.remove(1325389904000L));
+ assertTrue(bandwidthValues.isEmpty());
+ }
+
+ @Test()
+ public void testWriteHistoryOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine("opt write-history 2012-01-01 "
+ + "03:51:44 (900 s) 4345856,261120,7591936,1748992");
+ assertNotNull(descriptor.getWriteHistory());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistory3012() throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "3012-01-01 03:51:44 (900 s) 4345856,261120,7591936,1748992");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryNoSeconds()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51 (900 s) 4345856,261120,7591936,1748992");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryNoParathenses()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51:44 900 s 4345856,261120,7591936,1748992");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryNoSpaceSeconds()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51:44 (900s) 4345856,261120,7591936,1748992");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryTrailingComma()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51:44 (900 s) 4345856,261120,7591936,");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryOneTwoThree()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51:44 (900 s) one,two,three");
+ }
+
+ @Test()
+ public void testWriteHistoryNoValuesSpace()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 "
+ + "(900 s) ");
+ assertEquals(900, (long) descriptor.getWriteHistory().
+ getIntervalLength());
+ assertTrue(descriptor.getWriteHistory().getBandwidthValues().
+ isEmpty());
+ }
+
+ @Test()
+ public void testWriteHistoryNoValuesNoSpace()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 "
+ + "(900 s)");
+ assertEquals(900, (long) descriptor.getWriteHistory().
+ getIntervalLength());
+ assertTrue(descriptor.getWriteHistory().getBandwidthValues().
+ isEmpty());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryNoS() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 "
+ + "(900 ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testWriteHistoryTrailingNumber()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithWriteHistoryLine("write-history "
+ + "2012-01-01 03:51:44 (900 s) 4345856 1");
+ }
+
+ @Test()
+ public void testWriteHistory1800Seconds()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 "
+ + "(1800 s) 4345856");
+ assertEquals(1800L, (long) descriptor.getWriteHistory().
+ getIntervalLength());
+ }
+
+ @Test()
+ public void testReadHistory() throws DescriptorParseException {
+ String readHistoryLine = "read-history 2012-01-01 03:51:44 (900 s) "
+ + "4268032,139264,7797760,1415168";
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithReadHistoryLine(readHistoryLine);
+ assertNotNull(descriptor.getReadHistory());
+ BandwidthHistory parsedReadHistory = descriptor.getReadHistory();
+ assertEquals(readHistoryLine, parsedReadHistory.getLine());
+ assertEquals(1325389904000L, (long) parsedReadHistory.
+ getHistoryEndMillis());
+ assertEquals(900L, (long) parsedReadHistory.getIntervalLength());
+ SortedMap<Long, Long> bandwidthValues = parsedReadHistory.
+ getBandwidthValues();
+ assertEquals(4268032L, (long) bandwidthValues.remove(1325387204000L));
+ assertEquals(139264L, (long) bandwidthValues.remove(1325388104000L));
+ assertEquals(7797760L, (long) bandwidthValues.remove(1325389004000L));
+ assertEquals(1415168L, (long) bandwidthValues.remove(1325389904000L));
+ assertTrue(bandwidthValues.isEmpty());
+ }
+
+ /* TODO There are some old server descriptors with " read-history"
+ * lines. Find out if these were spec-compliant and if other lines may
+ * start with leading spaces, too. */
+ @Test(expected = DescriptorParseException.class)
+ public void testReadHistoryLeadingSpace()
+ throws DescriptorParseException {
+ String readHistoryLine = " read-history 2012-01-01 03:51:44 (900 s) "
+ + "4268032,139264,7797760,1415168";
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithReadHistoryLine(readHistoryLine);
+ }
+
+ @Test()
+ public void testEventdnsOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithEventdnsLine("opt eventdns true");
+ assertTrue(descriptor.getUsesEnhancedDnsLogic());
+ }
+
+ @Test()
+ public void testEventdnsTrue() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithEventdnsLine("eventdns true");
+ assertTrue(descriptor.getUsesEnhancedDnsLogic());
+ }
+
+ @Test()
+ public void testEventdnsFalse() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithEventdnsLine("eventdns false");
+ assertFalse(descriptor.getUsesEnhancedDnsLogic());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEventdns1() throws DescriptorParseException {
+ DescriptorBuilder.createWithEventdnsLine("eventdns 1");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testEventdnsNo() throws DescriptorParseException {
+ DescriptorBuilder.createWithEventdnsLine("eventdns no");
+ }
+
+ @Test()
+ public void testCachesExtraInfoOpt() throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithCachesExtraInfoLine("opt caches-extra-info");
+ assertTrue(descriptor.getCachesExtraInfo());
+ }
+
+ @Test()
+ public void testCachesExtraInfoNoSpace()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithCachesExtraInfoLine("caches-extra-info");
+ assertTrue(descriptor.getCachesExtraInfo());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testCachesExtraInfoTrue() throws DescriptorParseException {
+ DescriptorBuilder.createWithCachesExtraInfoLine("caches-extra-info "
+ + "true");
+ }
+
+ @Test()
+ public void testAllowSingleHopExitsOpt()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithCachesExtraInfoLine("opt allow-single-hop-exits");
+ assertTrue(descriptor.getAllowSingleHopExits());
+ }
+
+ @Test()
+ public void testAllowSingleHopExitsNoSpace()
+ throws DescriptorParseException {
+ RelayServerDescriptor descriptor = DescriptorBuilder.
+ createWithCachesExtraInfoLine("allow-single-hop-exits");
+ assertTrue(descriptor.getAllowSingleHopExits());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testAllowSingleHopExitsTrue()
+ throws DescriptorParseException {
+ DescriptorBuilder.createWithCachesExtraInfoLine(
+ "allow-single-hop-exits true");
+ }
+}
+