commit 38c48ddd0c49978bbfa5e0a987cfd3a890692a5c Author: Karsten Loesing karsten.loesing@gmx.net Date: Thu Jan 9 14:24:03 2014 +0100
Parse micodesc consensuses and microdescriptors.
Required for implementing #2785. --- .../torproject/descriptor/DirectorySignature.java | 3 + src/org/torproject/descriptor/Microdescriptor.java | 47 ++++ .../torproject/descriptor/NetworkStatusEntry.java | 11 +- .../descriptor/RelayNetworkStatusConsensus.java | 5 +- .../torproject/descriptor/impl/DescriptorImpl.java | 12 +- .../descriptor/impl/DirectorySignatureImpl.java | 15 +- .../descriptor/impl/MicrodescriptorImpl.java | 261 ++++++++++++++++++++ .../descriptor/impl/NetworkStatusEntryImpl.java | 51 +++- .../descriptor/impl/NetworkStatusImpl.java | 8 +- .../torproject/descriptor/impl/ParseHelper.java | 17 +- .../impl/RelayNetworkStatusConsensusImpl.java | 37 ++- .../descriptor/impl/ServerDescriptorImpl.java | 2 +- 12 files changed, 440 insertions(+), 29 deletions(-)
diff --git a/src/org/torproject/descriptor/DirectorySignature.java b/src/org/torproject/descriptor/DirectorySignature.java index 29eb055..012b770 100644 --- a/src/org/torproject/descriptor/DirectorySignature.java +++ b/src/org/torproject/descriptor/DirectorySignature.java @@ -4,6 +4,9 @@ package org.torproject.descriptor;
public interface DirectorySignature {
+ /* Return the digest algorithm, which is "sha1" by default. */ + public String getAlgorithm(); + /* Return the directory identity fingerprint. */ public String getIdentity();
diff --git a/src/org/torproject/descriptor/Microdescriptor.java b/src/org/torproject/descriptor/Microdescriptor.java new file mode 100644 index 0000000..f106f83 --- /dev/null +++ b/src/org/torproject/descriptor/Microdescriptor.java @@ -0,0 +1,47 @@ +/* Copyright 2014 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor; + +import java.util.List; + +/* Contains a relay microdescriptor. */ +public interface Microdescriptor extends Descriptor { + + /* Return the descriptor digest that is used to reference this + * microdescriptor in a network status. */ + public String getMicrodescriptorDigest(); + + /* Return the onion key in PEM format. */ + public String getOnionKey(); + + /* Return the ntor onion key base64 string with padding omitted, or null + * if the microdescriptor didn't contain an ntor onion key line. */ + public String getNtorOnionKey(); + + /* Return the relay's additional OR addresses and ports contained in + * or-address lines, or an empty list if the microdescriptor doesn't + * contain such lines. */ + public List<String> getOrAddresses(); + + /* Return nicknames, ($-prefixed) fingerprints, $fingerprint=nickname, + * or $fingerprint~nickname tuples contained in the family line of this + * relay, or null if the descriptor does not contain a family line. */ + public List<String> getFamilyEntries(); + + /* Return the default policy of the port summary or null if the + * microdescriptor didn't contain a port summary line. */ + public String getDefaultPolicy(); + + /* Return the port list of the port summary or null if the + * microdescriptor didn't contain a port summary line. */ + public String getPortList(); + + /* Return the default policy of the IPv6 port summary or null if the + * microdescriptor didn't contain an IPv6 port summary line. */ + public String getIpv6DefaultPolicy(); + + /* Return the port list of the IPv6 port summary or null if the + * microdescriptor didn't contain an IPv6 port summary line. */ + public String getIpv6PortList(); +} + diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java index 2f0bb89..cb15ce2 100644 --- a/src/org/torproject/descriptor/NetworkStatusEntry.java +++ b/src/org/torproject/descriptor/NetworkStatusEntry.java @@ -1,8 +1,9 @@ -/* Copyright 2011, 2012 The Tor Project +/* Copyright 2011--2014 The Tor Project * See LICENSE for licensing information */ package org.torproject.descriptor;
import java.util.List; +import java.util.Set; import java.util.SortedSet;
/* Status entry contained in a network status with version 2 or higher or @@ -18,7 +19,8 @@ public interface NetworkStatusEntry { /* Return the relay fingerprint. */ public String getFingerprint();
- /* Return the descriptor identity. */ + /* Return the descriptor identity or null if the containing status is a + * microdesc consensus. */ public String getDescriptor();
/* Return the publication timestamp. */ @@ -33,6 +35,11 @@ public interface NetworkStatusEntry { /* Return the DirPort. */ public int getDirPort();
+ /* Return the (possibly empty) set of microdescriptor digest(s) if the + * containing status is a vote or microdesc consensus, or null + * otherwise. */ + public Set<String> getMicrodescriptorDigests(); + /* Return the relay's additional OR addresses and ports contained in * or-address lines, or an empty list if the network status doesn't * contain such lines. */ diff --git a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java index 4a1634f..53eaf6b 100644 --- a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java +++ b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java @@ -1,4 +1,4 @@ -/* Copyright 2011, 2012 The Tor Project +/* Copyright 2011--2014 The Tor Project * See LICENSE for licensing information */ package org.torproject.descriptor;
@@ -12,6 +12,9 @@ public interface RelayNetworkStatusConsensus extends Descriptor { /* Return the network status version. */ public int getNetworkStatusVersion();
+ /* Return the flavor name, or null if this consensus is unflavored. */ + public String getConsensusFlavor(); + /* Return the consensus method. */ public int getConsensusMethod();
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java index b1e4408..770ee4f 100644 --- a/src/org/torproject/descriptor/impl/DescriptorImpl.java +++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java @@ -27,8 +27,10 @@ public abstract class DescriptorImpl implements Descriptor { first100Chars.length); String firstLines = new String(first100Chars); if (firstLines.startsWith("@type network-status-consensus-3 1.") || - ((firstLines.startsWith("network-status-version 3\n") || - firstLines.contains("\nnetwork-status-version 3\n")) && + firstLines.startsWith("@type network-status-microdesc-" + + "consensus-3 1.") || + ((firstLines.startsWith("network-status-version 3") || + firstLines.contains("\nnetwork-status-version 3")) && firstLines.contains("\nvote-status consensus\n"))) { parsedDescriptors.addAll(RelayNetworkStatusConsensusImpl. parseConsensuses(rawDescriptorBytes, @@ -58,6 +60,12 @@ public abstract class DescriptorImpl implements Descriptor { parsedDescriptors.addAll(ExtraInfoDescriptorImpl. parseDescriptors(rawDescriptorBytes, failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type microdescriptor 1.") || + firstLines.startsWith("onion-key\n") || + firstLines.contains("\nonion-key\n")) { + parsedDescriptors.addAll(MicrodescriptorImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); } else if (firstLines.startsWith("@type bridge-pool-assignment 1.") || firstLines.startsWith("bridge-pool-assignment ") || firstLines.contains("\nbridge-pool-assignment ")) { diff --git a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java index d205345..d8548ec 100644 --- a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java +++ b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java @@ -41,14 +41,18 @@ public class DirectorySignatureImpl implements DirectorySignature { String line = s.next(); if (line.startsWith("directory-signature ")) { String[] parts = line.split(" ", -1); - if (parts.length != 3) { + int algorithmOffset = 0; + if (parts.length == 4) { + this.algorithm = parts[1]; + algorithmOffset = 1; + } else if (parts.length != 3) { throw new DescriptorParseException("Illegal line '" + line + "'."); } this.identity = ParseHelper.parseTwentyByteHexString(line, - parts[1]); + parts[1 + algorithmOffset]); this.signingKeyDigest = ParseHelper.parseTwentyByteHexString( - line, parts[2]); + line, parts[2 + algorithmOffset]); } else if (line.startsWith("-----BEGIN")) { crypto = new StringBuilder(); crypto.append(line + "\n"); @@ -73,6 +77,11 @@ public class DirectorySignatureImpl implements DirectorySignature { } }
+ private String algorithm = "sha1"; + public String getAlgorithm() { + return this.algorithm; + } + private String identity; public String getIdentity() { return this.identity; diff --git a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java new file mode 100644 index 0000000..15fb91e --- /dev/null +++ b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java @@ -0,0 +1,261 @@ +/* Copyright 2014 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; + +import org.apache.commons.codec.digest.DigestUtils; +import org.torproject.descriptor.Microdescriptor; + +/* Contains a microdescriptor. */ +public class MicrodescriptorImpl extends DescriptorImpl + implements Microdescriptor { + + protected static List<Microdescriptor> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<Microdescriptor> parsedDescriptors = + new ArrayList<Microdescriptor>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "onion-key\n"); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + Microdescriptor parsedDescriptor = + new MicrodescriptorImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected MicrodescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseDescriptorBytes(); + this.calculateDigest(); + Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList( + "onion-key".split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList(( + "ntor-onion-key,family,p,p6").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("onion-key"); + return; + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = null; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("@")) { + continue; + } + String[] parts = line.split(" "); + String keyword = parts[0]; + if (keyword.equals("onion-key")) { + this.parseOnionKeyLine(line, parts); + nextCrypto = "onion-key"; + } else if (keyword.equals("ntor-onion-key")) { + this.parseNtorOnionKeyLine(line, parts); + } else if (keyword.equals("a")) { + this.parseALine(line, parts); + } else if (keyword.equals("family")) { + this.parseFamilyLine(line, parts); + } else if (keyword.equals("p")) { + this.parsePLine(line, parts); + } else if (keyword.equals("p6")) { + this.parseP6Line(line, parts); + } else if (line.startsWith("-----BEGIN")) { + crypto = new StringBuilder(); + crypto.append(line + "\n"); + } else if (line.startsWith("-----END")) { + crypto.append(line + "\n"); + String cryptoString = crypto.toString(); + crypto = null; + if (nextCrypto.equals("onion-key")) { + this.onionKey = cryptoString; + } else { + throw new DescriptorParseException("Unrecognized crypto " + + "block in microdescriptor."); + } + nextCrypto = null; + } else if (crypto != null) { + crypto.append(line + "\n"); + } else { + ParseHelper.parseKeyword(line, parts[0]); + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in microdescriptor."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseOnionKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("onion-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseNtorOnionKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.ntorOnionKey = parts[1].replaceAll("=", ""); + } + + private void parseALine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + /* TODO Add more checks. */ + /* TODO Add tests. */ + this.orAddresses.add(parts[1]); + } + + private void parseFamilyLine(String line, String[] parts) + throws DescriptorParseException { + this.familyEntries = new ArrayList<String>(); + for (int i = 1; i < parts.length; i++) { + if (parts[i].startsWith("$")) { + if (parts[i].contains("=") ^ parts[i].contains("~")) { + String separator = parts[i].contains("=") ? "=" : "~"; + String fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[i].substring(1, parts[i].indexOf(separator))); + String nickname = ParseHelper.parseNickname(line, + parts[i].substring(parts[i].indexOf( + separator) + 1)); + this.familyEntries.add("$" + fingerprint + separator + + nickname); + } else { + this.familyEntries.add("$" + + ParseHelper.parseTwentyByteHexString(line, + parts[i].substring(1))); + } + } else { + this.familyEntries.add(ParseHelper.parseNickname(line, parts[i])); + } + } + } + + private void parsePLine(String line, String[] parts) + throws DescriptorParseException { + this.validatePOrP6Line(line, parts); + this.defaultPolicy = parts[1]; + this.portList = parts[2]; + } + + private void parseP6Line(String line, String[] parts) + throws DescriptorParseException { + this.validatePOrP6Line(line, parts); + this.ipv6DefaultPolicy = parts[1]; + this.ipv6PortList = parts[2]; + } + + private void validatePOrP6Line(String line, String[] parts) + throws DescriptorParseException { + boolean isValid = true; + if (parts.length != 3) { + isValid = false; + } else if (!parts[1].equals("accept") && !parts[1].equals("reject")) { + isValid = false; + } else { + 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 calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "onion-key\n"; + int start = ascii.indexOf(startToken); + int end = ascii.length(); + if (start >= 0 && end > start) { + byte[] forDigest = new byte[end - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, end - start); + this.microdescriptorDigest = DigestUtils.sha256Hex(forDigest); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } + if (this.microdescriptorDigest == null) { + throw new DescriptorParseException("Could not calculate " + + "microdescriptor digest."); + } + } + + private String microdescriptorDigest; + public String getMicrodescriptorDigest() { + return this.microdescriptorDigest; + } + + private String onionKey; + public String getOnionKey() { + return this.onionKey; + } + + private String ntorOnionKey; + public String getNtorOnionKey() { + return this.ntorOnionKey; + } + + private List<String> orAddresses = new ArrayList<String>(); + public List<String> getOrAddresses() { + return new ArrayList<String>(this.orAddresses); + } + + private List<String> familyEntries; + public List<String> getFamilyEntries() { + return this.familyEntries == null ? null : + new ArrayList<String>(this.familyEntries); + } + private String defaultPolicy; + public String getDefaultPolicy() { + return this.defaultPolicy; + } + + private String portList; + public String getPortList() { + return this.portList; + } + + private String ipv6DefaultPolicy; + public String getIpv6DefaultPolicy() { + return this.ipv6DefaultPolicy; + } + + private String ipv6PortList; + public String getIpv6PortList() { + return this.ipv6PortList; + } +} + diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java index 337484e..19f951e 100644 --- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java +++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java @@ -1,10 +1,12 @@ -/* Copyright 2011, 2012 The Tor Project +/* Copyright 2011--2014 The Tor Project * See LICENSE for licensing information */ package org.torproject.descriptor.impl;
import java.util.ArrayList; +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 java.util.TreeSet; @@ -18,6 +20,8 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { return this.statusEntryBytes; }
+ private boolean microdescConsensus; + private boolean failUnrecognizedDescriptorLines; private List<String> unrecognizedLines; protected List<String> getAndClearUnrecognizedLines() { @@ -27,9 +31,10 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { }
protected NetworkStatusEntryImpl(byte[] statusEntryBytes, - boolean failUnrecognizedDescriptorLines) + boolean microdescConsensus, boolean failUnrecognizedDescriptorLines) throws DescriptorParseException { this.statusEntryBytes = statusEntryBytes; + this.microdescConsensus = microdescConsensus; this.failUnrecognizedDescriptorLines = failUnrecognizedDescriptorLines; this.initializeKeywords(); @@ -95,20 +100,28 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
private void parseRLine(String line, String[] parts) throws DescriptorParseException { - if (parts.length < 9) { + if ((!this.microdescConsensus && parts.length != 9) || + (this.microdescConsensus && parts.length != 8)) { throw new DescriptorParseException("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]); + int descriptorOffset = 0; + if (!this.microdescConsensus) { + this.descriptor = ParseHelper.parseTwentyByteBase64String(line, + parts[3]); + descriptorOffset = 1; + } 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]); + 3 + descriptorOffset, 4 + descriptorOffset); + this.address = ParseHelper.parseIpv4Address(line, + parts[5 + descriptorOffset]); + this.orPort = ParseHelper.parsePort(line, + parts[6 + descriptorOffset]); + this.dirPort = ParseHelper.parsePort(line, + parts[7 + descriptorOffset]); }
private void parseALine(String line, String[] parts) @@ -194,8 +207,18 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
private void parseMLine(String line, String[] parts) throws DescriptorParseException { - /* TODO Implement parsing of m lines in votes as specified in - * dir-spec.txt. */ + if (this.microdescriptorDigests == null) { + this.microdescriptorDigests = new HashSet<String>(); + } + if (parts.length == 2) { + this.microdescriptorDigests.add( + ParseHelper.parseThirtyTwoByteBase64String(line, parts[1])); + } else if (parts.length == 3 && parts[2].length() > 7) { + /* 7 == "sha256=".length() */ + this.microdescriptorDigests.add( + ParseHelper.parseThirtyTwoByteBase64String(line, + parts[2].substring(7))); + } }
private String nickname; @@ -233,6 +256,12 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry { return this.dirPort; }
+ private Set<String> microdescriptorDigests; + public Set<String> getMicrodescriptorDigests() { + return this.microdescriptorDigests == null ? null : + new HashSet<String>(this.microdescriptorDigests); + } + private List<String> orAddresses = new ArrayList<String>(); public List<String> getOrAddresses() { return new ArrayList<String>(this.orAddresses); diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java index 94a76bf..6358ca3 100644 --- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java +++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java @@ -176,7 +176,7 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { protected void parseStatusEntry(byte[] statusEntryBytes) throws DescriptorParseException { NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl( - statusEntryBytes, this.failUnrecognizedDescriptorLines); + statusEntryBytes, false, this.failUnrecognizedDescriptorLines); this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); List<String> unrecognizedStatusEntryLines = statusEntry. getAndClearUnrecognizedLines(); @@ -210,13 +210,13 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { } }
- private SortedMap<String, DirSourceEntry> dirSourceEntries = + protected 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 = + protected SortedMap<String, NetworkStatusEntry> statusEntries = new TreeMap<String, NetworkStatusEntry>(); public SortedMap<String, NetworkStatusEntry> getStatusEntries() { return new TreeMap<String, NetworkStatusEntry>(this.statusEntries); @@ -228,7 +228,7 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { return this.statusEntries.get(fingerprint); }
- private SortedMap<String, DirectorySignature> directorySignatures; + protected SortedMap<String, DirectorySignature> directorySignatures; public SortedMap<String, DirectorySignature> getDirectorySignatures() { return this.directorySignatures == null ? null : new TreeMap<String, DirectorySignature>(this.directorySignatures); diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java index ebb8bc6..fd38eb0 100644 --- a/src/org/torproject/descriptor/impl/ParseHelper.java +++ b/src/org/torproject/descriptor/impl/ParseHelper.java @@ -239,11 +239,11 @@ public class ParseHelper { return nickname; }
- private static Pattern base64Pattern = + private static Pattern twentyByteBase64Pattern = Pattern.compile("^[0-9a-zA-Z+/]{27}$"); public static String parseTwentyByteBase64String(String line, String base64String) throws DescriptorParseException { - if (!base64Pattern.matcher(base64String).matches()) { + if (!twentyByteBase64Pattern.matcher(base64String).matches()) { throw new DescriptorParseException("'" + base64String + "' in line '" + line + "' is not a valid base64-encoded " + "20-byte value."); @@ -252,6 +252,19 @@ public class ParseHelper { toUpperCase(); }
+ private static Pattern thirtyTwoByteBase64Pattern = + Pattern.compile("^[0-9a-zA-Z+/]{43}$"); + public static String parseThirtyTwoByteBase64String(String line, + String base64String) throws DescriptorParseException { + if (!thirtyTwoByteBase64Pattern.matcher(base64String).matches()) { + throw new DescriptorParseException("'" + base64String + + "' in line '" + line + "' is not a valid base64-encoded " + + "32-byte value."); + } + return Hex.encodeHexString(Base64.decodeBase64(base64String + "=")). + toUpperCase(); + } + public static SortedMap<String, Integer> parseCommaSeparatedKeyIntegerValueList(String line, String[] partsNoOpt, int index, int keyLength) diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java index dad5bc3..d6babee 100644 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java +++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java @@ -1,4 +1,4 @@ -/* Copyright 2011, 2012 The Tor Project +/* Copyright 2011--2014 The Tor Project * See LICENSE for licensing information */ package org.torproject.descriptor.impl;
@@ -17,7 +17,7 @@ import java.util.TreeSet; import org.apache.commons.codec.digest.DigestUtils; import org.torproject.descriptor.RelayNetworkStatusConsensus;
-/* Contains a network status consensus. */ +/* Contains a network status consensus or microdesc consensus. */ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl implements RelayNetworkStatusConsensus {
@@ -120,6 +120,23 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl } }
+ private boolean microdescConsensus = false; + protected void parseStatusEntry(byte[] statusEntryBytes) + throws DescriptorParseException { + NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl( + statusEntryBytes, this.microdescConsensus, + this.failUnrecognizedDescriptorLines); + this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); + List<String> unrecognizedStatusEntryLines = statusEntry. + getAndClearUnrecognizedLines(); + if (unrecognizedStatusEntryLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); + } + } + protected void parseFooter(byte[] footerBytes) throws DescriptorParseException { Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n"); @@ -144,11 +161,20 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
private void parseNetworkStatusVersionLine(String line, String[] parts) throws DescriptorParseException { - if (!line.equals("network-status-version 3")) { + if (!line.startsWith("network-status-version 3")) { throw new DescriptorParseException("Illegal network status version " + "number in line '" + line + "'."); } this.networkStatusVersion = 3; + if (parts.length == 3) { + this.consensusFlavor = parts[2]; + if (this.consensusFlavor.equals("microdesc")) { + this.microdescConsensus = true; + } + } else if (parts.length != 2) { + throw new DescriptorParseException("Illegal network status version " + + "line '" + line + "'."); + } }
private void parseVoteStatusLine(String line, String[] parts) @@ -277,6 +303,11 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl return this.networkStatusVersion; }
+ private String consensusFlavor; + public String getConsensusFlavor() { + return this.consensusFlavor; + } + private int consensusMethod; public int getConsensusMethod() { return this.consensusMethod; diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java index baf4456..d1fcd28 100644 --- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java +++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java @@ -508,7 +508,7 @@ public class ServerDescriptorImpl extends DescriptorImpl if (partsNoOpt.length != 2) { throw new DescriptorParseException("Illegal line '" + line + "'."); } - this.ntorOnionKey = partsNoOpt[1].replaceAll("=", ""); + this.ntorOnionKey = partsNoOpt[1].replaceAll("=", ""); }
private void calculateDigest() throws DescriptorParseException {
tor-commits@lists.torproject.org