mirror of
https://github.com/torproject/metrics-lib.git
synced 2024-11-30 12:40:51 +00:00
Make consensus parsing a lot more robust.
This commit is contained in:
parent
358b92b019
commit
228d6564f8
@ -22,6 +22,9 @@ public interface DirSourceEntry {
|
||||
/* Return the ORPort. */
|
||||
public int getOrPort();
|
||||
|
||||
/* Return whether the dir-source was created using a legacy key. */
|
||||
public boolean isLegacy();
|
||||
|
||||
/* Return the contact line. */
|
||||
public String getContactLine();
|
||||
|
||||
|
@ -30,16 +30,29 @@ public interface NetworkStatusEntry {
|
||||
/* Return the DirPort. */
|
||||
public int getDirPort();
|
||||
|
||||
/* Return the relay flags. */
|
||||
/* Return the relay flags or null if the status entry didn't contain any
|
||||
* relay flags. */
|
||||
public SortedSet<String> getFlags();
|
||||
|
||||
/* Return the Tor software version. */
|
||||
/* Return the Tor software version or null if the status entry didn't
|
||||
* contain a version line. */
|
||||
public String getVersion();
|
||||
|
||||
/* Return the bandwidth weight line. */
|
||||
public String getBandwidth();
|
||||
/* Return the bandwidth weight or -1L if the status entry didn't
|
||||
* contain a bandwidth line. */
|
||||
public long getBandwidth();
|
||||
|
||||
/* Return the port summary line. */
|
||||
public String getPorts();
|
||||
/* Return the measured bandwidth or -1L if the status entry didn't
|
||||
* contain a bandwidth line or didn't contain a Measured= keyword in its
|
||||
* bandwidth line. */
|
||||
public long getMeasured();
|
||||
|
||||
/* Return the default policy of the port summary or null if the status
|
||||
* entry didn't contain a port summary line. */
|
||||
public String getDefaultPolicy();
|
||||
|
||||
/* Return the port list of the port summary or null if the status entry
|
||||
* didn't contain a port summary line. */
|
||||
public String getPortList();
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,11 @@ public interface RelayNetworkStatusConsensus 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 consensus doesn't
|
||||
* contain recommended server versions. */
|
||||
@ -38,8 +41,9 @@ public interface RelayNetworkStatusConsensus extends Descriptor {
|
||||
/* Return known relay flags. */
|
||||
public SortedSet<String> getKnownFlags();
|
||||
|
||||
/* Return consensus parameters. */
|
||||
public SortedMap<String, String> getConsensusParams();
|
||||
/* Return consensus parameters or null if the consensus doesn't contain
|
||||
* consensus parameters. */
|
||||
public SortedMap<String, Integer> getConsensusParams();
|
||||
|
||||
/* Return dir-source entries representing the directories of which
|
||||
* votes are contained in this consensus. */
|
||||
@ -58,7 +62,8 @@ public interface RelayNetworkStatusConsensus extends Descriptor {
|
||||
/* Return directory signatures. */
|
||||
public SortedMap<String, String> getDirectorySignatures();
|
||||
|
||||
/* Return bandwidth weights. */
|
||||
public SortedMap<String, String> getBandwidthWeights();
|
||||
/* Return bandwidth weights or null if the consensus doesn't contain
|
||||
* bandwidth weights. */
|
||||
public SortedMap<String, Integer> getBandwidthWeights();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
/* Copyright 2011 The Tor Project
|
||||
* See LICENSE for licensing information */
|
||||
package org.torproject.descriptor.impl;
|
||||
|
||||
public class DescriptorParseException extends Exception {
|
||||
protected DescriptorParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.text.ParseException;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import org.torproject.descriptor.DirSourceEntry;
|
||||
|
||||
public class DirSourceEntryImpl implements DirSourceEntry {
|
||||
@ -16,30 +18,110 @@ public class DirSourceEntryImpl implements DirSourceEntry {
|
||||
}
|
||||
|
||||
protected DirSourceEntryImpl(byte[] dirSourceEntryBytes)
|
||||
throws ParseException {
|
||||
throws DescriptorParseException {
|
||||
this.dirSourceEntryBytes = dirSourceEntryBytes;
|
||||
this.initializeKeywords();
|
||||
this.parseDirSourceEntryBytes();
|
||||
this.checkKeywords();
|
||||
}
|
||||
|
||||
private SortedSet<String> exactlyOnceKeywords, atMostOnceKeywords;
|
||||
private void initializeKeywords() {
|
||||
this.exactlyOnceKeywords = new TreeSet<String>();
|
||||
this.exactlyOnceKeywords.add("dir-source");
|
||||
this.exactlyOnceKeywords.add("vote-digest");
|
||||
this.atMostOnceKeywords = new TreeSet<String>();
|
||||
this.atMostOnceKeywords.add("contact");
|
||||
}
|
||||
|
||||
private void parsedExactlyOnceKeyword(String keyword)
|
||||
throws DescriptorParseException {
|
||||
if (!this.exactlyOnceKeywords.contains(keyword)) {
|
||||
throw new DescriptorParseException("Duplicate '" + keyword
|
||||
+ "' line in dir-source.");
|
||||
}
|
||||
this.exactlyOnceKeywords.remove(keyword);
|
||||
}
|
||||
|
||||
private void parsedAtMostOnceKeyword(String keyword)
|
||||
throws DescriptorParseException {
|
||||
if (!this.atMostOnceKeywords.contains(keyword)) {
|
||||
throw new DescriptorParseException("Duplicate " + keyword + "line "
|
||||
+ "in dir-source.");
|
||||
}
|
||||
this.atMostOnceKeywords.remove(keyword);
|
||||
}
|
||||
|
||||
private void checkKeywords() throws DescriptorParseException {
|
||||
if (!this.exactlyOnceKeywords.isEmpty()) {
|
||||
throw new DescriptorParseException("dir-source does not contain a '"
|
||||
+ this.exactlyOnceKeywords.first() + "' line.");
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDirSourceEntryBytes()
|
||||
throws DescriptorParseException {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new StringReader(
|
||||
new String(this.dirSourceEntryBytes)));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith("dir-source ")) {
|
||||
String[] parts = line.split(" ");
|
||||
this.nickname = parts[1];
|
||||
this.identity = parts[2];
|
||||
this.ip = parts[4];
|
||||
this.dirPort = Integer.parseInt(parts[5]);
|
||||
this.orPort = Integer.parseInt(parts[6]);
|
||||
} else if (line.startsWith("contact ")) {
|
||||
this.contactLine = line.substring("contact ".length());
|
||||
} else if (line.startsWith("vote-digest ")) {
|
||||
this.voteDigest = line.split(" ")[1];
|
||||
if (line.startsWith("dir-source")) {
|
||||
this.parseDirSourceLine(line);
|
||||
} else if (line.startsWith("contact")) {
|
||||
this.parseContactLine(line);
|
||||
} else if (line.startsWith("vote-digest")) {
|
||||
this.parseVoteDigestLine(line);
|
||||
} else {
|
||||
/* TODO Should we really throw an exception here? */
|
||||
throw new DescriptorParseException("Unknown line '" + line
|
||||
+ "' in dir-source entry.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
/* TODO This cannot happen, right? */
|
||||
throw new RuntimeException("Internal error: Ran into an "
|
||||
+ "IOException while parsing a String in memory. Something's "
|
||||
+ "really wrong.", e);
|
||||
}
|
||||
/* TODO Implement some plausibility tests. */
|
||||
}
|
||||
|
||||
private void parseDirSourceLine(String line)
|
||||
throws DescriptorParseException {
|
||||
this.parsedExactlyOnceKeyword("dir-source");
|
||||
String[] parts = line.split(" ");
|
||||
String nickname = parts[1];
|
||||
if (nickname.endsWith("-legacy")) {
|
||||
nickname = nickname.substring(0, nickname.length()
|
||||
- "-legacy".length());
|
||||
this.isLegacy = true;
|
||||
this.parsedExactlyOnceKeyword("vote-digest");
|
||||
}
|
||||
this.nickname = ParseHelper.parseNickname(line, nickname);
|
||||
this.identity = ParseHelper.parseTwentyByteHexString(line, parts[2]);
|
||||
this.ip = ParseHelper.parseIpv4Address(line, parts[4]);
|
||||
this.dirPort = ParseHelper.parsePort(line, parts[5]);
|
||||
this.orPort = ParseHelper.parsePort(line, parts[6]);
|
||||
}
|
||||
|
||||
private void parseContactLine(String line)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("contact");
|
||||
if (line.length() > "contact ".length()) {
|
||||
this.contactLine = line.substring("contact ".length());
|
||||
} else {
|
||||
this.contactLine = "";
|
||||
}
|
||||
}
|
||||
|
||||
private void parseVoteDigestLine(String line)
|
||||
throws DescriptorParseException {
|
||||
this.parsedExactlyOnceKeyword("vote-digest");
|
||||
String[] parts = line.split(" ");
|
||||
if (parts.length != 2) {
|
||||
throw new DescriptorParseException("Invalid line '" + line + "'.");
|
||||
}
|
||||
this.voteDigest = ParseHelper.parseTwentyByteHexString(line,
|
||||
parts[1]);
|
||||
}
|
||||
|
||||
private String nickname;
|
||||
@ -52,6 +134,11 @@ public class DirSourceEntryImpl implements DirSourceEntry {
|
||||
return this.identity;
|
||||
}
|
||||
|
||||
private boolean isLegacy;
|
||||
public boolean isLegacy() {
|
||||
return this.isLegacy;
|
||||
}
|
||||
|
||||
private String ip;
|
||||
public String getIp() {
|
||||
return this.ip;
|
||||
|
@ -5,14 +5,10 @@ package org.torproject.descriptor.impl;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeSet;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.torproject.descriptor.NetworkStatusEntry;
|
||||
|
||||
public class NetworkStatusEntryImpl implements NetworkStatusEntry {
|
||||
@ -22,60 +18,163 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
|
||||
return this.statusEntryBytes;
|
||||
}
|
||||
|
||||
private static SimpleDateFormat dateTimeFormat;
|
||||
static {
|
||||
dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
protected NetworkStatusEntryImpl(byte[] statusEntryBytes)
|
||||
throws DescriptorParseException {
|
||||
this.statusEntryBytes = statusEntryBytes;
|
||||
this.initializeKeywords();
|
||||
this.parseStatusEntryBytes();
|
||||
}
|
||||
|
||||
protected NetworkStatusEntryImpl(byte[] statusEntryBytes)
|
||||
throws ParseException {
|
||||
this.statusEntryBytes = statusEntryBytes;
|
||||
private SortedSet<String> atMostOnceKeywords;
|
||||
private void initializeKeywords() {
|
||||
this.atMostOnceKeywords = new TreeSet<String>();
|
||||
this.atMostOnceKeywords.add("s");
|
||||
this.atMostOnceKeywords.add("v");
|
||||
this.atMostOnceKeywords.add("w");
|
||||
this.atMostOnceKeywords.add("p");
|
||||
this.atMostOnceKeywords.add("m");
|
||||
}
|
||||
|
||||
private void parsedAtMostOnceKeyword(String keyword)
|
||||
throws DescriptorParseException {
|
||||
if (!this.atMostOnceKeywords.contains(keyword)) {
|
||||
throw new DescriptorParseException("Duplicate '" + keyword
|
||||
+ "' line in status entry.");
|
||||
}
|
||||
this.atMostOnceKeywords.remove(keyword);
|
||||
}
|
||||
|
||||
private void parseStatusEntryBytes() throws DescriptorParseException {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new StringReader(
|
||||
new String(this.statusEntryBytes)));
|
||||
String line;
|
||||
String line = br.readLine();
|
||||
if (line == null || !line.startsWith("r ")) {
|
||||
throw new DescriptorParseException("Status entry must start with "
|
||||
+ "an r line.");
|
||||
}
|
||||
String[] rLineParts = line.split(" ");
|
||||
this.parseRLine(line, rLineParts);
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith("r ")) {
|
||||
String[] parts = line.split(" ");
|
||||
if (parts.length < 9) {
|
||||
throw new RuntimeException("r line '" + line + "' has fewer "
|
||||
+ "space-separated elements than expected.");
|
||||
}
|
||||
this.nickname = parts[1];
|
||||
this.fingerprint = Hex.encodeHexString(Base64.decodeBase64(
|
||||
parts[2] + "=")).toLowerCase();
|
||||
this.descriptor = Hex.encodeHexString(Base64.decodeBase64(
|
||||
parts[3] + "=")).toLowerCase();
|
||||
this.publishedMillis = dateTimeFormat.parse(parts[4] + " "
|
||||
+ parts[5]).getTime();
|
||||
this.address = parts[6];
|
||||
this.orPort = Integer.parseInt(parts[7]);
|
||||
this.dirPort = Integer.parseInt(parts[8]);
|
||||
} else if (line.equals("s")) {
|
||||
/* No flags to add. */
|
||||
} else if (line.startsWith("s ")) {
|
||||
this.flags.addAll(Arrays.asList(line.substring("s ".length()).
|
||||
split(" ")));
|
||||
} else if (line.startsWith("v ") || line.startsWith("opt v")) {
|
||||
this.version = line.substring(
|
||||
line.startsWith("v ") ? "v ".length() : "opt v".length());
|
||||
} else if (line.startsWith("w ")) {
|
||||
this.bandwidth = line.substring("w ".length());
|
||||
} else if (line.startsWith("p ")) {
|
||||
this.ports = line.substring(2);
|
||||
} else if (line.startsWith("m ")) {
|
||||
/* TODO Parse m lines in votes. */
|
||||
String[] parts = !line.startsWith("opt ") ? line.split(" ") :
|
||||
line.substring("opt ".length()).split(" ");
|
||||
String keyword = parts[0];
|
||||
if (keyword.equals("s")) {
|
||||
this.parseSLine(line, parts);
|
||||
} else if (keyword.equals("v")) {
|
||||
this.parseVLine(line, parts);
|
||||
} else if (keyword.equals("w")) {
|
||||
this.parseWLine(line, parts);
|
||||
} else if (keyword.equals("p")) {
|
||||
this.parsePLine(line, parts);
|
||||
} else if (keyword.equals("m")) {
|
||||
this.parseMLine(line, parts);
|
||||
} else {
|
||||
throw new RuntimeException("Unknown line '" + line + "' in "
|
||||
+ "status entry.");
|
||||
/* 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("Unknown line '" + line
|
||||
+ "' in status entry.");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
/* TODO Do something. */
|
||||
throw new RuntimeException("Internal error: Ran into an "
|
||||
+ "IOException while parsing a String in memory. Something's "
|
||||
+ "really wrong.", e);
|
||||
}
|
||||
/* TODO Add some plausibility checks, like if we have a nickname
|
||||
* etc. */
|
||||
}
|
||||
|
||||
private void parseRLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
if (parts.length < 9) {
|
||||
throw new RuntimeException("r line '" + line + "' has fewer "
|
||||
+ "space-separated elements than expected.");
|
||||
}
|
||||
this.nickname = ParseHelper.parseNickname(line, parts[1]);
|
||||
this.fingerprint = ParseHelper.parseTwentyByteBase64String(line,
|
||||
parts[2]);
|
||||
this.descriptor = ParseHelper.parseTwentyByteBase64String(line,
|
||||
parts[3]);
|
||||
this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts,
|
||||
4, 5);
|
||||
this.address = ParseHelper.parseIpv4Address(line, parts[6]);
|
||||
this.orPort = ParseHelper.parsePort(line, parts[7]);
|
||||
this.dirPort = ParseHelper.parsePort(line, parts[8]);
|
||||
}
|
||||
|
||||
private void parseSLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("s");
|
||||
this.flags = new TreeSet<String>();
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
this.flags.add(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseVLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("v");
|
||||
String noOptLine = line;
|
||||
if (noOptLine.startsWith("opt ")) {
|
||||
noOptLine = noOptLine.substring(4);
|
||||
}
|
||||
if (noOptLine.length() < 3) {
|
||||
throw new DescriptorParseException("Invalid line '" + line + "' in "
|
||||
+ "status entry.");
|
||||
} else {
|
||||
this.version = noOptLine.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseWLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("w");
|
||||
SortedMap<String, Integer> pairs = ParseHelper.parseKeyValuePairs(
|
||||
line, parts, 1);
|
||||
if (pairs.isEmpty()) {
|
||||
throw new DescriptorParseException("Illegal line '" + line + "'.");
|
||||
}
|
||||
if (pairs.containsKey("Bandwidth")) {
|
||||
this.bandwidth = pairs.remove("Bandwidth");
|
||||
}
|
||||
if (pairs.containsKey("Measured")) {
|
||||
this.measured = pairs.remove("Measured");
|
||||
}
|
||||
if (!pairs.isEmpty()) {
|
||||
throw new DescriptorParseException("Unknown key-value pair in "
|
||||
+ "line '" + line + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void parsePLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("p");
|
||||
boolean isValid = true;
|
||||
if (parts.length != 3) {
|
||||
isValid = false;
|
||||
} else if (!parts[1].equals("accept") && !parts[1].equals("reject")) {
|
||||
isValid = false;
|
||||
} else {
|
||||
this.defaultPolicy = parts[1];
|
||||
this.portList = parts[2];
|
||||
String[] ports = parts[2].split(",", -1);
|
||||
for (int i = 0; i < ports.length; i++) {
|
||||
if (ports[i].length() < 1) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new DescriptorParseException("Illegal line '" + line + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void parseMLine(String line, String[] parts)
|
||||
throws DescriptorParseException {
|
||||
this.parsedAtMostOnceKeyword("m");
|
||||
/* TODO Implement parsing of m lines in votes. Try to find where m
|
||||
* lines are specified first. */
|
||||
}
|
||||
|
||||
private String nickname;
|
||||
@ -113,7 +212,7 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
|
||||
return this.dirPort;
|
||||
}
|
||||
|
||||
private SortedSet<String> flags = new TreeSet<String>();
|
||||
private SortedSet<String> flags;
|
||||
public SortedSet<String> getFlags() {
|
||||
return new TreeSet<String>(this.flags);
|
||||
}
|
||||
@ -123,14 +222,24 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
private String bandwidth;
|
||||
public String getBandwidth() {
|
||||
private long bandwidth = -1L;
|
||||
public long getBandwidth() {
|
||||
return this.bandwidth;
|
||||
}
|
||||
|
||||
private String ports;
|
||||
public String getPorts() {
|
||||
return this.ports;
|
||||
private long measured = -1L;
|
||||
public long getMeasured() {
|
||||
return this.measured;
|
||||
}
|
||||
|
||||
private String defaultPolicy;
|
||||
public String getDefaultPolicy() {
|
||||
return this.defaultPolicy;
|
||||
}
|
||||
|
||||
private String portList;
|
||||
public String getPortList() {
|
||||
return this.portList;
|
||||
}
|
||||
}
|
||||
|
||||
|
147
src/org/torproject/descriptor/impl/ParseHelper.java
Normal file
147
src/org/torproject/descriptor/impl/ParseHelper.java
Normal file
@ -0,0 +1,147 @@
|
||||
/* Copyright 2011 The Tor Project
|
||||
* See LICENSE for licensing information */
|
||||
package org.torproject.descriptor.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TimeZone;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
public class ParseHelper {
|
||||
|
||||
private static Pattern ipv4Pattern =
|
||||
Pattern.compile("^[0-9\\.]{7,15}$");
|
||||
public static String parseIpv4Address(String line, String address)
|
||||
throws DescriptorParseException {
|
||||
boolean isValid = true;
|
||||
if (!ipv4Pattern.matcher(address).matches()) {
|
||||
isValid = false;
|
||||
} else {
|
||||
String[] parts = address.split("\\.", -1);
|
||||
if (parts.length != 4) {
|
||||
isValid = false;
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
try {
|
||||
int octetValue = Integer.parseInt(parts[i]);
|
||||
if (octetValue < 0 || octetValue > 255) {
|
||||
isValid = false;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new DescriptorParseException("'" + address + "' in line '"
|
||||
+ line + "' is not a valid IPv4 address.");
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
public static int parsePort(String line, String portString)
|
||||
throws DescriptorParseException {
|
||||
int port = -1;
|
||||
try {
|
||||
port = Integer.parseInt(portString);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DescriptorParseException("'" + portString + "' in line '"
|
||||
+ line + "' is not a valid port number.");
|
||||
}
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new DescriptorParseException("'" + portString + "' in line '"
|
||||
+ line + "' is not a valid port number.");
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
private static SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
static {
|
||||
dateTimeFormat.setLenient(false);
|
||||
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
public static long parseTimestampAtIndex(String line, String[] parts,
|
||||
int dateIndex, int timeIndex) throws DescriptorParseException {
|
||||
if (dateIndex >= parts.length || timeIndex >= parts.length) {
|
||||
throw new DescriptorParseException("Line '" + line + "' does not "
|
||||
+ "contain a timestamp at the expected position.");
|
||||
}
|
||||
long result = -1L;
|
||||
try {
|
||||
result = dateTimeFormat.parse(
|
||||
parts[dateIndex] + " " + parts[timeIndex]).getTime();
|
||||
} catch (ParseException e) {
|
||||
/* Leave result at -1L. */
|
||||
}
|
||||
if (result < 0L || result > 2000000000000L) {
|
||||
throw new DescriptorParseException("Illegal timestamp format in "
|
||||
+ "line '" + line + "'.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Pattern twentyByteHexPattern =
|
||||
Pattern.compile("^[0-9A-F]{40}$");
|
||||
public static String parseTwentyByteHexString(String line,
|
||||
String hexString) throws DescriptorParseException {
|
||||
if (!twentyByteHexPattern.matcher(hexString).matches()) {
|
||||
throw new DescriptorParseException("Illegal hex string in line '"
|
||||
+ line + "'.");
|
||||
}
|
||||
return hexString;
|
||||
}
|
||||
|
||||
public static SortedMap<String, Integer> parseKeyValuePairs(String line,
|
||||
String[] parts, int startIndex) throws DescriptorParseException {
|
||||
SortedMap<String, Integer> result = new TreeMap<String, Integer>();
|
||||
for (int i = startIndex; i < parts.length; i++) {
|
||||
String pair = parts[i];
|
||||
String[] pairParts = pair.split("=");
|
||||
if (pairParts.length != 2) {
|
||||
throw new DescriptorParseException("Illegal key-value pair in "
|
||||
+ "line '" + line + "'.");
|
||||
}
|
||||
String pairName = pairParts[0];
|
||||
try {
|
||||
int pairValue = Integer.parseInt(pairParts[1]);
|
||||
result.put(pairName, pairValue);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DescriptorParseException("Illegal value in line '"
|
||||
+ line + "'.");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Pattern nicknamePattern =
|
||||
Pattern.compile("^[0-9a-zA-Z]{1,19}$");
|
||||
public static String parseNickname(String line, String nickname)
|
||||
throws DescriptorParseException {
|
||||
if (!nicknamePattern.matcher(nickname).matches()) {
|
||||
throw new DescriptorParseException("Illegal nickname in line '"
|
||||
+ line + "'.");
|
||||
}
|
||||
return nickname;
|
||||
}
|
||||
|
||||
private static Pattern base64Pattern =
|
||||
Pattern.compile("^[0-9a-zA-Z+/]{27}$");
|
||||
public static String parseTwentyByteBase64String(String line,
|
||||
String base64String) throws DescriptorParseException {
|
||||
if (!base64Pattern.matcher(base64String).matches()) {
|
||||
throw new DescriptorParseException("'" + base64String
|
||||
+ "' in line '" + line + "' is not a valid base64-encoded "
|
||||
+ "20-byte value.");
|
||||
}
|
||||
return Hex.encodeHexString(Base64.decodeBase64(base64String + "=")).
|
||||
toUpperCase();
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ 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.DirSourceEntry;
|
||||
import org.torproject.descriptor.NetworkStatusEntry;
|
||||
@ -41,185 +39,322 @@ public class RelayNetworkStatusConsensusImpl
|
||||
}
|
||||
byte[] descBytes = new byte[end - start];
|
||||
System.arraycopy(consensusBytes, start, descBytes, 0, end - start);
|
||||
RelayNetworkStatusConsensus parsedConsensus =
|
||||
new RelayNetworkStatusConsensusImpl(descBytes);
|
||||
parsedConsensuses.add(parsedConsensus);
|
||||
start = end;
|
||||
try {
|
||||
RelayNetworkStatusConsensus parsedConsensus =
|
||||
new RelayNetworkStatusConsensusImpl(descBytes);
|
||||
parsedConsensuses.add(parsedConsensus);
|
||||
} catch (DescriptorParseException e) {
|
||||
/* TODO Handle this error somehow. */
|
||||
System.err.println("Failed to parse consensus. Skipping.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return parsedConsensuses;
|
||||
}
|
||||
|
||||
protected RelayNetworkStatusConsensusImpl(byte[] consensusBytes) {
|
||||
protected RelayNetworkStatusConsensusImpl(byte[] consensusBytes)
|
||||
throws DescriptorParseException {
|
||||
this.consensusBytes = consensusBytes;
|
||||
this.initializeKeywords();
|
||||
this.parseConsensusBytes();
|
||||
this.checkConsistency();
|
||||
/* TODO Find a way to handle parse and consistency-check problems. */
|
||||
this.checkKeywords();
|
||||
}
|
||||
|
||||
private void parseConsensusBytes() {
|
||||
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)
|
||||
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;
|
||||
SimpleDateFormat dateTimeFormat = new SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
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;
|
||||
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 consensus")) {
|
||||
throw new RuntimeException("Line '" + line + "' indicates "
|
||||
+ "that this string is not a consensus. Aborting "
|
||||
+ "parsing.");
|
||||
}
|
||||
} else if (line.startsWith("consensus-method ")) {
|
||||
this.consensusMethod = Integer.parseInt(line.substring(
|
||||
"consensus-method ".length()));
|
||||
} 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 = new TreeSet<String>(
|
||||
Arrays.asList(line.split(" ")[1].split(",")));
|
||||
} else if (line.startsWith("server-versions ")) {
|
||||
this.recommendedServerVersions = new TreeSet<String>(
|
||||
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];
|
||||
String paramValue = param.split("=")[1];
|
||||
this.consensusParams.put(paramName, paramValue);
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("dir-source ") ||
|
||||
line.startsWith("r ") || line.equals("directory-footer")) {
|
||||
/* TODO Add code for parsing legacy dir sources. */
|
||||
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")) {
|
||||
this.parseVoteStatusLine(line, parts);
|
||||
} else if (keyword.equals("consensus-method")) {
|
||||
this.parseConsensusMethodLine(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 if (keyword.equals("dir-source") || keyword.equals("r") ||
|
||||
keyword.equals("directory-footer")) {
|
||||
if (dirSourceEntryLines != null) {
|
||||
DirSourceEntry dirSourceEntry = new DirSourceEntryImpl(
|
||||
dirSourceEntryLines.toString().getBytes());
|
||||
this.dirSourceEntries.put(dirSourceEntry.getIdentity(),
|
||||
dirSourceEntry);
|
||||
this.parseDirSourceEntryLines(dirSourceEntryLines.toString());
|
||||
dirSourceEntryLines = null;
|
||||
}
|
||||
if (statusEntryLines != null) {
|
||||
NetworkStatusEntryImpl statusEntry =
|
||||
new NetworkStatusEntryImpl(
|
||||
statusEntryLines.toString().getBytes());
|
||||
this.statusEntries.put(statusEntry.getFingerprint(),
|
||||
statusEntry);
|
||||
this.parseStatusEntryLines(statusEntryLines.toString());
|
||||
statusEntryLines = null;
|
||||
}
|
||||
if (line.startsWith("dir-source ")) {
|
||||
dirSourceEntryLines = new StringBuilder();
|
||||
dirSourceEntryLines.append(line + "\n");
|
||||
} else if (line.startsWith("r ")) {
|
||||
statusEntryLines = new StringBuilder();
|
||||
statusEntryLines.append(line + "\n");
|
||||
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.");
|
||||
}
|
||||
} else if (line.startsWith("contact ") ||
|
||||
line.startsWith("vote-digest ")) {
|
||||
dirSourceEntryLines.append(line + "\n");
|
||||
} else if (line.startsWith("s ") || line.equals("s") ||
|
||||
line.startsWith("v ") || line.startsWith("w ") ||
|
||||
line.startsWith("p ")) {
|
||||
statusEntryLines.append(line + "\n");
|
||||
} else if (line.startsWith("bandwidth-weights ")) {
|
||||
if (line.length() > "bandwidth-weights ".length()) {
|
||||
for (String weight : line.substring("bandwidth-weights ".
|
||||
length()).split(" ")) {
|
||||
String weightName = weight.split("=")[0];
|
||||
String weightValue = weight.split("=")[1];
|
||||
this.bandwidthWeights.put(weightName, weightValue);
|
||||
}
|
||||
} 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.");
|
||||
}
|
||||
|
||||
} else if (line.startsWith("directory-signature ")) {
|
||||
String[] parts = line.split(" ");
|
||||
String identity = parts[1];
|
||||
String signingKeyDigest = parts[2];
|
||||
this.directorySignatures.put(identity, signingKeyDigest);
|
||||
statusEntryLines.append(line + "\n");
|
||||
} 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) {
|
||||
throw new RuntimeException("Unrecognized line '" + line + "'.");
|
||||
/* 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. */
|
||||
} 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. */
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConsistency() {
|
||||
if (this.networkStatusVersion == 0) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'network-status-version' line.");
|
||||
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.");
|
||||
}
|
||||
if (this.consensusMethod == 0) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'consensus-method' line.");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
if (this.validAfterMillis == 0L) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'valid-after' line.");
|
||||
try {
|
||||
this.consensusMethod = Integer.parseInt(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DescriptorParseException("Illegal consensus method "
|
||||
+ "number in line '" + line + "'.");
|
||||
}
|
||||
if (this.freshUntilMillis == 0L) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'fresh-until' line.");
|
||||
if (this.consensusMethod < 1) {
|
||||
throw new DescriptorParseException("Illegal consensus method "
|
||||
+ "number in line '" + line + "'.");
|
||||
}
|
||||
if (this.validUntilMillis == 0L) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'valid-until' line.");
|
||||
}
|
||||
|
||||
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 + "'.");
|
||||
}
|
||||
if (this.votingDelay.isEmpty()) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'voting-delay' line.");
|
||||
try {
|
||||
this.voteSeconds = Long.parseLong(parts[1]);
|
||||
this.distSeconds = Long.parseLong(parts[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new DescriptorParseException("Illegal values in line '" + line
|
||||
+ "'.");
|
||||
}
|
||||
if (this.knownFlags.isEmpty()) {
|
||||
throw new RuntimeException("Consensus doesn't contain a "
|
||||
+ "'known-flags' line.");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private SortedSet<String> parseClientOrServerVersions(String line,
|
||||
String[] parts) throws DescriptorParseException {
|
||||
SortedSet<String> result = new TreeSet<String>();
|
||||
if (parts.length == 1) {
|
||||
return result;
|
||||
} else if (parts.length > 2) {
|
||||
throw new DescriptorParseException("Illegal versions line '" + line
|
||||
+ "'.");
|
||||
}
|
||||
if (this.dirSourceEntries.isEmpty()) {
|
||||
throw new RuntimeException("Consensus doesn't contain any "
|
||||
+ "'dir-source' lines.");
|
||||
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);
|
||||
}
|
||||
if (this.statusEntries.isEmpty()) {
|
||||
throw new RuntimeException("Consensus doesn't contain any 'r' "
|
||||
+ "lines.");
|
||||
return result;
|
||||
}
|
||||
|
||||
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
|
||||
+ "'.");
|
||||
}
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
this.knownFlags.add(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -252,9 +387,14 @@ public class RelayNetworkStatusConsensusImpl
|
||||
return this.validUntilMillis;
|
||||
}
|
||||
|
||||
private List<Long> votingDelay = new ArrayList<Long>();
|
||||
public List<Long> getVotingDelay() {
|
||||
return new ArrayList<Long>(this.votingDelay);
|
||||
private long voteSeconds;
|
||||
public long getVoteSeconds() {
|
||||
return this.voteSeconds;
|
||||
}
|
||||
|
||||
private long distSeconds;
|
||||
public long getDistSeconds() {
|
||||
return this.distSeconds;
|
||||
}
|
||||
|
||||
private SortedSet<String> recommendedClientVersions;
|
||||
@ -274,10 +414,10 @@ public class RelayNetworkStatusConsensusImpl
|
||||
return new TreeSet<String>(this.knownFlags);
|
||||
}
|
||||
|
||||
private SortedMap<String, String> consensusParams =
|
||||
new TreeMap<String, String>();
|
||||
public SortedMap<String, String> getConsensusParams() {
|
||||
return new TreeMap<String, String>(this.consensusParams);
|
||||
private SortedMap<String, Integer> consensusParams;
|
||||
public SortedMap<String, Integer> getConsensusParams() {
|
||||
return this.consensusParams == null ? null:
|
||||
new TreeMap<String, Integer>(this.consensusParams);
|
||||
}
|
||||
|
||||
private SortedMap<String, DirSourceEntry> dirSourceEntries =
|
||||
@ -304,10 +444,10 @@ public class RelayNetworkStatusConsensusImpl
|
||||
return new TreeMap<String, String>(this.directorySignatures);
|
||||
}
|
||||
|
||||
private SortedMap<String, String> bandwidthWeights =
|
||||
new TreeMap<String, String>();
|
||||
public SortedMap<String, String> getBandwidthWeights() {
|
||||
return new TreeMap<String, String>(this.bandwidthWeights);
|
||||
private SortedMap<String, Integer> bandwidthWeights;
|
||||
public SortedMap<String, Integer> getBandwidthWeights() {
|
||||
return this.bandwidthWeights == null ? null :
|
||||
new TreeMap<String, Integer>(this.bandwidthWeights);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,11 +149,16 @@ public class RelayNetworkStatusVoteImpl
|
||||
} else if (line.startsWith("r ") ||
|
||||
line.equals("directory-footer")) {
|
||||
if (statusEntryLines != null) {
|
||||
NetworkStatusEntryImpl statusEntry =
|
||||
new NetworkStatusEntryImpl(
|
||||
statusEntryLines.toString().getBytes());
|
||||
this.statusEntries.put(statusEntry.getFingerprint(),
|
||||
statusEntry);
|
||||
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 ")) {
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user