Commit 2bcd6bb0 authored by Karsten Loesing's avatar Karsten Loesing
Browse files

Parse new protocol versions lines.

parent 74ee2145
......@@ -13,6 +13,9 @@
the application can decide at any time to stop consuming
descriptors without having to worry about the reader thread not
being done.
- Parse "proto" lines in server descriptors, "pr" lines in status
entries, and "(recommended|required)-(client|relay)-protocols"
lines in consensuses and votes.
# Changes in version 1.5.0 - 2016-10-19
......
......@@ -5,6 +5,7 @@ package org.torproject.descriptor;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
/**
......@@ -122,6 +123,14 @@ public interface NetworkStatusEntry {
*/
public String getVersion();
/**
* Return the version numbers of all protocols supported by this server, or
* null if the status entry does not specify supported protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getProtocols();
/**
* Return the bandwidth weight of this server or -1 if the status entry
* didn't contain a bandwidth line.
......
......@@ -112,6 +112,42 @@ public interface RelayNetworkStatusConsensus extends Descriptor {
*/
public List<String> getRecommendedClientVersions();
/**
* Return the version numbers of all protocols that clients should support,
* or null if the consensus does not contain an opinion about protocol
* versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRecommendedClientProtocols();
/**
* Return the version numbers of all protocols that relays should support,
* or null if the consensus does not contain an opinion about protocol
* versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRecommendedRelayProtocols();
/**
* Return the version numbers of all protocols that clients must support,
* or null if the consensus does not contain an opinion about protocol
* versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRequiredClientProtocols();
/**
* Return the version numbers of all protocols that relays must support,
* or null if the consensus does not contain an opinion about protocol
* versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRequiredRelayProtocols();
/**
* Return a list of software packages and their versions together with a
* URL and one or more digests in the format <code>PackageName Version
......
......@@ -104,6 +104,38 @@ public interface RelayNetworkStatusVote extends Descriptor {
*/
public List<String> getRecommendedClientVersions();
/**
* Return the version numbers of all protocols that clients should support,
* or null if the vote does not contain an opinion about protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRecommendedClientProtocols();
/**
* Return the version numbers of all protocols that relays should support,
* or null if the vote does not contain an opinion about protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRecommendedRelayProtocols();
/**
* Return the version numbers of all protocols that clients must support,
* or null if the vote does not contain an opinion about protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRequiredClientProtocols();
/**
* Return the version numbers of all protocols that relays must support,
* or null if the vote does not contain an opinion about protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getRequiredRelayProtocols();
/**
* Return a list of software packages and their versions together with a
* URL and one or more digests in the format <code>PackageName Version
......
......@@ -4,6 +4,8 @@
package org.torproject.descriptor;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
/**
* Contains a relay or sanitized bridge server descriptor.
......@@ -151,6 +153,14 @@ public interface ServerDescriptor extends Descriptor {
*/
public String getPlatform();
/**
* Return the version numbers of all protocols supported by this server, or
* null if this descriptor does not specify supported protocol versions.
*
* @since 1.6.0
*/
public SortedMap<String, SortedSet<Long>> getProtocols();
/**
* Return the time in milliseconds since the epoch when this descriptor
* and the corresponding extra-info descriptor were generated.
......
......@@ -57,6 +57,7 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
this.atMostOnceKeywords = new TreeSet<>();
this.atMostOnceKeywords.add("s");
this.atMostOnceKeywords.add("v");
this.atMostOnceKeywords.add("pr");
this.atMostOnceKeywords.add("w");
this.atMostOnceKeywords.add("p");
}
......@@ -95,6 +96,9 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
case "v":
this.parseVLine(line, parts);
break;
case "pr":
this.parsePrLine(line, parts);
break;
case "w":
this.parseWLine(line, parts);
break;
......@@ -191,6 +195,12 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
}
}
private void parsePrLine(String line, String[] parts)
throws DescriptorParseException {
this.parsedAtMostOnceKeyword("pr");
this.protocols = ParseHelper.parseProtocolVersions(line, line, parts);
}
private void parseWLine(String line, String[] parts)
throws DescriptorParseException {
this.parsedAtMostOnceKeyword("w");
......@@ -359,6 +369,13 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
return this.version;
}
private SortedMap<String, SortedSet<Long>> protocols;
@Override
public SortedMap<String, SortedSet<Long>> getProtocols() {
return this.protocols;
}
private long bandwidth = -1L;
@Override
......
......@@ -8,13 +8,16 @@ import org.torproject.descriptor.DescriptorParseException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.xml.bind.DatatypeConverter;
......@@ -578,5 +581,49 @@ public class ParseHelper {
throw new DescriptorParseException("Unable to locate "
+ "master-key-ed25519 in identity-ed25519.");
}
private static Map<String, SortedMap<String, SortedSet<Long>>>
parsedProtocolVersions = new HashMap<>();
protected static SortedMap<String, SortedSet<Long>> parseProtocolVersions(
String line, String lineNoOpt, String[] partsNoOpt)
throws DescriptorParseException {
if (!parsedProtocolVersions.containsKey(lineNoOpt)) {
SortedMap<String, SortedSet<Long>> parsed = new TreeMap<>();
boolean invalid = false;
try {
for (int i = 1; i < partsNoOpt.length; i++) {
String[] part = partsNoOpt[i].split("=");
SortedSet<Long> versions = new TreeSet<>();
for (String val : part[1].split(",")) {
if (val.contains("-")) {
String[] fromTo = val.split("-");
long from = Long.parseLong(fromTo[0]);
long to = Long.parseLong(fromTo[1]);
if (from > to || to >= 0x1_0000_0000L) {
invalid = true;
} else {
for (long j = from;
j <= to; j++) {
versions.add(j);
}
}
} else {
versions.add(Long.parseLong(val));
}
}
parsed.put(part[0], Collections.unmodifiableSortedSet(versions));
}
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
throw new DescriptorParseException("Invalid line '" + line + "'.", e);
}
if (invalid) {
throw new DescriptorParseException("Invalid line '" + line + "'.");
}
parsedProtocolVersions.put(lineNoOpt,
Collections.unmodifiableSortedMap(parsed));
}
return parsedProtocolVersions.get(lineNoOpt);
}
}
......@@ -52,7 +52,9 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
+ "valid-until,voting-delay,known-flags").split(",")));
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList((
"client-versions,server-versions,params,directory-footer,"
"client-versions,server-versions,recommended-client-protocols,"
+ "recommended-relay-protocols,required-client-protocols,"
+ "required-relay-protocols,params,directory-footer,"
+ "bandwidth-weights").split(",")));
this.checkAtMostOnceKeywords(atMostOnceKeywords);
this.checkFirstKeyword("network-status-version");
......@@ -124,6 +126,18 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
case "server-versions":
this.parseServerVersionsLine(line, parts);
break;
case "recommended-client-protocols":
this.parseRecommendedClientProtocolsLine(line, parts);
break;
case "recommended-relay-protocols":
this.parseRecommendedRelayProtocolsLine(line, parts);
break;
case "required-client-protocols":
this.parseRequiredClientProtocolsLine(line, parts);
break;
case "required-relay-protocols":
this.parseRequiredRelayProtocolsLine(line, parts);
break;
case "package":
this.parsePackageLine(line, parts);
break;
......@@ -281,6 +295,30 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
line, parts);
}
private void parseRecommendedClientProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.recommendedClientProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRecommendedRelayProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.recommendedRelayProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRequiredClientProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.requiredClientProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRequiredRelayProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.requiredRelayProtocols = ParseHelper.parseProtocolVersions(line, line,
parts);
}
private void parsePackageLine(String line, String[] parts)
throws DescriptorParseException {
if (parts.length < 5) {
......@@ -397,6 +435,34 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
: Arrays.asList(this.recommendedServerVersions);
}
private SortedMap<String, SortedSet<Long>> recommendedClientProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRecommendedClientProtocols() {
return this.recommendedClientProtocols;
}
private SortedMap<String, SortedSet<Long>> recommendedRelayProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRecommendedRelayProtocols() {
return this.recommendedRelayProtocols;
}
private SortedMap<String, SortedSet<Long>> requiredClientProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRequiredClientProtocols() {
return this.requiredClientProtocols;
}
private SortedMap<String, SortedSet<Long>> requiredRelayProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRequiredRelayProtocols() {
return this.requiredRelayProtocols;
}
private List<String> packageLines;
@Override
......
......@@ -52,6 +52,8 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList((
"consensus-methods,client-versions,server-versions,"
+ "recommended-client-protocols,recommended-relay-protocols,"
+ "required-client-protocols,required-relay-protocols,"
+ "flag-thresholds,params,contact,"
+ "legacy-key,dir-key-crosscert,dir-address,directory-footer")
.split(",")));
......@@ -117,6 +119,18 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
case "server-versions":
this.parseServerVersionsLine(line, parts);
break;
case "recommended-client-protocols":
this.parseRecommendedClientProtocolsLine(line, parts);
break;
case "recommended-relay-protocols":
this.parseRecommendedRelayProtocolsLine(line, parts);
break;
case "required-client-protocols":
this.parseRequiredClientProtocolsLine(line, parts);
break;
case "required-relay-protocols":
this.parseRequiredRelayProtocolsLine(line, parts);
break;
case "package":
this.parsePackageLine(line, parts);
break;
......@@ -305,6 +319,30 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
line, parts);
}
private void parseRecommendedClientProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.recommendedClientProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRecommendedRelayProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.recommendedRelayProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRequiredClientProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.requiredClientProtocols = ParseHelper.parseProtocolVersions(line,
line, parts);
}
private void parseRequiredRelayProtocolsLine(String line, String[] parts)
throws DescriptorParseException {
this.requiredRelayProtocols = ParseHelper.parseProtocolVersions(line, line,
parts);
}
private void parsePackageLine(String line, String[] parts)
throws DescriptorParseException {
if (parts.length < 5) {
......@@ -710,6 +748,34 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
: Arrays.asList(this.recommendedServerVersions);
}
private SortedMap<String, SortedSet<Long>> recommendedClientProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRecommendedClientProtocols() {
return this.recommendedClientProtocols;
}
private SortedMap<String, SortedSet<Long>> recommendedRelayProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRecommendedRelayProtocols() {
return this.recommendedRelayProtocols;
}
private SortedMap<String, SortedSet<Long>> requiredClientProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRequiredClientProtocols() {
return this.requiredClientProtocols;
}
private SortedMap<String, SortedSet<Long>> requiredRelayProtocols;
@Override
public SortedMap<String, SortedSet<Long>> getRequiredRelayProtocols() {
return this.requiredRelayProtocols;
}
private List<String> packageLines;
@Override
......
......@@ -16,6 +16,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import javax.xml.bind.DatatypeConverter;
......@@ -34,7 +36,7 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
"router,bandwidth,published".split(",")));
this.checkExactlyOnceKeywords(exactlyOnceKeywords);
Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList((
"identity-ed25519,master-key-ed25519,platform,fingerprint,"
"identity-ed25519,master-key-ed25519,platform,proto,fingerprint,"
+ "hibernating,uptime,contact,family,read-history,write-history,"
+ "eventdns,caches-extra-info,extra-info-digest,"
+ "hidden-service-dir,protocols,allow-single-hop-exits,onion-key,"
......@@ -80,6 +82,9 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
case "platform":
this.parsePlatformLine(line, lineNoOpt, partsNoOpt);
break;
case "proto":
this.parseProtoLine(line, lineNoOpt, partsNoOpt);
break;
case "published":
this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
break;
......@@ -303,6 +308,12 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
}
}
private void parseProtoLine(String line, String lineNoOpt,
String[] partsNoOpt) throws DescriptorParseException {
this.protocols = ParseHelper.parseProtocolVersions(line, lineNoOpt,
partsNoOpt);
}
private void parsePublishedLine(String line, String lineNoOpt,
String[] partsNoOpt) throws DescriptorParseException {
this.publishedMillis = ParseHelper.parseTimestampAtIndex(line,
......@@ -812,6 +823,13 @@ public abstract class ServerDescriptorImpl extends DescriptorImpl
return this.platform;
}
private SortedMap<String, SortedSet<Long>> protocols;
@Override
public SortedMap<String, SortedSet<Long>> getProtocols() {
return this.protocols;
}
private long publishedMillis;
@Override
......
......@@ -126,6 +126,54 @@ public class ConsensusBuilder {
return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true);
}
private String recommendedClientProtocolsLine =
"recommended-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 "
+ "HSIntro=3 HSRend=1 Link=4 LinkAuth=1 Microdesc=1-2 Relay=2";
protected static RelayNetworkStatusConsensus
createWithRecommendedClientProtocolsLine(String line)
throws DescriptorParseException {
ConsensusBuilder cb = new ConsensusBuilder();
cb.recommendedClientProtocolsLine = line;
return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true);
}
private String recommendedRelayProtocolsLine =
"recommended-relay-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 "
+ "HSIntro=3 HSRend=1 Link=4 LinkAuth=1 Microdesc=1-2 Relay=2";
protected static RelayNetworkStatusConsensus
createWithRecommendedRelayProtocolsLine(String line)
throws DescriptorParseException {
ConsensusBuilder cb = new ConsensusBuilder();
cb.recommendedRelayProtocolsLine = line;
return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true);
}
private String requiredClientProtocolsLine =
"required-client-protocols Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 "
+ "HSIntro=3 HSRend=1 Link=4 LinkAuth=1 Microdesc=1-2 Relay=2";
protected static RelayNetworkStatusConsensus
createWithRequiredClientProtocolsLine(String line)
throws DescriptorParseException {
ConsensusBuilder cb = new ConsensusBuilder();
cb.requiredClientProtocolsLine = line;
return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true);
}
private String requiredRelayProtocolsLine =
"required-relay-protocols Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 "
+ "HSRend=1 Link=3-4 LinkAuth=1 Microdesc=1 Relay=1-2";
protected static RelayNetworkStatusConsensus
createWithRequiredRelayProtocolsLine(String line)
throws DescriptorParseException {
ConsensusBuilder cb = new ConsensusBuilder();
cb.requiredRelayProtocolsLine = line;
return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true);
}
private String paramsLine = "params "
+ "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 "
+ "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 "
......@@ -322,6 +370,18 @@ public class ConsensusBuilder {
if (this.knownFlagsLine != null) {
sb.append(this.knownFlagsLine).append("\n");
}
if (this.recommendedClientProtocolsLine != null) {
sb.append(this.recommendedClientProtocolsLine).append("\n");
}
if (this.recommendedRelayProtocolsLine != null) {
sb.append(this.recommendedRelayProtocolsLine).append("\n");
}
if (this.requiredClientProtocolsLine != null) {
sb.append(this.requiredClientProtocolsLine).append("\n");
}
if (this.requiredRelayProtocolsLine != null) {
sb.append(this.requiredRelayProtocolsLine).append("\n");
}
if (this.paramsLine != null) {
sb.append(this.paramsLine).append("\n");
}
......
......@@ -19,6 +19,7 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TreeSet;
/* TODO Add test cases for all lines starting with "opt ". */
......@@ -242,6 +243,16 @@ public class RelayNetworkStatusConsensusImplTest {
return createWithStatusEntry(seb.buildStatusEntry());
}
private String prLine = "pr Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 "
+ "HSIntro=3 HSRend=1-2 Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2";