commit 466725e2ea478420fddd4a8bc6d6e199d5d31b8e Author: Karsten Loesing karsten.loesing@gmx.net Date: Sat May 19 19:30:28 2012 +0200
Parse v1 directories and contained server descriptors. --- .../torproject/descriptor/NetworkStatusEntry.java | 2 + src/org/torproject/descriptor/RelayDirectory.java | 41 ++ .../torproject/descriptor/RouterStatusEntry.java | 20 + .../torproject/descriptor/ServerDescriptor.java | 4 +- .../descriptor/impl/BandwidthHistoryImpl.java | 38 +- .../torproject/descriptor/impl/DescriptorImpl.java | 5 + .../descriptor/impl/RelayDirectoryImpl.java | 513 ++++++++++++++++++++ .../descriptor/impl/RouterStatusEntryImpl.java | 37 ++ .../descriptor/impl/ServerDescriptorImpl.java | 28 +- 9 files changed, 671 insertions(+), 17 deletions(-)
diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java index ba5939c..3bda176 100644 --- a/src/org/torproject/descriptor/NetworkStatusEntry.java +++ b/src/org/torproject/descriptor/NetworkStatusEntry.java @@ -5,6 +5,8 @@ package org.torproject.descriptor; import java.util.List; import java.util.SortedSet;
+/* Status entry contained in a network status with version 2 or higher or + * in a bridge network status. */ public interface NetworkStatusEntry {
/* Return the raw status entry bytes. */ diff --git a/src/org/torproject/descriptor/RelayDirectory.java b/src/org/torproject/descriptor/RelayDirectory.java new file mode 100644 index 0000000..b8a9472 --- /dev/null +++ b/src/org/torproject/descriptor/RelayDirectory.java @@ -0,0 +1,41 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor; + +import java.util.List; + +/* Contains a v1 signed directory. */ +public interface RelayDirectory extends Descriptor { + + /* Return the published time in milliseconds. */ + public long getPublishedMillis(); + + /* Return the directory signing key digest. */ + public String getDirSigningKey(); + + /* Return recommended software versions or null if the directory doesn't + * list recommended software. */ + public List<String> getRecommendedSoftware(); + + /* Return the directory signature. */ + public String getDirectorySignature(); + + /* Return router status entries, one for each contained relay. */ + public List<RouterStatusEntry> getRouterStatusEntries(); + + /* Return a list of server descriptors contained in the signed + * directory. */ + public List<ServerDescriptor> getServerDescriptors(); + + /* Return a (very likely empty) list of exceptions from parsing the + * contained server descriptors. */ + public List<Exception> getServerDescriptorParseExceptions(); + + /* Return the directory nickname. */ + public String getNickname(); + + /* Return the directory digest that the directory authority used to sign + * the directory. */ + public String getDirectoryDigest(); +} + diff --git a/src/org/torproject/descriptor/RouterStatusEntry.java b/src/org/torproject/descriptor/RouterStatusEntry.java new file mode 100644 index 0000000..6652e94 --- /dev/null +++ b/src/org/torproject/descriptor/RouterStatusEntry.java @@ -0,0 +1,20 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor; + +/* Status entry contained in a v1 signed directory. */ +public interface RouterStatusEntry { + + /* Return the relay nickname, or null if the relay is unverified. */ + public String getNickname(); + + /* Return the relay fingerprint. */ + public String getFingerprint(); + + /* Return whether the relay is verified. */ + public boolean isVerified(); + + /* Return whether the relay is live. */ + public boolean isLive(); +} + diff --git a/src/org/torproject/descriptor/ServerDescriptor.java b/src/org/torproject/descriptor/ServerDescriptor.java index bcc4913..a7f76c7 100644 --- a/src/org/torproject/descriptor/ServerDescriptor.java +++ b/src/org/torproject/descriptor/ServerDescriptor.java @@ -40,7 +40,9 @@ public interface ServerDescriptor extends Descriptor { public int getBandwidthBurst();
/* Return the observed bandwidth in bytes per second as an estimate of - * the capacity that the relay can handle. */ + * the capacity that the relay can handle, or -1 if the descriptor + * doesn't contain an observed bandwidth value (which is the case for + * Tor versions 0.0.8 or older). */ public int getBandwidthObserved();
/* Return the platform string containing the Tor software version and diff --git a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java index 56ae512..65def52 100644 --- a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java +++ b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java @@ -12,32 +12,42 @@ public class BandwidthHistoryImpl implements BandwidthHistory { protected BandwidthHistoryImpl(String line, String lineNoOpt, String[] partsNoOpt) throws DescriptorParseException { boolean isValid = false; - if (partsNoOpt.length >= 5) { + this.line = line; + if (lineNoOpt.startsWith("read-history ")) { + lineNoOpt = "read-history " + + lineNoOpt.substring("read-history ".length()); + partsNoOpt = lineNoOpt.split(" "); + } + if (partsNoOpt.length == 5 || partsNoOpt.length == 6) { try { - this.line = line; - if (lineNoOpt.startsWith("read-history ")) { - lineNoOpt = "read-history " - + lineNoOpt.substring("read-history ".length()); - partsNoOpt = lineNoOpt.split(" "); - } this.historyEndMillis = ParseHelper.parseTimestampAtIndex(line, partsNoOpt, 1, 2); if (partsNoOpt[3].startsWith("(") && - partsNoOpt[4].equals("s)")) { + partsNoOpt[4].startsWith("s)")) { this.intervalLength = Long.parseLong(partsNoOpt[3]. substring(1)); if (this.intervalLength <= 0L) { throw new DescriptorParseException("Only positive interval " + "lengths are allowed in line '" + line + "'."); } - if (partsNoOpt.length > 6) { - /* Invalid line, handle below. */ - } else if (partsNoOpt.length == 5) { - /* No bandwidth values to parse. */ + String[] values = null; + if (partsNoOpt.length == 5 && + partsNoOpt[4].equals("s)")) { + /* There are no bandwidth values to parse. */ isValid = true; - } else { + } else if (partsNoOpt.length == 6) { + /* There are bandwidth values to parse. */ + values = partsNoOpt[5].split(",", -1); + } else if (partsNoOpt[4].length() > 2) { + /* There are bandwidth values to parse, but there is no space + * between "s)" and "0,0,0,0". Very old Tor versions around + * Tor 0.0.8 wrote such history lines, and even though + * dir-spec.txt implies a space here, the old format isn't + * totally broken. Let's pretend there's a space. */ + values = partsNoOpt[4].substring(2).split(",", -1); + } + if (values != null) { long endMillis = this.historyEndMillis; - String[] values = partsNoOpt[5].split(",", -1); for (int i = values.length - 1; i >= 0; i--) { long bandwidthValue = Long.parseLong(values[i]); if (bandwidthValue < 0L) { diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java index afaacc7..27e5153 100644 --- a/src/org/torproject/descriptor/impl/DescriptorImpl.java +++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java @@ -78,6 +78,11 @@ public abstract class DescriptorImpl implements Descriptor { firstLines.contains("\nnetwork-status-version 2\n")) { parsedDescriptors.add(new RelayNetworkStatusImpl(rawDescriptorBytes, failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type directory 1.0\n") || + firstLines.startsWith("signed-directory\n") || + firstLines.contains("\nsigned-directory\n")) { + parsedDescriptors.add(new RelayDirectoryImpl(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); } else { throw new DescriptorParseException("Could not detect descriptor " + "type in descriptor starting with '" + firstLines + "'."); diff --git a/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java b/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java new file mode 100644 index 0000000..03d727e --- /dev/null +++ b/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java @@ -0,0 +1,513 @@ +/* Copyright 2012 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.RelayDirectory; +import org.torproject.descriptor.RouterStatusEntry; +import org.torproject.descriptor.ServerDescriptor; + +/* TODO Write unit tests. */ + +public class RelayDirectoryImpl extends DescriptorImpl + implements RelayDirectory { + + protected static List<RelayDirectory> parseDirectories( + byte[] directoriesBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<RelayDirectory> parsedDirectories = + new ArrayList<RelayDirectory>(); + List<byte[]> splitDirectoriesBytes = + DescriptorImpl.splitRawDescriptorBytes(directoriesBytes, + "signed-directory\n"); + for (byte[] directoryBytes : splitDirectoriesBytes) { + RelayDirectory parsedDirectory = + new RelayDirectoryImpl(directoryBytes, + failUnrecognizedDescriptorLines); + parsedDirectories.add(parsedDirectory); + } + return parsedDirectories; + } + + protected RelayDirectoryImpl(byte[] directoryBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(directoryBytes, failUnrecognizedDescriptorLines, true); + this.splitAndParseParts(rawDescriptorBytes); + this.calculateDigest(); + Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList(( + "signed-directory,recommended-software," + + "directory-signature").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList( + "dir-signing-key,running-routers,router-status".split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("signed-directory"); + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "signed-directory\n"; + String sigToken = "\ndirectory-signature "; + if (!ascii.contains(sigToken)) { + return; + } + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + sig = ascii.indexOf("\n", sig) + 1; + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, sig - start); + this.directoryDigest = DigestUtils.shaHex(forDigest); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } + if (this.directoryDigest == null) { + throw new DescriptorParseException("Could not calculate v1 " + + "directory digest."); + } + } + + private void splitAndParseParts(byte[] rawDescriptorBytes) + throws DescriptorParseException { + if (this.rawDescriptorBytes.length == 0) { + throw new DescriptorParseException("Descriptor is empty."); + } + String descriptorString = new String(rawDescriptorBytes); + int startIndex = 0; + int firstRouterIndex = this.findFirstIndexOfKeyword(descriptorString, + "router"); + int directorySignatureIndex = this.findFirstIndexOfKeyword( + descriptorString, "directory-signature"); + int endIndex = descriptorString.length(); + if (directorySignatureIndex < 0) { + directorySignatureIndex = endIndex; + } + if (firstRouterIndex < 0) { + firstRouterIndex = directorySignatureIndex; + } + if (firstRouterIndex > startIndex) { + this.parseHeaderBytes(descriptorString, startIndex, + firstRouterIndex); + } + if (directorySignatureIndex > firstRouterIndex) { + this.parseServerDescriptorBytes(descriptorString, firstRouterIndex, + directorySignatureIndex); + } + if (endIndex > directorySignatureIndex) { + this.parseDirectorySignatureBytes(descriptorString, + directorySignatureIndex, endIndex); + } + } + + private int findFirstIndexOfKeyword(String descriptorString, + String keyword) { + if (descriptorString.startsWith(keyword)) { + return 0; + } else if (descriptorString.contains("\n" + keyword + " ")) { + return descriptorString.indexOf("\n" + keyword + " ") + 1; + } else if (descriptorString.contains("\n" + keyword + "\n")) { + return descriptorString.indexOf("\n" + keyword + "\n") + 1; + } else { + return -1; + } + } + + private void parseHeaderBytes(String descriptorString, int start, + int end) throws DescriptorParseException { + byte[] headerBytes = new byte[end - start]; + System.arraycopy(this.rawDescriptorBytes, start, + headerBytes, 0, end - start); + this.parseHeader(headerBytes); + } + + private void parseServerDescriptorBytes(String descriptorString, + int start, int end) throws DescriptorParseException { + List<byte[]> splitServerDescriptorBytes = + this.splitByKeyword(descriptorString, "router", start, end); + for (byte[] statusEntryBytes : splitServerDescriptorBytes) { + this.parseServerDescriptor(statusEntryBytes); + } + } + + private void parseDirectorySignatureBytes(String descriptorString, + int start, int end) throws DescriptorParseException { + List<byte[]> splitDirectorySignatureBytes = this.splitByKeyword( + descriptorString, "directory-signature", start, end); + for (byte[] directorySignatureBytes : splitDirectorySignatureBytes) { + this.parseDirectorySignature(directorySignatureBytes); + } + } + + private List<byte[]> splitByKeyword(String descriptorString, + String keyword, int start, int end) { + List<byte[]> splitParts = new ArrayList<byte[]>(); + int from = start; + while (from < end) { + int to = descriptorString.indexOf("\n" + keyword + " ", from); + if (to < 0) { + to = descriptorString.indexOf("\n" + keyword + "\n", from); + } + if (to < 0) { + to = end; + } else { + to += 1; + } + int toNoNewline = to; + while (toNoNewline > from && + descriptorString.charAt(toNoNewline - 1) == '\n') { + toNoNewline--; + } + byte[] part = new byte[toNoNewline - from]; + System.arraycopy(this.rawDescriptorBytes, from, part, 0, + toNoNewline - from); + from = to; + splitParts.add(part); + } + return splitParts; + } + + private void parseHeader(byte[] headerBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); + String publishedLine = null, nextCrypto = null, + runningRoutersLine = null, routerStatusLine = null; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + if (line.isEmpty() || line.startsWith("@")) { + continue; + } + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String[] partsNoOpt = lineNoOpt.split(" "); + String keyword = partsNoOpt[0]; + if (keyword.equals("signed-directory")) { + this.parseSignedDirectoryLine(line, lineNoOpt, partsNoOpt); + } else if (keyword.equals("published")) { + if (publishedLine != null) { + throw new DescriptorParseException("Keyword 'published' is " + + "contained more than once, but must be contained exactly " + + "once."); + } else { + publishedLine = line; + } + } else if (keyword.equals("dir-signing-key")) { + this.parseDirSigningKeyLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "dir-signing-key"; + } else if (keyword.equals("recommended-software")) { + this.parseRecommendedSoftwareLine(line, lineNoOpt, partsNoOpt); + } else if (keyword.equals("running-routers")) { + runningRoutersLine = line; + } else if (keyword.equals("router-status")) { + routerStatusLine = line; + } 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("dir-signing-key") && + this.dirSigningKey == null) { + this.dirSigningKey = cryptoString; + } else { + throw new DescriptorParseException("Unrecognized crypto " + + "block in v1 directory."); + } + nextCrypto = null; + } else if (crypto != null) { + crypto.append(line + "\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in v1 directory."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.add(line); + } + } + } + if (publishedLine == null) { + throw new DescriptorParseException("Keyword 'published' is " + + "contained 0 times, but must be contained exactly once."); + } else { + String publishedLineNoOpt = publishedLine.startsWith("opt ") ? + publishedLine.substring("opt ".length()) : publishedLine; + String[] publishedPartsNoOpt = publishedLineNoOpt.split(" "); + this.parsePublishedLine(publishedLine, publishedLineNoOpt, + publishedPartsNoOpt); + } + if (routerStatusLine != null) { + String routerStatusLineNoOpt = routerStatusLine.startsWith("opt ") ? + routerStatusLine.substring("opt ".length()) : routerStatusLine; + String[] routerStatusPartsNoOpt = routerStatusLineNoOpt.split(" "); + this.parseRouterStatusLine(routerStatusLine, routerStatusLineNoOpt, + routerStatusPartsNoOpt); + } else if (runningRoutersLine != null) { + String runningRoutersLineNoOpt = + runningRoutersLine.startsWith("opt ") ? + runningRoutersLine.substring("opt ".length()) : + runningRoutersLine; + String[] runningRoutersPartsNoOpt = + runningRoutersLineNoOpt.split(" "); + this.parseRunningRoutersLine(runningRoutersLine, + runningRoutersLineNoOpt, runningRoutersPartsNoOpt); + } else { + throw new DescriptorParseException("Either running-routers or " + + "router-status line must be given."); + } + } + + protected void parseServerDescriptor(byte[] serverDescriptorBytes) { + try { + ServerDescriptorImpl serverDescriptor = new ServerDescriptorImpl( + serverDescriptorBytes, this.failUnrecognizedDescriptorLines); + this.serverDescriptors.add(serverDescriptor); + } catch (DescriptorParseException e) { + this.serverDescriptorParseExceptions.add(e); + } + } + + private void parseDirectorySignature(byte[] directorySignatureBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(directorySignatureBytes)). + useDelimiter("\n"); + String nextCrypto = null; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String[] partsNoOpt = lineNoOpt.split(" "); + String keyword = partsNoOpt[0]; + if (keyword.equals("directory-signature")) { + this.parseDirectorySignatureLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "directory-signature"; + } 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("directory-signature")) { + this.directorySignature = cryptoString; + } else { + throw new DescriptorParseException("Unrecognized crypto " + + "block in v2 network status."); + } + nextCrypto = null; + } else if (crypto != null) { + crypto.append(line + "\n"); + } else if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in v2 network status."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.add(line); + } + } + } + + private void parseSignedDirectoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("signed-directory")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parsePublishedLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, + partsNoOpt, 1, 2); + } + + private void parseDirSigningKeyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length > 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } else if (partsNoOpt.length == 2) { + /* Early directories didn't have a crypto object following the + * "dir-signing-key" line, but had the key base64-encoded in the + * same line. */ + StringBuilder sb = new StringBuilder(); + sb.append("-----BEGIN RSA PUBLIC KEY-----\n"); + String keyString = partsNoOpt[1]; + while (keyString.length() > 64) { + sb.append(keyString.substring(0, 64) + "\n"); + keyString = keyString.substring(64); + } + if (keyString.length() > 0) { + sb.append(keyString + "\n"); + } + sb.append("-----END RSA PUBLIC KEY-----\n"); + this.dirSigningKey = sb.toString(); + } + } + + private void parseRecommendedSoftwareLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + List<String> result = new ArrayList<String>(); + if (partsNoOpt.length > 2) { + throw new DescriptorParseException("Illegal versions line '" + line + + "'."); + } else if (partsNoOpt.length == 2) { + String[] versions = partsNoOpt[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); + } + } + this.recommendedSoftware = result; + } + + private void parseRunningRoutersLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + for (int i = 1; i < partsNoOpt.length; i++) { + String part = partsNoOpt[i]; + String debugLine = "running-routers [...] " + part + " [...]"; + boolean isLive = true; + if (part.startsWith("!")) { + isLive = false; + part = part.substring(1); + } + boolean isVerified; + String fingerprint = null, nickname = null; + if (part.startsWith("$")) { + isVerified = false; + fingerprint = ParseHelper.parseTwentyByteHexString(debugLine, + part.substring(1)); + } else { + isVerified = true; + nickname = ParseHelper.parseNickname(debugLine, part); + } + this.statusEntries.add(new RouterStatusEntryImpl(fingerprint, + nickname, isLive, isVerified)); + } + } + + private void parseRouterStatusLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + for (int i = 1; i < partsNoOpt.length; i++) { + String part = partsNoOpt[i]; + String debugLine = "router-status [...] " + part + " [...]"; + RouterStatusEntry entry = null; + if (part.contains("=")) { + String[] partParts = part.split("="); + if (partParts.length == 2) { + boolean isVerified = true, isLive; + String nickname; + if (partParts[0].startsWith("!")) { + isLive = false; + nickname = ParseHelper.parseNickname(debugLine, + partParts[0].substring(1)); + } else { + isLive = true; + nickname = ParseHelper.parseNickname(debugLine, partParts[0]); + } + String fingerprint = ParseHelper.parseTwentyByteHexString( + debugLine, partParts[1].substring(1)); + entry = new RouterStatusEntryImpl(fingerprint, nickname, isLive, + isVerified); + } + } else { + boolean isVerified = false, isLive; + String nickname = null, fingerprint; + if (part.startsWith("!")) { + isLive = false; + fingerprint = ParseHelper.parseTwentyByteHexString( + debugLine, part.substring(2)); + } else { + isLive = true; + fingerprint = ParseHelper.parseTwentyByteHexString( + debugLine, part.substring(1));; + } + entry = new RouterStatusEntryImpl(fingerprint, nickname, isLive, + isVerified); + } + if (entry == null) { + throw new DescriptorParseException("Illegal router-status entry '" + + part + "' in v1 directory."); + } + this.statusEntries.add(entry); + } + } + + private void parseDirectorySignatureLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]); + } + + private long publishedMillis; + public long getPublishedMillis() { + return this.publishedMillis; + } + + private String dirSigningKey; + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private List<String> recommendedSoftware; + public List<String> getRecommendedSoftware() { + return this.recommendedSoftware == null ? null : + new ArrayList<String>(this.recommendedSoftware); + } + + private String directorySignature; + public String getDirectorySignature() { + return this.directorySignature; + } + + private List<RouterStatusEntry> statusEntries = + new ArrayList<RouterStatusEntry>(); + public List<RouterStatusEntry> getRouterStatusEntries() { + return new ArrayList<RouterStatusEntry>(this.statusEntries); + } + + private List<ServerDescriptor> serverDescriptors = + new ArrayList<ServerDescriptor>(); + public List<ServerDescriptor> getServerDescriptors() { + return new ArrayList<ServerDescriptor>(this.serverDescriptors); + } + + private List<Exception> serverDescriptorParseExceptions = + new ArrayList<Exception>(); + public List<Exception> getServerDescriptorParseExceptions() { + return new ArrayList<Exception>(this.serverDescriptorParseExceptions); + } + + private String nickname; + public String getNickname() { + return this.nickname; + } + + private String directoryDigest; + public String getDirectoryDigest() { + return this.directoryDigest; + } +} + diff --git a/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java b/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java new file mode 100644 index 0000000..68f549e --- /dev/null +++ b/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java @@ -0,0 +1,37 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.RouterStatusEntry; + +public class RouterStatusEntryImpl implements RouterStatusEntry { + + protected RouterStatusEntryImpl(String fingerprint, String nickname, + boolean isLive, boolean isVerified) { + this.fingerprint = fingerprint; + this.nickname = nickname; + this.isLive = isLive; + this.isVerified = isVerified; + } + + private String nickname; + public String getNickname() { + return this.nickname; + } + + private String fingerprint; + public String getFingerprint() { + return this.fingerprint; + } + + private boolean isLive; + public boolean isLive() { + return this.isLive; + } + + private boolean isVerified; + public boolean isVerified() { + return this.isVerified; + } +} + diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java index 77b06dd..5237348 100644 --- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java +++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java @@ -123,6 +123,8 @@ public class ServerDescriptorImpl extends DescriptorImpl this.parseProtocolsLine(line, lineNoOpt, partsNoOpt); } else if (keyword.equals("allow-single-hop-exits")) { this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt); + } else if (keyword.equals("dircacheport")) { + this.parseDircacheportLine(line, lineNoOpt, partsNoOpt); } else if (line.startsWith("-----BEGIN")) { crypto = new StringBuilder(); crypto.append(line + "\n"); @@ -183,7 +185,7 @@ public class ServerDescriptorImpl extends DescriptorImpl
private void parseBandwidthLine(String line, String lineNoOpt, String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 4) { + if (partsNoOpt.length < 3 || partsNoOpt.length > 4) { throw new DescriptorParseException("Wrong number of values in line " + "'" + line + "'."); } @@ -191,11 +193,18 @@ public class ServerDescriptorImpl extends DescriptorImpl try { this.bandwidthRate = Integer.parseInt(partsNoOpt[1]); this.bandwidthBurst = Integer.parseInt(partsNoOpt[2]); - this.bandwidthObserved = Integer.parseInt(partsNoOpt[3]); + if (partsNoOpt.length == 4) { + this.bandwidthObserved = Integer.parseInt(partsNoOpt[3]); + } if (this.bandwidthRate >= 0 && this.bandwidthBurst >= 0 && this.bandwidthObserved >= 0) { isValid = true; } + if (partsNoOpt.length < 4) { + /* Tor versions 0.0.8 and older only wrote bandwidth lines with + * rate and burst values, but no observed value. */ + this.bandwidthObserved = -1; + } } catch (NumberFormatException e) { /* Handle below. */ } @@ -435,6 +444,21 @@ public class ServerDescriptorImpl extends DescriptorImpl this.allowSingleHopExits = true; }
+ private void parseDircacheportLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + /* The dircacheport line was only contained in server descriptors + * published by Tor 0.0.8 and before. It's only specified in old + * tor-spec.txt versions. */ + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + if (this.dirPort != 0) { + throw new DescriptorParseException("At most one of dircacheport " + + "and the directory port in the router line may be non-zero."); + } + this.dirPort = ParseHelper.parsePort(line, partsNoOpt[1]); + } + private void calculateDigest() throws DescriptorParseException { try { String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
tor-commits@lists.torproject.org