tor-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
December 2011
- 17 participants
- 533 discussions

[torflow/master] Be more strict w/ circ failures and fast nodes.
by mikeperry@torproject.org 15 Dec '11
by mikeperry@torproject.org 15 Dec '11
15 Dec '11
commit 1e6a4219b57b8ea91418bd1c580a5b255e10a491
Author: Mike Perry <mikeperry-git(a)fscked.org>
Date: Thu Dec 15 11:50:42 2011 -0800
Be more strict w/ circ failures and fast nodes.
We use unfiltered bandwidth because I suspect we're being too generous to fast
nodes. To get more feedback, they should have to have spare capacity all the
time, not just sometimes.
However, gimpy nodes will still get to use filtered bandwidth if we've
punished them too much already.
Also, circuit failures now target a 0% rate (full success) instead of the
network average, but the penalty only applies if you fall below the network
average for your node class.
---
NetworkScanners/BwAuthority/aggregate.py | 32 +++++++++++++++++++++--------
1 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/NetworkScanners/BwAuthority/aggregate.py b/NetworkScanners/BwAuthority/aggregate.py
index b8b01ea..46e4679 100755
--- a/NetworkScanners/BwAuthority/aggregate.py
+++ b/NetworkScanners/BwAuthority/aggregate.py
@@ -21,6 +21,7 @@ IGNORE_GUARDS = 0
# The guard measurement period is based on the client turnover
# rate for guard nodes
+# XXX: Make this a consensus param
GUARD_SAMPLE_RATE = 2*7*24*60*60 # 2wks
# PID constant defaults. May be overridden by consensus
@@ -579,7 +580,17 @@ def main(argv):
n.use_bw = n.ns_bw
if cs_junk.use_pid_tgt:
- n.pid_error = (n.filt_bw - pid_tgt_avg[n.node_class()]) \
+ n.pid_error = (n.strm_bw - pid_tgt_avg[n.node_class()]) \
+ / pid_tgt_avg[n.node_class()]
+ # use filt_bw for pid_error < 0
+ if cs_junk.use_mercy:
+ if cs_junk.use_desc_bw:
+ if n.pid_error_sum < 0 and n.pid_error < 0:
+ n.pid_error = (n.filt_bw - pid_tgt_avg[n.node_class()]) \
+ / pid_tgt_avg[n.node_class()]
+ else:
+ if n.desc_bw > n.ns_bw and n.pid_error < 0:
+ n.pid_error = (n.filt_bw - pid_tgt_avg[n.node_class()]) \
/ pid_tgt_avg[n.node_class()]
else:
if cs_junk.use_best_ratio and n.sbw_ratio > n.fbw_ratio:
@@ -593,14 +604,17 @@ def main(argv):
# only in the event of update?
# Penalize nodes for circ failure rate
if cs_junk.use_circ_fails:
- # FIXME: Should we compute this relative to 0? Why target anything
- # less?
- circ_error = ((1.0-n.circ_fail_rate) - true_circ_avg[n.node_class()]) \
- / true_circ_avg[n.node_class()]
- # FIXME: Hrmm, should we only penalize for circ fails, or should
- # we reward, too?
- if circ_error < 0:
- n.pid_error = min(circ_error,n.pid_error)
+ # Compute circ_error relative to 1.0 (full success), but only
+ # apply it if it is both below the network avg and worse than
+ # the pid_error
+ if (1.0-n.circ_fail_rate) < true_circ_avg[n.node_class()]:
+ circ_error = -n.circ_fail_rate # ((1.0-fail) - 1.0)/1.0
+ if circ_error < 0 and circ_error < n.pid_error:
+ plog("INFO",
+ "CPU overload for %s node %s=%s desc=%d ns=%d pid_error=%f circ_error=%f circ_fail=%f" %
+ (n.node_class(), n.nick, n.idhex, n.desc_bw, n.ns_bw,
+ n.pid_error, circ_error, n.circ_fail_rate))
+ n.pid_error = min(circ_error,n.pid_error)
# Don't accumulate too much amplification for fast nodes
if cs_junk.use_desc_bw:
1
0

[torflow/master] Make the guard sample rate a consensus param.
by mikeperry@torproject.org 15 Dec '11
by mikeperry@torproject.org 15 Dec '11
15 Dec '11
commit 3c0dd74bcc46050b4b7131b479a779bf26258926
Author: Mike Perry <mikeperry-git(a)fscked.org>
Date: Thu Dec 15 12:01:15 2011 -0800
Make the guard sample rate a consensus param.
---
NetworkScanners/BwAuthority/README.spec.txt | 6 ++++++
NetworkScanners/BwAuthority/aggregate.py | 9 +++++++--
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/NetworkScanners/BwAuthority/README.spec.txt b/NetworkScanners/BwAuthority/README.spec.txt
index a1b732e..835b312 100644
--- a/NetworkScanners/BwAuthority/README.spec.txt
+++ b/NetworkScanners/BwAuthority/README.spec.txt
@@ -585,3 +585,9 @@
If absent, the default is 500.0, which translates to a 501X
multiplier of descriptor bandwidth.
+
+ "bwauthguardrate=N"
+ Restricts the rate at which we perform feedback on Guard nodes
+ to at most every N seconds.
+
+ If absent, the default is 2*7*24*60*60, or two weeks.
diff --git a/NetworkScanners/BwAuthority/aggregate.py b/NetworkScanners/BwAuthority/aggregate.py
index 46e4679..77e3cdc 100755
--- a/NetworkScanners/BwAuthority/aggregate.py
+++ b/NetworkScanners/BwAuthority/aggregate.py
@@ -21,7 +21,6 @@ IGNORE_GUARDS = 0
# The guard measurement period is based on the client turnover
# rate for guard nodes
-# XXX: Make this a consensus param
GUARD_SAMPLE_RATE = 2*7*24*60*60 # 2wks
# PID constant defaults. May be overridden by consensus
@@ -243,6 +242,7 @@ class ConsensusJunk:
self.use_desc_bw = True
self.use_mercy = False
+ self.guard_sample_rate = GUARD_SAMPLE_RATE
self.pid_max = 500.0
self.K_p = K_p
self.T_i = T_i
@@ -288,6 +288,10 @@ class ConsensusJunk:
elif p.startswith("bwauthpidmax="):
self.pid_max = (int(p.split("=")[1])/10000.0)
plog("INFO", "Got pid_max=%f from consensus." % self.pid_max)
+ elif p.startswith("bwauthguardrate="):
+ self.guard_sample_rate = int(p.split("=")[1])
+ plog("INFO", "Got guard_sample_rate=%d from consensus." %
+ self.guard_sample_rate)
except:
plog("NOTICE", "Bw auth PID control disabled due to parse error.")
traceback.print_exc()
@@ -655,7 +659,8 @@ def main(argv):
and "Exit" not in prev_consensus[n.idhex].flags):
# Do full feedback if our previous vote > 2.5 weeks old
if n.idhex not in prev_votes.vote_map or \
- n.measured_at - prev_votes.vote_map[n.idhex].measured_at > GUARD_SAMPLE_RATE:
+ n.measured_at - prev_votes.vote_map[n.idhex].measured_at \
+ > cs_junk.guard_sample_rate:
n.new_bw = n.get_pid_bw(prev_votes.vote_map[n.idhex],
cs_junk.K_p,
cs_junk.K_i,
1
0

15 Dec '11
commit b8adf0c86092950488d06c37e1b8f3a5cae451fd
Author: Mike Perry <mikeperry-git(a)fscked.org>
Date: Sun Dec 11 21:39:59 2011 -0800
Log the network pid error averages.
Also, log node class when we cap, and in some other cases.
---
NetworkScanners/BwAuthority/aggregate.py | 42 ++++++++++++++++++++++--------
1 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/NetworkScanners/BwAuthority/aggregate.py b/NetworkScanners/BwAuthority/aggregate.py
index 15d8d82..b8b01ea 100755
--- a/NetworkScanners/BwAuthority/aggregate.py
+++ b/NetworkScanners/BwAuthority/aggregate.py
@@ -589,8 +589,12 @@ def main(argv):
n.pid_error = (n.filt_bw - true_filt_avg[n.node_class()]) \
/ true_filt_avg[n.node_class()]
+ # XXX: Refactor the following 3 clauses out into it's own function, so we can log
+ # only in the event of update?
# Penalize nodes for circ failure rate
if cs_junk.use_circ_fails:
+ # FIXME: Should we compute this relative to 0? Why target anything
+ # less?
circ_error = ((1.0-n.circ_fail_rate) - true_circ_avg[n.node_class()]) \
/ true_circ_avg[n.node_class()]
# FIXME: Hrmm, should we only penalize for circ fails, or should
@@ -601,13 +605,13 @@ def main(argv):
# Don't accumulate too much amplification for fast nodes
if cs_junk.use_desc_bw:
if n.pid_error_sum > cs_junk.pid_max and n.pid_error > 0:
- plog("INFO", "Capping feedback for node %s=%s desc=%d ns=%d pid_error_sum=%f" %
- (n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error_sum))
+ plog("INFO", "Capping feedback for %s node %s=%s desc=%d ns=%d pid_error_sum=%f" %
+ (n.node_class(), n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error_sum))
n.pid_error_sum = cs_junk.pid_max
else:
if float(n.ns_bw)/n.desc_bw > cs_junk.pid_max and n.pid_error > 0:
- plog("INFO", "Capping feedback for node %s=%s desc=%d ns=%d pid_error=%f" %
- (n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error))
+ plog("INFO", "Capping feedback for %s node %s=%s desc=%d ns=%d pid_error=%f" %
+ (n.node_class(), n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error))
n.pid_error = 0
n.pid_error_sum = 0
@@ -617,12 +621,12 @@ def main(argv):
# If node was demoted in the past and we plan to demote it again,
# let's just not and say we did.
if n.desc_bw > n.ns_bw and n.pid_error < 0:
- plog("DEBUG", "Showing mercy for node %s=%s desc=%d ns=%d pid_error=%f" %
- (n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error))
+ plog("DEBUG", "Showing mercy for %s node %s=%s desc=%d ns=%d pid_error=%f" %
+ (n.node_class(), n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error))
n.use_bw = n.desc_bw
if n.pid_error_sum < 0 and n.pid_error < 0:
- plog("DEBUG", "Showing mercy for node %s=%s desc=%d ns=%d pid_error_sum=%f" %
- (n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error_sum))
+ plog("DEBUG", "Showing mercy for %s node %s=%s desc=%d ns=%d pid_error_sum=%f" %
+ (n.node_class(), n.nick, n.idhex, n.desc_bw, n.ns_bw, n.pid_error_sum))
n.pid_error_sum = 0
if n.idhex in prev_votes.vote_map:
@@ -736,7 +740,7 @@ def main(argv):
# Go through the list and cap them to NODE_CAP
for n in nodes.itervalues():
if n.new_bw >= 0x7fffffff:
- plog("WARN", "Bandwidth of node "+n.nick+"="+n.idhex+" exceeded maxint32: "+str(n.new_bw))
+ plog("WARN", "Bandwidth of "+n.node_class()+" node "+n.nick+"="+n.idhex+" exceeded maxint32: "+str(n.new_bw))
n.new_bw = 0x7fffffff
if cs_junk.T_i > 0 and cs_junk.T_i_decay > 0 \
and math.fabs(n.pid_error_sum) > \
@@ -744,7 +748,7 @@ def main(argv):
plog("NOTICE", "Large pid_error_sum for node "+n.idhex+"="+n.nick+": "+
str(n.pid_error_sum)+" vs "+str(n.pid_error))
if n.new_bw > tot_net_bw*NODE_CAP:
- plog("INFO", "Clipping extremely fast node "+n.idhex+"="+n.nick+
+ plog("INFO", "Clipping extremely fast "+n.node_class()+" node "+n.idhex+"="+n.nick+
" at "+str(100*NODE_CAP)+"% of network capacity ("+
str(n.new_bw)+"->"+str(int(tot_net_bw*NODE_CAP))+") "+
" pid_error="+str(n.pid_error)+
@@ -753,7 +757,7 @@ def main(argv):
n.pid_error_sum = 0 # Don't let unused error accumulate...
if n.new_bw <= 0:
if n.idhex in prev_consensus:
- plog("INFO", str(prev_consensus[n.idhex].flags)+" node "+n.idhex+"="+n.nick+" has bandwidth <= 0: "+str(n.new_bw))
+ plog("INFO", n.node_class()+" node "+n.idhex+"="+n.nick+" has bandwidth <= 0: "+str(n.new_bw))
else:
plog("INFO", "New node "+n.idhex+"="+n.nick+" has bandwidth < 0: "+str(n.new_bw))
n.new_bw = 1
@@ -808,6 +812,22 @@ def main(argv):
"Only measured %f of the previous consensus bandwidth despite measuring %f of the nodes" %
(measured_bw_pct, measured_pct))
+ for cl in ["Guard+Exit", "Guard", "Exit", "Middle"]:
+ c_nodes = filter(lambda n: n.node_class() == cl, nodes.itervalues())
+ nc_nodes = filter(lambda n: n.pid_error < 0, c_nodes)
+ pc_nodes = filter(lambda n: n.pid_error > 0, c_nodes)
+ plog("INFO", "Avg "+cl+" pid_error="+str(sum(map(lambda n: n.pid_error, c_nodes))/len(c_nodes)))
+ plog("INFO", "Avg "+cl+" |pid_error|="+str(sum(map(lambda n: abs(n.pid_error), c_nodes))/len(c_nodes)))
+ plog("INFO", "Avg "+cl+" +pid_error=+"+str(sum(map(lambda n: n.pid_error, pc_nodes))/len(pc_nodes)))
+ plog("INFO", "Avg "+cl+" -pid_error="+str(sum(map(lambda n: n.pid_error, nc_nodes))/len(nc_nodes)))
+
+ n_nodes = filter(lambda n: n.pid_error < 0, nodes.itervalues())
+ p_nodes = filter(lambda n: n.pid_error > 0, nodes.itervalues())
+ plog("INFO", "Avg network pid_error="+str(sum(map(lambda n: n.pid_error, nodes.itervalues()))/len(nodes)))
+ plog("INFO", "Avg network |pid_error|="+str(sum(map(lambda n: abs(n.pid_error), nodes.itervalues()))/len(nodes)))
+ plog("INFO", "Avg network +pid_error=+"+str(sum(map(lambda n: n.pid_error, p_nodes))/len(p_nodes)))
+ plog("INFO", "Avg network -pid_error="+str(sum(map(lambda n: n.pid_error, n_nodes))/len(n_nodes)))
+
plog("INFO",
"Measured "+str(measured_pct) +"% of all tor nodes ("
+str(measured_bw_pct)+"% of previous consensus bw).")
1
0
commit 57bd584c646fa1d6fab7ef17d8a5dd774bba3d2d
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Dec 15 21:30:00 2011 +0100
Fix a bug in the vote parser.
---
.../impl/RelayNetworkStatusVoteImpl.java | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index 5089210..fd17e7b 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -267,7 +267,7 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
keyword.equals("dir-key-certification")) {
} else if (line.startsWith("-----BEGIN")) {
skipCrypto = true;
- } else if (line.equals("-----END")) {
+ } else if (line.startsWith("-----END")) {
skipCrypto = false;
} else if (!skipCrypto) {
/* TODO Is throwing an exception the right thing to do here?
1
0

15 Dec '11
commit 682f6e0d9ed945513a89fb40f8c11a142089eb4b
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Dec 15 21:46:51 2011 +0100
Fix a bug in the network status parser.
---
.../descriptor/impl/NetworkStatusImpl.java | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index af8b9e2..898d127 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -171,7 +171,10 @@ public abstract class NetworkStatusImpl {
int from = start;
while (from < end) {
int to = descriptorString.indexOf("\n" + keyword, from);
- if (to < 0) {
+ /* Searching for "\nkeyword" instead of "\nkeyword " or
+ * "\nkeyword\n" seems fragile. Workaround is to check for
+ * to > end, but that's really a hack. */
+ if (to > end || to < 0) {
to = end;
} else {
to += 1;
1
0

[metrics-lib/master] Make votes use the general network-status parsing code.
by karsten@torproject.org 15 Dec '11
by karsten@torproject.org 15 Dec '11
15 Dec '11
commit cb9c62a90e30052db5599bc469da95de161af087
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Dec 15 20:45:42 2011 +0100
Make votes use the general network-status parsing code.
---
.../descriptor/RelayNetworkStatusVote.java | 11 +-
.../descriptor/impl/NetworkStatusImpl.java | 20 +-
.../impl/RelayNetworkStatusVoteImpl.java | 577 ++++++++++++--------
.../impl/RelayNetworkStatusVoteImplTest.java | 574 +++++++++++++++++++
4 files changed, 933 insertions(+), 249 deletions(-)
diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
index 6bfdd6f..aab90bd 100644
--- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java
+++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java
@@ -27,8 +27,11 @@ public interface RelayNetworkStatusVote extends Descriptor {
/* Return the valid-until time in milliseconds. */
public long getValidUntilMillis();
- /* Return a list of the voting-delay times in seconds. */
- public List<Long> getVotingDelay();
+ /* Return the VoteSeconds time in seconds. */
+ public long getVoteSeconds();
+
+ /* Return the DistSeconds time in seconds. */
+ public long getDistSeconds();
/* Return recommended server versions or null if the authority doesn't
* recommend server versions. */
@@ -65,6 +68,10 @@ public interface RelayNetworkStatusVote extends Descriptor {
/* Return the directory key certificate version. */
public int getDirKeyCertificateVersion();
+ /* Return the legacy key or null if the directory authority does not use
+ * a legacy key. */
+ public String getLegacyKey();
+
/* Return the directory key publication timestamp. */
public long getDirKeyPublishedMillis();
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index 6ca546e..af8b9e2 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -51,6 +51,7 @@ public abstract class NetworkStatusImpl {
protected NetworkStatusImpl(byte[] rawDescriptorBytes)
throws DescriptorParseException {
this.rawDescriptorBytes = rawDescriptorBytes;
+ this.countKeywords(rawDescriptorBytes);
this.splitAndParseParts(rawDescriptorBytes);
}
@@ -126,7 +127,6 @@ public abstract class NetworkStatusImpl {
System.arraycopy(this.rawDescriptorBytes, start,
headerBytes, 0, end - start);
this.rememberFirstKeyword(headerBytes);
- this.countKeywords(headerBytes);
this.parseHeader(headerBytes);
}
@@ -153,7 +153,6 @@ public abstract class NetworkStatusImpl {
byte[] directoryFooterBytes = new byte[end - start];
System.arraycopy(this.rawDescriptorBytes, start,
directoryFooterBytes, 0, end - start);
- this.countKeywords(directoryFooterBytes);
this.parseFooter(directoryFooterBytes);
}
@@ -264,17 +263,17 @@ public abstract class NetworkStatusImpl {
* subclasses. */
private Map<String, Integer> parsedKeywords =
new HashMap<String, Integer>();
- protected void countKeywords(byte[] headerOrFooterBytes)
+ protected void countKeywords(byte[] rawDescriptorBytes)
throws DescriptorParseException {
try {
BufferedReader br = new BufferedReader(new StringReader(
- new String(headerOrFooterBytes)));
+ new String(rawDescriptorBytes)));
String line;
boolean skipCrypto = false;
while ((line = br.readLine()) != null) {
if (line.startsWith("-----BEGIN")) {
skipCrypto = true;
- } else if (line.equals("-----END")) {
+ } else if (line.startsWith("-----END")) {
skipCrypto = false;
} else if (!skipCrypto) {
String keyword = line.split(" ", -1)[0];
@@ -299,11 +298,14 @@ public abstract class NetworkStatusImpl {
protected void checkExactlyOnceKeywords(Set<String> keywords)
throws DescriptorParseException {
for (String keyword : keywords) {
- if (!this.parsedKeywords.containsKey(keyword) ||
- this.parsedKeywords.get(keyword) != 1) {
+ int contained = 0;
+ if (this.parsedKeywords.containsKey(keyword)) {
+ contained = this.parsedKeywords.get(keyword);
+ }
+ if (contained != 1) {
throw new DescriptorParseException("Keyword '" + keyword + "' is "
- + "contained " + this.parsedKeywords.get(keyword) + " times, "
- + "but must be contained exactly once.");
+ + "contained " + contained + " times, but must be contained "
+ + "exactly once.");
}
}
}
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index a7a5328..5089210 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -7,258 +7,341 @@ import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
-import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import org.torproject.descriptor.Descriptor;
-import org.torproject.descriptor.NetworkStatusEntry;
import org.torproject.descriptor.RelayNetworkStatusVote;
+/* TODO Find out if all keywords in the dir-source section are required.
+ * They are not all mentioned in dir-spec.txt. */
+
/* Contains a network status vote. */
-/* TODO This class is sharing a lot of parsing code with the consensus
- * class. Should there be an abstract super class for the two? */
-public class RelayNetworkStatusVoteImpl
+public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
implements RelayNetworkStatusVote {
protected static List<RelayNetworkStatusVote> parseVotes(
- byte[] voteBytes) {
+ byte[] votesBytes) {
List<RelayNetworkStatusVote> parsedVotes =
new ArrayList<RelayNetworkStatusVote>();
- String startToken = "network-status-version 3";
- String splitToken = "\n" + startToken;
- String ascii = new String(voteBytes);
- int length = voteBytes.length, start = ascii.indexOf(startToken);
- while (start < length) {
- int end = ascii.indexOf(splitToken, start);
- if (end < 0) {
- end = length;
- } else {
- end += 1;
+ List<byte[]> splitVotesBytes =
+ NetworkStatusImpl.splitRawDescriptorBytes(votesBytes,
+ "network-status-version 3");
+ try {
+ for (byte[] voteBytes : splitVotesBytes) {
+ RelayNetworkStatusVote parsedVote =
+ new RelayNetworkStatusVoteImpl(voteBytes);
+ parsedVotes.add(parsedVote);
}
- byte[] descBytes = new byte[end - start];
- System.arraycopy(voteBytes, start, descBytes, 0, end - start);
- RelayNetworkStatusVote parsedVote =
- new RelayNetworkStatusVoteImpl(descBytes);
- parsedVotes.add(parsedVote);
- start = end;
+ } catch (DescriptorParseException e) {
+ /* TODO Handle this error somehow. */
+ System.err.println("Failed to parse vote. Skipping.");
+ e.printStackTrace();
}
return parsedVotes;
}
- protected RelayNetworkStatusVoteImpl(byte[] voteBytes) {
- this.voteBytes = voteBytes;
- this.parseVoteBytes();
- this.checkConsistency();
- /* TODO Find a way to handle parse and consistency-check problems. */
+ protected RelayNetworkStatusVoteImpl(byte[] voteBytes)
+ throws DescriptorParseException {
+ super(voteBytes);
+ Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList((
+ "vote-status,consensus-methods,published,valid-after,fresh-until,"
+ + "valid-until,voting-delay,known-flags,dir-source,"
+ + "dir-key-certificate-version,fingerprint,dir-key-published,"
+ + "dir-key-expires,directory-footer").split(",")));
+ this.checkExactlyOnceKeywords(exactlyOnceKeywords);
+ Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList((
+ "client-versions,server-versions,params,contact,legacy-key").
+ split(",")));
+ this.checkAtMostOnceKeywords(atMostOnceKeywords);
+ this.checkFirstKeyword("network-status-version");
}
- private void parseVoteBytes() {
- String line = null;
+ protected void parseHeader(byte[] headerBytes)
+ throws DescriptorParseException {
try {
BufferedReader br = new BufferedReader(new StringReader(
- new String(this.voteBytes)));
- SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
- "yyyy-MM-dd HH:mm:ss");
- dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- StringBuilder dirSourceEntryLines = null, statusEntryLines = null;
- boolean skipCrypto = false;
+ new String(headerBytes)));
+ String line;
while ((line = br.readLine()) != null) {
- if (line.startsWith("network-status-version ")) {
- this.networkStatusVersion = Integer.parseInt(line.substring(
- "network-status-version ".length()));
- } else if (line.startsWith("vote-status ")) {
- if (!line.equals("vote-status vote")) {
- throw new RuntimeException("Line '" + line + "' indicates "
- + "that this string is not a vote. Aborting parsing.");
- }
- } else if (line.startsWith("consensus-methods ")) {
- for (String consensusMethodString : line.substring(
- "consensus-methods ".length()).split(" ")) {
- this.consensusMethods.add(Integer.parseInt(
- consensusMethodString));
- }
- } else if (line.startsWith("published ")) {
- this.publishedMillis = dateTimeFormat.parse(
- line.substring("published ".length())).getTime();
- } else if (line.startsWith("valid-after ")) {
- this.validAfterMillis = dateTimeFormat.parse(
- line.substring("valid-after ".length())).getTime();
- } else if (line.startsWith("fresh-until ")) {
- this.freshUntilMillis = dateTimeFormat.parse(
- line.substring("fresh-until ".length())).getTime();
- } else if (line.startsWith("valid-until ")) {
- this.validUntilMillis = dateTimeFormat.parse(
- line.substring("valid-until ".length())).getTime();
- } else if (line.startsWith("voting-delay ")) {
- for (String votingDelayString : line.substring(
- "voting-delay ".length()).split(" ")) {
- this.votingDelay.add(Long.parseLong(votingDelayString));
- }
- } else if (line.startsWith("client-versions ")) {
- this.recommendedClientVersions =
- Arrays.asList(line.split(" ")[1].split(","));
- } else if (line.startsWith("server-versions ")) {
- this.recommendedServerVersions =
- Arrays.asList(line.split(" ")[1].split(","));
- } else if (line.startsWith("known-flags ")) {
- for (String flag : line.substring("known-flags ".length()).
- split(" ")) {
- this.knownFlags.add(flag);
- }
- } else if (line.startsWith("params ")) {
- if (line.length() > "params ".length()) {
- for (String param :
- line.substring("params ".length()).split(" ")) {
- String paramName = param.split("=")[0];
- int paramValue = Integer.parseInt(param.split("=")[1]);
- this.consensusParams.put(paramName, paramValue);
- }
- }
- } else if (line.startsWith("dir-source ")) {
- String[] parts = line.split(" ");
- this.nickname = parts[1];
- this.identity = parts[2];
- this.address = parts[4];
- this.dirPort = Integer.parseInt(parts[5]);
- this.orPort = Integer.parseInt(parts[6]);
- /* TODO Add code for parsing legacy dir sources. */
- } else if (line.startsWith("contact ")) {
- this.contactLine = line.substring("contact ".length());
- } else if (line.startsWith("dir-key-certificate-version ")) {
- this.dirKeyCertificateVersion = Integer.parseInt(line.substring(
- "dir-key-certificate-version ".length()));
- } else if (line.startsWith("fingerprint ")) {
- /* Nothing new to learn here. We already know the fingerprint
- * from the dir-source line. */
- } else if (line.startsWith("dir-key-published ")) {
- this.dirKeyPublishedMillis = dateTimeFormat.parse(
- line.substring("dir-key-published ".length())).getTime();
- } else if (line.startsWith("dir-key-expires ")) {
- this.dirKeyExpiresMillis = dateTimeFormat.parse(
- line.substring("dir-key-expires ".length())).getTime();
- } else if (line.equals("dir-identity-key") ||
- line.equals("dir-signing-key") ||
- line.equals("dir-key-crosscert") ||
- line.equals("dir-key-certification")) {
- /* Ignore crypto parts for now. */
- } else if (line.startsWith("r ") ||
- line.equals("directory-footer")) {
- if (statusEntryLines != null) {
- try {
- NetworkStatusEntryImpl statusEntry =
- new NetworkStatusEntryImpl(
- statusEntryLines.toString().getBytes());
- this.statusEntries.put(statusEntry.getFingerprint(),
- statusEntry);
- } catch (DescriptorParseException e) {
- System.err.println("Could not parse status entry in vote. "
- + "Skipping.");
- }
- statusEntryLines = null;
- }
- if (line.startsWith("r ")) {
- statusEntryLines = new StringBuilder();
- statusEntryLines.append(line + "\n");
- }
- } else if (line.startsWith("s ") || line.equals("s") ||
- line.startsWith("opt v ") || line.startsWith("w ") ||
- line.startsWith("p ") || line.startsWith("m ")) {
- statusEntryLines.append(line + "\n");
- } else if (line.startsWith("directory-signature ")) {
- String[] parts = line.split(" ");
- String identity = parts[1];
- String signingKeyDigest = parts[2];
- this.directorySignatures.put(identity, signingKeyDigest);
- } else if (line.startsWith("-----BEGIN")) {
- skipCrypto = true;
- } else if (line.startsWith("-----END")) {
- skipCrypto = false;
- } else if (!skipCrypto) {
- throw new RuntimeException("Unrecognized line '" + line + "'.");
+ String[] parts = line.split(" ");
+ String keyword = parts[0];
+ if (keyword.equals("network-status-version")) {
+ this.parseNetworkStatusVersionLine(line, parts);
+ } else if (keyword.equals("vote-status")) {
+ this.parseVoteStatusLine(line, parts);
+ } else if (keyword.equals("consensus-methods")) {
+ this.parseConsensusMethodsLine(line, parts);
+ } else if (keyword.equals("published")) {
+ this.parsePublishedLine(line, parts);
+ } else if (keyword.equals("valid-after")) {
+ this.parseValidAfterLine(line, parts);
+ } else if (keyword.equals("fresh-until")) {
+ this.parseFreshUntilLine(line, parts);
+ } else if (keyword.equals("valid-until")) {
+ this.parseValidUntilLine(line, parts);
+ } else if (keyword.equals("voting-delay")) {
+ this.parseVotingDelayLine(line, parts);
+ } else if (keyword.equals("client-versions")) {
+ this.parseClientVersionsLine(line, parts);
+ } else if (keyword.equals("server-versions")) {
+ this.parseServerVersionsLine(line, parts);
+ } else if (keyword.equals("known-flags")) {
+ this.parseKnownFlagsLine(line, parts);
+ } else if (keyword.equals("params")) {
+ this.parseParamsLine(line, parts);
+ } else {
+ /* TODO Is throwing an exception the right thing to do here?
+ * This is probably fine for development, but once the library
+ * is in production use, this seems annoying. */
+ throw new DescriptorParseException("Unrecognized line '" + line
+ + "'.");
}
}
} catch (IOException e) {
throw new RuntimeException("Internal error: Ran into an "
+ "IOException while parsing a String in memory. Something's "
+ "really wrong.", e);
- } catch (ParseException e) {
- /* TODO Handle me correctly. */
- throw new RuntimeException("Parse error in line '" + line + "'.");
+ }
+ }
+
+ private void parseNetworkStatusVersionLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (!line.equals("network-status-version 3")) {
+ throw new DescriptorParseException("Illegal network status version "
+ + "number in line '" + line + "'.");
+ }
+ this.networkStatusVersion = 3;
+ }
+
+ private void parseVoteStatusLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length != 2 || !parts[1].equals("vote")) {
+ throw new DescriptorParseException("Line '" + line + "' indicates "
+ + "that this is not a vote.");
+ }
+ }
+
+ private void parseConsensusMethodsLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length < 2) {
+ throw new DescriptorParseException("Illegal line '" + line
+ + "' in vote.");
+ }
+ this.consensusMethods = new ArrayList<Integer>();
+ for (int i = 1; i < parts.length; i++) {
+ int consensusMethod = -1;
+ try {
+ consensusMethod = Integer.parseInt(parts[i]);
+ } catch (NumberFormatException e) {
+ /* We'll notice below that consensusMethod is still -1. */
+ }
+ if (consensusMethod < 1) {
+ throw new DescriptorParseException("Illegal consensus method "
+ + "number in line '" + line + "'.");
+ }
+ this.consensusMethods.add(Integer.parseInt(parts[i]));
+ }
+ }
+
+ private void parsePublishedLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+ 1, 2);
+ }
+
+ private void parseValidAfterLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+ 1, 2);
+ }
+
+ private void parseFreshUntilLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+ 1, 2);
+ }
+
+ private void parseValidUntilLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts,
+ 1, 2);
+ }
+
+ private void parseVotingDelayLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length != 3) {
+ throw new DescriptorParseException("Wrong number of values in line "
+ + "'" + line + "'.");
+ }
+ try {
+ this.voteSeconds = Long.parseLong(parts[1]);
+ this.distSeconds = Long.parseLong(parts[2]);
} catch (NumberFormatException e) {
- /* TODO Handle me. In theory, we shouldn't catch runtime
- * exceptions, but in this case it keeps the parsing code small. */
- } catch (ArrayIndexOutOfBoundsException e) {
- /* TODO Handle me. In theory, we shouldn't catch runtime
- * exceptions, but in this case it keeps the parsing code small. */
+ throw new DescriptorParseException("Illegal values in line '" + line
+ + "'.");
}
}
- private byte[] voteBytes;
- public byte[] getRawDescriptorBytes() {
- return this.voteBytes;
+ private void parseClientVersionsLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.recommendedClientVersions = this.parseClientOrServerVersions(
+ line, parts);
}
- private int networkStatusVersion;
- public int getNetworkStatusVersion() {
- return this.networkStatusVersion;
+ private void parseServerVersionsLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.recommendedServerVersions = this.parseClientOrServerVersions(
+ line, parts);
}
- private List<Integer> consensusMethods = new ArrayList<Integer>();
- public List<Integer> getConsensusMethods() {
- return this.consensusMethods;
+ private List<String> parseClientOrServerVersions(String line,
+ String[] parts) throws DescriptorParseException {
+ List<String> result = new ArrayList<String>();
+ if (parts.length == 1) {
+ return result;
+ } else if (parts.length > 2) {
+ throw new DescriptorParseException("Illegal versions line '" + line
+ + "'.");
+ }
+ String[] versions = parts[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);
+ }
+ return result;
}
- private long publishedMillis;
- public long getPublishedMillis() {
- return this.publishedMillis;
+ private void parseKnownFlagsLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length < 2) {
+ throw new DescriptorParseException("No known flags in line '" + line
+ + "'.");
+ }
+ this.knownFlags = new TreeSet<String>();
+ for (int i = 1; i < parts.length; i++) {
+ this.knownFlags.add(parts[i]);
+ }
}
- private long validAfterMillis;
- public long getValidAfterMillis() {
- return this.validAfterMillis;
+ private void parseParamsLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.consensusParams = ParseHelper.parseKeyValuePairs(line, parts, 1);
}
- private long freshUntilMillis;
- public long getFreshUntilMillis() {
- return this.freshUntilMillis;
+ protected void parseDirSource(byte[] dirSourceBytes)
+ throws DescriptorParseException {
+ try {
+ BufferedReader br = new BufferedReader(new StringReader(
+ new String(dirSourceBytes)));
+ String line;
+ boolean skipCrypto = false;
+ while ((line = br.readLine()) != null) {
+ String[] parts = line.split(" ");
+ String keyword = parts[0];
+ if (keyword.equals("dir-source")) {
+ this.parseDirSourceLine(line, parts);
+ } else if (keyword.equals("contact")) {
+ this.parseContactLine(line, parts);
+ } else if (keyword.equals("dir-key-certificate-version")) {
+ this.parseDirKeyCertificateVersionLine(line, parts);
+ } else if (keyword.equals("fingerprint")) {
+ /* Nothing new to learn here. We already know the fingerprint
+ * from the dir-source line. */
+ } else if (keyword.equals("legacy-key")) {
+ this.parseLegacyKeyLine(line, parts);
+ } else if (keyword.equals("dir-key-published")) {
+ this.parseDirKeyPublished(line, parts);
+ } else if (keyword.equals("dir-key-expires")) {
+ this.parseDirKeyExpiresLine(line, parts);
+ } else if (keyword.equals("dir-identity-key") ||
+ keyword.equals("dir-signing-key") ||
+ keyword.equals("dir-key-crosscert") ||
+ keyword.equals("dir-key-certification")) {
+ } else if (line.startsWith("-----BEGIN")) {
+ skipCrypto = true;
+ } else if (line.equals("-----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. */
+ 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 long validUntilMillis;
- public long getValidUntilMillis() {
- return this.validUntilMillis;
+ private void parseDirSourceLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.nickname = ParseHelper.parseNickname(line, parts[1]);
+ this.identity = ParseHelper.parseTwentyByteHexString(line, parts[2]);
+ this.address = ParseHelper.parseIpv4Address(line, parts[4]);
+ this.dirPort = ParseHelper.parsePort(line, parts[5]);
+ this.orPort = ParseHelper.parsePort(line, parts[6]);
}
- private List<Long> votingDelay = new ArrayList<Long>();
- public List<Long> getVotingDelay() {
- return new ArrayList<Long>(this.votingDelay);
+ private void parseContactLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (line.length() > "contact ".length()) {
+ this.contactLine = line.substring("contact ".length());
+ } else {
+ this.contactLine = "";
+ }
}
- private List<String> recommendedClientVersions;
- public List<String> getRecommendedClientVersions() {
- return this.recommendedClientVersions == null ? null :
- new ArrayList<String>(this.recommendedClientVersions);
+ private void parseDirKeyCertificateVersionLine(String line,
+ String[] parts) throws DescriptorParseException {
+ if (parts.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line
+ + "' in vote.");
+ }
+ try {
+ this.dirKeyCertificateVersion = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException e) {
+ throw new DescriptorParseException("Illegal dir key certificate "
+ + "version in line '" + line + "'.");
+ }
+ if (this.dirKeyCertificateVersion < 1) {
+ throw new DescriptorParseException("Illegal dir key certificate "
+ + "version in line '" + line + "'.");
+ }
}
- private List<String> recommendedServerVersions;
- public List<String> getRecommendedServerVersions() {
- return this.recommendedServerVersions == null ? null :
- new ArrayList<String>(this.recommendedServerVersions);
+ private void parseLegacyKeyLine(String line, String[] parts)
+ throws DescriptorParseException {
+ if (parts.length != 2) {
+ throw new DescriptorParseException("Illegal line '" + line + "'.");
+ }
+ this.legacyKey = ParseHelper.parseTwentyByteHexString(line, parts[2]);
}
- private SortedSet<String> knownFlags = new TreeSet<String>();
- public SortedSet<String> getKnownFlags() {
- return new TreeSet<String>(this.knownFlags);
+ private void parseDirKeyPublished(String line, String[] parts)
+ throws DescriptorParseException {
+ this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line,
+ parts, 1, 2);
}
- private SortedMap<String, Integer> consensusParams =
- new TreeMap<String, Integer>();
- public SortedMap<String, Integer> getConsensusParams() {
- return new TreeMap<String, Integer>(this.consensusParams);
+ private void parseDirKeyExpiresLine(String line, String[] parts)
+ throws DescriptorParseException {
+ this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line,
+ parts, 1, 2);
+ }
+
+ protected void parseFooter(byte[] footerBytes) {
+ /* There is nothing in the footer that we'd want to parse. */
}
private String nickname;
@@ -296,6 +379,11 @@ public class RelayNetworkStatusVoteImpl
return this.dirKeyCertificateVersion;
}
+ private String legacyKey;
+ public String getLegacyKey() {
+ return this.legacyKey;
+ }
+
private long dirKeyPublishedMillis;
public long getDirKeyPublishedMillis() {
return this.dirKeyPublishedMillis;
@@ -311,54 +399,67 @@ public class RelayNetworkStatusVoteImpl
return this.signingKeyDigest;
}
- private SortedMap<String, NetworkStatusEntry> statusEntries =
- new TreeMap<String, NetworkStatusEntry>();
- public SortedMap<String, NetworkStatusEntry> getStatusEntries() {
- return new TreeMap<String, NetworkStatusEntry>(this.statusEntries);
+ private int networkStatusVersion;
+ public int getNetworkStatusVersion() {
+ return this.networkStatusVersion;
}
- public boolean containsStatusEntry(String fingerprint) {
- return this.statusEntries.containsKey(fingerprint);
+
+ private List<Integer> consensusMethods;
+ public List<Integer> getConsensusMethods() {
+ return new ArrayList<Integer>(this.consensusMethods);
}
- public NetworkStatusEntry getStatusEntry(String fingerprint) {
- return this.statusEntries.get(fingerprint);
+
+ private long publishedMillis;
+ public long getPublishedMillis() {
+ return this.publishedMillis;
}
+ private long validAfterMillis;
+ public long getValidAfterMillis() {
+ return this.validAfterMillis;
+ }
- private SortedMap<String, String> directorySignatures =
- new TreeMap<String, String>();
- public SortedMap<String, String> getDirectorySignatures() {
- return new TreeMap<String, String>(this.directorySignatures);
+ private long freshUntilMillis;
+ public long getFreshUntilMillis() {
+ return this.freshUntilMillis;
}
- private void checkConsistency() {
- if (this.networkStatusVersion == 0) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'network-status-version' line.");
- }
- if (this.validAfterMillis == 0L) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'valid-after' line.");
- }
- if (this.freshUntilMillis == 0L) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'fresh-until' line.");
- }
- if (this.validUntilMillis == 0L) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'valid-until' line.");
- }
- if (this.votingDelay.isEmpty()) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'voting-delay' line.");
- }
- if (this.knownFlags.isEmpty()) {
- throw new RuntimeException("Consensus doesn't contain a "
- + "'known-flags' line.");
- }
- if (this.statusEntries.isEmpty()) {
- throw new RuntimeException("Consensus doesn't contain any 'r' "
- + "lines.");
- }
+ private long validUntilMillis;
+ public long getValidUntilMillis() {
+ return this.validUntilMillis;
+ }
+
+ private long voteSeconds;
+ public long getVoteSeconds() {
+ return this.voteSeconds;
+ }
+
+ private long distSeconds;
+ public long getDistSeconds() {
+ return this.distSeconds;
+ }
+
+ private List<String> recommendedClientVersions;
+ public List<String> getRecommendedClientVersions() {
+ return this.recommendedClientVersions == null ? null :
+ new ArrayList<String>(this.recommendedClientVersions);
+ }
+
+ private List<String> recommendedServerVersions;
+ public List<String> getRecommendedServerVersions() {
+ return this.recommendedServerVersions == null ? null :
+ new ArrayList<String>(this.recommendedServerVersions);
+ }
+
+ private SortedSet<String> knownFlags;
+ public SortedSet<String> getKnownFlags() {
+ return new TreeSet<String>(this.knownFlags);
+ }
+
+ private SortedMap<String, Integer> consensusParams;
+ public SortedMap<String, Integer> getConsensusParams() {
+ return this.consensusParams == null ? null:
+ new TreeMap<String, Integer>(this.consensusParams);
}
}
diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
new file mode 100644
index 0000000..8b62480
--- /dev/null
+++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java
@@ -0,0 +1,574 @@
+/* Copyright 2011 The Tor Project
+ * See LICENSE for licensing information */
+package org.torproject.descriptor.impl;
+
+import org.torproject.descriptor.RelayNetworkStatusVote;
+import org.torproject.descriptor.impl.RelayNetworkStatusVoteImpl;
+
+import java.util.*;
+
+import org.junit.*;
+import org.junit.rules.*;
+import static org.junit.Assert.*;
+
+/* TODO Add tests (and possibly a DirSourceLineBuilder) to test the
+ * following methods:
+ * - String getNickname();
+ * - String getIdentity();
+ * - String getAddress();
+ * - int getDirport();
+ * - int getOrport();
+ * - String getContactLine();
+ * - int getDirKeyCertificateVersion();
+ * - String getLegacyKey();
+ * - long getDirKeyPublishedMillis();
+ * - long getDirKeyExpiresMillis();
+ * - String getSigningKeyDigest();
+ */
+
+/* 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
+ * in the directory header. */
+public class RelayNetworkStatusVoteImplTest {
+
+ /* Helper class to build a vote based on default data and modifications
+ * requested by test methods. */
+ private static class VoteBuilder {
+ private String networkStatusVersionLine = "network-status-version 3";
+ private static RelayNetworkStatusVote
+ createWithNetworkStatusVersionLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.networkStatusVersionLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String voteStatusLine = "vote-status vote";
+ private static RelayNetworkStatusVote
+ createWithVoteStatusLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.voteStatusLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String consensusMethodsLine =
+ "consensus-methods 1 2 3 4 5 6 7 8 9 10 11";
+ private static RelayNetworkStatusVote
+ createWithConsensusMethodsLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.consensusMethodsLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String publishedLine = "published 2011-11-30 08:50:01";
+ private static RelayNetworkStatusVote
+ createWithPublishedLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.publishedLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String validAfterLine = "valid-after 2011-11-30 09:00:00";
+ private static RelayNetworkStatusVote
+ createWithValidAfterLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.validAfterLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String freshUntilLine = "fresh-until 2011-11-30 10:00:00";
+ private static RelayNetworkStatusVote
+ createWithFreshUntilLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.freshUntilLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String validUntilLine = "valid-until 2011-11-30 12:00:00";
+ private static RelayNetworkStatusVote
+ createWithValidUntilLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.validUntilLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String votingDelayLine = "voting-delay 300 300";
+ private static RelayNetworkStatusVote
+ createWithVotingDelayLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.votingDelayLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String clientVersionsLine = "client-versions 0.2.1.31,"
+ + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha";
+ private static RelayNetworkStatusVote
+ createWithClientVersionsLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.clientVersionsLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String serverVersionsLine = "server-versions 0.2.1.31,"
+ + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha";
+ private static RelayNetworkStatusVote
+ createWithServerVersionsLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.serverVersionsLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String knownFlagsLine = "known-flags Authority BadExit Exit "
+ + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid";
+ private static RelayNetworkStatusVote
+ createWithKnownFlagsLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.knownFlagsLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private String paramsLine = "params "
+ + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 "
+ + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 "
+ + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 "
+ + "cbtquantile=80 circwindow=1000 refuseunknownexits=1";
+ private static RelayNetworkStatusVote
+ createWithParamsLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.paramsLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private List<String> dirSources = new ArrayList<String>();
+ private List<String> statusEntries = new ArrayList<String>();
+ private String directoryFooterLine = "directory-footer";
+ private static RelayNetworkStatusVote
+ createWithDirectoryFooterLine(String line)
+ throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ vb.directoryFooterLine = line;
+ return new RelayNetworkStatusVoteImpl(vb.buildVote());
+ }
+ private List<String> directorySignatures = new ArrayList<String>();
+ private VoteBuilder() {
+ this.dirSources.add("dir-source urras "
+ + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 "
+ + "208.83.223.34 443 80\n"
+ + "contact 4096R/E012B42D Jacob Appelbaum "
+ + "<jacob(a)appelbaum.net>\n"
+ + "dir-key-certificate-version 3\n"
+ + "fingerprint 80550987E1D626E3EBA5E5E75A458DE0626D088C\n"
+ + "dir-key-published 2011-04-27 05:34:37\n"
+ + "dir-key-expires 2012-04-27 05:34:37\n"
+ + "dir-identity-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIIBigKCAYEAtKpuLgVK25sfScjsxfVU1ljofrDygt9GP7bNJl/rghX42KUT"
+ + "975W\nrGp/fbhF7p+FcKCzNOhJFINQbRf/5E3lN8mzoamIU43QqQ9RRVf946"
+ + "88UsazVsAN\nNVT0v9J0cr387WePjenRuIE1MmiP0nmw/XdvbPTayqax7VYl"
+ + "cUMXGHl8DnWix1EN\nRwmeig+JBte0JS12oo2HG9zcSfjLJVjY6ZmvRrVycX"
+ + "iRxGc/JgNlSrV4cxUNykaB\nJ6pO6J499OZfQu7m1vAPTENrVJ4yEfRGRwFI"
+ + "Y+d/s8BkKcaiWtXAfTe31uBI6GEH\nmS3HNu1JVSuoaUiQIvVYDLMfBvMcNy"
+ + "Ax97UT1l6E0Tn6a7pgChrquGwXai1xGzk8\n58aXwdSFoFBSTCkyemopq5H2"
+ + "0p/nkPAO0pHL1kTvcaKz9CEj4XcKm+kOmzejYmIa\nkbWNcRpXPiUZ+xmwGt"
+ + "sq30xrzqiONmERkxqlmf7bVQPFvh3Kz6hGcmTBhTbHSe9h\nzDgmdaTNn3EH"
+ + "AgMBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----\n"
+ + "dir-signing-key\n"
+ + "-----BEGIN RSA PUBLIC KEY-----\n"
+ + "MIGJAoGBAN05qyHFQlTqykMP8yLuD4G2UuYulD4Xs8iSX5uqF+WGsUA1E4zZ"
+ + "h48h\nDFj8+drFiCu3EqhMEmVG4ACtJK2uz6D1XohUsbPWTR6LSnWJ8q6/zf"
+ + "TSLumBGsN7\nPUXyMNjwRKL6UvrcbYk1d2mRBLO7SAP/sFW5fHhIBVeLIWrz"
+ + "Q19rAgMBAAE=\n"
+ + "-----END RSA PUBLIC KEY-----\n"
+ + "dir-key-crosscert\n"
+ + "-----BEGIN ID SIGNATURE-----\n"
+ + "rPBFn6IJ6TvAHj4pSwlg+RTn1fP89JGSVa08wuyJr5dAvZsdakQXvRjamT9o"
+ + "JUaZ\nnY5Rl/tRlGuSQ0BglTPPKoXdKERK0FUr9f0EKrQy7NDUgE2j9losiR"
+ + "uyKzhA3neZ\nK4yF8bhqAwM51u7fzAhIjNeRif9c04rhFJJCseco84w=\n"
+ + "-----END ID SIGNATURE-----\n"
+ + "dir-key-certification\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "hPSh6FuohNF5ccjiMbkvr8cZJwGFuL11cNtwN9k0X3pUdFZVATIEkqBe7z+r"
+ + "E2PX\nPw+BGyC6wYAieoTVIhLpwKqd7DXLYjuhPZ28+7MQaDL01AqYeRp5PT"
+ + "01PxrFY0Um\nlVf95uqUitgvDT76Ne4ExWk6UvGlYB9OBgBySZz8VWe9znoM"
+ + "qb0uHn/p8IzqTApT\nAxRWXBHClntMeRqtGxaj8DcdJFn8yMxQiZG7MfDg2s"
+ + "q2ySPJyGlN+neoVDVhZiDI\n9LTNmw60gWlUp2erFeam8Mo1ZBC4DPNjQEm6"
+ + "QeHZFZMkhDuO6SwS/FL712A42+Co\nYtMaVot/p5FG2ZSBXbgl2XP5/z8ELn"
+ + "pmXqMbPAoWRo3BPNSJkIQQNog8Q5ZrK+av\nZDw5eGPltGKsXOkvuzIMM8nB"
+ + "eAnDPDgYvzrIFObEGbvY/P8mzVAZxp3Yz+sRtNel\nC1SWz/Fx+Saex5oI7D"
+ + "J3xtSD4XqKb/wYwZFT8IxDYq1t2tFXdHxd4QPRVcvc0zYC\n"
+ + "-----END SIGNATURE-----");
+ this.statusEntries.add("r right2privassy3 "
+ + "ADQ6gCT3DiFHKPDFr3rODBUI8HM lJY5Vf7kXec+VdkGW2flEsfkFC8 "
+ + "2011-11-12 00:03:40 50.63.8.215 9023 0\n"
+ + "s Exit Fast Guard Running Stable Valid\n"
+ + "opt v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)\n"
+ + "w Bandwidth=297 Measured=73\n"
+ + "p accept 80,1194,1220,1293,1500,1533,1677,1723,1863,"
+ + "2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321,"
+ + "4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000,"
+ + "8008,8074,8080,8087-8088,8443,8888,9418,9999-10000,19294,"
+ + "19638\n"
+ + "m 8,9,10,11 sha256=9ciEx9t0McXk9A06I7qwN7pxuNOdpCP64RV/6cx2Zkc");
+ this.directorySignatures.add("directory-signature "
+ + "80550987E1D626E3EBA5E5E75A458DE0626D088C "
+ + "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19\n"
+ + "-----BEGIN SIGNATURE-----\n"
+ + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn"
+ + "F3Yh\nqXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi"
+ + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n"
+ + "-----END SIGNATURE-----");
+ }
+ private byte[] buildVote() {
+ StringBuilder sb = new StringBuilder();
+ this.appendHeader(sb);
+ this.appendBody(sb);
+ this.appendFooter(sb);
+ return sb.toString().getBytes();
+ }
+ private void appendHeader(StringBuilder sb) {
+ if (this.networkStatusVersionLine != null) {
+ sb.append(this.networkStatusVersionLine + "\n");
+ }
+ if (this.voteStatusLine != null) {
+ sb.append(this.voteStatusLine + "\n");
+ }
+ if (this.consensusMethodsLine != null) {
+ sb.append(this.consensusMethodsLine + "\n");
+ }
+ if (this.publishedLine != null) {
+ sb.append(this.publishedLine + "\n");
+ }
+ if (this.validAfterLine != null) {
+ sb.append(this.validAfterLine + "\n");
+ }
+ if (this.freshUntilLine != null) {
+ sb.append(this.freshUntilLine + "\n");
+ }
+ if (this.validUntilLine != null) {
+ sb.append(this.validUntilLine + "\n");
+ }
+ if (this.votingDelayLine != null) {
+ sb.append(this.votingDelayLine + "\n");
+ }
+ if (this.clientVersionsLine != null) {
+ sb.append(this.clientVersionsLine + "\n");
+ }
+ if (this.serverVersionsLine != null) {
+ sb.append(this.serverVersionsLine + "\n");
+ }
+ if (this.knownFlagsLine != null) {
+ sb.append(this.knownFlagsLine + "\n");
+ }
+ if (this.paramsLine != null) {
+ sb.append(this.paramsLine + "\n");
+ }
+ for (String dirSource : this.dirSources) {
+ sb.append(dirSource + "\n");
+ }
+ }
+ private void appendBody(StringBuilder sb) {
+ for (String statusEntry : this.statusEntries) {
+ sb.append(statusEntry + "\n");
+ }
+ }
+ private void appendFooter(StringBuilder sb) {
+ if (this.directoryFooterLine != null) {
+ sb.append(this.directoryFooterLine + "\n");
+ }
+ for (String directorySignature : this.directorySignatures) {
+ sb.append(directorySignature + "\n");
+ }
+ }
+ }
+
+ @Test()
+ public void testSampleVote() throws DescriptorParseException {
+ VoteBuilder vb = new VoteBuilder();
+ RelayNetworkStatusVote vote =
+ new RelayNetworkStatusVoteImpl(vb.buildVote());
+ assertEquals(3, vote.getNetworkStatusVersion());
+ List<Integer> consensusMethods = Arrays.asList(
+ new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
+ assertEquals(vote.getConsensusMethods(), consensusMethods);
+ assertEquals(1322643001000L, vote.getPublishedMillis());
+ assertEquals(1322643600000L, vote.getValidAfterMillis());
+ assertEquals(1322647200000L, vote.getFreshUntilMillis());
+ assertEquals(1322654400000L, vote.getValidUntilMillis());
+ assertEquals(300L, vote.getVoteSeconds());
+ assertEquals(300L, vote.getDistSeconds());
+ assertTrue(vote.getKnownFlags().contains("Running"));
+ assertEquals(30000, (int) vote.getConsensusParams().get(
+ "CircuitPriorityHalflifeMsec"));
+ assertEquals("Tor 0.2.1.29 (r8e9b25e6c7a2e70c)",
+ vote.getStatusEntry("00343A8024F70E214728F0C5AF7ACE0C1508F073").
+ getVersion());
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionNoLine()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionNewLine()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version 3\n");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionNewLineSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version 3\n ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionPrefixLineAtChar()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "@vote\nnetwork-status-version 3");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionPrefixLine()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "directory-footer\nnetwork-status-version 3");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionPrefixLinePoundChar()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "#vote\nnetwork-status-version 3");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionNoSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionOneSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersion42()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version 42");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionFourtyTwo()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ "network-status-version FourtyTwo");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testNetworkStatusVersionSpaceBefore()
+ throws DescriptorParseException {
+ VoteBuilder.createWithNetworkStatusVersionLine(
+ " network-status-version 3");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusSpaceBefore() throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine(" vote-status vote");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusNoSpace() throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine("vote-status");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusOneSpace() throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine("vote-status ");
+ }
+
+ @Test()
+ public void testVoteStatusVoteOneSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine("vote-status vote ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusConsensus() throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine("vote-status consensus");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVoteStatusTheMagicVoteStatus()
+ throws DescriptorParseException {
+ VoteBuilder.createWithVoteStatusLine(
+ "vote-status TheMagicVoteStatus");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodNoLine()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodNoSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine("consensus-methods");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodOneSpace()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine("consensus-methods ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodEleven()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine(
+ "consensus-methods eleven");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodMinusOne()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine("consensus-methods -1");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodNinePeriod()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine("consensus-methods "
+ + "999999999999999999999999999999999999999999999999999999999999");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testConsensusMethodTwoLines()
+ throws DescriptorParseException {
+ VoteBuilder.createWithConsensusMethodsLine(
+ "consensus-method 1\nconsensus-method 1");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testPublishedNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithPublishedLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidAfterNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithValidAfterLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidAfterNoSpace() throws DescriptorParseException {
+ VoteBuilder.createWithValidAfterLine("valid-after");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidAfterOneSpace() throws DescriptorParseException {
+ VoteBuilder.createWithValidAfterLine("valid-after ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidAfterLongAgo() throws DescriptorParseException {
+ VoteBuilder.createWithValidAfterLine("valid-after long ago");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidAfterFeb30() throws DescriptorParseException {
+ VoteBuilder.createWithValidAfterLine(
+ "valid-after 2011-02-30 09:00:00");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFreshUntilNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithFreshUntilLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testFreshUntilAroundTen() throws DescriptorParseException {
+ VoteBuilder.createWithFreshUntilLine(
+ "fresh-until 2011-11-30 around ten");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testValidUntilTomorrowMorning()
+ throws DescriptorParseException {
+ VoteBuilder.createWithValidUntilLine(
+ "valid-until tomorrow morning");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelayNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelayNoSpace() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine("voting-delay");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelayOneSpace() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine("voting-delay ");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelayTriple() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine(
+ "voting-delay 300 300 300");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelaySingle() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine("voting-delay 300");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testVotingDelayOneTwo() throws DescriptorParseException {
+ VoteBuilder.createWithVotingDelayLine("voting-delay one two");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testClientVersionsComma() throws DescriptorParseException {
+ VoteBuilder.createWithClientVersionsLine("client-versions ,");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testClientVersionsCommaVersion()
+ throws DescriptorParseException {
+ VoteBuilder.createWithClientVersionsLine(
+ "client-versions ,0.2.2.34");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testKnownFlagsNoLine() throws DescriptorParseException {
+ VoteBuilder.createWithKnownFlagsLine(null);
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testKnownFlagsNoSpace() throws DescriptorParseException {
+ VoteBuilder.createWithKnownFlagsLine("known-flags");
+ }
+
+ @Test(expected = DescriptorParseException.class)
+ public void testKnownFlagsOneSpace() throws DescriptorParseException {
+ VoteBuilder.createWithKnownFlagsLine("known-flags ");
+ }
+}
+
1
0

r25259: {website} add a menu listing of contact options at the top. (website/trunk/about/en)
by Andrew Lewman 15 Dec '11
by Andrew Lewman 15 Dec '11
15 Dec '11
Author: phobos
Date: 2011-12-15 19:43:46 +0000 (Thu, 15 Dec 2011)
New Revision: 25259
Modified:
website/trunk/about/en/contact.wml
Log:
add a menu listing of contact options at the top.
Modified: website/trunk/about/en/contact.wml
===================================================================
--- website/trunk/about/en/contact.wml 2011-12-15 15:43:05 UTC (rev 25258)
+++ website/trunk/about/en/contact.wml 2011-12-15 19:43:46 UTC (rev 25259)
@@ -12,6 +12,13 @@
</div>
<div id="maincol">
<h2>Tor: Contact</h2>
+ <ul>
+ <li><a href="<page about/contact>#email">Email</a></li>
+ <li><a href="<page about/contact>#identica">Identi.ca</a></li>
+ <li><a href="<page about/contact>#irc">IRC</a></li>
+ <li><a href="<page about/contact>#livechat">Live Chat</a></li>
+ <li><a href="<page about/contact>#phone">Telephone</a></li>
+ </ul>
<h3>Email</h3>
<a id="email"></a>
1
0
Author: phobos
Date: 2011-12-15 15:43:05 +0000 (Thu, 15 Dec 2011)
New Revision: 25258
Modified:
website/trunk/.htaccess
Log:
add a contact rewrite.
Modified: website/trunk/.htaccess
===================================================================
--- website/trunk/.htaccess 2011-12-15 07:17:09 UTC (rev 25257)
+++ website/trunk/.htaccess 2011-12-15 15:43:05 UTC (rev 25258)
@@ -37,6 +37,7 @@
# other (feel free to categorize)
RewriteRule ^people(.*) /about/corepeople$1 [R=301,L]
RewriteRule ^donate/$ /donate/donate [R=301,L]
+RewriteRule ^contact(.*) /about/contact$1 [R=301,L]
# Download websites
RewriteRule ^download/$ /download/download [R=301,L]
1
0

[metrics-lib/master] Skip all crypto parts when counting keywords.
by karsten@torproject.org 15 Dec '11
by karsten@torproject.org 15 Dec '11
15 Dec '11
commit da64a0275745cf63694886011484f6bde2a6866e
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Thu Dec 15 15:37:12 2011 +0100
Skip all crypto parts when counting keywords.
---
.../descriptor/impl/NetworkStatusImpl.java | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index f7488c6..6ca546e 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -272,9 +272,9 @@ public abstract class NetworkStatusImpl {
String line;
boolean skipCrypto = false;
while ((line = br.readLine()) != null) {
- if (line.startsWith("-----BEGIN SIGNATURE-----")) {
+ if (line.startsWith("-----BEGIN")) {
skipCrypto = true;
- } else if (line.equals("-----END SIGNATURE-----")) {
+ } else if (line.equals("-----END")) {
skipCrypto = false;
} else if (!skipCrypto) {
String keyword = line.split(" ", -1)[0];
1
0

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