commit c2a0dbf8bf100a19660ad512b88d93f3d7c18a1e Author: Karsten Loesing karsten.loesing@gmx.net Date: Tue Feb 5 14:49:28 2013 +0100
Parse the new flag-thresholds line in votes. --- .../descriptor/RelayNetworkStatusVote.java | 41 ++++++++ .../descriptor/impl/NetworkStatusEntryImpl.java | 4 +- .../torproject/descriptor/impl/ParseHelper.java | 26 ++++-- .../impl/RelayNetworkStatusConsensusImpl.java | 8 +- .../impl/RelayNetworkStatusVoteImpl.java | 102 +++++++++++++++++++- .../impl/RelayNetworkStatusVoteImplTest.java | 82 ++++++++++++++++ 6 files changed, 246 insertions(+), 17 deletions(-)
diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java index eda0cef..98abb14 100644 --- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java +++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java @@ -44,6 +44,47 @@ public interface RelayNetworkStatusVote extends Descriptor { /* Return known relay flags. */ public SortedSet<String> getKnownFlags();
+ /* Return the minimum uptime in seconds that this authority requires for + * assigning the Stable flag, or -1 if the authority doesn't report this + * value. */ + public long getStableUptime(); + + /* Return the minimum MTBF (mean time between failure) that this + * authority requires for assigning the Stable flag, or -1 if the + * authority doesn't report this value. */ + public long getStableMtbf(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Fast flag, or -1 if the authority doesn't report this + * value. */ + public long getFastBandwidth(); + + /* Return the minimum WFU (weighted fractional uptime) in percent that + * this authority requires for assigning the Guard flag, or -1.0 if the + * authority doesn't report this value. */ + public double getGuardWfu(); + + /* Return the minimum time in seconds that this authority needs to know + * about a relay before assigning the Guard flag, or -1 if the authority + * doesn't report this information. */ + public long getGuardTk(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can be guards, or -1 if the + * authority doesn't report this value. */ + public long getGuardBandwidthIncludingExits(); + + /* Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can not be guards, or -1 if the + * authority doesn't report this value. */ + public long getGuardBandwidthExcludingExits(); + + /* Return 1 if the authority has measured enough MTBF info to use the + * MTBF requirement instead of the uptime requirement for assigning the + * Stable flag, 0 if not, or -1 if the authority doesn't report this + * information. */ + public int getEnoughMtbfInfo(); + /* Return consensus parameters. */ public SortedMap<String, Integer> getConsensusParams();
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java index 121f66e..901e17e 100644 --- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java +++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java @@ -149,8 +149,8 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { private void parseWLine(String line, String[] parts) throws DescriptorParseException { this.parsedAtMostOnceKeyword("w"); - SortedMap<String, Integer> pairs = ParseHelper.parseKeyValuePairs( - line, parts, 1, "="); + SortedMap<String, Integer> pairs = + ParseHelper.parseKeyValueIntegerPairs(line, parts, 1, "="); if (pairs.isEmpty()) { throw new DescriptorParseException("Illegal line '" + line + "'."); } diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java index 61d1eb0..1b7eff2 100644 --- a/src/org/torproject/descriptor/impl/ParseHelper.java +++ b/src/org/torproject/descriptor/impl/ParseHelper.java @@ -6,6 +6,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; @@ -182,10 +183,10 @@ public class ParseHelper { return hexString.toUpperCase(); }
- public static SortedMap<String, Integer> parseKeyValuePairs(String line, - String[] parts, int startIndex, String separatorString) + public static SortedMap<String, String> parseKeyValueStringPairs( + String line, String[] parts, int startIndex, String separatorString) throws DescriptorParseException { - SortedMap<String, Integer> result = new TreeMap<String, Integer>(); + SortedMap<String, String> result = new TreeMap<String, String>(); for (int i = startIndex; i < parts.length; i++) { String pair = parts[i]; String[] pairParts = pair.split(separatorString); @@ -193,11 +194,22 @@ public class ParseHelper { throw new DescriptorParseException("Illegal key-value pair in " + "line '" + line + "'."); } - String pairName = pairParts[0]; + result.put(pairParts[0], pairParts[1]); + } + return result; + } + + public static SortedMap<String, Integer> parseKeyValueIntegerPairs( + String line, String[] parts, int startIndex, String separatorString) + throws DescriptorParseException { + SortedMap<String, Integer> result = new TreeMap<String, Integer>(); + SortedMap<String, String> keyValueStringPairs = + ParseHelper.parseKeyValueStringPairs(line, parts, startIndex, + separatorString); + for (Map.Entry<String, String> e : keyValueStringPairs.entrySet()) { try { - int pairValue = Integer.parseInt(pairParts[1]); - result.put(pairName, pairValue); - } catch (NumberFormatException e) { + result.put(e.getKey(), Integer.parseInt(e.getValue())); + } catch (NumberFormatException ex) { throw new DescriptorParseException("Illegal value in line '" + line + "'."); } diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java index 35cb316..dad5bc3 100644 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java +++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java @@ -257,14 +257,14 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
private void parseParamsLine(String line, String[] parts) throws DescriptorParseException { - this.consensusParams = ParseHelper.parseKeyValuePairs(line, parts, 1, - "="); + this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); }
private void parseBandwidthWeightsLine(String line, String[] parts) throws DescriptorParseException { - this.bandwidthWeights = ParseHelper.parseKeyValuePairs(line, parts, 1, - "="); + this.bandwidthWeights = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); }
private String consensusDigest; diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java index 3695074..c5ba980 100644 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java +++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.SortedMap; @@ -48,14 +49,28 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl + "dir-key-certification,directory-signature").split(","))); this.checkExactlyOnceKeywords(exactlyOnceKeywords); Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList(( - "client-versions,server-versions,params,contact,legacy-key," - + "dir-key-crosscert,dir-address,directory-footer").split(","))); + "client-versions,server-versions,flag-thresholds,params,contact," + + "legacy-key,dir-key-crosscert,dir-address,directory-footer"). + split(","))); this.checkAtMostOnceKeywords(atMostOnceKeywords); this.checkFirstKeyword("network-status-version"); }
protected void parseHeader(byte[] headerBytes) throws DescriptorParseException { + /* Initialize flag-thresholds values here for the case that the vote + * doesn't contain those values. Initializing them in the constructor + * or when declaring variables wouldn't work, because those parts are + * evaluated later and would overwrite everything we parse here. */ + this.stableUptime = -1L; + this.stableMtbf = -1L; + this.fastBandwidth = -1L; + this.guardWfu = -1.0; + this.guardTk = -1L; + this.guardBandwidthIncludingExits = -1L; + this.guardBandwidthExcludingExits = -1L; + this.enoughMtbfInfo = -1; + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); boolean skipCrypto = false; /* TODO Parse crypto parts. */ while (s.hasNext()) { @@ -84,6 +99,8 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl this.parseServerVersionsLine(line, parts); } else if (keyword.equals("known-flags")) { this.parseKnownFlagsLine(line, parts); + } else if (keyword.equals("flag-thresholds")) { + this.parseFlagThresholdsLine(line, parts); } else if (keyword.equals("params")) { this.parseParamsLine(line, parts); } else if (keyword.equals("dir-source")) { @@ -247,10 +264,47 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl } }
+ private void parseFlagThresholdsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No flag thresholds in line '" + + line + "'."); + } + SortedMap<String, String> flagThresholds = + ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); + try { + for (Map.Entry<String, String> e : flagThresholds.entrySet()) { + if (e.getKey().equals("stable-uptime")) { + this.stableUptime = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("stable-mtbf")) { + this.stableMtbf = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("fast-speed")) { + this.fastBandwidth = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-wfu")) { + this.guardWfu = Double.parseDouble(e.getValue(). + replaceAll("%", "")); + } else if (e.getKey().equals("guard-tk")) { + this.guardTk = Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-bw-inc-exits")) { + this.guardBandwidthIncludingExits = + Long.parseLong(e.getValue()); + } else if (e.getKey().equals("guard-bw-exc-exits")) { + this.guardBandwidthExcludingExits = + Long.parseLong(e.getValue()); + } else if (e.getKey().equals("enough-mtbf")) { + this.enoughMtbfInfo = Integer.parseInt(e.getValue()); + } + } + } catch (NumberFormatException ex) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + private void parseParamsLine(String line, String[] parts) throws DescriptorParseException { - this.consensusParams = ParseHelper.parseKeyValuePairs(line, parts, 1, - "="); + this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); }
private void parseDirSourceLine(String line, String[] parts) @@ -465,6 +519,46 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl return new TreeSet<String>(this.knownFlags); }
+ private long stableUptime; + public long getStableUptime() { + return this.stableUptime; + } + + private long stableMtbf; + public long getStableMtbf() { + return this.stableMtbf; + } + + private long fastBandwidth; + public long getFastBandwidth() { + return this.fastBandwidth; + } + + private double guardWfu; + public double getGuardWfu() { + return this.guardWfu; + } + + private long guardTk; + public long getGuardTk() { + return this.guardTk; + } + + private long guardBandwidthIncludingExits; + public long getGuardBandwidthIncludingExits() { + return this.guardBandwidthIncludingExits; + } + + private long guardBandwidthExcludingExits; + public long getGuardBandwidthExcludingExits() { + return this.guardBandwidthExcludingExits; + } + + private int enoughMtbfInfo; + public int getEnoughMtbfInfo() { + return this.enoughMtbfInfo; + } + private SortedMap<String, Integer> consensusParams; public SortedMap<String, Integer> getConsensusParams() { return this.consensusParams == null ? null: diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java index 7fbdb7d..b3fbcfd 100644 --- a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java +++ b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java @@ -115,6 +115,17 @@ public class RelayNetworkStatusVoteImplTest { vb.knownFlagsLine = line; return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); } + private String flagThresholdsLine = "flag-thresholds " + + "stable-uptime=693369 stable-mtbf=153249 fast-speed=40960 " + + "guard-wfu=94.669% guard-tk=691200 guard-bw-inc-exits=174080 " + + "guard-bw-exc-exits=184320 enough-mtbf=1"; + private static RelayNetworkStatusVote + createWithFlagThresholdsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.flagThresholdsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } private String paramsLine = "params " + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 " + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 " @@ -388,6 +399,9 @@ public class RelayNetworkStatusVoteImplTest { if (this.knownFlagsLine != null) { sb.append(this.knownFlagsLine + "\n"); } + if (this.flagThresholdsLine != null) { + sb.append(this.flagThresholdsLine + "\n"); + } if (this.paramsLine != null) { sb.append(this.paramsLine + "\n"); } @@ -749,6 +763,74 @@ public class RelayNetworkStatusVoteImplTest { VoteBuilder.createWithKnownFlagsLine("known-flags "); }
+ @Test() + public void testFlagThresholdsLine() throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + RelayNetworkStatusVote vote = + new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + assertEquals(693369L, vote.getStableUptime()); + assertEquals(153249L, vote.getStableMtbf()); + assertEquals(40960L, vote.getFastBandwidth()); + assertEquals(94.669, vote.getGuardWfu(), 0.001); + assertEquals(691200L, vote.getGuardTk()); + assertEquals(174080L, vote.getGuardBandwidthIncludingExits()); + assertEquals(184320L, vote.getGuardBandwidthExcludingExits()); + assertEquals(1, vote.getEnoughMtbfInfo()); + } + + @Test() + public void testFlagThresholdsNoLine() throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithFlagThresholdsLine(null); + assertEquals(-1L, vote.getStableUptime()); + assertEquals(-1L, vote.getStableMtbf()); + assertEquals(-1L, vote.getFastBandwidth()); + assertEquals(-1.0, vote.getGuardWfu(), 0.001); + assertEquals(-1L, vote.getGuardTk()); + assertEquals(-1L, vote.getGuardBandwidthIncludingExits()); + assertEquals(-1L, vote.getGuardBandwidthExcludingExits()); + assertEquals(-1, vote.getEnoughMtbfInfo()); + } + + @Test() + public void testFlagThresholdsAllZeroes() + throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds " + + "stable-uptime=0 stable-mtbf=0 fast-speed=0 guard-wfu=0.0% " + + "guard-tk=0 guard-bw-inc-exits=0 guard-bw-exc-exits=0 " + + "enough-mtbf=0"); + assertEquals(0L, vote.getStableUptime()); + assertEquals(0L, vote.getStableMtbf()); + assertEquals(0L, vote.getFastBandwidth()); + assertEquals(0.0, vote.getGuardWfu(), 0.001); + assertEquals(0L, vote.getGuardTk()); + assertEquals(0L, vote.getGuardBandwidthIncludingExits()); + assertEquals(0L, vote.getGuardBandwidthExcludingExits()); + assertEquals(0, vote.getEnoughMtbfInfo()); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdsNoSpace() + throws DescriptorParseException { + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds"); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdsOneSpace() + throws DescriptorParseException { + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds "); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdDuplicate() + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.flagThresholdsLine = vb.flagThresholdsLine + "\n" + + vb.flagThresholdsLine; + new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + @Test(expected = DescriptorParseException.class) public void testNicknameMissing() throws DescriptorParseException { VoteBuilder.createWithDirSourceLine("dir-source "
tor-commits@lists.torproject.org