commit 600a020877bb82ee1fd5852a5694d268411e2ed4 Author: Karsten Loesing karsten.loesing@gmx.net Date: Tue Jul 5 10:28:06 2016 +0200
Move all Java sources to src/main/java/. --- build.xml | 10 +- .../torproject/descriptor/BandwidthHistory.java | 52 + .../descriptor/BridgeExtraInfoDescriptor.java | 25 + .../torproject/descriptor/BridgeNetworkStatus.java | 128 ++ .../descriptor/BridgePoolAssignment.java | 47 + .../descriptor/BridgeServerDescriptor.java | 24 + .../java/org/torproject/descriptor/Descriptor.java | 39 + .../torproject/descriptor/DescriptorCollector.java | 62 + .../descriptor/DescriptorDownloader.java | 198 +++ .../org/torproject/descriptor/DescriptorFile.java | 77 + .../descriptor/DescriptorParseException.java | 20 + .../torproject/descriptor/DescriptorParser.java | 47 + .../torproject/descriptor/DescriptorReader.java | 143 ++ .../torproject/descriptor/DescriptorRequest.java | 100 ++ .../descriptor/DescriptorSourceFactory.java | 187 +++ .../org/torproject/descriptor/DirSourceEntry.java | 96 ++ .../descriptor/DirectoryKeyCertificate.java | 109 ++ .../torproject/descriptor/DirectorySignature.java | 52 + .../java/org/torproject/descriptor/ExitList.java | 92 ++ .../org/torproject/descriptor/ExitListEntry.java | 55 + .../torproject/descriptor/ExtraInfoDescriptor.java | 646 ++++++++ .../ImplementationNotAccessibleException.java | 22 + .../org/torproject/descriptor/Microdescriptor.java | 135 ++ .../torproject/descriptor/NetworkStatusEntry.java | 177 ++ .../org/torproject/descriptor/RelayDirectory.java | 104 ++ .../descriptor/RelayExtraInfoDescriptor.java | 21 + .../torproject/descriptor/RelayNetworkStatus.java | 176 ++ .../descriptor/RelayNetworkStatusConsensus.java | 223 +++ .../descriptor/RelayNetworkStatusVote.java | 408 +++++ .../descriptor/RelayServerDescriptor.java | 20 + .../torproject/descriptor/RouterStatusEntry.java | 51 + .../torproject/descriptor/ServerDescriptor.java | 435 +++++ .../org/torproject/descriptor/TorperfResult.java | 215 +++ .../descriptor/impl/BandwidthHistoryImpl.java | 100 ++ .../descriptor/impl/BlockingIteratorImpl.java | 98 ++ .../impl/BridgeExtraInfoDescriptorImpl.java | 37 + .../descriptor/impl/BridgeNetworkStatusImpl.java | 230 +++ .../descriptor/impl/BridgePoolAssignmentImpl.java | 99 ++ .../impl/BridgeServerDescriptorImpl.java | 37 + .../descriptor/impl/DescriptorCollectorImpl.java | 249 +++ .../descriptor/impl/DescriptorDownloaderImpl.java | 283 ++++ .../descriptor/impl/DescriptorFileImpl.java | 78 + .../torproject/descriptor/impl/DescriptorImpl.java | 337 ++++ .../descriptor/impl/DescriptorParseException.java | 15 + .../descriptor/impl/DescriptorParserImpl.java | 28 + .../descriptor/impl/DescriptorReaderImpl.java | 364 ++++ .../descriptor/impl/DescriptorRequestImpl.java | 114 ++ .../descriptor/impl/DirSourceEntryImpl.java | 218 +++ .../descriptor/impl/DirectoryDownloader.java | 104 ++ .../impl/DirectoryKeyCertificateImpl.java | 308 ++++ .../descriptor/impl/DirectorySignatureImpl.java | 115 ++ .../descriptor/impl/DownloadCoordinator.java | 10 + .../descriptor/impl/DownloadCoordinatorImpl.java | 298 ++++ .../descriptor/impl/ExitListEntryImpl.java | 216 +++ .../torproject/descriptor/impl/ExitListImpl.java | 142 ++ .../descriptor/impl/ExtraInfoDescriptorImpl.java | 1284 +++++++++++++++ .../descriptor/impl/MicrodescriptorImpl.java | 328 ++++ .../descriptor/impl/NetworkStatusEntryImpl.java | 382 +++++ .../descriptor/impl/NetworkStatusImpl.java | 270 +++ .../torproject/descriptor/impl/ParseHelper.java | 567 +++++++ .../descriptor/impl/RelayDirectoryImpl.java | 547 ++++++ .../impl/RelayExtraInfoDescriptorImpl.java | 37 + .../impl/RelayNetworkStatusConsensusImpl.java | 414 +++++ .../descriptor/impl/RelayNetworkStatusImpl.java | 384 +++++ .../impl/RelayNetworkStatusVoteImpl.java | 761 +++++++++ .../descriptor/impl/RelayServerDescriptorImpl.java | 37 + .../descriptor/impl/RouterStatusEntryImpl.java | 41 + .../descriptor/impl/ServerDescriptorImpl.java | 985 +++++++++++ .../descriptor/impl/TorperfResultImpl.java | 546 ++++++ .../org/torproject/descriptor/package-info.java | 80 + .../torproject/descriptor/BandwidthHistory.java | 52 - .../descriptor/BridgeExtraInfoDescriptor.java | 25 - .../torproject/descriptor/BridgeNetworkStatus.java | 128 -- .../descriptor/BridgePoolAssignment.java | 47 - .../descriptor/BridgeServerDescriptor.java | 24 - src/org/torproject/descriptor/Descriptor.java | 39 - .../torproject/descriptor/DescriptorCollector.java | 62 - .../descriptor/DescriptorDownloader.java | 198 --- src/org/torproject/descriptor/DescriptorFile.java | 77 - .../descriptor/DescriptorParseException.java | 20 - .../torproject/descriptor/DescriptorParser.java | 47 - .../torproject/descriptor/DescriptorReader.java | 143 -- .../torproject/descriptor/DescriptorRequest.java | 100 -- .../descriptor/DescriptorSourceFactory.java | 187 --- src/org/torproject/descriptor/DirSourceEntry.java | 96 -- .../descriptor/DirectoryKeyCertificate.java | 109 -- .../torproject/descriptor/DirectorySignature.java | 52 - src/org/torproject/descriptor/ExitList.java | 92 -- src/org/torproject/descriptor/ExitListEntry.java | 55 - .../torproject/descriptor/ExtraInfoDescriptor.java | 646 -------- .../ImplementationNotAccessibleException.java | 22 - src/org/torproject/descriptor/Microdescriptor.java | 135 -- .../torproject/descriptor/NetworkStatusEntry.java | 177 -- src/org/torproject/descriptor/RelayDirectory.java | 104 -- .../descriptor/RelayExtraInfoDescriptor.java | 21 - .../torproject/descriptor/RelayNetworkStatus.java | 176 -- .../descriptor/RelayNetworkStatusConsensus.java | 223 --- .../descriptor/RelayNetworkStatusVote.java | 408 ----- .../descriptor/RelayServerDescriptor.java | 20 - .../torproject/descriptor/RouterStatusEntry.java | 51 - .../torproject/descriptor/ServerDescriptor.java | 435 ----- src/org/torproject/descriptor/TorperfResult.java | 215 --- .../descriptor/impl/BandwidthHistoryImpl.java | 100 -- .../descriptor/impl/BlockingIteratorImpl.java | 98 -- .../impl/BridgeExtraInfoDescriptorImpl.java | 37 - .../descriptor/impl/BridgeNetworkStatusImpl.java | 230 --- .../descriptor/impl/BridgePoolAssignmentImpl.java | 99 -- .../impl/BridgeServerDescriptorImpl.java | 37 - .../descriptor/impl/DescriptorCollectorImpl.java | 249 --- .../descriptor/impl/DescriptorDownloaderImpl.java | 283 ---- .../descriptor/impl/DescriptorFileImpl.java | 78 - .../torproject/descriptor/impl/DescriptorImpl.java | 337 ---- .../descriptor/impl/DescriptorParseException.java | 15 - .../descriptor/impl/DescriptorParserImpl.java | 28 - .../descriptor/impl/DescriptorReaderImpl.java | 364 ---- .../descriptor/impl/DescriptorRequestImpl.java | 114 -- .../descriptor/impl/DirSourceEntryImpl.java | 218 --- .../descriptor/impl/DirectoryDownloader.java | 104 -- .../impl/DirectoryKeyCertificateImpl.java | 308 ---- .../descriptor/impl/DirectorySignatureImpl.java | 115 -- .../descriptor/impl/DownloadCoordinator.java | 10 - .../descriptor/impl/DownloadCoordinatorImpl.java | 298 ---- .../descriptor/impl/ExitListEntryImpl.java | 216 --- .../torproject/descriptor/impl/ExitListImpl.java | 142 -- .../descriptor/impl/ExtraInfoDescriptorImpl.java | 1284 --------------- .../descriptor/impl/MicrodescriptorImpl.java | 328 ---- .../descriptor/impl/NetworkStatusEntryImpl.java | 382 ----- .../descriptor/impl/NetworkStatusImpl.java | 270 --- .../torproject/descriptor/impl/ParseHelper.java | 567 ------- .../descriptor/impl/RelayDirectoryImpl.java | 547 ------ .../impl/RelayExtraInfoDescriptorImpl.java | 37 - .../impl/RelayNetworkStatusConsensusImpl.java | 414 ----- .../descriptor/impl/RelayNetworkStatusImpl.java | 384 ----- .../impl/RelayNetworkStatusVoteImpl.java | 761 --------- .../descriptor/impl/RelayServerDescriptorImpl.java | 37 - .../descriptor/impl/RouterStatusEntryImpl.java | 41 - .../descriptor/impl/ServerDescriptorImpl.java | 985 ----------- .../descriptor/impl/TorperfResultImpl.java | 546 ------ src/org/torproject/descriptor/package-info.java | 80 - .../descriptor/benchmark/MeasurePerformance.java | 278 ++++ .../descriptor/impl/BridgeNetworkStatusTest.java | 151 ++ .../descriptor/impl/ConsensusBuilder.java | 321 ++++ .../impl/DescriptorCollectorImplTest.java | 134 ++ .../descriptor/impl/ExitListImplTest.java | 131 ++ .../impl/ExtraInfoDescriptorImplTest.java | 1737 ++++++++++++++++++++ .../descriptor/impl/MicrodescriptorImplTest.java | 82 + .../impl/RelayNetworkStatusConsensusImplTest.java | 1272 ++++++++++++++ .../impl/RelayNetworkStatusVoteImplTest.java | 1373 ++++++++++++++++ .../descriptor/impl/ServerDescriptorImplTest.java | 1605 ++++++++++++++++++ .../descriptor/impl/TorperfResultImplTest.java | 97 ++ .../descriptor/benchmark/MeasurePerformance.java | 278 ---- .../descriptor/impl/BridgeNetworkStatusTest.java | 151 -- .../descriptor/impl/ConsensusBuilder.java | 321 ---- .../impl/DescriptorCollectorImplTest.java | 134 -- .../descriptor/impl/ExitListImplTest.java | 131 -- .../impl/ExtraInfoDescriptorImplTest.java | 1737 -------------------- .../descriptor/impl/MicrodescriptorImplTest.java | 82 - .../impl/RelayNetworkStatusConsensusImplTest.java | 1272 -------------- .../impl/RelayNetworkStatusVoteImplTest.java | 1373 ---------------- .../descriptor/impl/ServerDescriptorImplTest.java | 1605 ------------------ .../descriptor/impl/TorperfResultImplTest.java | 97 -- 161 files changed, 21515 insertions(+), 21515 deletions(-)
diff --git a/build.xml b/build.xml index 6bb773b..0d6cf7d 100644 --- a/build.xml +++ b/build.xml @@ -1,10 +1,10 @@ <project default="jar" name="descriptor" basedir="."> <property name="release.version" value="1.2.0-dev" /> - <property name="sources" value="src"/> + <property name="sources" value="src/main/java"/> <property name="resources" value="resources"/> <property name="classes" value="classes"/> <property name="docs" value="javadoc"/> - <property name="tests" value="test"/> + <property name="tests" value="src/test/java"/> <property name="libs" value="lib"/> <property name="jarfile" value="descriptor-${release.version}.jar" /> <property name="jarsourcesfile" @@ -152,11 +152,11 @@ <include name="*.md" /> </tarfileset> <tarfileset dir="${sources}" - prefix="descriptor-${release.version}/src" /> + prefix="descriptor-${release.version}/${sources}" /> <tarfileset dir="${tests}" - prefix="descriptor-${release.version}/test" /> + prefix="descriptor-${release.version}/${tests}" /> <tarfileset dir="${libs}" - prefix="descriptor-${release.version}/lib" /> + prefix="descriptor-${release.version}/${libs}" /> </tar> </target> </project> diff --git a/src/main/java/org/torproject/descriptor/BandwidthHistory.java b/src/main/java/org/torproject/descriptor/BandwidthHistory.java new file mode 100644 index 0000000..0be1a53 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/BandwidthHistory.java @@ -0,0 +1,52 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.SortedMap; + +/** + * Contains the bandwidth history of a relay or bridge. + * + * <p>A bandwidth history is not a descriptor type of its own but usually + * part of extra-info descriptors ({@link ExtraInfoDescriptor}) or server + * descriptors ({@link ServerDescriptor}).</p> + * + * @since 1.0.0 + */ +public interface BandwidthHistory { + + /** + * Return the original bandwidth history line as contained in the + * descriptor, possibly prefixed with {@code "opt "}. + * + * @since 1.0.0 + */ + public String getLine(); + + /** + * Return the time in milliseconds since the epoch when the most recent + * interval ends. + * + * @since 1.0.0 + */ + public long getHistoryEndMillis(); + + /** + * Return the interval length in seconds. + * + * @since 1.0.0 + */ + public long getIntervalLength(); + + /** + * Return the (possibly empty) bandwidth history with map keys being + * times in milliseconds since the epoch when intervals end and map + * values being number of bytes used in the interval, ordered from + * oldest to newest interval. + * + * @since 1.0.0 + */ + public SortedMap<Long, Long> getBandwidthValues(); +} + diff --git a/src/main/java/org/torproject/descriptor/BridgeExtraInfoDescriptor.java b/src/main/java/org/torproject/descriptor/BridgeExtraInfoDescriptor.java new file mode 100644 index 0000000..a3c168d --- /dev/null +++ b/src/main/java/org/torproject/descriptor/BridgeExtraInfoDescriptor.java @@ -0,0 +1,25 @@ +/* Copyright 2015--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a sanitized bridge extra-info descriptor. + * + * <p>Sanitized bridge extra-info descriptors share many contents with + * relay extra-info descriptors ({@link RelayExtraInfoDescriptor}), which + * is why they share a common + * superinterface ({@link ExtraInfoDescriptor}). The main purpose of + * having two subinterfaces is being able to distinguish descriptor types + * more easily.</p> + * + * <p>Details about sanitizing bridge extra-info descriptors can be found + * <a href="https://collector.torproject.org/#type-bridge-extra-info">here</a>. + * </p> + * + * @since 1.1.0 + */ +public interface BridgeExtraInfoDescriptor extends ExtraInfoDescriptor { + +} + diff --git a/src/main/java/org/torproject/descriptor/BridgeNetworkStatus.java b/src/main/java/org/torproject/descriptor/BridgeNetworkStatus.java new file mode 100644 index 0000000..c7458fd --- /dev/null +++ b/src/main/java/org/torproject/descriptor/BridgeNetworkStatus.java @@ -0,0 +1,128 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.SortedMap; + +/** + * Contains a sanitized bridge network status document. + * + * <p>The bridge directory authority periodically publishes a network + * status document with one entry per known bridge in the network + * ({@link NetworkStatusEntry}) containing: a hash of its identity key, a + * hash of its most recent server descriptor, and a summary of what the + * bridge authority believed about its status.</p> + * + * <p>The main purpose of this document is to get an authoritative list of + * running bridges to the bridge distribution service BridgeDB.</p> + * + * <p>Details about sanitizing bridge network statuses can be found + * <a href="https://collector.torproject.org/#type-bridge-network-status">here</a>. + * </p> + * + * @since 1.0.0 + */ +public interface BridgeNetworkStatus extends Descriptor { + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the minimum uptime in seconds that this authority requires + * for assigning the Stable flag, or -1 if the authority doesn't report + * this value. + * + * @since 1.1.0 + */ + public long getStableUptime(); + + /** + * Return the minimum MTBF (mean time between failure) that this + * authority requires for assigning the Stable flag, or -1 if the + * authority doesn't report this value. + * + * @since 1.1.0 + */ + public long getStableMtbf(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Fast flag, or -1 if the authority doesn't report this + * value. + * + * @since 1.1.0 + */ + public long getFastBandwidth(); + + /** + * Return the minimum WFU (weighted fractional uptime) in percent that + * this authority requires for assigning the Guard flag, or -1 if the + * authority doesn't report this value. + * + * @since 1.1.0 + */ + public double getGuardWfu(); + + /** + * Return the minimum weighted time in seconds that this authority + * needs to know about a relay before assigning the Guard flag, or -1 if + * the authority doesn't report this information. + * + * @since 1.1.0 + */ + public long getGuardTk(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can be guards, or -1 if the + * authority doesn't report this value. + * + * @since 1.1.0 + */ + public long getGuardBandwidthIncludingExits(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can not be guards, or -1 if the + * authority doesn't report this value. + * + * @since 1.1.0 + */ + public long getGuardBandwidthExcludingExits(); + + /** + * Return 1 if the authority has measured enough MTBF info to use the + * MTBF requirement instead of the uptime requirement for assigning the + * Stable flag, 0 if not, or -1 if the authority doesn't report this + * information. + * + * @since 1.1.0 + */ + public int getEnoughMtbfInfo(); + + /** + * Return 1 if the authority has enough measured bandwidths that it'll + * ignore the advertised bandwidth claims of routers without measured + * bandwidth, 0 if not, or -1 if the authority doesn't report this + * information. + * + * @since 1.1.0 + */ + public int getIgnoringAdvertisedBws(); + + /** + * Return status entries for each contained bridge, with map keys being + * SHA-1 digests of SHA-1 digest of the bridges' public identity keys, + * encoded as 40 upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public SortedMap<String, NetworkStatusEntry> getStatusEntries(); +} + diff --git a/src/main/java/org/torproject/descriptor/BridgePoolAssignment.java b/src/main/java/org/torproject/descriptor/BridgePoolAssignment.java new file mode 100644 index 0000000..2de4ee9 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/BridgePoolAssignment.java @@ -0,0 +1,47 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.SortedMap; + +/** + * Contains a sanitized list of bridges together with the distribution + * pools they have been assigned to by the bridge distribution service + * BridgeDB. + * + * <p>BridgeDB receives bridge network statuses + * ({@link BridgeNetworkStatus}) from the bridge authority, assigns these + * bridges to persistent distribution rings, and hands them out to bridge + * users. BridgeDB periodically dumps the list of running bridges with + * information about the rings, subrings, and file buckets to which they + * are assigned to a local file.</p> + * + * <p>Details about sanitizing bridge pool assignments can be found + * <a href="https://collector.torproject.org/#type-bridge-pool-assignment">here</a>. + * </p> + * + * @since 1.0.0 + */ +public interface BridgePoolAssignment extends Descriptor { + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the entries contained in this bridge pool assignment list + * with map keys being SHA-1 digests of SHA-1 digest of the bridges' + * public identity keys, encoded as 40 upper-case hexadecimal + * characters, and map values being assignment strings, e.g. + * {@code "https ring=3 flag=stable"}. + * + * @since 1.0.0 + */ + public SortedMap<String, String> getEntries(); +} + diff --git a/src/main/java/org/torproject/descriptor/BridgeServerDescriptor.java b/src/main/java/org/torproject/descriptor/BridgeServerDescriptor.java new file mode 100644 index 0000000..7d4503f --- /dev/null +++ b/src/main/java/org/torproject/descriptor/BridgeServerDescriptor.java @@ -0,0 +1,24 @@ +/* Copyright 2015--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a sanitized bridge server descriptor. + * + * <p>Sanitized bridge server descriptors share many contents with relay + * server descriptors ({@link RelayServerDescriptor}), which is why they + * share a common superinterface ({@link ServerDescriptor}). The main + * purpose of having two subinterfaces is being able to distinguish + * descriptor types more easily.</p> + * + * <p>Details about sanitizing bridge server descriptors can be found + * <a href="https://collector.torproject.org/#type-bridge-server-descriptor">here</a>. + * </p> + * + * @since 1.1.0 + */ +public interface BridgeServerDescriptor extends ServerDescriptor { + +} + diff --git a/src/main/java/org/torproject/descriptor/Descriptor.java b/src/main/java/org/torproject/descriptor/Descriptor.java new file mode 100644 index 0000000..7cad109 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/Descriptor.java @@ -0,0 +1,39 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Superinterface for any descriptor with access to generic information + * about the descriptor. + * + * @since 1.0.0 + */ +public interface Descriptor { + + /** + * Return the raw descriptor bytes. + * + * @since 1.0.0 + */ + public byte[] getRawDescriptorBytes(); + + /** + * Return the (possibly empty) list of annotations in the format + * {@code "@key( value)*"}. + * + * @since 1.0.0 + */ + public List<String> getAnnotations(); + + /** + * Return any unrecognized lines when parsing this descriptor, or an + * empty list if there were no unrecognized lines. + * + * @since 1.0.0 + */ + public List<String> getUnrecognizedLines(); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorCollector.java b/src/main/java/org/torproject/descriptor/DescriptorCollector.java new file mode 100644 index 0000000..b1027dc --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorCollector.java @@ -0,0 +1,62 @@ +/* Copyright 2015--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.io.File; + +/** + * Descriptor source that synchronizes descriptors from the CollecTor + * service to a given local directory. + * + * <p>This type is not a descriptor source in the proper sense, because it + * does not produce descriptors by itself. But it often creates the + * prerequisites for reading descriptors from disk using + * {@link DescriptorReader}.</p> + * + * <p>Code sample:</p> + * <pre>{@code + * DescriptorCollector descriptorCollector = + * DescriptorSourceFactory.createDescriptorCollector(); + * descriptorCollector.collectDescriptors( + * // Download from Tor's main CollecTor instance, + * "https://collector.torproject.org", + * // include network status consensuses and relay server descriptors + * new String[] { "/recent/relay-descriptors/consensuses/", + * "/recent/relay-descriptors/server-descriptors/" }, + * // regardless of last-modified time, + * 0L, + * // write to the local directory called in/, + * new File("in"), + * // and delete extraneous files that do not exist remotely anymore. + * true); + * }</pre> + * + * @since 1.0.0 + */ +public interface DescriptorCollector { + + /** + * Fetch remote files from a CollecTor instance that do not yet exist + * locally and possibly delete local files that do not exist remotely + * anymore. + * + * @param collecTorBaseUrl CollecTor base URL without trailing slash, + * e.g., {@code "https://collector.torproject.org%22%7D + * @param remoteDirectories Remote directories to collect descriptors + * from, e.g., + * {@code "/recent/relay-descriptors/server-descriptors/"}, without + * processing subdirectories unless they are explicitly listed + * @param minLastModified Minimum last-modified time in milliseconds of + * files to be collected, or 0 for collecting all files + * @param localDirectory Directory where collected files will be written + * @param deleteExtraneousLocalFiles Whether to delete all local files + * that do not exist remotely anymore + * + * @since 1.0.0 + */ + public void collectDescriptors(String collecTorBaseUrl, + String[] remoteDirectories, long minLastModified, + File localDirectory, boolean deleteExtraneousLocalFiles); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorDownloader.java b/src/main/java/org/torproject/descriptor/DescriptorDownloader.java new file mode 100644 index 0000000..f0b1101 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorDownloader.java @@ -0,0 +1,198 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.Iterator; +import java.util.Set; + +/** + * Descriptor source that downloads relay descriptors from directory + * authorities or mirrors. + * + * <p>Downloading descriptors is done in a batch which starts after + * setting any configuration options and initiating the download + * process.</p> + * + * @since 1.0.0 + */ +public interface DescriptorDownloader { + + /** + * Add a directory authority to download descriptors from, which is + * only required for downloading network status votes and will be used + * when no directory mirrors are available. + * + * @since 1.0.0 + */ + public void addDirectoryAuthority(String nickname, String ip, + int dirPort); + + /** + * Add a directory mirror to download descriptors from, which is + * preferred for downloading descriptors, except for network status + * votes which are only available on directory authorities. + * + * @since 1.0.0 + */ + public void addDirectoryMirror(String nickname, String ip, int dirPort); + + /** + * Include the current network status consensus in the downloads. + * + * @since 1.0.0 + */ + public void setIncludeCurrentConsensus(); + + /** + * Include the current network status consensus in the downloads, and + * attempt to download it from all directory authorities. + * + * <p>The primary purpose of doing this is to compare different + * consensuses and download characteristics to each other. Typically, + * downloading from a single directory mirror or authority is + * sufficient.</p> + * + * @since 1.0.0 + */ + public void setIncludeCurrentConsensusFromAllDirectoryAuthorities(); + + /** + * Include the current network status votes referenced from a + * previously downloaded consensus in the downloads, which requires + * downloading the current consensus from at least one directory mirror + * or authority. + * + * @since 1.0.0 + */ + public void setIncludeCurrentReferencedVotes(); + + /** + * Include the current network status vote published by the given + * directory authority in the downloads, which requires downloading from + * at least one directory authority. + * + * @since 1.0.0 + */ + public void setIncludeCurrentVote(String fingerprint); + + /** + * Include the current network status votes published by the given + * directory authorities in the downloads, which requires downloading + * from at least one directory authority. + * + * @since 1.0.0 + */ + public void setIncludeCurrentVotes(Set<String> fingerprints); + + /** + * Include all server descriptors referenced from a previously + * downloaded network status consensus in the downloads. + * + * @since 1.0.0 + */ + public void setIncludeReferencedServerDescriptors(); + + /** + * Exclude the server descriptor with the given identifier from the + * downloads even if it's referenced from a consensus and we're supposed + * to download all referenced server descriptors. + * + * @since 1.0.0 + */ + public void setExcludeServerDescriptor(String identifier); + + /** + * Exclude the server descriptors with the given identifiers from the + * downloads even if they are referenced from a consensus and we're + * supposed to download all referenced server descriptors. + * + * @since 1.0.0 + */ + public void setExcludeServerDescriptors(Set<String> identifier); + + /** + * Include all extra-info descriptors referenced from previously + * downloaded server descriptors in the downloads. + * + * @since 1.0.0 + */ + public void setIncludeReferencedExtraInfoDescriptors(); + + /** + * Exclude the extra-info descriptor with the given identifier from the + * downloads even if it's referenced from a server descriptor and we're + * supposed to download all referenced extra-info descriptors. + * + * @since 1.0.0 + */ + public void setExcludeExtraInfoDescriptor(String identifier); + + /** + * Exclude the extra-info descriptors with the given identifiers from + * the downloads even if they are referenced from server descriptors + * and we're supposed to download all referenced extra-info + * descriptors. + * + * @since 1.0.0 + */ + public void setExcludeExtraInfoDescriptors(Set<String> identifiers); + + /** + * Define a connect timeout for a single request. + * + * <p>If a timeout expires, no further requests will be sent to the + * directory authority or mirror. Setting this value to 0 disables the + * connect timeout. Default value is 1 minute (60 * 1000).</p> + * + * @since 1.0.0 + */ + public void setConnectTimeout(long connectTimeoutMillis); + + /** + * Define a read timeout for a single request. + * + * <p>If a timeout expires, no further requests will be sent to the + * directory authority or mirror. Setting this value to 0 disables the + * read timeout. Default value is 1 minute (60 * 1000).</p> + * + * @since 1.0.0 + */ + public void setReadTimeout(long readTimeoutMillis); + + /** + * Define a global timeout for all requests. + * + * <p>Once this timeout expires, all running requests are aborted and no + * further requests are made. Setting this value to 0 disables the + * global timeout. Default is 1 hour (60 * 60 * 1000).</p> + * + * @since 1.0.0 + */ + public void setGlobalTimeout(long globalTimeoutMillis); + + /** + * Fail descriptor parsing when encountering an unrecognized line. + * + * <p>This option is not set by default, because the Tor specifications + * allow for new lines to be added that shall be ignored by older Tor + * versions. But some applications may want to handle unrecognized + * descriptor lines explicitly.</p> + * + * @since 1.0.0 + */ + public void setFailUnrecognizedDescriptorLines(); + + /** + * Download the previously configured relay descriptors and make them + * available via the returned blocking iterator. + * + * <p>Whenever the downloader runs out of descriptors and expects to + * provide more shortly after, it blocks the caller. This method can + * only be run once.</p> + * + * @since 1.0.0 + */ + public Iterator<DescriptorRequest> downloadDescriptors(); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorFile.java b/src/main/java/org/torproject/descriptor/DescriptorFile.java new file mode 100644 index 0000000..417d7f9 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorFile.java @@ -0,0 +1,77 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.io.File; +import java.util.List; + +/** + * Container for descriptors read from a file. + * + * <p>When the {@link DescriptorReader} reads descriptors from local files + * it provides an iterator over these containers which in turn contain + * references to classes implementing the {@link Descriptor} interface. + * This container also stores potentially useful meta-data about the + * descriptor file.</p> + * + * @since 1.0.0 + */ +public interface DescriptorFile { + + /** + * Return the directory where this descriptor file was contained, or + * null if the file was contained in a tarball. + * + * @since 1.0.0 + */ + public File getDirectory(); + + /** + * Return the tarball where this descriptor file was contained, or null + * if the file was not contained in a tarball. + * + * @since 1.0.0 + */ + public File getTarball(); + + /** + * Return the descriptor file itself, or null if the descriptor file + * was contained in a tarball. + * + * @since 1.0.0 + */ + public File getFile(); + + /** + * Return the descriptor file name, which is either the absolute path + * of the file on disk, or the tar file entry name. + * + * @since 1.0.0 + */ + public String getFileName(); + + /** + * Return the time in milliseconds since the epoch when the descriptor + * file on disk was last modified. + * + * @since 1.0.0 + */ + public long getLastModified(); + + /** + * Return the descriptors contained in the descriptor file. + * + * @since 1.0.0 + */ + public List<Descriptor> getDescriptors(); + + /** + * Return the first exception that was thrown when reading this file or + * parsing its content, or null if no exception was thrown. + * + * @since 1.0.0 + */ + public Exception getException(); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorParseException.java b/src/main/java/org/torproject/descriptor/DescriptorParseException.java new file mode 100644 index 0000000..309d3f7 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorParseException.java @@ -0,0 +1,20 @@ +/* Copyright 2014--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Thrown if raw descriptor contents cannot be parsed to one or more + * {@link Descriptor} instances, according to descriptor specifications. + * + * @since 1.0.0 + */ +@SuppressWarnings("deprecation") +public class DescriptorParseException + extends org.torproject.descriptor.impl.DescriptorParseException { + private static final long serialVersionUID = 100L; + public DescriptorParseException(String message) { + super(message); + } +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorParser.java b/src/main/java/org/torproject/descriptor/DescriptorParser.java new file mode 100644 index 0000000..680b8b2 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorParser.java @@ -0,0 +1,47 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Descriptor source that parses descriptors from raw descriptor contents. + * + * <p>Unlike most of the other descriptor sources this descriptor source + * does not operate in a batch-processing mode. It takes the raw + * descriptor contents of one or more descriptors, parses them, and + * returns a list of descriptors.</p> + * + * <p>This descriptor source is internally used by other descriptor + * sources but can also be used directly by applications that obtain + * raw descriptor contents via other means than one of the existing + * descriptor sources.</p> + * + * @since 1.0.0 + */ +public interface DescriptorParser { + + /** + * Fail descriptor parsing when encountering an unrecognized line. + * + * <p>This option is not set by default, because the Tor specifications + * allow for new lines to be added that shall be ignored by older Tor + * versions. But some applications may want to handle unrecognized + * descriptor lines explicitly.</p> + * + * @since 1.0.0 + */ + public void setFailUnrecognizedDescriptorLines( + boolean failUnrecognizedDescriptorLines); + + /** + * Parse descriptors in the given byte array, possibly parsing the + * publication time from the file name, depending on the descriptor + * type. + * + * @since 1.0.0 + */ + public List<Descriptor> parseDescriptors(byte[] rawDescriptorBytes, + String fileName) throws DescriptorParseException; +} diff --git a/src/main/java/org/torproject/descriptor/DescriptorReader.java b/src/main/java/org/torproject/descriptor/DescriptorReader.java new file mode 100644 index 0000000..771755e --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorReader.java @@ -0,0 +1,143 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.io.File; +import java.util.Iterator; +import java.util.SortedMap; + +/** + * Descriptor source that reads descriptors from local files and provides + * an iterator over parsed descriptors. + * + * <p>This descriptor source is likely the most widely used one, possibly + * in combination with {@link DescriptorCollector} to synchronize + * descriptors from the CollecTor service.</p> + * + * <p>Reading descriptors is done in a batch which starts after setting + * any configuration options and initiating the read process.</p> + * + * <p>Code sample:</p> + * <pre>{@code + * DescriptorReader descriptorReader = + * DescriptorSourceFactory.createDescriptorReader(); + * // Read descriptors from local directory called in/. + * descriptorReader.addDirectory(new File("in")); + * Iterator<DescriptorFile> descriptorFiles = + * descriptorReader.readDescriptors(); + * while (descriptorFiles.hasNext()) { + * DescriptorFile descriptorFile = descriptorFiles.next(); + * for (Descriptor descriptor : descriptorFile.getDescriptors()) { + * if ((descriptor instanceof RelayNetworkStatusConsensus)) { + * // Only process network status consensuses, ignore the rest. + * RelayNetworkStatusConsensus consensus = + * (RelayNetworkStatusConsensus) descriptor; + * processConsensus(consensus); + * } + * } + * }}</pre> + * + * @since 1.0.0 + */ +public interface DescriptorReader { + + /** + * Add a local directory to read descriptors from, which may contain + * descriptor files or tarballs containing descriptor files. + * + * @since 1.0.0 + */ + public void addDirectory(File directory); + + /** + * Add a tarball to read descriptors from, which may be uncompressed, + * bz2-compressed, or xz-compressed. + * + * @since 1.0.0 + */ + public void addTarball(File tarball); + + /** + * Exclude files that are listed in the given history file and that + * haven't changed since they have last been read. + * + * <p>Add a new line for each descriptor that is read in this execution + * and remove lines for files that don't exist anymore.</p> + * + * <p>Lines in the history file contain the last modified time in + * milliseconds since the epoch and the absolute path of a file.</p> + * + * @since 1.0.0 + */ + public void setExcludeFiles(File historyFile); + + /** + * Exclude files if they haven't changed since the corresponding last + * modified timestamps. + * + * <p>Can be used instead of (or in addition to) a history file.</p> + * + * @since 1.0.0 + */ + public void setExcludedFiles(SortedMap<String, Long> excludedFiles); + + /** + * Return files and last modified timestamps of files that exist in the + * input directory or directories, but that have been excluded from + * parsing, because they haven't changed since they were last read. + * + * <p>Can be used instead of (or in addition to) a history file when + * combined with the set of parsed files.</p> + * + * @since 1.0.0 + */ + public SortedMap<String, Long> getExcludedFiles(); + + /** + * Return files and last modified timestamps of files that exist in the + * input directory or directories and that have been parsed. + * + * <p>Can be used instead of (or in addition to) a history file when + * combined with the set of excluded files.</p> + * + * @since 1.0.0 + */ + public SortedMap<String, Long> getParsedFiles(); + + /** + * Fail descriptor parsing when encountering an unrecognized line. + * + * <p>This option is not set by default, because the Tor specifications + * allow for new lines to be added that shall be ignored by older Tor + * versions. But some applications may want to handle unrecognized + * descriptor lines explicitly.</p> + * + * @since 1.0.0 + */ + public void setFailUnrecognizedDescriptorLines(); + + /** + * Don't keep more than this number of parsed descriptor files in the + * queue. + * + * <p>The default is 100, but if descriptor files contain hundreds or + * even thousands of descriptors, that default may be too high.</p> + * + * @since 1.0.0 + */ + public void setMaxDescriptorFilesInQueue(int max); + + /** + * Read the previously configured descriptors and make them available + * via the returned blocking iterator. + * + * <p>Whenever the reader runs out of descriptors and expects to provide + * more shortly after, it blocks the caller. This method can only be + * run once.</p> + * + * @since 1.0.0 + */ + public Iterator<DescriptorFile> readDescriptors(); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorRequest.java b/src/main/java/org/torproject/descriptor/DescriptorRequest.java new file mode 100644 index 0000000..c36c0c0 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorRequest.java @@ -0,0 +1,100 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Container for descriptors downloaded from a directory authority or + * mirror. + * + * <p>When the {@link DescriptorDownloader} downloads descriptors from + * directory authorities or mirrors it provides an iterator over these + * containers which in turn contain references to classes implementing the + * {@link Descriptor} interface. This container also stores potentially + * useful meta-data about the descriptor request.</p> + * + * @since 1.0.0 + */ +public interface DescriptorRequest { + + /** + * Return the request URL that was used in this request. + * + * @since 1.0.0 + */ + public String getRequestUrl(); + + /** + * Return the nickname of the directory mirror or authority as + * previously configured. + * + * @since 1.0.0 + */ + public String getDirectoryNickname(); + + /** + * Return the first exception that was thrown when making this request + * or parsing the response, or null if no exception was thrown. + * + * @since 1.0.0 + */ + public Exception getException(); + + /** + * Return the response code that the directory mirror or authority + * returned. + * + * @since 1.0.0 + */ + public int getResponseCode(); + + /** + * Return the time in milliseconds since the epoch when this request + * was started. + * + * @since 1.0.0 + */ + public long getRequestStart(); + + /** + * Return the time in milliseconds since the epoch when this request + * ended. + * + * @since 1.0.0 + */ + public long getRequestEnd(); + + /** + * Return whether this request ended, because the connect timeout has + * expired. + * + * @since 1.0.0 + */ + public boolean connectTimeoutHasExpired(); + + /** + * Return whether this request ended, because the read timeout has + * expired. + * + * @since 1.0.0 + */ + public boolean readTimeoutHasExpired(); + + /** + * Return whether this request ended, because the global timeout for + * all requests has expired. + * + * @since 1.0.0 + */ + public boolean globalTimeoutHasExpired(); + + /** + * Return the descriptors contained in the reply. + * + * @since 1.0.0 + */ + public List<Descriptor> getDescriptors(); +} + diff --git a/src/main/java/org/torproject/descriptor/DescriptorSourceFactory.java b/src/main/java/org/torproject/descriptor/DescriptorSourceFactory.java new file mode 100644 index 0000000..af13f39 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DescriptorSourceFactory.java @@ -0,0 +1,187 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Factory for descriptor sources which in turn produce descriptors. + * + * <p>Descriptor sources are the only producers of classes implementing + * the {@link Descriptor} superinterface. There exist descriptor sources + * for obtaining remote descriptor data ({@link DescriptorDownloader} and + * {@link DescriptorCollector}) and descriptor sources for processing + * local descriptor data ({@link DescriptorReader} and + * {@link DescriptorParser}).</p> + * + * <p>By default, this factory returns implementations from the library's + * own impl package. This may be overridden by setting Java properties, + * though most users will simply use the default implementations.</p> + * + * <p>These properties can be used for setting the implementation:</p> + * <ul> + * <li>{@code descriptor.collector}</li> + * <li>{@code descriptor.downloader}</li> + * <li>{@code descriptor.parser}</li> + * <li>{@code descriptor.reader}</li> + * </ul> + * + * <p>Assuming the classpath contains the special implementation + * referenced, your application classes as well as a descriptor API jar + * the following is an example for using a different implementation of the + * descriptor downloader:</p> + * + * <p><code> + * java -Ddescriptor.downloader=my.special.descriptorimpl.Downloader my.app.Mainclass + * </code></p> + * + * @since 1.0.0 + */ +public final class DescriptorSourceFactory { + + /** + * Default implementation of the {@link DescriptorDownloader} + * descriptor source. + * + * @since 1.0.0 + */ + public final static String DOWNLOADER_DEFAULT = + "org.torproject.descriptor.impl.DescriptorDownloaderImpl"; + + /** + * Default implementation of the {@link DescriptorParser} descriptor + * source. + * + * @since 1.0.0 + */ + public final static String PARSER_DEFAULT = + "org.torproject.descriptor.impl.DescriptorParserImpl"; + + /** + * Default implementation of the {@link DescriptorReader} descriptor + * source. + * + * @since 1.0.0 + */ + public final static String READER_DEFAULT = + "org.torproject.descriptor.impl.DescriptorReaderImpl"; + + /** + * Default implementation of the {@link DescriptorCollector} descriptor + * source. + * + * @since 1.0.0 + */ + public final static String COLLECTOR_DEFAULT = + "org.torproject.descriptor.impl.DescriptorCollectorImpl"; + + /** + * Property name for overriding the implementation of the + * {@link DescriptorParser} descriptor source, which is by default set + * to the class in {@link #PARSER_DEFAULT}. + * + * @since 1.0.0 + */ + public final static String PARSER_PROPERTY = "descriptor.parser"; + + /** + * Property name for overriding the implementation of the + * {@link DescriptorReader} descriptor source, which is by default set + * to the class in {@link #READER_DEFAULT}. + * + * @since 1.0.0 + */ + public final static String READER_PROPERTY = "descriptor.reader"; + + /** + * Property name for overriding the implementation of the + * {@link DescriptorDownloader} descriptor source, which is by default + * set to the class in {@link #DOWNLOADER_DEFAULT}. + * + * @since 1.0.0 + */ + public final static String DOWNLOADER_PROPERTY = + "descriptor.downloader"; + + /** + * Property name for overriding the implementation of the + * {@link DescriptorCollector} descriptor source, which is by default + * set to the class in {@link #COLLECTOR_DEFAULT}. + * + * @since 1.0.0 + */ + public final static String COLLECTOR_PROPERTY = "descriptor.collector"; + + /** + * Create a new {@link DescriptorParser} by instantiating the class in + * {@link #PARSER_PROPERTY}. + * + * @since 1.0.0 + */ + public final static DescriptorParser createDescriptorParser() { + return (DescriptorParser) retrieve(PARSER_PROPERTY); + } + + /** + * Create a new {@link DescriptorReader} by instantiating the class in + * {@link #READER_PROPERTY}. + * + * @since 1.0.0 + */ + public final static DescriptorReader createDescriptorReader() { + return (DescriptorReader) retrieve(READER_PROPERTY); + } + + /** + * Create a new {@link DescriptorDownloader} by instantiating the class + * in {@link #DOWNLOADER_PROPERTY}. + * + * @since 1.0.0 + */ + public final static DescriptorDownloader createDescriptorDownloader() { + return (DescriptorDownloader) retrieve(DOWNLOADER_PROPERTY); + } + + /** + * Create a new {@link DescriptorCollector} by instantiating the class + * in {@link #COLLECTOR_PROPERTY}. + * + * @since 1.0.0 + */ + public final static DescriptorCollector createDescriptorCollector() { + return (DescriptorCollector) retrieve(COLLECTOR_PROPERTY); + } + + private final static <T> Object retrieve(String type) { + Object object; + String clazzName = null; + try { + switch (type) { + case PARSER_PROPERTY: + clazzName = System.getProperty(type, PARSER_DEFAULT); + break; + case DOWNLOADER_PROPERTY: + clazzName = System.getProperty(type, DOWNLOADER_DEFAULT); + break; + case READER_PROPERTY: + clazzName = System.getProperty(type, READER_DEFAULT); + break; + case COLLECTOR_PROPERTY: + clazzName = System.getProperty(type, COLLECTOR_DEFAULT); + break; + } + object = ClassLoader.getSystemClassLoader().loadClass(clazzName). + newInstance(); + } catch (ClassNotFoundException ex) { + throw new ImplementationNotAccessibleException("Cannot load class " + + clazzName + "for type " + type, ex); + } catch (InstantiationException ex) { + throw new ImplementationNotAccessibleException("Cannot load class " + + clazzName + "for type " + type, ex); + } catch (IllegalAccessException ex) { + throw new ImplementationNotAccessibleException("Cannot load class " + + clazzName + "for type " + type, ex); + } + return object; + } +} + diff --git a/src/main/java/org/torproject/descriptor/DirSourceEntry.java b/src/main/java/org/torproject/descriptor/DirSourceEntry.java new file mode 100644 index 0000000..96d81ee --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DirSourceEntry.java @@ -0,0 +1,96 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains details about an authority and its vote that contributed to a + * consensus. + * + * <p>A directory source entry is not a descriptor type of its own but is + * part of a network status consensus + * ({@link RelayNetworkStatusConsensus}).</p> + * + * @since 1.0.0 + */ +public interface DirSourceEntry { + + /** + * Return the raw directory source entry bytes. + * + * @since 1.0.0 + */ + public byte[] getDirSourceEntryBytes(); + + /** + * Return the authority's nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return a SHA-1 digest of the authority's long-term authority + * identity key used for the version 3 directory protocol, encoded as + * 40 upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public String getIdentity(); + + /** + * Return the authority's hostname. + * + * @since 1.2.0 + */ + public String getHostname(); + + /** + * Return the authority's primary IPv4 address in dotted-quad format. + * + * @since 1.0.0 + */ + public String getIp(); + + /** + * Return the TCP port where this authority accepts directory-related + * HTTP connections. + * + * @since 1.0.0 + */ + public int getDirPort(); + + /** + * Return the TCP port where this authority accepts TLS connections for + * the main OR protocol. + * + * @since 1.0.0 + */ + public int getOrPort(); + + /** + * Return whether this directory source entry was created using a + * legacy key. + * + * @since 1.0.0 + */ + public boolean isLegacy(); + + /** + * Return the contact information for this authority, which may contain + * non-ASCII characters. + * + * @since 1.0.0 + */ + public String getContactLine(); + + /** + * Return the SHA-1 vote digest, encoded as 40 lower-case hexadecimal + * characters. + * + * @since 1.0.0 + */ + public String getVoteDigest(); +} + diff --git a/src/main/java/org/torproject/descriptor/DirectoryKeyCertificate.java b/src/main/java/org/torproject/descriptor/DirectoryKeyCertificate.java new file mode 100644 index 0000000..07211ef --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DirectoryKeyCertificate.java @@ -0,0 +1,109 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a key certificate in the version 3 directory protocol. + * + * <p>Every directory authority in the version 3 directory protocol uses + * two keys: a medium-term signing key, and a long-term authority identity + * key. (Authorities also have a relay identity key used in their role as + * a relay and by earlier versions of the directory protocol.) The + * identity key is used from time to time to sign new key certificates + * containing signing keys. The contained signing key is used to sign key + * certificates and status documents.</p> + * + * @since 1.0.0 + */ +public interface DirectoryKeyCertificate extends Descriptor { + + /** + * Return the version of this descriptor, which must be 3 or higher. + * + * @since 1.0.0 + */ + public int getDirKeyCertificateVersion(); + + /** + * Return the authority's primary IPv4 address in dotted-quad format, + * or null if the certificate does not contain an address. + * + * @since 1.0.0 + */ + public String getAddress(); + + /** + * Return the TCP port where this authority accepts directory-related + * HTTP connections, or -1 if the certificate does not contain a port. + * + * @since 1.0.0 + */ + public int getPort(); + + /** + * Return a SHA-1 digest of the authority's long-term authority + * identity key used for the version 3 directory protocol, encoded as + * 40 upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return the authority's identity key in PEM format. + * + * @since 1.0.0 + */ + public String getDirIdentityKey(); + + /** + * Return the time in milliseconds since the epoch when the authority's + * signing key and this key certificate were generated. + * + * @since 1.0.0 + */ + public long getDirKeyPublishedMillis(); + + /** + * Return the time in milliseconds since the epoch after which the + * authority's signing key is no longer valid. + * + * @since 1.0.0 + */ + public long getDirKeyExpiresMillis(); + + /** + * Return the authority's signing key in PEM format. + * + * @since 1.0.0 + */ + public String getDirSigningKey(); + + /** + * Return the signature of the authority's identity key made using the + * authority's signing key, or null if the certificate does not contain + * such a signature. + * + * @since 1.0.0 + */ + public String getDirKeyCrosscert(); + + /** + * Return the certificate signature from the initial item + * "dir-key-certificate-version" until the final item + * "dir-key-certification", signed with the authority identity key. + * + * @since 1.0.0 + */ + public String getDirKeyCertification(); + + /** + * Return the SHA-1 certificate digest, encoded as 40 lower-case + * hexadecimal characters. + * + * @since 1.0.0 + */ + public String getCertificateDigest(); +} + diff --git a/src/main/java/org/torproject/descriptor/DirectorySignature.java b/src/main/java/org/torproject/descriptor/DirectorySignature.java new file mode 100644 index 0000000..8877a4e --- /dev/null +++ b/src/main/java/org/torproject/descriptor/DirectorySignature.java @@ -0,0 +1,52 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains the signature of a network status consensus or vote. + * + * <p>A directory signature is not a descriptor type of its own but is + * part of a network status consensus + * ({@link RelayNetworkStatusConsensus}) or vote + * ({@link RelayNetworkStatusVote}).</p> + * + * @since 1.0.0 + */ +public interface DirectorySignature { + + /** + * Return the digest algorithm, which is "sha1" by default and which + * can be "sha256" or another digest algorithm. + * + * @since 1.0.0 + */ + public String getAlgorithm(); + + /** + * Return the SHA-1 digest of the authority's long-term identity key in + * the version 3 directory protocol, encoded as 40 upper-case + * hexadecimal characters. + * + * @since 1.0.0 + */ + public String getIdentity(); + + /** + * Return the SHA-1 digest of the authority's medium-term signing key + * in the version 3 directory protocol, encoded as 40 upper-case + * hexadecimal characters. + * + * @since 1.0.0 + */ + public String getSigningKeyDigest(); + + /** + * Return the directory signature string made with the authority's + * identity key in the version 3 directory protocol. + * + * @since 1.0.0 + */ + public String getSignature(); +} + diff --git a/src/main/java/org/torproject/descriptor/ExitList.java b/src/main/java/org/torproject/descriptor/ExitList.java new file mode 100644 index 0000000..2a5cb2e --- /dev/null +++ b/src/main/java/org/torproject/descriptor/ExitList.java @@ -0,0 +1,92 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.Map; +import java.util.Set; + +/** + * Contains an exit list containing the IP addresses of relays that the + * exit list service TorDNSEL found when exiting through them. + * + * @since 1.0.0 + */ +public interface ExitList extends Descriptor { + + /** + * End-of-line character expected in exit lists. + * + * @since 1.0.0 + */ + public final static String EOL = "\n"; + + /** + * Exit list entry containing results from a single exit scan. + * + * @since 1.1.0 + */ + public interface Entry { + + /** + * Return the scanned relay's fingerprint, which is a SHA-1 digest of + * the relays's public identity key, encoded as 40 upper-case + * hexadecimal characters. + * + * @since 1.1.0 + */ + public String getFingerprint(); + + /** + * Return the time in milliseconds since the epoch when the scanned + * relay's last known descriptor was published. + * + * @since 1.1.0 + */ + public long getPublishedMillis(); + + /** + * Return the time in milliseconds since the epoch when the network + * status that this scan was based on was published. + * + * @since 1.1.0 + */ + public long getLastStatusMillis(); + + /** + * Return the IP addresses that were determined in the scan with map + * keys being IPv4 addresses in dotted-quad format and map values + * being scan times in milliseconds since the epoch. + * + * @since 1.1.0 + */ + public Map<String, Long> getExitAddresses(); + } + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was downloaded. + * + * @since 1.0.0 + */ + public long getDownloadedMillis(); + + /** + * Return the unordered set of exit scan results. + * + * @since 1.0.0 + * @deprecated The {@link ExitListEntry} type has been deprecated and + * superseded by {@link ExitList.Entry} which is returned by + * {@link #getEntries()}. + */ + @Deprecated + public Set<ExitListEntry> getExitListEntries(); + + /** + * Return the unordered set of exit scan results. + * + * @since 1.1.0 + */ + public Set<ExitList.Entry> getEntries(); +} + diff --git a/src/main/java/org/torproject/descriptor/ExitListEntry.java b/src/main/java/org/torproject/descriptor/ExitListEntry.java new file mode 100644 index 0000000..2a3d79f --- /dev/null +++ b/src/main/java/org/torproject/descriptor/ExitListEntry.java @@ -0,0 +1,55 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Exit list entry containing results from a single exit scan. + * + * @since 1.0.0 + * @deprecated Superseded by {@link ExitList.Entry}. + */ +@Deprecated +public interface ExitListEntry extends ExitList.Entry { + + /** + * Return the scanned relay's fingerprint, which is a SHA-1 digest of + * the relays's public identity key, encoded as 40 upper-case + * hexadecimal characters. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return the time in milliseconds since the epoch when the scanned + * relay's last known descriptor was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the time in milliseconds since the epoch when the network + * status that this scan was based on was published. + * + * @since 1.0.0 + */ + public long getLastStatusMillis(); + + /** + * Return the IPv4 address in dotted-quad format that was determined in + * the scan. + * + * @since 1.0.0 + */ + public String getExitAddress(); + + /** + * Return the scan time in milliseconds since the epoch. + * + * @since 1.0.0 + */ + public long getScanMillis(); +} + diff --git a/src/main/java/org/torproject/descriptor/ExtraInfoDescriptor.java b/src/main/java/org/torproject/descriptor/ExtraInfoDescriptor.java new file mode 100644 index 0000000..49efbf3 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/ExtraInfoDescriptor.java @@ -0,0 +1,646 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +/** + * Contains a relay or sanitized bridge extra-info descriptor. + * + * <p>Relays publish extra-info descriptors as an addendum to server + * descriptors ({@link ServerDescriptor}) to report extraneous information + * to the directory authorities that clients do not need to download in + * order to function. This information primarily consists of statistics + * gathered by the relay about its usage and can take up a lot of + * descriptor space. The separation of server descriptors and extra-info + * descriptors has become less relevant with the introduction of + * microdescriptors ({@link Microdescriptor}) that are derived from server + * descriptors by the directory authority and which clients download + * instead of server descriptors, but it persists.</p> + * + * <p>Bridges publish extra-info descriptors to the bridge authority for + * the same reason, to include statistics about their usage without + * increasing the directory protocol overhead for bridge clients. In this + * case, the separation of server descriptors and extra-info descriptors + * is slightly more relevant, because there are no microdescriptors for + * bridges, so that bridge clients still download server descriptors of + * bridges they're using. Another reason is that bridges need to include + * information like details of all the transports they support in their + * descriptors, and bridge clients using one such transport are not + * supposed to learn the details of the other transports.</p> + * + * <p>It's worth noting that all contents of extra-info descriptors are + * written and signed by relays and bridges without a third party + * verifying their correctness. The (bridge) directory authorities may + * decide to exclude dishonest servers from the network statuses they + * produce, but that wouldn't be reflected in extra-info descriptors.</p> + * + * @since 1.0.0 + */ +public interface ExtraInfoDescriptor extends Descriptor { + + /** + * Return the SHA-1 descriptor digest, encoded as 40 lower-case (relay + * descriptors) or upper-case (bridge descriptors) hexadecimal + * characters, that is used to reference this descriptor from a server + * descriptor. + * + * @since 1.0.0 + */ + public String getExtraInfoDigest(); + + /** + * Return the SHA-256 descriptor digest, encoded as 43 base64 + * characters without padding characters, that may be used to reference + * this descriptor from a server descriptor. + * + * @since 1.1.0 + */ + public String getExtraInfoDigestSha256(); + + /** + * Return the server's nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return a SHA-1 digest of the server's public identity key, encoded + * as 40 upper-case hexadecimal characters, that is typically used to + * uniquely identify the server. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return the time in milliseconds since the epoch when this descriptor + * and the corresponding server descriptor were generated. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the server's history of read bytes, or null if the descriptor + * does not contain a bandwidth history; older Tor versions included + * bandwidth histories in their server descriptors + * ({@link ServerDescriptor#getReadHistory()}). + * + * @since 1.0.0 + */ + public BandwidthHistory getReadHistory(); + + /** + * Return the server's history of written bytes, or null if the + * descriptor does not contain a bandwidth history; older Tor versions + * included bandwidth histories in their server descriptors + * ({@link ServerDescriptor#getWriteHistory()}). + * + * @since 1.0.0 + */ + public BandwidthHistory getWriteHistory(); + + /** + * Return a SHA-1 digest of the GeoIP database file used by this server + * to resolve client IP addresses to country codes, encoded as 40 + * upper-case hexadecimal characters, or null if no GeoIP database + * digest is included. + * + * @since 1.0.0 + */ + public String getGeoipDbDigest(); + + /** + * Return a SHA-1 digest of the GeoIPv6 database file used by this + * server to resolve client IP addresses to country codes, encoded as 40 + * upper-case hexadecimal characters, or null if no GeoIPv6 database + * digest is included. + * + * @since 1.0.0 + */ + public String getGeoip6DbDigest(); + + /** + * Return the time in milliseconds since the epoch when the included + * directory request statistics interval ended, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public long getDirreqStatsEndMillis(); + + /** + * Return the interval length of the included directory request + * statistics in seconds, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public long getDirreqStatsIntervalLength(); + + /** + * Return statistics on unique IP addresses requesting v2 network + * statuses with map keys being country codes and map values being + * numbers of unique IP addresses rounded up to the nearest multiple of + * 8, or null if no such statistics are included (which is the case with + * recent Tor versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV2Ips(); + + /** + * Return statistics on unique IP addresses requesting v3 network + * status consensuses of any flavor with map keys being country codes + * and map values being numbers of unique IP addresses rounded up to the + * nearest multiple of 8, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV3Ips(); + + /** + * Return statistics on directory requests for v2 network statuses with + * map keys being country codes and map values being request numbers + * rounded up to the nearest multiple of 8, or null if no such + * statistics are included (which is the case with recent Tor + * versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV2Reqs(); + + /** + * Return statistics on directory requests for v3 network status + * consensuses of any flavor with map keys being country codes and map + * values being request numbers rounded up to the nearest multiple of 8, + * or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV3Reqs(); + + /** + * Return the share of requests for v2 network statuses that the server + * expects to receive from clients, or -1.0 if this share is not + * included (which is the case with recent Tor versions). + * + * @since 1.0.0 + */ + public double getDirreqV2Share(); + + /** + * Return the share of requests for v3 network status consensuses of + * any flavor that the server expects to receive from clients, or -1.0 + * if this share is not included (which is the case with recent Tor + * versions). + * + * @since 1.0.0 + */ + public double getDirreqV3Share(); + + /** + * Return statistics on responses to directory requests for v2 network + * statuses with map keys being response strings and map values being + * response numbers rounded up to the nearest multiple of 4, or null if + * no such statistics are included (which is the case with recent Tor + * versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV2Resp(); + + /** + * Return statistics on responses to directory requests for v3 network + * status consensuses of any flavor with map keys being response strings + * and map values being response numbers rounded up to the nearest + * multiple of 4, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV3Resp(); + + /** + * Return statistics on directory requests for v2 network statuses to + * the server's directory port with map keys being statistic keys and + * map values being statistic values like counts or quantiles, or null + * if no such statistics are included (which is the case with recent Tor + * versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV2DirectDl(); + + /** + * Return statistics on directory requests for v3 network status + * consensuses of any flavor to the server's directory port with map + * keys being statistic keys and map values being statistic values like + * counts or quantiles, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV3DirectDl(); + + /** + * Return statistics on directory requests for v2 network statuses + * tunneled through a circuit with map keys being statistic keys and map + * values being statistic values, or null if no such statistics are + * included (which is the case with recent Tor versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV2TunneledDl(); + + /** + * Return statistics on directory requests for v3 network status + * consensuses of any flavor tunneled through a circuit with map keys + * being statistic keys and map values being statistic values, or null + * if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getDirreqV3TunneledDl(); + + /** + * Return the directory request read history contained in this + * descriptor, or null if no such history is contained. + * + * @since 1.0.0 + */ + public BandwidthHistory getDirreqReadHistory(); + + /** + * Return the directory request write history contained in this + * descriptor, or null if no such history is contained. + * + * @since 1.0.0 + */ + public BandwidthHistory getDirreqWriteHistory(); + + /** + * Return the time in milliseconds since the epoch when the included + * entry statistics interval ended, or -1 if no such statistics are + * included. + * + * @since 1.0.0 + */ + public long getEntryStatsEndMillis(); + + /** + * Return the interval length of the included entry statistics in + * seconds, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public long getEntryStatsIntervalLength(); + + /** + * Return statistics on client IP addresses with map keys being country + * codes and map values being the number of unique IP addresses that + * have connected from that country rounded up to the nearest multiple + * of 8, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getEntryIps(); + + /** + * Return the time in milliseconds since the epoch when the included + * cell statistics interval ended, or -1 if no such statistics are + * included. + * + * @since 1.0.0 + */ + public long getCellStatsEndMillis(); + + /** + * Return the interval length of the included cell statistics in + * seconds, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public long getCellStatsIntervalLength(); + + /** + * Return the mean number of processed cells per circuit by circuit + * decile starting with the loudest decile at index 0 and the quietest + * decile at index 8, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public List<Integer> getCellProcessedCells(); + + /** + * Return the mean number of cells contained in circuit queues by + * circuit decile starting with the loudest decile at index 0 and the + * quietest decile at index 8, or null if no such statistics are + * included. + * + * @since 1.0.0 + */ + public List<Double> getCellQueuedCells(); + + /** + * Return the mean times in milliseconds that cells spend in circuit + * queues by circuit decile starting with the loudest decile at index 0 + * and the quietest decile at index 8, or null if no such statistics are + * included. + * + * @since 1.0.0 + */ + public List<Integer> getCellTimeInQueue(); + + /** + * Return the mean number of circuits included in any of the cell + * statistics deciles, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public int getCellCircuitsPerDecile(); + + /** + * Return the time in milliseconds since the epoch when the included + * statistics on bi-directional connection usage ended, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public long getConnBiDirectStatsEndMillis(); + + /** + * Return the interval length of the included statistics on + * bi-directional connection usage in seconds, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public long getConnBiDirectStatsIntervalLength(); + + /** + * Return the number of connections on which this server read and wrote + * less than 2 KiB/s in a 10-second interval, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public int getConnBiDirectBelow(); + + /** + * Return the number of connections on which this server read and wrote + * at least 2 KiB/s in a 10-second interval and at least 10 times more + * in read direction than in write direction, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public int getConnBiDirectRead(); + + /** + * Return the number of connections on which this server read and wrote + * at least 2 KiB/s in a 10-second interval and at least 10 times more + * in write direction than in read direction, or -1 if no such + * statistics are included. + * + * @since 1.0.0 + */ + public int getConnBiDirectWrite(); + + /** + * Return the number of connections on which this server read and wrote + * at least 2 KiB/s in a 10-second interval but not 10 times more in + * either direction, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public int getConnBiDirectBoth(); + + /** + * Return the time in milliseconds since the epoch when the included + * exit statistics interval ended, or -1 if no such statistics are + * included. + * + * @since 1.0.0 + */ + public long getExitStatsEndMillis(); + + /** + * Return the interval length of the included exit statistics in + * seconds, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public long getExitStatsIntervalLength(); + + /** + * Return statistics on KiB written to streams exiting the Tor network + * by target TCP port with map keys being string representations of + * ports (or {@code "other"}) and map values being KiB rounded up to the + * next full KiB, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Long> getExitKibibytesWritten(); + + /** + * Return statistics on KiB read from streams exiting the Tor network + * by target TCP port with map keys being string representations of + * ports (or {@code "other"}) and map values being KiB rounded up to the + * next full KiB, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Long> getExitKibibytesRead(); + + /** + * Return statistics on opened streams exiting the Tor network by + * target TCP port with map keys being string representations of ports + * (or {@code "other"}) and map values being the number of opened + * streams, rounded up to the nearest multiple of 4, or null if no such + * statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Long> getExitStreamsOpened(); + + /** + * Return the time in milliseconds since the epoch when the included + * "geoip" statistics interval started, or -1 if no such statistics are + * included (which is the case except for very old Tor versions). + * + * @since 1.0.0 + */ + public long getGeoipStartTimeMillis(); + + /** + * Return statistics on the origin of client IP addresses with map keys + * being country codes and map values being the number of unique IP + * addresses that have connected from that country between the start of + * the statistics interval and the descriptor publication time rounded + * up to the nearest multiple of 8, or null if no such statistics are + * included (which is the case except for very old Tor versions). + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getGeoipClientOrigins(); + + /** + * Return the time in milliseconds since the epoch when the included + * bridge statistics interval ended, or -1 if no such statistics are + * included. + * + * @since 1.0.0 + */ + public long getBridgeStatsEndMillis(); + + /** + * Return the interval length of the included bridge statistics in + * seconds, or -1 if no such statistics are included. + * + * @since 1.0.0 + */ + public long getBridgeStatsIntervalLength(); + + /** + * Return statistics on bridge client IP addresses by country with map + * keys being country codes and map values being the number of unique IP + * addresses that have connected from that country rounded up to the + * nearest multiple of 8, or null if no such statistics are included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getBridgeIps(); + + /** + * Return statistics on bridge client IP addresses by IP version with + * map keys being protocol families, e.g., {@code "v4"} or {@code "v6"}, + * and map values being the number of unique IP addresses rounded up to + * the nearest multiple of 8, or null if no such statistics are + * included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getBridgeIpVersions(); + + /** + * Return statistics on bridge client IP addresses by transport with + * map keys being pluggable transport names, e.g., {@code "obfs2"} or + * {@code "obfs3"} for known transports, {@code "<OR>"} for the default + * onion routing protocol, or {@code "<??>"} for an unknown transport, + * and map values being the number of unique IP addresses rounded up to + * the nearest multiple of 8, or null if no such statistics are + * included. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getBridgeIpTransports(); + + /** + * Return the (possibly empty) list of pluggable transports supported + * by this server. + * + * @since 1.0.0 + */ + public List<String> getTransports(); + + /** + * Return the time in milliseconds since the epoch when the included + * hidden-service statistics interval ended, or -1 if no such statistics + * are included. + * + * @since 1.1.0 + */ + public long getHidservStatsEndMillis(); + + /** + * Return the interval length of the included hidden-service statistics + * in seconds, or -1 if no such statistics are included. + * + * @since 1.1.0 + */ + public long getHidservStatsIntervalLength(); + + /** + * Return the approximate number of RELAY cells seen in either + * direction on a circuit after receiving and successfully processing a + * RENDEZVOUS1 cell, or null if no such statistics are included. + * + * @since 1.1.0 + */ + public Double getHidservRendRelayedCells(); + + /** + * Return the obfuscation parameters applied to the original + * measurement value of RELAY cells seen in either direction on a + * circuit after receiving and successfully processing a RENDEZVOUS1 + * cell, or null if no such statistics are included. + * + * @since 1.1.0 + */ + public Map<String, Double> getHidservRendRelayedCellsParameters(); + + /** + * Return the approximate number of unique hidden-service identities + * seen in descriptors published to and accepted by this hidden-service + * directory, or null if no such statistics are included. + * + * @since 1.1.0 + */ + public Double getHidservDirOnionsSeen(); + + /** + * Return the obfuscation parameters applied to the original + * measurement value of unique hidden-service identities seen in + * descriptors published to and accepted by this hidden-service + * directory, or null if no such statistics are included. + * + * @since 1.1.0 + */ + public Map<String, Double> getHidservDirOnionsSeenParameters(); + + /** + * Return the RSA-1024 signature of the PKCS1-padded descriptor digest, + * taken from the beginning of the router line through the newline after + * the router-signature line, or null if the descriptor doesn't contain + * a signature (which is the case in sanitized bridge descriptors). + * + * @since 1.1.0 + */ + public String getRouterSignature(); + + /** + * Return the Ed25519 certificate in PEM format, or null if the + * descriptor doesn't contain one. + * + * @since 1.1.0 + */ + public String getIdentityEd25519(); + + /** + * Return the Ed25519 master key, encoded as 43 base64 characters + * without padding characters, which was either parsed from the optional + * {@code "master-key-ed25519"} line or derived from the (likewise + * optional) Ed25519 certificate following the + * {@code "identity-ed25519"} line, or null if the descriptor contains + * neither Ed25519 master key nor Ed25519 certificate. + * + * @since 1.1.0 + */ + public String getMasterKeyEd25519(); + + /** + * Return the Ed25519 signature of the SHA-256 digest of the entire + * descriptor, encoded as 86 base64 characters without padding + * characters, from the first character up to and including the first + * space after the {@code "router-sig-ed25519"} string, prefixed with + * the string {@code "Tor router descriptor signature v1"}. + * + * @since 1.1.0 + */ + public String getRouterSignatureEd25519(); +} + diff --git a/src/main/java/org/torproject/descriptor/ImplementationNotAccessibleException.java b/src/main/java/org/torproject/descriptor/ImplementationNotAccessibleException.java new file mode 100644 index 0000000..c54e48f --- /dev/null +++ b/src/main/java/org/torproject/descriptor/ImplementationNotAccessibleException.java @@ -0,0 +1,22 @@ +/* Copyright 2014--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Thrown if a descriptor source implementation class cannot be found, + * instantiated, or accessed. + * + * @see DescriptorSourceFactory + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class ImplementationNotAccessibleException + extends RuntimeException { + + public ImplementationNotAccessibleException(String string, + Throwable ex) { + super(string, ex); + } +} + diff --git a/src/main/java/org/torproject/descriptor/Microdescriptor.java b/src/main/java/org/torproject/descriptor/Microdescriptor.java new file mode 100644 index 0000000..f19b7df --- /dev/null +++ b/src/main/java/org/torproject/descriptor/Microdescriptor.java @@ -0,0 +1,135 @@ +/* Copyright 2014--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Contains a relay microdescriptor. + * + * <p>A microdescriptor is a stripped-down version of a relay server + * descriptor ({@link RelayServerDescriptor}) generated by the directory + * authorities by extracting and/or transforming relay server descriptor + * contents following strict rules without adding the authority's opinion + * about the relay. Microdescriptors are referenced from microdescriptor + * consensuses ({@link RelayNetworkStatusConsensus}) and downloaded by + * clients to make path-selection decisions and to build circuits. + * Microdescriptors contain only the most relevant parts that clients care + * about. Microdescriptors are expected to be relatively static and only + * change about once per week.</p> + * + * @since 1.0.0 + */ +public interface Microdescriptor extends Descriptor { + + /** + * Return the SHA-256 descriptor digest, encoded as 43 base64 + * characters without padding characters, that is used to reference this + * descriptor from a vote or microdescriptor consensus. + * + * @since 1.0.0 + */ + public String getMicrodescriptorDigest(); + + /** + * Return the RSA-1024 public key in PEM format used to encrypt CREATE + * cells for this server, or null if the descriptor doesn't contain an + * onion key. + * + * @since 1.0.0 + */ + public String getOnionKey(); + + /** + * Return the curve25519 public key, encoded as 43 base64 characters + * without padding characters, that is used for the ntor circuit + * extended handshake, or null if the descriptor didn't contain an + * ntor-onion-key line. + * + * @since 1.0.0 + */ + public String getNtorOnionKey(); + + /** + * Return IP addresses and TCP ports where this server accepts TLS + * connections for the main OR protocol, or an empty list if the server + * does not support additional addresses or ports; entries are given in + * the order as they are listed in the descriptor; IPv4 addresses are + * given in dotted-quad format, IPv6 addresses use the colon-separated + * hexadecimal format surrounded by square brackets, and TCP ports are + * separated from the IP address using a colon. + * + * @since 1.0.0 + */ + public List<String> getOrAddresses(); + + /** + * Return nicknames, $-prefixed identity fingerprints, or tuples of the + * format {@code $fingerprint=nickname} or {@code $fingerprint~nickname} + * of servers contained in this server's family, or null if the + * descriptor does not contain a family line. + * + * @since 1.0.0 + */ + public List<String> getFamilyEntries(); + + /** + * Return the default policy, {@code "accept"} or {@code "reject"}, of + * the IPv4 port summary, or null if the descriptor didn't contain an + * IPv4 exit-policy summary line which is equivalent to rejecting all + * streams to IPv4 targets. + * + * @since 1.0.0 + */ + public String getDefaultPolicy(); + + /** + * Return the port list of the IPv4 exit-policy summary, or null if the + * descriptor didn't contain an IPv4 exit-policy summary line which is + * equivalent to rejecting all streams to IPv4 targets. + * + * @since 1.0.0 + */ + public String getPortList(); + + /** + * Return the default policy, {@code "accept"} or {@code "reject"}, of + * the IPv6 port summary, or null if the descriptor didn't contain an + * IPv6 exit-policy summary line which is equivalent to rejecting all + * streams to IPv6 targets. + * + * @since 1.0.0 + */ + public String getIpv6DefaultPolicy(); + + /** + * Return the port list of the IPv6 exit-policy summary, or null if the + * descriptor didn't contain an IPv6 exit-policy summary line which is + * equivalent to rejecting all streams to IPv6 targets. + * + * @since 1.0.0 + */ + public String getIpv6PortList(); + + /** + * Return a SHA-1 digest of the server's RSA-1024 identity key, encoded + * as 27 base64 characters without padding characters, that is only + * included to prevent collisions between microdescriptors, or null if + * no such digest is included. + * + * @since 1.1.0 + */ + public String getRsa1024Identity(); + + /** + * Return a SHA-256 digest of the server's Ed25519 identity key, + * encoded as 43 base64 characters without padding characters, that is + * only included to prevent collisions between microdescriptors, or null + * if no such digest is included. + * + * @since 1.1.0 + */ + public String getEd25519Identity(); +} + diff --git a/src/main/java/org/torproject/descriptor/NetworkStatusEntry.java b/src/main/java/org/torproject/descriptor/NetworkStatusEntry.java new file mode 100644 index 0000000..43b3175 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/NetworkStatusEntry.java @@ -0,0 +1,177 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +/** + * Contains an entry in a network status in the version 2 or 3 directory + * protocol or in a bridge network status. + * + * <p>A network status entry is not a descriptor type of its own but is + * part of a network status in the version 2 directory protocol + * ({@link RelayNetworkStatus}), a vote ({@link RelayNetworkStatusVote}) + * or flavored/unflavored consensus (@link RelayNetworkStatusConsensus}) + * in the version 3 directory protocol, or a bridge network status + * ({@link BridgeNetworkStatus}). Entries in signed directories in the + * version 1 directory protocol are represented by router status entries + * ({@link RouterStatusEntry}).</p> + * + * @since 1.0.0 + */ +public interface NetworkStatusEntry { + + /** + * Return the raw network status entry bytes. + * + * @since 1.0.0 + */ + public byte[] getStatusEntryBytes(); + + /** + * Return the server nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return a SHA-1 digest of the server's identity key, encoded as 40 + * upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return the SHA-1 digest of the server descriptor, or null if the + * containing network status does not contain server descriptor + * references, like a microdesc consensus. + * + * @since 1.0.0 + */ + public String getDescriptor(); + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the server's primary IPv4 address in dotted-quad format. + * + * @since 1.0.0 + */ + public String getAddress(); + + /** + * Return the TCP port where this server accepts TLS connections for + * the main OR protocol. + * + * @since 1.0.0 + */ + public int getOrPort(); + + /** + * Return the TCP port where this server accepts directory-related HTTP + * connections. + * + * @since 1.0.0 + */ + public int getDirPort(); + + /** + * Return the (possibly empty) set of microdescriptor digests if the + * containing network status is a vote or microdesc consensus, or null + * otherwise. + * + * @since 1.0.0 + */ + public Set<String> getMicrodescriptorDigests(); + + /** + * Return additional IP addresses and TCP ports where this server + * accepts TLS connections for the main OR protocol, or an empty list if + * the network status doesn't contain any such additional addresses and + * ports. + * + * @since 1.0.0 + */ + public List<String> getOrAddresses(); + + /** + * Return the relay flags assigned to this server, or null if the + * status entry didn't contain any relay flags. + * + * @since 1.0.0 + */ + public SortedSet<String> getFlags(); + + /** + * Return the Tor software version, or null if the status entry didn't + * contain version information. + * + * @since 1.0.0 + */ + public String getVersion(); + + /** + * Return the bandwidth weight of this server or -1 if the status entry + * didn't contain a bandwidth line. + * + * @since 1.0.0 + */ + public long getBandwidth(); + + /** + * Return the measured bandwidth or -1 if the status entry either + * didn't contain bandwidth information or didn't contain an indication + * that this information is based on measured bandwidth. + * + * @since 1.0.0 + */ + public long getMeasured(); + + /** + * Return whether the status entry is yet unmeasured by the bandwidth + * authorities; only included in consensuses using method 17 or higher. + * + * @since 1.0.0 + */ + public boolean getUnmeasured(); + + /** + * Return the default policy of the port summary, which can be either + * {@code "accept"} or {@code "reject"}, or null if the status entry + * didn't contain an exit policy summary. + * + * @since 1.0.0 + */ + public String getDefaultPolicy(); + + /** + * Return the list of ports or port intervals of the exit port summary, + * or null if the status entry didn't contain an exit policy summary. + * + * @since 1.0.0 + */ + public String getPortList(); + + /** + * Return the server's Ed25519 master key, encoded as 43 base64 + * characters without padding characters, "none" if the relay doesn't + * have an Ed25519 identity, or null if the status entry didn't contain + * this information or if the status is not a vote. + * + * @since 1.1.0 + */ + public String getMasterKeyEd25519(); +} + diff --git a/src/main/java/org/torproject/descriptor/RelayDirectory.java b/src/main/java/org/torproject/descriptor/RelayDirectory.java new file mode 100644 index 0000000..8f3e58b --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayDirectory.java @@ -0,0 +1,104 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Contains a signed directory in the version 1 directory protocol. + * + * <p>Directory authorities in the (long outdated) version 1 of the + * directory protocol served signed directory documents containing a list + * of signed server descriptors ({@link ServerDescriptor}) along with + * short summaries of the status of each server + * ({@link RouterStatusEntry}).</p> + * + * <p>Clients in that version of the directory protocol would fetch this + * signed directory to get up-to-date information on the state of the + * network and be certain that the list was attested by a trusted + * directory authority.</p> + * + * <p>Signed directories in the version 1 directory protocol have first + * been superseded by network status documents in the version 2 directory + * protocol ({@link RelayNetworkStatus}) and later by network status + * consensuses ({@link RelayNetworkStatusConsensus}) in the version 3 + * directory protocol.</p> + * + * @since 1.0.0 + */ +public interface RelayDirectory extends Descriptor { + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the RSA-1024 public key in PEM format used by this authority + * as long-term identity key and to sign network statuses, or null if + * this key is not included in the descriptor header. + * + * @since 1.0.0 + */ + public String getDirSigningKey(); + + /** + * Return recommended Tor versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedSoftware(); + + /** + * Return the directory signature string made with the authority's + * identity key. + * + * @since 1.0.0 + */ + public String getDirectorySignature(); + + /** + * Return router status entries, one for each contained relay. + * + * @since 1.0.0 + */ + public List<RouterStatusEntry> getRouterStatusEntries(); + + /** + * Return a list of server descriptors contained in the signed + * directory. + * + * @since 1.0.0 + */ + public List<ServerDescriptor> getServerDescriptors(); + + /** + * Return a (very likely empty) list of exceptions from parsing the + * contained server descriptors. + * + * @since 1.0.0 + */ + public List<Exception> getServerDescriptorParseExceptions(); + + /** + * Return the directory nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return the SHA-1 directory digest, encoded as 40 lower-case + * hexadecimal characters, that the directory authority used to sign the + * directory. + * + * @since 1.0.0 + */ + public String getDirectoryDigest(); +} + diff --git a/src/main/java/org/torproject/descriptor/RelayExtraInfoDescriptor.java b/src/main/java/org/torproject/descriptor/RelayExtraInfoDescriptor.java new file mode 100644 index 0000000..73f8438 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayExtraInfoDescriptor.java @@ -0,0 +1,21 @@ +/* Copyright 2015--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a relay extra-info descriptor. + * + * <p>Relay extra-info descriptors share many contents with sanitized + * bridge extra-info descriptors ({@link BridgeExtraInfoDescriptor}), + * which is why they share a common superinterface + * ({@link ExtraInfoDescriptor}). The main purpose of having two + * subinterfaces is being able to distinguish descriptor types more + * easily.</p> + * + * @since 1.1.0 + */ +public interface RelayExtraInfoDescriptor extends ExtraInfoDescriptor { + +} + diff --git a/src/main/java/org/torproject/descriptor/RelayNetworkStatus.java b/src/main/java/org/torproject/descriptor/RelayNetworkStatus.java new file mode 100644 index 0000000..db3ddac --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayNetworkStatus.java @@ -0,0 +1,176 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Contains a network status document in the version 2 directory protocol. + * + * <p>Directory authorities in the (outdated) version 2 of the directory + * protocol published signed network status documents. Each network + * status listed, for every relay in the network + * ({@link NetworkStatusEntry}): a hash of its identity key, a hash of its + * most recent server descriptor, and a summary of what the authority + * believed about its status.</p> + * + * <p>Clients would download the authorities' network status documents in + * turn, and believe statements about routers iff they were attested to by + * more than half of the authorities.</p> + * + * <p>Network status documents in the version 2 directory protocol + * supersede signed directories in the version 1 directory protocol + * ({@link RelayDirectory}) and have been superseded by network status + * consensuses ({@link RelayNetworkStatusConsensus}) in the version 3 + * directory protocol.</p> + * + * @since 1.0.0 + */ +public interface RelayNetworkStatus extends Descriptor { + + /** + * Return the document format version of this descriptor which is 2. + * + * @since 1.0.0 + */ + public int getNetworkStatusVersion(); + + /** + * Return the authority's hostname. + * + * @since 1.0.0 + */ + public String getHostname(); + + /** + * Return the authority's primary IPv4 address in dotted-quad format, + * or null if the descriptor does not contain an address. + * + * @since 1.0.0 + */ + public String getAddress(); + + /** + * Return the TCP port where this authority accepts directory-related + * HTTP connections, or 0 if the authority does not accept such + * connections. + * + * @since 1.0.0 + */ + public int getDirport(); + + /** + * Return a SHA-1 digest of the authority's public identity key, + * encoded as 40 upper-case hexadecimal characters, which is also used + * to sign network statuses. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return the contact information for this authority, which may contain + * non-ASCII characters. + * + * @since 1.0.0 + */ + public String getContactLine(); + + /** + * Return the RSA-1024 public key in PEM format used by this authority + * as long-term identity key and to sign network statuses. + * + * @since 1.0.0 + */ + public String getDirSigningKey(); + + /** + * Return recommended Tor versions for server usage, or null if the + * authority does not recommend server versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedServerVersions(); + + /** + * Return recommended Tor versions for client usage, or null if the + * authority does not recommend client versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedClientVersions(); + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the set of flags that this directory assigns to relays, or + * null if the status does not assign such flags. + * + * @since 1.0.0 + */ + public SortedSet<String> getDirOptions(); + + /** + * Return status entries for each contained server, with map keys being + * SHA-1 digests of the servers' public identity keys, encoded as 40 + * upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public SortedMap<String, NetworkStatusEntry> getStatusEntries(); + + /** + * Return whether a status entry with the given relay fingerprint + * (SHA-1 digest of the server's public identity key, encoded as 40 + * upper-case hexadecimal characters) exists; convenience method for + * {@code getStatusEntries().containsKey(fingerprint)}. + * + * @since 1.0.0 + */ + public boolean containsStatusEntry(String fingerprint); + + /** + * Return a status entry by relay fingerprint (SHA-1 digest of the + * server's public identity key, encoded as 40 upper-case hexadecimal + * characters), or null if no such status entry exists; convenience + * method for {@code getStatusEntries().get(fingerprint)}. + * + * @since 1.0.0 + */ + public NetworkStatusEntry getStatusEntry(String fingerprint); + + /** + * Return the authority's nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return the directory signature string made with the authority's + * identity key. + * + * @since 1.0.0 + */ + public String getDirectorySignature(); + + /** + * Return the SHA-1 status digest, encoded as 40 lower-case hexadecimal + * characters, that the directory authority used to sign the network + * status. + * + * @since 1.0.0 + */ + public String getStatusDigest(); +} + diff --git a/src/main/java/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/main/java/org/torproject/descriptor/RelayNetworkStatusConsensus.java new file mode 100644 index 0000000..15fdaca --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayNetworkStatusConsensus.java @@ -0,0 +1,223 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Contains a network status consensus in the version 3 directory protocol. + * + * <p>Directory authorities in the version 3 of the directory protocol + * periodically generate a view of the current descriptors and status for + * known relays and send a signed summary of this view to the other + * authorities ({@link RelayNetworkStatusVote}). The authorities compute + * the result of this vote and sign a network status consensus containing + * the result of the vote, which is this document.</p> + * + * <p>Clients use consensus documents to find out when their list of + * relays is out-of-date by looking at the contained network status + * entries ({@link NetworkStatusEntry}). If it is, they download any + * missing server descriptors ({@link ServerDescriptor}).</p> + * + * @since 1.0.0 + */ +public interface RelayNetworkStatusConsensus extends Descriptor { + + /** + * Return the document format version of this descriptor which is 3 or + * higher. + * + * @since 1.0.0 + */ + public int getNetworkStatusVersion(); + + /** + * Return the consensus flavor name, which denotes the variant of the + * original, unflavored consensus, encoded as a string of alphanumeric + * characters and dashes, or null if this descriptor is the unflavored + * consensus. + * + * @since 1.0.0 + */ + public String getConsensusFlavor(); + + /** + * Return the consensus method number of this descriptor, which is the + * highest consensus method supported by more than 2/3 of voting + * authorities, or 0 if no consensus method is contained in the + * descriptor. + * + * @since 1.0.0 + */ + public int getConsensusMethod(); + + /** + * Return the time in milliseconds since the epoch at which this + * descriptor became valid. + * + * @since 1.0.0 + */ + public long getValidAfterMillis(); + + /** + * Return the time in milliseconds since the epoch until which this + * descriptor is the freshest that is available. + * + * @since 1.0.0 + */ + public long getFreshUntilMillis(); + + /** + * Return the time in milliseconds since the epoch until which this + * descriptor was valid. + * + * @since 1.0.0 + */ + public long getValidUntilMillis(); + + /** + * Return the number of seconds that the directory authorities will + * allow to collect votes from the other authorities when producing the + * next consensus. + * + * @since 1.0.0 + */ + public long getVoteSeconds(); + + /** + * Return the number of seconds that the directory authorities will + * allow to collect signatures from the other authorities when producing + * the next consensus. + * + * @since 1.0.0 + */ + public long getDistSeconds(); + + /** + * Return recommended Tor versions for server usage, or null if the + * consensus does not contain an opinion about server versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedServerVersions(); + + /** + * Return recommended Tor versions for client usage, or null if the + * consensus does not contain an opinion about client versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedClientVersions(); + + /** + * Return a list of software packages and their versions together with a + * URL and one or more digests in the format <code>PackageName Version + * URL DIGESTS</code> that are known by at least three directory + * authorities and agreed upon by the majority of directory authorities, + * or null if the consensus does not contain package information. + * + * @since 1.3.0 + */ + public List<String> getPackageLines(); + + /** + * Return known relay flags in this descriptor that were contained in + * enough votes for this consensus to be an authoritative opinion for + * these relay flags. + * + * @since 1.0.0 + */ + public SortedSet<String> getKnownFlags(); + + /** + * Return consensus parameters contained in this descriptor with map + * keys being case-sensitive parameter identifiers and map values being + * parameter values, or null if the consensus doesn't contain consensus + * parameters. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getConsensusParams(); + + /** + * Return directory source entries for each directory authority that + * contributed to the consensus, with map keys being SHA-1 digests of + * the authorities' identity keys in the version 3 directory protocol, + * encoded as 40 upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public SortedMap<String, DirSourceEntry> getDirSourceEntries(); + + /** + * Return status entries for each contained server, with map keys being + * SHA-1 digests of the servers' public identity keys, encoded as 40 + * upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public SortedMap<String, NetworkStatusEntry> getStatusEntries(); + + /** + * Return whether a status entry with the given relay fingerprint + * (SHA-1 digest of the server's public identity key, encoded as 40 + * upper-case hexadecimal characters) exists; convenience method for + * {@code getStatusEntries().containsKey(fingerprint)}. + * + * @since 1.0.0 + */ + public boolean containsStatusEntry(String fingerprint); + + /** + * Return a status entry by relay fingerprint (SHA-1 digest of the + * server's public identity key, encoded as 40 upper-case hexadecimal + * characters), or null if no such status entry exists; convenience + * method for {@code getStatusEntries().get(fingerprint)}. + * + * @since 1.0.0 + */ + public NetworkStatusEntry getStatusEntry(String fingerprint); + + /** + * Return directory signatures of this consensus, with map keys being + * SHA-1 digests of the authorities' identity keys in the version 3 + * directory protocol, encoded as 40 upper-case hexadecimal characters. + * + * @deprecated Replaced by {@link #getSignatures()} which permits an + * arbitrary number of signatures made by an authority using the same + * identity key digest and different algorithms. + * + * @since 1.0.0 + */ + public SortedMap<String, DirectorySignature> getDirectorySignatures(); + + /** + * Return the list of signatures contained in this consensus. + * + * @since 1.3.0 + */ + public List<DirectorySignature> getSignatures(); + + /** + * Return optional weights to be applied to router bandwidths during + * path selection with map keys being case-sensitive weight identifiers + * and map values being weight values, or null if the consensus doesn't + * contain such weights. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getBandwidthWeights(); + + /** + * Return the SHA-1 digest of this consensus, encoded as 40 upper-case + * hexadecimal characters that directory authorities use to sign the + * consensus. + * + * @since 1.0.0 + */ + public String getConsensusDigest(); +} + diff --git a/src/main/java/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/main/java/org/torproject/descriptor/RelayNetworkStatusVote.java new file mode 100644 index 0000000..1f77db6 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayNetworkStatusVote.java @@ -0,0 +1,408 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Contains a network status vote in the version 3 directory protocol. + * + * <p>Directory authorities in the version 3 of the directory protocol + * periodically generate a view of the current descriptors and status for + * known relays and send a signed summary of this view to the other + * authorities, which is this document. The authorities compute the + * result of this vote and sign a network status consensus containing the + * result of the vote ({@link RelayNetworkStatusConsensus}).</p> + * + * @since 1.0.0 + */ +public interface RelayNetworkStatusVote extends Descriptor { + + /** + * Return the document format version of this descriptor which is 3 or + * higher. + * + * @since 1.0.0 + */ + public int getNetworkStatusVersion(); + + /** + * Return the list of consensus method numbers supported by this + * authority, or null if the descriptor doesn't say so, which would mean + * that only method 1 is supported. + * + * @since 1.0.0 + */ + public List<Integer> getConsensusMethods(); + + /** + * Return the time in milliseconds since the epoch when this descriptor + * was published. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return the time in milliseconds since the epoch at which the + * consensus is supposed to become valid. + * + * @since 1.0.0 + */ + public long getValidAfterMillis(); + + /** + * Return the time in milliseconds since the epoch until which the + * consensus is supposed to be the freshest that is available. + * + * @since 1.0.0 + */ + public long getFreshUntilMillis(); + + /** + * Return the time in milliseconds since the epoch until which the + * consensus is supposed to be valid. + * + * @since 1.0.0 + */ + public long getValidUntilMillis(); + + /** + * Return the number of seconds that the directory authorities will + * allow to collect votes from the other authorities when producing the + * next consensus. + * + * @since 1.0.0 + */ + public long getVoteSeconds(); + + /** + * Return the number of seconds that the directory authorities will + * allow to collect signatures from the other authorities when producing + * the next consensus. + * + * @since 1.0.0 + */ + public long getDistSeconds(); + + /** + * Return recommended Tor versions for server usage, or null if the + * authority does not recommend server versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedServerVersions(); + + /** + * Return recommended Tor versions for client usage, or null if the + * authority does not recommend client versions. + * + * @since 1.0.0 + */ + public List<String> getRecommendedClientVersions(); + + /** + * Return a list of software packages and their versions together with a + * URL and one or more digests in the format <code>PackageName Version + * URL DIGESTS</code> that are known by this directory authority, or + * null if this descriptor does not contain package information. + * + * @since 1.3.0 + */ + public List<String> getPackageLines(); + + /** + * Return known relay flags by this authority. + * + * @since 1.0.0 + */ + public SortedSet<String> getKnownFlags(); + + /** + * Return the minimum uptime in seconds that this authority requires + * for assigning the Stable flag, or -1 if the authority doesn't report + * this value. + * + * @since 1.0.0 + */ + public long getStableUptime(); + + /** + * Return the minimum MTBF (mean time between failure) that this + * authority requires for assigning the Stable flag, or -1 if the + * authority doesn't report this value. + * + * @since 1.0.0 + */ + public long getStableMtbf(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Fast flag, or -1 if the authority doesn't report this + * value. + * + * @since 1.0.0 + */ + public long getFastBandwidth(); + + /** + * Return the minimum WFU (weighted fractional uptime) in percent that + * this authority requires for assigning the Guard flag, or -1 if the + * authority doesn't report this value. + * + * @since 1.0.0 + */ + public double getGuardWfu(); + + /** + * Return the minimum weighted time in seconds that this authority + * needs to know about a relay before assigning the Guard flag, or -1 if + * the authority doesn't report this information. + * + * @since 1.0.0 + */ + public long getGuardTk(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can be guards, or -1 if the + * authority doesn't report this value. + * + * @since 1.0.0 + */ + public long getGuardBandwidthIncludingExits(); + + /** + * Return the minimum bandwidth that this authority requires for + * assigning the Guard flag if exits can not be guards, or -1 if the + * authority doesn't report this value. + * + * @since 1.0.0 + */ + public long getGuardBandwidthExcludingExits(); + + /** + * Return 1 if the authority has measured enough MTBF info to use the + * MTBF requirement instead of the uptime requirement for assigning the + * Stable flag, 0 if not, or -1 if the authority doesn't report this + * information. + * + * @since 1.0.0 + */ + public int getEnoughMtbfInfo(); + + /** + * Return 1 if the authority has enough measured bandwidths that it'll + * ignore the advertised bandwidth claims of routers without measured + * bandwidth, 0 if not, or -1 if the authority doesn't report this + * information. + * + * @since 1.1.0 + */ + public int getIgnoringAdvertisedBws(); + + /** + * Return consensus parameters contained in this descriptor with map + * keys being case-sensitive parameter identifiers and map values being + * parameter values, or null if the authority doesn't include consensus + * parameters in its vote. + * + * @since 1.0.0 + */ + public SortedMap<String, Integer> getConsensusParams(); + + /** + * Return the authority's nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return a SHA-1 digest of the authority's long-term authority + * identity key used for the version 3 directory protocol, encoded as + * 40 upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public String getIdentity(); + + /** + * Return the authority's hostname. + * + * @since 1.2.0 + */ + public String getHostname(); + + /** + * Return the authority's primary IPv4 address in dotted-quad format, + * or null if the descriptor does not contain an address. + * + * @since 1.0.0 + */ + public String getAddress(); + + /** + * Return the TCP port where this authority accepts directory-related + * HTTP connections, or 0 if the authority does not accept such + * connections. + * + * @since 1.0.0 + */ + public int getDirport(); + + /** + * Return the TCP port where this authority accepts TLS connections for + * the main OR protocol, or 0 if the authority does not accept such + * connections. + * + * @since 1.0.0 + */ + public int getOrport(); + + /** + * Return the contact information for this authority, which may contain + * non-ASCII characters, or null if no contact information is included + * in the descriptor. + * + * @since 1.0.0 + */ + public String getContactLine(); + + /** + * Return the version of the directory key certificate used by this + * authority, which must be 3 or higher. + * + * @since 1.0.0 + */ + public int getDirKeyCertificateVersion(); + + /** + * Return the SHA-1 digest for an obsolete authority identity key still + * used by this authority to keep older clients working, or null if this + * authority does not use such a key. + * + * @since 1.0.0 + */ + public String getLegacyDirKey(); + + /** + * Return the authority's identity key in PEM format. + * + * @since 1.2.0 + */ + public String getDirIdentityKey(); + + /** + * Return the time in milliseconds since the epoch when the authority's + * signing key and corresponding key certificate were generated. + * + * @since 1.0.0 + */ + public long getDirKeyPublishedMillis(); + + /** + * Return the time in milliseconds since the epoch after which the + * authority's signing key is no longer valid. + * + * @since 1.0.0 + */ + public long getDirKeyExpiresMillis(); + + /** + * Return the authority's signing key in PEM format. + * + * @since 1.2.0 + */ + public String getDirSigningKey(); + + /** + * Return the SHA-1 digest of the authority's signing key, encoded as + * 40 upper-case hexadecimal characters, or null if this digest cannot + * be obtained from the directory signature. + * + * @deprecated Removed in order to be more explicit that authorities may + * use different digest algorithms than "sha1"; see + * {@link #getSignatures()} and + * {@link DirectorySignature#getSigningKeyDigest()} for + * alternatives. + * + * @since 1.0.0 + */ + public String getSigningKeyDigest(); + + /** + * Return the signature of the authority's identity key made using the + * authority's signing key, or null if the vote does not contain such a + * signature. + * + * @since 1.2.0 + */ + public String getDirKeyCrosscert(); + + /** + * Return the certificate signature from the initial item + * "dir-key-certificate-version" until the final item + * "dir-key-certification", signed with the authority identity key. + * + * @since 1.2.0 + */ + public String getDirKeyCertification(); + + /** + * Return status entries for each contained server, with map keys being + * SHA-1 digests of the servers' public identity keys, encoded as 40 + * upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public SortedMap<String, NetworkStatusEntry> getStatusEntries(); + + /** + * Return whether a status entry with the given relay fingerprint + * (SHA-1 digest of the server's public identity key, encoded as 40 + * upper-case hexadecimal characters) exists; convenience method for + * {@code getStatusEntries().containsKey(fingerprint)}. + * + * @since 1.0.0 + */ + public boolean containsStatusEntry(String fingerprint); + + /** + * Return a status entry by relay fingerprint (SHA-1 digest of the + * server's public identity key, encoded as 40 upper-case hexadecimal + * characters), or null if no such status entry exists; convenience + * method for {@code getStatusEntries().get(fingerprint)}. + * + * @since 1.0.0 + */ + public NetworkStatusEntry getStatusEntry(String fingerprint); + + /** + * Return the directory signature of this vote, with the single map key + * being the SHA-1 digest of the authority's identity key in the version + * 3 directory protocol, encoded as 40 upper-case hexadecimal + * characters. + * + * @deprecated Replaced by {@link #getSignatures()} which permits an + * arbitrary number of signatures made by the authority using the same + * identity key digest and different algorithms. + * + * @since 1.0.0 + */ + public SortedMap<String, DirectorySignature> getDirectorySignatures(); + + /** + * Return a list of signatures contained in this vote, which is + * typically a single signature made by the authority but which may also + * be more than one signature made with different keys or algorithms. + * + * @since 1.3.0 + */ + public List<DirectorySignature> getSignatures(); +} + diff --git a/src/main/java/org/torproject/descriptor/RelayServerDescriptor.java b/src/main/java/org/torproject/descriptor/RelayServerDescriptor.java new file mode 100644 index 0000000..6ef3140 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RelayServerDescriptor.java @@ -0,0 +1,20 @@ +/* Copyright 2015--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a relay server descriptor. + * + * <p>Relay server descriptors share many contents with sanitized bridge + * server descriptors ({@link BridgeServerDescriptor}), which is why they + * share a common superinterface ({@link ServerDescriptor}). The main + * purpose of having two subinterfaces is being able to distinguish + * descriptor types more easily.</p> + * + * @since 1.1.0 + */ +public interface RelayServerDescriptor extends ServerDescriptor { + +} + diff --git a/src/main/java/org/torproject/descriptor/RouterStatusEntry.java b/src/main/java/org/torproject/descriptor/RouterStatusEntry.java new file mode 100644 index 0000000..f9a56db --- /dev/null +++ b/src/main/java/org/torproject/descriptor/RouterStatusEntry.java @@ -0,0 +1,51 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +/** + * Contains a router status entry contained in a signed directory in the + * version 1 directory protocol. + * + * <p>Directory authorities in the (long outdated) version 1 of the + * directory protocol included router status entries with short summaries + * of the status of each server in the signed directories they produced + * ({@link RelayDirectory}). These entries contained references to server + * descriptors published by relays together with the authorities' opinion + * on whether relays were verified and live.</p> + * + * @since 1.0.0 + */ +public interface RouterStatusEntry { + + /** + * Return the relay nickname consisting of 1 to 19 alphanumeric + * characters, or null if the relay is unverified. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return a SHA-1 digest of the relay's identity key, encoded as 40 + * upper-case hexadecimal characters. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return whether the relay is verified. + * + * @since 1.0.0 + */ + public boolean isVerified(); + + /** + * Return whether the relay is live. + * + * @since 1.0.0 + */ + public boolean isLive(); +} + diff --git a/src/main/java/org/torproject/descriptor/ServerDescriptor.java b/src/main/java/org/torproject/descriptor/ServerDescriptor.java new file mode 100644 index 0000000..d1af421 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/ServerDescriptor.java @@ -0,0 +1,435 @@ +/* Copyright 2011--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; + +/** + * Contains a relay or sanitized bridge server descriptor. + * + * <p>Relays publish server descriptors to the directory authorities to + * register in the network. Server descriptors contain information about + * the capabilities of a server, like their exit policy, that clients use + * to select servers for their circuits (along with information provided + * by directory authorities on reachability, stability, and capacity of + * servers). Server descriptors also contain network addresses and + * cryptographic material that clients use to build circuits.</p> + * + * <p>Prior to the introduction of microdescriptors + * ({@link Microdescriptor}), the directory authorities included + * cryptographic digests of server descriptors in network statuses + * ({@link RelayNetworkStatusConsensus}) and clients downloaded all + * referenced server descriptors. Nowadays, the directory authorities + * derive microdescriptors from server descriptors and reference those + * in network statuses, and clients only download microdescriptors instead + * of server descriptors.</p> + * + * <p>Bridges publish server descriptors to the bridge directory + * authority, also to announce themselves in the network. The bridge + * directory authority compiles a list of available bridges + * ({@link BridgeNetworkStatus}) for the bridge distribution service + * BridgeDB. There are no microdescriptors for bridges, so that bridge + * clients still rely on downloading bridge server descriptors directly + * from the bridge they're connecting to.</p> + * + * <p>It's worth noting that all contents of server descriptors are + * written and signed by relays and bridges without a third party + * verifying their correctness. The (bridge) directory authorities may + * decide to exclude dishonest servers from the network statuses they + * produce, but that wouldn't be reflected in server descriptors.</p> + * + * @since 1.0.0 + */ +public interface ServerDescriptor extends Descriptor { + + /** + * Return the SHA-1 descriptor digest, encoded as 40 lower-case (relay + * descriptors) or upper-case (bridge descriptors) hexadecimal + * characters, that is used to reference this descriptor from a network + * status descriptor. + * + * @since 1.0.0 + */ + public String getServerDescriptorDigest(); + + /** + * Return the SHA-256 descriptor digest, encoded as 43 base64 + * characters without padding characters, that may be used to reference + * this server descriptor from a network status descriptor. + * + * @since 1.1.0 + */ + public String getServerDescriptorDigestSha256(); + + /** + * Return the server's nickname consisting of 1 to 19 alphanumeric + * characters. + * + * @since 1.0.0 + */ + public String getNickname(); + + /** + * Return the server's primary IPv4 address in dotted-quad format. + * + * @since 1.0.0 + */ + public String getAddress(); + + /** + * Return the TCP port where this server accepts TLS connections for + * the main OR protocol, or 0 if the server does not accept such + * connections. + * + * @since 1.0.0 + */ + public int getOrPort(); + + /** + * Return the TCP port where this server accepts SOCKS connections, + * which is deprecated and should always be 0. + * + * @since 1.0.0 + */ + public int getSocksPort(); + + /** + * Return the TCP port where this server accepts directory-related HTTP + * connections, or 0 if the server does not accept such connections. + * + * @since 1.0.0 + */ + public int getDirPort(); + + /** + * Return IP addresses and TCP ports where this server accepts TLS + * connections for the main OR protocol, or an empty list if the server + * does not support additional addresses or ports; entries are given in + * the order as they are listed in the descriptor; IPv4 addresses are + * given in dotted-quad format, IPv6 addresses use the colon-separated + * hexadecimal format surrounded by square brackets, and TCP ports are + * separated from the IP address using a colon. + * + * @since 1.0.0 + */ + public List<String> getOrAddresses(); + + /** + * Return the average bandwidth in bytes per second that the server is + * willing to sustain over long periods. + * + * @since 1.0.0 + */ + public int getBandwidthRate(); + + /** + * Return the burst bandwidth in bytes per second that the server is + * willing to sustain in very short intervals. + * + * @since 1.0.0 + */ + public int getBandwidthBurst(); + + /** + * Return the observed bandwidth in bytes per second as an estimate of + * the capacity that the server can handle, or -1 if the descriptor + * doesn't contain an observed bandwidth value (which is the case for + * Tor 0.0.8 or older). + * + * @since 1.0.0 + */ + public int getBandwidthObserved(); + + /** + * Return a human-readable string describing the Tor software version + * and the operating system of this server, which may contain non-ASCII + * characters, typically written as {@code "Tor $version on $system"}, + * or null if this descriptor does not contain a platform line. + * + * @since 1.0.0 + */ + public String getPlatform(); + + /** + * Return the time in milliseconds since the epoch when this descriptor + * and the corresponding extra-info descriptor were generated. + * + * @since 1.0.0 + */ + public long getPublishedMillis(); + + /** + * Return a SHA-1 digest of the server's public identity key, encoded + * as 40 upper-case hexadecimal characters (without spaces after every 4 + * characters as opposed to the encoding in the descriptor), that is + * typically used to uniquely identify the server, or null if this + * descriptor does not contain a fingerprint line. + * + * @since 1.0.0 + */ + public String getFingerprint(); + + /** + * Return whether the server was hibernating when this descriptor was + * published and should not be used to build circuits. + * + * @since 1.0.0 + */ + public boolean isHibernating(); + + /** + * Return the number of seconds that the server process has been + * running (which might even be negative in a few descriptors due to a + * bug that was fixed in Tor 0.1.2.7-alpha), or null if the descriptor + * does not contain an uptime line. + * + * @since 1.0.0 + */ + public Long getUptime(); + + /** + * Return the RSA-1024 public key in PEM format used to encrypt CREATE + * cells for this server, or null if the descriptor doesn't contain an + * onion key (which is the case in sanitized bridge descriptors). + * + * @since 1.0.0 + */ + public String getOnionKey(); + + /** + * Return the RSA-1024 public key in PEM format used by this server as + * long-term identity key, or null if the descriptor doesn't contain a + * signing key (which is the case in sanitized bridge descriptors). + * + * @since 1.0.0 + */ + public String getSigningKey(); + + /** + * Return the server's exit policy consisting of one or more accept or + * reject rules that the server follows when deciding whether to allow a + * new stream to a given IP address and TCP port. + * + * @since 1.0.0 + */ + public List<String> getExitPolicyLines(); + + /** + * Return the RSA-1024 signature of the PKCS1-padded descriptor digest, + * taken from the beginning of the router line through the newline after + * the router-signature line, or null if the descriptor doesn't contain + * a signature (which is the case in sanitized bridge descriptors). + * + * @since 1.0.0 + */ + public String getRouterSignature(); + + /** + * Return the contact information for this server, which may contain + * non-ASCII characters, or null if no contact information is included + * in the descriptor. + * + * @since 1.0.0 + */ + public String getContact(); + + /** + * Return nicknames, $-prefixed identity fingerprints, or tuples of the + * format {@code $fingerprint=nickname} or {@code $fingerprint~nickname} + * of servers contained in this server's family, or null if the + * descriptor does not contain a family line. + * + * @since 1.0.0 + */ + public List<String> getFamilyEntries(); + + /** + * Return the server's history of read bytes, or null if the descriptor + * does not contain a bandwidth history; current Tor versions include + * bandwidth histories in their extra-info descriptors + * ({@link ExtraInfoDescriptor#getReadHistory()}), not in their server + * descriptors. + * + * @since 1.0.0 + */ + public BandwidthHistory getReadHistory(); + + /** + * Return the server's history of written bytes, or null if the + * descriptor does not contain a bandwidth history; current Tor versions + * include bandwidth histories in their extra-info descriptors + * ({@link ExtraInfoDescriptor#getWriteHistory()}), not in their server + * descriptors. + * + * @since 1.0.0 + */ + public BandwidthHistory getWriteHistory(); + + /** + * Return true if the server uses the enhanced DNS logic, or false if + * doesn't use it or doesn't include an eventdns line in its + * descriptor; current Tor versions should be presumed to have the evdns + * backend. + * + * @since 1.0.0 + */ + public boolean getUsesEnhancedDnsLogic(); + + /** + * Return whether this server is a directory cache that provides + * extra-info descriptors. + * + * @since 1.0.0 + */ + public boolean getCachesExtraInfo(); + + /** + * Return the SHA-1 digest of the server's extra-info descriptor, + * encoded as 40 upper-case hexadecimal characters, or null if the + * server did not upload a corresponding extra-info descriptor. + * + * @since 1.0.0 + */ + public String getExtraInfoDigest(); + + /** + * Return the SHA-256 digest of the server's extra-info descriptor, + * encoded as 43 base64 characters without padding characters, or null + * if the server either did not upload a corresponding extra-info + * descriptor or did not refer to it using a SHA-256 digest. + * + * @since 1.1.0 + */ + public String getExtraInfoDigestSha256(); + + /** + * Return the list of hidden service descriptor version numbers that + * this server stores and serves, or null if it doesn't store and serve + * any hidden service descriptors. + * + * @since 1.0.0 + */ + public List<Integer> getHiddenServiceDirVersions(); + + /** + * Return the list of link protocol versions that this server + * supports. + * + * @since 1.0.0 + */ + public List<Integer> getLinkProtocolVersions(); + + /** + * Return the list of circuit protocol versions that this server + * supports. + * + * @since 1.0.0 + */ + public List<Integer> getCircuitProtocolVersions(); + + /** + * Return whether this server allows single-hop circuits to make exit + * connections. + * + * @since 1.0.0 + */ + public boolean getAllowSingleHopExits(); + + /** + * Return the default policy, {@code "accept"} or {@code "reject"}, of + * the IPv6 port summary, or null if the descriptor didn't contain an + * IPv6 exit-policy summary line which is equivalent to rejecting all + * streams to IPv6 targets. + * + * @since 1.0.0 + */ + public String getIpv6DefaultPolicy(); + + /** + * Return the port list of the IPv6 exit-policy summary, or null if the + * descriptor didn't contain an IPv6 exit-policy summary line which is + * equivalent to rejecting all streams to IPv6 targets. + * + * @since 1.0.0 + */ + public String getIpv6PortList(); + + /** + * Return the curve25519 public key, encoded as 43 base64 characters + * without padding characters, that is used for the ntor circuit + * extended handshake, or null if the descriptor didn't contain an + * ntor-onion-key line. */ + public String getNtorOnionKey(); + + /** + * Return the Ed25519 certificate in PEM format, or null if the + * descriptor doesn't contain one. + * + * @since 1.1.0 + */ + public String getIdentityEd25519(); + + /** + * Return the Ed25519 master key, encoded as 43 base64 characters + * without padding characters, which was either parsed from the optional + * {@code "master-key-ed25519"} line or derived from the (likewise + * optional) Ed25519 certificate following the + * {@code "identity-ed25519"} line, or null if the descriptor contains + * neither Ed25519 master key nor Ed25519 certificate. + * + * @since 1.1.0 + */ + public String getMasterKeyEd25519(); + + /** + * Return the Ed25519 signature of the SHA-256 digest of the entire + * descriptor, encoded as 86 base64 characters without padding + * characters, from the first character up to and including the first + * space after the {@code "router-sig-ed25519"} string, prefixed with + * the string {@code "Tor router descriptor signature v1"}. + * + * @since 1.1.0 + */ + public String getRouterSignatureEd25519(); + + /** + * Return an RSA-1024 signature in PEM format, generated using the + * server's onion key, that proves that the party creating the + * descriptor had control over the private key corresponding to the + * onion key, or null if the descriptor does not contain such a + * signature. + * + * @since 1.1.0 + */ + public String getOnionKeyCrosscert(); + + /** + * Return an Ed25519 signature in PEM format, generated using the + * server's ntor onion key, that proves that the party creating the + * descriptor had control over the private key corresponding to the ntor + * onion key, or null if the descriptor does not contain such a + * signature. + * + * @since 1.1.0 + */ + public String getNtorOnionKeyCrosscert(); + + /** + * Return the sign of the Ed25519 public key corresponding to the ntor + * onion key as 0 or 1, or -1 if the descriptor does not contain this + * information. + * + * @since 1.1.0 + */ + public int getNtorOnionKeyCrosscertSign(); + + /** + * Return whether the server accepts "tunneled" directory requests using + * a BEGIN_DIR cell over the server's OR port. + * + * @since 1.3.0 + */ + public boolean getTunnelledDirServer(); +} + diff --git a/src/main/java/org/torproject/descriptor/TorperfResult.java b/src/main/java/org/torproject/descriptor/TorperfResult.java new file mode 100644 index 0000000..188200b --- /dev/null +++ b/src/main/java/org/torproject/descriptor/TorperfResult.java @@ -0,0 +1,215 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.descriptor; + +import java.util.List; +import java.util.SortedMap; + +/** + * Contains performance measurement results from making simple HTTP + * requests over the Tor network. + * + * <p>The performance measurement service Torperf publishes performance + * data from making simple HTTP requests over the Tor network. Torperf + * uses a trivial SOCKS client to download files of various sizes over the + * Tor network and notes how long substeps take.</p> + * + * @since 1.0.0 + */ +public interface TorperfResult extends Descriptor { + + /** + * Return all unrecognized keys together with their values, or null if + * all keys were recognized. + * + * @since 1.2.0 + */ + public SortedMap<String, String> getUnrecognizedKeys(); + + /** + * Return the configured name of the data source. + * + * @since 1.0.0 + */ + public String getSource(); + + /** + * Return the configured file size in bytes. + * + * @since 1.0.0 + */ + public int getFileSize(); + + /** + * Return the time in milliseconds since the epoch when the connection + * process started. + * + * @since 1.0.0 + */ + public long getStartMillis(); + + /** + * Return the time in milliseconds since the epoch when the socket was + * created. + * + * @since 1.0.0 + */ + public long getSocketMillis(); + + /** + * Return the time in milliseconds since the epoch when the socket was + * connected. + * + * @since 1.0.0 + */ + public long getConnectMillis(); + + /** + * Return the time in milliseconds since the epoch when SOCKS 5 + * authentication methods have been negotiated. + * + * @since 1.0.0 + */ + public long getNegotiateMillis(); + + /** + * Return the time in milliseconds since the epoch when the SOCKS + * request was sent. + * + * @since 1.0.0 + */ + public long getRequestMillis(); + + /** + * Return the time in milliseconds since the epoch when the SOCKS + * response was received. + * + * @since 1.0.0 + */ + public long getResponseMillis(); + + /** + * Return the time in milliseconds since the epoch when the HTTP + * request was written. + * + * @since 1.0.0 + */ + public long getDataRequestMillis(); + + /** + * Return the time in milliseconds since the epoch when the first + * response was received. + * + * @since 1.0.0 + */ + public long getDataResponseMillis(); + + /** + * Return the time in milliseconds since the epoch when the payload was + * complete. + * + * @since 1.0.0 + */ + public long getDataCompleteMillis(); + + /** + * Return the total number of bytes written. + * + * @since 1.0.0 + */ + public int getWriteBytes(); + + /** + * Return the total number of bytes read. + * + * @since 1.0.0 + */ + public int getReadBytes(); + + /** + * Return whether the request timed out (as opposed to failing), or + * null if the torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public Boolean didTimeout(); + + /** + * Return the times in milliseconds since the epoch when {@code x%} of + * expected bytes were read for {@code 0 <= x <= 100}, or null if the + * torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public SortedMap<Integer, Long> getDataPercentiles(); + + /** + * Return the time in milliseconds since the epoch when the circuit was + * launched, or -1 if the torperf line didn't contain that + * information. + * + * @since 1.0.0 + */ + public long getLaunchMillis(); + + /** + * Return the time in milliseconds since the epoch when the circuit was + * used, or -1 if the torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public long getUsedAtMillis(); + + /** + * Return a list of fingerprints of the relays in the circuit, or null + * if the torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public List<String> getPath(); + + /** + * Return a list of times in milliseconds since the epoch when circuit + * hops were built, or null if the torperf line didn't contain that + * information. + * + * @since 1.0.0 + */ + public List<Long> getBuildTimes(); + + /** + * Return the circuit build timeout that the Tor client used when + * building this circuit, or -1 if the torperf line didn't contain that + * information. + * + * @since 1.0.0 + */ + public long getTimeout(); + + /** + * Return the circuit build time quantile that the Tor client uses to + * determine its circuit-build timeout, or -1 if the torperf line + * didn't contain that information. + * + * @since 1.0.0 + */ + public double getQuantile(); + + /** + * Return the identifier of the circuit used for this measurement, or + * -1 if the torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public int getCircId(); + + /** + * Return the identifier of the stream used for this measurement, or -1 + * if the torperf line didn't contain that information. + * + * @since 1.0.0 + */ + public int getUsedBy(); +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BandwidthHistoryImpl.java b/src/main/java/org/torproject/descriptor/impl/BandwidthHistoryImpl.java new file mode 100644 index 0000000..295e0a4 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BandwidthHistoryImpl.java @@ -0,0 +1,100 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.torproject.descriptor.BandwidthHistory; + +public class BandwidthHistoryImpl implements BandwidthHistory { + + protected BandwidthHistoryImpl(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + boolean isValid = false; + this.line = line; + if (partsNoOpt.length == 5 || partsNoOpt.length == 6) { + try { + this.historyEndMillis = ParseHelper.parseTimestampAtIndex(line, + partsNoOpt, 1, 2); + if (partsNoOpt[3].startsWith("(") && + 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 + "'."); + } + String[] values = null; + if (partsNoOpt.length == 5 && + partsNoOpt[4].equals("s)")) { + /* There are no bandwidth values to parse. */ + isValid = true; + } 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) { + this.bandwidthValues = new long[values.length]; + for (int i = values.length - 1; i >= 0; i--) { + long bandwidthValue = Long.parseLong(values[i]); + if (bandwidthValue < 0L) { + throw new DescriptorParseException("Negative bandwidth " + + "values are not allowed in line '" + line + "'."); + } + this.bandwidthValues[i] = bandwidthValue; + } + isValid = true; + } + } + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (!isValid) { + throw new DescriptorParseException("Invalid bandwidth-history line " + + "'" + line + "'."); + } + } + + private String line; + @Override + public String getLine() { + return this.line; + } + + private long historyEndMillis; + @Override + public long getHistoryEndMillis() { + return this.historyEndMillis; + } + + private long intervalLength; + @Override + public long getIntervalLength() { + return this.intervalLength; + } + + private long[] bandwidthValues; + @Override + public SortedMap<Long, Long> getBandwidthValues() { + SortedMap<Long, Long> result = new TreeMap<>(); + if (this.bandwidthValues != null) { + long endMillis = this.historyEndMillis; + for (int i = this.bandwidthValues.length - 1; i >= 0; i--) { + result.put(endMillis, bandwidthValues[i]); + endMillis -= this.intervalLength * 1000L; + } + } + return result; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BlockingIteratorImpl.java b/src/main/java/org/torproject/descriptor/impl/BlockingIteratorImpl.java new file mode 100644 index 0000000..66426d8 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BlockingIteratorImpl.java @@ -0,0 +1,98 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Queue; + +/* Provide an iterator for a queue of objects and block when there are + * currently no objects in the queue. Allow the producer to signal that + * there won't be further objects and unblock any waiting consumers. */ +public class BlockingIteratorImpl<T> implements Iterator<T> { + + /* Queue containing produced elemnts waiting for consumers. */ + private Queue<T> queue = new LinkedList<>(); + + /* Maximum number of elements in queue. */ + private int maxQueueSize = 100; + + /* Restrict object construction to the impl package. */ + protected BlockingIteratorImpl() { + } + + /* Create instance with maximum queue size. */ + protected BlockingIteratorImpl(int maxQueueSize) { + this.maxQueueSize = maxQueueSize; + } + + /* Add an object to the queue if there's still room. */ + protected synchronized void add(T object) { + if (this.outOfDescriptors) { + throw new IllegalStateException("Internal error: Adding results to " + + "descriptor queue not allowed after sending end-of-stream " + + "object."); + } + while (this.queue.size() >= this.maxQueueSize) { + try { + wait(); + } catch (InterruptedException e) { + } + } + this.queue.offer(object); + notifyAll(); + } + + /* Signalize that there won't be any further objects to be enqueued. */ + private boolean outOfDescriptors = false; + protected synchronized void setOutOfDescriptors() { + if (this.outOfDescriptors) { + throw new IllegalStateException("Internal error: Sending " + + "end-of-stream object only permitted once."); + } + this.outOfDescriptors = true; + notifyAll(); + } + + /* Return whether there are more objects. Block if there are currently + * no objects, but the producer hasn't signalized that there won't be + * further objects. */ + @Override + public synchronized boolean hasNext() { + while (!this.outOfDescriptors && this.queue.isEmpty()) { + try { + wait(); + } catch (InterruptedException e) { + } + } + return this.queue.peek() != null; + } + + /* Return the next object in the queue or throw an exception when there + * are no further objects. Block if there are currently no objects, but + * the producer hasn't signalized that there won't be further + * objects. */ + @Override + public synchronized T next() { + while (!this.outOfDescriptors && this.queue.isEmpty()) { + try { + wait(); + } catch (InterruptedException e) { + } + } + if (this.queue.peek() == null) { + throw new NoSuchElementException(); + } + notifyAll(); + return this.queue.remove(); + } + + /* Don't support explicitly removing objects. They are removed + * anyway. */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java new file mode 100644 index 0000000..15d40d8 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java @@ -0,0 +1,37 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.BridgeExtraInfoDescriptor; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExtraInfoDescriptor; + +public class BridgeExtraInfoDescriptorImpl + extends ExtraInfoDescriptorImpl implements BridgeExtraInfoDescriptor { + + protected static List<ExtraInfoDescriptor> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<ExtraInfoDescriptor> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "extra-info "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + ExtraInfoDescriptor parsedDescriptor = + new BridgeExtraInfoDescriptorImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected BridgeExtraInfoDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java b/src/main/java/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java new file mode 100644 index 0000000..bf3804d --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java @@ -0,0 +1,230 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Map; +import java.util.Scanner; +import java.util.SortedMap; +import java.util.TimeZone; + +import org.torproject.descriptor.BridgeNetworkStatus; + +/* Contains a bridge network status. */ +public class BridgeNetworkStatusImpl extends NetworkStatusImpl + implements BridgeNetworkStatus { + + protected BridgeNetworkStatusImpl(byte[] statusBytes, + String fileName, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(statusBytes, failUnrecognizedDescriptorLines, false, false); + this.setPublishedMillisFromFileName(fileName); + } + + private void setPublishedMillisFromFileName(String fileName) + throws DescriptorParseException { + if (this.publishedMillis != 0L) { + /* We already learned the publication timestamp from parsing the + * "published" line. */ + return; + } + if (fileName.length() == + "20000101-000000-4A0CCD2DDC7995083D73F5D667100C8A5831F16D". + length()) { + String publishedString = fileName.substring(0, + "yyyyMMdd-HHmmss".length()); + try { + SimpleDateFormat fileNameFormat = new SimpleDateFormat( + "yyyyMMdd-HHmmss"); + fileNameFormat.setLenient(false); + fileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + this.publishedMillis = fileNameFormat.parse(publishedString). + getTime(); + } catch (ParseException e) { + } + } + if (this.publishedMillis == 0L) { + throw new DescriptorParseException("Unrecognized bridge network " + + "status file name '" + fileName + "'."); + } + } + + protected void parseHeader(byte[] headerBytes) + throws DescriptorParseException { + /* Initialize flag-thresholds values here for the case that the status + * doesn't contain those values. Initializing them in the constructor + * or when declaring variables wouldn't work, because those parts are + * evaluated later and would overwrite everything we parse here. */ + this.stableUptime = -1L; + this.stableMtbf = -1L; + this.fastBandwidth = -1L; + this.guardWfu = -1.0; + this.guardTk = -1L; + this.guardBandwidthIncludingExits = -1L; + this.guardBandwidthExcludingExits = -1L; + this.enoughMtbfInfo = -1; + this.ignoringAdvertisedBws = -1; + + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "published": + this.parsePublishedLine(line, parts); + break; + case "flag-thresholds": + this.parseFlagThresholdsLine(line, parts); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in bridge network status."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parsePublishedLine(String line, String[] parts) + throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseFlagThresholdsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No flag thresholds in line '" + + line + "'."); + } + SortedMap<String, String> flagThresholds = + ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); + try { + for (Map.Entry<String, String> e : flagThresholds.entrySet()) { + switch (e.getKey()) { + case "stable-uptime": + this.stableUptime = Long.parseLong(e.getValue()); + break; + case "stable-mtbf": + this.stableMtbf = Long.parseLong(e.getValue()); + break; + case "fast-speed": + this.fastBandwidth = Long.parseLong(e.getValue()); + break; + case "guard-wfu": + this.guardWfu = Double.parseDouble(e.getValue(). + replaceAll("%", "")); + break; + case "guard-tk": + this.guardTk = Long.parseLong(e.getValue()); + break; + case "guard-bw-inc-exits": + this.guardBandwidthIncludingExits = + Long.parseLong(e.getValue()); + break; + case "guard-bw-exc-exits": + this.guardBandwidthExcludingExits = + Long.parseLong(e.getValue()); + break; + case "enough-mtbf": + this.enoughMtbfInfo = Integer.parseInt(e.getValue()); + break; + case "ignoring-advertised-bws": + this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); + break; + } + } + } catch (NumberFormatException ex) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + + protected void parseDirSource(byte[] dirSourceBytes) + throws DescriptorParseException { + throw new DescriptorParseException("No directory source expected in " + + "bridge network status."); + } + + protected void parseFooter(byte[] footerBytes) + throws DescriptorParseException { + throw new DescriptorParseException("No directory footer expected in " + + "bridge network status."); + } + + protected void parseDirectorySignature(byte[] directorySignatureBytes) + throws DescriptorParseException { + throw new DescriptorParseException("No directory signature expected " + + "in bridge network status."); + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private long stableUptime; + @Override + public long getStableUptime() { + return this.stableUptime; + } + + private long stableMtbf; + @Override + public long getStableMtbf() { + return this.stableMtbf; + } + + private long fastBandwidth; + @Override + public long getFastBandwidth() { + return this.fastBandwidth; + } + + private double guardWfu; + @Override + public double getGuardWfu() { + return this.guardWfu; + } + + private long guardTk; + @Override + public long getGuardTk() { + return this.guardTk; + } + + private long guardBandwidthIncludingExits; + @Override + public long getGuardBandwidthIncludingExits() { + return this.guardBandwidthIncludingExits; + } + + private long guardBandwidthExcludingExits; + @Override + public long getGuardBandwidthExcludingExits() { + return this.guardBandwidthExcludingExits; + } + + private int enoughMtbfInfo; + @Override + public int getEnoughMtbfInfo() { + return this.enoughMtbfInfo; + } + + private int ignoringAdvertisedBws; + @Override + public int getIgnoringAdvertisedBws() { + return this.ignoringAdvertisedBws; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java b/src/main/java/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java new file mode 100644 index 0000000..99578e8 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java @@ -0,0 +1,99 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +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 java.util.SortedMap; +import java.util.TreeMap; + +import org.torproject.descriptor.BridgePoolAssignment; + +/* TODO Write a test class. */ +public class BridgePoolAssignmentImpl extends DescriptorImpl + implements BridgePoolAssignment { + + protected static List<BridgePoolAssignment> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<BridgePoolAssignment> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "bridge-pool-assignment "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + BridgePoolAssignment parsedDescriptor = + new BridgePoolAssignmentImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected BridgePoolAssignmentImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseDescriptorBytes(); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList( + new String[] { "bridge-pool-assignment" })); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + this.checkFirstKeyword("bridge-pool-assignment"); + this.clearParsedKeywords(); + return; + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("bridge-pool-assignment ")) { + this.parseBridgePoolAssignmentLine(line); + } else { + this.parseBridgeLine(line); + } + } + } + + private void parseBridgePoolAssignmentLine(String line) + throws DescriptorParseException { + String[] parts = line.split("[ \t]+"); + if (parts.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + + "' in bridge pool assignment."); + } + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseBridgeLine(String line) + throws DescriptorParseException { + String[] parts = line.split("[ \t]+"); + if (parts.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in bridge pool assignment."); + } + String fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[0]); + String poolAndDetails = line.substring(line.indexOf(" ") + 1); + this.entries.put(fingerprint, poolAndDetails); + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private SortedMap<String, String> entries = new TreeMap<>(); + @Override + public SortedMap<String, String> getEntries() { + return new TreeMap<>(this.entries); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java new file mode 100644 index 0000000..eb2b933 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java @@ -0,0 +1,37 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.BridgeServerDescriptor; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ServerDescriptor; + +public class BridgeServerDescriptorImpl extends ServerDescriptorImpl + implements BridgeServerDescriptor { + + protected static List<ServerDescriptor> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<ServerDescriptor> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "router "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + ServerDescriptor parsedDescriptor = + new BridgeServerDescriptorImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected BridgeServerDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorCollectorImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorCollectorImpl.java new file mode 100644 index 0000000..1a030ef --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorCollectorImpl.java @@ -0,0 +1,249 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.BufferedOutputStream; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Map; +import java.util.Scanner; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Stack; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +import org.torproject.descriptor.DescriptorCollector; + +public class DescriptorCollectorImpl implements DescriptorCollector { + + @Override + public void collectDescriptors(String collecTorBaseUrl, + String[] remoteDirectories, long minLastModified, + File localDirectory, boolean deleteExtraneousLocalFiles) { + collecTorBaseUrl = collecTorBaseUrl.endsWith("/") + ? collecTorBaseUrl.substring(0, collecTorBaseUrl.length() - 1) + : collecTorBaseUrl; + if (minLastModified < 0) { + throw new IllegalArgumentException("A negative minimum " + + "last-modified time is not permitted."); + } + if (localDirectory.exists() && !localDirectory.isDirectory()) { + throw new IllegalArgumentException("Local directory already exists " + + "and is not a directory."); + } + SortedMap<String, Long> localFiles = + this.statLocalDirectory(localDirectory); + SortedMap<String, String> fetchedDirectoryListings = + this.fetchRemoteDirectories(collecTorBaseUrl, remoteDirectories); + SortedSet<String> parsedDirectories = new TreeSet<>(); + SortedMap<String, Long> remoteFiles = new TreeMap<>(); + for (Map.Entry<String, String> e : + fetchedDirectoryListings.entrySet()) { + String remoteDirectory = e.getKey(); + String directoryListing = e.getValue(); + SortedMap<String, Long> parsedRemoteFiles = + this.parseDirectoryListing(remoteDirectory, directoryListing); + if (parsedRemoteFiles == null) { + continue; + } + parsedDirectories.add(remoteDirectory); + remoteFiles.putAll(parsedRemoteFiles); + } + this.fetchRemoteFiles(collecTorBaseUrl, remoteFiles, minLastModified, + localDirectory, localFiles); + if (deleteExtraneousLocalFiles) { + this.deleteExtraneousLocalFiles(parsedDirectories, remoteFiles, + localDirectory, localFiles); + } + } + + SortedMap<String, Long> statLocalDirectory( + File localDirectory) { + SortedMap<String, Long> localFiles = new TreeMap<>(); + if (!localDirectory.exists()) { + return localFiles; + } + Stack<File> files = new Stack<>(); + files.add(localDirectory); + while (!files.isEmpty()) { + File file = files.pop(); + if (file.isDirectory()) { + files.addAll(Arrays.asList(file.listFiles())); + } else { + String localPath = file.getPath().substring( + localDirectory.getPath().length()); + localFiles.put(localPath, file.lastModified()); + } + } + return localFiles; + } + + SortedMap<String, String> fetchRemoteDirectories( + String collecTorBaseUrl, String[] remoteDirectories) { + SortedMap<String, String> fetchedDirectoryListings = new TreeMap<>(); + for (String remoteDirectory : remoteDirectories) { + String remoteDirectoryWithSlashAtBeginAndEnd = + (remoteDirectory.startsWith("/") ? "" : "/") + remoteDirectory + + (remoteDirectory.endsWith("/") ? "" : "/"); + String directoryUrl = collecTorBaseUrl + + remoteDirectoryWithSlashAtBeginAndEnd; + String directoryListing = this.fetchRemoteDirectory(directoryUrl); + if (directoryListing.length() > 0) { + fetchedDirectoryListings.put( + remoteDirectoryWithSlashAtBeginAndEnd, directoryListing); + } + } + return fetchedDirectoryListings; + } + + String fetchRemoteDirectory(String url) { + StringBuilder sb = new StringBuilder(); + HttpURLConnection huc = null; + try { + URL u = new URL(url); + huc = (HttpURLConnection) u.openConnection(); + huc.setRequestMethod("GET"); + huc.connect(); + int responseCode = huc.getResponseCode(); + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader( + huc.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + sb.append(line).append("\n"); + } + br.close(); + } + } catch (IOException e) { + e.printStackTrace(); + if (huc != null) { + huc.disconnect(); + } + return ""; + } + return sb.toString(); + } + + final Pattern DIRECTORY_LISTING_LINE_PATTERN = + Pattern.compile(".* href=\"([^\"/]+)\"" /* filename */ + + ".*>(\\d{2}-\\w{3}-\\d{4} \\d{2}:\\d{2})\\s*<.*"); /* dateTime */ + + SortedMap<String, Long> parseDirectoryListing( + String remoteDirectory, String directoryListing) { + SortedMap<String, Long> remoteFiles = new TreeMap<>(); + DateFormat dateTimeFormat = ParseHelper.getDateFormat( + "dd-MMM-yyyy HH:mm"); + try { + Scanner s = new Scanner(directoryListing); + s.useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + Matcher matcher = DIRECTORY_LISTING_LINE_PATTERN.matcher(line); + if (matcher.matches()) { + String filename = matcher.group(1); + long lastModifiedMillis = dateTimeFormat.parse( + matcher.group(2)).getTime(); + remoteFiles.put(remoteDirectory + filename, lastModifiedMillis); + } + } + s.close(); + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + return remoteFiles; + } + + void fetchRemoteFiles(String collecTorBaseUrl, + SortedMap<String, Long> remoteFiles, long minLastModified, + File localDirectory, SortedMap<String, Long> localFiles) { + for (Map.Entry<String, Long> e : remoteFiles.entrySet()) { + String filename = e.getKey(); + long lastModifiedMillis = e.getValue(); + if (lastModifiedMillis < minLastModified || + (localFiles.containsKey(filename) && + localFiles.get(filename) >= lastModifiedMillis)) { + continue; + } + String url = collecTorBaseUrl + filename; + File destinationFile = new File(localDirectory.getPath() + + filename); + this.fetchRemoteFile(url, destinationFile, lastModifiedMillis); + } + } + + void fetchRemoteFile(String url, File destinationFile, + long lastModifiedMillis) { + HttpURLConnection huc = null; + try { + File destinationDirectory = destinationFile.getParentFile(); + destinationDirectory.mkdirs(); + File tempDestinationFile = new File(destinationDirectory, "." + + destinationFile.getName()); + BufferedOutputStream bos = new BufferedOutputStream( + new FileOutputStream(tempDestinationFile)); + URL u = new URL(url); + huc = (HttpURLConnection) u.openConnection(); + huc.setRequestMethod("GET"); + if (!url.endsWith(".xz")) { + huc.addRequestProperty("Accept-Encoding", "gzip"); + } + huc.connect(); + int responseCode = huc.getResponseCode(); + if (responseCode == 200) { + InputStream is; + if (huc.getContentEncoding() != null && + huc.getContentEncoding().equalsIgnoreCase("gzip")) { + is = new GZIPInputStream(huc.getInputStream()); + } else { + is = huc.getInputStream(); + } + BufferedInputStream bis = new BufferedInputStream(is); + int len; + byte[] data = new byte[8192]; + while ((len = bis.read(data, 0, 8192)) >= 0) { + bos.write(data, 0, len); + } + bis.close(); + bos.close(); + tempDestinationFile.renameTo(destinationFile); + destinationFile.setLastModified(lastModifiedMillis); + } + } catch (IOException e) { + e.printStackTrace(); + if (huc != null) { + huc.disconnect(); + } + } + } + + void deleteExtraneousLocalFiles( + SortedSet<String> parsedDirectories, + SortedMap<String, Long> remoteFiles, File localDirectory, + SortedMap<String, Long> localFiles) { + for (String localPath : localFiles.keySet()) { + for (String remoteDirectory : parsedDirectories) { + if (localPath.startsWith(remoteDirectory)) { + if (!remoteFiles.containsKey(localPath)) { + new File(localDirectory.getPath() + localPath).delete(); + } + } + } + } + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java new file mode 100644 index 0000000..e726ce9 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java @@ -0,0 +1,283 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.torproject.descriptor.DescriptorRequest; +import org.torproject.descriptor.DescriptorDownloader; + +public class DescriptorDownloaderImpl + implements DescriptorDownloader { + + private boolean hasStartedDownloading = false; + + private SortedMap<String, DirectoryDownloader> directoryAuthorities = + new TreeMap<>(); + @Override + public void addDirectoryAuthority(String nickname, String ip, + int dirPort) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.checkDirectoryParameters(nickname, ip, dirPort); + DirectoryDownloader directoryAuthority = new DirectoryDownloader( + nickname, ip, dirPort); + this.directoryAuthorities.put(nickname, directoryAuthority); + } + + private SortedMap<String, DirectoryDownloader> directoryMirrors = + new TreeMap<>(); + @Override + public void addDirectoryMirror(String nickname, String ip, + int dirPort) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.checkDirectoryParameters(nickname, ip, dirPort); + DirectoryDownloader directoryMirror = new DirectoryDownloader( + nickname, ip, dirPort); + this.directoryMirrors.put(nickname, directoryMirror); + /* TODO Implement prioritizing mirrors for non-vote downloads. */ + throw new UnsupportedOperationException("Prioritizing directory " + + "mirrors over directory authorities is not implemented yet. " + + "Until it is, configuring directory mirrors is misleading and " + + "therefore not supported."); + } + + private void checkDirectoryParameters(String nickname, String ip, + int dirPort) { + if (nickname == null || nickname.length() < 1) { + throw new IllegalArgumentException("'" + nickname + "' is not a " + + "valid nickname."); + } + if (ip == null || ip.length() < 7 || ip.split("\\.").length != 4) { + throw new IllegalArgumentException("'" + ip + "' is not a valid IP " + + "address."); + } + if (dirPort < 1 || dirPort > 65535) { + throw new IllegalArgumentException(String.valueOf(dirPort) + " is " + + "not a valid DirPort."); + } + /* TODO Relax the requirement for directory nicknames to be unique. + * In theory, we can identify them by ip+port. */ + if (this.directoryAuthorities.containsKey(nickname) || + this.directoryMirrors.containsKey(nickname)) { + throw new IllegalArgumentException("Directory nicknames must be " + + "unique."); + } + } + + private boolean downloadConsensus = false; + @Override + public void setIncludeCurrentConsensus() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.downloadConsensus = true; + } + + private boolean downloadConsensusFromAllAuthorities = false; + @Override + public void setIncludeCurrentConsensusFromAllDirectoryAuthorities() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.downloadConsensusFromAllAuthorities = true; + } + + private boolean includeCurrentReferencedVotes = false; + @Override + public void setIncludeCurrentReferencedVotes() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.includeCurrentReferencedVotes = true; + } + + private Set<String> downloadVotes = new HashSet<>(); + @Override + public void setIncludeCurrentVote(String fingerprint) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.checkVoteFingerprint(fingerprint); + this.downloadVotes.add(fingerprint); + } + + @Override + public void setIncludeCurrentVotes(Set<String> fingerprints) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + if (fingerprints == null) { + throw new IllegalArgumentException("Set of fingerprints must not " + + "be null."); + } + for (String fingerprint : fingerprints) { + this.checkVoteFingerprint(fingerprint); + } + for (String fingerprint : fingerprints) { + this.setIncludeCurrentVote(fingerprint); + } + } + + private void checkVoteFingerprint(String fingerprint) { + if (fingerprint == null || fingerprint.length() != 40) { + throw new IllegalArgumentException("'" + fingerprint + "' is not a " + + "valid fingerprint."); + } + } + + @Override + public void setIncludeReferencedServerDescriptors() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading server " + + "descriptors is not implemented yet."); + } + + @Override + public void setExcludeServerDescriptor(String identifier) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading server " + + "descriptors is not implemented yet."); + } + + @Override + public void setExcludeServerDescriptors(Set<String> identifier) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading server " + + "descriptors is not implemented yet."); + } + + @Override + public void setIncludeReferencedExtraInfoDescriptors() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading extra-info " + + "descriptors is not implemented yet."); + } + + @Override + public void setExcludeExtraInfoDescriptor(String identifier) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading extra-info " + + "descriptors is not implemented yet."); + } + + @Override + public void setExcludeExtraInfoDescriptors(Set<String> identifiers) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + /* TODO Implement me. */ + throw new UnsupportedOperationException("Downloading extra-info " + + "descriptors is not implemented yet."); + } + + private long readTimeoutMillis = 60L * 1000L; + @Override + public void setReadTimeout(long readTimeoutMillis) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + if (readTimeoutMillis < 0L) { + throw new IllegalArgumentException("Read timeout value " + + String.valueOf(readTimeoutMillis) + " may not be " + + "negative."); + } + this.readTimeoutMillis = readTimeoutMillis; + } + + private long connectTimeoutMillis = 60L * 1000L; + @Override + public void setConnectTimeout(long connectTimeoutMillis) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + if (connectTimeoutMillis < 0L) { + throw new IllegalArgumentException("Connect timeout value " + + String.valueOf(connectTimeoutMillis) + " may not be " + + "negative."); + } + this.connectTimeoutMillis = connectTimeoutMillis; + } + + private long globalTimeoutMillis = 60L * 60L * 1000L; + @Override + public void setGlobalTimeout(long globalTimeoutMillis) { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + if (globalTimeoutMillis < 0L) { + throw new IllegalArgumentException("Global timeout value " + + String.valueOf(globalTimeoutMillis) + " may not be " + + "negative."); + } + this.globalTimeoutMillis = globalTimeoutMillis; + } + + private boolean failUnrecognizedDescriptorLines = false; + @Override + public void setFailUnrecognizedDescriptorLines() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to download."); + } + this.failUnrecognizedDescriptorLines = true; + } + + @Override + public Iterator<DescriptorRequest> downloadDescriptors() { + if (this.hasStartedDownloading) { + throw new IllegalStateException("Initiating downloads is only " + + "permitted once."); + } + this.hasStartedDownloading = true; + DownloadCoordinatorImpl downloadCoordinator = + new DownloadCoordinatorImpl(this.directoryAuthorities, + this.directoryMirrors, this.downloadConsensus, + this.downloadConsensusFromAllAuthorities, this.downloadVotes, + this.includeCurrentReferencedVotes, this.connectTimeoutMillis, + this.readTimeoutMillis, this.globalTimeoutMillis, + this.failUnrecognizedDescriptorLines); + Iterator<DescriptorRequest> descriptorQueue = downloadCoordinator. + getDescriptorQueue(); + return descriptorQueue; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorFileImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorFileImpl.java new file mode 100644 index 0000000..801c546 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorFileImpl.java @@ -0,0 +1,78 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorFile; + +public class DescriptorFileImpl implements DescriptorFile { + + private File directory; + protected void setDirectory(File directory) { + this.directory = directory; + } + @Override + public File getDirectory() { + return this.directory; + } + + private File tarball; + protected void setTarball(File tarball) { + this.tarball = tarball; + } + @Override + public File getTarball() { + return this.tarball; + } + + private File file; + protected void setFile(File file) { + this.file = file; + } + @Override + public File getFile() { + return this.file; + } + + private String fileName; + protected void setFileName(String fileName) { + this.fileName = fileName; + } + @Override + public String getFileName() { + return this.fileName; + } + + private long lastModified; + protected void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + @Override + public long getLastModified() { + return this.lastModified; + } + + private List<Descriptor> descriptors; + protected void setDescriptors(List<Descriptor> descriptors) { + this.descriptors = descriptors; + } + @Override + public List<Descriptor> getDescriptors() { + return this.descriptors == null ? new ArrayList<Descriptor>() : + new ArrayList<>(this.descriptors); + } + + private Exception exception; + protected void setException(Exception exception) { + this.exception = exception; + } + @Override + public Exception getException() { + return this.exception; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorImpl.java new file mode 100644 index 0000000..5625b3f --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorImpl.java @@ -0,0 +1,337 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; + +import org.torproject.descriptor.Descriptor; + +public abstract class DescriptorImpl implements Descriptor { + + protected static List<Descriptor> parseDescriptors( + byte[] rawDescriptorBytes, String fileName, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<Descriptor> parsedDescriptors = new ArrayList<>(); + if (rawDescriptorBytes == null) { + return parsedDescriptors; + } + byte[] first100Chars = new byte[Math.min(100, + rawDescriptorBytes.length)]; + System.arraycopy(rawDescriptorBytes, 0, first100Chars, 0, + first100Chars.length); + String firstLines = new String(first100Chars); + if (firstLines.startsWith("@type network-status-consensus-3 1.") || + 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, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type network-status-vote-3 1.") + || ((firstLines.startsWith("network-status-version 3\n") || + firstLines.contains("\nnetwork-status-version 3\n")) && + firstLines.contains("\nvote-status vote\n"))) { + parsedDescriptors.addAll(RelayNetworkStatusVoteImpl. + parseVotes(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type bridge-network-status 1.") + || firstLines.startsWith("r ")) { + parsedDescriptors.add(new BridgeNetworkStatusImpl( + rawDescriptorBytes, fileName, failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith( + "@type bridge-server-descriptor 1.")) { + parsedDescriptors.addAll(BridgeServerDescriptorImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type server-descriptor 1.") || + firstLines.startsWith("router ") || + firstLines.contains("\nrouter ")) { + parsedDescriptors.addAll(RelayServerDescriptorImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type bridge-extra-info 1.")) { + parsedDescriptors.addAll(BridgeExtraInfoDescriptorImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type extra-info 1.") || + firstLines.startsWith("extra-info ") || + firstLines.contains("\nextra-info ")) { + parsedDescriptors.addAll(RelayExtraInfoDescriptorImpl. + 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 ")) { + parsedDescriptors.addAll(BridgePoolAssignmentImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type dir-key-certificate-3 1.") || + firstLines.startsWith("dir-key-certificate-version ") || + firstLines.contains("\ndir-key-certificate-version ")) { + parsedDescriptors.addAll(DirectoryKeyCertificateImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type tordnsel 1.") || + firstLines.startsWith("ExitNode ") || + firstLines.contains("\nExitNode ")) { + parsedDescriptors.add(new ExitListImpl(rawDescriptorBytes, fileName, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type network-status-2 1.") || + firstLines.startsWith("network-status-version 2\n") || + firstLines.contains("\nnetwork-status-version 2\n")) { + parsedDescriptors.add(new RelayNetworkStatusImpl(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type directory 1.") || + firstLines.startsWith("signed-directory\n") || + firstLines.contains("\nsigned-directory\n")) { + parsedDescriptors.add(new RelayDirectoryImpl(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); + } else if (firstLines.startsWith("@type torperf 1.")) { + parsedDescriptors.addAll(TorperfResultImpl.parseTorperfResults( + rawDescriptorBytes, failUnrecognizedDescriptorLines)); + } else { + throw new DescriptorParseException("Could not detect descriptor " + + "type in descriptor starting with '" + firstLines + "'."); + } + return parsedDescriptors; + } + + protected static List<byte[]> splitRawDescriptorBytes( + byte[] rawDescriptorBytes, String startToken) { + List<byte[]> rawDescriptors = new ArrayList<>(); + String splitToken = "\n" + startToken; + String ascii; + try { + ascii = new String(rawDescriptorBytes, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + return rawDescriptors; + } + int endAllDescriptors = rawDescriptorBytes.length, + startAnnotations = 0; + boolean containsAnnotations = ascii.startsWith("@") || + ascii.contains("\n@"); + while (startAnnotations < endAllDescriptors) { + int startDescriptor; + if (ascii.indexOf(startToken, startAnnotations) == 0) { + startDescriptor = startAnnotations; + } else { + startDescriptor = ascii.indexOf(splitToken, startAnnotations - 1); + if (startDescriptor < 0) { + break; + } else { + startDescriptor += 1; + } + } + int endDescriptor = -1; + if (containsAnnotations) { + endDescriptor = ascii.indexOf("\n@", startDescriptor); + } + if (endDescriptor < 0) { + endDescriptor = ascii.indexOf(splitToken, startDescriptor); + } + if (endDescriptor < 0) { + endDescriptor = endAllDescriptors - 1; + } + endDescriptor += 1; + byte[] rawDescriptor = new byte[endDescriptor - startAnnotations]; + System.arraycopy(rawDescriptorBytes, startAnnotations, + rawDescriptor, 0, endDescriptor - startAnnotations); + startAnnotations = endDescriptor; + rawDescriptors.add(rawDescriptor); + } + return rawDescriptors; + } + + protected byte[] rawDescriptorBytes; + @Override + public byte[] getRawDescriptorBytes() { + return this.rawDescriptorBytes; + } + + protected boolean failUnrecognizedDescriptorLines = false; + + protected List<String> unrecognizedLines; + @Override + public List<String> getUnrecognizedLines() { + return this.unrecognizedLines == null ? new ArrayList<String>() : + new ArrayList<>(this.unrecognizedLines); + } + + protected DescriptorImpl(byte[] rawDescriptorBytes, + boolean failUnrecognizedDescriptorLines, boolean blankLinesAllowed) + throws DescriptorParseException { + this.rawDescriptorBytes = rawDescriptorBytes; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.cutOffAnnotations(rawDescriptorBytes); + this.countKeywords(rawDescriptorBytes, blankLinesAllowed); + } + + /* Parse annotation lines from the descriptor bytes. */ + private List<String> annotations = new ArrayList<>(); + private void cutOffAnnotations(byte[] rawDescriptorBytes) + throws DescriptorParseException { + String ascii = new String(rawDescriptorBytes); + int start = 0; + while ((start == 0 && ascii.startsWith("@")) || + (start > 0 && ascii.indexOf("\n@", start - 1) >= 0)) { + int end = ascii.indexOf("\n", start); + if (end < 0) { + throw new DescriptorParseException("Annotation line does not " + + "contain a newline."); + } + this.annotations.add(ascii.substring(start, end)); + start = end + 1; + } + if (start > 0) { + int length = rawDescriptorBytes.length; + byte[] rawDescriptor = new byte[length - start]; + System.arraycopy(rawDescriptorBytes, start, rawDescriptor, 0, + length - start); + this.rawDescriptorBytes = rawDescriptor; + } + } + @Override + public List<String> getAnnotations() { + return new ArrayList<>(this.annotations); + } + + /* Count parsed keywords for consistency checks by subclasses. */ + private String firstKeyword, lastKeyword; + private Map<String, Integer> parsedKeywords = new HashMap<>(); + private void countKeywords(byte[] rawDescriptorBytes, + boolean blankLinesAllowed) throws DescriptorParseException { + if (rawDescriptorBytes.length == 0) { + throw new DescriptorParseException("Descriptor is empty."); + } + String descriptorString = new String(rawDescriptorBytes); + if (!blankLinesAllowed && (descriptorString.startsWith("\n") || + descriptorString.contains("\n\n"))) { + throw new DescriptorParseException("Blank lines are not allowed."); + } + boolean skipCrypto = false; + Scanner s = new Scanner(descriptorString).useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("-----BEGIN")) { + skipCrypto = true; + } else if (line.startsWith("-----END")) { + skipCrypto = false; + } else if (!line.isEmpty() && !line.startsWith("@") && + !skipCrypto) { + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String keyword = lineNoOpt.split(" ", -1)[0]; + if (keyword.equals("")) { + throw new DescriptorParseException("Illegal keyword in line '" + + line + "'."); + } + if (this.firstKeyword == null) { + this.firstKeyword = keyword; + } + lastKeyword = keyword; + if (parsedKeywords.containsKey(keyword)) { + parsedKeywords.put(keyword, parsedKeywords.get(keyword) + 1); + } else { + parsedKeywords.put(keyword, 1); + } + } + } + } + + protected void checkFirstKeyword(String keyword) + throws DescriptorParseException { + if (this.firstKeyword == null || + !this.firstKeyword.equals(keyword)) { + throw new DescriptorParseException("Keyword '" + keyword + "' must " + + "be contained in the first line."); + } + } + + protected void checkLastKeyword(String keyword) + throws DescriptorParseException { + if (this.lastKeyword == null || + !this.lastKeyword.equals(keyword)) { + throw new DescriptorParseException("Keyword '" + keyword + "' must " + + "be contained in the last line."); + } + } + + protected void checkExactlyOnceKeywords(Set<String> keywords) + throws DescriptorParseException { + for (String keyword : keywords) { + int contained = 0; + if (this.parsedKeywords.containsKey(keyword)) { + contained = this.parsedKeywords.get(keyword); + } + if (contained != 1) { + throw new DescriptorParseException("Keyword '" + keyword + "' is " + + "contained " + contained + " times, but must be contained " + + "exactly once."); + } + } + } + + protected void checkAtLeastOnceKeywords(Set<String> keywords) + throws DescriptorParseException { + for (String keyword : keywords) { + if (!this.parsedKeywords.containsKey(keyword)) { + throw new DescriptorParseException("Keyword '" + keyword + "' is " + + "contained 0 times, but must be contained at least once."); + } + } + } + + protected void checkAtMostOnceKeywords(Set<String> keywords) + throws DescriptorParseException { + for (String keyword : keywords) { + if (this.parsedKeywords.containsKey(keyword) && + this.parsedKeywords.get(keyword) > 1) { + throw new DescriptorParseException("Keyword '" + keyword + "' is " + + "contained " + this.parsedKeywords.get(keyword) + " times, " + + "but must be contained at most once."); + } + } + } + + protected void checkKeywordsDependOn(Set<String> dependentKeywords, + String dependingKeyword) throws DescriptorParseException { + for (String dependentKeyword : dependentKeywords) { + if (this.parsedKeywords.containsKey(dependentKeyword) && + !this.parsedKeywords.containsKey(dependingKeyword)) { + throw new DescriptorParseException("Keyword '" + dependentKeyword + + "' is contained, but keyword '" + dependingKeyword + "' is " + + "not."); + } + } + } + + protected int getKeywordCount(String keyword) { + if (!this.parsedKeywords.containsKey(keyword)) { + return 0; + } else { + return this.parsedKeywords.get(keyword); + } + } + + protected void clearParsedKeywords() { + this.parsedKeywords = null; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorParseException.java b/src/main/java/org/torproject/descriptor/impl/DescriptorParseException.java new file mode 100644 index 0000000..0f9add2 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorParseException.java @@ -0,0 +1,15 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +/** + * @deprecated Replaced by + * org.torproject.descriptor.DescriptorParseException + */ +@Deprecated public class DescriptorParseException extends Exception { + private static final long serialVersionUID = 100L; + protected DescriptorParseException(String message) { + super(message); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorParserImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorParserImpl.java new file mode 100644 index 0000000..6ac53f8 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorParserImpl.java @@ -0,0 +1,28 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.List; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorParser; + +public class DescriptorParserImpl implements DescriptorParser { + + private boolean failUnrecognizedDescriptorLines; + + @Override + public void setFailUnrecognizedDescriptorLines( + boolean failUnrecognizedDescriptorLines) { + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + } + + @Override + public List<Descriptor> parseDescriptors(byte[] rawDescriptorBytes, + String fileName) throws DescriptorParseException { + return DescriptorImpl.parseDescriptors(rawDescriptorBytes, fileName, + this.failUnrecognizedDescriptorLines); + } +} diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorReaderImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorReaderImpl.java new file mode 100644 index 0000000..8da88e9 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorReaderImpl.java @@ -0,0 +1,364 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.Stack; +import java.util.TreeMap; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorFile; +import org.torproject.descriptor.DescriptorParser; +import org.torproject.descriptor.DescriptorReader; + +public class DescriptorReaderImpl implements DescriptorReader { + + private boolean hasStartedReading = false; + + private List<File> directories = new ArrayList<>(); + @Override + public void addDirectory(File directory) { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.directories.add(directory); + } + + private List<File> tarballs = new ArrayList<>(); + @Override + public void addTarball(File tarball) { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.tarballs.add(tarball); + } + + private File historyFile; + @Override + public void setExcludeFiles(File historyFile) { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.historyFile = historyFile; + } + + private SortedMap<String, Long> excludedFiles; + @Override + public void setExcludedFiles(SortedMap<String, Long> excludedFiles) { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.excludedFiles = excludedFiles; + } + + @Override + public SortedMap<String, Long> getExcludedFiles() { + if (this.reader == null || !this.reader.hasFinishedReading) { + throw new IllegalStateException("Operation is not permitted before " + + "finishing to read."); + } + return new TreeMap<>(this.reader.excludedFilesAfter); + } + + @Override + public SortedMap<String, Long> getParsedFiles() { + if (this.reader == null || !this.reader.hasFinishedReading) { + throw new IllegalStateException("Operation is not permitted before " + + "finishing to read."); + } + return new TreeMap<>(this.reader.parsedFilesAfter); + } + + private boolean failUnrecognizedDescriptorLines = false; + @Override + public void setFailUnrecognizedDescriptorLines() { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.failUnrecognizedDescriptorLines = true; + } + + private Integer maxDescriptorFilesInQueue = null; + @Override + public void setMaxDescriptorFilesInQueue(int max) { + if (this.hasStartedReading) { + throw new IllegalStateException("Reconfiguration is not permitted " + + "after starting to read."); + } + this.maxDescriptorFilesInQueue = max; + } + + private DescriptorReaderRunnable reader; + @Override + public Iterator<DescriptorFile> readDescriptors() { + if (this.hasStartedReading) { + throw new IllegalStateException("Initiating reading is only " + + "permitted once."); + } + this.hasStartedReading = true; + BlockingIteratorImpl<DescriptorFile> descriptorQueue = + this.maxDescriptorFilesInQueue == null + ? new BlockingIteratorImpl<DescriptorFile>() + : new BlockingIteratorImpl<DescriptorFile>( + this.maxDescriptorFilesInQueue); + this.reader = new DescriptorReaderRunnable(this.directories, + this.tarballs, descriptorQueue, this.historyFile, + this.excludedFiles, this.failUnrecognizedDescriptorLines); + new Thread(this.reader).start(); + return descriptorQueue; + } + + private static class DescriptorReaderRunnable implements Runnable { + private List<File> directories; + private List<File> tarballs; + private BlockingIteratorImpl<DescriptorFile> descriptorQueue; + private File historyFile; + private SortedMap<String, Long> excludedFilesBefore = new TreeMap<>(), + excludedFilesAfter = new TreeMap<>(), + parsedFilesAfter = new TreeMap<>(); + private DescriptorParser descriptorParser; + private boolean hasFinishedReading = false; + private DescriptorReaderRunnable(List<File> directories, + List<File> tarballs, + BlockingIteratorImpl<DescriptorFile> descriptorQueue, + File historyFile, SortedMap<String, Long> excludedFiles, + boolean failUnrecognizedDescriptorLines) { + this.directories = directories; + this.tarballs = tarballs; + this.descriptorQueue = descriptorQueue; + this.historyFile = historyFile; + if (excludedFiles != null) { + this.excludedFilesBefore = excludedFiles; + } + this.descriptorParser = new DescriptorParserImpl(); + this.descriptorParser.setFailUnrecognizedDescriptorLines( + failUnrecognizedDescriptorLines); + } + public void run() { + try { + this.readOldHistory(); + this.readDescriptors(); + this.readTarballs(); + this.hasFinishedReading = true; + } catch (Throwable t) { + /* We're usually not writing to stdout or stderr, but we shouldn't + * stay quiet about this potential bug. If we were to switch to a + * logging API, this would qualify as ERROR. */ + System.err.println("Bug: uncaught exception or error while " + + "reading descriptors:"); + t.printStackTrace(); + } finally { + this.descriptorQueue.setOutOfDescriptors(); + } + if (this.hasFinishedReading) { + this.writeNewHistory(); + } + } + private void readOldHistory() { + if (this.historyFile == null) { + return; + } + try { + BufferedReader br = new BufferedReader(new FileReader( + this.historyFile)); + String line; + while ((line = br.readLine()) != null) { + if (!line.contains(" ")) { + /* TODO Handle this problem? */ + continue; + } + long lastModifiedMillis = Long.parseLong(line.substring(0, + line.indexOf(" "))); + String absolutePath = line.substring(line.indexOf(" ") + 1); + this.excludedFilesBefore.put(absolutePath, lastModifiedMillis); + } + br.close(); + } catch (IOException e) { + /* TODO Handle this exception. */ + } catch (NumberFormatException e) { + /* TODO Handle this exception. */ + } + } + private void writeNewHistory() { + if (this.historyFile == null) { + return; + } + try { + if (this.historyFile.getParentFile() != null) { + this.historyFile.getParentFile().mkdirs(); + } + BufferedWriter bw = new BufferedWriter(new FileWriter( + this.historyFile)); + SortedMap<String, Long> newHistory = new TreeMap<>(); + newHistory.putAll(this.excludedFilesAfter); + newHistory.putAll(this.parsedFilesAfter); + for (Map.Entry<String, Long> e : newHistory.entrySet()) { + String absolutePath = e.getKey(); + long lastModifiedMillis = e.getValue(); + bw.write(String.valueOf(lastModifiedMillis) + " " + absolutePath + + "\n"); + } + bw.close(); + } catch (IOException e) { + /* TODO Handle this exception. */ + } + } + private void readDescriptors() { + for (File directory : this.directories) { + if (!directory.exists() || !directory.isDirectory()) { + continue; + } + Stack<File> files = new Stack<>(); + files.add(directory); + boolean abortReading = false; + while (!abortReading && !files.isEmpty()) { + File file = files.pop(); + if (file.isDirectory()) { + files.addAll(Arrays.asList(file.listFiles())); + } else if (file.getName().endsWith(".tar") || + file.getName().endsWith(".tar.bz2") || + file.getName().endsWith(".tar.xz")) { + this.tarballs.add(file); + } else { + String absolutePath = file.getAbsolutePath(); + long lastModifiedMillis = file.lastModified(); + if (this.excludedFilesBefore.containsKey(absolutePath) && + this.excludedFilesBefore.get(absolutePath) == + lastModifiedMillis) { + this.excludedFilesAfter.put(absolutePath, + lastModifiedMillis); + continue; + } + this.parsedFilesAfter.put(absolutePath, lastModifiedMillis); + DescriptorFileImpl descriptorFile = new DescriptorFileImpl(); + try { + descriptorFile.setDirectory(directory); + descriptorFile.setFile(file); + descriptorFile.setFileName(file.getAbsolutePath()); + descriptorFile.setLastModified(lastModifiedMillis); + descriptorFile.setDescriptors(this.readFile(file)); + } catch (DescriptorParseException e) { + descriptorFile.setException(e); + } catch (IOException e) { + descriptorFile.setException(e); + abortReading = true; + } + this.descriptorQueue.add(descriptorFile); + } + } + } + } + private void readTarballs() { + List<File> files = new ArrayList<>(this.tarballs); + boolean abortReading = false; + while (!abortReading && !files.isEmpty()) { + File tarball = files.remove(0); + if (!tarball.getName().endsWith(".tar") && + !tarball.getName().endsWith(".tar.bz2") && + !tarball.getName().endsWith(".tar.xz")) { + continue; + } + String absolutePath = tarball.getAbsolutePath(); + long lastModifiedMillis = tarball.lastModified(); + if (this.excludedFilesBefore.containsKey(absolutePath) && + this.excludedFilesBefore.get(absolutePath) == + lastModifiedMillis) { + this.excludedFilesAfter.put(absolutePath, lastModifiedMillis); + continue; + } + this.parsedFilesAfter.put(absolutePath, lastModifiedMillis); + try { + FileInputStream in = new FileInputStream(tarball); + if (in.available() > 0) { + TarArchiveInputStream tais = null; + if (tarball.getName().endsWith(".tar.bz2")) { + tais = new TarArchiveInputStream( + new BZip2CompressorInputStream(in)); + } else if (tarball.getName().endsWith(".tar.xz")) { + tais = new TarArchiveInputStream( + new XZCompressorInputStream(in)); + } else if (tarball.getName().endsWith(".tar")) { + tais = new TarArchiveInputStream(in); + } + BufferedInputStream bis = new BufferedInputStream(tais); + TarArchiveEntry tae = null; + while ((tae = tais.getNextTarEntry()) != null) { + if (tae.isDirectory()) { + continue; + } + DescriptorFileImpl descriptorFile = + new DescriptorFileImpl(); + descriptorFile.setTarball(tarball); + descriptorFile.setFileName(tae.getName()); + descriptorFile.setLastModified(tae.getLastModifiedDate(). + getTime()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[1024]; + while ((len = bis.read(data, 0, 1024)) >= 0) { + baos.write(data, 0, len); + } + byte[] rawDescriptorBytes = baos.toByteArray(); + if (rawDescriptorBytes.length < 1) { + continue; + } + try { + String fileName = tae.getName().substring( + tae.getName().lastIndexOf("/") + 1); + List<Descriptor> parsedDescriptors = + this.descriptorParser.parseDescriptors( + rawDescriptorBytes, fileName); + descriptorFile.setDescriptors(parsedDescriptors); + } catch (DescriptorParseException e) { + descriptorFile.setException(e); + } + this.descriptorQueue.add(descriptorFile); + } + } + } catch (IOException e) { + abortReading = true; + } + } + } + private List<Descriptor> readFile(File file) throws IOException, + DescriptorParseException { + FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[1024]; + while ((len = bis.read(data, 0, 1024)) >= 0) { + baos.write(data, 0, len); + } + bis.close(); + byte[] rawDescriptorBytes = baos.toByteArray(); + return this.descriptorParser.parseDescriptors(rawDescriptorBytes, + file.getName()); + } + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DescriptorRequestImpl.java b/src/main/java/org/torproject/descriptor/impl/DescriptorRequestImpl.java new file mode 100644 index 0000000..0238f24 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DescriptorRequestImpl.java @@ -0,0 +1,114 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.List; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorRequest; + +public class DescriptorRequestImpl implements DescriptorRequest { + + private String requestedResource; + protected void setRequestedResource(String requestedResource) { + this.requestedResource = requestedResource; + } + protected String getRequestedResource() { + return this.requestedResource; + } + + private String descriptorType; + protected void setDescriptorType(String descriptorType) { + this.descriptorType = descriptorType; + } + protected String getDescriptorType() { + return this.descriptorType; + } + + private byte[] responseBytes; + protected byte[] getResponseBytes() { + return this.responseBytes; + } + protected void setResponseBytes(byte[] responseBytes) { + this.responseBytes = responseBytes; + } + + private String requestUrl; + @Override + public String getRequestUrl() { + return this.requestUrl; + } + + private String directoryNickname; + protected void setDirectoryNickname(String directoryNickname) { + this.directoryNickname = directoryNickname; + } + @Override + public String getDirectoryNickname() { + return this.directoryNickname; + } + + private int responseCode; + protected void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + @Override + public int getResponseCode() { + return this.responseCode; + } + + private long requestStart; + protected void setRequestStart(long requestStart) { + this.requestStart = requestStart; + } + @Override + public long getRequestStart() { + return this.requestStart; + } + + private long requestEnd; + protected void setRequestEnd(long requestEnd) { + this.requestEnd = requestEnd; + } + @Override + public long getRequestEnd() { + return this.requestEnd; + } + + private boolean connectTimeoutHasExpired; + @Override + public boolean connectTimeoutHasExpired() { + return this.connectTimeoutHasExpired; + } + + private boolean readTimeoutHasExpired; + @Override + public boolean readTimeoutHasExpired() { + return this.readTimeoutHasExpired; + } + + private boolean globalTimeoutHasExpired; + @Override + public boolean globalTimeoutHasExpired() { + return this.globalTimeoutHasExpired; + } + + private List<Descriptor> descriptors; + protected void setDescriptors(List<Descriptor> descriptors) { + this.descriptors = descriptors; + } + @Override + public List<Descriptor> getDescriptors() { + return this.descriptors; + } + + private Exception exception; + protected void setException(Exception exception) { + this.exception = exception; + } + @Override + public Exception getException() { + return this.exception; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DirSourceEntryImpl.java b/src/main/java/org/torproject/descriptor/impl/DirSourceEntryImpl.java new file mode 100644 index 0000000..fb2f5ad --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DirSourceEntryImpl.java @@ -0,0 +1,218 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.torproject.descriptor.DirSourceEntry; + +public class DirSourceEntryImpl implements DirSourceEntry { + + private byte[] dirSourceEntryBytes; + @Override + public byte[] getDirSourceEntryBytes() { + return this.dirSourceEntryBytes; + } + + private boolean failUnrecognizedDescriptorLines; + private List<String> unrecognizedLines; + protected List<String> getAndClearUnrecognizedLines() { + List<String> lines = this.unrecognizedLines; + this.unrecognizedLines = null; + return lines; + } + + protected DirSourceEntryImpl(byte[] dirSourceEntryBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + this.dirSourceEntryBytes = dirSourceEntryBytes; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.initializeKeywords(); + this.parseDirSourceEntryBytes(); + this.checkAndClearKeywords(); + } + + private SortedSet<String> exactlyOnceKeywords, atMostOnceKeywords; + private void initializeKeywords() { + this.exactlyOnceKeywords = new TreeSet<>(); + this.exactlyOnceKeywords.add("dir-source"); + this.exactlyOnceKeywords.add("vote-digest"); + this.atMostOnceKeywords = new TreeSet<>(); + 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 checkAndClearKeywords() throws DescriptorParseException { + if (!this.exactlyOnceKeywords.isEmpty()) { + throw new DescriptorParseException("dir-source does not contain a '" + + this.exactlyOnceKeywords.first() + "' line."); + } + this.exactlyOnceKeywords = null; + this.atMostOnceKeywords = null; + } + + private void parseDirSourceEntryBytes() + throws DescriptorParseException { + Scanner s = new Scanner(new String(this.dirSourceEntryBytes)). + useDelimiter("\n"); + boolean skipCrypto = false; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split(" "); + switch (parts[0]) { + case "dir-source": + this.parseDirSourceLine(line); + break; + case "contact": + this.parseContactLine(line); + break; + case "vote-digest": + this.parseVoteDigestLine(line); + break; + case "-----BEGIN": + skipCrypto = true; + break; + case "-----END": + skipCrypto = false; + break; + default: + if (!skipCrypto) { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in dir-source entry."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + private void parseDirSourceLine(String line) + throws DescriptorParseException { + this.parsedExactlyOnceKeyword("dir-source"); + String[] parts = line.split("[ \t]+"); + if (parts.length != 7) { + throw new DescriptorParseException("Invalid line '" + line + "'."); + } + 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]); + if (parts[3].length() < 1) { + throw new DescriptorParseException("Illegal hostname in '" + line + + "'."); + } + this.hostname = parts[3]; + 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("[ \t]+"); + if (parts.length != 2) { + throw new DescriptorParseException("Invalid line '" + line + "'."); + } + this.voteDigest = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String identity; + @Override + public String getIdentity() { + return this.identity; + } + + private boolean isLegacy; + @Override + public boolean isLegacy() { + return this.isLegacy; + } + + private String hostname; + @Override + public String getHostname() { + return this.hostname; + } + + private String ip; + @Override + public String getIp() { + return this.ip; + } + + private int dirPort; + @Override + public int getDirPort() { + return this.dirPort; + } + + private int orPort; + @Override + public int getOrPort() { + return this.orPort; + } + + private String contactLine; + @Override + public String getContactLine() { + return this.contactLine; + } + + private String voteDigest; + @Override + public String getVoteDigest() { + return this.voteDigest; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DirectoryDownloader.java b/src/main/java/org/torproject/descriptor/impl/DirectoryDownloader.java new file mode 100644 index 0000000..a27ed76 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DirectoryDownloader.java @@ -0,0 +1,104 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.zip.InflaterInputStream; + +import org.torproject.descriptor.DescriptorParser; +import org.torproject.descriptor.DescriptorSourceFactory; + +/* Download descriptors from one directory authority or mirror. First, + * ask the coordinator thread to create a request, run it, and deliver + * the response. Repeat until the coordinator thread says there are no + * further requests to make. */ +public class DirectoryDownloader implements Runnable { + + private String nickname; + private String ipPort; + private DescriptorParser descriptorParser; + protected DirectoryDownloader(String nickname, String ip, int dirPort) { + this.nickname = nickname; + this.ipPort = ip + ":" + String.valueOf(dirPort); + this.descriptorParser = + DescriptorSourceFactory.createDescriptorParser(); + } + + private DownloadCoordinator downloadCoordinator; + protected void setDownloadCoordinator( + DownloadCoordinator downloadCoordinator) { + this.downloadCoordinator = downloadCoordinator; + } + + private long connectTimeout; + protected void setConnectTimeout(long connectTimeout) { + this.connectTimeout = connectTimeout; + } + + private long readTimeout; + protected void setReadTimeout(long readTimeout) { + this.readTimeout = readTimeout; + } + + protected void setFailUnrecognizedDescriptorLines( + boolean failUnrecognizedDescriptorLines) { + this.descriptorParser.setFailUnrecognizedDescriptorLines( + failUnrecognizedDescriptorLines); + } + + @Override + public void run() { + boolean keepRunning = true; + do { + DescriptorRequestImpl request = + this.downloadCoordinator.createRequest(this.nickname); + if (request != null) { + String url = "http://" + this.ipPort + + request.getRequestedResource(); + request.setRequestStart(System.currentTimeMillis()); + HttpURLConnection huc = null; + try { + URL u = new URL(url); + huc = (HttpURLConnection) u.openConnection(); + huc.setConnectTimeout((int) this.connectTimeout); + huc.setReadTimeout((int) this.readTimeout); + huc.setRequestMethod("GET"); + huc.connect(); + int responseCode = huc.getResponseCode(); + request.setResponseCode(responseCode); + if (responseCode == 200) { + BufferedInputStream in = new BufferedInputStream( + new InflaterInputStream(huc.getInputStream())); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len; + byte[] data = new byte[8192]; + while ((len = in.read(data, 0, 8192)) >= 0) { + baos.write(data, 0, len); + } + in.close(); + byte[] responseBytes = baos.toByteArray(); + request.setResponseBytes(responseBytes); + request.setRequestEnd(System.currentTimeMillis()); + request.setDescriptors(this.descriptorParser.parseDescriptors( + responseBytes, null)); + } + } catch (Exception e) { + request.setException(e); + if (huc != null) { + huc.disconnect(); + } + /* Stop downloading from this directory if there are any + * problems, e.g., refused connections. */ + keepRunning = false; + } + this.downloadCoordinator.deliverResponse(request); + } else { + keepRunning = false; + } + } while (keepRunning); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java b/src/main/java/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java new file mode 100644 index 0000000..b62fc8e --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java @@ -0,0 +1,308 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.DirectoryKeyCertificate; + +/* TODO Add test class. */ + +public class DirectoryKeyCertificateImpl extends DescriptorImpl + implements DirectoryKeyCertificate { + + protected static List<DirectoryKeyCertificate> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<DirectoryKeyCertificate> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DirectoryKeyCertificateImpl.splitRawDescriptorBytes( + descriptorsBytes, "dir-key-certificate-version "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + DirectoryKeyCertificate parsedDescriptor = + new DirectoryKeyCertificateImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected DirectoryKeyCertificateImpl(byte[] rawDescriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseDescriptorBytes(); + this.calculateDigest(); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( + "dir-key-certificate-version,fingerprint,dir-identity-key," + + "dir-key-published,dir-key-expires,dir-signing-key," + + "dir-key-certification").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "dir-address,dir-key-crosscert").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("dir-key-certificate-version"); + this.checkLastKeyword("dir-key-certification"); + this.clearParsedKeywords(); + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "dir-key-certificate-version": + this.parseDirKeyCertificateVersionLine(line, parts); + break; + case "dir-address": + this.parseDirAddressLine(line, parts); + break; + case "fingerprint": + this.parseFingerprintLine(line, parts); + break; + case "dir-identity-key": + this.parseDirIdentityKeyLine(line, parts); + nextCrypto = "dir-identity-key"; + break; + case "dir-key-published": + this.parseDirKeyPublishedLine(line, parts); + break; + case "dir-key-expires": + this.parseDirKeyExpiresLine(line, parts); + break; + case "dir-signing-key": + this.parseDirSigningKeyLine(line, parts); + nextCrypto = "dir-signing-key"; + break; + case "dir-key-crosscert": + this.parseDirKeyCrosscertLine(line, parts); + nextCrypto = "dir-key-crosscert"; + break; + case "dir-key-certification": + this.parseDirKeyCertificationLine(line, parts); + nextCrypto = "dir-key-certification"; + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\n"); + String cryptoString = crypto.toString(); + crypto = null; + switch (nextCrypto) { + case "dir-identity-key": + this.dirIdentityKey = cryptoString; + break; + case "dir-signing-key": + this.dirSigningKey = cryptoString; + break; + case "dir-key-crosscert": + this.dirKeyCrosscert = cryptoString; + break; + case "dir-key-certification": + this.dirKeyCertification = cryptoString; + break; + default: + throw new DescriptorParseException("Unrecognized crypto " + + "block in directory key certificate."); + } + nextCrypto = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in directory key certificate."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + private void parseDirKeyCertificateVersionLine(String line, + String[] parts) throws DescriptorParseException { + if (!line.equals("dir-key-certificate-version 3")) { + throw new DescriptorParseException("Illegal directory key " + + "certificate version number in line '" + line + "'."); + } + this.dirKeyCertificateVersion = 3; + } + + private void parseDirAddressLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2 || parts[1].split(":").length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in directory key certificate."); + } + this.address = ParseHelper.parseIpv4Address(line, + parts[1].split(":")[0]); + this.port = ParseHelper.parsePort(line, parts[1].split(":")[1]); + } + + private void parseFingerprintLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in directory key certificate."); + } + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + } + + private void parseDirIdentityKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-identity-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyPublishedLine(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirKeyExpiresLine(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirSigningKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-signing-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCrosscertLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-crosscert")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCertificationLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-certification")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "dir-key-certificate-version "; + String sigToken = "\ndir-key-certification\n"; + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, sig - start); + this.certificateDigest = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.certificateDigest == null) { + throw new DescriptorParseException("Could not calculate " + + "certificate digest."); + } + } + + private int dirKeyCertificateVersion; + @Override + public int getDirKeyCertificateVersion() { + return this.dirKeyCertificateVersion; + } + + private String address; + @Override + public String getAddress() { + return this.address; + } + + private int port = -1; + @Override + public int getPort() { + return this.port; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private String dirIdentityKey; + @Override + public String getDirIdentityKey() { + return this.dirIdentityKey; + } + + private long dirKeyPublishedMillis; + @Override + public long getDirKeyPublishedMillis() { + return this.dirKeyPublishedMillis; + } + + private long dirKeyExpiresMillis; + @Override + public long getDirKeyExpiresMillis() { + return this.dirKeyExpiresMillis; + } + + private String dirSigningKey; + @Override + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private String dirKeyCrosscert; + @Override + public String getDirKeyCrosscert() { + return this.dirKeyCrosscert; + } + + private String dirKeyCertification; + @Override + public String getDirKeyCertification() { + return this.dirKeyCertification; + } + + private String certificateDigest; + @Override + public String getCertificateDigest() { + return this.certificateDigest; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DirectorySignatureImpl.java b/src/main/java/org/torproject/descriptor/impl/DirectorySignatureImpl.java new file mode 100644 index 0000000..a955f62 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DirectorySignatureImpl.java @@ -0,0 +1,115 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import org.torproject.descriptor.DirectorySignature; + +public class DirectorySignatureImpl implements DirectorySignature { + + private byte[] directorySignatureBytes; + + private boolean failUnrecognizedDescriptorLines; + private List<String> unrecognizedLines; + protected List<String> getAndClearUnrecognizedLines() { + List<String> lines = this.unrecognizedLines; + this.unrecognizedLines = null; + return lines; + } + + protected DirectorySignatureImpl(byte[] directorySignatureBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + this.directorySignatureBytes = directorySignatureBytes; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.parseDirectorySignatureBytes(); + } + + private void parseDirectorySignatureBytes() + throws DescriptorParseException { + Scanner s = new Scanner(new String(this.directorySignatureBytes)). + useDelimiter("\n"); + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split(" ", -1); + String keyword = parts[0]; + switch (keyword) { + case "directory-signature": + int algorithmOffset = 0; + switch (parts.length) { + case 4: + this.algorithm = parts[1]; + algorithmOffset = 1; + break; + case 3: + break; + default: + throw new DescriptorParseException("Illegal line '" + line + + "'."); + } + this.identity = ParseHelper.parseHexString(line, + parts[1 + algorithmOffset]); + this.signingKeyDigest = ParseHelper.parseHexString( + line, parts[2 + algorithmOffset]); + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\n"); + String cryptoString = crypto.toString(); + crypto = null; + this.signature = cryptoString; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in dir-source entry."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + static final String DEFAULT_ALGORITHM = "sha1"; + + private String algorithm; + @Override + public String getAlgorithm() { + return this.algorithm == null ? DEFAULT_ALGORITHM : this.algorithm; + } + + private String identity; + @Override + public String getIdentity() { + return this.identity; + } + + private String signingKeyDigest; + @Override + public String getSigningKeyDigest() { + return this.signingKeyDigest; + } + + private String signature; + @Override + public String getSignature() { + return this.signature; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/DownloadCoordinator.java b/src/main/java/org/torproject/descriptor/impl/DownloadCoordinator.java new file mode 100644 index 0000000..72cfeae --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DownloadCoordinator.java @@ -0,0 +1,10 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +public interface DownloadCoordinator { + + public DescriptorRequestImpl createRequest(String nickname); + + public void deliverResponse(DescriptorRequestImpl request); +} diff --git a/src/main/java/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java b/src/main/java/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java new file mode 100644 index 0000000..a8e3731 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java @@ -0,0 +1,298 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorRequest; +import org.torproject.descriptor.DirSourceEntry; +import org.torproject.descriptor.RelayNetworkStatusConsensus; + +/* TODO This whole download logic is a mess and needs a cleanup. */ +public class DownloadCoordinatorImpl implements DownloadCoordinator { + + private BlockingIteratorImpl<DescriptorRequest> descriptorQueue = + new BlockingIteratorImpl<>(); + protected Iterator<DescriptorRequest> getDescriptorQueue() { + return this.descriptorQueue; + } + + private SortedSet<String> runningDirectories; + private SortedMap<String, DirectoryDownloader> directoryAuthorities; + private SortedMap<String, DirectoryDownloader> directoryMirrors; + private boolean downloadConsensusFromAllAuthorities; + private boolean includeCurrentReferencedVotes; + private long connectTimeoutMillis; + private long readTimeoutMillis; + private long globalTimeoutMillis; + private boolean failUnrecognizedDescriptorLines; + + protected DownloadCoordinatorImpl( + SortedMap<String, DirectoryDownloader> directoryAuthorities, + SortedMap<String, DirectoryDownloader> directoryMirrors, + boolean downloadConsensus, + boolean downloadConsensusFromAllAuthorities, + Set<String> downloadVotes, boolean includeCurrentReferencedVotes, + long connectTimeoutMillis, long readTimeoutMillis, + long globalTimeoutMillis, boolean failUnrecognizedDescriptorLines) { + this.directoryAuthorities = directoryAuthorities; + this.directoryMirrors = directoryMirrors; + this.runningDirectories = new TreeSet<>(); + this.runningDirectories.addAll(directoryAuthorities.keySet()); + this.runningDirectories.addAll(directoryMirrors.keySet()); + this.missingConsensus = downloadConsensus; + this.downloadConsensusFromAllAuthorities = + downloadConsensusFromAllAuthorities; + this.missingVotes = downloadVotes; + this.includeCurrentReferencedVotes = includeCurrentReferencedVotes; + this.connectTimeoutMillis = connectTimeoutMillis; + this.readTimeoutMillis = readTimeoutMillis; + this.globalTimeoutMillis = globalTimeoutMillis; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + if (this.directoryMirrors.isEmpty() && + this.directoryAuthorities.isEmpty()) { + this.descriptorQueue.setOutOfDescriptors(); + /* TODO Should we say anything if we don't have any directories + * configured? */ + } else { + GlobalTimer globalTimer = new GlobalTimer(this.globalTimeoutMillis, + this); + this.globalTimerThread = new Thread(globalTimer); + this.globalTimerThread.start(); + for (DirectoryDownloader directoryMirror : + this.directoryMirrors.values()) { + directoryMirror.setDownloadCoordinator(this); + directoryMirror.setConnectTimeout(this.connectTimeoutMillis); + directoryMirror.setReadTimeout(this.readTimeoutMillis); + directoryMirror.setFailUnrecognizedDescriptorLines( + this.failUnrecognizedDescriptorLines); + new Thread(directoryMirror).start(); + } + for (DirectoryDownloader directoryAuthority : + this.directoryAuthorities.values()) { + directoryAuthority.setDownloadCoordinator(this); + directoryAuthority.setConnectTimeout(this.connectTimeoutMillis); + directoryAuthority.setReadTimeout(this.readTimeoutMillis); + directoryAuthority.setFailUnrecognizedDescriptorLines( + this.failUnrecognizedDescriptorLines); + new Thread(directoryAuthority).start(); + } + } + } + + /* Interrupt all downloads if the total download time exceeds a given + * time. */ + private Thread globalTimerThread; + private static class GlobalTimer implements Runnable { + private long timeoutMillis; + private DownloadCoordinatorImpl downloadCoordinator; + private GlobalTimer(long timeoutMillis, + DownloadCoordinatorImpl downloadCoordinator) { + this.timeoutMillis = timeoutMillis; + this.downloadCoordinator = downloadCoordinator; + } + public void run() { + long started = System.currentTimeMillis(), sleep; + while ((sleep = started + this.timeoutMillis + - System.currentTimeMillis()) > 0L) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + return; + } + } + this.downloadCoordinator.interruptAllDownloads(); + } + } + + /* Are we missing the consensus, and should the next directory that + * hasn't tried downloading it before attempt to download it? */ + private boolean missingConsensus = false; + + /* Which directories are currently attempting to download the + * consensus? */ + private Set<String> requestingConsensuses = new HashSet<>(); + + /* Which directories have attempted to download the consensus so far, + * including those directories that are currently attempting it? */ + private Set<String> requestedConsensuses = new HashSet<>(); + + /* Which votes are we currently missing? */ + private Set<String> missingVotes = new HashSet<>(); + + /* Which vote (map value) is a given directory (map key) currently + * attempting to download? */ + private Map<String, String> requestingVotes = new HashMap<>(); + + /* Which votes (map value) has a given directory (map key) attempted or + * is currently attempting to download? */ + private Map<String, Set<String>> requestedVotes = new HashMap<>(); + + private boolean hasFinishedDownloading = false; + + /* Look up what request a directory should make next. If there is + * nothing to do right now, but maybe later, block the caller. If + * we're done downloading, return null to notify the caller. */ + @Override + public synchronized DescriptorRequestImpl createRequest( + String nickname) { + while (!this.hasFinishedDownloading) { + DescriptorRequestImpl request = new DescriptorRequestImpl(); + request.setDirectoryNickname(nickname); + if ((this.missingConsensus || + (this.downloadConsensusFromAllAuthorities && + this.directoryAuthorities.containsKey(nickname))) && + !this.requestedConsensuses.contains(nickname)) { + if (!this.downloadConsensusFromAllAuthorities) { + this.missingConsensus = false; + } + this.requestingConsensuses.add(nickname); + this.requestedConsensuses.add(nickname); + request.setRequestedResource( + "/tor/status-vote/current/consensus.z"); + request.setDescriptorType("consensus"); + return request; + } + if (!this.missingVotes.isEmpty() && + this.directoryAuthorities.containsKey(nickname)) { + String requestingVote = null; + for (String missingVote : this.missingVotes) { + if (!this.requestedVotes.containsKey(nickname) || + !this.requestedVotes.get(nickname).contains(missingVote)) { + requestingVote = missingVote; + } + } + if (requestingVote != null) { + this.requestingVotes.put(nickname, requestingVote); + if (!this.requestedVotes.containsKey(nickname)) { + this.requestedVotes.put(nickname, new HashSet<String>()); + } + this.requestedVotes.get(nickname).add(requestingVote); + this.missingVotes.remove(requestingVote); + request.setRequestedResource("/tor/status-vote/current/" + + requestingVote + ".z"); + request.setDescriptorType("vote"); + return request; + } + } + /* TODO Add server descriptors and extra-info descriptors later. */ + try { + this.wait(); + } catch (InterruptedException e) { + /* TODO What shall we do? */ + } + } + return null; + } + + /* Deliver a response which may either contain one or more descriptors + * or a failure response code. Update the lists of missing descriptors, + * decide if there are more descriptors to download, and wake up any + * waiting downloader threads. */ + @Override + public synchronized void deliverResponse( + DescriptorRequestImpl response) { + String nickname = response.getDirectoryNickname(); + if (response.getException() != null) { + this.runningDirectories.remove(nickname); + } + switch (response.getDescriptorType()) { + case "consensus": + this.requestingConsensuses.remove(nickname); + if (response.getResponseCode() == 200 && + response.getDescriptors() != null) { + if (this.includeCurrentReferencedVotes) { + /* TODO Only add votes if the consensus is not older than one + * hour. Or does that make no sense? */ + for (Descriptor parsedDescriptor : + response.getDescriptors()) { + if (!(parsedDescriptor instanceof + RelayNetworkStatusConsensus)) { + continue; + } + RelayNetworkStatusConsensus parsedConsensus = + (RelayNetworkStatusConsensus) parsedDescriptor; + for (DirSourceEntry dirSource : + parsedConsensus.getDirSourceEntries().values()) { + String identity = dirSource.getIdentity(); + if (!this.missingVotes.contains(identity)) { + boolean alreadyRequested = false; + for (Set<String> requestedBefore : + this.requestedVotes.values()) { + if (requestedBefore.contains(identity)) { + alreadyRequested = true; + break; + } + } + if (!alreadyRequested) { + this.missingVotes.add(identity); + } + } + } + } + /* TODO Later, add referenced server descriptors. */ + } + } else { + this.missingConsensus = true; + } + break; + case "vote": + String requestedVote = requestingVotes.remove(nickname); + if (response.getResponseCode() != 200) { + this.missingVotes.add(requestedVote); + } + } + if (response.getRequestEnd() != 0L) { + this.descriptorQueue.add(response); + } + boolean doneDownloading = true; + if ((this.missingConsensus || + this.downloadConsensusFromAllAuthorities) && + (!this.requestedConsensuses.containsAll( + this.runningDirectories) || + !this.requestingConsensuses.isEmpty())) { + doneDownloading = false; + } + if (!this.requestingVotes.isEmpty()) { + doneDownloading = false; + } else if (!this.missingVotes.isEmpty()) { + if (!this.requestedVotes.keySet().containsAll( + this.runningDirectories)) { + doneDownloading = false; + } else { + for (String missingVote : this.missingVotes) { + for (String runningDirectory : this.runningDirectories) { + Set<String> reqVotes = this.requestedVotes.get( + runningDirectory); + if (!reqVotes.contains(missingVote)) { + doneDownloading = false; + } + } + } + } + } + if (doneDownloading) { + this.hasFinishedDownloading = true; + this.globalTimerThread.interrupt(); + this.descriptorQueue.setOutOfDescriptors(); + } + /* Wake up all waiting downloader threads. Maybe they can now + * download something, or they'll realize we're done downloading. */ + this.notifyAll(); + } + + private synchronized void interruptAllDownloads() { + this.hasFinishedDownloading = true; + this.notifyAll(); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/ExitListEntryImpl.java b/src/main/java/org/torproject/descriptor/impl/ExitListEntryImpl.java new file mode 100644 index 0000000..efbf31c --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/ExitListEntryImpl.java @@ -0,0 +1,216 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExitList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.torproject.descriptor.ExitListEntry; + +public class ExitListEntryImpl implements ExitListEntry, ExitList.Entry { + + private byte[] exitListEntryBytes; + + private boolean failUnrecognizedDescriptorLines; + private List<String> unrecognizedLines; + protected List<String> getAndClearUnrecognizedLines() { + List<String> lines = this.unrecognizedLines; + this.unrecognizedLines = null; + return lines; + } + + @Deprecated + private ExitListEntryImpl(String fingerprint, long publishedMillis, + long lastStatusMillis, String exitAddress, long scanMillis) { + this.fingerprint = fingerprint; + this.publishedMillis = publishedMillis; + this.lastStatusMillis = lastStatusMillis; + this.exitAddresses.put(exitAddress, scanMillis); + } + + @Deprecated + List<ExitListEntry> oldEntries() { + List<ExitListEntry> result = new ArrayList<>(); + if (this.exitAddresses.size() > 1) { + for (Map.Entry<String, Long> entry : + this.exitAddresses.entrySet()) { + result.add(new ExitListEntryImpl(this.fingerprint, + this.publishedMillis, this.lastStatusMillis, entry.getKey(), + entry.getValue())); + } + } else { + result.add(this); + } + return result; + } + + protected ExitListEntryImpl(byte[] exitListEntryBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + this.exitListEntryBytes = exitListEntryBytes; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.initializeKeywords(); + this.parseExitListEntryBytes(); + this.checkAndClearKeywords(); + } + + private SortedSet<String> keywordCountingSet; + private void initializeKeywords() { + this.keywordCountingSet = new TreeSet<>(); + this.keywordCountingSet.add("ExitNode"); + this.keywordCountingSet.add("Published"); + this.keywordCountingSet.add("LastStatus"); + this.keywordCountingSet.add("ExitAddress"); + } + + private void parsedExactlyOnceKeyword(String keyword) + throws DescriptorParseException { + if (!this.keywordCountingSet.contains(keyword)) { + throw new DescriptorParseException("Duplicate '" + keyword + + "' line in exit list entry."); + } + this.keywordCountingSet.remove(keyword); + } + + private void checkAndClearKeywords() throws DescriptorParseException { + for (String missingKeyword : this.keywordCountingSet) { + throw new DescriptorParseException("Missing '" + missingKeyword + + "' line in exit list entry."); + } + this.keywordCountingSet = null; + } + + private void parseExitListEntryBytes() + throws DescriptorParseException { + Scanner s = new Scanner(new String(this.exitListEntryBytes)). + useDelimiter(ExitList.EOL); + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split(" "); + String keyword = parts[0]; + switch (keyword) { + case "ExitNode": + this.parseExitNodeLine(line, parts); + break; + case "Published": + this.parsePublishedLine(line, parts); + break; + case "LastStatus": + this.parseLastStatusLine(line, parts); + break; + case "ExitAddress": + this.parseExitAddressLine(line, parts); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in exit list entry."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseExitNodeLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Invalid line '" + line + "' in " + + "exit list entry."); + } + this.parsedExactlyOnceKeyword(parts[0]); + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + } + + private void parsePublishedLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3) { + throw new DescriptorParseException("Invalid line '" + line + "' in " + + "exit list entry."); + } + this.parsedExactlyOnceKeyword(parts[0]); + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseLastStatusLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3) { + throw new DescriptorParseException("Invalid line '" + line + "' in " + + "exit list entry."); + } + this.parsedExactlyOnceKeyword(parts[0]); + this.lastStatusMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseExitAddressLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 4) { + throw new DescriptorParseException("Invalid line '" + line + "' in " + + "exit list entry."); + } + this.keywordCountingSet.remove(parts[0]); + this.exitAddresses.put(ParseHelper.parseIpv4Address(line, parts[1]), + ParseHelper.parseTimestampAtIndex(line, parts, 2, 3)); + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private long lastStatusMillis; + @Override + public long getLastStatusMillis() { + return this.lastStatusMillis; + } + + private String exitAddress; + @Override + public String getExitAddress() { + if (null == exitAddress) { + Map.Entry<String, Long> randomEntry = + this.exitAddresses.entrySet().iterator().next(); + this.exitAddress = randomEntry.getKey(); + this.scanMillis = randomEntry.getValue(); + } + return this.exitAddress; + } + + private Map<String, Long> exitAddresses = new HashMap<>(); + @Override + public Map<String, Long> getExitAddresses(){ + return new HashMap<>(this.exitAddresses); + } + + private long scanMillis; + @Override + public long getScanMillis() { + if (null == exitAddress) { + getExitAddress(); + } + return scanMillis; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/ExitListImpl.java b/src/main/java/org/torproject/descriptor/impl/ExitListImpl.java new file mode 100644 index 0000000..10619ba --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/ExitListImpl.java @@ -0,0 +1,142 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; +import java.util.TimeZone; + +import org.torproject.descriptor.ExitList; +import org.torproject.descriptor.ExitListEntry; + +public class ExitListImpl extends DescriptorImpl implements ExitList { + + protected ExitListImpl(byte[] rawDescriptorBytes, String fileName, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); + this.splitAndParseExitListEntries(rawDescriptorBytes); + this.setPublishedMillisFromFileName(fileName); + } + + private void setPublishedMillisFromFileName(String fileName) + throws DescriptorParseException { + if (this.downloadedMillis == 0L && + fileName.length() == "2012-02-01-04-06-24".length()) { + try { + SimpleDateFormat fileNameFormat = new SimpleDateFormat( + "yyyy-MM-dd-HH-mm-ss"); + fileNameFormat.setLenient(false); + fileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + this.downloadedMillis = fileNameFormat.parse(fileName).getTime(); + } catch (ParseException e) { + /* Handle below. */ + } + } + if (this.downloadedMillis == 0L) { + throw new DescriptorParseException("Unrecognized exit list file " + + "name '" + fileName + "'."); + } + } + + private void splitAndParseExitListEntries(byte[] rawDescriptorBytes) + throws DescriptorParseException { + if (this.rawDescriptorBytes.length == 0) { + throw new DescriptorParseException("Descriptor is empty."); + } + String descriptorString = new String(rawDescriptorBytes); + Scanner s = new Scanner(descriptorString).useDelimiter(EOL); + StringBuilder sb = new StringBuilder(); + boolean firstEntry = true; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("@")) { /* Skip annotation. */ + if (!s.hasNext()) { + throw new DescriptorParseException("Descriptor is empty."); + } else { + line = s.next(); + } + } + String[] parts = line.split(" "); + String keyword = parts[0]; + switch (keyword) { + case "Downloaded": + this.downloadedMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + break; + case "ExitNode": + if (!firstEntry) { + this.parseExitListEntry(sb.toString().getBytes()); + } else { + firstEntry = false; + } + sb = new StringBuilder(); + sb.append(line).append(ExitList.EOL); + break; + case "Published": + sb.append(line).append(ExitList.EOL); + break; + case "LastStatus": + sb.append(line).append(ExitList.EOL); + break; + case "ExitAddress": + sb.append(line).append(ExitList.EOL); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in exit list."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + /* Parse the last entry. */ + this.parseExitListEntry(sb.toString().getBytes()); + } + + protected void parseExitListEntry(byte[] exitListEntryBytes) + throws DescriptorParseException { + ExitListEntryImpl exitListEntry = new ExitListEntryImpl( + exitListEntryBytes, this.failUnrecognizedDescriptorLines); + this.exitListEntries.add(exitListEntry); + this.oldExitListEntries.addAll(exitListEntry.oldEntries()); + List<String> unrecognizedExitListEntryLines = exitListEntry. + getAndClearUnrecognizedLines(); + if (unrecognizedExitListEntryLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(unrecognizedExitListEntryLines); + } + } + + private long downloadedMillis; + @Override + public long getDownloadedMillis() { + return this.downloadedMillis; + } + + private Set<ExitListEntry> oldExitListEntries = new HashSet<>(); + @Deprecated + @Override + public Set<ExitListEntry> getExitListEntries() { + return new HashSet<>(this.oldExitListEntries); + } + + private Set<ExitList.Entry> exitListEntries = new HashSet<>(); + @Override + public Set<ExitList.Entry> getEntries() { + return new HashSet<>(this.exitListEntries); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java new file mode 100644 index 0000000..3f72616 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java @@ -0,0 +1,1284 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.BandwidthHistory; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExtraInfoDescriptor; + +public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl + implements ExtraInfoDescriptor { + + protected ExtraInfoDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseDescriptorBytes(); + this.calculateDigest(); + this.calculateDigestSha256(); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( + "extra-info,published").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> dirreqStatsKeywords = new HashSet<>(Arrays.asList(( + "dirreq-stats-end,dirreq-v2-ips,dirreq-v3-ips,dirreq-v2-reqs," + + "dirreq-v3-reqs,dirreq-v2-share,dirreq-v3-share,dirreq-v2-resp," + + "dirreq-v3-resp,dirreq-v2-direct-dl,dirreq-v3-direct-dl," + + "dirreq-v2-tunneled-dl,dirreq-v3-tunneled-dl,").split(","))); + Set<String> entryStatsKeywords = new HashSet<>(Arrays.asList( + "entry-stats-end,entry-ips".split(","))); + Set<String> cellStatsKeywords = new HashSet<>(Arrays.asList(( + "cell-stats-end,cell-processed-cells,cell-queued-cells," + + "cell-time-in-queue,cell-circuits-per-decile").split(","))); + Set<String> connBiDirectStatsKeywords = new HashSet<>( + Arrays.asList("conn-bi-direct".split(","))); + Set<String> exitStatsKeywords = new HashSet<>(Arrays.asList(( + "exit-stats-end,exit-kibibytes-written,exit-kibibytes-read," + + "exit-streams-opened").split(","))); + Set<String> bridgeStatsKeywords = new HashSet<>(Arrays.asList( + "bridge-stats-end,bridge-stats-ips".split(","))); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "identity-ed25519,master-key-ed25519,read-history,write-history," + + "dirreq-read-history,dirreq-write-history,geoip-db-digest," + + "router-sig-ed25519,router-signature,router-digest-sha256," + + "router-digest").split(","))); + atMostOnceKeywords.addAll(dirreqStatsKeywords); + atMostOnceKeywords.addAll(entryStatsKeywords); + atMostOnceKeywords.addAll(cellStatsKeywords); + atMostOnceKeywords.addAll(connBiDirectStatsKeywords); + atMostOnceKeywords.addAll(exitStatsKeywords); + atMostOnceKeywords.addAll(bridgeStatsKeywords); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkKeywordsDependOn(dirreqStatsKeywords, "dirreq-stats-end"); + this.checkKeywordsDependOn(entryStatsKeywords, "entry-stats-end"); + this.checkKeywordsDependOn(cellStatsKeywords, "cell-stats-end"); + this.checkKeywordsDependOn(exitStatsKeywords, "exit-stats-end"); + this.checkKeywordsDependOn(bridgeStatsKeywords, "bridge-stats-end"); + this.checkFirstKeyword("extra-info"); + this.clearParsedKeywords(); + return; + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = ""; + List<String> cryptoLines = null; + while (s.hasNext()) { + String line = s.next(); + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String[] partsNoOpt = lineNoOpt.split("[ \t]+"); + String keyword = partsNoOpt[0]; + switch (keyword) { + case "extra-info": + this.parseExtraInfoLine(line, lineNoOpt, partsNoOpt); + break; + case "published": + this.parsePublishedLine(line, lineNoOpt, partsNoOpt); + break; + case "read-history": + this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "write-history": + this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "geoip-db-digest": + this.parseGeoipDbDigestLine(line, lineNoOpt, partsNoOpt); + break; + case "geoip6-db-digest": + this.parseGeoip6DbDigestLine(line, lineNoOpt, partsNoOpt); + break; + case "geoip-start-time": + this.parseGeoipStartTimeLine(line, lineNoOpt, partsNoOpt); + break; + case "geoip-client-origins": + this.parseGeoipClientOriginsLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-stats-end": + this.parseDirreqStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-ips": + this.parseDirreqV2IpsLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-ips": + this.parseDirreqV3IpsLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-reqs": + this.parseDirreqV2ReqsLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-reqs": + this.parseDirreqV3ReqsLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-share": + this.parseDirreqV2ShareLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-share": + this.parseDirreqV3ShareLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-resp": + this.parseDirreqV2RespLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-resp": + this.parseDirreqV3RespLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-direct-dl": + this.parseDirreqV2DirectDlLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-direct-dl": + this.parseDirreqV3DirectDlLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v2-tunneled-dl": + this.parseDirreqV2TunneledDlLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-v3-tunneled-dl": + this.parseDirreqV3TunneledDlLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-read-history": + this.parseDirreqReadHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "dirreq-write-history": + this.parseDirreqWriteHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "entry-stats-end": + this.parseEntryStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "entry-ips": + this.parseEntryIpsLine(line, lineNoOpt, partsNoOpt); + break; + case "cell-stats-end": + this.parseCellStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "cell-processed-cells": + this.parseCellProcessedCellsLine(line, lineNoOpt, partsNoOpt); + break; + case "cell-queued-cells": + this.parseCellQueuedCellsLine(line, lineNoOpt, partsNoOpt); + break; + case "cell-time-in-queue": + this.parseCellTimeInQueueLine(line, lineNoOpt, partsNoOpt); + break; + case "cell-circuits-per-decile": + this.parseCellCircuitsPerDecileLine(line, lineNoOpt, partsNoOpt); + break; + case "conn-bi-direct": + this.parseConnBiDirectLine(line, lineNoOpt, partsNoOpt); + break; + case "exit-stats-end": + this.parseExitStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "exit-kibibytes-written": + this.parseExitKibibytesWrittenLine(line, lineNoOpt, partsNoOpt); + break; + case "exit-kibibytes-read": + this.parseExitKibibytesReadLine(line, lineNoOpt, partsNoOpt); + break; + case "exit-streams-opened": + this.parseExitStreamsOpenedLine(line, lineNoOpt, partsNoOpt); + break; + case "bridge-stats-end": + this.parseBridgeStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "bridge-ips": + this.parseBridgeStatsIpsLine(line, lineNoOpt, partsNoOpt); + break; + case "bridge-ip-versions": + this.parseBridgeIpVersionsLine(line, lineNoOpt, partsNoOpt); + break; + case "bridge-ip-transports": + this.parseBridgeIpTransportsLine(line, lineNoOpt, partsNoOpt); + break; + case "transport": + this.parseTransportLine(line, lineNoOpt, partsNoOpt); + break; + case "hidserv-stats-end": + this.parseHidservStatsEndLine(line, lineNoOpt, partsNoOpt); + break; + case "hidserv-rend-relayed-cells": + this.parseHidservRendRelayedCellsLine(line, lineNoOpt, + partsNoOpt); + break; + case "hidserv-dir-onions-seen": + this.parseHidservDirOnionsSeenLine(line, lineNoOpt, partsNoOpt); + break; + case "identity-ed25519": + this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt); + nextCrypto = "identity-ed25519"; + break; + case "master-key-ed25519": + this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt); + break; + case "router-sig-ed25519": + this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt); + break; + case "router-signature": + this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "router-signature"; + break; + case "router-digest": + this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt); + break; + case "router-digest-sha256": + this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt); + break; + case "-----BEGIN": + cryptoLines = new ArrayList<>(); + cryptoLines.add(line); + break; + case "-----END": + cryptoLines.add(line); + StringBuilder sb = new StringBuilder(); + for (String cryptoLine : cryptoLines) { + sb.append("\n").append(cryptoLine); + } + String cryptoString = sb.toString().substring(1); + switch (nextCrypto) { + case "router-signature": + this.routerSignature = cryptoString; + break; + case "identity-ed25519": + this.identityEd25519 = cryptoString; + this.parseIdentityEd25519CryptoBlock(cryptoString); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized crypto " + + "block '" + cryptoString + "' in extra-info " + + "descriptor."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(cryptoLines); + } + cryptoLines = null; + nextCrypto = ""; + } + break; + default: + if (cryptoLines != null) { + cryptoLines.add(line); + } else { + ParseHelper.parseKeyword(line, partsNoOpt[0]); + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in extra-info descriptor."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + private void parseExtraInfoLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + + "' in extra-info descriptor."); + } + this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]); + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[2]); + } + + private void parsePublishedLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, + partsNoOpt, 1, 2); + } + + private void parseReadHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.readHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseWriteHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.writeHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseGeoipDbDigestLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in extra-info descriptor."); + } + this.geoipDbDigest = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[1]); + } + + private void parseGeoip6DbDigestLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in extra-info descriptor."); + } + this.geoip6DbDigest = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[1]); + } + + private void parseGeoipStartTimeLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + + "' in extra-info descriptor."); + } + this.geoipStartTimeMillis = ParseHelper.parseTimestampAtIndex(line, + partsNoOpt, 1, 2); + } + + private void parseGeoipClientOriginsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.geoipClientOrigins = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 2); + } + + private void parseDirreqStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.dirreqStatsEndMillis = parsedStatsEndData[0]; + this.dirreqStatsIntervalLength = parsedStatsEndData[1]; + } + + private long[] parseStatsEndLine(String line, String partsNoOpt[], + int partsNoOptExpectedLength) throws DescriptorParseException { + if (partsNoOpt.length != partsNoOptExpectedLength || + partsNoOpt[3].length() < 2 || !partsNoOpt[3].startsWith("(") || + !partsNoOpt[4].equals("s)")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + long[] result = new long[2]; + result[0] = ParseHelper.parseTimestampAtIndex(line, partsNoOpt, 1, 2); + result[1] = ParseHelper.parseSeconds(line, + partsNoOpt[3].substring(1)); + if (result[1] <= 0) { + throw new DescriptorParseException("Interval length must be " + + "positive in line '" + line + "'."); + } + return result; + } + + private void parseDirreqV2IpsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2Ips = ParseHelper.parseCommaSeparatedKeyIntegerValueList( + line, partsNoOpt, 1, 2); + } + + private void parseDirreqV3IpsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3Ips = ParseHelper.parseCommaSeparatedKeyIntegerValueList( + line, partsNoOpt, 1, 2); + } + + private void parseDirreqV2ReqsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2Reqs = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 2); + } + + private void parseDirreqV3ReqsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3Reqs = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 2); + } + + private void parseDirreqV2ShareLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2Share = this.parseShareLine(line, partsNoOpt); + } + + private void parseDirreqV3ShareLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3Share = this.parseShareLine(line, partsNoOpt); + } + + private double parseShareLine(String line, String[] partsNoOpt) + throws DescriptorParseException { + double share = -1.0; + if (partsNoOpt.length == 2 && partsNoOpt[1].length() >= 2 && + partsNoOpt[1].endsWith("%")) { + String shareString = partsNoOpt[1]; + shareString = shareString.substring(0, shareString.length() - 1); + try { + share = Double.parseDouble(shareString); + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (share < 0.0) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + return share; + } + + private void parseDirreqV2RespLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2Resp = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseDirreqV3RespLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3Resp = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseDirreqV2DirectDlLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2DirectDl = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseDirreqV3DirectDlLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3DirectDl = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseDirreqV2TunneledDlLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV2TunneledDl = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseDirreqV3TunneledDlLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqV3TunneledDl = + ParseHelper.parseCommaSeparatedKeyIntegerValueList( + line,partsNoOpt, 1, 0); + } + + private void parseDirreqReadHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqReadHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseDirreqWriteHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.dirreqWriteHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseEntryStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.entryStatsEndMillis = parsedStatsEndData[0]; + this.entryStatsIntervalLength = parsedStatsEndData[1]; + } + + private void parseEntryIpsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.entryIps = ParseHelper.parseCommaSeparatedKeyIntegerValueList( + line, partsNoOpt, 1, 2); + } + + private void parseCellStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.cellStatsEndMillis = parsedStatsEndData[0]; + this.cellStatsIntervalLength = parsedStatsEndData[1]; + } + + private void parseCellProcessedCellsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.cellProcessedCells = ParseHelper. + parseCommaSeparatedIntegerValueList(line, partsNoOpt, 1); + if (this.cellProcessedCells.length != 10) { + throw new DescriptorParseException("There must be exact ten values " + + "in line '" + line + "'."); + } + } + + private void parseCellQueuedCellsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.cellQueuedCells = ParseHelper.parseCommaSeparatedDoubleValueList( + line, partsNoOpt, 1); + if (this.cellQueuedCells.length != 10) { + throw new DescriptorParseException("There must be exact ten values " + + "in line '" + line + "'."); + } + } + + private void parseCellTimeInQueueLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.cellTimeInQueue = ParseHelper. + parseCommaSeparatedIntegerValueList(line, partsNoOpt, 1); + if (this.cellTimeInQueue.length != 10) { + throw new DescriptorParseException("There must be exact ten values " + + "in line '" + line + "'."); + } + } + + private void parseCellCircuitsPerDecileLine(String line, + String lineNoOpt, String[] partsNoOpt) + throws DescriptorParseException { + int circuits = -1; + if (partsNoOpt.length == 2) { + try { + circuits = Integer.parseInt(partsNoOpt[1]); + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (circuits < 0) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.cellCircuitsPerDecile = circuits; + } + + private void parseConnBiDirectLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 6); + this.connBiDirectStatsEndMillis = parsedStatsEndData[0]; + this.connBiDirectStatsIntervalLength = parsedStatsEndData[1]; + Integer[] parsedConnBiDirectStats = ParseHelper. + parseCommaSeparatedIntegerValueList(line, partsNoOpt, 5); + if (parsedConnBiDirectStats.length != 4) { + throw new DescriptorParseException("Illegal line '" + line + "' in " + + "extra-info descriptor."); + } + this.connBiDirectBelow = parsedConnBiDirectStats[0]; + this.connBiDirectRead = parsedConnBiDirectStats[1]; + this.connBiDirectWrite = parsedConnBiDirectStats[2]; + this.connBiDirectBoth = parsedConnBiDirectStats[3]; + } + + private void parseExitStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.exitStatsEndMillis = parsedStatsEndData[0]; + this.exitStatsIntervalLength = parsedStatsEndData[1]; + } + + private void parseExitKibibytesWrittenLine(String line, + String lineNoOpt, String[] partsNoOpt) + throws DescriptorParseException { + this.exitKibibytesWritten = this.sortByPorts(ParseHelper. + parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); + this.verifyPorts(line, this.exitKibibytesWritten.keySet()); + this.verifyBytesOrStreams(line, this.exitKibibytesWritten.values()); + } + + private void parseExitKibibytesReadLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.exitKibibytesRead = this.sortByPorts(ParseHelper. + parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); + this.verifyPorts(line, this.exitKibibytesRead.keySet()); + this.verifyBytesOrStreams(line, this.exitKibibytesRead.values()); + } + + private void parseExitStreamsOpenedLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.exitStreamsOpened = this.sortByPorts(ParseHelper. + parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); + this.verifyPorts(line, this.exitStreamsOpened.keySet()); + this.verifyBytesOrStreams(line, this.exitStreamsOpened.values()); + } + + private SortedMap<String, Long> sortByPorts( + SortedMap<String, Long> naturalOrder) { + SortedMap<String, Long> byPortNumber = + new TreeMap<String, Long>(new Comparator<String>() { + public int compare(String arg0, String arg1) { + int port0 = 0, port1 = 0; + try { + port1 = Integer.parseInt(arg1); + } catch (NumberFormatException e) { + return -1; + } + try { + port0 = Integer.parseInt(arg0); + } catch (NumberFormatException e) { + return 1; + } + if (port0 < port1) { + return -1; + } else if (port0 > port1) { + return 1; + } else { + return 0; + } + }}); + byPortNumber.putAll(naturalOrder); + return byPortNumber; + } + + private void verifyPorts(String line, Set<String> ports) + throws DescriptorParseException { + boolean valid = true; + try { + for (String port : ports) { + if (!port.equals("other") && Integer.parseInt(port) <= 0) { + valid = false; + break; + } + } + } catch (NumberFormatException e) { + valid = false; + } + if (!valid) { + throw new DescriptorParseException("Invalid port in line '" + line + + "'."); + } + } + + private void verifyBytesOrStreams(String line, + Collection<Long> bytesOrStreams) throws DescriptorParseException { + boolean valid = true; + for (long s : bytesOrStreams) { + if (s < 0L) { + valid = false; + break; + } + } + if (!valid) { + throw new DescriptorParseException("Invalid value in line '" + line + + "'."); + } + } + + private void parseBridgeStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.bridgeStatsEndMillis = parsedStatsEndData[0]; + this.bridgeStatsIntervalLength = parsedStatsEndData[1]; + } + + private void parseBridgeStatsIpsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.bridgeIps = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 2); + } + + private void parseBridgeIpVersionsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.bridgeIpVersions = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 2); + } + + private void parseBridgeIpTransportsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.bridgeIpTransports = + ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, + partsNoOpt, 1, 0); + } + + private void parseTransportLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.transports.add(partsNoOpt[1]); + } + + private void parseHidservStatsEndLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, + 5); + this.hidservStatsEndMillis = parsedStatsEndData[0]; + this.hidservStatsIntervalLength = parsedStatsEndData[1]; + } + + private void parseHidservRendRelayedCellsLine(String line, + String lineNoOpt, String[] partsNoOpt) + throws DescriptorParseException { + if (partsNoOpt.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + try { + this.hidservRendRelayedCells = Double.parseDouble(partsNoOpt[1]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.hidservRendRelayedCellsParameters = + ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line, + partsNoOpt, 2); + } + + private void parseHidservDirOnionsSeenLine(String line, + String lineNoOpt, String[] partsNoOpt) + throws DescriptorParseException { + if (partsNoOpt.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + try { + this.hidservDirOnionsSeen = Double.parseDouble(partsNoOpt[1]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.hidservDirOnionsSeenParameters = + ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line, + partsNoOpt, 2); + } + + private void parseRouterSignatureLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("router-signature")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseRouterDigestLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[1]); + } + + private void parseIdentityEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 1) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseIdentityEd25519CryptoBlock(String cryptoString) + throws DescriptorParseException { + String masterKeyEd25519FromIdentityEd25519 = + ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( + cryptoString); + if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals( + masterKeyEd25519FromIdentityEd25519)) { + throw new DescriptorParseException("Mismatch between " + + "identity-ed25519 and master-key-ed25519."); + } + this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519; + } + + private void parseMasterKeyEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1]; + if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals( + masterKeyEd25519FromMasterKeyEd25519Line)) { + throw new DescriptorParseException("Mismatch between " + + "identity-ed25519 and master-key-ed25519."); + } + this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line; + } + + private void parseRouterSigEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.routerSignatureEd25519 = partsNoOpt[1]; + } + + private void parseRouterDigestSha256Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]); + this.extraInfoDigestSha256 = partsNoOpt[1]; + } + + private void calculateDigest() throws DescriptorParseException { + if (this.extraInfoDigest != null) { + /* We already learned the descriptor digest of this bridge + * descriptor from a "router-digest" line. */ + return; + } + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "extra-info "; + String sigToken = "\nrouter-signature\n"; + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, sig - start); + this.extraInfoDigest = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.extraInfoDigest == null) { + throw new DescriptorParseException("Could not calculate extra-info " + + "descriptor digest."); + } + } + + private void calculateDigestSha256() throws DescriptorParseException { + if (this.extraInfoDigestSha256 != null) { + /* We already learned the descriptor digest of this bridge + * descriptor from a "router-digest-sha256" line. */ + return; + } + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "extra-info "; + String sigToken = "\n-----END SIGNATURE-----\n"; + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, forDigest, + 0, sig - start); + this.extraInfoDigestSha256 = DatatypeConverter.printBase64Binary( + MessageDigest.getInstance("SHA-256").digest(forDigest)). + replaceAll("=", ""); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.extraInfoDigestSha256 == null) { + throw new DescriptorParseException("Could not calculate extra-info " + + "descriptor SHA-256 digest."); + } + } + + private String extraInfoDigest; + @Override + public String getExtraInfoDigest() { + return this.extraInfoDigest; + } + + private String extraInfoDigestSha256; + @Override + public String getExtraInfoDigestSha256() { + return this.extraInfoDigestSha256; + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private BandwidthHistory readHistory; + @Override + public BandwidthHistory getReadHistory() { + return this.readHistory; + } + + private BandwidthHistory writeHistory; + @Override + public BandwidthHistory getWriteHistory() { + return this.writeHistory; + } + + private String geoipDbDigest; + @Override + public String getGeoipDbDigest() { + return this.geoipDbDigest; + } + + private String geoip6DbDigest; + @Override + public String getGeoip6DbDigest() { + return this.geoip6DbDigest; + } + + private long dirreqStatsEndMillis = -1L; + @Override + public long getDirreqStatsEndMillis() { + return this.dirreqStatsEndMillis; + } + + private long dirreqStatsIntervalLength = -1L; + @Override + public long getDirreqStatsIntervalLength() { + return this.dirreqStatsIntervalLength; + } + + private String dirreqV2Ips; + @Override + public SortedMap<String, Integer> getDirreqV2Ips() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV2Ips); + } + + private String dirreqV3Ips; + @Override + public SortedMap<String, Integer> getDirreqV3Ips() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV3Ips); + } + + private String dirreqV2Reqs; + @Override + public SortedMap<String, Integer> getDirreqV2Reqs() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV2Reqs); + } + + private String dirreqV3Reqs; + @Override + public SortedMap<String, Integer> getDirreqV3Reqs() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV3Reqs); + } + + private double dirreqV2Share = -1.0; + @Override + public double getDirreqV2Share() { + return this.dirreqV2Share; + } + + private double dirreqV3Share = -1.0; + @Override + public double getDirreqV3Share() { + return this.dirreqV3Share; + } + + private String dirreqV2Resp; + @Override + public SortedMap<String, Integer> getDirreqV2Resp() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV2Resp); + } + + private String dirreqV3Resp; + @Override + public SortedMap<String, Integer> getDirreqV3Resp() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV3Resp); + } + + private String dirreqV2DirectDl; + @Override + public SortedMap<String, Integer> getDirreqV2DirectDl() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV2DirectDl); + } + + private String dirreqV3DirectDl; + @Override + public SortedMap<String, Integer> getDirreqV3DirectDl() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV3DirectDl); + } + + private String dirreqV2TunneledDl; + @Override + public SortedMap<String, Integer> getDirreqV2TunneledDl() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV2TunneledDl); + } + + private String dirreqV3TunneledDl; + @Override + public SortedMap<String, Integer> getDirreqV3TunneledDl() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.dirreqV3TunneledDl); + } + + private BandwidthHistory dirreqReadHistory; + @Override + public BandwidthHistory getDirreqReadHistory() { + return this.dirreqReadHistory; + } + + private BandwidthHistory dirreqWriteHistory; + @Override + public BandwidthHistory getDirreqWriteHistory() { + return this.dirreqWriteHistory; + } + + private long entryStatsEndMillis = -1L; + @Override + public long getEntryStatsEndMillis() { + return this.entryStatsEndMillis; + } + + private long entryStatsIntervalLength = -1L; + @Override + public long getEntryStatsIntervalLength() { + return this.entryStatsIntervalLength; + } + + private String entryIps; + @Override + public SortedMap<String, Integer> getEntryIps() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.entryIps); + } + + private long cellStatsEndMillis = -1L; + @Override + public long getCellStatsEndMillis() { + return this.cellStatsEndMillis; + } + + private long cellStatsIntervalLength = -1L; + @Override + public long getCellStatsIntervalLength() { + return this.cellStatsIntervalLength; + } + + private Integer[] cellProcessedCells; + @Override + public List<Integer> getCellProcessedCells() { + return this.cellProcessedCells == null ? null : + Arrays.asList(this.cellProcessedCells); + } + + private Double[] cellQueuedCells; + @Override + public List<Double> getCellQueuedCells() { + return this.cellQueuedCells == null ? null : + Arrays.asList(this.cellQueuedCells); + } + + private Integer[] cellTimeInQueue; + @Override + public List<Integer> getCellTimeInQueue() { + return this.cellTimeInQueue == null ? null : + Arrays.asList(this.cellTimeInQueue); + } + + private int cellCircuitsPerDecile = -1; + @Override + public int getCellCircuitsPerDecile() { + return this.cellCircuitsPerDecile; + } + + private long connBiDirectStatsEndMillis = -1L; + @Override + public long getConnBiDirectStatsEndMillis() { + return this.connBiDirectStatsEndMillis; + } + + private long connBiDirectStatsIntervalLength = -1L; + @Override + public long getConnBiDirectStatsIntervalLength() { + return this.connBiDirectStatsIntervalLength; + } + + private int connBiDirectBelow = -1; + @Override + public int getConnBiDirectBelow() { + return this.connBiDirectBelow; + } + + private int connBiDirectRead = -1; + @Override + public int getConnBiDirectRead() { + return this.connBiDirectRead; + } + + private int connBiDirectWrite = -1; + @Override + public int getConnBiDirectWrite() { + return this.connBiDirectWrite; + } + + private int connBiDirectBoth = -1; + @Override + public int getConnBiDirectBoth() { + return this.connBiDirectBoth; + } + + private long exitStatsEndMillis = -1L; + @Override + public long getExitStatsEndMillis() { + return this.exitStatsEndMillis; + } + + private long exitStatsIntervalLength = -1L; + @Override + public long getExitStatsIntervalLength() { + return this.exitStatsIntervalLength; + } + + private SortedMap<String, Long> exitKibibytesWritten; + @Override + public SortedMap<String, Long> getExitKibibytesWritten() { + return this.exitKibibytesWritten == null ? null : + new TreeMap<>(this.exitKibibytesWritten); + } + + private SortedMap<String, Long> exitKibibytesRead; + @Override + public SortedMap<String, Long> getExitKibibytesRead() { + return this.exitKibibytesRead == null ? null : + new TreeMap<>(this.exitKibibytesRead); + } + + private SortedMap<String, Long> exitStreamsOpened; + @Override + public SortedMap<String, Long> getExitStreamsOpened() { + return this.exitStreamsOpened == null ? null : + new TreeMap<>(this.exitStreamsOpened); + } + + private long geoipStartTimeMillis = -1L; + @Override + public long getGeoipStartTimeMillis() { + return this.geoipStartTimeMillis; + } + + private String geoipClientOrigins; + @Override + public SortedMap<String, Integer> getGeoipClientOrigins() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.geoipClientOrigins); + } + + private long bridgeStatsEndMillis = -1L; + @Override + public long getBridgeStatsEndMillis() { + return this.bridgeStatsEndMillis; + } + + private long bridgeStatsIntervalLength = -1L; + @Override + public long getBridgeStatsIntervalLength() { + return this.bridgeStatsIntervalLength; + } + + private String bridgeIps; + @Override + public SortedMap<String, Integer> getBridgeIps() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.bridgeIps); + } + + private String bridgeIpVersions; + @Override + public SortedMap<String, Integer> getBridgeIpVersions() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.bridgeIpVersions); + } + + private String bridgeIpTransports; + @Override + public SortedMap<String, Integer> getBridgeIpTransports() { + return ParseHelper.convertCommaSeparatedKeyIntegerValueList( + this.bridgeIpTransports); + } + + private List<String> transports = new ArrayList<>(); + @Override + public List<String> getTransports() { + return new ArrayList<>(this.transports); + } + + private long hidservStatsEndMillis = -1L; + @Override + public long getHidservStatsEndMillis() { + return this.hidservStatsEndMillis; + } + + private long hidservStatsIntervalLength = -1L; + @Override + public long getHidservStatsIntervalLength() { + return this.hidservStatsIntervalLength; + } + + private Double hidservRendRelayedCells; + @Override + public Double getHidservRendRelayedCells() { + return this.hidservRendRelayedCells; + } + + private Map<String, Double> hidservRendRelayedCellsParameters; + @Override + public Map<String, Double> getHidservRendRelayedCellsParameters() { + return this.hidservRendRelayedCellsParameters == null ? null : + new HashMap<>(this.hidservRendRelayedCellsParameters); + } + + private Double hidservDirOnionsSeen; + @Override + public Double getHidservDirOnionsSeen() { + return this.hidservDirOnionsSeen; + } + + private Map<String, Double> hidservDirOnionsSeenParameters; + @Override + public Map<String, Double> getHidservDirOnionsSeenParameters() { + return this.hidservDirOnionsSeenParameters == null ? null : + new HashMap<>(this.hidservDirOnionsSeenParameters); + } + + private String routerSignature; + @Override + public String getRouterSignature() { + return this.routerSignature; + } + + private String identityEd25519; + @Override + public String getIdentityEd25519() { + return this.identityEd25519; + } + + private String masterKeyEd25519; + @Override + public String getMasterKeyEd25519() { + return this.masterKeyEd25519; + } + + private String routerSignatureEd25519; + @Override + public String getRouterSignatureEd25519() { + return this.routerSignatureEd25519; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/MicrodescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/MicrodescriptorImpl.java new file mode 100644 index 0000000..4931c31 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/MicrodescriptorImpl.java @@ -0,0 +1,328 @@ +/* Copyright 2014--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; +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<>(); + 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<>(Arrays.asList( + "onion-key".split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "ntor-onion-key,family,p,p6,id").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("onion-key"); + this.clearParsedKeywords(); + return; + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("@")) { + continue; + } + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "onion-key": + this.parseOnionKeyLine(line, parts); + nextCrypto = "onion-key"; + break; + case "ntor-onion-key": + this.parseNtorOnionKeyLine(line, parts); + break; + case "a": + this.parseALine(line, parts); + break; + case "family": + this.parseFamilyLine(line, parts); + break; + case "p": + this.parsePLine(line, parts); + break; + case "p6": + this.parseP6Line(line, parts); + break; + case "id": + this.parseIdLine(line, parts); + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\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 = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\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<>(); + } + 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 { + String[] familyEntries = new String[parts.length - 1]; + 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)); + familyEntries[i - 1] = "$" + fingerprint + separator + nickname; + } else { + familyEntries[i - 1] = "$" + + ParseHelper.parseTwentyByteHexString(line, + parts[i].substring(1)); + } + } else { + familyEntries[i - 1] = ParseHelper.parseNickname(line, parts[i]); + } + } + this.familyEntries = familyEntries; + } + + 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 { + switch (parts[1]) { + case "accept": + case "reject": + String[] ports = parts[2].split(",", -1); + for (int i = 0; i < ports.length; i++) { + if (ports[i].length() < 1) { + isValid = false; + break; + } + } + break; + default: + isValid = false; + } + } + if (!isValid) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseIdLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } else { + switch (parts[1]) { + case "ed25519": + ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]); + this.ed25519Identity = parts[2]; + break; + case "rsa1024": + ParseHelper.parseTwentyByteBase64String(line, parts[2]); + this.rsa1024Identity = parts[2]; + break; + default: + 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 = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-256").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.microdescriptorDigest == null) { + throw new DescriptorParseException("Could not calculate " + + "microdescriptor digest."); + } + } + + private String microdescriptorDigest; + @Override + public String getMicrodescriptorDigest() { + return this.microdescriptorDigest; + } + + private String onionKey; + @Override + public String getOnionKey() { + return this.onionKey; + } + + private String ntorOnionKey; + @Override + public String getNtorOnionKey() { + return this.ntorOnionKey; + } + + private List<String> orAddresses = new ArrayList<>(); + @Override + public List<String> getOrAddresses() { + return new ArrayList<>(this.orAddresses); + } + + private String[] familyEntries; + @Override + public List<String> getFamilyEntries() { + return this.familyEntries == null ? null : + Arrays.asList(this.familyEntries); + } + private String defaultPolicy; + @Override + public String getDefaultPolicy() { + return this.defaultPolicy; + } + + private String portList; + @Override + public String getPortList() { + return this.portList; + } + + private String ipv6DefaultPolicy; + @Override + public String getIpv6DefaultPolicy() { + return this.ipv6DefaultPolicy; + } + + private String ipv6PortList; + @Override + public String getIpv6PortList() { + return this.ipv6PortList; + } + + private String rsa1024Identity; + @Override + public String getRsa1024Identity() { + return this.rsa1024Identity; + } + + private String ed25519Identity; + @Override + public String getEd25519Identity() { + return this.ed25519Identity; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java new file mode 100644 index 0000000..b73d211 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java @@ -0,0 +1,382 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.torproject.descriptor.NetworkStatusEntry; + +public class NetworkStatusEntryImpl implements NetworkStatusEntry { + + private byte[] statusEntryBytes; + @Override + public byte[] getStatusEntryBytes() { + return this.statusEntryBytes; + } + + private boolean microdescConsensus; + + private boolean failUnrecognizedDescriptorLines; + private List<String> unrecognizedLines; + protected List<String> getAndClearUnrecognizedLines() { + List<String> lines = this.unrecognizedLines; + this.unrecognizedLines = null; + return lines; + } + + protected NetworkStatusEntryImpl(byte[] statusEntryBytes, + boolean microdescConsensus, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + this.statusEntryBytes = statusEntryBytes; + this.microdescConsensus = microdescConsensus; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.initializeKeywords(); + this.parseStatusEntryBytes(); + this.clearAtMostOnceKeywords(); + } + + private SortedSet<String> atMostOnceKeywords; + private void initializeKeywords() { + this.atMostOnceKeywords = new TreeSet<>(); + this.atMostOnceKeywords.add("s"); + this.atMostOnceKeywords.add("v"); + this.atMostOnceKeywords.add("w"); + this.atMostOnceKeywords.add("p"); + } + + 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 { + Scanner s = new Scanner(new String(this.statusEntryBytes)). + useDelimiter("\n"); + String line = null; + if (!s.hasNext() || !(line = s.next()).startsWith("r ")) { + throw new DescriptorParseException("Status entry must start with " + + "an r line."); + } + String[] rLineParts = line.split("[ \t]+"); + this.parseRLine(line, rLineParts); + while (s.hasNext()) { + line = s.next(); + String[] parts = !line.startsWith("opt ") ? line.split("[ \t]+") : + line.substring("opt ".length()).split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "a": + this.parseALine(line, parts); + break; + case "s": + this.parseSLine(line, parts); + break; + case "v": + this.parseVLine(line, parts); + break; + case "w": + this.parseWLine(line, parts); + break; + case "p": + this.parsePLine(line, parts); + break; + case "m": + this.parseMLine(line, parts); + break; + case "id": + this.parseIdLine(line, parts); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in status entry."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseRLine(String line, String[] parts) + throws DescriptorParseException { + 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]); + int descriptorOffset = 0; + if (!this.microdescConsensus) { + this.descriptor = ParseHelper.parseTwentyByteBase64String(line, + parts[3]); + descriptorOffset = 1; + } + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 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) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Invalid line '" + line + "' in " + + "status entry."); + } + /* TODO Add more checks. */ + /* TODO Add tests. */ + this.orAddresses.add(parts[1]); + } + + private static Map<String, Integer> flagIndexes = new HashMap<>(); + private static Map<Integer, String> flagStrings = new HashMap<>(); + + private void parseSLine(String line, String[] parts) + throws DescriptorParseException { + this.parsedAtMostOnceKeyword("s"); + BitSet flags = new BitSet(flagIndexes.size()); + for (int i = 1; i < parts.length; i++) { + String flag = parts[i]; + if (!flagIndexes.containsKey(flag)) { + flagStrings.put(flagIndexes.size(), flag); + flagIndexes.put(flag, flagIndexes.size()); + } + flags.set(flagIndexes.get(flag)); + } + this.flags = flags; + } + + 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.parseKeyValueIntegerPairs(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.containsKey("Unmeasured")) { + this.unmeasured = pairs.remove("Unmeasured") == 1L; + } + if (!pairs.isEmpty()) { + /* Ignore unknown key-value pair. */ + } + } + + private void parsePLine(String line, String[] parts) + throws DescriptorParseException { + this.parsedAtMostOnceKeyword("p"); + boolean isValid = true; + if (parts.length != 3) { + isValid = false; + } else { + switch (parts[1]) { + case "accept": + case "reject": + 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; + } + } + break; + default: + isValid = false; + } + } + if (!isValid) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseMLine(String line, String[] parts) + throws DescriptorParseException { + if (this.microdescriptorDigests == null) { + this.microdescriptorDigests = new HashSet<>(); + } + 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 void parseIdLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3 || !"ed25519".equals(parts[1])) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } else if ("none".equals(parts[2])) { + this.masterKeyEd25519 = "none"; + } else { + ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]); + this.masterKeyEd25519 = parts[2]; + } + } + + private void clearAtMostOnceKeywords() { + this.atMostOnceKeywords = null; + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private String descriptor; + @Override + public String getDescriptor() { + return this.descriptor; + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private String address; + @Override + public String getAddress() { + return this.address; + } + + private int orPort; + @Override + public int getOrPort() { + return this.orPort; + } + + private int dirPort; + @Override + public int getDirPort() { + return this.dirPort; + } + + private Set<String> microdescriptorDigests; + @Override + public Set<String> getMicrodescriptorDigests() { + return this.microdescriptorDigests == null ? null : + new HashSet<>(this.microdescriptorDigests); + } + + private List<String> orAddresses = new ArrayList<>(); + @Override + public List<String> getOrAddresses() { + return new ArrayList<>(this.orAddresses); + } + + private BitSet flags; + @Override + public SortedSet<String> getFlags() { + SortedSet<String> result = new TreeSet<>(); + if (this.flags != null) { + for (int i = this.flags.nextSetBit(0); i >= 0; + i = this.flags.nextSetBit(i + 1)) { + result.add(flagStrings.get(i)); + } + } + return result; + } + + private String version; + @Override + public String getVersion() { + return this.version; + } + + private long bandwidth = -1L; + @Override + public long getBandwidth() { + return this.bandwidth; + } + + private long measured = -1L; + @Override + public long getMeasured() { + return this.measured; + } + + private boolean unmeasured = false; + @Override + public boolean getUnmeasured() { + return this.unmeasured; + } + + private String defaultPolicy; + @Override + public String getDefaultPolicy() { + return this.defaultPolicy; + } + + private String portList; + @Override + public String getPortList() { + return this.portList; + } + + private String masterKeyEd25519; + @Override + public String getMasterKeyEd25519() { + return this.masterKeyEd25519; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java new file mode 100644 index 0000000..5fa22c7 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/NetworkStatusImpl.java @@ -0,0 +1,270 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.torproject.descriptor.DirSourceEntry; +import org.torproject.descriptor.DirectorySignature; +import org.torproject.descriptor.NetworkStatusEntry; + +/* Parse the common parts of v3 consensuses, v3 votes, v3 microdesc + * consensuses, v2 statuses, and sanitized bridge network statuses and + * delegate the specific parts to the subclasses. */ +public abstract class NetworkStatusImpl extends DescriptorImpl { + + protected NetworkStatusImpl(byte[] rawDescriptorBytes, + boolean failUnrecognizedDescriptorLines, + boolean containsDirSourceEntries, boolean blankLinesAllowed) + throws DescriptorParseException { + super(rawDescriptorBytes, failUnrecognizedDescriptorLines, + blankLinesAllowed); + this.splitAndParseParts(this.rawDescriptorBytes, + containsDirSourceEntries); + } + + private void splitAndParseParts(byte[] rawDescriptorBytes, + boolean containsDirSourceEntries) throws DescriptorParseException { + if (this.rawDescriptorBytes.length == 0) { + throw new DescriptorParseException("Descriptor is empty."); + } + String descriptorString = new String(rawDescriptorBytes); + int startIndex = 0; + int firstDirSourceIndex = !containsDirSourceEntries ? -1 : + this.findFirstIndexOfKeyword(descriptorString, "dir-source"); + int firstRIndex = this.findFirstIndexOfKeyword(descriptorString, "r"); + int directoryFooterIndex = this.findFirstIndexOfKeyword( + descriptorString, "directory-footer"); + int firstDirectorySignatureIndex = this.findFirstIndexOfKeyword( + descriptorString, "directory-signature"); + int endIndex = descriptorString.length(); + if (firstDirectorySignatureIndex < 0) { + firstDirectorySignatureIndex = endIndex; + } + if (directoryFooterIndex < 0) { + directoryFooterIndex = firstDirectorySignatureIndex; + } + if (firstRIndex < 0) { + firstRIndex = directoryFooterIndex; + } + if (firstDirSourceIndex < 0) { + firstDirSourceIndex = firstRIndex; + } + if (firstDirSourceIndex > startIndex) { + this.parseHeaderBytes(descriptorString, startIndex, + firstDirSourceIndex); + } + if (firstRIndex > firstDirSourceIndex) { + this.parseDirSourceBytes(descriptorString, firstDirSourceIndex, + firstRIndex); + } + if (directoryFooterIndex > firstRIndex) { + this.parseStatusEntryBytes(descriptorString, firstRIndex, + directoryFooterIndex); + } + if (firstDirectorySignatureIndex > directoryFooterIndex) { + this.parseDirectoryFooterBytes(descriptorString, + directoryFooterIndex, firstDirectorySignatureIndex); + } + if (endIndex > firstDirectorySignatureIndex) { + this.parseDirectorySignatureBytes(descriptorString, + firstDirectorySignatureIndex, 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 parseDirSourceBytes(String descriptorString, int start, + int end) throws DescriptorParseException { + List<byte[]> splitDirSourceBytes = + this.splitByKeyword(descriptorString, "dir-source", start, end); + for (byte[] dirSourceBytes : splitDirSourceBytes) { + this.parseDirSource(dirSourceBytes); + } + } + + private void parseStatusEntryBytes(String descriptorString, int start, + int end) throws DescriptorParseException { + List<byte[]> splitStatusEntryBytes = + this.splitByKeyword(descriptorString, "r", start, end); + for (byte[] statusEntryBytes : splitStatusEntryBytes) { + this.parseStatusEntry(statusEntryBytes); + } + } + + private void parseDirectoryFooterBytes(String descriptorString, + int start, int end) throws DescriptorParseException { + byte[] directoryFooterBytes = new byte[end - start]; + System.arraycopy(this.rawDescriptorBytes, start, + directoryFooterBytes, 0, end - start); + this.parseFooter(directoryFooterBytes); + } + + 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<>(); + 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; + } + byte[] part = new byte[to - from]; + System.arraycopy(this.rawDescriptorBytes, from, part, 0, + to - from); + from = to; + splitParts.add(part); + } + return splitParts; + } + + protected abstract void parseHeader(byte[] headerBytes) + throws DescriptorParseException; + + protected void parseDirSource(byte[] dirSourceBytes) + throws DescriptorParseException { + DirSourceEntryImpl dirSourceEntry = new DirSourceEntryImpl( + dirSourceBytes, this.failUnrecognizedDescriptorLines); + this.dirSourceEntries.put(dirSourceEntry.getIdentity(), + dirSourceEntry); + List<String> unrecognizedDirSourceLines = dirSourceEntry. + getAndClearUnrecognizedLines(); + if (unrecognizedDirSourceLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(unrecognizedDirSourceLines); + } + } + + protected String[] parseClientOrServerVersions(String line, + String[] parts) throws DescriptorParseException { + String[] result = null; + switch (parts.length) { + case 1: + result = new String[0]; + break; + case 2: + result = parts[1].split(",", -1); + for (String version : result) { + if (version.length() < 1) { + throw new DescriptorParseException("Illegal versions line '" + + line + "'."); + } + } + break; + default: + throw new DescriptorParseException("Illegal versions line '" + line + + "'."); + } + return result; + } + + protected void parseStatusEntry(byte[] statusEntryBytes) + throws DescriptorParseException { + NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl( + statusEntryBytes, false, this.failUnrecognizedDescriptorLines); + this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); + List<String> unrecognizedStatusEntryLines = statusEntry. + getAndClearUnrecognizedLines(); + if (unrecognizedStatusEntryLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); + } + } + + protected abstract void parseFooter(byte[] footerBytes) + throws DescriptorParseException; + + protected void parseDirectorySignature(byte[] directorySignatureBytes) + throws DescriptorParseException { + if (this.signatures == null) { + this.signatures = new ArrayList<>(); + } + DirectorySignatureImpl signature = new DirectorySignatureImpl( + directorySignatureBytes, failUnrecognizedDescriptorLines); + this.signatures.add(signature); + List<String> unrecognizedStatusEntryLines = signature. + getAndClearUnrecognizedLines(); + if (unrecognizedStatusEntryLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); + } + } + + protected SortedMap<String, DirSourceEntry> dirSourceEntries = + new TreeMap<>(); + public SortedMap<String, DirSourceEntry> getDirSourceEntries() { + return new TreeMap<>(this.dirSourceEntries); + } + + protected SortedMap<String, NetworkStatusEntry> statusEntries = + new TreeMap<>(); + public SortedMap<String, NetworkStatusEntry> getStatusEntries() { + return new TreeMap<>(this.statusEntries); + } + public boolean containsStatusEntry(String fingerprint) { + return this.statusEntries.containsKey(fingerprint); + } + public NetworkStatusEntry getStatusEntry(String fingerprint) { + return this.statusEntries.get(fingerprint); + } + + protected List<DirectorySignature> signatures; + public List<DirectorySignature> getSignatures() { + return this.signatures == null ? null + : new ArrayList<>(this.signatures); + } + public SortedMap<String, DirectorySignature> getDirectorySignatures() { + SortedMap<String, DirectorySignature> directorySignatures = null; + if (this.signatures != null) { + directorySignatures = new TreeMap<>(); + for (DirectorySignature signature : this.signatures) { + directorySignatures.put(signature.getIdentity(), signature); + } + } + return directorySignatures; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/ParseHelper.java b/src/main/java/org/torproject/descriptor/impl/ParseHelper.java new file mode 100644 index 0000000..82c0813 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/ParseHelper.java @@ -0,0 +1,567 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.SortedMap; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; + +public class ParseHelper { + + private static Pattern keywordPattern = + Pattern.compile("^[A-Za-z0-9-]+$"); + protected static String parseKeyword(String line, String keyword) + throws DescriptorParseException { + if (!keywordPattern.matcher(keyword).matches()) { + throw new DescriptorParseException("Unrecognized character in " + + "keyword '" + keyword + "' in line '" + line + "'."); + } + return keyword; + } + + private static Pattern ipv4Pattern = + Pattern.compile("^[0-9\\.]{7,15}$"); + protected 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; + } + + protected 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; + } + + protected static long parseSeconds(String line, String secondsString) + throws DescriptorParseException { + try { + return Long.parseLong(secondsString); + } catch (NumberFormatException e) { + throw new DescriptorParseException("'" + secondsString + "' in " + + "line '" + line + "' is not a valid time in seconds."); + } + } + + protected static String parseExitPattern(String line, String exitPattern) + throws DescriptorParseException { + if (!exitPattern.contains(":")) { + throw new DescriptorParseException("'" + exitPattern + "' in line '" + + line + "' must contain address and port."); + } + String[] parts = exitPattern.split(":"); + String addressPart = parts[0]; + /* TODO Extend to IPv6. */ + if (addressPart.equals("*")) { + /* Nothing to check. */ + } else if (addressPart.contains("/")) { + String[] addressParts = addressPart.split("/"); + String address = addressParts[0]; + String mask = addressParts[1]; + ParseHelper.parseIpv4Address(line, address); + if (addressParts.length != 2) { + throw new DescriptorParseException("'" + addressPart + "' in " + + "line '" + line + "' is not a valid address part."); + } + if (mask.contains(".")) { + ParseHelper.parseIpv4Address(line, mask); + } else { + int maskValue = -1; + try { + maskValue = Integer.parseInt(mask); + } catch (NumberFormatException e) { + /* Handle below. */ + } + if (maskValue < 0 || maskValue > 32) { + throw new DescriptorParseException("'" + mask + "' in line '" + + line + "' is not a valid IPv4 mask."); + } + } + } else { + ParseHelper.parseIpv4Address(line, addressPart); + } + String portPart = parts[1]; + if (portPart.equals("*")) { + /* Nothing to check. */ + } else if (portPart.contains("-")) { + String[] portParts = portPart.split("-"); + String fromPort = portParts[0]; + ParseHelper.parsePort(line, fromPort); + String toPort = portParts[1]; + ParseHelper.parsePort(line, toPort); + } else { + ParseHelper.parsePort(line, portPart); + } + return exitPattern; + } + + private static ThreadLocal<Map<String, DateFormat>> dateFormats = + new ThreadLocal<Map<String, DateFormat>> () { + public Map<String, DateFormat> get() { + return super.get(); + } + protected Map<String, DateFormat> initialValue() { + return new HashMap<>(); + } + public void remove() { + super.remove(); + } + public void set(Map<String, DateFormat> value) { + super.set(value); + } + }; + static DateFormat getDateFormat(String format) { + Map<String, DateFormat> threadDateFormats = dateFormats.get(); + if (!threadDateFormats.containsKey(format)) { + DateFormat dateFormat = new SimpleDateFormat(format, Locale.US); + dateFormat.setLenient(false); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + threadDateFormats.put(format, dateFormat); + } + return threadDateFormats.get(format); + } + + protected 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 { + DateFormat dateTimeFormat = getDateFormat("yyyy-MM-dd HH:mm:ss"); + result = dateTimeFormat.parse( + parts[dateIndex] + " " + parts[timeIndex]).getTime(); + } catch (ParseException e) { + /* Leave result at -1L. */ + } + if (result < 0L || result / 1000L > (long) Integer.MAX_VALUE) { + throw new DescriptorParseException("Illegal timestamp format in " + + "line '" + line + "'."); + } + return result; + } + + protected static long parseDateAtIndex(String line, String[] parts, + int dateIndex) throws DescriptorParseException { + if (dateIndex >= parts.length) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a date at the expected position."); + } + long result = -1L; + try { + DateFormat dateFormat = getDateFormat("yyyy-MM-dd"); + result = dateFormat.parse(parts[dateIndex]).getTime(); + } catch (ParseException e) { + /* Leave result at -1L. */ + } + if (result < 0L || result / 1000L > (long) Integer.MAX_VALUE) { + throw new DescriptorParseException("Illegal date format in line '" + + line + "'."); + } + return result; + } + + protected static String parseTwentyByteHexString(String line, + String hexString) throws DescriptorParseException { + return parseHexString(line, hexString, 40); + } + + protected static String parseHexString(String line, String hexString) + throws DescriptorParseException { + return parseHexString(line, hexString, -1); + } + + private static Pattern hexPattern = Pattern.compile("^[0-9a-fA-F]*$"); + private static String parseHexString(String line, String hexString, + int expectedLength) throws DescriptorParseException { + if (!hexPattern.matcher(hexString).matches() || + hexString.length() % 2 != 0 || + (expectedLength >= 0 && hexString.length() != expectedLength)) { + throw new DescriptorParseException("Illegal hex string in line '" + + line + "'."); + } + return hexString.toUpperCase(); + } + + protected static SortedMap<String, String> parseKeyValueStringPairs( + String line, String[] parts, int startIndex, String separatorString) + throws DescriptorParseException { + SortedMap<String, String> result = new TreeMap<>(); + for (int i = startIndex; i < parts.length; i++) { + String pair = parts[i]; + String[] pairParts = pair.split(separatorString); + if (pairParts.length != 2) { + throw new DescriptorParseException("Illegal key-value pair in " + + "line '" + line + "'."); + } + result.put(pairParts[0], pairParts[1]); + } + return result; + } + + protected static SortedMap<String, Integer> parseKeyValueIntegerPairs( + String line, String[] parts, int startIndex, String separatorString) + throws DescriptorParseException { + SortedMap<String, Integer> result = new TreeMap<>(); + SortedMap<String, String> keyValueStringPairs = + ParseHelper.parseKeyValueStringPairs(line, parts, startIndex, + separatorString); + for (Map.Entry<String, String> e : keyValueStringPairs.entrySet()) { + try { + result.put(e.getKey(), Integer.parseInt(e.getValue())); + } catch (NumberFormatException ex) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + return result; + } + + private static Pattern nicknamePattern = + Pattern.compile("^[0-9a-zA-Z]{1,19}$"); + protected static String parseNickname(String line, String nickname) + throws DescriptorParseException { + if (!nicknamePattern.matcher(nickname).matches()) { + throw new DescriptorParseException("Illegal nickname in line '" + + line + "'."); + } + return nickname; + } + + protected static boolean parseBoolean(String b, String line) + throws DescriptorParseException { + switch (b) { + case "1": + return true; + case "0": + return false; + default: + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private static Pattern twentyByteBase64Pattern = + Pattern.compile("^[0-9a-zA-Z+/]{27}$"); + protected static String parseTwentyByteBase64String(String line, + String base64String) throws DescriptorParseException { + if (!twentyByteBase64Pattern.matcher(base64String).matches()) { + throw new DescriptorParseException("'" + base64String + + "' in line '" + line + "' is not a valid base64-encoded " + + "20-byte value."); + } + return DatatypeConverter.printHexBinary( + DatatypeConverter.parseBase64Binary(base64String + "=")). + toUpperCase(); + } + + private static Pattern thirtyTwoByteBase64Pattern = + Pattern.compile("^[0-9a-zA-Z+/]{43}$"); + protected 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 DatatypeConverter.printHexBinary( + DatatypeConverter.parseBase64Binary(base64String + "=")). + toUpperCase(); + } + + private static Map<Integer, Pattern> + commaSeparatedKeyValueListPatterns = new HashMap<>(); + protected static String parseCommaSeparatedKeyIntegerValueList( + String line, String[] partsNoOpt, int index, int keyLength) + throws DescriptorParseException { + String result = ""; + if (partsNoOpt.length < index) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a key-value list at index " + index + "."); + } else if (partsNoOpt.length > index + 1 ) { + throw new DescriptorParseException("Line '" + line + "' contains " + + "unrecognized values beyond the expected key-value list at " + + "index " + index + "."); + } else if (partsNoOpt.length > index) { + if (!commaSeparatedKeyValueListPatterns.containsKey(keyLength)) { + String keyPattern = "[0-9a-zA-Z?<>\\-_]" + + (keyLength == 0 ? "+" : "{" + keyLength + "}"); + String valuePattern = "\\-?[0-9]{1,9}"; + String patternString = String.format("^%s=%s(,%s=%s)*$", + keyPattern, valuePattern, keyPattern, valuePattern); + commaSeparatedKeyValueListPatterns.put(keyLength, + Pattern.compile(patternString)); + } + Pattern pattern = commaSeparatedKeyValueListPatterns.get( + keyLength); + if (pattern.matcher(partsNoOpt[index]).matches()) { + result = partsNoOpt[index]; + } else { + throw new DescriptorParseException("Line '" + line + "' " + + "contains an illegal key or value."); + } + } + return result; + } + + protected static SortedMap<String, Integer> + convertCommaSeparatedKeyIntegerValueList(String validatedString) { + SortedMap<String, Integer> result = null; + if (validatedString != null) { + result = new TreeMap<>(); + if (validatedString.contains("=")) { + for (String listElement : validatedString.split(",", -1)) { + String[] keyAndValue = listElement.split("="); + result.put(keyAndValue[0], Integer.parseInt(keyAndValue[1])); + } + } + } + return result; + } + + protected static SortedMap<String, Long> + parseCommaSeparatedKeyLongValueList(String line, + String[] partsNoOpt, int index, int keyLength) + throws DescriptorParseException { + SortedMap<String, Long> result = new TreeMap<>(); + if (partsNoOpt.length < index) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a key-value list at index " + index + "."); + } else if (partsNoOpt.length > index + 1 ) { + throw new DescriptorParseException("Line '" + line + "' contains " + + "unrecognized values beyond the expected key-value list at " + + "index " + index + "."); + } else if (partsNoOpt.length > index) { + String[] listElements = partsNoOpt[index].split(",", -1); + for (String listElement : listElements) { + String[] keyAndValue = listElement.split("="); + String key = null; + long value = -1; + if (keyAndValue.length == 2 && (keyLength == 0 || + keyAndValue[0].length() == keyLength)) { + try { + value = Long.parseLong(keyAndValue[1]); + key = keyAndValue[0]; + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (key == null) { + throw new DescriptorParseException("Line '" + line + "' " + + "contains an illegal key or value in list element '" + + listElement + "'."); + } + result.put(key, value); + } + } + return result; + } + + protected static Integer[] parseCommaSeparatedIntegerValueList( + String line, String[] partsNoOpt, int index) + throws DescriptorParseException { + Integer[] result = null; + if (partsNoOpt.length < index) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a comma-separated value list at index " + index + + "."); + } else if (partsNoOpt.length > index + 1) { + throw new DescriptorParseException("Line '" + line + "' contains " + + "unrecognized values beyond the expected comma-separated " + + "value list at index " + index + "."); + } else if (partsNoOpt.length > index) { + String[] listElements = partsNoOpt[index].split(",", -1); + result = new Integer[listElements.length]; + for (int i = 0; i < listElements.length; i++) { + try { + result[i] = Integer.parseInt(listElements[i]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Line '" + line + "' " + + "contains an illegal value in list element '" + + listElements[i] + "'."); + } + } + } + return result; + } + + protected static Double[] parseCommaSeparatedDoubleValueList( + String line, String[] partsNoOpt, int index) + throws DescriptorParseException { + Double[] result = null; + if (partsNoOpt.length < index) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a comma-separated value list at index " + index + + "."); + } else if (partsNoOpt.length > index + 1) { + throw new DescriptorParseException("Line '" + line + "' contains " + + "unrecognized values beyond the expected comma-separated " + + "value list at index " + index + "."); + } else if (partsNoOpt.length > index) { + String[] listElements = partsNoOpt[index].split(",", -1); + result = new Double[listElements.length]; + for (int i = 0; i < listElements.length; i++) { + try { + result[i] = Double.parseDouble(listElements[i]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Line '" + line + "' " + + "contains an illegal value in list element '" + + listElements[i] + "'."); + } + } + } + return result; + } + + protected static Map<String, Double> + parseSpaceSeparatedStringKeyDoubleValueMap(String line, + String[] partsNoOpt, int startIndex) + throws DescriptorParseException { + Map<String, Double> result = new LinkedHashMap<>(); + if (partsNoOpt.length < startIndex) { + throw new DescriptorParseException("Line '" + line + "' does not " + + "contain a key-value list starting at index " + startIndex + + "."); + } + for (int i = startIndex; i < partsNoOpt.length; i++) { + String listElement = partsNoOpt[i]; + String[] keyAndValue = listElement.split("="); + String key = null; + Double value = null; + if (keyAndValue.length == 2) { + try { + value = Double.parseDouble(keyAndValue[1]); + key = keyAndValue[0]; + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (key == null) { + throw new DescriptorParseException("Line '" + line + "' contains " + + "an illegal key or value in list element '" + listElement + + "'."); + } + result.put(key, value); + } + return result; + } + + protected static String + parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( + String identityEd25519CryptoBlock) throws DescriptorParseException { + String identityEd25519CryptoBlockNoNewlines = + identityEd25519CryptoBlock.replaceAll("\n", ""); + String beginEd25519CertLine = "-----BEGIN ED25519 CERT-----", + endEd25519CertLine = "-----END ED25519 CERT-----"; + if (!identityEd25519CryptoBlockNoNewlines.startsWith( + beginEd25519CertLine)) { + throw new DescriptorParseException("Illegal start of " + + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock + + "'."); + } + if (!identityEd25519CryptoBlockNoNewlines.endsWith( + endEd25519CertLine)) { + throw new DescriptorParseException("Illegal end of " + + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock + + "'."); + } + String identityEd25519Base64 = identityEd25519CryptoBlockNoNewlines. + substring(beginEd25519CertLine.length(), + identityEd25519CryptoBlock.length() + - endEd25519CertLine.length()).replaceAll("=", ""); + byte[] identityEd25519 = DatatypeConverter.parseBase64Binary( + identityEd25519Base64); + if (identityEd25519.length < 40) { + throw new DescriptorParseException("Invalid length of " + + "identity-ed25519 (in bytes): " + identityEd25519.length); + } else if (identityEd25519[0] != 0x01) { + throw new DescriptorParseException("Unknown version in " + + "identity-ed25519: " + identityEd25519[0]); + } else if (identityEd25519[1] != 0x04) { + throw new DescriptorParseException("Unknown cert type in " + + "identity-ed25519: " + identityEd25519[1]); + } else if (identityEd25519[6] != 0x01) { + throw new DescriptorParseException("Unknown certified key type in " + + "identity-ed25519: " + identityEd25519[1]); + } else if (identityEd25519[39] == 0x00) { + throw new DescriptorParseException("No extensions in " + + "identity-ed25519 (which would contain the encoded " + + "master-key-ed25519): " + identityEd25519[39]); + } else { + int extensionStart = 40; + for (int i = 0; i < (int) identityEd25519[39]; i++) { + if (identityEd25519.length < extensionStart + 4) { + throw new DescriptorParseException("Invalid extension with id " + + i + " in identity-ed25519."); + } + int extensionLength = identityEd25519[extensionStart]; + extensionLength <<= 8; + extensionLength += identityEd25519[extensionStart + 1]; + int extensionType = identityEd25519[extensionStart + 2]; + if (extensionLength == 32 && extensionType == 4) { + if (identityEd25519.length < extensionStart + 4 + 32) { + throw new DescriptorParseException("Invalid extension with " + + "id " + i + " in identity-ed25519."); + } + byte[] masterKeyEd25519 = new byte[32]; + System.arraycopy(identityEd25519, extensionStart + 4, + masterKeyEd25519, 0, masterKeyEd25519.length); + String masterKeyEd25519Base64 = DatatypeConverter. + printBase64Binary(masterKeyEd25519).replaceAll("=", ""); + String masterKeyEd25519Base64NoTrailingEqualSigns = + masterKeyEd25519Base64.replaceAll("=", ""); + return masterKeyEd25519Base64NoTrailingEqualSigns; + } + extensionStart += 4 + extensionLength; + } + } + throw new DescriptorParseException("Unable to locate " + + "master-key-ed25519 in identity-ed25519."); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayDirectoryImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayDirectoryImpl.java new file mode 100644 index 0000000..1ff15cb --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayDirectoryImpl.java @@ -0,0 +1,547 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; +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<>(); + 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<>(Arrays.asList(( + "signed-directory,recommended-software," + + "directory-signature").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList( + "dir-signing-key,running-routers,router-status".split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("signed-directory"); + this.clearParsedKeywords(); + } + + 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 = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException 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<>(); + 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 = "", + 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("[ \t]+"); + String keyword = partsNoOpt[0]; + switch (keyword) { + case "signed-directory": + this.parseSignedDirectoryLine(line, lineNoOpt, partsNoOpt); + break; + case "published": + if (publishedLine != null) { + throw new DescriptorParseException("Keyword 'published' is " + + "contained more than once, but must be contained exactly " + + "once."); + } else { + publishedLine = line; + } + break; + case "dir-signing-key": + this.parseDirSigningKeyLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "dir-signing-key"; + break; + case "recommended-software": + this.parseRecommendedSoftwareLine(line, lineNoOpt, partsNoOpt); + break; + case "running-routers": + runningRoutersLine = line; + break; + case "router-status": + routerStatusLine = line; + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\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 = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in v1 directory."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + 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("[ \t]+"); + this.parsePublishedLine(publishedLine, publishedLineNoOpt, + publishedPartsNoOpt); + } + if (routerStatusLine != null) { + String routerStatusLineNoOpt = routerStatusLine.startsWith("opt ") ? + routerStatusLine.substring("opt ".length()) : routerStatusLine; + String[] routerStatusPartsNoOpt = + routerStatusLineNoOpt.split("[ \t]+"); + this.parseRouterStatusLine(routerStatusLine, routerStatusLineNoOpt, + routerStatusPartsNoOpt); + } else if (runningRoutersLine != null) { + String runningRoutersLineNoOpt = + runningRoutersLine.startsWith("opt ") ? + runningRoutersLine.substring("opt ".length()) : + runningRoutersLine; + String[] runningRoutersPartsNoOpt = + runningRoutersLineNoOpt.split("[ \t]+"); + 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 RelayServerDescriptorImpl(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 = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String[] partsNoOpt = lineNoOpt.split("[ \t]+"); + String keyword = partsNoOpt[0]; + switch (keyword) { + case "directory-signature": + this.parseDirectorySignatureLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "directory-signature"; + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\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 = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in v2 network status."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + 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)).append("\n"); + keyString = keyString.substring(64); + } + if (keyString.length() > 0) { + sb.append(keyString).append("\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<>(); + 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; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private String dirSigningKey; + @Override + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private List<String> recommendedSoftware; + @Override + public List<String> getRecommendedSoftware() { + return this.recommendedSoftware == null ? null : + new ArrayList<>(this.recommendedSoftware); + } + + private String directorySignature; + @Override + public String getDirectorySignature() { + return this.directorySignature; + } + + private List<RouterStatusEntry> statusEntries = new ArrayList<>(); + @Override + public List<RouterStatusEntry> getRouterStatusEntries() { + return new ArrayList<>(this.statusEntries); + } + + private List<ServerDescriptor> serverDescriptors = new ArrayList<>(); + @Override + public List<ServerDescriptor> getServerDescriptors() { + return new ArrayList<>(this.serverDescriptors); + } + + private List<Exception> serverDescriptorParseExceptions = + new ArrayList<>(); + @Override + public List<Exception> getServerDescriptorParseExceptions() { + return new ArrayList<>(this.serverDescriptorParseExceptions); + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String directoryDigest; + @Override + public String getDirectoryDigest() { + return this.directoryDigest; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java new file mode 100644 index 0000000..73d4dfa --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java @@ -0,0 +1,37 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExtraInfoDescriptor; +import org.torproject.descriptor.RelayExtraInfoDescriptor; + +public class RelayExtraInfoDescriptorImpl + extends ExtraInfoDescriptorImpl implements RelayExtraInfoDescriptor { + + protected static List<ExtraInfoDescriptor> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<ExtraInfoDescriptor> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "extra-info "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + ExtraInfoDescriptor parsedDescriptor = + new RelayExtraInfoDescriptorImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected RelayExtraInfoDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java new file mode 100644 index 0000000..fe045c1 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java @@ -0,0 +1,414 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.RelayNetworkStatusConsensus; + +/* Contains a network status consensus or microdesc consensus. */ +public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl + implements RelayNetworkStatusConsensus { + + protected static List<RelayNetworkStatusConsensus> parseConsensuses( + byte[] consensusesBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<RelayNetworkStatusConsensus> parsedConsensuses = + new ArrayList<>(); + List<byte[]> splitConsensusBytes = + DescriptorImpl.splitRawDescriptorBytes(consensusesBytes, + "network-status-version 3"); + for (byte[] consensusBytes : splitConsensusBytes) { + RelayNetworkStatusConsensus parsedConsensus = + new RelayNetworkStatusConsensusImpl(consensusBytes, + failUnrecognizedDescriptorLines); + parsedConsensuses.add(parsedConsensus); + } + return parsedConsensuses; + } + + protected RelayNetworkStatusConsensusImpl(byte[] consensusBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(consensusBytes, failUnrecognizedDescriptorLines, true, false); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( + "vote-status,consensus-method,valid-after,fresh-until," + + "valid-until,voting-delay,known-flags").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "client-versions,server-versions,params,directory-footer," + + "bandwidth-weights").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("network-status-version"); + this.clearParsedKeywords(); + this.calculateDigest(); + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "network-status-version "; + String sigToken = "\ndirectory-signature "; + if (!ascii.contains(sigToken)) { + return; + } + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, sig - start); + this.consensusDigest = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.consensusDigest == null) { + throw new DescriptorParseException("Could not calculate consensus " + + "digest."); + } + } + + protected void parseHeader(byte[] headerBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "network-status-version": + this.parseNetworkStatusVersionLine(line, parts); + break; + case "vote-status": + this.parseVoteStatusLine(line, parts); + break; + case "consensus-method": + this.parseConsensusMethodLine(line, parts); + break; + case "valid-after": + this.parseValidAfterLine(line, parts); + break; + case "fresh-until": + this.parseFreshUntilLine(line, parts); + break; + case "valid-until": + this.parseValidUntilLine(line, parts); + break; + case "voting-delay": + this.parseVotingDelayLine(line, parts); + break; + case "client-versions": + this.parseClientVersionsLine(line, parts); + break; + case "server-versions": + this.parseServerVersionsLine(line, parts); + break; + case "package": + this.parsePackageLine(line, parts); + break; + case "known-flags": + this.parseKnownFlagsLine(line, parts); + break; + case "params": + this.parseParamsLine(line, parts); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in consensus."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + 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<>(); + } + this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); + } + } + + protected void parseFooter(byte[] footerBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "directory-footer": + break; + case "bandwidth-weights": + this.parseBandwidthWeightsLine(line, parts); + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in consensus."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseNetworkStatusVersionLine(String line, String[] parts) + throws DescriptorParseException { + 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) + throws DescriptorParseException { + if (parts.length != 2 || !parts[1].equals("consensus")) { + throw new DescriptorParseException("Line '" + line + "' indicates " + + "that this is not a consensus."); + } + } + + private void parseConsensusMethodLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in consensus."); + } + try { + this.consensusMethod = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal consensus method " + + "number in line '" + line + "'."); + } + if (this.consensusMethod < 1) { + throw new DescriptorParseException("Illegal consensus method " + + "number in line '" + line + "'."); + } + } + + private void parseValidAfterLine(String line, String[] parts) + throws DescriptorParseException { + this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseFreshUntilLine(String line, String[] parts) + throws DescriptorParseException { + this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseValidUntilLine(String line, String[] parts) + throws DescriptorParseException { + this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseVotingDelayLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + 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 + + "'."); + } + } + + private void parseClientVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedClientVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parseServerVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedServerVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parsePackageLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 5) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + if (this.packageLines == null) { + this.packageLines = new ArrayList<>(); + } + this.packageLines.add(line.substring("package ".length())); + } + + private void parseKnownFlagsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No known flags in line '" + line + + "'."); + } + String[] knownFlags = new String[parts.length - 1]; + for (int i = 1; i < parts.length; i++) { + knownFlags[i - 1] = parts[i]; + } + this.knownFlags = knownFlags; + } + + private void parseParamsLine(String line, String[] parts) + throws DescriptorParseException { + this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); + } + + private void parseBandwidthWeightsLine(String line, String[] parts) + throws DescriptorParseException { + this.bandwidthWeights = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); + } + + private String consensusDigest; + @Override + public String getConsensusDigest() { + return this.consensusDigest; + } + + private int networkStatusVersion; + @Override + public int getNetworkStatusVersion() { + return this.networkStatusVersion; + } + + private String consensusFlavor; + @Override + public String getConsensusFlavor() { + return this.consensusFlavor; + } + + private int consensusMethod; + @Override + public int getConsensusMethod() { + return this.consensusMethod; + } + + private long validAfterMillis; + @Override + public long getValidAfterMillis() { + return this.validAfterMillis; + } + + private long freshUntilMillis; + @Override + public long getFreshUntilMillis() { + return this.freshUntilMillis; + } + + private long validUntilMillis; + @Override + public long getValidUntilMillis() { + return this.validUntilMillis; + } + + private long voteSeconds; + @Override + public long getVoteSeconds() { + return this.voteSeconds; + } + + private long distSeconds; + @Override + public long getDistSeconds() { + return this.distSeconds; + } + + private String[] recommendedClientVersions; + @Override + public List<String> getRecommendedClientVersions() { + return this.recommendedClientVersions == null ? null : + Arrays.asList(this.recommendedClientVersions); + } + + private String[] recommendedServerVersions; + @Override + public List<String> getRecommendedServerVersions() { + return this.recommendedServerVersions == null ? null : + Arrays.asList(this.recommendedServerVersions); + } + + private List<String> packageLines; + @Override + public List<String> getPackageLines() { + return this.packageLines == null ? null + : new ArrayList<>(this.packageLines); + } + + private String[] knownFlags; + @Override + public SortedSet<String> getKnownFlags() { + return new TreeSet<>(Arrays.asList(this.knownFlags)); + } + + private SortedMap<String, Integer> consensusParams; + @Override + public SortedMap<String, Integer> getConsensusParams() { + return this.consensusParams == null ? null: + new TreeMap<>(this.consensusParams); + } + + private SortedMap<String, Integer> bandwidthWeights; + @Override + public SortedMap<String, Integer> getBandwidthWeights() { + return this.bandwidthWeights == null ? null : + new TreeMap<>(this.bandwidthWeights); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java new file mode 100644 index 0000000..a5469db --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java @@ -0,0 +1,384 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 java.util.SortedSet; +import java.util.TreeSet; + +import javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.RelayNetworkStatus; + +/* TODO Write unit tests. */ + +public class RelayNetworkStatusImpl extends NetworkStatusImpl + implements RelayNetworkStatus { + + protected static List<RelayNetworkStatus> parseStatuses( + byte[] statusesBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<RelayNetworkStatus> parsedStatuses = new ArrayList<>(); + List<byte[]> splitStatusBytes = + DescriptorImpl.splitRawDescriptorBytes(statusesBytes, + "network-status-version 2"); + for (byte[] statusBytes : splitStatusBytes) { + RelayNetworkStatus parsedStatus = new RelayNetworkStatusImpl( + statusBytes, failUnrecognizedDescriptorLines); + parsedStatuses.add(parsedStatus); + } + return parsedStatuses; + } + + protected RelayNetworkStatusImpl(byte[] statusBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(statusBytes, failUnrecognizedDescriptorLines, false, true); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( + "network-status-version,dir-source,fingerprint,contact," + + "dir-signing-key,published").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList( + "dir-options,client-versions,server-versions".split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("network-status-version"); + this.clearParsedKeywords(); + this.calculateDigest(); + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "network-status-version "; + 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.statusDigest = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.statusDigest == null) { + throw new DescriptorParseException("Could not calculate status " + + "digest."); + } + } + + protected void parseHeader(byte[] headerBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); + String nextCrypto = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + if (line.isEmpty()) { + continue; + } + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "network-status-version": + this.parseNetworkStatusVersionLine(line, parts); + break; + case "dir-source": + this.parseDirSourceLine(line, parts); + break; + case "fingerprint": + this.parseFingerprintLine(line, parts); + break; + case "contact": + this.parseContactLine(line, parts); + break; + case "dir-signing-key": + this.parseDirSigningKeyLine(line, parts); + nextCrypto = "dir-signing-key"; + break; + case "client-versions": + this.parseClientVersionsLine(line, parts); + break; + case "server-versions": + this.parseServerVersionsLine(line, parts); + break; + case "published": + this.parsePublishedLine(line, parts); + break; + case "dir-options": + this.parseDirOptionsLine(line, parts); + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\n"); + String cryptoString = crypto.toString(); + crypto = null; + if (nextCrypto.equals("dir-signing-key")) { + this.dirSigningKey = cryptoString; + } else { + throw new DescriptorParseException("Unrecognized crypto " + + "block in v2 network status."); + } + nextCrypto = ""; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in v2 network status."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + protected void parseFooter(byte[] footerBytes) + throws DescriptorParseException { + throw new DescriptorParseException("No directory footer expected in " + + "v2 network status."); + } + + protected void parseDirectorySignature(byte[] directorySignatureBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(directorySignatureBytes)). + useDelimiter("\n"); + String nextCrypto = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "directory-signature": + this.parseDirectorySignatureLine(line, parts); + nextCrypto = "directory-signature"; + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\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 = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + line + + "' in v2 network status."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseNetworkStatusVersionLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("network-status-version 2")) { + throw new DescriptorParseException("Illegal network status version " + + "number in line '" + line + "'."); + } + this.networkStatusVersion = 2; + } + + private void parseDirSourceLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 4) { + throw new DescriptorParseException("Illegal line '" + line + + "' in v2 network status."); + } + if (parts[1].length() < 1) { + throw new DescriptorParseException("Illegal hostname in '" + line + + "'."); + } + this.address = ParseHelper.parseIpv4Address(line, parts[2]); + this.dirPort = ParseHelper.parsePort(line, parts[3]); + } + + + private void parseFingerprintLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in v2 network status."); + } + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + } + + private void parseContactLine(String line, String[] parts) + throws DescriptorParseException { + if (line.length() > "contact ".length()) { + this.contactLine = line.substring("contact ".length()); + } else { + this.contactLine = ""; + } + } + + private void parseDirSigningKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-signing-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseClientVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedClientVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parseServerVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedServerVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parsePublishedLine(String line, String[] parts) + throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseDirOptionsLine(String line, String[] parts) + throws DescriptorParseException { + String[] dirOptions = new String[parts.length - 1]; + for (int i = 1; i < parts.length; i++) { + dirOptions[i - 1] = parts[i]; + } + this.dirOptions = dirOptions; + } + + private void parseDirectorySignatureLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.nickname = ParseHelper.parseNickname(line, parts[1]); + } + + private String statusDigest; + @Override + public String getStatusDigest() { + return this.statusDigest; + } + + private int networkStatusVersion; + @Override + public int getNetworkStatusVersion() { + return this.networkStatusVersion; + } + + private String hostname; + @Override + public String getHostname() { + return this.hostname; + } + + private String address; + @Override + public String getAddress() { + return this.address; + } + + private int dirPort; + @Override + public int getDirport() { + return this.dirPort; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private String contactLine; + @Override + public String getContactLine() { + return this.contactLine; + } + + private String dirSigningKey; + @Override + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private String[] recommendedClientVersions; + @Override + public List<String> getRecommendedClientVersions() { + return this.recommendedClientVersions == null ? null : + Arrays.asList(this.recommendedClientVersions); + } + + private String[] recommendedServerVersions; + @Override + public List<String> getRecommendedServerVersions() { + return this.recommendedServerVersions == null ? null : + Arrays.asList(this.recommendedServerVersions); + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private String[] dirOptions; + @Override + public SortedSet<String> getDirOptions() { + return new TreeSet<>(Arrays.asList(this.dirOptions)); + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String directorySignature; + @Override + public String getDirectorySignature() { + return this.directorySignature; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java new file mode 100644 index 0000000..384ad1f --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java @@ -0,0 +1,761 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.DirectorySignature; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.torproject.descriptor.RelayNetworkStatusVote; + +/* Contains a network status vote. */ +public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl + implements RelayNetworkStatusVote { + + protected static List<RelayNetworkStatusVote> parseVotes( + byte[] votesBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<RelayNetworkStatusVote> parsedVotes = new ArrayList<>(); + List<byte[]> splitVotesBytes = + DescriptorImpl.splitRawDescriptorBytes(votesBytes, + "network-status-version 3"); + for (byte[] voteBytes : splitVotesBytes) { + RelayNetworkStatusVote parsedVote = + new RelayNetworkStatusVoteImpl(voteBytes, + failUnrecognizedDescriptorLines); + parsedVotes.add(parsedVote); + } + return parsedVotes; + } + + protected RelayNetworkStatusVoteImpl(byte[] voteBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(voteBytes, failUnrecognizedDescriptorLines, false, false); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( + "vote-status,published,valid-after,fresh-until," + + "valid-until,voting-delay,known-flags,dir-source," + + "dir-key-certificate-version,fingerprint,dir-key-published," + + "dir-key-expires,dir-identity-key,dir-signing-key," + + "dir-key-certification").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "consensus-methods,client-versions,server-versions," + + "flag-thresholds,params,contact," + + "legacy-key,dir-key-crosscert,dir-address,directory-footer"). + split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + Set<String> atLeastOnceKeywords = new HashSet<>(Arrays.asList( + "directory-signature")); + this.checkAtLeastOnceKeywords(atLeastOnceKeywords); + this.checkFirstKeyword("network-status-version"); + this.clearParsedKeywords(); + } + + protected void parseHeader(byte[] headerBytes) + throws DescriptorParseException { + /* Initialize flag-thresholds values here for the case that the vote + * doesn't contain those values. Initializing them in the constructor + * or when declaring variables wouldn't work, because those parts are + * evaluated later and would overwrite everything we parse here. */ + this.stableUptime = -1L; + this.stableMtbf = -1L; + this.fastBandwidth = -1L; + this.guardWfu = -1.0; + this.guardTk = -1L; + this.guardBandwidthIncludingExits = -1L; + this.guardBandwidthExcludingExits = -1L; + this.enoughMtbfInfo = -1; + this.ignoringAdvertisedBws = -1; + + Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); + String nextCrypto = ""; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split("[ \t]+"); + String keyword = parts[0]; + switch (keyword) { + case "network-status-version": + this.parseNetworkStatusVersionLine(line, parts); + break; + case "vote-status": + this.parseVoteStatusLine(line, parts); + break; + case "consensus-methods": + this.parseConsensusMethodsLine(line, parts); + break; + case "published": + this.parsePublishedLine(line, parts); + break; + case "valid-after": + this.parseValidAfterLine(line, parts); + break; + case "fresh-until": + this.parseFreshUntilLine(line, parts); + break; + case "valid-until": + this.parseValidUntilLine(line, parts); + break; + case "voting-delay": + this.parseVotingDelayLine(line, parts); + break; + case "client-versions": + this.parseClientVersionsLine(line, parts); + break; + case "server-versions": + this.parseServerVersionsLine(line, parts); + break; + case "package": + this.parsePackageLine(line, parts); + break; + case "known-flags": + this.parseKnownFlagsLine(line, parts); + break; + case "flag-thresholds": + this.parseFlagThresholdsLine(line, parts); + break; + case "params": + this.parseParamsLine(line, parts); + break; + case "dir-source": + this.parseDirSourceLine(line, parts); + break; + case "contact": + this.parseContactLine(line, parts); + break; + case "dir-key-certificate-version": + this.parseDirKeyCertificateVersionLine(line, parts); + break; + case "dir-address": + this.parseDirAddressLine(line, parts); + break; + case "fingerprint": + this.parseFingerprintLine(line, parts); + break; + case "legacy-dir-key": + this.parseLegacyDirKeyLine(line, parts); + break; + case "dir-key-published": + this.parseDirKeyPublished(line, parts); + break; + case "dir-key-expires": + this.parseDirKeyExpiresLine(line, parts); + break; + case "dir-identity-key": + this.parseDirIdentityKeyLine(line, parts); + nextCrypto = "dir-identity-key"; + break; + case "dir-signing-key": + this.parseDirSigningKeyLine(line, parts); + nextCrypto = "dir-signing-key"; + break; + case "dir-key-crosscert": + this.parseDirKeyCrosscertLine(line, parts); + nextCrypto = "dir-key-crosscert"; + break; + case "dir-key-certification": + this.parseDirKeyCertificationLine(line, parts); + nextCrypto = "dir-key-certification"; + break; + case "-----BEGIN": + crypto = new StringBuilder(); + crypto.append(line).append("\n"); + break; + case "-----END": + crypto.append(line).append("\n"); + String cryptoString = crypto.toString(); + crypto = null; + switch (nextCrypto) { + case "dir-identity-key": + this.dirIdentityKey = cryptoString; + break; + case "dir-signing-key": + this.dirSigningKey = cryptoString; + break; + case "dir-key-crosscert": + this.dirKeyCrosscert = cryptoString; + break; + case "dir-key-certification": + this.dirKeyCertification = cryptoString; + break; + default: + throw new DescriptorParseException("Unrecognized crypto " + + "block in vote."); + } + nextCrypto = ""; + break; + default: + if (crypto != null) { + crypto.append(line).append("\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in vote."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + private void parseNetworkStatusVersionLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("network-status-version 3")) { + throw new DescriptorParseException("Illegal network status version " + + "number in line '" + line + "'."); + } + this.networkStatusVersion = 3; + } + + private void parseVoteStatusLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2 || !parts[1].equals("vote")) { + throw new DescriptorParseException("Line '" + line + "' indicates " + + "that this is not a vote."); + } + } + + private void parseConsensusMethodsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in vote."); + } + Integer[] consensusMethods = new Integer[parts.length - 1]; + for (int i = 1; i < parts.length; i++) { + int consensusMethod = -1; + try { + consensusMethod = Integer.parseInt(parts[i]); + } catch (NumberFormatException e) { + /* We'll notice below that consensusMethod is still -1. */ + } + if (consensusMethod < 1) { + throw new DescriptorParseException("Illegal consensus method " + + "number in line '" + line + "'."); + } + consensusMethods[i - 1] = consensusMethod; + } + this.consensusMethods = consensusMethods; + } + + private void parsePublishedLine(String line, String[] parts) + throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseValidAfterLine(String line, String[] parts) + throws DescriptorParseException { + this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseFreshUntilLine(String line, String[] parts) + throws DescriptorParseException { + this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseValidUntilLine(String line, String[] parts) + throws DescriptorParseException { + this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, + 1, 2); + } + + private void parseVotingDelayLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 3) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + 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 + + "'."); + } + } + + private void parseClientVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedClientVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parseServerVersionsLine(String line, String[] parts) + throws DescriptorParseException { + this.recommendedServerVersions = this.parseClientOrServerVersions( + line, parts); + } + + private void parsePackageLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 5) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + if (this.packageLines == null) { + this.packageLines = new ArrayList<>(); + } + this.packageLines.add(line.substring("package ".length())); + } + + private void parseKnownFlagsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No known flags in line '" + line + + "'."); + } + String[] knownFlags = new String[parts.length - 1]; + for (int i = 1; i < parts.length; i++) { + knownFlags[i - 1] = parts[i]; + } + this.knownFlags = knownFlags; + } + + private void parseFlagThresholdsLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length < 2) { + throw new DescriptorParseException("No flag thresholds in line '" + + line + "'."); + } + SortedMap<String, String> flagThresholds = + ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); + try { + for (Map.Entry<String, String> e : flagThresholds.entrySet()) { + switch (e.getKey()) { + case "stable-uptime": + this.stableUptime = Long.parseLong(e.getValue()); + break; + case "stable-mtbf": + this.stableMtbf = Long.parseLong(e.getValue()); + break; + case "fast-speed": + this.fastBandwidth = Long.parseLong(e.getValue()); + break; + case "guard-wfu": + this.guardWfu = Double.parseDouble(e.getValue(). + replaceAll("%", "")); + break; + case "guard-tk": + this.guardTk = Long.parseLong(e.getValue()); + break; + case "guard-bw-inc-exits": + this.guardBandwidthIncludingExits = + Long.parseLong(e.getValue()); + break; + case "guard-bw-exc-exits": + this.guardBandwidthExcludingExits = + Long.parseLong(e.getValue()); + break; + case "enough-mtbf": + this.enoughMtbfInfo = Integer.parseInt(e.getValue()); + break; + case "ignoring-advertised-bws": + this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); + break; + default: + // empty + } + } + } catch (NumberFormatException ex) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + + private void parseParamsLine(String line, String[] parts) + throws DescriptorParseException { + this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, + parts, 1, "="); + } + + private void parseDirSourceLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 7) { + throw new DescriptorParseException("Illegal line '" + line + + "' in vote."); + } + this.nickname = ParseHelper.parseNickname(line, parts[1]); + this.identity = ParseHelper.parseTwentyByteHexString(line, parts[2]); + if (parts[3].length() < 1) { + throw new DescriptorParseException("Illegal hostname in '" + line + + "'."); + } + this.hostname = parts[3]; + this.address = ParseHelper.parseIpv4Address(line, parts[4]); + this.dirPort = ParseHelper.parsePort(line, parts[5]); + this.orPort = ParseHelper.parsePort(line, parts[6]); + } + + private void parseContactLine(String line, String[] parts) + throws DescriptorParseException { + if (line.length() > "contact ".length()) { + this.contactLine = line.substring("contact ".length()); + } else { + this.contactLine = ""; + } + } + + private void parseDirKeyCertificateVersionLine(String line, + String[] parts) throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in vote."); + } + try { + this.dirKeyCertificateVersion = Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal dir key certificate " + + "version in line '" + line + "'."); + } + if (this.dirKeyCertificateVersion < 1) { + throw new DescriptorParseException("Illegal dir key certificate " + + "version in line '" + line + "'."); + } + } + + private void parseDirAddressLine(String line, String[] parts) { + /* Nothing new to learn here. Also, this line hasn't been observed + * "in the wild" yet. Maybe it's just an urban legend. */ + } + + private void parseFingerprintLine(String line, String[] parts) + throws DescriptorParseException { + /* Nothing new to learn here. We already know the fingerprint from + * the dir-source line. But we should at least check that there's a + * valid fingerprint in this line. */ + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in vote."); + } + ParseHelper.parseTwentyByteHexString(line, parts[1]); + } + + private void parseLegacyDirKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.legacyDirKey = ParseHelper.parseTwentyByteHexString(line, parts[1]); + } + + private void parseDirKeyPublished(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirKeyExpiresLine(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirIdentityKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-identity-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirSigningKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-signing-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCrosscertLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-crosscert")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCertificationLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-certification")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + protected void parseFooter(byte[] footerBytes) + throws DescriptorParseException { + Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n"); + while (s.hasNext()) { + String line = s.next(); + if (!line.equals("directory-footer")) { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in vote."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String identity; + @Override + public String getIdentity() { + return this.identity; + } + + private String hostname; + @Override + public String getHostname() { + return this.hostname; + } + + private String address; + @Override + public String getAddress() { + return this.address; + } + + private int dirPort; + @Override + public int getDirport() { + return this.dirPort; + } + + private int orPort; + @Override + public int getOrport() { + return this.orPort; + } + + private String contactLine; + @Override + public String getContactLine() { + return this.contactLine; + } + + private int dirKeyCertificateVersion; + @Override + public int getDirKeyCertificateVersion() { + return this.dirKeyCertificateVersion; + } + + private String legacyDirKey; + @Override + public String getLegacyDirKey() { + return this.legacyDirKey; + } + + private long dirKeyPublishedMillis; + @Override + public long getDirKeyPublishedMillis() { + return this.dirKeyPublishedMillis; + } + + private long dirKeyExpiresMillis; + @Override + public long getDirKeyExpiresMillis() { + return this.dirKeyExpiresMillis; + } + + private String dirIdentityKey; + @Override + public String getDirIdentityKey() { + return this.dirIdentityKey; + } + + private String dirSigningKey; + @Override + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private String dirKeyCrosscert; + @Override + public String getDirKeyCrosscert() { + return this.dirKeyCrosscert; + } + + private String dirKeyCertification; + @Override + public String getDirKeyCertification() { + return this.dirKeyCertification; + } + + @Override + public String getSigningKeyDigest() { + String signingKeyDigest = null; + if (this.signatures != null && !this.signatures.isEmpty()) { + for (DirectorySignature signature : this.signatures) { + if (DirectorySignatureImpl.DEFAULT_ALGORITHM.equals( + signature.getAlgorithm())) { + signingKeyDigest = signature.getSigningKeyDigest(); + break; + } + } + } + return signingKeyDigest; + } + + private int networkStatusVersion; + @Override + public int getNetworkStatusVersion() { + return this.networkStatusVersion; + } + + private Integer[] consensusMethods; + @Override + public List<Integer> getConsensusMethods() { + return this.consensusMethods == null ? null : + Arrays.asList(this.consensusMethods); + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private long validAfterMillis; + @Override + public long getValidAfterMillis() { + return this.validAfterMillis; + } + + private long freshUntilMillis; + @Override + public long getFreshUntilMillis() { + return this.freshUntilMillis; + } + + private long validUntilMillis; + @Override + public long getValidUntilMillis() { + return this.validUntilMillis; + } + + private long voteSeconds; + @Override + public long getVoteSeconds() { + return this.voteSeconds; + } + + private long distSeconds; + @Override + public long getDistSeconds() { + return this.distSeconds; + } + + private String[] recommendedClientVersions; + @Override + public List<String> getRecommendedClientVersions() { + return this.recommendedClientVersions == null ? null : + Arrays.asList(this.recommendedClientVersions); + } + + private String[] recommendedServerVersions; + @Override + public List<String> getRecommendedServerVersions() { + return this.recommendedServerVersions == null ? null : + Arrays.asList(this.recommendedServerVersions); + } + + private List<String> packageLines; + @Override + public List<String> getPackageLines() { + return this.packageLines == null ? null + : new ArrayList<>(this.packageLines); + } + + private String[] knownFlags; + @Override + public SortedSet<String> getKnownFlags() { + return new TreeSet<>(Arrays.asList(this.knownFlags)); + } + + private long stableUptime; + @Override + public long getStableUptime() { + return this.stableUptime; + } + + private long stableMtbf; + @Override + public long getStableMtbf() { + return this.stableMtbf; + } + + private long fastBandwidth; + @Override + public long getFastBandwidth() { + return this.fastBandwidth; + } + + private double guardWfu; + @Override + public double getGuardWfu() { + return this.guardWfu; + } + + private long guardTk; + @Override + public long getGuardTk() { + return this.guardTk; + } + + private long guardBandwidthIncludingExits; + @Override + public long getGuardBandwidthIncludingExits() { + return this.guardBandwidthIncludingExits; + } + + private long guardBandwidthExcludingExits; + @Override + public long getGuardBandwidthExcludingExits() { + return this.guardBandwidthExcludingExits; + } + + private int enoughMtbfInfo; + @Override + public int getEnoughMtbfInfo() { + return this.enoughMtbfInfo; + } + + private int ignoringAdvertisedBws; + @Override + public int getIgnoringAdvertisedBws() { + return this.ignoringAdvertisedBws; + } + + private SortedMap<String, Integer> consensusParams; + @Override + public SortedMap<String, Integer> getConsensusParams() { + return this.consensusParams == null ? null: + new TreeMap<>(this.consensusParams); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java new file mode 100644 index 0000000..4957072 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java @@ -0,0 +1,37 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.RelayServerDescriptor; +import org.torproject.descriptor.ServerDescriptor; + +public class RelayServerDescriptorImpl extends ServerDescriptorImpl + implements RelayServerDescriptor { + + protected static List<ServerDescriptor> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<ServerDescriptor> parsedDescriptors = new ArrayList<>(); + List<byte[]> splitDescriptorsBytes = + DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, + "router "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + ServerDescriptor parsedDescriptor = + new RelayServerDescriptorImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected RelayServerDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines); + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/RouterStatusEntryImpl.java b/src/main/java/org/torproject/descriptor/impl/RouterStatusEntryImpl.java new file mode 100644 index 0000000..a359c50 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/RouterStatusEntryImpl.java @@ -0,0 +1,41 @@ +/* Copyright 2012--2015 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; + @Override + public String getNickname() { + return this.nickname; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private boolean isLive; + @Override + public boolean isLive() { + return this.isLive; + } + + private boolean isVerified; + @Override + public boolean isVerified() { + return this.isVerified; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/main/java/org/torproject/descriptor/impl/ServerDescriptorImpl.java new file mode 100644 index 0000000..1805dca --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/ServerDescriptorImpl.java @@ -0,0 +1,985 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +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 javax.xml.bind.DatatypeConverter; + +import org.torproject.descriptor.BandwidthHistory; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ServerDescriptor; + +/* Contains a server descriptor. */ +public abstract class ServerDescriptorImpl extends DescriptorImpl + implements ServerDescriptor { + + protected ServerDescriptorImpl(byte[] descriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(descriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseDescriptorBytes(); + this.calculateDigest(); + this.calculateDigestSha256(); + Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList( + "router,bandwidth,published".split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( + "identity-ed25519,master-key-ed25519,platform,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," + + "signing-key,ipv6-policy,ntor-onion-key,onion-key-crosscert," + + "ntor-onion-key-crosscert,tunnelled-dir-server," + + "router-sig-ed25519,router-signature,router-digest-sha256," + + "router-digest").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("router"); + if (this.getKeywordCount("accept") == 0 && + this.getKeywordCount("reject") == 0) { + throw new DescriptorParseException("Either keyword 'accept' or " + + "'reject' must be contained at least once."); + } + this.clearParsedKeywords(); + return; + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = ""; + List<String> cryptoLines = null; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("@")) { + continue; + } + String lineNoOpt = line.startsWith("opt ") ? + line.substring("opt ".length()) : line; + String[] partsNoOpt = lineNoOpt.split("[ \t]+"); + String keyword = partsNoOpt[0]; + switch (keyword) { + case "router": + this.parseRouterLine(line, lineNoOpt, partsNoOpt); + break; + case "or-address": + this.parseOrAddressLine(line, lineNoOpt, partsNoOpt); + break; + case "bandwidth": + this.parseBandwidthLine(line, lineNoOpt, partsNoOpt); + break; + case "platform": + this.parsePlatformLine(line, lineNoOpt, partsNoOpt); + break; + case "published": + this.parsePublishedLine(line, lineNoOpt, partsNoOpt); + break; + case "fingerprint": + this.parseFingerprintLine(line, lineNoOpt, partsNoOpt); + break; + case "hibernating": + this.parseHibernatingLine(line, lineNoOpt, partsNoOpt); + break; + case "uptime": + this.parseUptimeLine(line, lineNoOpt, partsNoOpt); + break; + case "onion-key": + this.parseOnionKeyLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "onion-key"; + break; + case "signing-key": + this.parseSigningKeyLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "signing-key"; + break; + case "accept": + this.parseAcceptLine(line, lineNoOpt, partsNoOpt); + break; + case "reject": + this.parseRejectLine(line, lineNoOpt, partsNoOpt); + break; + case "router-signature": + this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt); + nextCrypto = "router-signature"; + break; + case "contact": + this.parseContactLine(line, lineNoOpt, partsNoOpt); + break; + case "family": + this.parseFamilyLine(line, lineNoOpt, partsNoOpt); + break; + case "read-history": + this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "write-history": + this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt); + break; + case "eventdns": + this.parseEventdnsLine(line, lineNoOpt, partsNoOpt); + break; + case "caches-extra-info": + this.parseCachesExtraInfoLine(line, lineNoOpt, partsNoOpt); + break; + case "extra-info-digest": + this.parseExtraInfoDigestLine(line, lineNoOpt, partsNoOpt); + break; + case "hidden-service-dir": + this.parseHiddenServiceDirLine(line, lineNoOpt, partsNoOpt); + break; + case "protocols": + this.parseProtocolsLine(line, lineNoOpt, partsNoOpt); + break; + case "allow-single-hop-exits": + this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt); + break; + case "dircacheport": + this.parseDircacheportLine(line, lineNoOpt, partsNoOpt); + break; + case "router-digest": + this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt); + break; + case "router-digest-sha256": + this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt); + break; + case "ipv6-policy": + this.parseIpv6PolicyLine(line, lineNoOpt, partsNoOpt); + break; + case "ntor-onion-key": + this.parseNtorOnionKeyLine(line, lineNoOpt, partsNoOpt); + break; + case "identity-ed25519": + this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt); + nextCrypto = "identity-ed25519"; + break; + case "master-key-ed25519": + this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt); + break; + case "router-sig-ed25519": + this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt); + break; + case "onion-key-crosscert": + this.parseOnionKeyCrosscert(line, lineNoOpt, partsNoOpt); + nextCrypto = "onion-key-crosscert"; + break; + case "ntor-onion-key-crosscert": + this.parseNtorOnionKeyCrosscert(line, lineNoOpt, partsNoOpt); + nextCrypto = "ntor-onion-key-crosscert"; + break; + case "tunnelled-dir-server": + this.parseTunnelledDirServerLine(line, lineNoOpt, partsNoOpt); + break; + case "-----BEGIN": + cryptoLines = new ArrayList<>(); + cryptoLines.add(line); + break; + case "-----END": + cryptoLines.add(line); + StringBuilder sb = new StringBuilder(); + for (String cryptoLine : cryptoLines) { + sb.append("\n").append(cryptoLine); + } + String cryptoString = sb.toString().substring(1); + switch (nextCrypto) { + case "onion-key": + this.onionKey = cryptoString; + break; + case "signing-key": + this.signingKey = cryptoString; + break; + case "router-signature": + this.routerSignature = cryptoString; + break; + case "identity-ed25519": + this.identityEd25519 = cryptoString; + this.parseIdentityEd25519CryptoBlock(cryptoString); + break; + case "onion-key-crosscert": + this.onionKeyCrosscert = cryptoString; + break; + case "ntor-onion-key-crosscert": + this.ntorOnionKeyCrosscert = cryptoString; + break; + default: + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized crypto " + + "block '" + cryptoString + "' in server descriptor."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.addAll(cryptoLines); + } + } + cryptoLines = null; + nextCrypto = ""; + break; + default: + if (cryptoLines != null) { + cryptoLines.add(line); + } else { + ParseHelper.parseKeyword(line, partsNoOpt[0]); + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in server descriptor."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + } + + private void parseRouterLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 6) { + throw new DescriptorParseException("Illegal line '" + line + + "' in server descriptor."); + } + this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]); + this.address = ParseHelper.parseIpv4Address(line, partsNoOpt[2]); + this.orPort = ParseHelper.parsePort(line, partsNoOpt[3]); + this.socksPort = ParseHelper.parsePort(line, partsNoOpt[4]); + this.dirPort = ParseHelper.parsePort(line, partsNoOpt[5]); + } + + private void parseOrAddressLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + /* TODO Add more checks. */ + /* TODO Add tests. */ + this.orAddresses.add(partsNoOpt[1]); + } + + private void parseBandwidthLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length < 3 || partsNoOpt.length > 4) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + boolean isValid = false; + try { + this.bandwidthRate = Integer.parseInt(partsNoOpt[1]); + this.bandwidthBurst = Integer.parseInt(partsNoOpt[2]); + 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. */ + } + if (!isValid) { + throw new DescriptorParseException("Illegal values in line '" + line + + "'."); + } + } + + private void parsePlatformLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (lineNoOpt.length() > "platform ".length()) { + this.platform = lineNoOpt.substring("platform ".length()); + } else { + this.platform = ""; + } + } + + private void parsePublishedLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, + partsNoOpt, 1, 2); + } + + private void parseFingerprintLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (lineNoOpt.length() != "fingerprint".length() + 5 * 10) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + lineNoOpt.substring("fingerprint ".length()).replaceAll(" ", "")); + } + + private void parseHibernatingLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.hibernating = ParseHelper.parseBoolean(partsNoOpt[1], line); + } + + private void parseUptimeLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Wrong number of values in line " + + "'" + line + "'."); + } + boolean isValid = false; + try { + this.uptime = Long.parseLong(partsNoOpt[1]); + isValid = true; + } catch (NumberFormatException e) { + /* Handle below. */ + } + if (!isValid) { + throw new DescriptorParseException("Illegal value in line '" + line + + "'."); + } + } + + private void parseOnionKeyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("onion-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseSigningKeyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("signing-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseAcceptLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt); + } + + private void parseRejectLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt); + } + + private void parseExitPolicyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + ParseHelper.parseExitPattern(line, partsNoOpt[1]); + this.exitPolicyLines.add(lineNoOpt); + } + + private void parseRouterSignatureLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("router-signature")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseContactLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (lineNoOpt.length() > "contact ".length()) { + this.contact = lineNoOpt.substring("contact ".length()); + } else { + this.contact = ""; + } + } + + private void parseFamilyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + String[] familyEntries = new String[partsNoOpt.length - 1]; + for (int i = 1; i < partsNoOpt.length; i++) { + if (partsNoOpt[i].startsWith("$")) { + if (partsNoOpt[i].contains("=") ^ partsNoOpt[i].contains("~")) { + String separator = partsNoOpt[i].contains("=") ? "=" : "~"; + String fingerprint = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[i].substring(1, partsNoOpt[i].indexOf( + separator))); + String nickname = ParseHelper.parseNickname(line, + partsNoOpt[i].substring(partsNoOpt[i].indexOf( + separator) + 1)); + familyEntries[i - 1] = "$" + fingerprint + separator + nickname; + } else { + familyEntries[i - 1] = "$" + + ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[i].substring(1)); + } + } else { + familyEntries[i - 1] = ParseHelper.parseNickname(line, + partsNoOpt[i]); + } + } + this.familyEntries = familyEntries; + } + + private void parseReadHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.readHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseWriteHistoryLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + this.writeHistory = new BandwidthHistoryImpl(line, lineNoOpt, + partsNoOpt); + } + + private void parseEventdnsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.usesEnhancedDnsLogic = ParseHelper.parseBoolean(partsNoOpt[1], line); + } + + private void parseCachesExtraInfoLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("caches-extra-info")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.cachesExtraInfo = true; + } + + private void parseExtraInfoDigestLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length < 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line, + partsNoOpt[1]); + if (partsNoOpt.length >= 3) { + ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[2]); + this.extraInfoDigestSha256 = partsNoOpt[2]; + } + } + + private void parseHiddenServiceDirLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length == 1) { + this.hiddenServiceDirVersions = new Integer[] { 2 }; + } else { + try { + Integer[] result = new Integer[partsNoOpt.length - 1]; + for (int i = 1; i < partsNoOpt.length; i++) { + result[i - 1] = Integer.parseInt(partsNoOpt[i]); + } + this.hiddenServiceDirVersions = result; + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal value in line '" + + line + "'."); + } + } + } + + private void parseProtocolsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + int linkIndex = -1, circuitIndex = -1; + for (int i = 1; i < partsNoOpt.length; i++) { + switch (partsNoOpt[i]) { + case "Link": + linkIndex = i; + break; + case "Circuit": + circuitIndex = i; + break; + default: + // empty + } + } + if (linkIndex < 0 || circuitIndex < 0 || circuitIndex < linkIndex) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + try { + Integer[] linkProtocolVersions = + new Integer[circuitIndex - linkIndex - 1]; + for (int i = linkIndex + 1, j = 0; i < circuitIndex; i++, j++) { + linkProtocolVersions[j] = Integer.parseInt(partsNoOpt[i]); + } + Integer[] circuitProtocolVersions = + new Integer[partsNoOpt.length - circuitIndex - 1]; + for (int i = circuitIndex + 1, j = 0; i < partsNoOpt.length; + i++, j++) { + circuitProtocolVersions[j] = Integer.parseInt(partsNoOpt[i]); + } + this.linkProtocolVersions = linkProtocolVersions; + this.circuitProtocolVersions = circuitProtocolVersions; + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseAllowSingleHopExitsLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("allow-single-hop-exits")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + 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 parseRouterDigestLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.serverDescriptorDigest = ParseHelper.parseTwentyByteHexString( + line, partsNoOpt[1]); + } + + private void parseIpv6PolicyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + boolean isValid = true; + if (partsNoOpt.length != 3) { + isValid = false; + } else { + switch (partsNoOpt[1]) { + case "accept": + case "reject": + this.ipv6DefaultPolicy = partsNoOpt[1]; + this.ipv6PortList = partsNoOpt[2]; + String[] ports = partsNoOpt[2].split(",", -1); + for (int i = 0; i < ports.length; i++) { + if (ports[i].length() < 1) { + isValid = false; + break; + } + } + break; + default: + isValid = false; + } + } + if (!isValid) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseNtorOnionKeyLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.ntorOnionKey = partsNoOpt[1].replaceAll("=", ""); + } + + private void parseIdentityEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 1) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseOnionKeyCrosscert(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 1) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseNtorOnionKeyCrosscert(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + try { + this.ntorOnionKeyCrosscertSign = Integer.parseInt(partsNoOpt[1]); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseTunnelledDirServerLine(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (!lineNoOpt.equals("tunnelled-dir-server")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.tunnelledDirServer = true; + } + + private void parseIdentityEd25519CryptoBlock(String cryptoString) + throws DescriptorParseException { + String masterKeyEd25519FromIdentityEd25519 = + ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( + cryptoString); + if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals( + masterKeyEd25519FromIdentityEd25519)) { + throw new DescriptorParseException("Mismatch between " + + "identity-ed25519 and master-key-ed25519."); + } + this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519; + } + + private void parseMasterKeyEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1]; + if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals( + masterKeyEd25519FromMasterKeyEd25519Line)) { + throw new DescriptorParseException("Mismatch between " + + "identity-ed25519 and master-key-ed25519."); + } + this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line; + } + + private void parseRouterSigEd25519Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + this.routerSignatureEd25519 = partsNoOpt[1]; + } + + private void parseRouterDigestSha256Line(String line, String lineNoOpt, + String[] partsNoOpt) throws DescriptorParseException { + if (partsNoOpt.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]); + this.serverDescriptorDigestSha256 = partsNoOpt[1]; + } + + private void calculateDigest() throws DescriptorParseException { + if (this.serverDescriptorDigest != null) { + /* We already learned the descriptor digest of this bridge + * descriptor from a "router-digest" line. */ + return; + } + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "router "; + String sigToken = "\nrouter-signature\n"; + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, + forDigest, 0, sig - start); + this.serverDescriptorDigest = DatatypeConverter.printHexBinary( + MessageDigest.getInstance("SHA-1").digest(forDigest)). + toLowerCase(); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.serverDescriptorDigest == null) { + throw new DescriptorParseException("Could not calculate server " + + "descriptor digest."); + } + } + + private void calculateDigestSha256() throws DescriptorParseException { + if (this.serverDescriptorDigestSha256 != null) { + /* We already learned the descriptor digest of this bridge + * descriptor from a "router-digest-sha256" line. */ + return; + } + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "router "; + String sigToken = "\n-----END SIGNATURE-----\n"; + int start = ascii.indexOf(startToken); + int sig = ascii.indexOf(sigToken) + sigToken.length(); + if (start >= 0 && sig >= 0 && sig > start) { + byte[] forDigest = new byte[sig - start]; + System.arraycopy(this.getRawDescriptorBytes(), start, forDigest, + 0, sig - start); + this.serverDescriptorDigestSha256 = + DatatypeConverter.printBase64Binary( + MessageDigest.getInstance("SHA-256").digest(forDigest)). + replaceAll("=", ""); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } catch (NoSuchAlgorithmException e) { + /* Handle below. */ + } + if (this.serverDescriptorDigestSha256 == null) { + throw new DescriptorParseException("Could not calculate server " + + "descriptor SHA-256 digest."); + } + } + + private String serverDescriptorDigest; + @Override + public String getServerDescriptorDigest() { + return this.serverDescriptorDigest; + } + + private String serverDescriptorDigestSha256; + @Override + public String getServerDescriptorDigestSha256() { + return this.serverDescriptorDigestSha256; + } + + private String nickname; + @Override + public String getNickname() { + return this.nickname; + } + + private String address; + @Override + public String getAddress() { + return this.address; + } + + private int orPort; + @Override + public int getOrPort() { + return this.orPort; + } + + private int socksPort; + @Override + public int getSocksPort() { + return this.socksPort; + } + + private int dirPort; + @Override + public int getDirPort() { + return this.dirPort; + } + + private List<String> orAddresses = new ArrayList<>(); + @Override + public List<String> getOrAddresses() { + return new ArrayList<>(this.orAddresses); + } + + private int bandwidthRate; + @Override + public int getBandwidthRate() { + return this.bandwidthRate; + } + + private int bandwidthBurst; + @Override + public int getBandwidthBurst() { + return this.bandwidthBurst; + } + + private int bandwidthObserved; + @Override + public int getBandwidthObserved() { + return this.bandwidthObserved; + } + + private String platform; + @Override + public String getPlatform() { + return this.platform; + } + + private long publishedMillis; + @Override + public long getPublishedMillis() { + return this.publishedMillis; + } + + private String fingerprint; + @Override + public String getFingerprint() { + return this.fingerprint; + } + + private boolean hibernating; + @Override + public boolean isHibernating() { + return this.hibernating; + } + + private Long uptime; + @Override + public Long getUptime() { + return this.uptime; + } + + private String onionKey; + @Override + public String getOnionKey() { + return this.onionKey; + } + + private String signingKey; + @Override + public String getSigningKey() { + return this.signingKey; + } + + private List<String> exitPolicyLines = new ArrayList<>(); + @Override + public List<String> getExitPolicyLines() { + return new ArrayList<>(this.exitPolicyLines); + } + + private String routerSignature; + @Override + public String getRouterSignature() { + return this.routerSignature; + } + + private String contact; + @Override + public String getContact() { + return this.contact; + } + + private String[] familyEntries; + @Override + public List<String> getFamilyEntries() { + return this.familyEntries == null ? null : + Arrays.asList(this.familyEntries); + } + + private BandwidthHistory readHistory; + @Override + public BandwidthHistory getReadHistory() { + return this.readHistory; + } + + private BandwidthHistory writeHistory; + @Override + public BandwidthHistory getWriteHistory() { + return this.writeHistory; + } + + private boolean usesEnhancedDnsLogic; + @Override + public boolean getUsesEnhancedDnsLogic() { + return this.usesEnhancedDnsLogic; + } + + private boolean cachesExtraInfo; + @Override + public boolean getCachesExtraInfo() { + return this.cachesExtraInfo; + } + + private String extraInfoDigest; + @Override + public String getExtraInfoDigest() { + return this.extraInfoDigest; + } + + private String extraInfoDigestSha256; + @Override + public String getExtraInfoDigestSha256() { + return this.extraInfoDigestSha256; + } + + private Integer[] hiddenServiceDirVersions; + @Override + public List<Integer> getHiddenServiceDirVersions() { + return this.hiddenServiceDirVersions == null ? null : + Arrays.asList(this.hiddenServiceDirVersions); + } + + private Integer[] linkProtocolVersions; + @Override + public List<Integer> getLinkProtocolVersions() { + return this.linkProtocolVersions == null ? null : + Arrays.asList(this.linkProtocolVersions); + } + + private Integer[] circuitProtocolVersions; + @Override + public List<Integer> getCircuitProtocolVersions() { + return this.circuitProtocolVersions == null ? null : + Arrays.asList(this.circuitProtocolVersions); + } + + private boolean allowSingleHopExits; + @Override + public boolean getAllowSingleHopExits() { + return this.allowSingleHopExits; + } + + private String ipv6DefaultPolicy; + @Override + public String getIpv6DefaultPolicy() { + return this.ipv6DefaultPolicy; + } + + private String ipv6PortList; + @Override + public String getIpv6PortList() { + return this.ipv6PortList; + } + + private String ntorOnionKey; + @Override + public String getNtorOnionKey() { + return this.ntorOnionKey; + } + + private String identityEd25519; + @Override + public String getIdentityEd25519() { + return this.identityEd25519; + } + + private String masterKeyEd25519; + @Override + public String getMasterKeyEd25519() { + return this.masterKeyEd25519; + } + + private String routerSignatureEd25519; + @Override + public String getRouterSignatureEd25519() { + return this.routerSignatureEd25519; + } + + private String onionKeyCrosscert; + @Override + public String getOnionKeyCrosscert() { + return this.onionKeyCrosscert; + } + + private String ntorOnionKeyCrosscert; + @Override + public String getNtorOnionKeyCrosscert() { + return this.ntorOnionKeyCrosscert; + } + + private int ntorOnionKeyCrosscertSign = -1; + @Override + public int getNtorOnionKeyCrosscertSign() { + return ntorOnionKeyCrosscertSign; + } + + private boolean tunnelledDirServer; + @Override + public boolean getTunnelledDirServer() { + return this.tunnelledDirServer; + } +} + diff --git a/src/main/java/org/torproject/descriptor/impl/TorperfResultImpl.java b/src/main/java/org/torproject/descriptor/impl/TorperfResultImpl.java new file mode 100644 index 0000000..0800de0 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/impl/TorperfResultImpl.java @@ -0,0 +1,546 @@ +/* Copyright 2012--2016 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +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 java.util.SortedMap; +import java.util.TreeMap; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.TorperfResult; + +public class TorperfResultImpl extends DescriptorImpl + implements TorperfResult { + + protected static List<Descriptor> parseTorperfResults( + byte[] rawDescriptorBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + if (rawDescriptorBytes.length == 0) { + throw new DescriptorParseException("Descriptor is empty."); + } + List<Descriptor> parsedDescriptors = new ArrayList<>(); + String descriptorString = new String(rawDescriptorBytes); + Scanner s = new Scanner(descriptorString).useDelimiter("\r?\n"); + String typeAnnotation = ""; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("@type torperf ")) { + String[] parts = line.split(" "); + if (parts.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + + "'."); + } + String version = parts[2]; + if (!version.startsWith("1.")) { + throw new DescriptorParseException("Unsupported version in " + + " line '" + line + "'."); + } + typeAnnotation = line + "\n"; + } else { + parsedDescriptors.add(new TorperfResultImpl( + (typeAnnotation + line).getBytes(), + failUnrecognizedDescriptorLines)); + typeAnnotation = ""; + } + } + return parsedDescriptors; + } + + protected TorperfResultImpl(byte[] rawDescriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); + this.parseTorperfResultLine(new String(rawDescriptorBytes)); + } + + private void parseTorperfResultLine(String inputLine) + throws DescriptorParseException { + String line = inputLine; + while (line.startsWith("@") && line.contains("\n")) { + line = line.split("\n")[1]; + } + if (line.isEmpty()) { + throw new DescriptorParseException("Blank lines are not allowed."); + } + String[] parts = line.split(" "); + for (int i = 0; i < parts.length; i++) { + String keyValue = parts[i]; + String[] keyValueParts = keyValue.split("="); + if (keyValueParts.length != 2) { + throw new DescriptorParseException("Illegal key-value pair in " + + "line '" + line + "'."); + } + String key = keyValueParts[0]; + this.markKeyAsParsed(key, line); + String value = keyValueParts[1]; + switch (key) { + case "SOURCE": + this.parseSource(value, keyValue, line); + break; + case "FILESIZE": + this.parseFileSize(value, keyValue, line); + break; + case "START": + this.parseStart(value, keyValue, line); + break; + case "SOCKET": + this.parseSocket(value, keyValue, line); + break; + case "CONNECT": + this.parseConnect(value, keyValue, line); + break; + case "NEGOTIATE": + this.parseNegotiate(value, keyValue, line); + break; + case "REQUEST": + this.parseRequest(value, keyValue, line); + break; + case "RESPONSE": + this.parseResponse(value, keyValue, line); + break; + case "DATAREQUEST": + this.parseDataRequest(value, keyValue, line); + break; + case "DATARESPONSE": + this.parseDataResponse(value, keyValue, line); + break; + case "DATACOMPLETE": + this.parseDataComplete(value, keyValue, line); + break; + case "WRITEBYTES": + this.parseWriteBytes(value, keyValue, line); + break; + case "READBYTES": + this.parseReadBytes(value, keyValue, line); + break; + case "DIDTIMEOUT": + this.parseDidTimeout(value, keyValue, line); + break; + case "LAUNCH": + this.parseLaunch(value, keyValue, line); + break; + case "USED_AT": + this.parseUsedAt(value, keyValue, line); + break; + case "PATH": + this.parsePath(value, keyValue, line); + break; + case "BUILDTIMES": + this.parseBuildTimes(value, keyValue, line); + break; + case "TIMEOUT": + this.parseTimeout(value, keyValue, line); + break; + case "QUANTILE": + this.parseQuantile(value, keyValue, line); + break; + case "CIRC_ID": + this.parseCircId(value, keyValue, line); + break; + case "USED_BY": + this.parseUsedBy(value, keyValue, line); + break; + default: + if (key.startsWith("DATAPERC")) { + this.parseDataPercentile(value, keyValue, line); + } else if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized key '" + key + + "' in line '" + line + "'."); + } else { + if (this.unrecognizedKeys == null) { + this.unrecognizedKeys = new TreeMap<>(); + } + this.unrecognizedKeys.put(key, value); + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<>(); + } + if (!this.unrecognizedLines.contains(line)) { + this.unrecognizedLines.add(line); + } + } + } + } + this.checkAllRequiredKeysParsed(line); + } + + private Set<String> parsedKeys = new HashSet<>(); + private Set<String> requiredKeys = new HashSet<>(Arrays.asList( + ("SOURCE,FILESIZE,START,SOCKET,CONNECT,NEGOTIATE,REQUEST,RESPONSE," + + "DATAREQUEST,DATARESPONSE,DATACOMPLETE,WRITEBYTES,READBYTES"). + split(","))); + private void markKeyAsParsed(String key, String line) + throws DescriptorParseException { + if (this.parsedKeys.contains(key)) { + throw new DescriptorParseException("Key '" + key + "' is contained " + + "at least twice in line '" + line + "', but must be " + + "contained at most once."); + } + this.parsedKeys.add(key); + this.requiredKeys.remove(key); + } + private void checkAllRequiredKeysParsed(String line) + throws DescriptorParseException { + for (String key : this.requiredKeys) { + throw new DescriptorParseException("Key '" + key + "' is contained " + + "contained 0 times in line '" + line + "', but must be " + + "contained exactly once."); + } + } + + private void parseSource(String value, String keyValue, String line) + throws DescriptorParseException { + this.source = value; + } + + private void parseFileSize(String value, String keyValue, String line) + throws DescriptorParseException { + try { + this.fileSize = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal value in '" + keyValue + + "' in line '" + line + "'."); + } + } + + private void parseStart(String value, String keyValue, String line) + throws DescriptorParseException { + this.startMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseSocket(String value, String keyValue, String line) + throws DescriptorParseException { + this.socketMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseConnect(String value, String keyValue, String line) + throws DescriptorParseException { + this.connectMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseNegotiate(String value, String keyValue, String line) + throws DescriptorParseException { + this.negotiateMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseRequest(String value, String keyValue, String line) + throws DescriptorParseException { + this.requestMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseResponse(String value, String keyValue, String line) + throws DescriptorParseException { + this.responseMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseDataRequest(String value, String keyValue, + String line) throws DescriptorParseException { + this.dataRequestMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseDataResponse(String value, String keyValue, + String line) throws DescriptorParseException { + this.dataResponseMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseDataComplete(String value, String keyValue, + String line) throws DescriptorParseException { + this.dataCompleteMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseWriteBytes(String value, String keyValue, String line) + throws DescriptorParseException { + this.writeBytes = parseInt(value, keyValue, line); + } + + private void parseReadBytes(String value, String keyValue, String line) + throws DescriptorParseException { + this.readBytes = parseInt(value, keyValue, line); + } + + private void parseDidTimeout(String value, String keyValue, String line) + throws DescriptorParseException { + if (value.equals("1")) { + this.didTimeout = true; + } else if (value.equals("0")) { + this.didTimeout = false; + } else { + throw new DescriptorParseException("Illegal value in '" + keyValue + + "' in line '" + line + "'."); + } + } + + private void parseDataPercentile(String value, String keyValue, + String line) throws DescriptorParseException { + String key = keyValue.substring(0, keyValue.indexOf("=")); + String percentileString = key.substring("DATAPERC".length()); + int percentile = -1; + try { + percentile = Integer.parseInt(percentileString); + } catch (NumberFormatException e) { + /* Treat key as unrecognized below. */ + percentile = -1; + } + if (percentile < 0 || percentile > 100) { + if (this.unrecognizedKeys == null) { + this.unrecognizedKeys = new TreeMap<>(); + } + this.unrecognizedKeys.put(key, value); + } else { + long timestamp = this.parseTimestamp(value, keyValue, line); + if (this.dataPercentiles == null) { + this.dataPercentiles = new TreeMap<>(); + } + this.dataPercentiles.put(percentile, timestamp); + } + } + + private void parseLaunch(String value, String keyValue, String line) + throws DescriptorParseException { + this.launchMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parseUsedAt(String value, String keyValue, String line) + throws DescriptorParseException { + this.usedAtMillis = this.parseTimestamp(value, keyValue, line); + } + + private void parsePath(String value, String keyValue, String line) + throws DescriptorParseException { + String[] valueParts = value.split(","); + String[] result = new String[valueParts.length]; + for (int i = 0; i < valueParts.length; i++) { + if (valueParts[i].length() != 41) { + throw new DescriptorParseException("Illegal value in '" + keyValue + + "' in line '" + line + "'."); + } + result[i] = ParseHelper.parseTwentyByteHexString(line, + valueParts[i].substring(1)); + } + this.path = result; + } + + private void parseBuildTimes(String value, String keyValue, String line) + throws DescriptorParseException { + String[] valueParts = value.split(","); + Long[] result = new Long[valueParts.length]; + for (int i = 0; i < valueParts.length; i++) { + result[i] = this.parseTimestamp(valueParts[i], keyValue, line); + } + this.buildTimes = result; + } + + private void parseTimeout(String value, String keyValue, String line) + throws DescriptorParseException { + this.timeout = this.parseInt(value, keyValue, line); + } + + private void parseQuantile(String value, String keyValue, String line) + throws DescriptorParseException { + this.quantile = this.parseDouble(value, keyValue, line); + } + + private void parseCircId(String value, String keyValue, String line) + throws DescriptorParseException { + this.circId = this.parseInt(value, keyValue, line); + } + + private void parseUsedBy(String value, String keyValue, String line) + throws DescriptorParseException { + this.usedBy = this.parseInt(value, keyValue, line); + } + + private long parseTimestamp(String value, String keyValue, String line) + throws DescriptorParseException { + long timestamp = -1L; + if (value.contains(".") && value.split("\\.").length == 2) { + String zeroPaddedValue = (value + "000"); + String threeDecimalPlaces = zeroPaddedValue.substring(0, + zeroPaddedValue.indexOf(".") + 4); + String millisString = threeDecimalPlaces.replaceAll("\\.", ""); + try { + timestamp = Long.parseLong(millisString); + } catch (NumberFormatException e) { + /* Handle below. */ + } + } + if (timestamp < 0L) { + throw new DescriptorParseException("Illegal timestamp '" + value + + "' in '" + keyValue + "' in line '" + line + "'."); + } + return timestamp; + } + + private int parseInt(String value, String keyValue, String line) + throws DescriptorParseException { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal value in '" + keyValue + + "' in line '" + line + "'."); + } + } + + private double parseDouble(String value, String keyValue, String line) + throws DescriptorParseException { + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + throw new DescriptorParseException("Illegal value in '" + keyValue + + "' in line '" + line + "'."); + } + } + + private SortedMap<String, String> unrecognizedKeys; + @Override + public SortedMap<String, String> getUnrecognizedKeys() { + return this.unrecognizedKeys == null ? null + : new TreeMap<>(this.unrecognizedKeys); + } + + private String source; + @Override + public String getSource() { + return this.source; + } + + private int fileSize; + @Override + public int getFileSize() { + return this.fileSize; + } + + private long startMillis; + @Override + public long getStartMillis() { + return this.startMillis; + } + + private long socketMillis; + @Override + public long getSocketMillis() { + return this.socketMillis; + } + + private long connectMillis; + @Override + public long getConnectMillis() { + return this.connectMillis; + } + + private long negotiateMillis; + @Override + public long getNegotiateMillis() { + return this.negotiateMillis; + } + + private long requestMillis; + @Override + public long getRequestMillis() { + return this.requestMillis; + } + + private long responseMillis; + @Override + public long getResponseMillis() { + return this.responseMillis; + } + + private long dataRequestMillis; + @Override + public long getDataRequestMillis() { + return this.dataRequestMillis; + } + + private long dataResponseMillis; + @Override + public long getDataResponseMillis() { + return this.dataResponseMillis; + } + + private long dataCompleteMillis; + @Override + public long getDataCompleteMillis() { + return this.dataCompleteMillis; + } + + private int writeBytes; + @Override + public int getWriteBytes() { + return this.writeBytes; + } + + private int readBytes; + @Override + public int getReadBytes() { + return this.readBytes; + } + + private boolean didTimeout; + @Override + public Boolean didTimeout() { + return this.didTimeout; + } + + private SortedMap<Integer, Long> dataPercentiles; + @Override + public SortedMap<Integer, Long> getDataPercentiles() { + return this.dataPercentiles == null ? null + : new TreeMap<>(this.dataPercentiles); + } + + private long launchMillis = -1L; + @Override + public long getLaunchMillis() { + return this.launchMillis; + } + + private long usedAtMillis = -1L; + @Override + public long getUsedAtMillis() { + return this.usedAtMillis; + } + + private String[] path; + @Override + public List<String> getPath() { + return this.path == null ? null : Arrays.asList(this.path); + } + + private Long[] buildTimes; + @Override + public List<Long> getBuildTimes() { + return this.buildTimes == null ? null : + Arrays.asList(this.buildTimes); + } + + private long timeout = -1L; + @Override + public long getTimeout() { + return this.timeout; + } + + private double quantile = -1.0; + @Override + public double getQuantile() { + return this.quantile; + } + + private int circId = -1; + @Override + public int getCircId() { + return this.circId; + } + + private int usedBy = -1; + @Override + public int getUsedBy() { + return this.usedBy; + } +} + diff --git a/src/main/java/org/torproject/descriptor/package-info.java b/src/main/java/org/torproject/descriptor/package-info.java new file mode 100644 index 0000000..5b34554 --- /dev/null +++ b/src/main/java/org/torproject/descriptor/package-info.java @@ -0,0 +1,80 @@ +/* Copyright 2016 The Tor Project + * See LICENSE for licensing information */ + +/** + * Interfaces and essential classes for obtaining and processing Tor + * descriptors. + * + * <p>This package contains all relevant interfaces and + * classes that an application would need to use this library. + * Applications are strongly discouraged from accessing types from the + * implementation package ({@code org.torproject.descriptor.impl}) + * directly, because those may change without prior notice.</p> + * + * <p>Interfaces and classes in this package can be grouped into + * general-purpose types to obtain and process any type of descriptor and + * descriptors produced by different components of the Tor network:</p> + * + * <ol> + * <li>General-purpose types comprise + * {@link org.torproject.descriptor.DescriptorSourceFactory} which is the + * main entry point into using this library. This factory is used to + * create the descriptor sources for obtaining remote descriptor data + * ({@link org.torproject.descriptor.DescriptorDownloader} and + * {@link org.torproject.descriptor.DescriptorCollector}) and descriptor + * sources for processing local descriptor data + * ({@link org.torproject.descriptor.DescriptorReader} and + * {@link org.torproject.descriptor.DescriptorParser}). General-purpose + * types also include descriptor containers + * ({@link org.torproject.descriptor.DescriptorRequest} and + * {@link org.torproject.descriptor.DescriptorFile}) and the + * superinterface for all provided descriptors + * ({@link org.torproject.descriptor.Descriptor}).</li> + * + * <li>The first group of descriptors is published by relays and servers + * in the Tor network. These interfaces include server descriptors + * ({@link org.torproject.descriptor.ServerDescriptor} with subinterfaces + * {@link org.torproject.descriptor.RelayServerDescriptor} and + * {@link org.torproject.descriptor.BridgeServerDescriptor}), extra-info + * descriptors ({@link org.torproject.descriptor.ExtraInfoDescriptor} with + * subinterfaces + * {@link org.torproject.descriptor.RelayExtraInfoDescriptor} and + * {@link org.torproject.descriptor.BridgeExtraInfoDescriptor}), + * microdescriptors which are derived from server descriptors by the + * directory authorities + * ({@link org.torproject.descriptor.Microdescriptor}), and helper types + * for parts of the aforementioned descriptors + * ({@link org.torproject.descriptor.BandwidthHistory}).</li> + * + * <li>The second group of descriptors is generated by authoritative + * directory servers that form an opinion about relays and bridges in the + * Tor network. These include descriptors specified in version 3 of the + * directory protocol + * ({@link org.torproject.descriptor.RelayNetworkStatusConsensus}, + * {@link org.torproject.descriptor.RelayNetworkStatusVote}, + * {@link org.torproject.descriptor.DirectoryKeyCertificate}, and helper + * types for descriptor parts + * {@link org.torproject.descriptor.DirSourceEntry}, + * {@link org.torproject.descriptor.NetworkStatusEntry}, and + * {@link org.torproject.descriptor.DirectorySignature}), descriptors from + * earlier directory protocol version 2 + * ({@link org.torproject.descriptor.RelayNetworkStatus}) and version 1 + * ({@link org.torproject.descriptor.RelayDirectory} and + * {@link org.torproject.descriptor.RouterStatusEntry}), as well as + * descriptors published by the bridge authority and sanitized by the + * CollecTor service + * ({@link org.torproject.descriptor.BridgeNetworkStatus}).</li> + * + * <li>The third group of descriptors is created by auxiliary services + * connected to the Tor network rather than by the Tor software. This + * group comprises descriptors by the bridge distribution service BridgeDB + * ({@link org.torproject.descriptor.BridgePoolAssignment}), the exit list + * service TorDNSEL ({@link org.torproject.descriptor.ExitList}), and the + * performance measurement service Torperf + * ({@link org.torproject.descriptor.TorperfResult}).</li> + * </ol> + * + * @since 1.0.0 + */ +package org.torproject.descriptor; + diff --git a/src/org/torproject/descriptor/BandwidthHistory.java b/src/org/torproject/descriptor/BandwidthHistory.java deleted file mode 100644 index 0be1a53..0000000 --- a/src/org/torproject/descriptor/BandwidthHistory.java +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.SortedMap; - -/** - * Contains the bandwidth history of a relay or bridge. - * - * <p>A bandwidth history is not a descriptor type of its own but usually - * part of extra-info descriptors ({@link ExtraInfoDescriptor}) or server - * descriptors ({@link ServerDescriptor}).</p> - * - * @since 1.0.0 - */ -public interface BandwidthHistory { - - /** - * Return the original bandwidth history line as contained in the - * descriptor, possibly prefixed with {@code "opt "}. - * - * @since 1.0.0 - */ - public String getLine(); - - /** - * Return the time in milliseconds since the epoch when the most recent - * interval ends. - * - * @since 1.0.0 - */ - public long getHistoryEndMillis(); - - /** - * Return the interval length in seconds. - * - * @since 1.0.0 - */ - public long getIntervalLength(); - - /** - * Return the (possibly empty) bandwidth history with map keys being - * times in milliseconds since the epoch when intervals end and map - * values being number of bytes used in the interval, ordered from - * oldest to newest interval. - * - * @since 1.0.0 - */ - public SortedMap<Long, Long> getBandwidthValues(); -} - diff --git a/src/org/torproject/descriptor/BridgeExtraInfoDescriptor.java b/src/org/torproject/descriptor/BridgeExtraInfoDescriptor.java deleted file mode 100644 index a3c168d..0000000 --- a/src/org/torproject/descriptor/BridgeExtraInfoDescriptor.java +++ /dev/null @@ -1,25 +0,0 @@ -/* Copyright 2015--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a sanitized bridge extra-info descriptor. - * - * <p>Sanitized bridge extra-info descriptors share many contents with - * relay extra-info descriptors ({@link RelayExtraInfoDescriptor}), which - * is why they share a common - * superinterface ({@link ExtraInfoDescriptor}). The main purpose of - * having two subinterfaces is being able to distinguish descriptor types - * more easily.</p> - * - * <p>Details about sanitizing bridge extra-info descriptors can be found - * <a href="https://collector.torproject.org/#type-bridge-extra-info">here</a>. - * </p> - * - * @since 1.1.0 - */ -public interface BridgeExtraInfoDescriptor extends ExtraInfoDescriptor { - -} - diff --git a/src/org/torproject/descriptor/BridgeNetworkStatus.java b/src/org/torproject/descriptor/BridgeNetworkStatus.java deleted file mode 100644 index c7458fd..0000000 --- a/src/org/torproject/descriptor/BridgeNetworkStatus.java +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.SortedMap; - -/** - * Contains a sanitized bridge network status document. - * - * <p>The bridge directory authority periodically publishes a network - * status document with one entry per known bridge in the network - * ({@link NetworkStatusEntry}) containing: a hash of its identity key, a - * hash of its most recent server descriptor, and a summary of what the - * bridge authority believed about its status.</p> - * - * <p>The main purpose of this document is to get an authoritative list of - * running bridges to the bridge distribution service BridgeDB.</p> - * - * <p>Details about sanitizing bridge network statuses can be found - * <a href="https://collector.torproject.org/#type-bridge-network-status">here</a>. - * </p> - * - * @since 1.0.0 - */ -public interface BridgeNetworkStatus extends Descriptor { - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the minimum uptime in seconds that this authority requires - * for assigning the Stable flag, or -1 if the authority doesn't report - * this value. - * - * @since 1.1.0 - */ - public long getStableUptime(); - - /** - * Return the minimum MTBF (mean time between failure) that this - * authority requires for assigning the Stable flag, or -1 if the - * authority doesn't report this value. - * - * @since 1.1.0 - */ - public long getStableMtbf(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Fast flag, or -1 if the authority doesn't report this - * value. - * - * @since 1.1.0 - */ - public long getFastBandwidth(); - - /** - * Return the minimum WFU (weighted fractional uptime) in percent that - * this authority requires for assigning the Guard flag, or -1 if the - * authority doesn't report this value. - * - * @since 1.1.0 - */ - public double getGuardWfu(); - - /** - * Return the minimum weighted time in seconds that this authority - * needs to know about a relay before assigning the Guard flag, or -1 if - * the authority doesn't report this information. - * - * @since 1.1.0 - */ - public long getGuardTk(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Guard flag if exits can be guards, or -1 if the - * authority doesn't report this value. - * - * @since 1.1.0 - */ - public long getGuardBandwidthIncludingExits(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Guard flag if exits can not be guards, or -1 if the - * authority doesn't report this value. - * - * @since 1.1.0 - */ - public long getGuardBandwidthExcludingExits(); - - /** - * Return 1 if the authority has measured enough MTBF info to use the - * MTBF requirement instead of the uptime requirement for assigning the - * Stable flag, 0 if not, or -1 if the authority doesn't report this - * information. - * - * @since 1.1.0 - */ - public int getEnoughMtbfInfo(); - - /** - * Return 1 if the authority has enough measured bandwidths that it'll - * ignore the advertised bandwidth claims of routers without measured - * bandwidth, 0 if not, or -1 if the authority doesn't report this - * information. - * - * @since 1.1.0 - */ - public int getIgnoringAdvertisedBws(); - - /** - * Return status entries for each contained bridge, with map keys being - * SHA-1 digests of SHA-1 digest of the bridges' public identity keys, - * encoded as 40 upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public SortedMap<String, NetworkStatusEntry> getStatusEntries(); -} - diff --git a/src/org/torproject/descriptor/BridgePoolAssignment.java b/src/org/torproject/descriptor/BridgePoolAssignment.java deleted file mode 100644 index 2de4ee9..0000000 --- a/src/org/torproject/descriptor/BridgePoolAssignment.java +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.SortedMap; - -/** - * Contains a sanitized list of bridges together with the distribution - * pools they have been assigned to by the bridge distribution service - * BridgeDB. - * - * <p>BridgeDB receives bridge network statuses - * ({@link BridgeNetworkStatus}) from the bridge authority, assigns these - * bridges to persistent distribution rings, and hands them out to bridge - * users. BridgeDB periodically dumps the list of running bridges with - * information about the rings, subrings, and file buckets to which they - * are assigned to a local file.</p> - * - * <p>Details about sanitizing bridge pool assignments can be found - * <a href="https://collector.torproject.org/#type-bridge-pool-assignment">here</a>. - * </p> - * - * @since 1.0.0 - */ -public interface BridgePoolAssignment extends Descriptor { - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the entries contained in this bridge pool assignment list - * with map keys being SHA-1 digests of SHA-1 digest of the bridges' - * public identity keys, encoded as 40 upper-case hexadecimal - * characters, and map values being assignment strings, e.g. - * {@code "https ring=3 flag=stable"}. - * - * @since 1.0.0 - */ - public SortedMap<String, String> getEntries(); -} - diff --git a/src/org/torproject/descriptor/BridgeServerDescriptor.java b/src/org/torproject/descriptor/BridgeServerDescriptor.java deleted file mode 100644 index 7d4503f..0000000 --- a/src/org/torproject/descriptor/BridgeServerDescriptor.java +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright 2015--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a sanitized bridge server descriptor. - * - * <p>Sanitized bridge server descriptors share many contents with relay - * server descriptors ({@link RelayServerDescriptor}), which is why they - * share a common superinterface ({@link ServerDescriptor}). The main - * purpose of having two subinterfaces is being able to distinguish - * descriptor types more easily.</p> - * - * <p>Details about sanitizing bridge server descriptors can be found - * <a href="https://collector.torproject.org/#type-bridge-server-descriptor">here</a>. - * </p> - * - * @since 1.1.0 - */ -public interface BridgeServerDescriptor extends ServerDescriptor { - -} - diff --git a/src/org/torproject/descriptor/Descriptor.java b/src/org/torproject/descriptor/Descriptor.java deleted file mode 100644 index 7cad109..0000000 --- a/src/org/torproject/descriptor/Descriptor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Superinterface for any descriptor with access to generic information - * about the descriptor. - * - * @since 1.0.0 - */ -public interface Descriptor { - - /** - * Return the raw descriptor bytes. - * - * @since 1.0.0 - */ - public byte[] getRawDescriptorBytes(); - - /** - * Return the (possibly empty) list of annotations in the format - * {@code "@key( value)*"}. - * - * @since 1.0.0 - */ - public List<String> getAnnotations(); - - /** - * Return any unrecognized lines when parsing this descriptor, or an - * empty list if there were no unrecognized lines. - * - * @since 1.0.0 - */ - public List<String> getUnrecognizedLines(); -} - diff --git a/src/org/torproject/descriptor/DescriptorCollector.java b/src/org/torproject/descriptor/DescriptorCollector.java deleted file mode 100644 index b1027dc..0000000 --- a/src/org/torproject/descriptor/DescriptorCollector.java +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright 2015--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.io.File; - -/** - * Descriptor source that synchronizes descriptors from the CollecTor - * service to a given local directory. - * - * <p>This type is not a descriptor source in the proper sense, because it - * does not produce descriptors by itself. But it often creates the - * prerequisites for reading descriptors from disk using - * {@link DescriptorReader}.</p> - * - * <p>Code sample:</p> - * <pre>{@code - * DescriptorCollector descriptorCollector = - * DescriptorSourceFactory.createDescriptorCollector(); - * descriptorCollector.collectDescriptors( - * // Download from Tor's main CollecTor instance, - * "https://collector.torproject.org", - * // include network status consensuses and relay server descriptors - * new String[] { "/recent/relay-descriptors/consensuses/", - * "/recent/relay-descriptors/server-descriptors/" }, - * // regardless of last-modified time, - * 0L, - * // write to the local directory called in/, - * new File("in"), - * // and delete extraneous files that do not exist remotely anymore. - * true); - * }</pre> - * - * @since 1.0.0 - */ -public interface DescriptorCollector { - - /** - * Fetch remote files from a CollecTor instance that do not yet exist - * locally and possibly delete local files that do not exist remotely - * anymore. - * - * @param collecTorBaseUrl CollecTor base URL without trailing slash, - * e.g., {@code "https://collector.torproject.org"} - * @param remoteDirectories Remote directories to collect descriptors - * from, e.g., - * {@code "/recent/relay-descriptors/server-descriptors/"}, without - * processing subdirectories unless they are explicitly listed - * @param minLastModified Minimum last-modified time in milliseconds of - * files to be collected, or 0 for collecting all files - * @param localDirectory Directory where collected files will be written - * @param deleteExtraneousLocalFiles Whether to delete all local files - * that do not exist remotely anymore - * - * @since 1.0.0 - */ - public void collectDescriptors(String collecTorBaseUrl, - String[] remoteDirectories, long minLastModified, - File localDirectory, boolean deleteExtraneousLocalFiles); -} - diff --git a/src/org/torproject/descriptor/DescriptorDownloader.java b/src/org/torproject/descriptor/DescriptorDownloader.java deleted file mode 100644 index f0b1101..0000000 --- a/src/org/torproject/descriptor/DescriptorDownloader.java +++ /dev/null @@ -1,198 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.Iterator; -import java.util.Set; - -/** - * Descriptor source that downloads relay descriptors from directory - * authorities or mirrors. - * - * <p>Downloading descriptors is done in a batch which starts after - * setting any configuration options and initiating the download - * process.</p> - * - * @since 1.0.0 - */ -public interface DescriptorDownloader { - - /** - * Add a directory authority to download descriptors from, which is - * only required for downloading network status votes and will be used - * when no directory mirrors are available. - * - * @since 1.0.0 - */ - public void addDirectoryAuthority(String nickname, String ip, - int dirPort); - - /** - * Add a directory mirror to download descriptors from, which is - * preferred for downloading descriptors, except for network status - * votes which are only available on directory authorities. - * - * @since 1.0.0 - */ - public void addDirectoryMirror(String nickname, String ip, int dirPort); - - /** - * Include the current network status consensus in the downloads. - * - * @since 1.0.0 - */ - public void setIncludeCurrentConsensus(); - - /** - * Include the current network status consensus in the downloads, and - * attempt to download it from all directory authorities. - * - * <p>The primary purpose of doing this is to compare different - * consensuses and download characteristics to each other. Typically, - * downloading from a single directory mirror or authority is - * sufficient.</p> - * - * @since 1.0.0 - */ - public void setIncludeCurrentConsensusFromAllDirectoryAuthorities(); - - /** - * Include the current network status votes referenced from a - * previously downloaded consensus in the downloads, which requires - * downloading the current consensus from at least one directory mirror - * or authority. - * - * @since 1.0.0 - */ - public void setIncludeCurrentReferencedVotes(); - - /** - * Include the current network status vote published by the given - * directory authority in the downloads, which requires downloading from - * at least one directory authority. - * - * @since 1.0.0 - */ - public void setIncludeCurrentVote(String fingerprint); - - /** - * Include the current network status votes published by the given - * directory authorities in the downloads, which requires downloading - * from at least one directory authority. - * - * @since 1.0.0 - */ - public void setIncludeCurrentVotes(Set<String> fingerprints); - - /** - * Include all server descriptors referenced from a previously - * downloaded network status consensus in the downloads. - * - * @since 1.0.0 - */ - public void setIncludeReferencedServerDescriptors(); - - /** - * Exclude the server descriptor with the given identifier from the - * downloads even if it's referenced from a consensus and we're supposed - * to download all referenced server descriptors. - * - * @since 1.0.0 - */ - public void setExcludeServerDescriptor(String identifier); - - /** - * Exclude the server descriptors with the given identifiers from the - * downloads even if they are referenced from a consensus and we're - * supposed to download all referenced server descriptors. - * - * @since 1.0.0 - */ - public void setExcludeServerDescriptors(Set<String> identifier); - - /** - * Include all extra-info descriptors referenced from previously - * downloaded server descriptors in the downloads. - * - * @since 1.0.0 - */ - public void setIncludeReferencedExtraInfoDescriptors(); - - /** - * Exclude the extra-info descriptor with the given identifier from the - * downloads even if it's referenced from a server descriptor and we're - * supposed to download all referenced extra-info descriptors. - * - * @since 1.0.0 - */ - public void setExcludeExtraInfoDescriptor(String identifier); - - /** - * Exclude the extra-info descriptors with the given identifiers from - * the downloads even if they are referenced from server descriptors - * and we're supposed to download all referenced extra-info - * descriptors. - * - * @since 1.0.0 - */ - public void setExcludeExtraInfoDescriptors(Set<String> identifiers); - - /** - * Define a connect timeout for a single request. - * - * <p>If a timeout expires, no further requests will be sent to the - * directory authority or mirror. Setting this value to 0 disables the - * connect timeout. Default value is 1 minute (60 * 1000).</p> - * - * @since 1.0.0 - */ - public void setConnectTimeout(long connectTimeoutMillis); - - /** - * Define a read timeout for a single request. - * - * <p>If a timeout expires, no further requests will be sent to the - * directory authority or mirror. Setting this value to 0 disables the - * read timeout. Default value is 1 minute (60 * 1000).</p> - * - * @since 1.0.0 - */ - public void setReadTimeout(long readTimeoutMillis); - - /** - * Define a global timeout for all requests. - * - * <p>Once this timeout expires, all running requests are aborted and no - * further requests are made. Setting this value to 0 disables the - * global timeout. Default is 1 hour (60 * 60 * 1000).</p> - * - * @since 1.0.0 - */ - public void setGlobalTimeout(long globalTimeoutMillis); - - /** - * Fail descriptor parsing when encountering an unrecognized line. - * - * <p>This option is not set by default, because the Tor specifications - * allow for new lines to be added that shall be ignored by older Tor - * versions. But some applications may want to handle unrecognized - * descriptor lines explicitly.</p> - * - * @since 1.0.0 - */ - public void setFailUnrecognizedDescriptorLines(); - - /** - * Download the previously configured relay descriptors and make them - * available via the returned blocking iterator. - * - * <p>Whenever the downloader runs out of descriptors and expects to - * provide more shortly after, it blocks the caller. This method can - * only be run once.</p> - * - * @since 1.0.0 - */ - public Iterator<DescriptorRequest> downloadDescriptors(); -} - diff --git a/src/org/torproject/descriptor/DescriptorFile.java b/src/org/torproject/descriptor/DescriptorFile.java deleted file mode 100644 index 417d7f9..0000000 --- a/src/org/torproject/descriptor/DescriptorFile.java +++ /dev/null @@ -1,77 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.io.File; -import java.util.List; - -/** - * Container for descriptors read from a file. - * - * <p>When the {@link DescriptorReader} reads descriptors from local files - * it provides an iterator over these containers which in turn contain - * references to classes implementing the {@link Descriptor} interface. - * This container also stores potentially useful meta-data about the - * descriptor file.</p> - * - * @since 1.0.0 - */ -public interface DescriptorFile { - - /** - * Return the directory where this descriptor file was contained, or - * null if the file was contained in a tarball. - * - * @since 1.0.0 - */ - public File getDirectory(); - - /** - * Return the tarball where this descriptor file was contained, or null - * if the file was not contained in a tarball. - * - * @since 1.0.0 - */ - public File getTarball(); - - /** - * Return the descriptor file itself, or null if the descriptor file - * was contained in a tarball. - * - * @since 1.0.0 - */ - public File getFile(); - - /** - * Return the descriptor file name, which is either the absolute path - * of the file on disk, or the tar file entry name. - * - * @since 1.0.0 - */ - public String getFileName(); - - /** - * Return the time in milliseconds since the epoch when the descriptor - * file on disk was last modified. - * - * @since 1.0.0 - */ - public long getLastModified(); - - /** - * Return the descriptors contained in the descriptor file. - * - * @since 1.0.0 - */ - public List<Descriptor> getDescriptors(); - - /** - * Return the first exception that was thrown when reading this file or - * parsing its content, or null if no exception was thrown. - * - * @since 1.0.0 - */ - public Exception getException(); -} - diff --git a/src/org/torproject/descriptor/DescriptorParseException.java b/src/org/torproject/descriptor/DescriptorParseException.java deleted file mode 100644 index 309d3f7..0000000 --- a/src/org/torproject/descriptor/DescriptorParseException.java +++ /dev/null @@ -1,20 +0,0 @@ -/* Copyright 2014--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Thrown if raw descriptor contents cannot be parsed to one or more - * {@link Descriptor} instances, according to descriptor specifications. - * - * @since 1.0.0 - */ -@SuppressWarnings("deprecation") -public class DescriptorParseException - extends org.torproject.descriptor.impl.DescriptorParseException { - private static final long serialVersionUID = 100L; - public DescriptorParseException(String message) { - super(message); - } -} - diff --git a/src/org/torproject/descriptor/DescriptorParser.java b/src/org/torproject/descriptor/DescriptorParser.java deleted file mode 100644 index 680b8b2..0000000 --- a/src/org/torproject/descriptor/DescriptorParser.java +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Descriptor source that parses descriptors from raw descriptor contents. - * - * <p>Unlike most of the other descriptor sources this descriptor source - * does not operate in a batch-processing mode. It takes the raw - * descriptor contents of one or more descriptors, parses them, and - * returns a list of descriptors.</p> - * - * <p>This descriptor source is internally used by other descriptor - * sources but can also be used directly by applications that obtain - * raw descriptor contents via other means than one of the existing - * descriptor sources.</p> - * - * @since 1.0.0 - */ -public interface DescriptorParser { - - /** - * Fail descriptor parsing when encountering an unrecognized line. - * - * <p>This option is not set by default, because the Tor specifications - * allow for new lines to be added that shall be ignored by older Tor - * versions. But some applications may want to handle unrecognized - * descriptor lines explicitly.</p> - * - * @since 1.0.0 - */ - public void setFailUnrecognizedDescriptorLines( - boolean failUnrecognizedDescriptorLines); - - /** - * Parse descriptors in the given byte array, possibly parsing the - * publication time from the file name, depending on the descriptor - * type. - * - * @since 1.0.0 - */ - public List<Descriptor> parseDescriptors(byte[] rawDescriptorBytes, - String fileName) throws DescriptorParseException; -} diff --git a/src/org/torproject/descriptor/DescriptorReader.java b/src/org/torproject/descriptor/DescriptorReader.java deleted file mode 100644 index 771755e..0000000 --- a/src/org/torproject/descriptor/DescriptorReader.java +++ /dev/null @@ -1,143 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.io.File; -import java.util.Iterator; -import java.util.SortedMap; - -/** - * Descriptor source that reads descriptors from local files and provides - * an iterator over parsed descriptors. - * - * <p>This descriptor source is likely the most widely used one, possibly - * in combination with {@link DescriptorCollector} to synchronize - * descriptors from the CollecTor service.</p> - * - * <p>Reading descriptors is done in a batch which starts after setting - * any configuration options and initiating the read process.</p> - * - * <p>Code sample:</p> - * <pre>{@code - * DescriptorReader descriptorReader = - * DescriptorSourceFactory.createDescriptorReader(); - * // Read descriptors from local directory called in/. - * descriptorReader.addDirectory(new File("in")); - * Iterator<DescriptorFile> descriptorFiles = - * descriptorReader.readDescriptors(); - * while (descriptorFiles.hasNext()) { - * DescriptorFile descriptorFile = descriptorFiles.next(); - * for (Descriptor descriptor : descriptorFile.getDescriptors()) { - * if ((descriptor instanceof RelayNetworkStatusConsensus)) { - * // Only process network status consensuses, ignore the rest. - * RelayNetworkStatusConsensus consensus = - * (RelayNetworkStatusConsensus) descriptor; - * processConsensus(consensus); - * } - * } - * }}</pre> - * - * @since 1.0.0 - */ -public interface DescriptorReader { - - /** - * Add a local directory to read descriptors from, which may contain - * descriptor files or tarballs containing descriptor files. - * - * @since 1.0.0 - */ - public void addDirectory(File directory); - - /** - * Add a tarball to read descriptors from, which may be uncompressed, - * bz2-compressed, or xz-compressed. - * - * @since 1.0.0 - */ - public void addTarball(File tarball); - - /** - * Exclude files that are listed in the given history file and that - * haven't changed since they have last been read. - * - * <p>Add a new line for each descriptor that is read in this execution - * and remove lines for files that don't exist anymore.</p> - * - * <p>Lines in the history file contain the last modified time in - * milliseconds since the epoch and the absolute path of a file.</p> - * - * @since 1.0.0 - */ - public void setExcludeFiles(File historyFile); - - /** - * Exclude files if they haven't changed since the corresponding last - * modified timestamps. - * - * <p>Can be used instead of (or in addition to) a history file.</p> - * - * @since 1.0.0 - */ - public void setExcludedFiles(SortedMap<String, Long> excludedFiles); - - /** - * Return files and last modified timestamps of files that exist in the - * input directory or directories, but that have been excluded from - * parsing, because they haven't changed since they were last read. - * - * <p>Can be used instead of (or in addition to) a history file when - * combined with the set of parsed files.</p> - * - * @since 1.0.0 - */ - public SortedMap<String, Long> getExcludedFiles(); - - /** - * Return files and last modified timestamps of files that exist in the - * input directory or directories and that have been parsed. - * - * <p>Can be used instead of (or in addition to) a history file when - * combined with the set of excluded files.</p> - * - * @since 1.0.0 - */ - public SortedMap<String, Long> getParsedFiles(); - - /** - * Fail descriptor parsing when encountering an unrecognized line. - * - * <p>This option is not set by default, because the Tor specifications - * allow for new lines to be added that shall be ignored by older Tor - * versions. But some applications may want to handle unrecognized - * descriptor lines explicitly.</p> - * - * @since 1.0.0 - */ - public void setFailUnrecognizedDescriptorLines(); - - /** - * Don't keep more than this number of parsed descriptor files in the - * queue. - * - * <p>The default is 100, but if descriptor files contain hundreds or - * even thousands of descriptors, that default may be too high.</p> - * - * @since 1.0.0 - */ - public void setMaxDescriptorFilesInQueue(int max); - - /** - * Read the previously configured descriptors and make them available - * via the returned blocking iterator. - * - * <p>Whenever the reader runs out of descriptors and expects to provide - * more shortly after, it blocks the caller. This method can only be - * run once.</p> - * - * @since 1.0.0 - */ - public Iterator<DescriptorFile> readDescriptors(); -} - diff --git a/src/org/torproject/descriptor/DescriptorRequest.java b/src/org/torproject/descriptor/DescriptorRequest.java deleted file mode 100644 index c36c0c0..0000000 --- a/src/org/torproject/descriptor/DescriptorRequest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Container for descriptors downloaded from a directory authority or - * mirror. - * - * <p>When the {@link DescriptorDownloader} downloads descriptors from - * directory authorities or mirrors it provides an iterator over these - * containers which in turn contain references to classes implementing the - * {@link Descriptor} interface. This container also stores potentially - * useful meta-data about the descriptor request.</p> - * - * @since 1.0.0 - */ -public interface DescriptorRequest { - - /** - * Return the request URL that was used in this request. - * - * @since 1.0.0 - */ - public String getRequestUrl(); - - /** - * Return the nickname of the directory mirror or authority as - * previously configured. - * - * @since 1.0.0 - */ - public String getDirectoryNickname(); - - /** - * Return the first exception that was thrown when making this request - * or parsing the response, or null if no exception was thrown. - * - * @since 1.0.0 - */ - public Exception getException(); - - /** - * Return the response code that the directory mirror or authority - * returned. - * - * @since 1.0.0 - */ - public int getResponseCode(); - - /** - * Return the time in milliseconds since the epoch when this request - * was started. - * - * @since 1.0.0 - */ - public long getRequestStart(); - - /** - * Return the time in milliseconds since the epoch when this request - * ended. - * - * @since 1.0.0 - */ - public long getRequestEnd(); - - /** - * Return whether this request ended, because the connect timeout has - * expired. - * - * @since 1.0.0 - */ - public boolean connectTimeoutHasExpired(); - - /** - * Return whether this request ended, because the read timeout has - * expired. - * - * @since 1.0.0 - */ - public boolean readTimeoutHasExpired(); - - /** - * Return whether this request ended, because the global timeout for - * all requests has expired. - * - * @since 1.0.0 - */ - public boolean globalTimeoutHasExpired(); - - /** - * Return the descriptors contained in the reply. - * - * @since 1.0.0 - */ - public List<Descriptor> getDescriptors(); -} - diff --git a/src/org/torproject/descriptor/DescriptorSourceFactory.java b/src/org/torproject/descriptor/DescriptorSourceFactory.java deleted file mode 100644 index af13f39..0000000 --- a/src/org/torproject/descriptor/DescriptorSourceFactory.java +++ /dev/null @@ -1,187 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Factory for descriptor sources which in turn produce descriptors. - * - * <p>Descriptor sources are the only producers of classes implementing - * the {@link Descriptor} superinterface. There exist descriptor sources - * for obtaining remote descriptor data ({@link DescriptorDownloader} and - * {@link DescriptorCollector}) and descriptor sources for processing - * local descriptor data ({@link DescriptorReader} and - * {@link DescriptorParser}).</p> - * - * <p>By default, this factory returns implementations from the library's - * own impl package. This may be overridden by setting Java properties, - * though most users will simply use the default implementations.</p> - * - * <p>These properties can be used for setting the implementation:</p> - * <ul> - * <li>{@code descriptor.collector}</li> - * <li>{@code descriptor.downloader}</li> - * <li>{@code descriptor.parser}</li> - * <li>{@code descriptor.reader}</li> - * </ul> - * - * <p>Assuming the classpath contains the special implementation - * referenced, your application classes as well as a descriptor API jar - * the following is an example for using a different implementation of the - * descriptor downloader:</p> - * - * <p><code> - * java -Ddescriptor.downloader=my.special.descriptorimpl.Downloader my.app.Mainclass - * </code></p> - * - * @since 1.0.0 - */ -public final class DescriptorSourceFactory { - - /** - * Default implementation of the {@link DescriptorDownloader} - * descriptor source. - * - * @since 1.0.0 - */ - public final static String DOWNLOADER_DEFAULT = - "org.torproject.descriptor.impl.DescriptorDownloaderImpl"; - - /** - * Default implementation of the {@link DescriptorParser} descriptor - * source. - * - * @since 1.0.0 - */ - public final static String PARSER_DEFAULT = - "org.torproject.descriptor.impl.DescriptorParserImpl"; - - /** - * Default implementation of the {@link DescriptorReader} descriptor - * source. - * - * @since 1.0.0 - */ - public final static String READER_DEFAULT = - "org.torproject.descriptor.impl.DescriptorReaderImpl"; - - /** - * Default implementation of the {@link DescriptorCollector} descriptor - * source. - * - * @since 1.0.0 - */ - public final static String COLLECTOR_DEFAULT = - "org.torproject.descriptor.impl.DescriptorCollectorImpl"; - - /** - * Property name for overriding the implementation of the - * {@link DescriptorParser} descriptor source, which is by default set - * to the class in {@link #PARSER_DEFAULT}. - * - * @since 1.0.0 - */ - public final static String PARSER_PROPERTY = "descriptor.parser"; - - /** - * Property name for overriding the implementation of the - * {@link DescriptorReader} descriptor source, which is by default set - * to the class in {@link #READER_DEFAULT}. - * - * @since 1.0.0 - */ - public final static String READER_PROPERTY = "descriptor.reader"; - - /** - * Property name for overriding the implementation of the - * {@link DescriptorDownloader} descriptor source, which is by default - * set to the class in {@link #DOWNLOADER_DEFAULT}. - * - * @since 1.0.0 - */ - public final static String DOWNLOADER_PROPERTY = - "descriptor.downloader"; - - /** - * Property name for overriding the implementation of the - * {@link DescriptorCollector} descriptor source, which is by default - * set to the class in {@link #COLLECTOR_DEFAULT}. - * - * @since 1.0.0 - */ - public final static String COLLECTOR_PROPERTY = "descriptor.collector"; - - /** - * Create a new {@link DescriptorParser} by instantiating the class in - * {@link #PARSER_PROPERTY}. - * - * @since 1.0.0 - */ - public final static DescriptorParser createDescriptorParser() { - return (DescriptorParser) retrieve(PARSER_PROPERTY); - } - - /** - * Create a new {@link DescriptorReader} by instantiating the class in - * {@link #READER_PROPERTY}. - * - * @since 1.0.0 - */ - public final static DescriptorReader createDescriptorReader() { - return (DescriptorReader) retrieve(READER_PROPERTY); - } - - /** - * Create a new {@link DescriptorDownloader} by instantiating the class - * in {@link #DOWNLOADER_PROPERTY}. - * - * @since 1.0.0 - */ - public final static DescriptorDownloader createDescriptorDownloader() { - return (DescriptorDownloader) retrieve(DOWNLOADER_PROPERTY); - } - - /** - * Create a new {@link DescriptorCollector} by instantiating the class - * in {@link #COLLECTOR_PROPERTY}. - * - * @since 1.0.0 - */ - public final static DescriptorCollector createDescriptorCollector() { - return (DescriptorCollector) retrieve(COLLECTOR_PROPERTY); - } - - private final static <T> Object retrieve(String type) { - Object object; - String clazzName = null; - try { - switch (type) { - case PARSER_PROPERTY: - clazzName = System.getProperty(type, PARSER_DEFAULT); - break; - case DOWNLOADER_PROPERTY: - clazzName = System.getProperty(type, DOWNLOADER_DEFAULT); - break; - case READER_PROPERTY: - clazzName = System.getProperty(type, READER_DEFAULT); - break; - case COLLECTOR_PROPERTY: - clazzName = System.getProperty(type, COLLECTOR_DEFAULT); - break; - } - object = ClassLoader.getSystemClassLoader().loadClass(clazzName). - newInstance(); - } catch (ClassNotFoundException ex) { - throw new ImplementationNotAccessibleException("Cannot load class " - + clazzName + "for type " + type, ex); - } catch (InstantiationException ex) { - throw new ImplementationNotAccessibleException("Cannot load class " - + clazzName + "for type " + type, ex); - } catch (IllegalAccessException ex) { - throw new ImplementationNotAccessibleException("Cannot load class " - + clazzName + "for type " + type, ex); - } - return object; - } -} - diff --git a/src/org/torproject/descriptor/DirSourceEntry.java b/src/org/torproject/descriptor/DirSourceEntry.java deleted file mode 100644 index 96d81ee..0000000 --- a/src/org/torproject/descriptor/DirSourceEntry.java +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains details about an authority and its vote that contributed to a - * consensus. - * - * <p>A directory source entry is not a descriptor type of its own but is - * part of a network status consensus - * ({@link RelayNetworkStatusConsensus}).</p> - * - * @since 1.0.0 - */ -public interface DirSourceEntry { - - /** - * Return the raw directory source entry bytes. - * - * @since 1.0.0 - */ - public byte[] getDirSourceEntryBytes(); - - /** - * Return the authority's nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return a SHA-1 digest of the authority's long-term authority - * identity key used for the version 3 directory protocol, encoded as - * 40 upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public String getIdentity(); - - /** - * Return the authority's hostname. - * - * @since 1.2.0 - */ - public String getHostname(); - - /** - * Return the authority's primary IPv4 address in dotted-quad format. - * - * @since 1.0.0 - */ - public String getIp(); - - /** - * Return the TCP port where this authority accepts directory-related - * HTTP connections. - * - * @since 1.0.0 - */ - public int getDirPort(); - - /** - * Return the TCP port where this authority accepts TLS connections for - * the main OR protocol. - * - * @since 1.0.0 - */ - public int getOrPort(); - - /** - * Return whether this directory source entry was created using a - * legacy key. - * - * @since 1.0.0 - */ - public boolean isLegacy(); - - /** - * Return the contact information for this authority, which may contain - * non-ASCII characters. - * - * @since 1.0.0 - */ - public String getContactLine(); - - /** - * Return the SHA-1 vote digest, encoded as 40 lower-case hexadecimal - * characters. - * - * @since 1.0.0 - */ - public String getVoteDigest(); -} - diff --git a/src/org/torproject/descriptor/DirectoryKeyCertificate.java b/src/org/torproject/descriptor/DirectoryKeyCertificate.java deleted file mode 100644 index 07211ef..0000000 --- a/src/org/torproject/descriptor/DirectoryKeyCertificate.java +++ /dev/null @@ -1,109 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a key certificate in the version 3 directory protocol. - * - * <p>Every directory authority in the version 3 directory protocol uses - * two keys: a medium-term signing key, and a long-term authority identity - * key. (Authorities also have a relay identity key used in their role as - * a relay and by earlier versions of the directory protocol.) The - * identity key is used from time to time to sign new key certificates - * containing signing keys. The contained signing key is used to sign key - * certificates and status documents.</p> - * - * @since 1.0.0 - */ -public interface DirectoryKeyCertificate extends Descriptor { - - /** - * Return the version of this descriptor, which must be 3 or higher. - * - * @since 1.0.0 - */ - public int getDirKeyCertificateVersion(); - - /** - * Return the authority's primary IPv4 address in dotted-quad format, - * or null if the certificate does not contain an address. - * - * @since 1.0.0 - */ - public String getAddress(); - - /** - * Return the TCP port where this authority accepts directory-related - * HTTP connections, or -1 if the certificate does not contain a port. - * - * @since 1.0.0 - */ - public int getPort(); - - /** - * Return a SHA-1 digest of the authority's long-term authority - * identity key used for the version 3 directory protocol, encoded as - * 40 upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return the authority's identity key in PEM format. - * - * @since 1.0.0 - */ - public String getDirIdentityKey(); - - /** - * Return the time in milliseconds since the epoch when the authority's - * signing key and this key certificate were generated. - * - * @since 1.0.0 - */ - public long getDirKeyPublishedMillis(); - - /** - * Return the time in milliseconds since the epoch after which the - * authority's signing key is no longer valid. - * - * @since 1.0.0 - */ - public long getDirKeyExpiresMillis(); - - /** - * Return the authority's signing key in PEM format. - * - * @since 1.0.0 - */ - public String getDirSigningKey(); - - /** - * Return the signature of the authority's identity key made using the - * authority's signing key, or null if the certificate does not contain - * such a signature. - * - * @since 1.0.0 - */ - public String getDirKeyCrosscert(); - - /** - * Return the certificate signature from the initial item - * "dir-key-certificate-version" until the final item - * "dir-key-certification", signed with the authority identity key. - * - * @since 1.0.0 - */ - public String getDirKeyCertification(); - - /** - * Return the SHA-1 certificate digest, encoded as 40 lower-case - * hexadecimal characters. - * - * @since 1.0.0 - */ - public String getCertificateDigest(); -} - diff --git a/src/org/torproject/descriptor/DirectorySignature.java b/src/org/torproject/descriptor/DirectorySignature.java deleted file mode 100644 index 8877a4e..0000000 --- a/src/org/torproject/descriptor/DirectorySignature.java +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains the signature of a network status consensus or vote. - * - * <p>A directory signature is not a descriptor type of its own but is - * part of a network status consensus - * ({@link RelayNetworkStatusConsensus}) or vote - * ({@link RelayNetworkStatusVote}).</p> - * - * @since 1.0.0 - */ -public interface DirectorySignature { - - /** - * Return the digest algorithm, which is "sha1" by default and which - * can be "sha256" or another digest algorithm. - * - * @since 1.0.0 - */ - public String getAlgorithm(); - - /** - * Return the SHA-1 digest of the authority's long-term identity key in - * the version 3 directory protocol, encoded as 40 upper-case - * hexadecimal characters. - * - * @since 1.0.0 - */ - public String getIdentity(); - - /** - * Return the SHA-1 digest of the authority's medium-term signing key - * in the version 3 directory protocol, encoded as 40 upper-case - * hexadecimal characters. - * - * @since 1.0.0 - */ - public String getSigningKeyDigest(); - - /** - * Return the directory signature string made with the authority's - * identity key in the version 3 directory protocol. - * - * @since 1.0.0 - */ - public String getSignature(); -} - diff --git a/src/org/torproject/descriptor/ExitList.java b/src/org/torproject/descriptor/ExitList.java deleted file mode 100644 index 2a5cb2e..0000000 --- a/src/org/torproject/descriptor/ExitList.java +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.Map; -import java.util.Set; - -/** - * Contains an exit list containing the IP addresses of relays that the - * exit list service TorDNSEL found when exiting through them. - * - * @since 1.0.0 - */ -public interface ExitList extends Descriptor { - - /** - * End-of-line character expected in exit lists. - * - * @since 1.0.0 - */ - public final static String EOL = "\n"; - - /** - * Exit list entry containing results from a single exit scan. - * - * @since 1.1.0 - */ - public interface Entry { - - /** - * Return the scanned relay's fingerprint, which is a SHA-1 digest of - * the relays's public identity key, encoded as 40 upper-case - * hexadecimal characters. - * - * @since 1.1.0 - */ - public String getFingerprint(); - - /** - * Return the time in milliseconds since the epoch when the scanned - * relay's last known descriptor was published. - * - * @since 1.1.0 - */ - public long getPublishedMillis(); - - /** - * Return the time in milliseconds since the epoch when the network - * status that this scan was based on was published. - * - * @since 1.1.0 - */ - public long getLastStatusMillis(); - - /** - * Return the IP addresses that were determined in the scan with map - * keys being IPv4 addresses in dotted-quad format and map values - * being scan times in milliseconds since the epoch. - * - * @since 1.1.0 - */ - public Map<String, Long> getExitAddresses(); - } - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was downloaded. - * - * @since 1.0.0 - */ - public long getDownloadedMillis(); - - /** - * Return the unordered set of exit scan results. - * - * @since 1.0.0 - * @deprecated The {@link ExitListEntry} type has been deprecated and - * superseded by {@link ExitList.Entry} which is returned by - * {@link #getEntries()}. - */ - @Deprecated - public Set<ExitListEntry> getExitListEntries(); - - /** - * Return the unordered set of exit scan results. - * - * @since 1.1.0 - */ - public Set<ExitList.Entry> getEntries(); -} - diff --git a/src/org/torproject/descriptor/ExitListEntry.java b/src/org/torproject/descriptor/ExitListEntry.java deleted file mode 100644 index 2a3d79f..0000000 --- a/src/org/torproject/descriptor/ExitListEntry.java +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Exit list entry containing results from a single exit scan. - * - * @since 1.0.0 - * @deprecated Superseded by {@link ExitList.Entry}. - */ -@Deprecated -public interface ExitListEntry extends ExitList.Entry { - - /** - * Return the scanned relay's fingerprint, which is a SHA-1 digest of - * the relays's public identity key, encoded as 40 upper-case - * hexadecimal characters. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return the time in milliseconds since the epoch when the scanned - * relay's last known descriptor was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the time in milliseconds since the epoch when the network - * status that this scan was based on was published. - * - * @since 1.0.0 - */ - public long getLastStatusMillis(); - - /** - * Return the IPv4 address in dotted-quad format that was determined in - * the scan. - * - * @since 1.0.0 - */ - public String getExitAddress(); - - /** - * Return the scan time in milliseconds since the epoch. - * - * @since 1.0.0 - */ - public long getScanMillis(); -} - diff --git a/src/org/torproject/descriptor/ExtraInfoDescriptor.java b/src/org/torproject/descriptor/ExtraInfoDescriptor.java deleted file mode 100644 index 49efbf3..0000000 --- a/src/org/torproject/descriptor/ExtraInfoDescriptor.java +++ /dev/null @@ -1,646 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.Map; -import java.util.SortedMap; - -/** - * Contains a relay or sanitized bridge extra-info descriptor. - * - * <p>Relays publish extra-info descriptors as an addendum to server - * descriptors ({@link ServerDescriptor}) to report extraneous information - * to the directory authorities that clients do not need to download in - * order to function. This information primarily consists of statistics - * gathered by the relay about its usage and can take up a lot of - * descriptor space. The separation of server descriptors and extra-info - * descriptors has become less relevant with the introduction of - * microdescriptors ({@link Microdescriptor}) that are derived from server - * descriptors by the directory authority and which clients download - * instead of server descriptors, but it persists.</p> - * - * <p>Bridges publish extra-info descriptors to the bridge authority for - * the same reason, to include statistics about their usage without - * increasing the directory protocol overhead for bridge clients. In this - * case, the separation of server descriptors and extra-info descriptors - * is slightly more relevant, because there are no microdescriptors for - * bridges, so that bridge clients still download server descriptors of - * bridges they're using. Another reason is that bridges need to include - * information like details of all the transports they support in their - * descriptors, and bridge clients using one such transport are not - * supposed to learn the details of the other transports.</p> - * - * <p>It's worth noting that all contents of extra-info descriptors are - * written and signed by relays and bridges without a third party - * verifying their correctness. The (bridge) directory authorities may - * decide to exclude dishonest servers from the network statuses they - * produce, but that wouldn't be reflected in extra-info descriptors.</p> - * - * @since 1.0.0 - */ -public interface ExtraInfoDescriptor extends Descriptor { - - /** - * Return the SHA-1 descriptor digest, encoded as 40 lower-case (relay - * descriptors) or upper-case (bridge descriptors) hexadecimal - * characters, that is used to reference this descriptor from a server - * descriptor. - * - * @since 1.0.0 - */ - public String getExtraInfoDigest(); - - /** - * Return the SHA-256 descriptor digest, encoded as 43 base64 - * characters without padding characters, that may be used to reference - * this descriptor from a server descriptor. - * - * @since 1.1.0 - */ - public String getExtraInfoDigestSha256(); - - /** - * Return the server's nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return a SHA-1 digest of the server's public identity key, encoded - * as 40 upper-case hexadecimal characters, that is typically used to - * uniquely identify the server. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return the time in milliseconds since the epoch when this descriptor - * and the corresponding server descriptor were generated. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the server's history of read bytes, or null if the descriptor - * does not contain a bandwidth history; older Tor versions included - * bandwidth histories in their server descriptors - * ({@link ServerDescriptor#getReadHistory()}). - * - * @since 1.0.0 - */ - public BandwidthHistory getReadHistory(); - - /** - * Return the server's history of written bytes, or null if the - * descriptor does not contain a bandwidth history; older Tor versions - * included bandwidth histories in their server descriptors - * ({@link ServerDescriptor#getWriteHistory()}). - * - * @since 1.0.0 - */ - public BandwidthHistory getWriteHistory(); - - /** - * Return a SHA-1 digest of the GeoIP database file used by this server - * to resolve client IP addresses to country codes, encoded as 40 - * upper-case hexadecimal characters, or null if no GeoIP database - * digest is included. - * - * @since 1.0.0 - */ - public String getGeoipDbDigest(); - - /** - * Return a SHA-1 digest of the GeoIPv6 database file used by this - * server to resolve client IP addresses to country codes, encoded as 40 - * upper-case hexadecimal characters, or null if no GeoIPv6 database - * digest is included. - * - * @since 1.0.0 - */ - public String getGeoip6DbDigest(); - - /** - * Return the time in milliseconds since the epoch when the included - * directory request statistics interval ended, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public long getDirreqStatsEndMillis(); - - /** - * Return the interval length of the included directory request - * statistics in seconds, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public long getDirreqStatsIntervalLength(); - - /** - * Return statistics on unique IP addresses requesting v2 network - * statuses with map keys being country codes and map values being - * numbers of unique IP addresses rounded up to the nearest multiple of - * 8, or null if no such statistics are included (which is the case with - * recent Tor versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV2Ips(); - - /** - * Return statistics on unique IP addresses requesting v3 network - * status consensuses of any flavor with map keys being country codes - * and map values being numbers of unique IP addresses rounded up to the - * nearest multiple of 8, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV3Ips(); - - /** - * Return statistics on directory requests for v2 network statuses with - * map keys being country codes and map values being request numbers - * rounded up to the nearest multiple of 8, or null if no such - * statistics are included (which is the case with recent Tor - * versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV2Reqs(); - - /** - * Return statistics on directory requests for v3 network status - * consensuses of any flavor with map keys being country codes and map - * values being request numbers rounded up to the nearest multiple of 8, - * or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV3Reqs(); - - /** - * Return the share of requests for v2 network statuses that the server - * expects to receive from clients, or -1.0 if this share is not - * included (which is the case with recent Tor versions). - * - * @since 1.0.0 - */ - public double getDirreqV2Share(); - - /** - * Return the share of requests for v3 network status consensuses of - * any flavor that the server expects to receive from clients, or -1.0 - * if this share is not included (which is the case with recent Tor - * versions). - * - * @since 1.0.0 - */ - public double getDirreqV3Share(); - - /** - * Return statistics on responses to directory requests for v2 network - * statuses with map keys being response strings and map values being - * response numbers rounded up to the nearest multiple of 4, or null if - * no such statistics are included (which is the case with recent Tor - * versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV2Resp(); - - /** - * Return statistics on responses to directory requests for v3 network - * status consensuses of any flavor with map keys being response strings - * and map values being response numbers rounded up to the nearest - * multiple of 4, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV3Resp(); - - /** - * Return statistics on directory requests for v2 network statuses to - * the server's directory port with map keys being statistic keys and - * map values being statistic values like counts or quantiles, or null - * if no such statistics are included (which is the case with recent Tor - * versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV2DirectDl(); - - /** - * Return statistics on directory requests for v3 network status - * consensuses of any flavor to the server's directory port with map - * keys being statistic keys and map values being statistic values like - * counts or quantiles, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV3DirectDl(); - - /** - * Return statistics on directory requests for v2 network statuses - * tunneled through a circuit with map keys being statistic keys and map - * values being statistic values, or null if no such statistics are - * included (which is the case with recent Tor versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV2TunneledDl(); - - /** - * Return statistics on directory requests for v3 network status - * consensuses of any flavor tunneled through a circuit with map keys - * being statistic keys and map values being statistic values, or null - * if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getDirreqV3TunneledDl(); - - /** - * Return the directory request read history contained in this - * descriptor, or null if no such history is contained. - * - * @since 1.0.0 - */ - public BandwidthHistory getDirreqReadHistory(); - - /** - * Return the directory request write history contained in this - * descriptor, or null if no such history is contained. - * - * @since 1.0.0 - */ - public BandwidthHistory getDirreqWriteHistory(); - - /** - * Return the time in milliseconds since the epoch when the included - * entry statistics interval ended, or -1 if no such statistics are - * included. - * - * @since 1.0.0 - */ - public long getEntryStatsEndMillis(); - - /** - * Return the interval length of the included entry statistics in - * seconds, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public long getEntryStatsIntervalLength(); - - /** - * Return statistics on client IP addresses with map keys being country - * codes and map values being the number of unique IP addresses that - * have connected from that country rounded up to the nearest multiple - * of 8, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getEntryIps(); - - /** - * Return the time in milliseconds since the epoch when the included - * cell statistics interval ended, or -1 if no such statistics are - * included. - * - * @since 1.0.0 - */ - public long getCellStatsEndMillis(); - - /** - * Return the interval length of the included cell statistics in - * seconds, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public long getCellStatsIntervalLength(); - - /** - * Return the mean number of processed cells per circuit by circuit - * decile starting with the loudest decile at index 0 and the quietest - * decile at index 8, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public List<Integer> getCellProcessedCells(); - - /** - * Return the mean number of cells contained in circuit queues by - * circuit decile starting with the loudest decile at index 0 and the - * quietest decile at index 8, or null if no such statistics are - * included. - * - * @since 1.0.0 - */ - public List<Double> getCellQueuedCells(); - - /** - * Return the mean times in milliseconds that cells spend in circuit - * queues by circuit decile starting with the loudest decile at index 0 - * and the quietest decile at index 8, or null if no such statistics are - * included. - * - * @since 1.0.0 - */ - public List<Integer> getCellTimeInQueue(); - - /** - * Return the mean number of circuits included in any of the cell - * statistics deciles, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public int getCellCircuitsPerDecile(); - - /** - * Return the time in milliseconds since the epoch when the included - * statistics on bi-directional connection usage ended, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public long getConnBiDirectStatsEndMillis(); - - /** - * Return the interval length of the included statistics on - * bi-directional connection usage in seconds, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public long getConnBiDirectStatsIntervalLength(); - - /** - * Return the number of connections on which this server read and wrote - * less than 2 KiB/s in a 10-second interval, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public int getConnBiDirectBelow(); - - /** - * Return the number of connections on which this server read and wrote - * at least 2 KiB/s in a 10-second interval and at least 10 times more - * in read direction than in write direction, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public int getConnBiDirectRead(); - - /** - * Return the number of connections on which this server read and wrote - * at least 2 KiB/s in a 10-second interval and at least 10 times more - * in write direction than in read direction, or -1 if no such - * statistics are included. - * - * @since 1.0.0 - */ - public int getConnBiDirectWrite(); - - /** - * Return the number of connections on which this server read and wrote - * at least 2 KiB/s in a 10-second interval but not 10 times more in - * either direction, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public int getConnBiDirectBoth(); - - /** - * Return the time in milliseconds since the epoch when the included - * exit statistics interval ended, or -1 if no such statistics are - * included. - * - * @since 1.0.0 - */ - public long getExitStatsEndMillis(); - - /** - * Return the interval length of the included exit statistics in - * seconds, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public long getExitStatsIntervalLength(); - - /** - * Return statistics on KiB written to streams exiting the Tor network - * by target TCP port with map keys being string representations of - * ports (or {@code "other"}) and map values being KiB rounded up to the - * next full KiB, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Long> getExitKibibytesWritten(); - - /** - * Return statistics on KiB read from streams exiting the Tor network - * by target TCP port with map keys being string representations of - * ports (or {@code "other"}) and map values being KiB rounded up to the - * next full KiB, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Long> getExitKibibytesRead(); - - /** - * Return statistics on opened streams exiting the Tor network by - * target TCP port with map keys being string representations of ports - * (or {@code "other"}) and map values being the number of opened - * streams, rounded up to the nearest multiple of 4, or null if no such - * statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Long> getExitStreamsOpened(); - - /** - * Return the time in milliseconds since the epoch when the included - * "geoip" statistics interval started, or -1 if no such statistics are - * included (which is the case except for very old Tor versions). - * - * @since 1.0.0 - */ - public long getGeoipStartTimeMillis(); - - /** - * Return statistics on the origin of client IP addresses with map keys - * being country codes and map values being the number of unique IP - * addresses that have connected from that country between the start of - * the statistics interval and the descriptor publication time rounded - * up to the nearest multiple of 8, or null if no such statistics are - * included (which is the case except for very old Tor versions). - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getGeoipClientOrigins(); - - /** - * Return the time in milliseconds since the epoch when the included - * bridge statistics interval ended, or -1 if no such statistics are - * included. - * - * @since 1.0.0 - */ - public long getBridgeStatsEndMillis(); - - /** - * Return the interval length of the included bridge statistics in - * seconds, or -1 if no such statistics are included. - * - * @since 1.0.0 - */ - public long getBridgeStatsIntervalLength(); - - /** - * Return statistics on bridge client IP addresses by country with map - * keys being country codes and map values being the number of unique IP - * addresses that have connected from that country rounded up to the - * nearest multiple of 8, or null if no such statistics are included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getBridgeIps(); - - /** - * Return statistics on bridge client IP addresses by IP version with - * map keys being protocol families, e.g., {@code "v4"} or {@code "v6"}, - * and map values being the number of unique IP addresses rounded up to - * the nearest multiple of 8, or null if no such statistics are - * included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getBridgeIpVersions(); - - /** - * Return statistics on bridge client IP addresses by transport with - * map keys being pluggable transport names, e.g., {@code "obfs2"} or - * {@code "obfs3"} for known transports, {@code "<OR>"} for the default - * onion routing protocol, or {@code "<??>"} for an unknown transport, - * and map values being the number of unique IP addresses rounded up to - * the nearest multiple of 8, or null if no such statistics are - * included. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getBridgeIpTransports(); - - /** - * Return the (possibly empty) list of pluggable transports supported - * by this server. - * - * @since 1.0.0 - */ - public List<String> getTransports(); - - /** - * Return the time in milliseconds since the epoch when the included - * hidden-service statistics interval ended, or -1 if no such statistics - * are included. - * - * @since 1.1.0 - */ - public long getHidservStatsEndMillis(); - - /** - * Return the interval length of the included hidden-service statistics - * in seconds, or -1 if no such statistics are included. - * - * @since 1.1.0 - */ - public long getHidservStatsIntervalLength(); - - /** - * Return the approximate number of RELAY cells seen in either - * direction on a circuit after receiving and successfully processing a - * RENDEZVOUS1 cell, or null if no such statistics are included. - * - * @since 1.1.0 - */ - public Double getHidservRendRelayedCells(); - - /** - * Return the obfuscation parameters applied to the original - * measurement value of RELAY cells seen in either direction on a - * circuit after receiving and successfully processing a RENDEZVOUS1 - * cell, or null if no such statistics are included. - * - * @since 1.1.0 - */ - public Map<String, Double> getHidservRendRelayedCellsParameters(); - - /** - * Return the approximate number of unique hidden-service identities - * seen in descriptors published to and accepted by this hidden-service - * directory, or null if no such statistics are included. - * - * @since 1.1.0 - */ - public Double getHidservDirOnionsSeen(); - - /** - * Return the obfuscation parameters applied to the original - * measurement value of unique hidden-service identities seen in - * descriptors published to and accepted by this hidden-service - * directory, or null if no such statistics are included. - * - * @since 1.1.0 - */ - public Map<String, Double> getHidservDirOnionsSeenParameters(); - - /** - * Return the RSA-1024 signature of the PKCS1-padded descriptor digest, - * taken from the beginning of the router line through the newline after - * the router-signature line, or null if the descriptor doesn't contain - * a signature (which is the case in sanitized bridge descriptors). - * - * @since 1.1.0 - */ - public String getRouterSignature(); - - /** - * Return the Ed25519 certificate in PEM format, or null if the - * descriptor doesn't contain one. - * - * @since 1.1.0 - */ - public String getIdentityEd25519(); - - /** - * Return the Ed25519 master key, encoded as 43 base64 characters - * without padding characters, which was either parsed from the optional - * {@code "master-key-ed25519"} line or derived from the (likewise - * optional) Ed25519 certificate following the - * {@code "identity-ed25519"} line, or null if the descriptor contains - * neither Ed25519 master key nor Ed25519 certificate. - * - * @since 1.1.0 - */ - public String getMasterKeyEd25519(); - - /** - * Return the Ed25519 signature of the SHA-256 digest of the entire - * descriptor, encoded as 86 base64 characters without padding - * characters, from the first character up to and including the first - * space after the {@code "router-sig-ed25519"} string, prefixed with - * the string {@code "Tor router descriptor signature v1"}. - * - * @since 1.1.0 - */ - public String getRouterSignatureEd25519(); -} - diff --git a/src/org/torproject/descriptor/ImplementationNotAccessibleException.java b/src/org/torproject/descriptor/ImplementationNotAccessibleException.java deleted file mode 100644 index c54e48f..0000000 --- a/src/org/torproject/descriptor/ImplementationNotAccessibleException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright 2014--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Thrown if a descriptor source implementation class cannot be found, - * instantiated, or accessed. - * - * @see DescriptorSourceFactory - * @since 1.0.0 - */ -@SuppressWarnings("serial") -public class ImplementationNotAccessibleException - extends RuntimeException { - - public ImplementationNotAccessibleException(String string, - Throwable ex) { - super(string, ex); - } -} - diff --git a/src/org/torproject/descriptor/Microdescriptor.java b/src/org/torproject/descriptor/Microdescriptor.java deleted file mode 100644 index f19b7df..0000000 --- a/src/org/torproject/descriptor/Microdescriptor.java +++ /dev/null @@ -1,135 +0,0 @@ -/* Copyright 2014--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Contains a relay microdescriptor. - * - * <p>A microdescriptor is a stripped-down version of a relay server - * descriptor ({@link RelayServerDescriptor}) generated by the directory - * authorities by extracting and/or transforming relay server descriptor - * contents following strict rules without adding the authority's opinion - * about the relay. Microdescriptors are referenced from microdescriptor - * consensuses ({@link RelayNetworkStatusConsensus}) and downloaded by - * clients to make path-selection decisions and to build circuits. - * Microdescriptors contain only the most relevant parts that clients care - * about. Microdescriptors are expected to be relatively static and only - * change about once per week.</p> - * - * @since 1.0.0 - */ -public interface Microdescriptor extends Descriptor { - - /** - * Return the SHA-256 descriptor digest, encoded as 43 base64 - * characters without padding characters, that is used to reference this - * descriptor from a vote or microdescriptor consensus. - * - * @since 1.0.0 - */ - public String getMicrodescriptorDigest(); - - /** - * Return the RSA-1024 public key in PEM format used to encrypt CREATE - * cells for this server, or null if the descriptor doesn't contain an - * onion key. - * - * @since 1.0.0 - */ - public String getOnionKey(); - - /** - * Return the curve25519 public key, encoded as 43 base64 characters - * without padding characters, that is used for the ntor circuit - * extended handshake, or null if the descriptor didn't contain an - * ntor-onion-key line. - * - * @since 1.0.0 - */ - public String getNtorOnionKey(); - - /** - * Return IP addresses and TCP ports where this server accepts TLS - * connections for the main OR protocol, or an empty list if the server - * does not support additional addresses or ports; entries are given in - * the order as they are listed in the descriptor; IPv4 addresses are - * given in dotted-quad format, IPv6 addresses use the colon-separated - * hexadecimal format surrounded by square brackets, and TCP ports are - * separated from the IP address using a colon. - * - * @since 1.0.0 - */ - public List<String> getOrAddresses(); - - /** - * Return nicknames, $-prefixed identity fingerprints, or tuples of the - * format {@code $fingerprint=nickname} or {@code $fingerprint~nickname} - * of servers contained in this server's family, or null if the - * descriptor does not contain a family line. - * - * @since 1.0.0 - */ - public List<String> getFamilyEntries(); - - /** - * Return the default policy, {@code "accept"} or {@code "reject"}, of - * the IPv4 port summary, or null if the descriptor didn't contain an - * IPv4 exit-policy summary line which is equivalent to rejecting all - * streams to IPv4 targets. - * - * @since 1.0.0 - */ - public String getDefaultPolicy(); - - /** - * Return the port list of the IPv4 exit-policy summary, or null if the - * descriptor didn't contain an IPv4 exit-policy summary line which is - * equivalent to rejecting all streams to IPv4 targets. - * - * @since 1.0.0 - */ - public String getPortList(); - - /** - * Return the default policy, {@code "accept"} or {@code "reject"}, of - * the IPv6 port summary, or null if the descriptor didn't contain an - * IPv6 exit-policy summary line which is equivalent to rejecting all - * streams to IPv6 targets. - * - * @since 1.0.0 - */ - public String getIpv6DefaultPolicy(); - - /** - * Return the port list of the IPv6 exit-policy summary, or null if the - * descriptor didn't contain an IPv6 exit-policy summary line which is - * equivalent to rejecting all streams to IPv6 targets. - * - * @since 1.0.0 - */ - public String getIpv6PortList(); - - /** - * Return a SHA-1 digest of the server's RSA-1024 identity key, encoded - * as 27 base64 characters without padding characters, that is only - * included to prevent collisions between microdescriptors, or null if - * no such digest is included. - * - * @since 1.1.0 - */ - public String getRsa1024Identity(); - - /** - * Return a SHA-256 digest of the server's Ed25519 identity key, - * encoded as 43 base64 characters without padding characters, that is - * only included to prevent collisions between microdescriptors, or null - * if no such digest is included. - * - * @since 1.1.0 - */ - public String getEd25519Identity(); -} - diff --git a/src/org/torproject/descriptor/NetworkStatusEntry.java b/src/org/torproject/descriptor/NetworkStatusEntry.java deleted file mode 100644 index 43b3175..0000000 --- a/src/org/torproject/descriptor/NetworkStatusEntry.java +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.Set; -import java.util.SortedSet; - -/** - * Contains an entry in a network status in the version 2 or 3 directory - * protocol or in a bridge network status. - * - * <p>A network status entry is not a descriptor type of its own but is - * part of a network status in the version 2 directory protocol - * ({@link RelayNetworkStatus}), a vote ({@link RelayNetworkStatusVote}) - * or flavored/unflavored consensus (@link RelayNetworkStatusConsensus}) - * in the version 3 directory protocol, or a bridge network status - * ({@link BridgeNetworkStatus}). Entries in signed directories in the - * version 1 directory protocol are represented by router status entries - * ({@link RouterStatusEntry}).</p> - * - * @since 1.0.0 - */ -public interface NetworkStatusEntry { - - /** - * Return the raw network status entry bytes. - * - * @since 1.0.0 - */ - public byte[] getStatusEntryBytes(); - - /** - * Return the server nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return a SHA-1 digest of the server's identity key, encoded as 40 - * upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return the SHA-1 digest of the server descriptor, or null if the - * containing network status does not contain server descriptor - * references, like a microdesc consensus. - * - * @since 1.0.0 - */ - public String getDescriptor(); - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the server's primary IPv4 address in dotted-quad format. - * - * @since 1.0.0 - */ - public String getAddress(); - - /** - * Return the TCP port where this server accepts TLS connections for - * the main OR protocol. - * - * @since 1.0.0 - */ - public int getOrPort(); - - /** - * Return the TCP port where this server accepts directory-related HTTP - * connections. - * - * @since 1.0.0 - */ - public int getDirPort(); - - /** - * Return the (possibly empty) set of microdescriptor digests if the - * containing network status is a vote or microdesc consensus, or null - * otherwise. - * - * @since 1.0.0 - */ - public Set<String> getMicrodescriptorDigests(); - - /** - * Return additional IP addresses and TCP ports where this server - * accepts TLS connections for the main OR protocol, or an empty list if - * the network status doesn't contain any such additional addresses and - * ports. - * - * @since 1.0.0 - */ - public List<String> getOrAddresses(); - - /** - * Return the relay flags assigned to this server, or null if the - * status entry didn't contain any relay flags. - * - * @since 1.0.0 - */ - public SortedSet<String> getFlags(); - - /** - * Return the Tor software version, or null if the status entry didn't - * contain version information. - * - * @since 1.0.0 - */ - public String getVersion(); - - /** - * Return the bandwidth weight of this server or -1 if the status entry - * didn't contain a bandwidth line. - * - * @since 1.0.0 - */ - public long getBandwidth(); - - /** - * Return the measured bandwidth or -1 if the status entry either - * didn't contain bandwidth information or didn't contain an indication - * that this information is based on measured bandwidth. - * - * @since 1.0.0 - */ - public long getMeasured(); - - /** - * Return whether the status entry is yet unmeasured by the bandwidth - * authorities; only included in consensuses using method 17 or higher. - * - * @since 1.0.0 - */ - public boolean getUnmeasured(); - - /** - * Return the default policy of the port summary, which can be either - * {@code "accept"} or {@code "reject"}, or null if the status entry - * didn't contain an exit policy summary. - * - * @since 1.0.0 - */ - public String getDefaultPolicy(); - - /** - * Return the list of ports or port intervals of the exit port summary, - * or null if the status entry didn't contain an exit policy summary. - * - * @since 1.0.0 - */ - public String getPortList(); - - /** - * Return the server's Ed25519 master key, encoded as 43 base64 - * characters without padding characters, "none" if the relay doesn't - * have an Ed25519 identity, or null if the status entry didn't contain - * this information or if the status is not a vote. - * - * @since 1.1.0 - */ - public String getMasterKeyEd25519(); -} - diff --git a/src/org/torproject/descriptor/RelayDirectory.java b/src/org/torproject/descriptor/RelayDirectory.java deleted file mode 100644 index 8f3e58b..0000000 --- a/src/org/torproject/descriptor/RelayDirectory.java +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Contains a signed directory in the version 1 directory protocol. - * - * <p>Directory authorities in the (long outdated) version 1 of the - * directory protocol served signed directory documents containing a list - * of signed server descriptors ({@link ServerDescriptor}) along with - * short summaries of the status of each server - * ({@link RouterStatusEntry}).</p> - * - * <p>Clients in that version of the directory protocol would fetch this - * signed directory to get up-to-date information on the state of the - * network and be certain that the list was attested by a trusted - * directory authority.</p> - * - * <p>Signed directories in the version 1 directory protocol have first - * been superseded by network status documents in the version 2 directory - * protocol ({@link RelayNetworkStatus}) and later by network status - * consensuses ({@link RelayNetworkStatusConsensus}) in the version 3 - * directory protocol.</p> - * - * @since 1.0.0 - */ -public interface RelayDirectory extends Descriptor { - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the RSA-1024 public key in PEM format used by this authority - * as long-term identity key and to sign network statuses, or null if - * this key is not included in the descriptor header. - * - * @since 1.0.0 - */ - public String getDirSigningKey(); - - /** - * Return recommended Tor versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedSoftware(); - - /** - * Return the directory signature string made with the authority's - * identity key. - * - * @since 1.0.0 - */ - public String getDirectorySignature(); - - /** - * Return router status entries, one for each contained relay. - * - * @since 1.0.0 - */ - public List<RouterStatusEntry> getRouterStatusEntries(); - - /** - * Return a list of server descriptors contained in the signed - * directory. - * - * @since 1.0.0 - */ - public List<ServerDescriptor> getServerDescriptors(); - - /** - * Return a (very likely empty) list of exceptions from parsing the - * contained server descriptors. - * - * @since 1.0.0 - */ - public List<Exception> getServerDescriptorParseExceptions(); - - /** - * Return the directory nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return the SHA-1 directory digest, encoded as 40 lower-case - * hexadecimal characters, that the directory authority used to sign the - * directory. - * - * @since 1.0.0 - */ - public String getDirectoryDigest(); -} - diff --git a/src/org/torproject/descriptor/RelayExtraInfoDescriptor.java b/src/org/torproject/descriptor/RelayExtraInfoDescriptor.java deleted file mode 100644 index 73f8438..0000000 --- a/src/org/torproject/descriptor/RelayExtraInfoDescriptor.java +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright 2015--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a relay extra-info descriptor. - * - * <p>Relay extra-info descriptors share many contents with sanitized - * bridge extra-info descriptors ({@link BridgeExtraInfoDescriptor}), - * which is why they share a common superinterface - * ({@link ExtraInfoDescriptor}). The main purpose of having two - * subinterfaces is being able to distinguish descriptor types more - * easily.</p> - * - * @since 1.1.0 - */ -public interface RelayExtraInfoDescriptor extends ExtraInfoDescriptor { - -} - diff --git a/src/org/torproject/descriptor/RelayNetworkStatus.java b/src/org/torproject/descriptor/RelayNetworkStatus.java deleted file mode 100644 index db3ddac..0000000 --- a/src/org/torproject/descriptor/RelayNetworkStatus.java +++ /dev/null @@ -1,176 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.SortedMap; -import java.util.SortedSet; - -/** - * Contains a network status document in the version 2 directory protocol. - * - * <p>Directory authorities in the (outdated) version 2 of the directory - * protocol published signed network status documents. Each network - * status listed, for every relay in the network - * ({@link NetworkStatusEntry}): a hash of its identity key, a hash of its - * most recent server descriptor, and a summary of what the authority - * believed about its status.</p> - * - * <p>Clients would download the authorities' network status documents in - * turn, and believe statements about routers iff they were attested to by - * more than half of the authorities.</p> - * - * <p>Network status documents in the version 2 directory protocol - * supersede signed directories in the version 1 directory protocol - * ({@link RelayDirectory}) and have been superseded by network status - * consensuses ({@link RelayNetworkStatusConsensus}) in the version 3 - * directory protocol.</p> - * - * @since 1.0.0 - */ -public interface RelayNetworkStatus extends Descriptor { - - /** - * Return the document format version of this descriptor which is 2. - * - * @since 1.0.0 - */ - public int getNetworkStatusVersion(); - - /** - * Return the authority's hostname. - * - * @since 1.0.0 - */ - public String getHostname(); - - /** - * Return the authority's primary IPv4 address in dotted-quad format, - * or null if the descriptor does not contain an address. - * - * @since 1.0.0 - */ - public String getAddress(); - - /** - * Return the TCP port where this authority accepts directory-related - * HTTP connections, or 0 if the authority does not accept such - * connections. - * - * @since 1.0.0 - */ - public int getDirport(); - - /** - * Return a SHA-1 digest of the authority's public identity key, - * encoded as 40 upper-case hexadecimal characters, which is also used - * to sign network statuses. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return the contact information for this authority, which may contain - * non-ASCII characters. - * - * @since 1.0.0 - */ - public String getContactLine(); - - /** - * Return the RSA-1024 public key in PEM format used by this authority - * as long-term identity key and to sign network statuses. - * - * @since 1.0.0 - */ - public String getDirSigningKey(); - - /** - * Return recommended Tor versions for server usage, or null if the - * authority does not recommend server versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedServerVersions(); - - /** - * Return recommended Tor versions for client usage, or null if the - * authority does not recommend client versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedClientVersions(); - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the set of flags that this directory assigns to relays, or - * null if the status does not assign such flags. - * - * @since 1.0.0 - */ - public SortedSet<String> getDirOptions(); - - /** - * Return status entries for each contained server, with map keys being - * SHA-1 digests of the servers' public identity keys, encoded as 40 - * upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public SortedMap<String, NetworkStatusEntry> getStatusEntries(); - - /** - * Return whether a status entry with the given relay fingerprint - * (SHA-1 digest of the server's public identity key, encoded as 40 - * upper-case hexadecimal characters) exists; convenience method for - * {@code getStatusEntries().containsKey(fingerprint)}. - * - * @since 1.0.0 - */ - public boolean containsStatusEntry(String fingerprint); - - /** - * Return a status entry by relay fingerprint (SHA-1 digest of the - * server's public identity key, encoded as 40 upper-case hexadecimal - * characters), or null if no such status entry exists; convenience - * method for {@code getStatusEntries().get(fingerprint)}. - * - * @since 1.0.0 - */ - public NetworkStatusEntry getStatusEntry(String fingerprint); - - /** - * Return the authority's nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return the directory signature string made with the authority's - * identity key. - * - * @since 1.0.0 - */ - public String getDirectorySignature(); - - /** - * Return the SHA-1 status digest, encoded as 40 lower-case hexadecimal - * characters, that the directory authority used to sign the network - * status. - * - * @since 1.0.0 - */ - public String getStatusDigest(); -} - diff --git a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java deleted file mode 100644 index 15fdaca..0000000 --- a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java +++ /dev/null @@ -1,223 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.SortedMap; -import java.util.SortedSet; - -/** - * Contains a network status consensus in the version 3 directory protocol. - * - * <p>Directory authorities in the version 3 of the directory protocol - * periodically generate a view of the current descriptors and status for - * known relays and send a signed summary of this view to the other - * authorities ({@link RelayNetworkStatusVote}). The authorities compute - * the result of this vote and sign a network status consensus containing - * the result of the vote, which is this document.</p> - * - * <p>Clients use consensus documents to find out when their list of - * relays is out-of-date by looking at the contained network status - * entries ({@link NetworkStatusEntry}). If it is, they download any - * missing server descriptors ({@link ServerDescriptor}).</p> - * - * @since 1.0.0 - */ -public interface RelayNetworkStatusConsensus extends Descriptor { - - /** - * Return the document format version of this descriptor which is 3 or - * higher. - * - * @since 1.0.0 - */ - public int getNetworkStatusVersion(); - - /** - * Return the consensus flavor name, which denotes the variant of the - * original, unflavored consensus, encoded as a string of alphanumeric - * characters and dashes, or null if this descriptor is the unflavored - * consensus. - * - * @since 1.0.0 - */ - public String getConsensusFlavor(); - - /** - * Return the consensus method number of this descriptor, which is the - * highest consensus method supported by more than 2/3 of voting - * authorities, or 0 if no consensus method is contained in the - * descriptor. - * - * @since 1.0.0 - */ - public int getConsensusMethod(); - - /** - * Return the time in milliseconds since the epoch at which this - * descriptor became valid. - * - * @since 1.0.0 - */ - public long getValidAfterMillis(); - - /** - * Return the time in milliseconds since the epoch until which this - * descriptor is the freshest that is available. - * - * @since 1.0.0 - */ - public long getFreshUntilMillis(); - - /** - * Return the time in milliseconds since the epoch until which this - * descriptor was valid. - * - * @since 1.0.0 - */ - public long getValidUntilMillis(); - - /** - * Return the number of seconds that the directory authorities will - * allow to collect votes from the other authorities when producing the - * next consensus. - * - * @since 1.0.0 - */ - public long getVoteSeconds(); - - /** - * Return the number of seconds that the directory authorities will - * allow to collect signatures from the other authorities when producing - * the next consensus. - * - * @since 1.0.0 - */ - public long getDistSeconds(); - - /** - * Return recommended Tor versions for server usage, or null if the - * consensus does not contain an opinion about server versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedServerVersions(); - - /** - * Return recommended Tor versions for client usage, or null if the - * consensus does not contain an opinion about client versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedClientVersions(); - - /** - * Return a list of software packages and their versions together with a - * URL and one or more digests in the format <code>PackageName Version - * URL DIGESTS</code> that are known by at least three directory - * authorities and agreed upon by the majority of directory authorities, - * or null if the consensus does not contain package information. - * - * @since 1.3.0 - */ - public List<String> getPackageLines(); - - /** - * Return known relay flags in this descriptor that were contained in - * enough votes for this consensus to be an authoritative opinion for - * these relay flags. - * - * @since 1.0.0 - */ - public SortedSet<String> getKnownFlags(); - - /** - * Return consensus parameters contained in this descriptor with map - * keys being case-sensitive parameter identifiers and map values being - * parameter values, or null if the consensus doesn't contain consensus - * parameters. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getConsensusParams(); - - /** - * Return directory source entries for each directory authority that - * contributed to the consensus, with map keys being SHA-1 digests of - * the authorities' identity keys in the version 3 directory protocol, - * encoded as 40 upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public SortedMap<String, DirSourceEntry> getDirSourceEntries(); - - /** - * Return status entries for each contained server, with map keys being - * SHA-1 digests of the servers' public identity keys, encoded as 40 - * upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public SortedMap<String, NetworkStatusEntry> getStatusEntries(); - - /** - * Return whether a status entry with the given relay fingerprint - * (SHA-1 digest of the server's public identity key, encoded as 40 - * upper-case hexadecimal characters) exists; convenience method for - * {@code getStatusEntries().containsKey(fingerprint)}. - * - * @since 1.0.0 - */ - public boolean containsStatusEntry(String fingerprint); - - /** - * Return a status entry by relay fingerprint (SHA-1 digest of the - * server's public identity key, encoded as 40 upper-case hexadecimal - * characters), or null if no such status entry exists; convenience - * method for {@code getStatusEntries().get(fingerprint)}. - * - * @since 1.0.0 - */ - public NetworkStatusEntry getStatusEntry(String fingerprint); - - /** - * Return directory signatures of this consensus, with map keys being - * SHA-1 digests of the authorities' identity keys in the version 3 - * directory protocol, encoded as 40 upper-case hexadecimal characters. - * - * @deprecated Replaced by {@link #getSignatures()} which permits an - * arbitrary number of signatures made by an authority using the same - * identity key digest and different algorithms. - * - * @since 1.0.0 - */ - public SortedMap<String, DirectorySignature> getDirectorySignatures(); - - /** - * Return the list of signatures contained in this consensus. - * - * @since 1.3.0 - */ - public List<DirectorySignature> getSignatures(); - - /** - * Return optional weights to be applied to router bandwidths during - * path selection with map keys being case-sensitive weight identifiers - * and map values being weight values, or null if the consensus doesn't - * contain such weights. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getBandwidthWeights(); - - /** - * Return the SHA-1 digest of this consensus, encoded as 40 upper-case - * hexadecimal characters that directory authorities use to sign the - * consensus. - * - * @since 1.0.0 - */ - public String getConsensusDigest(); -} - diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java deleted file mode 100644 index 1f77db6..0000000 --- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java +++ /dev/null @@ -1,408 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.SortedMap; -import java.util.SortedSet; - -/** - * Contains a network status vote in the version 3 directory protocol. - * - * <p>Directory authorities in the version 3 of the directory protocol - * periodically generate a view of the current descriptors and status for - * known relays and send a signed summary of this view to the other - * authorities, which is this document. The authorities compute the - * result of this vote and sign a network status consensus containing the - * result of the vote ({@link RelayNetworkStatusConsensus}).</p> - * - * @since 1.0.0 - */ -public interface RelayNetworkStatusVote extends Descriptor { - - /** - * Return the document format version of this descriptor which is 3 or - * higher. - * - * @since 1.0.0 - */ - public int getNetworkStatusVersion(); - - /** - * Return the list of consensus method numbers supported by this - * authority, or null if the descriptor doesn't say so, which would mean - * that only method 1 is supported. - * - * @since 1.0.0 - */ - public List<Integer> getConsensusMethods(); - - /** - * Return the time in milliseconds since the epoch when this descriptor - * was published. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return the time in milliseconds since the epoch at which the - * consensus is supposed to become valid. - * - * @since 1.0.0 - */ - public long getValidAfterMillis(); - - /** - * Return the time in milliseconds since the epoch until which the - * consensus is supposed to be the freshest that is available. - * - * @since 1.0.0 - */ - public long getFreshUntilMillis(); - - /** - * Return the time in milliseconds since the epoch until which the - * consensus is supposed to be valid. - * - * @since 1.0.0 - */ - public long getValidUntilMillis(); - - /** - * Return the number of seconds that the directory authorities will - * allow to collect votes from the other authorities when producing the - * next consensus. - * - * @since 1.0.0 - */ - public long getVoteSeconds(); - - /** - * Return the number of seconds that the directory authorities will - * allow to collect signatures from the other authorities when producing - * the next consensus. - * - * @since 1.0.0 - */ - public long getDistSeconds(); - - /** - * Return recommended Tor versions for server usage, or null if the - * authority does not recommend server versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedServerVersions(); - - /** - * Return recommended Tor versions for client usage, or null if the - * authority does not recommend client versions. - * - * @since 1.0.0 - */ - public List<String> getRecommendedClientVersions(); - - /** - * Return a list of software packages and their versions together with a - * URL and one or more digests in the format <code>PackageName Version - * URL DIGESTS</code> that are known by this directory authority, or - * null if this descriptor does not contain package information. - * - * @since 1.3.0 - */ - public List<String> getPackageLines(); - - /** - * Return known relay flags by this authority. - * - * @since 1.0.0 - */ - public SortedSet<String> getKnownFlags(); - - /** - * Return the minimum uptime in seconds that this authority requires - * for assigning the Stable flag, or -1 if the authority doesn't report - * this value. - * - * @since 1.0.0 - */ - public long getStableUptime(); - - /** - * Return the minimum MTBF (mean time between failure) that this - * authority requires for assigning the Stable flag, or -1 if the - * authority doesn't report this value. - * - * @since 1.0.0 - */ - public long getStableMtbf(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Fast flag, or -1 if the authority doesn't report this - * value. - * - * @since 1.0.0 - */ - public long getFastBandwidth(); - - /** - * Return the minimum WFU (weighted fractional uptime) in percent that - * this authority requires for assigning the Guard flag, or -1 if the - * authority doesn't report this value. - * - * @since 1.0.0 - */ - public double getGuardWfu(); - - /** - * Return the minimum weighted time in seconds that this authority - * needs to know about a relay before assigning the Guard flag, or -1 if - * the authority doesn't report this information. - * - * @since 1.0.0 - */ - public long getGuardTk(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Guard flag if exits can be guards, or -1 if the - * authority doesn't report this value. - * - * @since 1.0.0 - */ - public long getGuardBandwidthIncludingExits(); - - /** - * Return the minimum bandwidth that this authority requires for - * assigning the Guard flag if exits can not be guards, or -1 if the - * authority doesn't report this value. - * - * @since 1.0.0 - */ - public long getGuardBandwidthExcludingExits(); - - /** - * Return 1 if the authority has measured enough MTBF info to use the - * MTBF requirement instead of the uptime requirement for assigning the - * Stable flag, 0 if not, or -1 if the authority doesn't report this - * information. - * - * @since 1.0.0 - */ - public int getEnoughMtbfInfo(); - - /** - * Return 1 if the authority has enough measured bandwidths that it'll - * ignore the advertised bandwidth claims of routers without measured - * bandwidth, 0 if not, or -1 if the authority doesn't report this - * information. - * - * @since 1.1.0 - */ - public int getIgnoringAdvertisedBws(); - - /** - * Return consensus parameters contained in this descriptor with map - * keys being case-sensitive parameter identifiers and map values being - * parameter values, or null if the authority doesn't include consensus - * parameters in its vote. - * - * @since 1.0.0 - */ - public SortedMap<String, Integer> getConsensusParams(); - - /** - * Return the authority's nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return a SHA-1 digest of the authority's long-term authority - * identity key used for the version 3 directory protocol, encoded as - * 40 upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public String getIdentity(); - - /** - * Return the authority's hostname. - * - * @since 1.2.0 - */ - public String getHostname(); - - /** - * Return the authority's primary IPv4 address in dotted-quad format, - * or null if the descriptor does not contain an address. - * - * @since 1.0.0 - */ - public String getAddress(); - - /** - * Return the TCP port where this authority accepts directory-related - * HTTP connections, or 0 if the authority does not accept such - * connections. - * - * @since 1.0.0 - */ - public int getDirport(); - - /** - * Return the TCP port where this authority accepts TLS connections for - * the main OR protocol, or 0 if the authority does not accept such - * connections. - * - * @since 1.0.0 - */ - public int getOrport(); - - /** - * Return the contact information for this authority, which may contain - * non-ASCII characters, or null if no contact information is included - * in the descriptor. - * - * @since 1.0.0 - */ - public String getContactLine(); - - /** - * Return the version of the directory key certificate used by this - * authority, which must be 3 or higher. - * - * @since 1.0.0 - */ - public int getDirKeyCertificateVersion(); - - /** - * Return the SHA-1 digest for an obsolete authority identity key still - * used by this authority to keep older clients working, or null if this - * authority does not use such a key. - * - * @since 1.0.0 - */ - public String getLegacyDirKey(); - - /** - * Return the authority's identity key in PEM format. - * - * @since 1.2.0 - */ - public String getDirIdentityKey(); - - /** - * Return the time in milliseconds since the epoch when the authority's - * signing key and corresponding key certificate were generated. - * - * @since 1.0.0 - */ - public long getDirKeyPublishedMillis(); - - /** - * Return the time in milliseconds since the epoch after which the - * authority's signing key is no longer valid. - * - * @since 1.0.0 - */ - public long getDirKeyExpiresMillis(); - - /** - * Return the authority's signing key in PEM format. - * - * @since 1.2.0 - */ - public String getDirSigningKey(); - - /** - * Return the SHA-1 digest of the authority's signing key, encoded as - * 40 upper-case hexadecimal characters, or null if this digest cannot - * be obtained from the directory signature. - * - * @deprecated Removed in order to be more explicit that authorities may - * use different digest algorithms than "sha1"; see - * {@link #getSignatures()} and - * {@link DirectorySignature#getSigningKeyDigest()} for - * alternatives. - * - * @since 1.0.0 - */ - public String getSigningKeyDigest(); - - /** - * Return the signature of the authority's identity key made using the - * authority's signing key, or null if the vote does not contain such a - * signature. - * - * @since 1.2.0 - */ - public String getDirKeyCrosscert(); - - /** - * Return the certificate signature from the initial item - * "dir-key-certificate-version" until the final item - * "dir-key-certification", signed with the authority identity key. - * - * @since 1.2.0 - */ - public String getDirKeyCertification(); - - /** - * Return status entries for each contained server, with map keys being - * SHA-1 digests of the servers' public identity keys, encoded as 40 - * upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public SortedMap<String, NetworkStatusEntry> getStatusEntries(); - - /** - * Return whether a status entry with the given relay fingerprint - * (SHA-1 digest of the server's public identity key, encoded as 40 - * upper-case hexadecimal characters) exists; convenience method for - * {@code getStatusEntries().containsKey(fingerprint)}. - * - * @since 1.0.0 - */ - public boolean containsStatusEntry(String fingerprint); - - /** - * Return a status entry by relay fingerprint (SHA-1 digest of the - * server's public identity key, encoded as 40 upper-case hexadecimal - * characters), or null if no such status entry exists; convenience - * method for {@code getStatusEntries().get(fingerprint)}. - * - * @since 1.0.0 - */ - public NetworkStatusEntry getStatusEntry(String fingerprint); - - /** - * Return the directory signature of this vote, with the single map key - * being the SHA-1 digest of the authority's identity key in the version - * 3 directory protocol, encoded as 40 upper-case hexadecimal - * characters. - * - * @deprecated Replaced by {@link #getSignatures()} which permits an - * arbitrary number of signatures made by the authority using the same - * identity key digest and different algorithms. - * - * @since 1.0.0 - */ - public SortedMap<String, DirectorySignature> getDirectorySignatures(); - - /** - * Return a list of signatures contained in this vote, which is - * typically a single signature made by the authority but which may also - * be more than one signature made with different keys or algorithms. - * - * @since 1.3.0 - */ - public List<DirectorySignature> getSignatures(); -} - diff --git a/src/org/torproject/descriptor/RelayServerDescriptor.java b/src/org/torproject/descriptor/RelayServerDescriptor.java deleted file mode 100644 index 6ef3140..0000000 --- a/src/org/torproject/descriptor/RelayServerDescriptor.java +++ /dev/null @@ -1,20 +0,0 @@ -/* Copyright 2015--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a relay server descriptor. - * - * <p>Relay server descriptors share many contents with sanitized bridge - * server descriptors ({@link BridgeServerDescriptor}), which is why they - * share a common superinterface ({@link ServerDescriptor}). The main - * purpose of having two subinterfaces is being able to distinguish - * descriptor types more easily.</p> - * - * @since 1.1.0 - */ -public interface RelayServerDescriptor extends ServerDescriptor { - -} - diff --git a/src/org/torproject/descriptor/RouterStatusEntry.java b/src/org/torproject/descriptor/RouterStatusEntry.java deleted file mode 100644 index f9a56db..0000000 --- a/src/org/torproject/descriptor/RouterStatusEntry.java +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -/** - * Contains a router status entry contained in a signed directory in the - * version 1 directory protocol. - * - * <p>Directory authorities in the (long outdated) version 1 of the - * directory protocol included router status entries with short summaries - * of the status of each server in the signed directories they produced - * ({@link RelayDirectory}). These entries contained references to server - * descriptors published by relays together with the authorities' opinion - * on whether relays were verified and live.</p> - * - * @since 1.0.0 - */ -public interface RouterStatusEntry { - - /** - * Return the relay nickname consisting of 1 to 19 alphanumeric - * characters, or null if the relay is unverified. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return a SHA-1 digest of the relay's identity key, encoded as 40 - * upper-case hexadecimal characters. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return whether the relay is verified. - * - * @since 1.0.0 - */ - public boolean isVerified(); - - /** - * Return whether the relay is live. - * - * @since 1.0.0 - */ - public boolean isLive(); -} - diff --git a/src/org/torproject/descriptor/ServerDescriptor.java b/src/org/torproject/descriptor/ServerDescriptor.java deleted file mode 100644 index d1af421..0000000 --- a/src/org/torproject/descriptor/ServerDescriptor.java +++ /dev/null @@ -1,435 +0,0 @@ -/* Copyright 2011--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; - -/** - * Contains a relay or sanitized bridge server descriptor. - * - * <p>Relays publish server descriptors to the directory authorities to - * register in the network. Server descriptors contain information about - * the capabilities of a server, like their exit policy, that clients use - * to select servers for their circuits (along with information provided - * by directory authorities on reachability, stability, and capacity of - * servers). Server descriptors also contain network addresses and - * cryptographic material that clients use to build circuits.</p> - * - * <p>Prior to the introduction of microdescriptors - * ({@link Microdescriptor}), the directory authorities included - * cryptographic digests of server descriptors in network statuses - * ({@link RelayNetworkStatusConsensus}) and clients downloaded all - * referenced server descriptors. Nowadays, the directory authorities - * derive microdescriptors from server descriptors and reference those - * in network statuses, and clients only download microdescriptors instead - * of server descriptors.</p> - * - * <p>Bridges publish server descriptors to the bridge directory - * authority, also to announce themselves in the network. The bridge - * directory authority compiles a list of available bridges - * ({@link BridgeNetworkStatus}) for the bridge distribution service - * BridgeDB. There are no microdescriptors for bridges, so that bridge - * clients still rely on downloading bridge server descriptors directly - * from the bridge they're connecting to.</p> - * - * <p>It's worth noting that all contents of server descriptors are - * written and signed by relays and bridges without a third party - * verifying their correctness. The (bridge) directory authorities may - * decide to exclude dishonest servers from the network statuses they - * produce, but that wouldn't be reflected in server descriptors.</p> - * - * @since 1.0.0 - */ -public interface ServerDescriptor extends Descriptor { - - /** - * Return the SHA-1 descriptor digest, encoded as 40 lower-case (relay - * descriptors) or upper-case (bridge descriptors) hexadecimal - * characters, that is used to reference this descriptor from a network - * status descriptor. - * - * @since 1.0.0 - */ - public String getServerDescriptorDigest(); - - /** - * Return the SHA-256 descriptor digest, encoded as 43 base64 - * characters without padding characters, that may be used to reference - * this server descriptor from a network status descriptor. - * - * @since 1.1.0 - */ - public String getServerDescriptorDigestSha256(); - - /** - * Return the server's nickname consisting of 1 to 19 alphanumeric - * characters. - * - * @since 1.0.0 - */ - public String getNickname(); - - /** - * Return the server's primary IPv4 address in dotted-quad format. - * - * @since 1.0.0 - */ - public String getAddress(); - - /** - * Return the TCP port where this server accepts TLS connections for - * the main OR protocol, or 0 if the server does not accept such - * connections. - * - * @since 1.0.0 - */ - public int getOrPort(); - - /** - * Return the TCP port where this server accepts SOCKS connections, - * which is deprecated and should always be 0. - * - * @since 1.0.0 - */ - public int getSocksPort(); - - /** - * Return the TCP port where this server accepts directory-related HTTP - * connections, or 0 if the server does not accept such connections. - * - * @since 1.0.0 - */ - public int getDirPort(); - - /** - * Return IP addresses and TCP ports where this server accepts TLS - * connections for the main OR protocol, or an empty list if the server - * does not support additional addresses or ports; entries are given in - * the order as they are listed in the descriptor; IPv4 addresses are - * given in dotted-quad format, IPv6 addresses use the colon-separated - * hexadecimal format surrounded by square brackets, and TCP ports are - * separated from the IP address using a colon. - * - * @since 1.0.0 - */ - public List<String> getOrAddresses(); - - /** - * Return the average bandwidth in bytes per second that the server is - * willing to sustain over long periods. - * - * @since 1.0.0 - */ - public int getBandwidthRate(); - - /** - * Return the burst bandwidth in bytes per second that the server is - * willing to sustain in very short intervals. - * - * @since 1.0.0 - */ - public int getBandwidthBurst(); - - /** - * Return the observed bandwidth in bytes per second as an estimate of - * the capacity that the server can handle, or -1 if the descriptor - * doesn't contain an observed bandwidth value (which is the case for - * Tor 0.0.8 or older). - * - * @since 1.0.0 - */ - public int getBandwidthObserved(); - - /** - * Return a human-readable string describing the Tor software version - * and the operating system of this server, which may contain non-ASCII - * characters, typically written as {@code "Tor $version on $system"}, - * or null if this descriptor does not contain a platform line. - * - * @since 1.0.0 - */ - public String getPlatform(); - - /** - * Return the time in milliseconds since the epoch when this descriptor - * and the corresponding extra-info descriptor were generated. - * - * @since 1.0.0 - */ - public long getPublishedMillis(); - - /** - * Return a SHA-1 digest of the server's public identity key, encoded - * as 40 upper-case hexadecimal characters (without spaces after every 4 - * characters as opposed to the encoding in the descriptor), that is - * typically used to uniquely identify the server, or null if this - * descriptor does not contain a fingerprint line. - * - * @since 1.0.0 - */ - public String getFingerprint(); - - /** - * Return whether the server was hibernating when this descriptor was - * published and should not be used to build circuits. - * - * @since 1.0.0 - */ - public boolean isHibernating(); - - /** - * Return the number of seconds that the server process has been - * running (which might even be negative in a few descriptors due to a - * bug that was fixed in Tor 0.1.2.7-alpha), or null if the descriptor - * does not contain an uptime line. - * - * @since 1.0.0 - */ - public Long getUptime(); - - /** - * Return the RSA-1024 public key in PEM format used to encrypt CREATE - * cells for this server, or null if the descriptor doesn't contain an - * onion key (which is the case in sanitized bridge descriptors). - * - * @since 1.0.0 - */ - public String getOnionKey(); - - /** - * Return the RSA-1024 public key in PEM format used by this server as - * long-term identity key, or null if the descriptor doesn't contain a - * signing key (which is the case in sanitized bridge descriptors). - * - * @since 1.0.0 - */ - public String getSigningKey(); - - /** - * Return the server's exit policy consisting of one or more accept or - * reject rules that the server follows when deciding whether to allow a - * new stream to a given IP address and TCP port. - * - * @since 1.0.0 - */ - public List<String> getExitPolicyLines(); - - /** - * Return the RSA-1024 signature of the PKCS1-padded descriptor digest, - * taken from the beginning of the router line through the newline after - * the router-signature line, or null if the descriptor doesn't contain - * a signature (which is the case in sanitized bridge descriptors). - * - * @since 1.0.0 - */ - public String getRouterSignature(); - - /** - * Return the contact information for this server, which may contain - * non-ASCII characters, or null if no contact information is included - * in the descriptor. - * - * @since 1.0.0 - */ - public String getContact(); - - /** - * Return nicknames, $-prefixed identity fingerprints, or tuples of the - * format {@code $fingerprint=nickname} or {@code $fingerprint~nickname} - * of servers contained in this server's family, or null if the - * descriptor does not contain a family line. - * - * @since 1.0.0 - */ - public List<String> getFamilyEntries(); - - /** - * Return the server's history of read bytes, or null if the descriptor - * does not contain a bandwidth history; current Tor versions include - * bandwidth histories in their extra-info descriptors - * ({@link ExtraInfoDescriptor#getReadHistory()}), not in their server - * descriptors. - * - * @since 1.0.0 - */ - public BandwidthHistory getReadHistory(); - - /** - * Return the server's history of written bytes, or null if the - * descriptor does not contain a bandwidth history; current Tor versions - * include bandwidth histories in their extra-info descriptors - * ({@link ExtraInfoDescriptor#getWriteHistory()}), not in their server - * descriptors. - * - * @since 1.0.0 - */ - public BandwidthHistory getWriteHistory(); - - /** - * Return true if the server uses the enhanced DNS logic, or false if - * doesn't use it or doesn't include an eventdns line in its - * descriptor; current Tor versions should be presumed to have the evdns - * backend. - * - * @since 1.0.0 - */ - public boolean getUsesEnhancedDnsLogic(); - - /** - * Return whether this server is a directory cache that provides - * extra-info descriptors. - * - * @since 1.0.0 - */ - public boolean getCachesExtraInfo(); - - /** - * Return the SHA-1 digest of the server's extra-info descriptor, - * encoded as 40 upper-case hexadecimal characters, or null if the - * server did not upload a corresponding extra-info descriptor. - * - * @since 1.0.0 - */ - public String getExtraInfoDigest(); - - /** - * Return the SHA-256 digest of the server's extra-info descriptor, - * encoded as 43 base64 characters without padding characters, or null - * if the server either did not upload a corresponding extra-info - * descriptor or did not refer to it using a SHA-256 digest. - * - * @since 1.1.0 - */ - public String getExtraInfoDigestSha256(); - - /** - * Return the list of hidden service descriptor version numbers that - * this server stores and serves, or null if it doesn't store and serve - * any hidden service descriptors. - * - * @since 1.0.0 - */ - public List<Integer> getHiddenServiceDirVersions(); - - /** - * Return the list of link protocol versions that this server - * supports. - * - * @since 1.0.0 - */ - public List<Integer> getLinkProtocolVersions(); - - /** - * Return the list of circuit protocol versions that this server - * supports. - * - * @since 1.0.0 - */ - public List<Integer> getCircuitProtocolVersions(); - - /** - * Return whether this server allows single-hop circuits to make exit - * connections. - * - * @since 1.0.0 - */ - public boolean getAllowSingleHopExits(); - - /** - * Return the default policy, {@code "accept"} or {@code "reject"}, of - * the IPv6 port summary, or null if the descriptor didn't contain an - * IPv6 exit-policy summary line which is equivalent to rejecting all - * streams to IPv6 targets. - * - * @since 1.0.0 - */ - public String getIpv6DefaultPolicy(); - - /** - * Return the port list of the IPv6 exit-policy summary, or null if the - * descriptor didn't contain an IPv6 exit-policy summary line which is - * equivalent to rejecting all streams to IPv6 targets. - * - * @since 1.0.0 - */ - public String getIpv6PortList(); - - /** - * Return the curve25519 public key, encoded as 43 base64 characters - * without padding characters, that is used for the ntor circuit - * extended handshake, or null if the descriptor didn't contain an - * ntor-onion-key line. */ - public String getNtorOnionKey(); - - /** - * Return the Ed25519 certificate in PEM format, or null if the - * descriptor doesn't contain one. - * - * @since 1.1.0 - */ - public String getIdentityEd25519(); - - /** - * Return the Ed25519 master key, encoded as 43 base64 characters - * without padding characters, which was either parsed from the optional - * {@code "master-key-ed25519"} line or derived from the (likewise - * optional) Ed25519 certificate following the - * {@code "identity-ed25519"} line, or null if the descriptor contains - * neither Ed25519 master key nor Ed25519 certificate. - * - * @since 1.1.0 - */ - public String getMasterKeyEd25519(); - - /** - * Return the Ed25519 signature of the SHA-256 digest of the entire - * descriptor, encoded as 86 base64 characters without padding - * characters, from the first character up to and including the first - * space after the {@code "router-sig-ed25519"} string, prefixed with - * the string {@code "Tor router descriptor signature v1"}. - * - * @since 1.1.0 - */ - public String getRouterSignatureEd25519(); - - /** - * Return an RSA-1024 signature in PEM format, generated using the - * server's onion key, that proves that the party creating the - * descriptor had control over the private key corresponding to the - * onion key, or null if the descriptor does not contain such a - * signature. - * - * @since 1.1.0 - */ - public String getOnionKeyCrosscert(); - - /** - * Return an Ed25519 signature in PEM format, generated using the - * server's ntor onion key, that proves that the party creating the - * descriptor had control over the private key corresponding to the ntor - * onion key, or null if the descriptor does not contain such a - * signature. - * - * @since 1.1.0 - */ - public String getNtorOnionKeyCrosscert(); - - /** - * Return the sign of the Ed25519 public key corresponding to the ntor - * onion key as 0 or 1, or -1 if the descriptor does not contain this - * information. - * - * @since 1.1.0 - */ - public int getNtorOnionKeyCrosscertSign(); - - /** - * Return whether the server accepts "tunneled" directory requests using - * a BEGIN_DIR cell over the server's OR port. - * - * @since 1.3.0 - */ - public boolean getTunnelledDirServer(); -} - diff --git a/src/org/torproject/descriptor/TorperfResult.java b/src/org/torproject/descriptor/TorperfResult.java deleted file mode 100644 index 188200b..0000000 --- a/src/org/torproject/descriptor/TorperfResult.java +++ /dev/null @@ -1,215 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ - -package org.torproject.descriptor; - -import java.util.List; -import java.util.SortedMap; - -/** - * Contains performance measurement results from making simple HTTP - * requests over the Tor network. - * - * <p>The performance measurement service Torperf publishes performance - * data from making simple HTTP requests over the Tor network. Torperf - * uses a trivial SOCKS client to download files of various sizes over the - * Tor network and notes how long substeps take.</p> - * - * @since 1.0.0 - */ -public interface TorperfResult extends Descriptor { - - /** - * Return all unrecognized keys together with their values, or null if - * all keys were recognized. - * - * @since 1.2.0 - */ - public SortedMap<String, String> getUnrecognizedKeys(); - - /** - * Return the configured name of the data source. - * - * @since 1.0.0 - */ - public String getSource(); - - /** - * Return the configured file size in bytes. - * - * @since 1.0.0 - */ - public int getFileSize(); - - /** - * Return the time in milliseconds since the epoch when the connection - * process started. - * - * @since 1.0.0 - */ - public long getStartMillis(); - - /** - * Return the time in milliseconds since the epoch when the socket was - * created. - * - * @since 1.0.0 - */ - public long getSocketMillis(); - - /** - * Return the time in milliseconds since the epoch when the socket was - * connected. - * - * @since 1.0.0 - */ - public long getConnectMillis(); - - /** - * Return the time in milliseconds since the epoch when SOCKS 5 - * authentication methods have been negotiated. - * - * @since 1.0.0 - */ - public long getNegotiateMillis(); - - /** - * Return the time in milliseconds since the epoch when the SOCKS - * request was sent. - * - * @since 1.0.0 - */ - public long getRequestMillis(); - - /** - * Return the time in milliseconds since the epoch when the SOCKS - * response was received. - * - * @since 1.0.0 - */ - public long getResponseMillis(); - - /** - * Return the time in milliseconds since the epoch when the HTTP - * request was written. - * - * @since 1.0.0 - */ - public long getDataRequestMillis(); - - /** - * Return the time in milliseconds since the epoch when the first - * response was received. - * - * @since 1.0.0 - */ - public long getDataResponseMillis(); - - /** - * Return the time in milliseconds since the epoch when the payload was - * complete. - * - * @since 1.0.0 - */ - public long getDataCompleteMillis(); - - /** - * Return the total number of bytes written. - * - * @since 1.0.0 - */ - public int getWriteBytes(); - - /** - * Return the total number of bytes read. - * - * @since 1.0.0 - */ - public int getReadBytes(); - - /** - * Return whether the request timed out (as opposed to failing), or - * null if the torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public Boolean didTimeout(); - - /** - * Return the times in milliseconds since the epoch when {@code x%} of - * expected bytes were read for {@code 0 <= x <= 100}, or null if the - * torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public SortedMap<Integer, Long> getDataPercentiles(); - - /** - * Return the time in milliseconds since the epoch when the circuit was - * launched, or -1 if the torperf line didn't contain that - * information. - * - * @since 1.0.0 - */ - public long getLaunchMillis(); - - /** - * Return the time in milliseconds since the epoch when the circuit was - * used, or -1 if the torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public long getUsedAtMillis(); - - /** - * Return a list of fingerprints of the relays in the circuit, or null - * if the torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public List<String> getPath(); - - /** - * Return a list of times in milliseconds since the epoch when circuit - * hops were built, or null if the torperf line didn't contain that - * information. - * - * @since 1.0.0 - */ - public List<Long> getBuildTimes(); - - /** - * Return the circuit build timeout that the Tor client used when - * building this circuit, or -1 if the torperf line didn't contain that - * information. - * - * @since 1.0.0 - */ - public long getTimeout(); - - /** - * Return the circuit build time quantile that the Tor client uses to - * determine its circuit-build timeout, or -1 if the torperf line - * didn't contain that information. - * - * @since 1.0.0 - */ - public double getQuantile(); - - /** - * Return the identifier of the circuit used for this measurement, or - * -1 if the torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public int getCircId(); - - /** - * Return the identifier of the stream used for this measurement, or -1 - * if the torperf line didn't contain that information. - * - * @since 1.0.0 - */ - public int getUsedBy(); -} - diff --git a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java b/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java deleted file mode 100644 index 295e0a4..0000000 --- a/src/org/torproject/descriptor/impl/BandwidthHistoryImpl.java +++ /dev/null @@ -1,100 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.torproject.descriptor.BandwidthHistory; - -public class BandwidthHistoryImpl implements BandwidthHistory { - - protected BandwidthHistoryImpl(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - boolean isValid = false; - this.line = line; - if (partsNoOpt.length == 5 || partsNoOpt.length == 6) { - try { - this.historyEndMillis = ParseHelper.parseTimestampAtIndex(line, - partsNoOpt, 1, 2); - if (partsNoOpt[3].startsWith("(") && - 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 + "'."); - } - String[] values = null; - if (partsNoOpt.length == 5 && - partsNoOpt[4].equals("s)")) { - /* There are no bandwidth values to parse. */ - isValid = true; - } 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) { - this.bandwidthValues = new long[values.length]; - for (int i = values.length - 1; i >= 0; i--) { - long bandwidthValue = Long.parseLong(values[i]); - if (bandwidthValue < 0L) { - throw new DescriptorParseException("Negative bandwidth " - + "values are not allowed in line '" + line + "'."); - } - this.bandwidthValues[i] = bandwidthValue; - } - isValid = true; - } - } - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (!isValid) { - throw new DescriptorParseException("Invalid bandwidth-history line " - + "'" + line + "'."); - } - } - - private String line; - @Override - public String getLine() { - return this.line; - } - - private long historyEndMillis; - @Override - public long getHistoryEndMillis() { - return this.historyEndMillis; - } - - private long intervalLength; - @Override - public long getIntervalLength() { - return this.intervalLength; - } - - private long[] bandwidthValues; - @Override - public SortedMap<Long, Long> getBandwidthValues() { - SortedMap<Long, Long> result = new TreeMap<>(); - if (this.bandwidthValues != null) { - long endMillis = this.historyEndMillis; - for (int i = this.bandwidthValues.length - 1; i >= 0; i--) { - result.put(endMillis, bandwidthValues[i]); - endMillis -= this.intervalLength * 1000L; - } - } - return result; - } -} - diff --git a/src/org/torproject/descriptor/impl/BlockingIteratorImpl.java b/src/org/torproject/descriptor/impl/BlockingIteratorImpl.java deleted file mode 100644 index 66426d8..0000000 --- a/src/org/torproject/descriptor/impl/BlockingIteratorImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.NoSuchElementException; -import java.util.Queue; - -/* Provide an iterator for a queue of objects and block when there are - * currently no objects in the queue. Allow the producer to signal that - * there won't be further objects and unblock any waiting consumers. */ -public class BlockingIteratorImpl<T> implements Iterator<T> { - - /* Queue containing produced elemnts waiting for consumers. */ - private Queue<T> queue = new LinkedList<>(); - - /* Maximum number of elements in queue. */ - private int maxQueueSize = 100; - - /* Restrict object construction to the impl package. */ - protected BlockingIteratorImpl() { - } - - /* Create instance with maximum queue size. */ - protected BlockingIteratorImpl(int maxQueueSize) { - this.maxQueueSize = maxQueueSize; - } - - /* Add an object to the queue if there's still room. */ - protected synchronized void add(T object) { - if (this.outOfDescriptors) { - throw new IllegalStateException("Internal error: Adding results to " - + "descriptor queue not allowed after sending end-of-stream " - + "object."); - } - while (this.queue.size() >= this.maxQueueSize) { - try { - wait(); - } catch (InterruptedException e) { - } - } - this.queue.offer(object); - notifyAll(); - } - - /* Signalize that there won't be any further objects to be enqueued. */ - private boolean outOfDescriptors = false; - protected synchronized void setOutOfDescriptors() { - if (this.outOfDescriptors) { - throw new IllegalStateException("Internal error: Sending " - + "end-of-stream object only permitted once."); - } - this.outOfDescriptors = true; - notifyAll(); - } - - /* Return whether there are more objects. Block if there are currently - * no objects, but the producer hasn't signalized that there won't be - * further objects. */ - @Override - public synchronized boolean hasNext() { - while (!this.outOfDescriptors && this.queue.isEmpty()) { - try { - wait(); - } catch (InterruptedException e) { - } - } - return this.queue.peek() != null; - } - - /* Return the next object in the queue or throw an exception when there - * are no further objects. Block if there are currently no objects, but - * the producer hasn't signalized that there won't be further - * objects. */ - @Override - public synchronized T next() { - while (!this.outOfDescriptors && this.queue.isEmpty()) { - try { - wait(); - } catch (InterruptedException e) { - } - } - if (this.queue.peek() == null) { - throw new NoSuchElementException(); - } - notifyAll(); - return this.queue.remove(); - } - - /* Don't support explicitly removing objects. They are removed - * anyway. */ - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} - diff --git a/src/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java deleted file mode 100644 index 15d40d8..0000000 --- a/src/org/torproject/descriptor/impl/BridgeExtraInfoDescriptorImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.BridgeExtraInfoDescriptor; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExtraInfoDescriptor; - -public class BridgeExtraInfoDescriptorImpl - extends ExtraInfoDescriptorImpl implements BridgeExtraInfoDescriptor { - - protected static List<ExtraInfoDescriptor> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<ExtraInfoDescriptor> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, - "extra-info "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - ExtraInfoDescriptor parsedDescriptor = - new BridgeExtraInfoDescriptorImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected BridgeExtraInfoDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines); - } -} - diff --git a/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java b/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java deleted file mode 100644 index bf3804d..0000000 --- a/src/org/torproject/descriptor/impl/BridgeNetworkStatusImpl.java +++ /dev/null @@ -1,230 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Map; -import java.util.Scanner; -import java.util.SortedMap; -import java.util.TimeZone; - -import org.torproject.descriptor.BridgeNetworkStatus; - -/* Contains a bridge network status. */ -public class BridgeNetworkStatusImpl extends NetworkStatusImpl - implements BridgeNetworkStatus { - - protected BridgeNetworkStatusImpl(byte[] statusBytes, - String fileName, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(statusBytes, failUnrecognizedDescriptorLines, false, false); - this.setPublishedMillisFromFileName(fileName); - } - - private void setPublishedMillisFromFileName(String fileName) - throws DescriptorParseException { - if (this.publishedMillis != 0L) { - /* We already learned the publication timestamp from parsing the - * "published" line. */ - return; - } - if (fileName.length() == - "20000101-000000-4A0CCD2DDC7995083D73F5D667100C8A5831F16D". - length()) { - String publishedString = fileName.substring(0, - "yyyyMMdd-HHmmss".length()); - try { - SimpleDateFormat fileNameFormat = new SimpleDateFormat( - "yyyyMMdd-HHmmss"); - fileNameFormat.setLenient(false); - fileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - this.publishedMillis = fileNameFormat.parse(publishedString). - getTime(); - } catch (ParseException e) { - } - } - if (this.publishedMillis == 0L) { - throw new DescriptorParseException("Unrecognized bridge network " - + "status file name '" + fileName + "'."); - } - } - - protected void parseHeader(byte[] headerBytes) - throws DescriptorParseException { - /* Initialize flag-thresholds values here for the case that the status - * doesn't contain those values. Initializing them in the constructor - * or when declaring variables wouldn't work, because those parts are - * evaluated later and would overwrite everything we parse here. */ - this.stableUptime = -1L; - this.stableMtbf = -1L; - this.fastBandwidth = -1L; - this.guardWfu = -1.0; - this.guardTk = -1L; - this.guardBandwidthIncludingExits = -1L; - this.guardBandwidthExcludingExits = -1L; - this.enoughMtbfInfo = -1; - this.ignoringAdvertisedBws = -1; - - Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "published": - this.parsePublishedLine(line, parts); - break; - case "flag-thresholds": - this.parseFlagThresholdsLine(line, parts); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in bridge network status."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private void parsePublishedLine(String line, String[] parts) - throws DescriptorParseException { - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseFlagThresholdsLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("No flag thresholds in line '" - + line + "'."); - } - SortedMap<String, String> flagThresholds = - ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); - try { - for (Map.Entry<String, String> e : flagThresholds.entrySet()) { - switch (e.getKey()) { - case "stable-uptime": - this.stableUptime = Long.parseLong(e.getValue()); - break; - case "stable-mtbf": - this.stableMtbf = Long.parseLong(e.getValue()); - break; - case "fast-speed": - this.fastBandwidth = Long.parseLong(e.getValue()); - break; - case "guard-wfu": - this.guardWfu = Double.parseDouble(e.getValue(). - replaceAll("%", "")); - break; - case "guard-tk": - this.guardTk = Long.parseLong(e.getValue()); - break; - case "guard-bw-inc-exits": - this.guardBandwidthIncludingExits = - Long.parseLong(e.getValue()); - break; - case "guard-bw-exc-exits": - this.guardBandwidthExcludingExits = - Long.parseLong(e.getValue()); - break; - case "enough-mtbf": - this.enoughMtbfInfo = Integer.parseInt(e.getValue()); - break; - case "ignoring-advertised-bws": - this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); - break; - } - } - } catch (NumberFormatException ex) { - throw new DescriptorParseException("Illegal value in line '" - + line + "'."); - } - } - - protected void parseDirSource(byte[] dirSourceBytes) - throws DescriptorParseException { - throw new DescriptorParseException("No directory source expected in " - + "bridge network status."); - } - - protected void parseFooter(byte[] footerBytes) - throws DescriptorParseException { - throw new DescriptorParseException("No directory footer expected in " - + "bridge network status."); - } - - protected void parseDirectorySignature(byte[] directorySignatureBytes) - throws DescriptorParseException { - throw new DescriptorParseException("No directory signature expected " - + "in bridge network status."); - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private long stableUptime; - @Override - public long getStableUptime() { - return this.stableUptime; - } - - private long stableMtbf; - @Override - public long getStableMtbf() { - return this.stableMtbf; - } - - private long fastBandwidth; - @Override - public long getFastBandwidth() { - return this.fastBandwidth; - } - - private double guardWfu; - @Override - public double getGuardWfu() { - return this.guardWfu; - } - - private long guardTk; - @Override - public long getGuardTk() { - return this.guardTk; - } - - private long guardBandwidthIncludingExits; - @Override - public long getGuardBandwidthIncludingExits() { - return this.guardBandwidthIncludingExits; - } - - private long guardBandwidthExcludingExits; - @Override - public long getGuardBandwidthExcludingExits() { - return this.guardBandwidthExcludingExits; - } - - private int enoughMtbfInfo; - @Override - public int getEnoughMtbfInfo() { - return this.enoughMtbfInfo; - } - - private int ignoringAdvertisedBws; - @Override - public int getIgnoringAdvertisedBws() { - return this.ignoringAdvertisedBws; - } -} - diff --git a/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java b/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java deleted file mode 100644 index 99578e8..0000000 --- a/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -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 java.util.SortedMap; -import java.util.TreeMap; - -import org.torproject.descriptor.BridgePoolAssignment; - -/* TODO Write a test class. */ -public class BridgePoolAssignmentImpl extends DescriptorImpl - implements BridgePoolAssignment { - - protected static List<BridgePoolAssignment> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<BridgePoolAssignment> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, - "bridge-pool-assignment "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - BridgePoolAssignment parsedDescriptor = - new BridgePoolAssignmentImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected BridgePoolAssignmentImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines, false); - this.parseDescriptorBytes(); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList( - new String[] { "bridge-pool-assignment" })); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - this.checkFirstKeyword("bridge-pool-assignment"); - this.clearParsedKeywords(); - return; - } - - private void parseDescriptorBytes() throws DescriptorParseException { - Scanner s = new Scanner(new String(this.rawDescriptorBytes)). - useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("bridge-pool-assignment ")) { - this.parseBridgePoolAssignmentLine(line); - } else { - this.parseBridgeLine(line); - } - } - } - - private void parseBridgePoolAssignmentLine(String line) - throws DescriptorParseException { - String[] parts = line.split("[ \t]+"); - if (parts.length != 3) { - throw new DescriptorParseException("Illegal line '" + line - + "' in bridge pool assignment."); - } - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - } - - private void parseBridgeLine(String line) - throws DescriptorParseException { - String[] parts = line.split("[ \t]+"); - if (parts.length < 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in bridge pool assignment."); - } - String fingerprint = ParseHelper.parseTwentyByteHexString(line, - parts[0]); - String poolAndDetails = line.substring(line.indexOf(" ") + 1); - this.entries.put(fingerprint, poolAndDetails); - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private SortedMap<String, String> entries = new TreeMap<>(); - @Override - public SortedMap<String, String> getEntries() { - return new TreeMap<>(this.entries); - } -} - diff --git a/src/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java deleted file mode 100644 index eb2b933..0000000 --- a/src/org/torproject/descriptor/impl/BridgeServerDescriptorImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.BridgeServerDescriptor; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ServerDescriptor; - -public class BridgeServerDescriptorImpl extends ServerDescriptorImpl - implements BridgeServerDescriptor { - - protected static List<ServerDescriptor> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<ServerDescriptor> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, - "router "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - ServerDescriptor parsedDescriptor = - new BridgeServerDescriptorImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected BridgeServerDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines); - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorCollectorImpl.java b/src/org/torproject/descriptor/impl/DescriptorCollectorImpl.java deleted file mode 100644 index 1a030ef..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorCollectorImpl.java +++ /dev/null @@ -1,249 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.BufferedOutputStream; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.DateFormat; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Map; -import java.util.Scanner; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.Stack; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; - -import org.torproject.descriptor.DescriptorCollector; - -public class DescriptorCollectorImpl implements DescriptorCollector { - - @Override - public void collectDescriptors(String collecTorBaseUrl, - String[] remoteDirectories, long minLastModified, - File localDirectory, boolean deleteExtraneousLocalFiles) { - collecTorBaseUrl = collecTorBaseUrl.endsWith("/") - ? collecTorBaseUrl.substring(0, collecTorBaseUrl.length() - 1) - : collecTorBaseUrl; - if (minLastModified < 0) { - throw new IllegalArgumentException("A negative minimum " - + "last-modified time is not permitted."); - } - if (localDirectory.exists() && !localDirectory.isDirectory()) { - throw new IllegalArgumentException("Local directory already exists " - + "and is not a directory."); - } - SortedMap<String, Long> localFiles = - this.statLocalDirectory(localDirectory); - SortedMap<String, String> fetchedDirectoryListings = - this.fetchRemoteDirectories(collecTorBaseUrl, remoteDirectories); - SortedSet<String> parsedDirectories = new TreeSet<>(); - SortedMap<String, Long> remoteFiles = new TreeMap<>(); - for (Map.Entry<String, String> e : - fetchedDirectoryListings.entrySet()) { - String remoteDirectory = e.getKey(); - String directoryListing = e.getValue(); - SortedMap<String, Long> parsedRemoteFiles = - this.parseDirectoryListing(remoteDirectory, directoryListing); - if (parsedRemoteFiles == null) { - continue; - } - parsedDirectories.add(remoteDirectory); - remoteFiles.putAll(parsedRemoteFiles); - } - this.fetchRemoteFiles(collecTorBaseUrl, remoteFiles, minLastModified, - localDirectory, localFiles); - if (deleteExtraneousLocalFiles) { - this.deleteExtraneousLocalFiles(parsedDirectories, remoteFiles, - localDirectory, localFiles); - } - } - - SortedMap<String, Long> statLocalDirectory( - File localDirectory) { - SortedMap<String, Long> localFiles = new TreeMap<>(); - if (!localDirectory.exists()) { - return localFiles; - } - Stack<File> files = new Stack<>(); - files.add(localDirectory); - while (!files.isEmpty()) { - File file = files.pop(); - if (file.isDirectory()) { - files.addAll(Arrays.asList(file.listFiles())); - } else { - String localPath = file.getPath().substring( - localDirectory.getPath().length()); - localFiles.put(localPath, file.lastModified()); - } - } - return localFiles; - } - - SortedMap<String, String> fetchRemoteDirectories( - String collecTorBaseUrl, String[] remoteDirectories) { - SortedMap<String, String> fetchedDirectoryListings = new TreeMap<>(); - for (String remoteDirectory : remoteDirectories) { - String remoteDirectoryWithSlashAtBeginAndEnd = - (remoteDirectory.startsWith("/") ? "" : "/") + remoteDirectory - + (remoteDirectory.endsWith("/") ? "" : "/"); - String directoryUrl = collecTorBaseUrl - + remoteDirectoryWithSlashAtBeginAndEnd; - String directoryListing = this.fetchRemoteDirectory(directoryUrl); - if (directoryListing.length() > 0) { - fetchedDirectoryListings.put( - remoteDirectoryWithSlashAtBeginAndEnd, directoryListing); - } - } - return fetchedDirectoryListings; - } - - String fetchRemoteDirectory(String url) { - StringBuilder sb = new StringBuilder(); - HttpURLConnection huc = null; - try { - URL u = new URL(url); - huc = (HttpURLConnection) u.openConnection(); - huc.setRequestMethod("GET"); - huc.connect(); - int responseCode = huc.getResponseCode(); - if (responseCode == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader( - huc.getInputStream())); - String line; - while ((line = br.readLine()) != null) { - sb.append(line).append("\n"); - } - br.close(); - } - } catch (IOException e) { - e.printStackTrace(); - if (huc != null) { - huc.disconnect(); - } - return ""; - } - return sb.toString(); - } - - final Pattern DIRECTORY_LISTING_LINE_PATTERN = - Pattern.compile(".* href="([^"/]+)"" /* filename */ - + ".*>(\d{2}-\w{3}-\d{4} \d{2}:\d{2})\s*<.*"); /* dateTime */ - - SortedMap<String, Long> parseDirectoryListing( - String remoteDirectory, String directoryListing) { - SortedMap<String, Long> remoteFiles = new TreeMap<>(); - DateFormat dateTimeFormat = ParseHelper.getDateFormat( - "dd-MMM-yyyy HH:mm"); - try { - Scanner s = new Scanner(directoryListing); - s.useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - Matcher matcher = DIRECTORY_LISTING_LINE_PATTERN.matcher(line); - if (matcher.matches()) { - String filename = matcher.group(1); - long lastModifiedMillis = dateTimeFormat.parse( - matcher.group(2)).getTime(); - remoteFiles.put(remoteDirectory + filename, lastModifiedMillis); - } - } - s.close(); - } catch (ParseException e) { - e.printStackTrace(); - return null; - } - return remoteFiles; - } - - void fetchRemoteFiles(String collecTorBaseUrl, - SortedMap<String, Long> remoteFiles, long minLastModified, - File localDirectory, SortedMap<String, Long> localFiles) { - for (Map.Entry<String, Long> e : remoteFiles.entrySet()) { - String filename = e.getKey(); - long lastModifiedMillis = e.getValue(); - if (lastModifiedMillis < minLastModified || - (localFiles.containsKey(filename) && - localFiles.get(filename) >= lastModifiedMillis)) { - continue; - } - String url = collecTorBaseUrl + filename; - File destinationFile = new File(localDirectory.getPath() - + filename); - this.fetchRemoteFile(url, destinationFile, lastModifiedMillis); - } - } - - void fetchRemoteFile(String url, File destinationFile, - long lastModifiedMillis) { - HttpURLConnection huc = null; - try { - File destinationDirectory = destinationFile.getParentFile(); - destinationDirectory.mkdirs(); - File tempDestinationFile = new File(destinationDirectory, "." - + destinationFile.getName()); - BufferedOutputStream bos = new BufferedOutputStream( - new FileOutputStream(tempDestinationFile)); - URL u = new URL(url); - huc = (HttpURLConnection) u.openConnection(); - huc.setRequestMethod("GET"); - if (!url.endsWith(".xz")) { - huc.addRequestProperty("Accept-Encoding", "gzip"); - } - huc.connect(); - int responseCode = huc.getResponseCode(); - if (responseCode == 200) { - InputStream is; - if (huc.getContentEncoding() != null && - huc.getContentEncoding().equalsIgnoreCase("gzip")) { - is = new GZIPInputStream(huc.getInputStream()); - } else { - is = huc.getInputStream(); - } - BufferedInputStream bis = new BufferedInputStream(is); - int len; - byte[] data = new byte[8192]; - while ((len = bis.read(data, 0, 8192)) >= 0) { - bos.write(data, 0, len); - } - bis.close(); - bos.close(); - tempDestinationFile.renameTo(destinationFile); - destinationFile.setLastModified(lastModifiedMillis); - } - } catch (IOException e) { - e.printStackTrace(); - if (huc != null) { - huc.disconnect(); - } - } - } - - void deleteExtraneousLocalFiles( - SortedSet<String> parsedDirectories, - SortedMap<String, Long> remoteFiles, File localDirectory, - SortedMap<String, Long> localFiles) { - for (String localPath : localFiles.keySet()) { - for (String remoteDirectory : parsedDirectories) { - if (localPath.startsWith(remoteDirectory)) { - if (!remoteFiles.containsKey(localPath)) { - new File(localDirectory.getPath() + localPath).delete(); - } - } - } - } - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java b/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java deleted file mode 100644 index e726ce9..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorDownloaderImpl.java +++ /dev/null @@ -1,283 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.torproject.descriptor.DescriptorRequest; -import org.torproject.descriptor.DescriptorDownloader; - -public class DescriptorDownloaderImpl - implements DescriptorDownloader { - - private boolean hasStartedDownloading = false; - - private SortedMap<String, DirectoryDownloader> directoryAuthorities = - new TreeMap<>(); - @Override - public void addDirectoryAuthority(String nickname, String ip, - int dirPort) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.checkDirectoryParameters(nickname, ip, dirPort); - DirectoryDownloader directoryAuthority = new DirectoryDownloader( - nickname, ip, dirPort); - this.directoryAuthorities.put(nickname, directoryAuthority); - } - - private SortedMap<String, DirectoryDownloader> directoryMirrors = - new TreeMap<>(); - @Override - public void addDirectoryMirror(String nickname, String ip, - int dirPort) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.checkDirectoryParameters(nickname, ip, dirPort); - DirectoryDownloader directoryMirror = new DirectoryDownloader( - nickname, ip, dirPort); - this.directoryMirrors.put(nickname, directoryMirror); - /* TODO Implement prioritizing mirrors for non-vote downloads. */ - throw new UnsupportedOperationException("Prioritizing directory " - + "mirrors over directory authorities is not implemented yet. " - + "Until it is, configuring directory mirrors is misleading and " - + "therefore not supported."); - } - - private void checkDirectoryParameters(String nickname, String ip, - int dirPort) { - if (nickname == null || nickname.length() < 1) { - throw new IllegalArgumentException("'" + nickname + "' is not a " - + "valid nickname."); - } - if (ip == null || ip.length() < 7 || ip.split("\.").length != 4) { - throw new IllegalArgumentException("'" + ip + "' is not a valid IP " - + "address."); - } - if (dirPort < 1 || dirPort > 65535) { - throw new IllegalArgumentException(String.valueOf(dirPort) + " is " - + "not a valid DirPort."); - } - /* TODO Relax the requirement for directory nicknames to be unique. - * In theory, we can identify them by ip+port. */ - if (this.directoryAuthorities.containsKey(nickname) || - this.directoryMirrors.containsKey(nickname)) { - throw new IllegalArgumentException("Directory nicknames must be " - + "unique."); - } - } - - private boolean downloadConsensus = false; - @Override - public void setIncludeCurrentConsensus() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.downloadConsensus = true; - } - - private boolean downloadConsensusFromAllAuthorities = false; - @Override - public void setIncludeCurrentConsensusFromAllDirectoryAuthorities() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.downloadConsensusFromAllAuthorities = true; - } - - private boolean includeCurrentReferencedVotes = false; - @Override - public void setIncludeCurrentReferencedVotes() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.includeCurrentReferencedVotes = true; - } - - private Set<String> downloadVotes = new HashSet<>(); - @Override - public void setIncludeCurrentVote(String fingerprint) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.checkVoteFingerprint(fingerprint); - this.downloadVotes.add(fingerprint); - } - - @Override - public void setIncludeCurrentVotes(Set<String> fingerprints) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - if (fingerprints == null) { - throw new IllegalArgumentException("Set of fingerprints must not " - + "be null."); - } - for (String fingerprint : fingerprints) { - this.checkVoteFingerprint(fingerprint); - } - for (String fingerprint : fingerprints) { - this.setIncludeCurrentVote(fingerprint); - } - } - - private void checkVoteFingerprint(String fingerprint) { - if (fingerprint == null || fingerprint.length() != 40) { - throw new IllegalArgumentException("'" + fingerprint + "' is not a " - + "valid fingerprint."); - } - } - - @Override - public void setIncludeReferencedServerDescriptors() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading server " - + "descriptors is not implemented yet."); - } - - @Override - public void setExcludeServerDescriptor(String identifier) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading server " - + "descriptors is not implemented yet."); - } - - @Override - public void setExcludeServerDescriptors(Set<String> identifier) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading server " - + "descriptors is not implemented yet."); - } - - @Override - public void setIncludeReferencedExtraInfoDescriptors() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading extra-info " - + "descriptors is not implemented yet."); - } - - @Override - public void setExcludeExtraInfoDescriptor(String identifier) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading extra-info " - + "descriptors is not implemented yet."); - } - - @Override - public void setExcludeExtraInfoDescriptors(Set<String> identifiers) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - /* TODO Implement me. */ - throw new UnsupportedOperationException("Downloading extra-info " - + "descriptors is not implemented yet."); - } - - private long readTimeoutMillis = 60L * 1000L; - @Override - public void setReadTimeout(long readTimeoutMillis) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - if (readTimeoutMillis < 0L) { - throw new IllegalArgumentException("Read timeout value " - + String.valueOf(readTimeoutMillis) + " may not be " - + "negative."); - } - this.readTimeoutMillis = readTimeoutMillis; - } - - private long connectTimeoutMillis = 60L * 1000L; - @Override - public void setConnectTimeout(long connectTimeoutMillis) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - if (connectTimeoutMillis < 0L) { - throw new IllegalArgumentException("Connect timeout value " - + String.valueOf(connectTimeoutMillis) + " may not be " - + "negative."); - } - this.connectTimeoutMillis = connectTimeoutMillis; - } - - private long globalTimeoutMillis = 60L * 60L * 1000L; - @Override - public void setGlobalTimeout(long globalTimeoutMillis) { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - if (globalTimeoutMillis < 0L) { - throw new IllegalArgumentException("Global timeout value " - + String.valueOf(globalTimeoutMillis) + " may not be " - + "negative."); - } - this.globalTimeoutMillis = globalTimeoutMillis; - } - - private boolean failUnrecognizedDescriptorLines = false; - @Override - public void setFailUnrecognizedDescriptorLines() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to download."); - } - this.failUnrecognizedDescriptorLines = true; - } - - @Override - public Iterator<DescriptorRequest> downloadDescriptors() { - if (this.hasStartedDownloading) { - throw new IllegalStateException("Initiating downloads is only " - + "permitted once."); - } - this.hasStartedDownloading = true; - DownloadCoordinatorImpl downloadCoordinator = - new DownloadCoordinatorImpl(this.directoryAuthorities, - this.directoryMirrors, this.downloadConsensus, - this.downloadConsensusFromAllAuthorities, this.downloadVotes, - this.includeCurrentReferencedVotes, this.connectTimeoutMillis, - this.readTimeoutMillis, this.globalTimeoutMillis, - this.failUnrecognizedDescriptorLines); - Iterator<DescriptorRequest> descriptorQueue = downloadCoordinator. - getDescriptorQueue(); - return descriptorQueue; - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorFileImpl.java b/src/org/torproject/descriptor/impl/DescriptorFileImpl.java deleted file mode 100644 index 801c546..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorFileImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorFile; - -public class DescriptorFileImpl implements DescriptorFile { - - private File directory; - protected void setDirectory(File directory) { - this.directory = directory; - } - @Override - public File getDirectory() { - return this.directory; - } - - private File tarball; - protected void setTarball(File tarball) { - this.tarball = tarball; - } - @Override - public File getTarball() { - return this.tarball; - } - - private File file; - protected void setFile(File file) { - this.file = file; - } - @Override - public File getFile() { - return this.file; - } - - private String fileName; - protected void setFileName(String fileName) { - this.fileName = fileName; - } - @Override - public String getFileName() { - return this.fileName; - } - - private long lastModified; - protected void setLastModified(long lastModified) { - this.lastModified = lastModified; - } - @Override - public long getLastModified() { - return this.lastModified; - } - - private List<Descriptor> descriptors; - protected void setDescriptors(List<Descriptor> descriptors) { - this.descriptors = descriptors; - } - @Override - public List<Descriptor> getDescriptors() { - return this.descriptors == null ? new ArrayList<Descriptor>() : - new ArrayList<>(this.descriptors); - } - - private Exception exception; - protected void setException(Exception exception) { - this.exception = exception; - } - @Override - public Exception getException() { - return this.exception; - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java deleted file mode 100644 index 5625b3f..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorImpl.java +++ /dev/null @@ -1,337 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; - -import org.torproject.descriptor.Descriptor; - -public abstract class DescriptorImpl implements Descriptor { - - protected static List<Descriptor> parseDescriptors( - byte[] rawDescriptorBytes, String fileName, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<Descriptor> parsedDescriptors = new ArrayList<>(); - if (rawDescriptorBytes == null) { - return parsedDescriptors; - } - byte[] first100Chars = new byte[Math.min(100, - rawDescriptorBytes.length)]; - System.arraycopy(rawDescriptorBytes, 0, first100Chars, 0, - first100Chars.length); - String firstLines = new String(first100Chars); - if (firstLines.startsWith("@type network-status-consensus-3 1.") || - 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, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type network-status-vote-3 1.") - || ((firstLines.startsWith("network-status-version 3\n") || - firstLines.contains("\nnetwork-status-version 3\n")) && - firstLines.contains("\nvote-status vote\n"))) { - parsedDescriptors.addAll(RelayNetworkStatusVoteImpl. - parseVotes(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type bridge-network-status 1.") - || firstLines.startsWith("r ")) { - parsedDescriptors.add(new BridgeNetworkStatusImpl( - rawDescriptorBytes, fileName, failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith( - "@type bridge-server-descriptor 1.")) { - parsedDescriptors.addAll(BridgeServerDescriptorImpl. - parseDescriptors(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type server-descriptor 1.") || - firstLines.startsWith("router ") || - firstLines.contains("\nrouter ")) { - parsedDescriptors.addAll(RelayServerDescriptorImpl. - parseDescriptors(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type bridge-extra-info 1.")) { - parsedDescriptors.addAll(BridgeExtraInfoDescriptorImpl. - parseDescriptors(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type extra-info 1.") || - firstLines.startsWith("extra-info ") || - firstLines.contains("\nextra-info ")) { - parsedDescriptors.addAll(RelayExtraInfoDescriptorImpl. - 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 ")) { - parsedDescriptors.addAll(BridgePoolAssignmentImpl. - parseDescriptors(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type dir-key-certificate-3 1.") || - firstLines.startsWith("dir-key-certificate-version ") || - firstLines.contains("\ndir-key-certificate-version ")) { - parsedDescriptors.addAll(DirectoryKeyCertificateImpl. - parseDescriptors(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type tordnsel 1.") || - firstLines.startsWith("ExitNode ") || - firstLines.contains("\nExitNode ")) { - parsedDescriptors.add(new ExitListImpl(rawDescriptorBytes, fileName, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type network-status-2 1.") || - firstLines.startsWith("network-status-version 2\n") || - firstLines.contains("\nnetwork-status-version 2\n")) { - parsedDescriptors.add(new RelayNetworkStatusImpl(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type directory 1.") || - firstLines.startsWith("signed-directory\n") || - firstLines.contains("\nsigned-directory\n")) { - parsedDescriptors.add(new RelayDirectoryImpl(rawDescriptorBytes, - failUnrecognizedDescriptorLines)); - } else if (firstLines.startsWith("@type torperf 1.")) { - parsedDescriptors.addAll(TorperfResultImpl.parseTorperfResults( - rawDescriptorBytes, failUnrecognizedDescriptorLines)); - } else { - throw new DescriptorParseException("Could not detect descriptor " - + "type in descriptor starting with '" + firstLines + "'."); - } - return parsedDescriptors; - } - - protected static List<byte[]> splitRawDescriptorBytes( - byte[] rawDescriptorBytes, String startToken) { - List<byte[]> rawDescriptors = new ArrayList<>(); - String splitToken = "\n" + startToken; - String ascii; - try { - ascii = new String(rawDescriptorBytes, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - return rawDescriptors; - } - int endAllDescriptors = rawDescriptorBytes.length, - startAnnotations = 0; - boolean containsAnnotations = ascii.startsWith("@") || - ascii.contains("\n@"); - while (startAnnotations < endAllDescriptors) { - int startDescriptor; - if (ascii.indexOf(startToken, startAnnotations) == 0) { - startDescriptor = startAnnotations; - } else { - startDescriptor = ascii.indexOf(splitToken, startAnnotations - 1); - if (startDescriptor < 0) { - break; - } else { - startDescriptor += 1; - } - } - int endDescriptor = -1; - if (containsAnnotations) { - endDescriptor = ascii.indexOf("\n@", startDescriptor); - } - if (endDescriptor < 0) { - endDescriptor = ascii.indexOf(splitToken, startDescriptor); - } - if (endDescriptor < 0) { - endDescriptor = endAllDescriptors - 1; - } - endDescriptor += 1; - byte[] rawDescriptor = new byte[endDescriptor - startAnnotations]; - System.arraycopy(rawDescriptorBytes, startAnnotations, - rawDescriptor, 0, endDescriptor - startAnnotations); - startAnnotations = endDescriptor; - rawDescriptors.add(rawDescriptor); - } - return rawDescriptors; - } - - protected byte[] rawDescriptorBytes; - @Override - public byte[] getRawDescriptorBytes() { - return this.rawDescriptorBytes; - } - - protected boolean failUnrecognizedDescriptorLines = false; - - protected List<String> unrecognizedLines; - @Override - public List<String> getUnrecognizedLines() { - return this.unrecognizedLines == null ? new ArrayList<String>() : - new ArrayList<>(this.unrecognizedLines); - } - - protected DescriptorImpl(byte[] rawDescriptorBytes, - boolean failUnrecognizedDescriptorLines, boolean blankLinesAllowed) - throws DescriptorParseException { - this.rawDescriptorBytes = rawDescriptorBytes; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - this.cutOffAnnotations(rawDescriptorBytes); - this.countKeywords(rawDescriptorBytes, blankLinesAllowed); - } - - /* Parse annotation lines from the descriptor bytes. */ - private List<String> annotations = new ArrayList<>(); - private void cutOffAnnotations(byte[] rawDescriptorBytes) - throws DescriptorParseException { - String ascii = new String(rawDescriptorBytes); - int start = 0; - while ((start == 0 && ascii.startsWith("@")) || - (start > 0 && ascii.indexOf("\n@", start - 1) >= 0)) { - int end = ascii.indexOf("\n", start); - if (end < 0) { - throw new DescriptorParseException("Annotation line does not " - + "contain a newline."); - } - this.annotations.add(ascii.substring(start, end)); - start = end + 1; - } - if (start > 0) { - int length = rawDescriptorBytes.length; - byte[] rawDescriptor = new byte[length - start]; - System.arraycopy(rawDescriptorBytes, start, rawDescriptor, 0, - length - start); - this.rawDescriptorBytes = rawDescriptor; - } - } - @Override - public List<String> getAnnotations() { - return new ArrayList<>(this.annotations); - } - - /* Count parsed keywords for consistency checks by subclasses. */ - private String firstKeyword, lastKeyword; - private Map<String, Integer> parsedKeywords = new HashMap<>(); - private void countKeywords(byte[] rawDescriptorBytes, - boolean blankLinesAllowed) throws DescriptorParseException { - if (rawDescriptorBytes.length == 0) { - throw new DescriptorParseException("Descriptor is empty."); - } - String descriptorString = new String(rawDescriptorBytes); - if (!blankLinesAllowed && (descriptorString.startsWith("\n") || - descriptorString.contains("\n\n"))) { - throw new DescriptorParseException("Blank lines are not allowed."); - } - boolean skipCrypto = false; - Scanner s = new Scanner(descriptorString).useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("-----BEGIN")) { - skipCrypto = true; - } else if (line.startsWith("-----END")) { - skipCrypto = false; - } else if (!line.isEmpty() && !line.startsWith("@") && - !skipCrypto) { - String lineNoOpt = line.startsWith("opt ") ? - line.substring("opt ".length()) : line; - String keyword = lineNoOpt.split(" ", -1)[0]; - if (keyword.equals("")) { - throw new DescriptorParseException("Illegal keyword in line '" - + line + "'."); - } - if (this.firstKeyword == null) { - this.firstKeyword = keyword; - } - lastKeyword = keyword; - if (parsedKeywords.containsKey(keyword)) { - parsedKeywords.put(keyword, parsedKeywords.get(keyword) + 1); - } else { - parsedKeywords.put(keyword, 1); - } - } - } - } - - protected void checkFirstKeyword(String keyword) - throws DescriptorParseException { - if (this.firstKeyword == null || - !this.firstKeyword.equals(keyword)) { - throw new DescriptorParseException("Keyword '" + keyword + "' must " - + "be contained in the first line."); - } - } - - protected void checkLastKeyword(String keyword) - throws DescriptorParseException { - if (this.lastKeyword == null || - !this.lastKeyword.equals(keyword)) { - throw new DescriptorParseException("Keyword '" + keyword + "' must " - + "be contained in the last line."); - } - } - - protected void checkExactlyOnceKeywords(Set<String> keywords) - throws DescriptorParseException { - for (String keyword : keywords) { - int contained = 0; - if (this.parsedKeywords.containsKey(keyword)) { - contained = this.parsedKeywords.get(keyword); - } - if (contained != 1) { - throw new DescriptorParseException("Keyword '" + keyword + "' is " - + "contained " + contained + " times, but must be contained " - + "exactly once."); - } - } - } - - protected void checkAtLeastOnceKeywords(Set<String> keywords) - throws DescriptorParseException { - for (String keyword : keywords) { - if (!this.parsedKeywords.containsKey(keyword)) { - throw new DescriptorParseException("Keyword '" + keyword + "' is " - + "contained 0 times, but must be contained at least once."); - } - } - } - - protected void checkAtMostOnceKeywords(Set<String> keywords) - throws DescriptorParseException { - for (String keyword : keywords) { - if (this.parsedKeywords.containsKey(keyword) && - this.parsedKeywords.get(keyword) > 1) { - throw new DescriptorParseException("Keyword '" + keyword + "' is " - + "contained " + this.parsedKeywords.get(keyword) + " times, " - + "but must be contained at most once."); - } - } - } - - protected void checkKeywordsDependOn(Set<String> dependentKeywords, - String dependingKeyword) throws DescriptorParseException { - for (String dependentKeyword : dependentKeywords) { - if (this.parsedKeywords.containsKey(dependentKeyword) && - !this.parsedKeywords.containsKey(dependingKeyword)) { - throw new DescriptorParseException("Keyword '" + dependentKeyword - + "' is contained, but keyword '" + dependingKeyword + "' is " - + "not."); - } - } - } - - protected int getKeywordCount(String keyword) { - if (!this.parsedKeywords.containsKey(keyword)) { - return 0; - } else { - return this.parsedKeywords.get(keyword); - } - } - - protected void clearParsedKeywords() { - this.parsedKeywords = null; - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorParseException.java b/src/org/torproject/descriptor/impl/DescriptorParseException.java deleted file mode 100644 index 0f9add2..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorParseException.java +++ /dev/null @@ -1,15 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -/** - * @deprecated Replaced by - * org.torproject.descriptor.DescriptorParseException - */ -@Deprecated public class DescriptorParseException extends Exception { - private static final long serialVersionUID = 100L; - protected DescriptorParseException(String message) { - super(message); - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorParserImpl.java b/src/org/torproject/descriptor/impl/DescriptorParserImpl.java deleted file mode 100644 index 6ac53f8..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorParserImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.List; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorParser; - -public class DescriptorParserImpl implements DescriptorParser { - - private boolean failUnrecognizedDescriptorLines; - - @Override - public void setFailUnrecognizedDescriptorLines( - boolean failUnrecognizedDescriptorLines) { - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - } - - @Override - public List<Descriptor> parseDescriptors(byte[] rawDescriptorBytes, - String fileName) throws DescriptorParseException { - return DescriptorImpl.parseDescriptors(rawDescriptorBytes, fileName, - this.failUnrecognizedDescriptorLines); - } -} diff --git a/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java b/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java deleted file mode 100644 index 8da88e9..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorReaderImpl.java +++ /dev/null @@ -1,364 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; -import java.util.Stack; -import java.util.TreeMap; - -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorFile; -import org.torproject.descriptor.DescriptorParser; -import org.torproject.descriptor.DescriptorReader; - -public class DescriptorReaderImpl implements DescriptorReader { - - private boolean hasStartedReading = false; - - private List<File> directories = new ArrayList<>(); - @Override - public void addDirectory(File directory) { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.directories.add(directory); - } - - private List<File> tarballs = new ArrayList<>(); - @Override - public void addTarball(File tarball) { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.tarballs.add(tarball); - } - - private File historyFile; - @Override - public void setExcludeFiles(File historyFile) { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.historyFile = historyFile; - } - - private SortedMap<String, Long> excludedFiles; - @Override - public void setExcludedFiles(SortedMap<String, Long> excludedFiles) { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.excludedFiles = excludedFiles; - } - - @Override - public SortedMap<String, Long> getExcludedFiles() { - if (this.reader == null || !this.reader.hasFinishedReading) { - throw new IllegalStateException("Operation is not permitted before " - + "finishing to read."); - } - return new TreeMap<>(this.reader.excludedFilesAfter); - } - - @Override - public SortedMap<String, Long> getParsedFiles() { - if (this.reader == null || !this.reader.hasFinishedReading) { - throw new IllegalStateException("Operation is not permitted before " - + "finishing to read."); - } - return new TreeMap<>(this.reader.parsedFilesAfter); - } - - private boolean failUnrecognizedDescriptorLines = false; - @Override - public void setFailUnrecognizedDescriptorLines() { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.failUnrecognizedDescriptorLines = true; - } - - private Integer maxDescriptorFilesInQueue = null; - @Override - public void setMaxDescriptorFilesInQueue(int max) { - if (this.hasStartedReading) { - throw new IllegalStateException("Reconfiguration is not permitted " - + "after starting to read."); - } - this.maxDescriptorFilesInQueue = max; - } - - private DescriptorReaderRunnable reader; - @Override - public Iterator<DescriptorFile> readDescriptors() { - if (this.hasStartedReading) { - throw new IllegalStateException("Initiating reading is only " - + "permitted once."); - } - this.hasStartedReading = true; - BlockingIteratorImpl<DescriptorFile> descriptorQueue = - this.maxDescriptorFilesInQueue == null - ? new BlockingIteratorImpl<DescriptorFile>() - : new BlockingIteratorImpl<DescriptorFile>( - this.maxDescriptorFilesInQueue); - this.reader = new DescriptorReaderRunnable(this.directories, - this.tarballs, descriptorQueue, this.historyFile, - this.excludedFiles, this.failUnrecognizedDescriptorLines); - new Thread(this.reader).start(); - return descriptorQueue; - } - - private static class DescriptorReaderRunnable implements Runnable { - private List<File> directories; - private List<File> tarballs; - private BlockingIteratorImpl<DescriptorFile> descriptorQueue; - private File historyFile; - private SortedMap<String, Long> excludedFilesBefore = new TreeMap<>(), - excludedFilesAfter = new TreeMap<>(), - parsedFilesAfter = new TreeMap<>(); - private DescriptorParser descriptorParser; - private boolean hasFinishedReading = false; - private DescriptorReaderRunnable(List<File> directories, - List<File> tarballs, - BlockingIteratorImpl<DescriptorFile> descriptorQueue, - File historyFile, SortedMap<String, Long> excludedFiles, - boolean failUnrecognizedDescriptorLines) { - this.directories = directories; - this.tarballs = tarballs; - this.descriptorQueue = descriptorQueue; - this.historyFile = historyFile; - if (excludedFiles != null) { - this.excludedFilesBefore = excludedFiles; - } - this.descriptorParser = new DescriptorParserImpl(); - this.descriptorParser.setFailUnrecognizedDescriptorLines( - failUnrecognizedDescriptorLines); - } - public void run() { - try { - this.readOldHistory(); - this.readDescriptors(); - this.readTarballs(); - this.hasFinishedReading = true; - } catch (Throwable t) { - /* We're usually not writing to stdout or stderr, but we shouldn't - * stay quiet about this potential bug. If we were to switch to a - * logging API, this would qualify as ERROR. */ - System.err.println("Bug: uncaught exception or error while " - + "reading descriptors:"); - t.printStackTrace(); - } finally { - this.descriptorQueue.setOutOfDescriptors(); - } - if (this.hasFinishedReading) { - this.writeNewHistory(); - } - } - private void readOldHistory() { - if (this.historyFile == null) { - return; - } - try { - BufferedReader br = new BufferedReader(new FileReader( - this.historyFile)); - String line; - while ((line = br.readLine()) != null) { - if (!line.contains(" ")) { - /* TODO Handle this problem? */ - continue; - } - long lastModifiedMillis = Long.parseLong(line.substring(0, - line.indexOf(" "))); - String absolutePath = line.substring(line.indexOf(" ") + 1); - this.excludedFilesBefore.put(absolutePath, lastModifiedMillis); - } - br.close(); - } catch (IOException e) { - /* TODO Handle this exception. */ - } catch (NumberFormatException e) { - /* TODO Handle this exception. */ - } - } - private void writeNewHistory() { - if (this.historyFile == null) { - return; - } - try { - if (this.historyFile.getParentFile() != null) { - this.historyFile.getParentFile().mkdirs(); - } - BufferedWriter bw = new BufferedWriter(new FileWriter( - this.historyFile)); - SortedMap<String, Long> newHistory = new TreeMap<>(); - newHistory.putAll(this.excludedFilesAfter); - newHistory.putAll(this.parsedFilesAfter); - for (Map.Entry<String, Long> e : newHistory.entrySet()) { - String absolutePath = e.getKey(); - long lastModifiedMillis = e.getValue(); - bw.write(String.valueOf(lastModifiedMillis) + " " + absolutePath - + "\n"); - } - bw.close(); - } catch (IOException e) { - /* TODO Handle this exception. */ - } - } - private void readDescriptors() { - for (File directory : this.directories) { - if (!directory.exists() || !directory.isDirectory()) { - continue; - } - Stack<File> files = new Stack<>(); - files.add(directory); - boolean abortReading = false; - while (!abortReading && !files.isEmpty()) { - File file = files.pop(); - if (file.isDirectory()) { - files.addAll(Arrays.asList(file.listFiles())); - } else if (file.getName().endsWith(".tar") || - file.getName().endsWith(".tar.bz2") || - file.getName().endsWith(".tar.xz")) { - this.tarballs.add(file); - } else { - String absolutePath = file.getAbsolutePath(); - long lastModifiedMillis = file.lastModified(); - if (this.excludedFilesBefore.containsKey(absolutePath) && - this.excludedFilesBefore.get(absolutePath) == - lastModifiedMillis) { - this.excludedFilesAfter.put(absolutePath, - lastModifiedMillis); - continue; - } - this.parsedFilesAfter.put(absolutePath, lastModifiedMillis); - DescriptorFileImpl descriptorFile = new DescriptorFileImpl(); - try { - descriptorFile.setDirectory(directory); - descriptorFile.setFile(file); - descriptorFile.setFileName(file.getAbsolutePath()); - descriptorFile.setLastModified(lastModifiedMillis); - descriptorFile.setDescriptors(this.readFile(file)); - } catch (DescriptorParseException e) { - descriptorFile.setException(e); - } catch (IOException e) { - descriptorFile.setException(e); - abortReading = true; - } - this.descriptorQueue.add(descriptorFile); - } - } - } - } - private void readTarballs() { - List<File> files = new ArrayList<>(this.tarballs); - boolean abortReading = false; - while (!abortReading && !files.isEmpty()) { - File tarball = files.remove(0); - if (!tarball.getName().endsWith(".tar") && - !tarball.getName().endsWith(".tar.bz2") && - !tarball.getName().endsWith(".tar.xz")) { - continue; - } - String absolutePath = tarball.getAbsolutePath(); - long lastModifiedMillis = tarball.lastModified(); - if (this.excludedFilesBefore.containsKey(absolutePath) && - this.excludedFilesBefore.get(absolutePath) == - lastModifiedMillis) { - this.excludedFilesAfter.put(absolutePath, lastModifiedMillis); - continue; - } - this.parsedFilesAfter.put(absolutePath, lastModifiedMillis); - try { - FileInputStream in = new FileInputStream(tarball); - if (in.available() > 0) { - TarArchiveInputStream tais = null; - if (tarball.getName().endsWith(".tar.bz2")) { - tais = new TarArchiveInputStream( - new BZip2CompressorInputStream(in)); - } else if (tarball.getName().endsWith(".tar.xz")) { - tais = new TarArchiveInputStream( - new XZCompressorInputStream(in)); - } else if (tarball.getName().endsWith(".tar")) { - tais = new TarArchiveInputStream(in); - } - BufferedInputStream bis = new BufferedInputStream(tais); - TarArchiveEntry tae = null; - while ((tae = tais.getNextTarEntry()) != null) { - if (tae.isDirectory()) { - continue; - } - DescriptorFileImpl descriptorFile = - new DescriptorFileImpl(); - descriptorFile.setTarball(tarball); - descriptorFile.setFileName(tae.getName()); - descriptorFile.setLastModified(tae.getLastModifiedDate(). - getTime()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[1024]; - while ((len = bis.read(data, 0, 1024)) >= 0) { - baos.write(data, 0, len); - } - byte[] rawDescriptorBytes = baos.toByteArray(); - if (rawDescriptorBytes.length < 1) { - continue; - } - try { - String fileName = tae.getName().substring( - tae.getName().lastIndexOf("/") + 1); - List<Descriptor> parsedDescriptors = - this.descriptorParser.parseDescriptors( - rawDescriptorBytes, fileName); - descriptorFile.setDescriptors(parsedDescriptors); - } catch (DescriptorParseException e) { - descriptorFile.setException(e); - } - this.descriptorQueue.add(descriptorFile); - } - } - } catch (IOException e) { - abortReading = true; - } - } - } - private List<Descriptor> readFile(File file) throws IOException, - DescriptorParseException { - FileInputStream fis = new FileInputStream(file); - BufferedInputStream bis = new BufferedInputStream(fis); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[1024]; - while ((len = bis.read(data, 0, 1024)) >= 0) { - baos.write(data, 0, len); - } - bis.close(); - byte[] rawDescriptorBytes = baos.toByteArray(); - return this.descriptorParser.parseDescriptors(rawDescriptorBytes, - file.getName()); - } - } -} - diff --git a/src/org/torproject/descriptor/impl/DescriptorRequestImpl.java b/src/org/torproject/descriptor/impl/DescriptorRequestImpl.java deleted file mode 100644 index 0238f24..0000000 --- a/src/org/torproject/descriptor/impl/DescriptorRequestImpl.java +++ /dev/null @@ -1,114 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.List; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorRequest; - -public class DescriptorRequestImpl implements DescriptorRequest { - - private String requestedResource; - protected void setRequestedResource(String requestedResource) { - this.requestedResource = requestedResource; - } - protected String getRequestedResource() { - return this.requestedResource; - } - - private String descriptorType; - protected void setDescriptorType(String descriptorType) { - this.descriptorType = descriptorType; - } - protected String getDescriptorType() { - return this.descriptorType; - } - - private byte[] responseBytes; - protected byte[] getResponseBytes() { - return this.responseBytes; - } - protected void setResponseBytes(byte[] responseBytes) { - this.responseBytes = responseBytes; - } - - private String requestUrl; - @Override - public String getRequestUrl() { - return this.requestUrl; - } - - private String directoryNickname; - protected void setDirectoryNickname(String directoryNickname) { - this.directoryNickname = directoryNickname; - } - @Override - public String getDirectoryNickname() { - return this.directoryNickname; - } - - private int responseCode; - protected void setResponseCode(int responseCode) { - this.responseCode = responseCode; - } - @Override - public int getResponseCode() { - return this.responseCode; - } - - private long requestStart; - protected void setRequestStart(long requestStart) { - this.requestStart = requestStart; - } - @Override - public long getRequestStart() { - return this.requestStart; - } - - private long requestEnd; - protected void setRequestEnd(long requestEnd) { - this.requestEnd = requestEnd; - } - @Override - public long getRequestEnd() { - return this.requestEnd; - } - - private boolean connectTimeoutHasExpired; - @Override - public boolean connectTimeoutHasExpired() { - return this.connectTimeoutHasExpired; - } - - private boolean readTimeoutHasExpired; - @Override - public boolean readTimeoutHasExpired() { - return this.readTimeoutHasExpired; - } - - private boolean globalTimeoutHasExpired; - @Override - public boolean globalTimeoutHasExpired() { - return this.globalTimeoutHasExpired; - } - - private List<Descriptor> descriptors; - protected void setDescriptors(List<Descriptor> descriptors) { - this.descriptors = descriptors; - } - @Override - public List<Descriptor> getDescriptors() { - return this.descriptors; - } - - private Exception exception; - protected void setException(Exception exception) { - this.exception = exception; - } - @Override - public Exception getException() { - return this.exception; - } -} - diff --git a/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java b/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java deleted file mode 100644 index fb2f5ad..0000000 --- a/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java +++ /dev/null @@ -1,218 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.torproject.descriptor.DirSourceEntry; - -public class DirSourceEntryImpl implements DirSourceEntry { - - private byte[] dirSourceEntryBytes; - @Override - public byte[] getDirSourceEntryBytes() { - return this.dirSourceEntryBytes; - } - - private boolean failUnrecognizedDescriptorLines; - private List<String> unrecognizedLines; - protected List<String> getAndClearUnrecognizedLines() { - List<String> lines = this.unrecognizedLines; - this.unrecognizedLines = null; - return lines; - } - - protected DirSourceEntryImpl(byte[] dirSourceEntryBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - this.dirSourceEntryBytes = dirSourceEntryBytes; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - this.initializeKeywords(); - this.parseDirSourceEntryBytes(); - this.checkAndClearKeywords(); - } - - private SortedSet<String> exactlyOnceKeywords, atMostOnceKeywords; - private void initializeKeywords() { - this.exactlyOnceKeywords = new TreeSet<>(); - this.exactlyOnceKeywords.add("dir-source"); - this.exactlyOnceKeywords.add("vote-digest"); - this.atMostOnceKeywords = new TreeSet<>(); - 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 checkAndClearKeywords() throws DescriptorParseException { - if (!this.exactlyOnceKeywords.isEmpty()) { - throw new DescriptorParseException("dir-source does not contain a '" - + this.exactlyOnceKeywords.first() + "' line."); - } - this.exactlyOnceKeywords = null; - this.atMostOnceKeywords = null; - } - - private void parseDirSourceEntryBytes() - throws DescriptorParseException { - Scanner s = new Scanner(new String(this.dirSourceEntryBytes)). - useDelimiter("\n"); - boolean skipCrypto = false; - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split(" "); - switch (parts[0]) { - case "dir-source": - this.parseDirSourceLine(line); - break; - case "contact": - this.parseContactLine(line); - break; - case "vote-digest": - this.parseVoteDigestLine(line); - break; - case "-----BEGIN": - skipCrypto = true; - break; - case "-----END": - skipCrypto = false; - break; - default: - if (!skipCrypto) { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in dir-source entry."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - private void parseDirSourceLine(String line) - throws DescriptorParseException { - this.parsedExactlyOnceKeyword("dir-source"); - String[] parts = line.split("[ \t]+"); - if (parts.length != 7) { - throw new DescriptorParseException("Invalid line '" + line + "'."); - } - 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]); - if (parts[3].length() < 1) { - throw new DescriptorParseException("Illegal hostname in '" + line - + "'."); - } - this.hostname = parts[3]; - 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("[ \t]+"); - if (parts.length != 2) { - throw new DescriptorParseException("Invalid line '" + line + "'."); - } - this.voteDigest = ParseHelper.parseTwentyByteHexString(line, - parts[1]); - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String identity; - @Override - public String getIdentity() { - return this.identity; - } - - private boolean isLegacy; - @Override - public boolean isLegacy() { - return this.isLegacy; - } - - private String hostname; - @Override - public String getHostname() { - return this.hostname; - } - - private String ip; - @Override - public String getIp() { - return this.ip; - } - - private int dirPort; - @Override - public int getDirPort() { - return this.dirPort; - } - - private int orPort; - @Override - public int getOrPort() { - return this.orPort; - } - - private String contactLine; - @Override - public String getContactLine() { - return this.contactLine; - } - - private String voteDigest; - @Override - public String getVoteDigest() { - return this.voteDigest; - } -} - diff --git a/src/org/torproject/descriptor/impl/DirectoryDownloader.java b/src/org/torproject/descriptor/impl/DirectoryDownloader.java deleted file mode 100644 index a27ed76..0000000 --- a/src/org/torproject/descriptor/impl/DirectoryDownloader.java +++ /dev/null @@ -1,104 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.zip.InflaterInputStream; - -import org.torproject.descriptor.DescriptorParser; -import org.torproject.descriptor.DescriptorSourceFactory; - -/* Download descriptors from one directory authority or mirror. First, - * ask the coordinator thread to create a request, run it, and deliver - * the response. Repeat until the coordinator thread says there are no - * further requests to make. */ -public class DirectoryDownloader implements Runnable { - - private String nickname; - private String ipPort; - private DescriptorParser descriptorParser; - protected DirectoryDownloader(String nickname, String ip, int dirPort) { - this.nickname = nickname; - this.ipPort = ip + ":" + String.valueOf(dirPort); - this.descriptorParser = - DescriptorSourceFactory.createDescriptorParser(); - } - - private DownloadCoordinator downloadCoordinator; - protected void setDownloadCoordinator( - DownloadCoordinator downloadCoordinator) { - this.downloadCoordinator = downloadCoordinator; - } - - private long connectTimeout; - protected void setConnectTimeout(long connectTimeout) { - this.connectTimeout = connectTimeout; - } - - private long readTimeout; - protected void setReadTimeout(long readTimeout) { - this.readTimeout = readTimeout; - } - - protected void setFailUnrecognizedDescriptorLines( - boolean failUnrecognizedDescriptorLines) { - this.descriptorParser.setFailUnrecognizedDescriptorLines( - failUnrecognizedDescriptorLines); - } - - @Override - public void run() { - boolean keepRunning = true; - do { - DescriptorRequestImpl request = - this.downloadCoordinator.createRequest(this.nickname); - if (request != null) { - String url = "http://" + this.ipPort - + request.getRequestedResource(); - request.setRequestStart(System.currentTimeMillis()); - HttpURLConnection huc = null; - try { - URL u = new URL(url); - huc = (HttpURLConnection) u.openConnection(); - huc.setConnectTimeout((int) this.connectTimeout); - huc.setReadTimeout((int) this.readTimeout); - huc.setRequestMethod("GET"); - huc.connect(); - int responseCode = huc.getResponseCode(); - request.setResponseCode(responseCode); - if (responseCode == 200) { - BufferedInputStream in = new BufferedInputStream( - new InflaterInputStream(huc.getInputStream())); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int len; - byte[] data = new byte[8192]; - while ((len = in.read(data, 0, 8192)) >= 0) { - baos.write(data, 0, len); - } - in.close(); - byte[] responseBytes = baos.toByteArray(); - request.setResponseBytes(responseBytes); - request.setRequestEnd(System.currentTimeMillis()); - request.setDescriptors(this.descriptorParser.parseDescriptors( - responseBytes, null)); - } - } catch (Exception e) { - request.setException(e); - if (huc != null) { - huc.disconnect(); - } - /* Stop downloading from this directory if there are any - * problems, e.g., refused connections. */ - keepRunning = false; - } - this.downloadCoordinator.deliverResponse(request); - } else { - keepRunning = false; - } - } while (keepRunning); - } -} - diff --git a/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java b/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java deleted file mode 100644 index b62fc8e..0000000 --- a/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java +++ /dev/null @@ -1,308 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.DirectoryKeyCertificate; - -/* TODO Add test class. */ - -public class DirectoryKeyCertificateImpl extends DescriptorImpl - implements DirectoryKeyCertificate { - - protected static List<DirectoryKeyCertificate> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<DirectoryKeyCertificate> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DirectoryKeyCertificateImpl.splitRawDescriptorBytes( - descriptorsBytes, "dir-key-certificate-version "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - DirectoryKeyCertificate parsedDescriptor = - new DirectoryKeyCertificateImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected DirectoryKeyCertificateImpl(byte[] rawDescriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); - this.parseDescriptorBytes(); - this.calculateDigest(); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( - "dir-key-certificate-version,fingerprint,dir-identity-key," - + "dir-key-published,dir-key-expires,dir-signing-key," - + "dir-key-certification").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "dir-address,dir-key-crosscert").split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("dir-key-certificate-version"); - this.checkLastKeyword("dir-key-certification"); - this.clearParsedKeywords(); - } - - private void parseDescriptorBytes() throws DescriptorParseException { - Scanner s = new Scanner(new String(this.rawDescriptorBytes)). - useDelimiter("\n"); - String nextCrypto = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "dir-key-certificate-version": - this.parseDirKeyCertificateVersionLine(line, parts); - break; - case "dir-address": - this.parseDirAddressLine(line, parts); - break; - case "fingerprint": - this.parseFingerprintLine(line, parts); - break; - case "dir-identity-key": - this.parseDirIdentityKeyLine(line, parts); - nextCrypto = "dir-identity-key"; - break; - case "dir-key-published": - this.parseDirKeyPublishedLine(line, parts); - break; - case "dir-key-expires": - this.parseDirKeyExpiresLine(line, parts); - break; - case "dir-signing-key": - this.parseDirSigningKeyLine(line, parts); - nextCrypto = "dir-signing-key"; - break; - case "dir-key-crosscert": - this.parseDirKeyCrosscertLine(line, parts); - nextCrypto = "dir-key-crosscert"; - break; - case "dir-key-certification": - this.parseDirKeyCertificationLine(line, parts); - nextCrypto = "dir-key-certification"; - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\n"); - String cryptoString = crypto.toString(); - crypto = null; - switch (nextCrypto) { - case "dir-identity-key": - this.dirIdentityKey = cryptoString; - break; - case "dir-signing-key": - this.dirSigningKey = cryptoString; - break; - case "dir-key-crosscert": - this.dirKeyCrosscert = cryptoString; - break; - case "dir-key-certification": - this.dirKeyCertification = cryptoString; - break; - default: - throw new DescriptorParseException("Unrecognized crypto " - + "block in directory key certificate."); - } - nextCrypto = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in directory key certificate."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - private void parseDirKeyCertificateVersionLine(String line, - String[] parts) throws DescriptorParseException { - if (!line.equals("dir-key-certificate-version 3")) { - throw new DescriptorParseException("Illegal directory key " - + "certificate version number in line '" + line + "'."); - } - this.dirKeyCertificateVersion = 3; - } - - private void parseDirAddressLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2 || parts[1].split(":").length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in directory key certificate."); - } - this.address = ParseHelper.parseIpv4Address(line, - parts[1].split(":")[0]); - this.port = ParseHelper.parsePort(line, parts[1].split(":")[1]); - } - - private void parseFingerprintLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in directory key certificate."); - } - this.fingerprint = ParseHelper.parseTwentyByteHexString(line, - parts[1]); - } - - private void parseDirIdentityKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-identity-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirKeyPublishedLine(String line, String[] parts) - throws DescriptorParseException { - this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - } - - private void parseDirKeyExpiresLine(String line, String[] parts) - throws DescriptorParseException { - this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - } - - private void parseDirSigningKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-signing-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirKeyCrosscertLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-key-crosscert")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirKeyCertificationLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-key-certification")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void calculateDigest() throws DescriptorParseException { - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "dir-key-certificate-version "; - String sigToken = "\ndir-key-certification\n"; - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, - forDigest, 0, sig - start); - this.certificateDigest = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.certificateDigest == null) { - throw new DescriptorParseException("Could not calculate " - + "certificate digest."); - } - } - - private int dirKeyCertificateVersion; - @Override - public int getDirKeyCertificateVersion() { - return this.dirKeyCertificateVersion; - } - - private String address; - @Override - public String getAddress() { - return this.address; - } - - private int port = -1; - @Override - public int getPort() { - return this.port; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private String dirIdentityKey; - @Override - public String getDirIdentityKey() { - return this.dirIdentityKey; - } - - private long dirKeyPublishedMillis; - @Override - public long getDirKeyPublishedMillis() { - return this.dirKeyPublishedMillis; - } - - private long dirKeyExpiresMillis; - @Override - public long getDirKeyExpiresMillis() { - return this.dirKeyExpiresMillis; - } - - private String dirSigningKey; - @Override - public String getDirSigningKey() { - return this.dirSigningKey; - } - - private String dirKeyCrosscert; - @Override - public String getDirKeyCrosscert() { - return this.dirKeyCrosscert; - } - - private String dirKeyCertification; - @Override - public String getDirKeyCertification() { - return this.dirKeyCertification; - } - - private String certificateDigest; - @Override - public String getCertificateDigest() { - return this.certificateDigest; - } -} - diff --git a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java deleted file mode 100644 index a955f62..0000000 --- a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -import org.torproject.descriptor.DirectorySignature; - -public class DirectorySignatureImpl implements DirectorySignature { - - private byte[] directorySignatureBytes; - - private boolean failUnrecognizedDescriptorLines; - private List<String> unrecognizedLines; - protected List<String> getAndClearUnrecognizedLines() { - List<String> lines = this.unrecognizedLines; - this.unrecognizedLines = null; - return lines; - } - - protected DirectorySignatureImpl(byte[] directorySignatureBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - this.directorySignatureBytes = directorySignatureBytes; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - this.parseDirectorySignatureBytes(); - } - - private void parseDirectorySignatureBytes() - throws DescriptorParseException { - Scanner s = new Scanner(new String(this.directorySignatureBytes)). - useDelimiter("\n"); - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split(" ", -1); - String keyword = parts[0]; - switch (keyword) { - case "directory-signature": - int algorithmOffset = 0; - switch (parts.length) { - case 4: - this.algorithm = parts[1]; - algorithmOffset = 1; - break; - case 3: - break; - default: - throw new DescriptorParseException("Illegal line '" + line - + "'."); - } - this.identity = ParseHelper.parseHexString(line, - parts[1 + algorithmOffset]); - this.signingKeyDigest = ParseHelper.parseHexString( - line, parts[2 + algorithmOffset]); - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\n"); - String cryptoString = crypto.toString(); - crypto = null; - this.signature = cryptoString; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in dir-source entry."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - static final String DEFAULT_ALGORITHM = "sha1"; - - private String algorithm; - @Override - public String getAlgorithm() { - return this.algorithm == null ? DEFAULT_ALGORITHM : this.algorithm; - } - - private String identity; - @Override - public String getIdentity() { - return this.identity; - } - - private String signingKeyDigest; - @Override - public String getSigningKeyDigest() { - return this.signingKeyDigest; - } - - private String signature; - @Override - public String getSignature() { - return this.signature; - } -} - diff --git a/src/org/torproject/descriptor/impl/DownloadCoordinator.java b/src/org/torproject/descriptor/impl/DownloadCoordinator.java deleted file mode 100644 index 72cfeae..0000000 --- a/src/org/torproject/descriptor/impl/DownloadCoordinator.java +++ /dev/null @@ -1,10 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -public interface DownloadCoordinator { - - public DescriptorRequestImpl createRequest(String nickname); - - public void deliverResponse(DescriptorRequestImpl request); -} diff --git a/src/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java b/src/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java deleted file mode 100644 index a8e3731..0000000 --- a/src/org/torproject/descriptor/impl/DownloadCoordinatorImpl.java +++ /dev/null @@ -1,298 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorRequest; -import org.torproject.descriptor.DirSourceEntry; -import org.torproject.descriptor.RelayNetworkStatusConsensus; - -/* TODO This whole download logic is a mess and needs a cleanup. */ -public class DownloadCoordinatorImpl implements DownloadCoordinator { - - private BlockingIteratorImpl<DescriptorRequest> descriptorQueue = - new BlockingIteratorImpl<>(); - protected Iterator<DescriptorRequest> getDescriptorQueue() { - return this.descriptorQueue; - } - - private SortedSet<String> runningDirectories; - private SortedMap<String, DirectoryDownloader> directoryAuthorities; - private SortedMap<String, DirectoryDownloader> directoryMirrors; - private boolean downloadConsensusFromAllAuthorities; - private boolean includeCurrentReferencedVotes; - private long connectTimeoutMillis; - private long readTimeoutMillis; - private long globalTimeoutMillis; - private boolean failUnrecognizedDescriptorLines; - - protected DownloadCoordinatorImpl( - SortedMap<String, DirectoryDownloader> directoryAuthorities, - SortedMap<String, DirectoryDownloader> directoryMirrors, - boolean downloadConsensus, - boolean downloadConsensusFromAllAuthorities, - Set<String> downloadVotes, boolean includeCurrentReferencedVotes, - long connectTimeoutMillis, long readTimeoutMillis, - long globalTimeoutMillis, boolean failUnrecognizedDescriptorLines) { - this.directoryAuthorities = directoryAuthorities; - this.directoryMirrors = directoryMirrors; - this.runningDirectories = new TreeSet<>(); - this.runningDirectories.addAll(directoryAuthorities.keySet()); - this.runningDirectories.addAll(directoryMirrors.keySet()); - this.missingConsensus = downloadConsensus; - this.downloadConsensusFromAllAuthorities = - downloadConsensusFromAllAuthorities; - this.missingVotes = downloadVotes; - this.includeCurrentReferencedVotes = includeCurrentReferencedVotes; - this.connectTimeoutMillis = connectTimeoutMillis; - this.readTimeoutMillis = readTimeoutMillis; - this.globalTimeoutMillis = globalTimeoutMillis; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - if (this.directoryMirrors.isEmpty() && - this.directoryAuthorities.isEmpty()) { - this.descriptorQueue.setOutOfDescriptors(); - /* TODO Should we say anything if we don't have any directories - * configured? */ - } else { - GlobalTimer globalTimer = new GlobalTimer(this.globalTimeoutMillis, - this); - this.globalTimerThread = new Thread(globalTimer); - this.globalTimerThread.start(); - for (DirectoryDownloader directoryMirror : - this.directoryMirrors.values()) { - directoryMirror.setDownloadCoordinator(this); - directoryMirror.setConnectTimeout(this.connectTimeoutMillis); - directoryMirror.setReadTimeout(this.readTimeoutMillis); - directoryMirror.setFailUnrecognizedDescriptorLines( - this.failUnrecognizedDescriptorLines); - new Thread(directoryMirror).start(); - } - for (DirectoryDownloader directoryAuthority : - this.directoryAuthorities.values()) { - directoryAuthority.setDownloadCoordinator(this); - directoryAuthority.setConnectTimeout(this.connectTimeoutMillis); - directoryAuthority.setReadTimeout(this.readTimeoutMillis); - directoryAuthority.setFailUnrecognizedDescriptorLines( - this.failUnrecognizedDescriptorLines); - new Thread(directoryAuthority).start(); - } - } - } - - /* Interrupt all downloads if the total download time exceeds a given - * time. */ - private Thread globalTimerThread; - private static class GlobalTimer implements Runnable { - private long timeoutMillis; - private DownloadCoordinatorImpl downloadCoordinator; - private GlobalTimer(long timeoutMillis, - DownloadCoordinatorImpl downloadCoordinator) { - this.timeoutMillis = timeoutMillis; - this.downloadCoordinator = downloadCoordinator; - } - public void run() { - long started = System.currentTimeMillis(), sleep; - while ((sleep = started + this.timeoutMillis - - System.currentTimeMillis()) > 0L) { - try { - Thread.sleep(sleep); - } catch (InterruptedException e) { - return; - } - } - this.downloadCoordinator.interruptAllDownloads(); - } - } - - /* Are we missing the consensus, and should the next directory that - * hasn't tried downloading it before attempt to download it? */ - private boolean missingConsensus = false; - - /* Which directories are currently attempting to download the - * consensus? */ - private Set<String> requestingConsensuses = new HashSet<>(); - - /* Which directories have attempted to download the consensus so far, - * including those directories that are currently attempting it? */ - private Set<String> requestedConsensuses = new HashSet<>(); - - /* Which votes are we currently missing? */ - private Set<String> missingVotes = new HashSet<>(); - - /* Which vote (map value) is a given directory (map key) currently - * attempting to download? */ - private Map<String, String> requestingVotes = new HashMap<>(); - - /* Which votes (map value) has a given directory (map key) attempted or - * is currently attempting to download? */ - private Map<String, Set<String>> requestedVotes = new HashMap<>(); - - private boolean hasFinishedDownloading = false; - - /* Look up what request a directory should make next. If there is - * nothing to do right now, but maybe later, block the caller. If - * we're done downloading, return null to notify the caller. */ - @Override - public synchronized DescriptorRequestImpl createRequest( - String nickname) { - while (!this.hasFinishedDownloading) { - DescriptorRequestImpl request = new DescriptorRequestImpl(); - request.setDirectoryNickname(nickname); - if ((this.missingConsensus || - (this.downloadConsensusFromAllAuthorities && - this.directoryAuthorities.containsKey(nickname))) && - !this.requestedConsensuses.contains(nickname)) { - if (!this.downloadConsensusFromAllAuthorities) { - this.missingConsensus = false; - } - this.requestingConsensuses.add(nickname); - this.requestedConsensuses.add(nickname); - request.setRequestedResource( - "/tor/status-vote/current/consensus.z"); - request.setDescriptorType("consensus"); - return request; - } - if (!this.missingVotes.isEmpty() && - this.directoryAuthorities.containsKey(nickname)) { - String requestingVote = null; - for (String missingVote : this.missingVotes) { - if (!this.requestedVotes.containsKey(nickname) || - !this.requestedVotes.get(nickname).contains(missingVote)) { - requestingVote = missingVote; - } - } - if (requestingVote != null) { - this.requestingVotes.put(nickname, requestingVote); - if (!this.requestedVotes.containsKey(nickname)) { - this.requestedVotes.put(nickname, new HashSet<String>()); - } - this.requestedVotes.get(nickname).add(requestingVote); - this.missingVotes.remove(requestingVote); - request.setRequestedResource("/tor/status-vote/current/" - + requestingVote + ".z"); - request.setDescriptorType("vote"); - return request; - } - } - /* TODO Add server descriptors and extra-info descriptors later. */ - try { - this.wait(); - } catch (InterruptedException e) { - /* TODO What shall we do? */ - } - } - return null; - } - - /* Deliver a response which may either contain one or more descriptors - * or a failure response code. Update the lists of missing descriptors, - * decide if there are more descriptors to download, and wake up any - * waiting downloader threads. */ - @Override - public synchronized void deliverResponse( - DescriptorRequestImpl response) { - String nickname = response.getDirectoryNickname(); - if (response.getException() != null) { - this.runningDirectories.remove(nickname); - } - switch (response.getDescriptorType()) { - case "consensus": - this.requestingConsensuses.remove(nickname); - if (response.getResponseCode() == 200 && - response.getDescriptors() != null) { - if (this.includeCurrentReferencedVotes) { - /* TODO Only add votes if the consensus is not older than one - * hour. Or does that make no sense? */ - for (Descriptor parsedDescriptor : - response.getDescriptors()) { - if (!(parsedDescriptor instanceof - RelayNetworkStatusConsensus)) { - continue; - } - RelayNetworkStatusConsensus parsedConsensus = - (RelayNetworkStatusConsensus) parsedDescriptor; - for (DirSourceEntry dirSource : - parsedConsensus.getDirSourceEntries().values()) { - String identity = dirSource.getIdentity(); - if (!this.missingVotes.contains(identity)) { - boolean alreadyRequested = false; - for (Set<String> requestedBefore : - this.requestedVotes.values()) { - if (requestedBefore.contains(identity)) { - alreadyRequested = true; - break; - } - } - if (!alreadyRequested) { - this.missingVotes.add(identity); - } - } - } - } - /* TODO Later, add referenced server descriptors. */ - } - } else { - this.missingConsensus = true; - } - break; - case "vote": - String requestedVote = requestingVotes.remove(nickname); - if (response.getResponseCode() != 200) { - this.missingVotes.add(requestedVote); - } - } - if (response.getRequestEnd() != 0L) { - this.descriptorQueue.add(response); - } - boolean doneDownloading = true; - if ((this.missingConsensus || - this.downloadConsensusFromAllAuthorities) && - (!this.requestedConsensuses.containsAll( - this.runningDirectories) || - !this.requestingConsensuses.isEmpty())) { - doneDownloading = false; - } - if (!this.requestingVotes.isEmpty()) { - doneDownloading = false; - } else if (!this.missingVotes.isEmpty()) { - if (!this.requestedVotes.keySet().containsAll( - this.runningDirectories)) { - doneDownloading = false; - } else { - for (String missingVote : this.missingVotes) { - for (String runningDirectory : this.runningDirectories) { - Set<String> reqVotes = this.requestedVotes.get( - runningDirectory); - if (!reqVotes.contains(missingVote)) { - doneDownloading = false; - } - } - } - } - } - if (doneDownloading) { - this.hasFinishedDownloading = true; - this.globalTimerThread.interrupt(); - this.descriptorQueue.setOutOfDescriptors(); - } - /* Wake up all waiting downloader threads. Maybe they can now - * download something, or they'll realize we're done downloading. */ - this.notifyAll(); - } - - private synchronized void interruptAllDownloads() { - this.hasFinishedDownloading = true; - this.notifyAll(); - } -} - diff --git a/src/org/torproject/descriptor/impl/ExitListEntryImpl.java b/src/org/torproject/descriptor/impl/ExitListEntryImpl.java deleted file mode 100644 index efbf31c..0000000 --- a/src/org/torproject/descriptor/impl/ExitListEntryImpl.java +++ /dev/null @@ -1,216 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExitList; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.torproject.descriptor.ExitListEntry; - -public class ExitListEntryImpl implements ExitListEntry, ExitList.Entry { - - private byte[] exitListEntryBytes; - - private boolean failUnrecognizedDescriptorLines; - private List<String> unrecognizedLines; - protected List<String> getAndClearUnrecognizedLines() { - List<String> lines = this.unrecognizedLines; - this.unrecognizedLines = null; - return lines; - } - - @Deprecated - private ExitListEntryImpl(String fingerprint, long publishedMillis, - long lastStatusMillis, String exitAddress, long scanMillis) { - this.fingerprint = fingerprint; - this.publishedMillis = publishedMillis; - this.lastStatusMillis = lastStatusMillis; - this.exitAddresses.put(exitAddress, scanMillis); - } - - @Deprecated - List<ExitListEntry> oldEntries() { - List<ExitListEntry> result = new ArrayList<>(); - if (this.exitAddresses.size() > 1) { - for (Map.Entry<String, Long> entry : - this.exitAddresses.entrySet()) { - result.add(new ExitListEntryImpl(this.fingerprint, - this.publishedMillis, this.lastStatusMillis, entry.getKey(), - entry.getValue())); - } - } else { - result.add(this); - } - return result; - } - - protected ExitListEntryImpl(byte[] exitListEntryBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - this.exitListEntryBytes = exitListEntryBytes; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - this.initializeKeywords(); - this.parseExitListEntryBytes(); - this.checkAndClearKeywords(); - } - - private SortedSet<String> keywordCountingSet; - private void initializeKeywords() { - this.keywordCountingSet = new TreeSet<>(); - this.keywordCountingSet.add("ExitNode"); - this.keywordCountingSet.add("Published"); - this.keywordCountingSet.add("LastStatus"); - this.keywordCountingSet.add("ExitAddress"); - } - - private void parsedExactlyOnceKeyword(String keyword) - throws DescriptorParseException { - if (!this.keywordCountingSet.contains(keyword)) { - throw new DescriptorParseException("Duplicate '" + keyword - + "' line in exit list entry."); - } - this.keywordCountingSet.remove(keyword); - } - - private void checkAndClearKeywords() throws DescriptorParseException { - for (String missingKeyword : this.keywordCountingSet) { - throw new DescriptorParseException("Missing '" + missingKeyword - + "' line in exit list entry."); - } - this.keywordCountingSet = null; - } - - private void parseExitListEntryBytes() - throws DescriptorParseException { - Scanner s = new Scanner(new String(this.exitListEntryBytes)). - useDelimiter(ExitList.EOL); - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split(" "); - String keyword = parts[0]; - switch (keyword) { - case "ExitNode": - this.parseExitNodeLine(line, parts); - break; - case "Published": - this.parsePublishedLine(line, parts); - break; - case "LastStatus": - this.parseLastStatusLine(line, parts); - break; - case "ExitAddress": - this.parseExitAddressLine(line, parts); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in exit list entry."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private void parseExitNodeLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Invalid line '" + line + "' in " - + "exit list entry."); - } - this.parsedExactlyOnceKeyword(parts[0]); - this.fingerprint = ParseHelper.parseTwentyByteHexString(line, - parts[1]); - } - - private void parsePublishedLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3) { - throw new DescriptorParseException("Invalid line '" + line + "' in " - + "exit list entry."); - } - this.parsedExactlyOnceKeyword(parts[0]); - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseLastStatusLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3) { - throw new DescriptorParseException("Invalid line '" + line + "' in " - + "exit list entry."); - } - this.parsedExactlyOnceKeyword(parts[0]); - this.lastStatusMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseExitAddressLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 4) { - throw new DescriptorParseException("Invalid line '" + line + "' in " - + "exit list entry."); - } - this.keywordCountingSet.remove(parts[0]); - this.exitAddresses.put(ParseHelper.parseIpv4Address(line, parts[1]), - ParseHelper.parseTimestampAtIndex(line, parts, 2, 3)); - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private long lastStatusMillis; - @Override - public long getLastStatusMillis() { - return this.lastStatusMillis; - } - - private String exitAddress; - @Override - public String getExitAddress() { - if (null == exitAddress) { - Map.Entry<String, Long> randomEntry = - this.exitAddresses.entrySet().iterator().next(); - this.exitAddress = randomEntry.getKey(); - this.scanMillis = randomEntry.getValue(); - } - return this.exitAddress; - } - - private Map<String, Long> exitAddresses = new HashMap<>(); - @Override - public Map<String, Long> getExitAddresses(){ - return new HashMap<>(this.exitAddresses); - } - - private long scanMillis; - @Override - public long getScanMillis() { - if (null == exitAddress) { - getExitAddress(); - } - return scanMillis; - } -} - diff --git a/src/org/torproject/descriptor/impl/ExitListImpl.java b/src/org/torproject/descriptor/impl/ExitListImpl.java deleted file mode 100644 index 10619ba..0000000 --- a/src/org/torproject/descriptor/impl/ExitListImpl.java +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Scanner; -import java.util.Set; -import java.util.TimeZone; - -import org.torproject.descriptor.ExitList; -import org.torproject.descriptor.ExitListEntry; - -public class ExitListImpl extends DescriptorImpl implements ExitList { - - protected ExitListImpl(byte[] rawDescriptorBytes, String fileName, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); - this.splitAndParseExitListEntries(rawDescriptorBytes); - this.setPublishedMillisFromFileName(fileName); - } - - private void setPublishedMillisFromFileName(String fileName) - throws DescriptorParseException { - if (this.downloadedMillis == 0L && - fileName.length() == "2012-02-01-04-06-24".length()) { - try { - SimpleDateFormat fileNameFormat = new SimpleDateFormat( - "yyyy-MM-dd-HH-mm-ss"); - fileNameFormat.setLenient(false); - fileNameFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - this.downloadedMillis = fileNameFormat.parse(fileName).getTime(); - } catch (ParseException e) { - /* Handle below. */ - } - } - if (this.downloadedMillis == 0L) { - throw new DescriptorParseException("Unrecognized exit list file " - + "name '" + fileName + "'."); - } - } - - private void splitAndParseExitListEntries(byte[] rawDescriptorBytes) - throws DescriptorParseException { - if (this.rawDescriptorBytes.length == 0) { - throw new DescriptorParseException("Descriptor is empty."); - } - String descriptorString = new String(rawDescriptorBytes); - Scanner s = new Scanner(descriptorString).useDelimiter(EOL); - StringBuilder sb = new StringBuilder(); - boolean firstEntry = true; - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("@")) { /* Skip annotation. */ - if (!s.hasNext()) { - throw new DescriptorParseException("Descriptor is empty."); - } else { - line = s.next(); - } - } - String[] parts = line.split(" "); - String keyword = parts[0]; - switch (keyword) { - case "Downloaded": - this.downloadedMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - break; - case "ExitNode": - if (!firstEntry) { - this.parseExitListEntry(sb.toString().getBytes()); - } else { - firstEntry = false; - } - sb = new StringBuilder(); - sb.append(line).append(ExitList.EOL); - break; - case "Published": - sb.append(line).append(ExitList.EOL); - break; - case "LastStatus": - sb.append(line).append(ExitList.EOL); - break; - case "ExitAddress": - sb.append(line).append(ExitList.EOL); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in exit list."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - /* Parse the last entry. */ - this.parseExitListEntry(sb.toString().getBytes()); - } - - protected void parseExitListEntry(byte[] exitListEntryBytes) - throws DescriptorParseException { - ExitListEntryImpl exitListEntry = new ExitListEntryImpl( - exitListEntryBytes, this.failUnrecognizedDescriptorLines); - this.exitListEntries.add(exitListEntry); - this.oldExitListEntries.addAll(exitListEntry.oldEntries()); - List<String> unrecognizedExitListEntryLines = exitListEntry. - getAndClearUnrecognizedLines(); - if (unrecognizedExitListEntryLines != null) { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(unrecognizedExitListEntryLines); - } - } - - private long downloadedMillis; - @Override - public long getDownloadedMillis() { - return this.downloadedMillis; - } - - private Set<ExitListEntry> oldExitListEntries = new HashSet<>(); - @Deprecated - @Override - public Set<ExitListEntry> getExitListEntries() { - return new HashSet<>(this.oldExitListEntries); - } - - private Set<ExitList.Entry> exitListEntries = new HashSet<>(); - @Override - public Set<ExitList.Entry> getEntries() { - return new HashSet<>(this.exitListEntries); - } -} - diff --git a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java deleted file mode 100644 index 3f72616..0000000 --- a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java +++ /dev/null @@ -1,1284 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.BandwidthHistory; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExtraInfoDescriptor; - -public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl - implements ExtraInfoDescriptor { - - protected ExtraInfoDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines, false); - this.parseDescriptorBytes(); - this.calculateDigest(); - this.calculateDigestSha256(); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( - "extra-info,published").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> dirreqStatsKeywords = new HashSet<>(Arrays.asList(( - "dirreq-stats-end,dirreq-v2-ips,dirreq-v3-ips,dirreq-v2-reqs," - + "dirreq-v3-reqs,dirreq-v2-share,dirreq-v3-share,dirreq-v2-resp," - + "dirreq-v3-resp,dirreq-v2-direct-dl,dirreq-v3-direct-dl," - + "dirreq-v2-tunneled-dl,dirreq-v3-tunneled-dl,").split(","))); - Set<String> entryStatsKeywords = new HashSet<>(Arrays.asList( - "entry-stats-end,entry-ips".split(","))); - Set<String> cellStatsKeywords = new HashSet<>(Arrays.asList(( - "cell-stats-end,cell-processed-cells,cell-queued-cells," - + "cell-time-in-queue,cell-circuits-per-decile").split(","))); - Set<String> connBiDirectStatsKeywords = new HashSet<>( - Arrays.asList("conn-bi-direct".split(","))); - Set<String> exitStatsKeywords = new HashSet<>(Arrays.asList(( - "exit-stats-end,exit-kibibytes-written,exit-kibibytes-read," - + "exit-streams-opened").split(","))); - Set<String> bridgeStatsKeywords = new HashSet<>(Arrays.asList( - "bridge-stats-end,bridge-stats-ips".split(","))); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "identity-ed25519,master-key-ed25519,read-history,write-history," - + "dirreq-read-history,dirreq-write-history,geoip-db-digest," - + "router-sig-ed25519,router-signature,router-digest-sha256," - + "router-digest").split(","))); - atMostOnceKeywords.addAll(dirreqStatsKeywords); - atMostOnceKeywords.addAll(entryStatsKeywords); - atMostOnceKeywords.addAll(cellStatsKeywords); - atMostOnceKeywords.addAll(connBiDirectStatsKeywords); - atMostOnceKeywords.addAll(exitStatsKeywords); - atMostOnceKeywords.addAll(bridgeStatsKeywords); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkKeywordsDependOn(dirreqStatsKeywords, "dirreq-stats-end"); - this.checkKeywordsDependOn(entryStatsKeywords, "entry-stats-end"); - this.checkKeywordsDependOn(cellStatsKeywords, "cell-stats-end"); - this.checkKeywordsDependOn(exitStatsKeywords, "exit-stats-end"); - this.checkKeywordsDependOn(bridgeStatsKeywords, "bridge-stats-end"); - this.checkFirstKeyword("extra-info"); - this.clearParsedKeywords(); - return; - } - - private void parseDescriptorBytes() throws DescriptorParseException { - Scanner s = new Scanner(new String(this.rawDescriptorBytes)). - useDelimiter("\n"); - String nextCrypto = ""; - List<String> cryptoLines = null; - while (s.hasNext()) { - String line = s.next(); - String lineNoOpt = line.startsWith("opt ") ? - line.substring("opt ".length()) : line; - String[] partsNoOpt = lineNoOpt.split("[ \t]+"); - String keyword = partsNoOpt[0]; - switch (keyword) { - case "extra-info": - this.parseExtraInfoLine(line, lineNoOpt, partsNoOpt); - break; - case "published": - this.parsePublishedLine(line, lineNoOpt, partsNoOpt); - break; - case "read-history": - this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "write-history": - this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "geoip-db-digest": - this.parseGeoipDbDigestLine(line, lineNoOpt, partsNoOpt); - break; - case "geoip6-db-digest": - this.parseGeoip6DbDigestLine(line, lineNoOpt, partsNoOpt); - break; - case "geoip-start-time": - this.parseGeoipStartTimeLine(line, lineNoOpt, partsNoOpt); - break; - case "geoip-client-origins": - this.parseGeoipClientOriginsLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-stats-end": - this.parseDirreqStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-ips": - this.parseDirreqV2IpsLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-ips": - this.parseDirreqV3IpsLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-reqs": - this.parseDirreqV2ReqsLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-reqs": - this.parseDirreqV3ReqsLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-share": - this.parseDirreqV2ShareLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-share": - this.parseDirreqV3ShareLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-resp": - this.parseDirreqV2RespLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-resp": - this.parseDirreqV3RespLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-direct-dl": - this.parseDirreqV2DirectDlLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-direct-dl": - this.parseDirreqV3DirectDlLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v2-tunneled-dl": - this.parseDirreqV2TunneledDlLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-v3-tunneled-dl": - this.parseDirreqV3TunneledDlLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-read-history": - this.parseDirreqReadHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "dirreq-write-history": - this.parseDirreqWriteHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "entry-stats-end": - this.parseEntryStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "entry-ips": - this.parseEntryIpsLine(line, lineNoOpt, partsNoOpt); - break; - case "cell-stats-end": - this.parseCellStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "cell-processed-cells": - this.parseCellProcessedCellsLine(line, lineNoOpt, partsNoOpt); - break; - case "cell-queued-cells": - this.parseCellQueuedCellsLine(line, lineNoOpt, partsNoOpt); - break; - case "cell-time-in-queue": - this.parseCellTimeInQueueLine(line, lineNoOpt, partsNoOpt); - break; - case "cell-circuits-per-decile": - this.parseCellCircuitsPerDecileLine(line, lineNoOpt, partsNoOpt); - break; - case "conn-bi-direct": - this.parseConnBiDirectLine(line, lineNoOpt, partsNoOpt); - break; - case "exit-stats-end": - this.parseExitStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "exit-kibibytes-written": - this.parseExitKibibytesWrittenLine(line, lineNoOpt, partsNoOpt); - break; - case "exit-kibibytes-read": - this.parseExitKibibytesReadLine(line, lineNoOpt, partsNoOpt); - break; - case "exit-streams-opened": - this.parseExitStreamsOpenedLine(line, lineNoOpt, partsNoOpt); - break; - case "bridge-stats-end": - this.parseBridgeStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "bridge-ips": - this.parseBridgeStatsIpsLine(line, lineNoOpt, partsNoOpt); - break; - case "bridge-ip-versions": - this.parseBridgeIpVersionsLine(line, lineNoOpt, partsNoOpt); - break; - case "bridge-ip-transports": - this.parseBridgeIpTransportsLine(line, lineNoOpt, partsNoOpt); - break; - case "transport": - this.parseTransportLine(line, lineNoOpt, partsNoOpt); - break; - case "hidserv-stats-end": - this.parseHidservStatsEndLine(line, lineNoOpt, partsNoOpt); - break; - case "hidserv-rend-relayed-cells": - this.parseHidservRendRelayedCellsLine(line, lineNoOpt, - partsNoOpt); - break; - case "hidserv-dir-onions-seen": - this.parseHidservDirOnionsSeenLine(line, lineNoOpt, partsNoOpt); - break; - case "identity-ed25519": - this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt); - nextCrypto = "identity-ed25519"; - break; - case "master-key-ed25519": - this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt); - break; - case "router-sig-ed25519": - this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt); - break; - case "router-signature": - this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "router-signature"; - break; - case "router-digest": - this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt); - break; - case "router-digest-sha256": - this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt); - break; - case "-----BEGIN": - cryptoLines = new ArrayList<>(); - cryptoLines.add(line); - break; - case "-----END": - cryptoLines.add(line); - StringBuilder sb = new StringBuilder(); - for (String cryptoLine : cryptoLines) { - sb.append("\n").append(cryptoLine); - } - String cryptoString = sb.toString().substring(1); - switch (nextCrypto) { - case "router-signature": - this.routerSignature = cryptoString; - break; - case "identity-ed25519": - this.identityEd25519 = cryptoString; - this.parseIdentityEd25519CryptoBlock(cryptoString); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized crypto " - + "block '" + cryptoString + "' in extra-info " - + "descriptor."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(cryptoLines); - } - cryptoLines = null; - nextCrypto = ""; - } - break; - default: - if (cryptoLines != null) { - cryptoLines.add(line); - } else { - ParseHelper.parseKeyword(line, partsNoOpt[0]); - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in extra-info descriptor."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - private void parseExtraInfoLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 3) { - throw new DescriptorParseException("Illegal line '" + line - + "' in extra-info descriptor."); - } - this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]); - this.fingerprint = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[2]); - } - - private void parsePublishedLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, - partsNoOpt, 1, 2); - } - - private void parseReadHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.readHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseWriteHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.writeHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseGeoipDbDigestLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in extra-info descriptor."); - } - this.geoipDbDigest = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[1]); - } - - private void parseGeoip6DbDigestLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in extra-info descriptor."); - } - this.geoip6DbDigest = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[1]); - } - - private void parseGeoipStartTimeLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 3) { - throw new DescriptorParseException("Illegal line '" + line - + "' in extra-info descriptor."); - } - this.geoipStartTimeMillis = ParseHelper.parseTimestampAtIndex(line, - partsNoOpt, 1, 2); - } - - private void parseGeoipClientOriginsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.geoipClientOrigins = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 2); - } - - private void parseDirreqStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.dirreqStatsEndMillis = parsedStatsEndData[0]; - this.dirreqStatsIntervalLength = parsedStatsEndData[1]; - } - - private long[] parseStatsEndLine(String line, String partsNoOpt[], - int partsNoOptExpectedLength) throws DescriptorParseException { - if (partsNoOpt.length != partsNoOptExpectedLength || - partsNoOpt[3].length() < 2 || !partsNoOpt[3].startsWith("(") || - !partsNoOpt[4].equals("s)")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - long[] result = new long[2]; - result[0] = ParseHelper.parseTimestampAtIndex(line, partsNoOpt, 1, 2); - result[1] = ParseHelper.parseSeconds(line, - partsNoOpt[3].substring(1)); - if (result[1] <= 0) { - throw new DescriptorParseException("Interval length must be " - + "positive in line '" + line + "'."); - } - return result; - } - - private void parseDirreqV2IpsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2Ips = ParseHelper.parseCommaSeparatedKeyIntegerValueList( - line, partsNoOpt, 1, 2); - } - - private void parseDirreqV3IpsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3Ips = ParseHelper.parseCommaSeparatedKeyIntegerValueList( - line, partsNoOpt, 1, 2); - } - - private void parseDirreqV2ReqsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2Reqs = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 2); - } - - private void parseDirreqV3ReqsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3Reqs = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 2); - } - - private void parseDirreqV2ShareLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2Share = this.parseShareLine(line, partsNoOpt); - } - - private void parseDirreqV3ShareLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3Share = this.parseShareLine(line, partsNoOpt); - } - - private double parseShareLine(String line, String[] partsNoOpt) - throws DescriptorParseException { - double share = -1.0; - if (partsNoOpt.length == 2 && partsNoOpt[1].length() >= 2 && - partsNoOpt[1].endsWith("%")) { - String shareString = partsNoOpt[1]; - shareString = shareString.substring(0, shareString.length() - 1); - try { - share = Double.parseDouble(shareString); - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (share < 0.0) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - return share; - } - - private void parseDirreqV2RespLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2Resp = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseDirreqV3RespLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3Resp = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseDirreqV2DirectDlLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2DirectDl = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseDirreqV3DirectDlLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3DirectDl = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseDirreqV2TunneledDlLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV2TunneledDl = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseDirreqV3TunneledDlLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqV3TunneledDl = - ParseHelper.parseCommaSeparatedKeyIntegerValueList( - line,partsNoOpt, 1, 0); - } - - private void parseDirreqReadHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqReadHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseDirreqWriteHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.dirreqWriteHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseEntryStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.entryStatsEndMillis = parsedStatsEndData[0]; - this.entryStatsIntervalLength = parsedStatsEndData[1]; - } - - private void parseEntryIpsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.entryIps = ParseHelper.parseCommaSeparatedKeyIntegerValueList( - line, partsNoOpt, 1, 2); - } - - private void parseCellStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.cellStatsEndMillis = parsedStatsEndData[0]; - this.cellStatsIntervalLength = parsedStatsEndData[1]; - } - - private void parseCellProcessedCellsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.cellProcessedCells = ParseHelper. - parseCommaSeparatedIntegerValueList(line, partsNoOpt, 1); - if (this.cellProcessedCells.length != 10) { - throw new DescriptorParseException("There must be exact ten values " - + "in line '" + line + "'."); - } - } - - private void parseCellQueuedCellsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.cellQueuedCells = ParseHelper.parseCommaSeparatedDoubleValueList( - line, partsNoOpt, 1); - if (this.cellQueuedCells.length != 10) { - throw new DescriptorParseException("There must be exact ten values " - + "in line '" + line + "'."); - } - } - - private void parseCellTimeInQueueLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.cellTimeInQueue = ParseHelper. - parseCommaSeparatedIntegerValueList(line, partsNoOpt, 1); - if (this.cellTimeInQueue.length != 10) { - throw new DescriptorParseException("There must be exact ten values " - + "in line '" + line + "'."); - } - } - - private void parseCellCircuitsPerDecileLine(String line, - String lineNoOpt, String[] partsNoOpt) - throws DescriptorParseException { - int circuits = -1; - if (partsNoOpt.length == 2) { - try { - circuits = Integer.parseInt(partsNoOpt[1]); - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (circuits < 0) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.cellCircuitsPerDecile = circuits; - } - - private void parseConnBiDirectLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 6); - this.connBiDirectStatsEndMillis = parsedStatsEndData[0]; - this.connBiDirectStatsIntervalLength = parsedStatsEndData[1]; - Integer[] parsedConnBiDirectStats = ParseHelper. - parseCommaSeparatedIntegerValueList(line, partsNoOpt, 5); - if (parsedConnBiDirectStats.length != 4) { - throw new DescriptorParseException("Illegal line '" + line + "' in " - + "extra-info descriptor."); - } - this.connBiDirectBelow = parsedConnBiDirectStats[0]; - this.connBiDirectRead = parsedConnBiDirectStats[1]; - this.connBiDirectWrite = parsedConnBiDirectStats[2]; - this.connBiDirectBoth = parsedConnBiDirectStats[3]; - } - - private void parseExitStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.exitStatsEndMillis = parsedStatsEndData[0]; - this.exitStatsIntervalLength = parsedStatsEndData[1]; - } - - private void parseExitKibibytesWrittenLine(String line, - String lineNoOpt, String[] partsNoOpt) - throws DescriptorParseException { - this.exitKibibytesWritten = this.sortByPorts(ParseHelper. - parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); - this.verifyPorts(line, this.exitKibibytesWritten.keySet()); - this.verifyBytesOrStreams(line, this.exitKibibytesWritten.values()); - } - - private void parseExitKibibytesReadLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.exitKibibytesRead = this.sortByPorts(ParseHelper. - parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); - this.verifyPorts(line, this.exitKibibytesRead.keySet()); - this.verifyBytesOrStreams(line, this.exitKibibytesRead.values()); - } - - private void parseExitStreamsOpenedLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.exitStreamsOpened = this.sortByPorts(ParseHelper. - parseCommaSeparatedKeyLongValueList(line, partsNoOpt, 1, 0)); - this.verifyPorts(line, this.exitStreamsOpened.keySet()); - this.verifyBytesOrStreams(line, this.exitStreamsOpened.values()); - } - - private SortedMap<String, Long> sortByPorts( - SortedMap<String, Long> naturalOrder) { - SortedMap<String, Long> byPortNumber = - new TreeMap<String, Long>(new Comparator<String>() { - public int compare(String arg0, String arg1) { - int port0 = 0, port1 = 0; - try { - port1 = Integer.parseInt(arg1); - } catch (NumberFormatException e) { - return -1; - } - try { - port0 = Integer.parseInt(arg0); - } catch (NumberFormatException e) { - return 1; - } - if (port0 < port1) { - return -1; - } else if (port0 > port1) { - return 1; - } else { - return 0; - } - }}); - byPortNumber.putAll(naturalOrder); - return byPortNumber; - } - - private void verifyPorts(String line, Set<String> ports) - throws DescriptorParseException { - boolean valid = true; - try { - for (String port : ports) { - if (!port.equals("other") && Integer.parseInt(port) <= 0) { - valid = false; - break; - } - } - } catch (NumberFormatException e) { - valid = false; - } - if (!valid) { - throw new DescriptorParseException("Invalid port in line '" + line - + "'."); - } - } - - private void verifyBytesOrStreams(String line, - Collection<Long> bytesOrStreams) throws DescriptorParseException { - boolean valid = true; - for (long s : bytesOrStreams) { - if (s < 0L) { - valid = false; - break; - } - } - if (!valid) { - throw new DescriptorParseException("Invalid value in line '" + line - + "'."); - } - } - - private void parseBridgeStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.bridgeStatsEndMillis = parsedStatsEndData[0]; - this.bridgeStatsIntervalLength = parsedStatsEndData[1]; - } - - private void parseBridgeStatsIpsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.bridgeIps = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 2); - } - - private void parseBridgeIpVersionsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.bridgeIpVersions = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 2); - } - - private void parseBridgeIpTransportsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.bridgeIpTransports = - ParseHelper.parseCommaSeparatedKeyIntegerValueList(line, - partsNoOpt, 1, 0); - } - - private void parseTransportLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length < 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.transports.add(partsNoOpt[1]); - } - - private void parseHidservStatsEndLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt, - 5); - this.hidservStatsEndMillis = parsedStatsEndData[0]; - this.hidservStatsIntervalLength = parsedStatsEndData[1]; - } - - private void parseHidservRendRelayedCellsLine(String line, - String lineNoOpt, String[] partsNoOpt) - throws DescriptorParseException { - if (partsNoOpt.length < 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - try { - this.hidservRendRelayedCells = Double.parseDouble(partsNoOpt[1]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.hidservRendRelayedCellsParameters = - ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line, - partsNoOpt, 2); - } - - private void parseHidservDirOnionsSeenLine(String line, - String lineNoOpt, String[] partsNoOpt) - throws DescriptorParseException { - if (partsNoOpt.length < 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - try { - this.hidservDirOnionsSeen = Double.parseDouble(partsNoOpt[1]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.hidservDirOnionsSeenParameters = - ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line, - partsNoOpt, 2); - } - - private void parseRouterSignatureLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("router-signature")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseRouterDigestLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[1]); - } - - private void parseIdentityEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 1) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseIdentityEd25519CryptoBlock(String cryptoString) - throws DescriptorParseException { - String masterKeyEd25519FromIdentityEd25519 = - ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( - cryptoString); - if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals( - masterKeyEd25519FromIdentityEd25519)) { - throw new DescriptorParseException("Mismatch between " - + "identity-ed25519 and master-key-ed25519."); - } - this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519; - } - - private void parseMasterKeyEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1]; - if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals( - masterKeyEd25519FromMasterKeyEd25519Line)) { - throw new DescriptorParseException("Mismatch between " - + "identity-ed25519 and master-key-ed25519."); - } - this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line; - } - - private void parseRouterSigEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.routerSignatureEd25519 = partsNoOpt[1]; - } - - private void parseRouterDigestSha256Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]); - this.extraInfoDigestSha256 = partsNoOpt[1]; - } - - private void calculateDigest() throws DescriptorParseException { - if (this.extraInfoDigest != null) { - /* We already learned the descriptor digest of this bridge - * descriptor from a "router-digest" line. */ - return; - } - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "extra-info "; - String sigToken = "\nrouter-signature\n"; - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, - forDigest, 0, sig - start); - this.extraInfoDigest = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.extraInfoDigest == null) { - throw new DescriptorParseException("Could not calculate extra-info " - + "descriptor digest."); - } - } - - private void calculateDigestSha256() throws DescriptorParseException { - if (this.extraInfoDigestSha256 != null) { - /* We already learned the descriptor digest of this bridge - * descriptor from a "router-digest-sha256" line. */ - return; - } - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "extra-info "; - String sigToken = "\n-----END SIGNATURE-----\n"; - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, forDigest, - 0, sig - start); - this.extraInfoDigestSha256 = DatatypeConverter.printBase64Binary( - MessageDigest.getInstance("SHA-256").digest(forDigest)). - replaceAll("=", ""); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.extraInfoDigestSha256 == null) { - throw new DescriptorParseException("Could not calculate extra-info " - + "descriptor SHA-256 digest."); - } - } - - private String extraInfoDigest; - @Override - public String getExtraInfoDigest() { - return this.extraInfoDigest; - } - - private String extraInfoDigestSha256; - @Override - public String getExtraInfoDigestSha256() { - return this.extraInfoDigestSha256; - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private BandwidthHistory readHistory; - @Override - public BandwidthHistory getReadHistory() { - return this.readHistory; - } - - private BandwidthHistory writeHistory; - @Override - public BandwidthHistory getWriteHistory() { - return this.writeHistory; - } - - private String geoipDbDigest; - @Override - public String getGeoipDbDigest() { - return this.geoipDbDigest; - } - - private String geoip6DbDigest; - @Override - public String getGeoip6DbDigest() { - return this.geoip6DbDigest; - } - - private long dirreqStatsEndMillis = -1L; - @Override - public long getDirreqStatsEndMillis() { - return this.dirreqStatsEndMillis; - } - - private long dirreqStatsIntervalLength = -1L; - @Override - public long getDirreqStatsIntervalLength() { - return this.dirreqStatsIntervalLength; - } - - private String dirreqV2Ips; - @Override - public SortedMap<String, Integer> getDirreqV2Ips() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV2Ips); - } - - private String dirreqV3Ips; - @Override - public SortedMap<String, Integer> getDirreqV3Ips() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV3Ips); - } - - private String dirreqV2Reqs; - @Override - public SortedMap<String, Integer> getDirreqV2Reqs() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV2Reqs); - } - - private String dirreqV3Reqs; - @Override - public SortedMap<String, Integer> getDirreqV3Reqs() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV3Reqs); - } - - private double dirreqV2Share = -1.0; - @Override - public double getDirreqV2Share() { - return this.dirreqV2Share; - } - - private double dirreqV3Share = -1.0; - @Override - public double getDirreqV3Share() { - return this.dirreqV3Share; - } - - private String dirreqV2Resp; - @Override - public SortedMap<String, Integer> getDirreqV2Resp() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV2Resp); - } - - private String dirreqV3Resp; - @Override - public SortedMap<String, Integer> getDirreqV3Resp() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV3Resp); - } - - private String dirreqV2DirectDl; - @Override - public SortedMap<String, Integer> getDirreqV2DirectDl() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV2DirectDl); - } - - private String dirreqV3DirectDl; - @Override - public SortedMap<String, Integer> getDirreqV3DirectDl() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV3DirectDl); - } - - private String dirreqV2TunneledDl; - @Override - public SortedMap<String, Integer> getDirreqV2TunneledDl() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV2TunneledDl); - } - - private String dirreqV3TunneledDl; - @Override - public SortedMap<String, Integer> getDirreqV3TunneledDl() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.dirreqV3TunneledDl); - } - - private BandwidthHistory dirreqReadHistory; - @Override - public BandwidthHistory getDirreqReadHistory() { - return this.dirreqReadHistory; - } - - private BandwidthHistory dirreqWriteHistory; - @Override - public BandwidthHistory getDirreqWriteHistory() { - return this.dirreqWriteHistory; - } - - private long entryStatsEndMillis = -1L; - @Override - public long getEntryStatsEndMillis() { - return this.entryStatsEndMillis; - } - - private long entryStatsIntervalLength = -1L; - @Override - public long getEntryStatsIntervalLength() { - return this.entryStatsIntervalLength; - } - - private String entryIps; - @Override - public SortedMap<String, Integer> getEntryIps() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.entryIps); - } - - private long cellStatsEndMillis = -1L; - @Override - public long getCellStatsEndMillis() { - return this.cellStatsEndMillis; - } - - private long cellStatsIntervalLength = -1L; - @Override - public long getCellStatsIntervalLength() { - return this.cellStatsIntervalLength; - } - - private Integer[] cellProcessedCells; - @Override - public List<Integer> getCellProcessedCells() { - return this.cellProcessedCells == null ? null : - Arrays.asList(this.cellProcessedCells); - } - - private Double[] cellQueuedCells; - @Override - public List<Double> getCellQueuedCells() { - return this.cellQueuedCells == null ? null : - Arrays.asList(this.cellQueuedCells); - } - - private Integer[] cellTimeInQueue; - @Override - public List<Integer> getCellTimeInQueue() { - return this.cellTimeInQueue == null ? null : - Arrays.asList(this.cellTimeInQueue); - } - - private int cellCircuitsPerDecile = -1; - @Override - public int getCellCircuitsPerDecile() { - return this.cellCircuitsPerDecile; - } - - private long connBiDirectStatsEndMillis = -1L; - @Override - public long getConnBiDirectStatsEndMillis() { - return this.connBiDirectStatsEndMillis; - } - - private long connBiDirectStatsIntervalLength = -1L; - @Override - public long getConnBiDirectStatsIntervalLength() { - return this.connBiDirectStatsIntervalLength; - } - - private int connBiDirectBelow = -1; - @Override - public int getConnBiDirectBelow() { - return this.connBiDirectBelow; - } - - private int connBiDirectRead = -1; - @Override - public int getConnBiDirectRead() { - return this.connBiDirectRead; - } - - private int connBiDirectWrite = -1; - @Override - public int getConnBiDirectWrite() { - return this.connBiDirectWrite; - } - - private int connBiDirectBoth = -1; - @Override - public int getConnBiDirectBoth() { - return this.connBiDirectBoth; - } - - private long exitStatsEndMillis = -1L; - @Override - public long getExitStatsEndMillis() { - return this.exitStatsEndMillis; - } - - private long exitStatsIntervalLength = -1L; - @Override - public long getExitStatsIntervalLength() { - return this.exitStatsIntervalLength; - } - - private SortedMap<String, Long> exitKibibytesWritten; - @Override - public SortedMap<String, Long> getExitKibibytesWritten() { - return this.exitKibibytesWritten == null ? null : - new TreeMap<>(this.exitKibibytesWritten); - } - - private SortedMap<String, Long> exitKibibytesRead; - @Override - public SortedMap<String, Long> getExitKibibytesRead() { - return this.exitKibibytesRead == null ? null : - new TreeMap<>(this.exitKibibytesRead); - } - - private SortedMap<String, Long> exitStreamsOpened; - @Override - public SortedMap<String, Long> getExitStreamsOpened() { - return this.exitStreamsOpened == null ? null : - new TreeMap<>(this.exitStreamsOpened); - } - - private long geoipStartTimeMillis = -1L; - @Override - public long getGeoipStartTimeMillis() { - return this.geoipStartTimeMillis; - } - - private String geoipClientOrigins; - @Override - public SortedMap<String, Integer> getGeoipClientOrigins() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.geoipClientOrigins); - } - - private long bridgeStatsEndMillis = -1L; - @Override - public long getBridgeStatsEndMillis() { - return this.bridgeStatsEndMillis; - } - - private long bridgeStatsIntervalLength = -1L; - @Override - public long getBridgeStatsIntervalLength() { - return this.bridgeStatsIntervalLength; - } - - private String bridgeIps; - @Override - public SortedMap<String, Integer> getBridgeIps() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.bridgeIps); - } - - private String bridgeIpVersions; - @Override - public SortedMap<String, Integer> getBridgeIpVersions() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.bridgeIpVersions); - } - - private String bridgeIpTransports; - @Override - public SortedMap<String, Integer> getBridgeIpTransports() { - return ParseHelper.convertCommaSeparatedKeyIntegerValueList( - this.bridgeIpTransports); - } - - private List<String> transports = new ArrayList<>(); - @Override - public List<String> getTransports() { - return new ArrayList<>(this.transports); - } - - private long hidservStatsEndMillis = -1L; - @Override - public long getHidservStatsEndMillis() { - return this.hidservStatsEndMillis; - } - - private long hidservStatsIntervalLength = -1L; - @Override - public long getHidservStatsIntervalLength() { - return this.hidservStatsIntervalLength; - } - - private Double hidservRendRelayedCells; - @Override - public Double getHidservRendRelayedCells() { - return this.hidservRendRelayedCells; - } - - private Map<String, Double> hidservRendRelayedCellsParameters; - @Override - public Map<String, Double> getHidservRendRelayedCellsParameters() { - return this.hidservRendRelayedCellsParameters == null ? null : - new HashMap<>(this.hidservRendRelayedCellsParameters); - } - - private Double hidservDirOnionsSeen; - @Override - public Double getHidservDirOnionsSeen() { - return this.hidservDirOnionsSeen; - } - - private Map<String, Double> hidservDirOnionsSeenParameters; - @Override - public Map<String, Double> getHidservDirOnionsSeenParameters() { - return this.hidservDirOnionsSeenParameters == null ? null : - new HashMap<>(this.hidservDirOnionsSeenParameters); - } - - private String routerSignature; - @Override - public String getRouterSignature() { - return this.routerSignature; - } - - private String identityEd25519; - @Override - public String getIdentityEd25519() { - return this.identityEd25519; - } - - private String masterKeyEd25519; - @Override - public String getMasterKeyEd25519() { - return this.masterKeyEd25519; - } - - private String routerSignatureEd25519; - @Override - public String getRouterSignatureEd25519() { - return this.routerSignatureEd25519; - } -} - diff --git a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java b/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java deleted file mode 100644 index 4931c31..0000000 --- a/src/org/torproject/descriptor/impl/MicrodescriptorImpl.java +++ /dev/null @@ -1,328 +0,0 @@ -/* Copyright 2014--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; -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<>(); - 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<>(Arrays.asList( - "onion-key".split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "ntor-onion-key,family,p,p6,id").split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("onion-key"); - this.clearParsedKeywords(); - return; - } - - private void parseDescriptorBytes() throws DescriptorParseException { - Scanner s = new Scanner(new String(this.rawDescriptorBytes)). - useDelimiter("\n"); - String nextCrypto = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("@")) { - continue; - } - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "onion-key": - this.parseOnionKeyLine(line, parts); - nextCrypto = "onion-key"; - break; - case "ntor-onion-key": - this.parseNtorOnionKeyLine(line, parts); - break; - case "a": - this.parseALine(line, parts); - break; - case "family": - this.parseFamilyLine(line, parts); - break; - case "p": - this.parsePLine(line, parts); - break; - case "p6": - this.parseP6Line(line, parts); - break; - case "id": - this.parseIdLine(line, parts); - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\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 = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\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<>(); - } - 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 { - String[] familyEntries = new String[parts.length - 1]; - 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)); - familyEntries[i - 1] = "$" + fingerprint + separator + nickname; - } else { - familyEntries[i - 1] = "$" - + ParseHelper.parseTwentyByteHexString(line, - parts[i].substring(1)); - } - } else { - familyEntries[i - 1] = ParseHelper.parseNickname(line, parts[i]); - } - } - this.familyEntries = familyEntries; - } - - 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 { - switch (parts[1]) { - case "accept": - case "reject": - String[] ports = parts[2].split(",", -1); - for (int i = 0; i < ports.length; i++) { - if (ports[i].length() < 1) { - isValid = false; - break; - } - } - break; - default: - isValid = false; - } - } - if (!isValid) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseIdLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } else { - switch (parts[1]) { - case "ed25519": - ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]); - this.ed25519Identity = parts[2]; - break; - case "rsa1024": - ParseHelper.parseTwentyByteBase64String(line, parts[2]); - this.rsa1024Identity = parts[2]; - break; - default: - 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 = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-256").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.microdescriptorDigest == null) { - throw new DescriptorParseException("Could not calculate " - + "microdescriptor digest."); - } - } - - private String microdescriptorDigest; - @Override - public String getMicrodescriptorDigest() { - return this.microdescriptorDigest; - } - - private String onionKey; - @Override - public String getOnionKey() { - return this.onionKey; - } - - private String ntorOnionKey; - @Override - public String getNtorOnionKey() { - return this.ntorOnionKey; - } - - private List<String> orAddresses = new ArrayList<>(); - @Override - public List<String> getOrAddresses() { - return new ArrayList<>(this.orAddresses); - } - - private String[] familyEntries; - @Override - public List<String> getFamilyEntries() { - return this.familyEntries == null ? null : - Arrays.asList(this.familyEntries); - } - private String defaultPolicy; - @Override - public String getDefaultPolicy() { - return this.defaultPolicy; - } - - private String portList; - @Override - public String getPortList() { - return this.portList; - } - - private String ipv6DefaultPolicy; - @Override - public String getIpv6DefaultPolicy() { - return this.ipv6DefaultPolicy; - } - - private String ipv6PortList; - @Override - public String getIpv6PortList() { - return this.ipv6PortList; - } - - private String rsa1024Identity; - @Override - public String getRsa1024Identity() { - return this.rsa1024Identity; - } - - private String ed25519Identity; - @Override - public String getEd25519Identity() { - return this.ed25519Identity; - } -} - diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java deleted file mode 100644 index b73d211..0000000 --- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java +++ /dev/null @@ -1,382 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.torproject.descriptor.NetworkStatusEntry; - -public class NetworkStatusEntryImpl implements NetworkStatusEntry { - - private byte[] statusEntryBytes; - @Override - public byte[] getStatusEntryBytes() { - return this.statusEntryBytes; - } - - private boolean microdescConsensus; - - private boolean failUnrecognizedDescriptorLines; - private List<String> unrecognizedLines; - protected List<String> getAndClearUnrecognizedLines() { - List<String> lines = this.unrecognizedLines; - this.unrecognizedLines = null; - return lines; - } - - protected NetworkStatusEntryImpl(byte[] statusEntryBytes, - boolean microdescConsensus, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - this.statusEntryBytes = statusEntryBytes; - this.microdescConsensus = microdescConsensus; - this.failUnrecognizedDescriptorLines = - failUnrecognizedDescriptorLines; - this.initializeKeywords(); - this.parseStatusEntryBytes(); - this.clearAtMostOnceKeywords(); - } - - private SortedSet<String> atMostOnceKeywords; - private void initializeKeywords() { - this.atMostOnceKeywords = new TreeSet<>(); - this.atMostOnceKeywords.add("s"); - this.atMostOnceKeywords.add("v"); - this.atMostOnceKeywords.add("w"); - this.atMostOnceKeywords.add("p"); - } - - 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 { - Scanner s = new Scanner(new String(this.statusEntryBytes)). - useDelimiter("\n"); - String line = null; - if (!s.hasNext() || !(line = s.next()).startsWith("r ")) { - throw new DescriptorParseException("Status entry must start with " - + "an r line."); - } - String[] rLineParts = line.split("[ \t]+"); - this.parseRLine(line, rLineParts); - while (s.hasNext()) { - line = s.next(); - String[] parts = !line.startsWith("opt ") ? line.split("[ \t]+") : - line.substring("opt ".length()).split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "a": - this.parseALine(line, parts); - break; - case "s": - this.parseSLine(line, parts); - break; - case "v": - this.parseVLine(line, parts); - break; - case "w": - this.parseWLine(line, parts); - break; - case "p": - this.parsePLine(line, parts); - break; - case "m": - this.parseMLine(line, parts); - break; - case "id": - this.parseIdLine(line, parts); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in status entry."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private void parseRLine(String line, String[] parts) - throws DescriptorParseException { - 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]); - int descriptorOffset = 0; - if (!this.microdescConsensus) { - this.descriptor = ParseHelper.parseTwentyByteBase64String(line, - parts[3]); - descriptorOffset = 1; - } - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 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) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Invalid line '" + line + "' in " - + "status entry."); - } - /* TODO Add more checks. */ - /* TODO Add tests. */ - this.orAddresses.add(parts[1]); - } - - private static Map<String, Integer> flagIndexes = new HashMap<>(); - private static Map<Integer, String> flagStrings = new HashMap<>(); - - private void parseSLine(String line, String[] parts) - throws DescriptorParseException { - this.parsedAtMostOnceKeyword("s"); - BitSet flags = new BitSet(flagIndexes.size()); - for (int i = 1; i < parts.length; i++) { - String flag = parts[i]; - if (!flagIndexes.containsKey(flag)) { - flagStrings.put(flagIndexes.size(), flag); - flagIndexes.put(flag, flagIndexes.size()); - } - flags.set(flagIndexes.get(flag)); - } - this.flags = flags; - } - - 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.parseKeyValueIntegerPairs(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.containsKey("Unmeasured")) { - this.unmeasured = pairs.remove("Unmeasured") == 1L; - } - if (!pairs.isEmpty()) { - /* Ignore unknown key-value pair. */ - } - } - - private void parsePLine(String line, String[] parts) - throws DescriptorParseException { - this.parsedAtMostOnceKeyword("p"); - boolean isValid = true; - if (parts.length != 3) { - isValid = false; - } else { - switch (parts[1]) { - case "accept": - case "reject": - 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; - } - } - break; - default: - isValid = false; - } - } - if (!isValid) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseMLine(String line, String[] parts) - throws DescriptorParseException { - if (this.microdescriptorDigests == null) { - this.microdescriptorDigests = new HashSet<>(); - } - 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 void parseIdLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3 || !"ed25519".equals(parts[1])) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } else if ("none".equals(parts[2])) { - this.masterKeyEd25519 = "none"; - } else { - ParseHelper.parseThirtyTwoByteBase64String(line, parts[2]); - this.masterKeyEd25519 = parts[2]; - } - } - - private void clearAtMostOnceKeywords() { - this.atMostOnceKeywords = null; - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private String descriptor; - @Override - public String getDescriptor() { - return this.descriptor; - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private String address; - @Override - public String getAddress() { - return this.address; - } - - private int orPort; - @Override - public int getOrPort() { - return this.orPort; - } - - private int dirPort; - @Override - public int getDirPort() { - return this.dirPort; - } - - private Set<String> microdescriptorDigests; - @Override - public Set<String> getMicrodescriptorDigests() { - return this.microdescriptorDigests == null ? null : - new HashSet<>(this.microdescriptorDigests); - } - - private List<String> orAddresses = new ArrayList<>(); - @Override - public List<String> getOrAddresses() { - return new ArrayList<>(this.orAddresses); - } - - private BitSet flags; - @Override - public SortedSet<String> getFlags() { - SortedSet<String> result = new TreeSet<>(); - if (this.flags != null) { - for (int i = this.flags.nextSetBit(0); i >= 0; - i = this.flags.nextSetBit(i + 1)) { - result.add(flagStrings.get(i)); - } - } - return result; - } - - private String version; - @Override - public String getVersion() { - return this.version; - } - - private long bandwidth = -1L; - @Override - public long getBandwidth() { - return this.bandwidth; - } - - private long measured = -1L; - @Override - public long getMeasured() { - return this.measured; - } - - private boolean unmeasured = false; - @Override - public boolean getUnmeasured() { - return this.unmeasured; - } - - private String defaultPolicy; - @Override - public String getDefaultPolicy() { - return this.defaultPolicy; - } - - private String portList; - @Override - public String getPortList() { - return this.portList; - } - - private String masterKeyEd25519; - @Override - public String getMasterKeyEd25519() { - return this.masterKeyEd25519; - } -} - diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java deleted file mode 100644 index 5fa22c7..0000000 --- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java +++ /dev/null @@ -1,270 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.torproject.descriptor.DirSourceEntry; -import org.torproject.descriptor.DirectorySignature; -import org.torproject.descriptor.NetworkStatusEntry; - -/* Parse the common parts of v3 consensuses, v3 votes, v3 microdesc - * consensuses, v2 statuses, and sanitized bridge network statuses and - * delegate the specific parts to the subclasses. */ -public abstract class NetworkStatusImpl extends DescriptorImpl { - - protected NetworkStatusImpl(byte[] rawDescriptorBytes, - boolean failUnrecognizedDescriptorLines, - boolean containsDirSourceEntries, boolean blankLinesAllowed) - throws DescriptorParseException { - super(rawDescriptorBytes, failUnrecognizedDescriptorLines, - blankLinesAllowed); - this.splitAndParseParts(this.rawDescriptorBytes, - containsDirSourceEntries); - } - - private void splitAndParseParts(byte[] rawDescriptorBytes, - boolean containsDirSourceEntries) throws DescriptorParseException { - if (this.rawDescriptorBytes.length == 0) { - throw new DescriptorParseException("Descriptor is empty."); - } - String descriptorString = new String(rawDescriptorBytes); - int startIndex = 0; - int firstDirSourceIndex = !containsDirSourceEntries ? -1 : - this.findFirstIndexOfKeyword(descriptorString, "dir-source"); - int firstRIndex = this.findFirstIndexOfKeyword(descriptorString, "r"); - int directoryFooterIndex = this.findFirstIndexOfKeyword( - descriptorString, "directory-footer"); - int firstDirectorySignatureIndex = this.findFirstIndexOfKeyword( - descriptorString, "directory-signature"); - int endIndex = descriptorString.length(); - if (firstDirectorySignatureIndex < 0) { - firstDirectorySignatureIndex = endIndex; - } - if (directoryFooterIndex < 0) { - directoryFooterIndex = firstDirectorySignatureIndex; - } - if (firstRIndex < 0) { - firstRIndex = directoryFooterIndex; - } - if (firstDirSourceIndex < 0) { - firstDirSourceIndex = firstRIndex; - } - if (firstDirSourceIndex > startIndex) { - this.parseHeaderBytes(descriptorString, startIndex, - firstDirSourceIndex); - } - if (firstRIndex > firstDirSourceIndex) { - this.parseDirSourceBytes(descriptorString, firstDirSourceIndex, - firstRIndex); - } - if (directoryFooterIndex > firstRIndex) { - this.parseStatusEntryBytes(descriptorString, firstRIndex, - directoryFooterIndex); - } - if (firstDirectorySignatureIndex > directoryFooterIndex) { - this.parseDirectoryFooterBytes(descriptorString, - directoryFooterIndex, firstDirectorySignatureIndex); - } - if (endIndex > firstDirectorySignatureIndex) { - this.parseDirectorySignatureBytes(descriptorString, - firstDirectorySignatureIndex, 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 parseDirSourceBytes(String descriptorString, int start, - int end) throws DescriptorParseException { - List<byte[]> splitDirSourceBytes = - this.splitByKeyword(descriptorString, "dir-source", start, end); - for (byte[] dirSourceBytes : splitDirSourceBytes) { - this.parseDirSource(dirSourceBytes); - } - } - - private void parseStatusEntryBytes(String descriptorString, int start, - int end) throws DescriptorParseException { - List<byte[]> splitStatusEntryBytes = - this.splitByKeyword(descriptorString, "r", start, end); - for (byte[] statusEntryBytes : splitStatusEntryBytes) { - this.parseStatusEntry(statusEntryBytes); - } - } - - private void parseDirectoryFooterBytes(String descriptorString, - int start, int end) throws DescriptorParseException { - byte[] directoryFooterBytes = new byte[end - start]; - System.arraycopy(this.rawDescriptorBytes, start, - directoryFooterBytes, 0, end - start); - this.parseFooter(directoryFooterBytes); - } - - 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<>(); - 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; - } - byte[] part = new byte[to - from]; - System.arraycopy(this.rawDescriptorBytes, from, part, 0, - to - from); - from = to; - splitParts.add(part); - } - return splitParts; - } - - protected abstract void parseHeader(byte[] headerBytes) - throws DescriptorParseException; - - protected void parseDirSource(byte[] dirSourceBytes) - throws DescriptorParseException { - DirSourceEntryImpl dirSourceEntry = new DirSourceEntryImpl( - dirSourceBytes, this.failUnrecognizedDescriptorLines); - this.dirSourceEntries.put(dirSourceEntry.getIdentity(), - dirSourceEntry); - List<String> unrecognizedDirSourceLines = dirSourceEntry. - getAndClearUnrecognizedLines(); - if (unrecognizedDirSourceLines != null) { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(unrecognizedDirSourceLines); - } - } - - protected String[] parseClientOrServerVersions(String line, - String[] parts) throws DescriptorParseException { - String[] result = null; - switch (parts.length) { - case 1: - result = new String[0]; - break; - case 2: - result = parts[1].split(",", -1); - for (String version : result) { - if (version.length() < 1) { - throw new DescriptorParseException("Illegal versions line '" - + line + "'."); - } - } - break; - default: - throw new DescriptorParseException("Illegal versions line '" + line - + "'."); - } - return result; - } - - protected void parseStatusEntry(byte[] statusEntryBytes) - throws DescriptorParseException { - NetworkStatusEntryImpl statusEntry = new NetworkStatusEntryImpl( - statusEntryBytes, false, this.failUnrecognizedDescriptorLines); - this.statusEntries.put(statusEntry.getFingerprint(), statusEntry); - List<String> unrecognizedStatusEntryLines = statusEntry. - getAndClearUnrecognizedLines(); - if (unrecognizedStatusEntryLines != null) { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); - } - } - - protected abstract void parseFooter(byte[] footerBytes) - throws DescriptorParseException; - - protected void parseDirectorySignature(byte[] directorySignatureBytes) - throws DescriptorParseException { - if (this.signatures == null) { - this.signatures = new ArrayList<>(); - } - DirectorySignatureImpl signature = new DirectorySignatureImpl( - directorySignatureBytes, failUnrecognizedDescriptorLines); - this.signatures.add(signature); - List<String> unrecognizedStatusEntryLines = signature. - getAndClearUnrecognizedLines(); - if (unrecognizedStatusEntryLines != null) { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); - } - } - - protected SortedMap<String, DirSourceEntry> dirSourceEntries = - new TreeMap<>(); - public SortedMap<String, DirSourceEntry> getDirSourceEntries() { - return new TreeMap<>(this.dirSourceEntries); - } - - protected SortedMap<String, NetworkStatusEntry> statusEntries = - new TreeMap<>(); - public SortedMap<String, NetworkStatusEntry> getStatusEntries() { - return new TreeMap<>(this.statusEntries); - } - public boolean containsStatusEntry(String fingerprint) { - return this.statusEntries.containsKey(fingerprint); - } - public NetworkStatusEntry getStatusEntry(String fingerprint) { - return this.statusEntries.get(fingerprint); - } - - protected List<DirectorySignature> signatures; - public List<DirectorySignature> getSignatures() { - return this.signatures == null ? null - : new ArrayList<>(this.signatures); - } - public SortedMap<String, DirectorySignature> getDirectorySignatures() { - SortedMap<String, DirectorySignature> directorySignatures = null; - if (this.signatures != null) { - directorySignatures = new TreeMap<>(); - for (DirectorySignature signature : this.signatures) { - directorySignatures.put(signature.getIdentity(), signature); - } - } - return directorySignatures; - } -} - diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java deleted file mode 100644 index 82c0813..0000000 --- a/src/org/torproject/descriptor/impl/ParseHelper.java +++ /dev/null @@ -1,567 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.SortedMap; -import java.util.TimeZone; -import java.util.TreeMap; -import java.util.regex.Pattern; - -import javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; - -public class ParseHelper { - - private static Pattern keywordPattern = - Pattern.compile("^[A-Za-z0-9-]+$"); - protected static String parseKeyword(String line, String keyword) - throws DescriptorParseException { - if (!keywordPattern.matcher(keyword).matches()) { - throw new DescriptorParseException("Unrecognized character in " - + "keyword '" + keyword + "' in line '" + line + "'."); - } - return keyword; - } - - private static Pattern ipv4Pattern = - Pattern.compile("^[0-9\.]{7,15}$"); - protected 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; - } - - protected 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; - } - - protected static long parseSeconds(String line, String secondsString) - throws DescriptorParseException { - try { - return Long.parseLong(secondsString); - } catch (NumberFormatException e) { - throw new DescriptorParseException("'" + secondsString + "' in " - + "line '" + line + "' is not a valid time in seconds."); - } - } - - protected static String parseExitPattern(String line, String exitPattern) - throws DescriptorParseException { - if (!exitPattern.contains(":")) { - throw new DescriptorParseException("'" + exitPattern + "' in line '" - + line + "' must contain address and port."); - } - String[] parts = exitPattern.split(":"); - String addressPart = parts[0]; - /* TODO Extend to IPv6. */ - if (addressPart.equals("*")) { - /* Nothing to check. */ - } else if (addressPart.contains("/")) { - String[] addressParts = addressPart.split("/"); - String address = addressParts[0]; - String mask = addressParts[1]; - ParseHelper.parseIpv4Address(line, address); - if (addressParts.length != 2) { - throw new DescriptorParseException("'" + addressPart + "' in " - + "line '" + line + "' is not a valid address part."); - } - if (mask.contains(".")) { - ParseHelper.parseIpv4Address(line, mask); - } else { - int maskValue = -1; - try { - maskValue = Integer.parseInt(mask); - } catch (NumberFormatException e) { - /* Handle below. */ - } - if (maskValue < 0 || maskValue > 32) { - throw new DescriptorParseException("'" + mask + "' in line '" - + line + "' is not a valid IPv4 mask."); - } - } - } else { - ParseHelper.parseIpv4Address(line, addressPart); - } - String portPart = parts[1]; - if (portPart.equals("*")) { - /* Nothing to check. */ - } else if (portPart.contains("-")) { - String[] portParts = portPart.split("-"); - String fromPort = portParts[0]; - ParseHelper.parsePort(line, fromPort); - String toPort = portParts[1]; - ParseHelper.parsePort(line, toPort); - } else { - ParseHelper.parsePort(line, portPart); - } - return exitPattern; - } - - private static ThreadLocal<Map<String, DateFormat>> dateFormats = - new ThreadLocal<Map<String, DateFormat>> () { - public Map<String, DateFormat> get() { - return super.get(); - } - protected Map<String, DateFormat> initialValue() { - return new HashMap<>(); - } - public void remove() { - super.remove(); - } - public void set(Map<String, DateFormat> value) { - super.set(value); - } - }; - static DateFormat getDateFormat(String format) { - Map<String, DateFormat> threadDateFormats = dateFormats.get(); - if (!threadDateFormats.containsKey(format)) { - DateFormat dateFormat = new SimpleDateFormat(format, Locale.US); - dateFormat.setLenient(false); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - threadDateFormats.put(format, dateFormat); - } - return threadDateFormats.get(format); - } - - protected 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 { - DateFormat dateTimeFormat = getDateFormat("yyyy-MM-dd HH:mm:ss"); - result = dateTimeFormat.parse( - parts[dateIndex] + " " + parts[timeIndex]).getTime(); - } catch (ParseException e) { - /* Leave result at -1L. */ - } - if (result < 0L || result / 1000L > (long) Integer.MAX_VALUE) { - throw new DescriptorParseException("Illegal timestamp format in " - + "line '" + line + "'."); - } - return result; - } - - protected static long parseDateAtIndex(String line, String[] parts, - int dateIndex) throws DescriptorParseException { - if (dateIndex >= parts.length) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a date at the expected position."); - } - long result = -1L; - try { - DateFormat dateFormat = getDateFormat("yyyy-MM-dd"); - result = dateFormat.parse(parts[dateIndex]).getTime(); - } catch (ParseException e) { - /* Leave result at -1L. */ - } - if (result < 0L || result / 1000L > (long) Integer.MAX_VALUE) { - throw new DescriptorParseException("Illegal date format in line '" - + line + "'."); - } - return result; - } - - protected static String parseTwentyByteHexString(String line, - String hexString) throws DescriptorParseException { - return parseHexString(line, hexString, 40); - } - - protected static String parseHexString(String line, String hexString) - throws DescriptorParseException { - return parseHexString(line, hexString, -1); - } - - private static Pattern hexPattern = Pattern.compile("^[0-9a-fA-F]*$"); - private static String parseHexString(String line, String hexString, - int expectedLength) throws DescriptorParseException { - if (!hexPattern.matcher(hexString).matches() || - hexString.length() % 2 != 0 || - (expectedLength >= 0 && hexString.length() != expectedLength)) { - throw new DescriptorParseException("Illegal hex string in line '" - + line + "'."); - } - return hexString.toUpperCase(); - } - - protected static SortedMap<String, String> parseKeyValueStringPairs( - String line, String[] parts, int startIndex, String separatorString) - throws DescriptorParseException { - SortedMap<String, String> result = new TreeMap<>(); - for (int i = startIndex; i < parts.length; i++) { - String pair = parts[i]; - String[] pairParts = pair.split(separatorString); - if (pairParts.length != 2) { - throw new DescriptorParseException("Illegal key-value pair in " - + "line '" + line + "'."); - } - result.put(pairParts[0], pairParts[1]); - } - return result; - } - - protected static SortedMap<String, Integer> parseKeyValueIntegerPairs( - String line, String[] parts, int startIndex, String separatorString) - throws DescriptorParseException { - SortedMap<String, Integer> result = new TreeMap<>(); - SortedMap<String, String> keyValueStringPairs = - ParseHelper.parseKeyValueStringPairs(line, parts, startIndex, - separatorString); - for (Map.Entry<String, String> e : keyValueStringPairs.entrySet()) { - try { - result.put(e.getKey(), Integer.parseInt(e.getValue())); - } catch (NumberFormatException ex) { - throw new DescriptorParseException("Illegal value in line '" - + line + "'."); - } - } - return result; - } - - private static Pattern nicknamePattern = - Pattern.compile("^[0-9a-zA-Z]{1,19}$"); - protected static String parseNickname(String line, String nickname) - throws DescriptorParseException { - if (!nicknamePattern.matcher(nickname).matches()) { - throw new DescriptorParseException("Illegal nickname in line '" - + line + "'."); - } - return nickname; - } - - protected static boolean parseBoolean(String b, String line) - throws DescriptorParseException { - switch (b) { - case "1": - return true; - case "0": - return false; - default: - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private static Pattern twentyByteBase64Pattern = - Pattern.compile("^[0-9a-zA-Z+/]{27}$"); - protected static String parseTwentyByteBase64String(String line, - String base64String) throws DescriptorParseException { - if (!twentyByteBase64Pattern.matcher(base64String).matches()) { - throw new DescriptorParseException("'" + base64String - + "' in line '" + line + "' is not a valid base64-encoded " - + "20-byte value."); - } - return DatatypeConverter.printHexBinary( - DatatypeConverter.parseBase64Binary(base64String + "=")). - toUpperCase(); - } - - private static Pattern thirtyTwoByteBase64Pattern = - Pattern.compile("^[0-9a-zA-Z+/]{43}$"); - protected 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 DatatypeConverter.printHexBinary( - DatatypeConverter.parseBase64Binary(base64String + "=")). - toUpperCase(); - } - - private static Map<Integer, Pattern> - commaSeparatedKeyValueListPatterns = new HashMap<>(); - protected static String parseCommaSeparatedKeyIntegerValueList( - String line, String[] partsNoOpt, int index, int keyLength) - throws DescriptorParseException { - String result = ""; - if (partsNoOpt.length < index) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a key-value list at index " + index + "."); - } else if (partsNoOpt.length > index + 1 ) { - throw new DescriptorParseException("Line '" + line + "' contains " - + "unrecognized values beyond the expected key-value list at " - + "index " + index + "."); - } else if (partsNoOpt.length > index) { - if (!commaSeparatedKeyValueListPatterns.containsKey(keyLength)) { - String keyPattern = "[0-9a-zA-Z?<>\-_]" - + (keyLength == 0 ? "+" : "{" + keyLength + "}"); - String valuePattern = "\-?[0-9]{1,9}"; - String patternString = String.format("^%s=%s(,%s=%s)*$", - keyPattern, valuePattern, keyPattern, valuePattern); - commaSeparatedKeyValueListPatterns.put(keyLength, - Pattern.compile(patternString)); - } - Pattern pattern = commaSeparatedKeyValueListPatterns.get( - keyLength); - if (pattern.matcher(partsNoOpt[index]).matches()) { - result = partsNoOpt[index]; - } else { - throw new DescriptorParseException("Line '" + line + "' " - + "contains an illegal key or value."); - } - } - return result; - } - - protected static SortedMap<String, Integer> - convertCommaSeparatedKeyIntegerValueList(String validatedString) { - SortedMap<String, Integer> result = null; - if (validatedString != null) { - result = new TreeMap<>(); - if (validatedString.contains("=")) { - for (String listElement : validatedString.split(",", -1)) { - String[] keyAndValue = listElement.split("="); - result.put(keyAndValue[0], Integer.parseInt(keyAndValue[1])); - } - } - } - return result; - } - - protected static SortedMap<String, Long> - parseCommaSeparatedKeyLongValueList(String line, - String[] partsNoOpt, int index, int keyLength) - throws DescriptorParseException { - SortedMap<String, Long> result = new TreeMap<>(); - if (partsNoOpt.length < index) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a key-value list at index " + index + "."); - } else if (partsNoOpt.length > index + 1 ) { - throw new DescriptorParseException("Line '" + line + "' contains " - + "unrecognized values beyond the expected key-value list at " - + "index " + index + "."); - } else if (partsNoOpt.length > index) { - String[] listElements = partsNoOpt[index].split(",", -1); - for (String listElement : listElements) { - String[] keyAndValue = listElement.split("="); - String key = null; - long value = -1; - if (keyAndValue.length == 2 && (keyLength == 0 || - keyAndValue[0].length() == keyLength)) { - try { - value = Long.parseLong(keyAndValue[1]); - key = keyAndValue[0]; - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (key == null) { - throw new DescriptorParseException("Line '" + line + "' " - + "contains an illegal key or value in list element '" - + listElement + "'."); - } - result.put(key, value); - } - } - return result; - } - - protected static Integer[] parseCommaSeparatedIntegerValueList( - String line, String[] partsNoOpt, int index) - throws DescriptorParseException { - Integer[] result = null; - if (partsNoOpt.length < index) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a comma-separated value list at index " + index - + "."); - } else if (partsNoOpt.length > index + 1) { - throw new DescriptorParseException("Line '" + line + "' contains " - + "unrecognized values beyond the expected comma-separated " - + "value list at index " + index + "."); - } else if (partsNoOpt.length > index) { - String[] listElements = partsNoOpt[index].split(",", -1); - result = new Integer[listElements.length]; - for (int i = 0; i < listElements.length; i++) { - try { - result[i] = Integer.parseInt(listElements[i]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Line '" + line + "' " - + "contains an illegal value in list element '" - + listElements[i] + "'."); - } - } - } - return result; - } - - protected static Double[] parseCommaSeparatedDoubleValueList( - String line, String[] partsNoOpt, int index) - throws DescriptorParseException { - Double[] result = null; - if (partsNoOpt.length < index) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a comma-separated value list at index " + index - + "."); - } else if (partsNoOpt.length > index + 1) { - throw new DescriptorParseException("Line '" + line + "' contains " - + "unrecognized values beyond the expected comma-separated " - + "value list at index " + index + "."); - } else if (partsNoOpt.length > index) { - String[] listElements = partsNoOpt[index].split(",", -1); - result = new Double[listElements.length]; - for (int i = 0; i < listElements.length; i++) { - try { - result[i] = Double.parseDouble(listElements[i]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Line '" + line + "' " - + "contains an illegal value in list element '" - + listElements[i] + "'."); - } - } - } - return result; - } - - protected static Map<String, Double> - parseSpaceSeparatedStringKeyDoubleValueMap(String line, - String[] partsNoOpt, int startIndex) - throws DescriptorParseException { - Map<String, Double> result = new LinkedHashMap<>(); - if (partsNoOpt.length < startIndex) { - throw new DescriptorParseException("Line '" + line + "' does not " - + "contain a key-value list starting at index " + startIndex - + "."); - } - for (int i = startIndex; i < partsNoOpt.length; i++) { - String listElement = partsNoOpt[i]; - String[] keyAndValue = listElement.split("="); - String key = null; - Double value = null; - if (keyAndValue.length == 2) { - try { - value = Double.parseDouble(keyAndValue[1]); - key = keyAndValue[0]; - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (key == null) { - throw new DescriptorParseException("Line '" + line + "' contains " - + "an illegal key or value in list element '" + listElement - + "'."); - } - result.put(key, value); - } - return result; - } - - protected static String - parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( - String identityEd25519CryptoBlock) throws DescriptorParseException { - String identityEd25519CryptoBlockNoNewlines = - identityEd25519CryptoBlock.replaceAll("\n", ""); - String beginEd25519CertLine = "-----BEGIN ED25519 CERT-----", - endEd25519CertLine = "-----END ED25519 CERT-----"; - if (!identityEd25519CryptoBlockNoNewlines.startsWith( - beginEd25519CertLine)) { - throw new DescriptorParseException("Illegal start of " - + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock - + "'."); - } - if (!identityEd25519CryptoBlockNoNewlines.endsWith( - endEd25519CertLine)) { - throw new DescriptorParseException("Illegal end of " - + "identity-ed25519 crypto block '" + identityEd25519CryptoBlock - + "'."); - } - String identityEd25519Base64 = identityEd25519CryptoBlockNoNewlines. - substring(beginEd25519CertLine.length(), - identityEd25519CryptoBlock.length() - - endEd25519CertLine.length()).replaceAll("=", ""); - byte[] identityEd25519 = DatatypeConverter.parseBase64Binary( - identityEd25519Base64); - if (identityEd25519.length < 40) { - throw new DescriptorParseException("Invalid length of " - + "identity-ed25519 (in bytes): " + identityEd25519.length); - } else if (identityEd25519[0] != 0x01) { - throw new DescriptorParseException("Unknown version in " - + "identity-ed25519: " + identityEd25519[0]); - } else if (identityEd25519[1] != 0x04) { - throw new DescriptorParseException("Unknown cert type in " - + "identity-ed25519: " + identityEd25519[1]); - } else if (identityEd25519[6] != 0x01) { - throw new DescriptorParseException("Unknown certified key type in " - + "identity-ed25519: " + identityEd25519[1]); - } else if (identityEd25519[39] == 0x00) { - throw new DescriptorParseException("No extensions in " - + "identity-ed25519 (which would contain the encoded " - + "master-key-ed25519): " + identityEd25519[39]); - } else { - int extensionStart = 40; - for (int i = 0; i < (int) identityEd25519[39]; i++) { - if (identityEd25519.length < extensionStart + 4) { - throw new DescriptorParseException("Invalid extension with id " - + i + " in identity-ed25519."); - } - int extensionLength = identityEd25519[extensionStart]; - extensionLength <<= 8; - extensionLength += identityEd25519[extensionStart + 1]; - int extensionType = identityEd25519[extensionStart + 2]; - if (extensionLength == 32 && extensionType == 4) { - if (identityEd25519.length < extensionStart + 4 + 32) { - throw new DescriptorParseException("Invalid extension with " - + "id " + i + " in identity-ed25519."); - } - byte[] masterKeyEd25519 = new byte[32]; - System.arraycopy(identityEd25519, extensionStart + 4, - masterKeyEd25519, 0, masterKeyEd25519.length); - String masterKeyEd25519Base64 = DatatypeConverter. - printBase64Binary(masterKeyEd25519).replaceAll("=", ""); - String masterKeyEd25519Base64NoTrailingEqualSigns = - masterKeyEd25519Base64.replaceAll("=", ""); - return masterKeyEd25519Base64NoTrailingEqualSigns; - } - extensionStart += 4 + extensionLength; - } - } - throw new DescriptorParseException("Unable to locate " - + "master-key-ed25519 in identity-ed25519."); - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java b/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java deleted file mode 100644 index 1ff15cb..0000000 --- a/src/org/torproject/descriptor/impl/RelayDirectoryImpl.java +++ /dev/null @@ -1,547 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; -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<>(); - 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<>(Arrays.asList(( - "signed-directory,recommended-software," - + "directory-signature").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList( - "dir-signing-key,running-routers,router-status".split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("signed-directory"); - this.clearParsedKeywords(); - } - - 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 = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException 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<>(); - 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 = "", - 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("[ \t]+"); - String keyword = partsNoOpt[0]; - switch (keyword) { - case "signed-directory": - this.parseSignedDirectoryLine(line, lineNoOpt, partsNoOpt); - break; - case "published": - if (publishedLine != null) { - throw new DescriptorParseException("Keyword 'published' is " - + "contained more than once, but must be contained exactly " - + "once."); - } else { - publishedLine = line; - } - break; - case "dir-signing-key": - this.parseDirSigningKeyLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "dir-signing-key"; - break; - case "recommended-software": - this.parseRecommendedSoftwareLine(line, lineNoOpt, partsNoOpt); - break; - case "running-routers": - runningRoutersLine = line; - break; - case "router-status": - routerStatusLine = line; - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\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 = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in v1 directory."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - 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("[ \t]+"); - this.parsePublishedLine(publishedLine, publishedLineNoOpt, - publishedPartsNoOpt); - } - if (routerStatusLine != null) { - String routerStatusLineNoOpt = routerStatusLine.startsWith("opt ") ? - routerStatusLine.substring("opt ".length()) : routerStatusLine; - String[] routerStatusPartsNoOpt = - routerStatusLineNoOpt.split("[ \t]+"); - this.parseRouterStatusLine(routerStatusLine, routerStatusLineNoOpt, - routerStatusPartsNoOpt); - } else if (runningRoutersLine != null) { - String runningRoutersLineNoOpt = - runningRoutersLine.startsWith("opt ") ? - runningRoutersLine.substring("opt ".length()) : - runningRoutersLine; - String[] runningRoutersPartsNoOpt = - runningRoutersLineNoOpt.split("[ \t]+"); - 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 RelayServerDescriptorImpl(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 = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - String lineNoOpt = line.startsWith("opt ") ? - line.substring("opt ".length()) : line; - String[] partsNoOpt = lineNoOpt.split("[ \t]+"); - String keyword = partsNoOpt[0]; - switch (keyword) { - case "directory-signature": - this.parseDirectorySignatureLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "directory-signature"; - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\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 = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in v2 network status."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - 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)).append("\n"); - keyString = keyString.substring(64); - } - if (keyString.length() > 0) { - sb.append(keyString).append("\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<>(); - 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; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private String dirSigningKey; - @Override - public String getDirSigningKey() { - return this.dirSigningKey; - } - - private List<String> recommendedSoftware; - @Override - public List<String> getRecommendedSoftware() { - return this.recommendedSoftware == null ? null : - new ArrayList<>(this.recommendedSoftware); - } - - private String directorySignature; - @Override - public String getDirectorySignature() { - return this.directorySignature; - } - - private List<RouterStatusEntry> statusEntries = new ArrayList<>(); - @Override - public List<RouterStatusEntry> getRouterStatusEntries() { - return new ArrayList<>(this.statusEntries); - } - - private List<ServerDescriptor> serverDescriptors = new ArrayList<>(); - @Override - public List<ServerDescriptor> getServerDescriptors() { - return new ArrayList<>(this.serverDescriptors); - } - - private List<Exception> serverDescriptorParseExceptions = - new ArrayList<>(); - @Override - public List<Exception> getServerDescriptorParseExceptions() { - return new ArrayList<>(this.serverDescriptorParseExceptions); - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String directoryDigest; - @Override - public String getDirectoryDigest() { - return this.directoryDigest; - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java deleted file mode 100644 index 73d4dfa..0000000 --- a/src/org/torproject/descriptor/impl/RelayExtraInfoDescriptorImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExtraInfoDescriptor; -import org.torproject.descriptor.RelayExtraInfoDescriptor; - -public class RelayExtraInfoDescriptorImpl - extends ExtraInfoDescriptorImpl implements RelayExtraInfoDescriptor { - - protected static List<ExtraInfoDescriptor> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<ExtraInfoDescriptor> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, - "extra-info "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - ExtraInfoDescriptor parsedDescriptor = - new RelayExtraInfoDescriptorImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected RelayExtraInfoDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines); - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java deleted file mode 100644 index fe045c1..0000000 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java +++ /dev/null @@ -1,414 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.RelayNetworkStatusConsensus; - -/* Contains a network status consensus or microdesc consensus. */ -public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl - implements RelayNetworkStatusConsensus { - - protected static List<RelayNetworkStatusConsensus> parseConsensuses( - byte[] consensusesBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<RelayNetworkStatusConsensus> parsedConsensuses = - new ArrayList<>(); - List<byte[]> splitConsensusBytes = - DescriptorImpl.splitRawDescriptorBytes(consensusesBytes, - "network-status-version 3"); - for (byte[] consensusBytes : splitConsensusBytes) { - RelayNetworkStatusConsensus parsedConsensus = - new RelayNetworkStatusConsensusImpl(consensusBytes, - failUnrecognizedDescriptorLines); - parsedConsensuses.add(parsedConsensus); - } - return parsedConsensuses; - } - - protected RelayNetworkStatusConsensusImpl(byte[] consensusBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(consensusBytes, failUnrecognizedDescriptorLines, true, false); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( - "vote-status,consensus-method,valid-after,fresh-until," - + "valid-until,voting-delay,known-flags").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "client-versions,server-versions,params,directory-footer," - + "bandwidth-weights").split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("network-status-version"); - this.clearParsedKeywords(); - this.calculateDigest(); - } - - private void calculateDigest() throws DescriptorParseException { - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "network-status-version "; - String sigToken = "\ndirectory-signature "; - if (!ascii.contains(sigToken)) { - return; - } - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, - forDigest, 0, sig - start); - this.consensusDigest = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.consensusDigest == null) { - throw new DescriptorParseException("Could not calculate consensus " - + "digest."); - } - } - - protected void parseHeader(byte[] headerBytes) - throws DescriptorParseException { - Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "network-status-version": - this.parseNetworkStatusVersionLine(line, parts); - break; - case "vote-status": - this.parseVoteStatusLine(line, parts); - break; - case "consensus-method": - this.parseConsensusMethodLine(line, parts); - break; - case "valid-after": - this.parseValidAfterLine(line, parts); - break; - case "fresh-until": - this.parseFreshUntilLine(line, parts); - break; - case "valid-until": - this.parseValidUntilLine(line, parts); - break; - case "voting-delay": - this.parseVotingDelayLine(line, parts); - break; - case "client-versions": - this.parseClientVersionsLine(line, parts); - break; - case "server-versions": - this.parseServerVersionsLine(line, parts); - break; - case "package": - this.parsePackageLine(line, parts); - break; - case "known-flags": - this.parseKnownFlagsLine(line, parts); - break; - case "params": - this.parseParamsLine(line, parts); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in consensus."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - 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<>(); - } - this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); - } - } - - protected void parseFooter(byte[] footerBytes) - throws DescriptorParseException { - Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "directory-footer": - break; - case "bandwidth-weights": - this.parseBandwidthWeightsLine(line, parts); - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in consensus."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private void parseNetworkStatusVersionLine(String line, String[] parts) - throws DescriptorParseException { - 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) - throws DescriptorParseException { - if (parts.length != 2 || !parts[1].equals("consensus")) { - throw new DescriptorParseException("Line '" + line + "' indicates " - + "that this is not a consensus."); - } - } - - private void parseConsensusMethodLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in consensus."); - } - try { - this.consensusMethod = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal consensus method " - + "number in line '" + line + "'."); - } - if (this.consensusMethod < 1) { - throw new DescriptorParseException("Illegal consensus method " - + "number in line '" + line + "'."); - } - } - - private void parseValidAfterLine(String line, String[] parts) - throws DescriptorParseException { - this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseFreshUntilLine(String line, String[] parts) - throws DescriptorParseException { - this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseValidUntilLine(String line, String[] parts) - throws DescriptorParseException { - this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseVotingDelayLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + 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 - + "'."); - } - } - - private void parseClientVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedClientVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parseServerVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedServerVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parsePackageLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 5) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + line + "'."); - } - if (this.packageLines == null) { - this.packageLines = new ArrayList<>(); - } - this.packageLines.add(line.substring("package ".length())); - } - - private void parseKnownFlagsLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("No known flags in line '" + line - + "'."); - } - String[] knownFlags = new String[parts.length - 1]; - for (int i = 1; i < parts.length; i++) { - knownFlags[i - 1] = parts[i]; - } - this.knownFlags = knownFlags; - } - - private void parseParamsLine(String line, String[] parts) - throws DescriptorParseException { - this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, - parts, 1, "="); - } - - private void parseBandwidthWeightsLine(String line, String[] parts) - throws DescriptorParseException { - this.bandwidthWeights = ParseHelper.parseKeyValueIntegerPairs(line, - parts, 1, "="); - } - - private String consensusDigest; - @Override - public String getConsensusDigest() { - return this.consensusDigest; - } - - private int networkStatusVersion; - @Override - public int getNetworkStatusVersion() { - return this.networkStatusVersion; - } - - private String consensusFlavor; - @Override - public String getConsensusFlavor() { - return this.consensusFlavor; - } - - private int consensusMethod; - @Override - public int getConsensusMethod() { - return this.consensusMethod; - } - - private long validAfterMillis; - @Override - public long getValidAfterMillis() { - return this.validAfterMillis; - } - - private long freshUntilMillis; - @Override - public long getFreshUntilMillis() { - return this.freshUntilMillis; - } - - private long validUntilMillis; - @Override - public long getValidUntilMillis() { - return this.validUntilMillis; - } - - private long voteSeconds; - @Override - public long getVoteSeconds() { - return this.voteSeconds; - } - - private long distSeconds; - @Override - public long getDistSeconds() { - return this.distSeconds; - } - - private String[] recommendedClientVersions; - @Override - public List<String> getRecommendedClientVersions() { - return this.recommendedClientVersions == null ? null : - Arrays.asList(this.recommendedClientVersions); - } - - private String[] recommendedServerVersions; - @Override - public List<String> getRecommendedServerVersions() { - return this.recommendedServerVersions == null ? null : - Arrays.asList(this.recommendedServerVersions); - } - - private List<String> packageLines; - @Override - public List<String> getPackageLines() { - return this.packageLines == null ? null - : new ArrayList<>(this.packageLines); - } - - private String[] knownFlags; - @Override - public SortedSet<String> getKnownFlags() { - return new TreeSet<>(Arrays.asList(this.knownFlags)); - } - - private SortedMap<String, Integer> consensusParams; - @Override - public SortedMap<String, Integer> getConsensusParams() { - return this.consensusParams == null ? null: - new TreeMap<>(this.consensusParams); - } - - private SortedMap<String, Integer> bandwidthWeights; - @Override - public SortedMap<String, Integer> getBandwidthWeights() { - return this.bandwidthWeights == null ? null : - new TreeMap<>(this.bandwidthWeights); - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java deleted file mode 100644 index a5469db..0000000 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusImpl.java +++ /dev/null @@ -1,384 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 java.util.SortedSet; -import java.util.TreeSet; - -import javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.RelayNetworkStatus; - -/* TODO Write unit tests. */ - -public class RelayNetworkStatusImpl extends NetworkStatusImpl - implements RelayNetworkStatus { - - protected static List<RelayNetworkStatus> parseStatuses( - byte[] statusesBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<RelayNetworkStatus> parsedStatuses = new ArrayList<>(); - List<byte[]> splitStatusBytes = - DescriptorImpl.splitRawDescriptorBytes(statusesBytes, - "network-status-version 2"); - for (byte[] statusBytes : splitStatusBytes) { - RelayNetworkStatus parsedStatus = new RelayNetworkStatusImpl( - statusBytes, failUnrecognizedDescriptorLines); - parsedStatuses.add(parsedStatus); - } - return parsedStatuses; - } - - protected RelayNetworkStatusImpl(byte[] statusBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(statusBytes, failUnrecognizedDescriptorLines, false, true); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( - "network-status-version,dir-source,fingerprint,contact," - + "dir-signing-key,published").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList( - "dir-options,client-versions,server-versions".split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("network-status-version"); - this.clearParsedKeywords(); - this.calculateDigest(); - } - - private void calculateDigest() throws DescriptorParseException { - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "network-status-version "; - 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.statusDigest = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.statusDigest == null) { - throw new DescriptorParseException("Could not calculate status " - + "digest."); - } - } - - protected void parseHeader(byte[] headerBytes) - throws DescriptorParseException { - Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); - String nextCrypto = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - if (line.isEmpty()) { - continue; - } - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "network-status-version": - this.parseNetworkStatusVersionLine(line, parts); - break; - case "dir-source": - this.parseDirSourceLine(line, parts); - break; - case "fingerprint": - this.parseFingerprintLine(line, parts); - break; - case "contact": - this.parseContactLine(line, parts); - break; - case "dir-signing-key": - this.parseDirSigningKeyLine(line, parts); - nextCrypto = "dir-signing-key"; - break; - case "client-versions": - this.parseClientVersionsLine(line, parts); - break; - case "server-versions": - this.parseServerVersionsLine(line, parts); - break; - case "published": - this.parsePublishedLine(line, parts); - break; - case "dir-options": - this.parseDirOptionsLine(line, parts); - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\n"); - String cryptoString = crypto.toString(); - crypto = null; - if (nextCrypto.equals("dir-signing-key")) { - this.dirSigningKey = cryptoString; - } else { - throw new DescriptorParseException("Unrecognized crypto " - + "block in v2 network status."); - } - nextCrypto = ""; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in v2 network status."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - protected void parseFooter(byte[] footerBytes) - throws DescriptorParseException { - throw new DescriptorParseException("No directory footer expected in " - + "v2 network status."); - } - - protected void parseDirectorySignature(byte[] directorySignatureBytes) - throws DescriptorParseException { - Scanner s = new Scanner(new String(directorySignatureBytes)). - useDelimiter("\n"); - String nextCrypto = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "directory-signature": - this.parseDirectorySignatureLine(line, parts); - nextCrypto = "directory-signature"; - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\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 = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" + line - + "' in v2 network status."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private void parseNetworkStatusVersionLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("network-status-version 2")) { - throw new DescriptorParseException("Illegal network status version " - + "number in line '" + line + "'."); - } - this.networkStatusVersion = 2; - } - - private void parseDirSourceLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 4) { - throw new DescriptorParseException("Illegal line '" + line - + "' in v2 network status."); - } - if (parts[1].length() < 1) { - throw new DescriptorParseException("Illegal hostname in '" + line - + "'."); - } - this.address = ParseHelper.parseIpv4Address(line, parts[2]); - this.dirPort = ParseHelper.parsePort(line, parts[3]); - } - - - private void parseFingerprintLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in v2 network status."); - } - this.fingerprint = ParseHelper.parseTwentyByteHexString(line, - parts[1]); - } - - private void parseContactLine(String line, String[] parts) - throws DescriptorParseException { - if (line.length() > "contact ".length()) { - this.contactLine = line.substring("contact ".length()); - } else { - this.contactLine = ""; - } - } - - private void parseDirSigningKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-signing-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseClientVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedClientVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parseServerVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedServerVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parsePublishedLine(String line, String[] parts) - throws DescriptorParseException { - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseDirOptionsLine(String line, String[] parts) - throws DescriptorParseException { - String[] dirOptions = new String[parts.length - 1]; - for (int i = 1; i < parts.length; i++) { - dirOptions[i - 1] = parts[i]; - } - this.dirOptions = dirOptions; - } - - private void parseDirectorySignatureLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.nickname = ParseHelper.parseNickname(line, parts[1]); - } - - private String statusDigest; - @Override - public String getStatusDigest() { - return this.statusDigest; - } - - private int networkStatusVersion; - @Override - public int getNetworkStatusVersion() { - return this.networkStatusVersion; - } - - private String hostname; - @Override - public String getHostname() { - return this.hostname; - } - - private String address; - @Override - public String getAddress() { - return this.address; - } - - private int dirPort; - @Override - public int getDirport() { - return this.dirPort; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private String contactLine; - @Override - public String getContactLine() { - return this.contactLine; - } - - private String dirSigningKey; - @Override - public String getDirSigningKey() { - return this.dirSigningKey; - } - - private String[] recommendedClientVersions; - @Override - public List<String> getRecommendedClientVersions() { - return this.recommendedClientVersions == null ? null : - Arrays.asList(this.recommendedClientVersions); - } - - private String[] recommendedServerVersions; - @Override - public List<String> getRecommendedServerVersions() { - return this.recommendedServerVersions == null ? null : - Arrays.asList(this.recommendedServerVersions); - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private String[] dirOptions; - @Override - public SortedSet<String> getDirOptions() { - return new TreeSet<>(Arrays.asList(this.dirOptions)); - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String directorySignature; - @Override - public String getDirectorySignature() { - return this.directorySignature; - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java deleted file mode 100644 index 384ad1f..0000000 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java +++ /dev/null @@ -1,761 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.DirectorySignature; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.Set; -import java.util.SortedMap; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import org.torproject.descriptor.RelayNetworkStatusVote; - -/* Contains a network status vote. */ -public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl - implements RelayNetworkStatusVote { - - protected static List<RelayNetworkStatusVote> parseVotes( - byte[] votesBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<RelayNetworkStatusVote> parsedVotes = new ArrayList<>(); - List<byte[]> splitVotesBytes = - DescriptorImpl.splitRawDescriptorBytes(votesBytes, - "network-status-version 3"); - for (byte[] voteBytes : splitVotesBytes) { - RelayNetworkStatusVote parsedVote = - new RelayNetworkStatusVoteImpl(voteBytes, - failUnrecognizedDescriptorLines); - parsedVotes.add(parsedVote); - } - return parsedVotes; - } - - protected RelayNetworkStatusVoteImpl(byte[] voteBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(voteBytes, failUnrecognizedDescriptorLines, false, false); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList(( - "vote-status,published,valid-after,fresh-until," - + "valid-until,voting-delay,known-flags,dir-source," - + "dir-key-certificate-version,fingerprint,dir-key-published," - + "dir-key-expires,dir-identity-key,dir-signing-key," - + "dir-key-certification").split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "consensus-methods,client-versions,server-versions," - + "flag-thresholds,params,contact," - + "legacy-key,dir-key-crosscert,dir-address,directory-footer"). - split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - Set<String> atLeastOnceKeywords = new HashSet<>(Arrays.asList( - "directory-signature")); - this.checkAtLeastOnceKeywords(atLeastOnceKeywords); - this.checkFirstKeyword("network-status-version"); - this.clearParsedKeywords(); - } - - protected void parseHeader(byte[] headerBytes) - throws DescriptorParseException { - /* Initialize flag-thresholds values here for the case that the vote - * doesn't contain those values. Initializing them in the constructor - * or when declaring variables wouldn't work, because those parts are - * evaluated later and would overwrite everything we parse here. */ - this.stableUptime = -1L; - this.stableMtbf = -1L; - this.fastBandwidth = -1L; - this.guardWfu = -1.0; - this.guardTk = -1L; - this.guardBandwidthIncludingExits = -1L; - this.guardBandwidthExcludingExits = -1L; - this.enoughMtbfInfo = -1; - this.ignoringAdvertisedBws = -1; - - Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n"); - String nextCrypto = ""; - StringBuilder crypto = null; - while (s.hasNext()) { - String line = s.next(); - String[] parts = line.split("[ \t]+"); - String keyword = parts[0]; - switch (keyword) { - case "network-status-version": - this.parseNetworkStatusVersionLine(line, parts); - break; - case "vote-status": - this.parseVoteStatusLine(line, parts); - break; - case "consensus-methods": - this.parseConsensusMethodsLine(line, parts); - break; - case "published": - this.parsePublishedLine(line, parts); - break; - case "valid-after": - this.parseValidAfterLine(line, parts); - break; - case "fresh-until": - this.parseFreshUntilLine(line, parts); - break; - case "valid-until": - this.parseValidUntilLine(line, parts); - break; - case "voting-delay": - this.parseVotingDelayLine(line, parts); - break; - case "client-versions": - this.parseClientVersionsLine(line, parts); - break; - case "server-versions": - this.parseServerVersionsLine(line, parts); - break; - case "package": - this.parsePackageLine(line, parts); - break; - case "known-flags": - this.parseKnownFlagsLine(line, parts); - break; - case "flag-thresholds": - this.parseFlagThresholdsLine(line, parts); - break; - case "params": - this.parseParamsLine(line, parts); - break; - case "dir-source": - this.parseDirSourceLine(line, parts); - break; - case "contact": - this.parseContactLine(line, parts); - break; - case "dir-key-certificate-version": - this.parseDirKeyCertificateVersionLine(line, parts); - break; - case "dir-address": - this.parseDirAddressLine(line, parts); - break; - case "fingerprint": - this.parseFingerprintLine(line, parts); - break; - case "legacy-dir-key": - this.parseLegacyDirKeyLine(line, parts); - break; - case "dir-key-published": - this.parseDirKeyPublished(line, parts); - break; - case "dir-key-expires": - this.parseDirKeyExpiresLine(line, parts); - break; - case "dir-identity-key": - this.parseDirIdentityKeyLine(line, parts); - nextCrypto = "dir-identity-key"; - break; - case "dir-signing-key": - this.parseDirSigningKeyLine(line, parts); - nextCrypto = "dir-signing-key"; - break; - case "dir-key-crosscert": - this.parseDirKeyCrosscertLine(line, parts); - nextCrypto = "dir-key-crosscert"; - break; - case "dir-key-certification": - this.parseDirKeyCertificationLine(line, parts); - nextCrypto = "dir-key-certification"; - break; - case "-----BEGIN": - crypto = new StringBuilder(); - crypto.append(line).append("\n"); - break; - case "-----END": - crypto.append(line).append("\n"); - String cryptoString = crypto.toString(); - crypto = null; - switch (nextCrypto) { - case "dir-identity-key": - this.dirIdentityKey = cryptoString; - break; - case "dir-signing-key": - this.dirSigningKey = cryptoString; - break; - case "dir-key-crosscert": - this.dirKeyCrosscert = cryptoString; - break; - case "dir-key-certification": - this.dirKeyCertification = cryptoString; - break; - default: - throw new DescriptorParseException("Unrecognized crypto " - + "block in vote."); - } - nextCrypto = ""; - break; - default: - if (crypto != null) { - crypto.append(line).append("\n"); - } else { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in vote."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - private void parseNetworkStatusVersionLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("network-status-version 3")) { - throw new DescriptorParseException("Illegal network status version " - + "number in line '" + line + "'."); - } - this.networkStatusVersion = 3; - } - - private void parseVoteStatusLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2 || !parts[1].equals("vote")) { - throw new DescriptorParseException("Line '" + line + "' indicates " - + "that this is not a vote."); - } - } - - private void parseConsensusMethodsLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in vote."); - } - Integer[] consensusMethods = new Integer[parts.length - 1]; - for (int i = 1; i < parts.length; i++) { - int consensusMethod = -1; - try { - consensusMethod = Integer.parseInt(parts[i]); - } catch (NumberFormatException e) { - /* We'll notice below that consensusMethod is still -1. */ - } - if (consensusMethod < 1) { - throw new DescriptorParseException("Illegal consensus method " - + "number in line '" + line + "'."); - } - consensusMethods[i - 1] = consensusMethod; - } - this.consensusMethods = consensusMethods; - } - - private void parsePublishedLine(String line, String[] parts) - throws DescriptorParseException { - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseValidAfterLine(String line, String[] parts) - throws DescriptorParseException { - this.validAfterMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseFreshUntilLine(String line, String[] parts) - throws DescriptorParseException { - this.freshUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseValidUntilLine(String line, String[] parts) - throws DescriptorParseException { - this.validUntilMillis = ParseHelper.parseTimestampAtIndex(line, parts, - 1, 2); - } - - private void parseVotingDelayLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 3) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + 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 - + "'."); - } - } - - private void parseClientVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedClientVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parseServerVersionsLine(String line, String[] parts) - throws DescriptorParseException { - this.recommendedServerVersions = this.parseClientOrServerVersions( - line, parts); - } - - private void parsePackageLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 5) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + line + "'."); - } - if (this.packageLines == null) { - this.packageLines = new ArrayList<>(); - } - this.packageLines.add(line.substring("package ".length())); - } - - private void parseKnownFlagsLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("No known flags in line '" + line - + "'."); - } - String[] knownFlags = new String[parts.length - 1]; - for (int i = 1; i < parts.length; i++) { - knownFlags[i - 1] = parts[i]; - } - this.knownFlags = knownFlags; - } - - private void parseFlagThresholdsLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length < 2) { - throw new DescriptorParseException("No flag thresholds in line '" - + line + "'."); - } - SortedMap<String, String> flagThresholds = - ParseHelper.parseKeyValueStringPairs(line, parts, 1, "="); - try { - for (Map.Entry<String, String> e : flagThresholds.entrySet()) { - switch (e.getKey()) { - case "stable-uptime": - this.stableUptime = Long.parseLong(e.getValue()); - break; - case "stable-mtbf": - this.stableMtbf = Long.parseLong(e.getValue()); - break; - case "fast-speed": - this.fastBandwidth = Long.parseLong(e.getValue()); - break; - case "guard-wfu": - this.guardWfu = Double.parseDouble(e.getValue(). - replaceAll("%", "")); - break; - case "guard-tk": - this.guardTk = Long.parseLong(e.getValue()); - break; - case "guard-bw-inc-exits": - this.guardBandwidthIncludingExits = - Long.parseLong(e.getValue()); - break; - case "guard-bw-exc-exits": - this.guardBandwidthExcludingExits = - Long.parseLong(e.getValue()); - break; - case "enough-mtbf": - this.enoughMtbfInfo = Integer.parseInt(e.getValue()); - break; - case "ignoring-advertised-bws": - this.ignoringAdvertisedBws = Integer.parseInt(e.getValue()); - break; - default: - // empty - } - } - } catch (NumberFormatException ex) { - throw new DescriptorParseException("Illegal value in line '" - + line + "'."); - } - } - - private void parseParamsLine(String line, String[] parts) - throws DescriptorParseException { - this.consensusParams = ParseHelper.parseKeyValueIntegerPairs(line, - parts, 1, "="); - } - - private void parseDirSourceLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 7) { - throw new DescriptorParseException("Illegal line '" + line - + "' in vote."); - } - this.nickname = ParseHelper.parseNickname(line, parts[1]); - this.identity = ParseHelper.parseTwentyByteHexString(line, parts[2]); - if (parts[3].length() < 1) { - throw new DescriptorParseException("Illegal hostname in '" + line - + "'."); - } - this.hostname = parts[3]; - this.address = ParseHelper.parseIpv4Address(line, parts[4]); - this.dirPort = ParseHelper.parsePort(line, parts[5]); - this.orPort = ParseHelper.parsePort(line, parts[6]); - } - - private void parseContactLine(String line, String[] parts) - throws DescriptorParseException { - if (line.length() > "contact ".length()) { - this.contactLine = line.substring("contact ".length()); - } else { - this.contactLine = ""; - } - } - - private void parseDirKeyCertificateVersionLine(String line, - String[] parts) throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in vote."); - } - try { - this.dirKeyCertificateVersion = Integer.parseInt(parts[1]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal dir key certificate " - + "version in line '" + line + "'."); - } - if (this.dirKeyCertificateVersion < 1) { - throw new DescriptorParseException("Illegal dir key certificate " - + "version in line '" + line + "'."); - } - } - - private void parseDirAddressLine(String line, String[] parts) { - /* Nothing new to learn here. Also, this line hasn't been observed - * "in the wild" yet. Maybe it's just an urban legend. */ - } - - private void parseFingerprintLine(String line, String[] parts) - throws DescriptorParseException { - /* Nothing new to learn here. We already know the fingerprint from - * the dir-source line. But we should at least check that there's a - * valid fingerprint in this line. */ - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line - + "' in vote."); - } - ParseHelper.parseTwentyByteHexString(line, parts[1]); - } - - private void parseLegacyDirKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (parts.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.legacyDirKey = ParseHelper.parseTwentyByteHexString(line, parts[1]); - } - - private void parseDirKeyPublished(String line, String[] parts) - throws DescriptorParseException { - this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - } - - private void parseDirKeyExpiresLine(String line, String[] parts) - throws DescriptorParseException { - this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line, - parts, 1, 2); - } - - private void parseDirIdentityKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-identity-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirSigningKeyLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-signing-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirKeyCrosscertLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-key-crosscert")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseDirKeyCertificationLine(String line, String[] parts) - throws DescriptorParseException { - if (!line.equals("dir-key-certification")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - protected void parseFooter(byte[] footerBytes) - throws DescriptorParseException { - Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n"); - while (s.hasNext()) { - String line = s.next(); - if (!line.equals("directory-footer")) { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in vote."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String identity; - @Override - public String getIdentity() { - return this.identity; - } - - private String hostname; - @Override - public String getHostname() { - return this.hostname; - } - - private String address; - @Override - public String getAddress() { - return this.address; - } - - private int dirPort; - @Override - public int getDirport() { - return this.dirPort; - } - - private int orPort; - @Override - public int getOrport() { - return this.orPort; - } - - private String contactLine; - @Override - public String getContactLine() { - return this.contactLine; - } - - private int dirKeyCertificateVersion; - @Override - public int getDirKeyCertificateVersion() { - return this.dirKeyCertificateVersion; - } - - private String legacyDirKey; - @Override - public String getLegacyDirKey() { - return this.legacyDirKey; - } - - private long dirKeyPublishedMillis; - @Override - public long getDirKeyPublishedMillis() { - return this.dirKeyPublishedMillis; - } - - private long dirKeyExpiresMillis; - @Override - public long getDirKeyExpiresMillis() { - return this.dirKeyExpiresMillis; - } - - private String dirIdentityKey; - @Override - public String getDirIdentityKey() { - return this.dirIdentityKey; - } - - private String dirSigningKey; - @Override - public String getDirSigningKey() { - return this.dirSigningKey; - } - - private String dirKeyCrosscert; - @Override - public String getDirKeyCrosscert() { - return this.dirKeyCrosscert; - } - - private String dirKeyCertification; - @Override - public String getDirKeyCertification() { - return this.dirKeyCertification; - } - - @Override - public String getSigningKeyDigest() { - String signingKeyDigest = null; - if (this.signatures != null && !this.signatures.isEmpty()) { - for (DirectorySignature signature : this.signatures) { - if (DirectorySignatureImpl.DEFAULT_ALGORITHM.equals( - signature.getAlgorithm())) { - signingKeyDigest = signature.getSigningKeyDigest(); - break; - } - } - } - return signingKeyDigest; - } - - private int networkStatusVersion; - @Override - public int getNetworkStatusVersion() { - return this.networkStatusVersion; - } - - private Integer[] consensusMethods; - @Override - public List<Integer> getConsensusMethods() { - return this.consensusMethods == null ? null : - Arrays.asList(this.consensusMethods); - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private long validAfterMillis; - @Override - public long getValidAfterMillis() { - return this.validAfterMillis; - } - - private long freshUntilMillis; - @Override - public long getFreshUntilMillis() { - return this.freshUntilMillis; - } - - private long validUntilMillis; - @Override - public long getValidUntilMillis() { - return this.validUntilMillis; - } - - private long voteSeconds; - @Override - public long getVoteSeconds() { - return this.voteSeconds; - } - - private long distSeconds; - @Override - public long getDistSeconds() { - return this.distSeconds; - } - - private String[] recommendedClientVersions; - @Override - public List<String> getRecommendedClientVersions() { - return this.recommendedClientVersions == null ? null : - Arrays.asList(this.recommendedClientVersions); - } - - private String[] recommendedServerVersions; - @Override - public List<String> getRecommendedServerVersions() { - return this.recommendedServerVersions == null ? null : - Arrays.asList(this.recommendedServerVersions); - } - - private List<String> packageLines; - @Override - public List<String> getPackageLines() { - return this.packageLines == null ? null - : new ArrayList<>(this.packageLines); - } - - private String[] knownFlags; - @Override - public SortedSet<String> getKnownFlags() { - return new TreeSet<>(Arrays.asList(this.knownFlags)); - } - - private long stableUptime; - @Override - public long getStableUptime() { - return this.stableUptime; - } - - private long stableMtbf; - @Override - public long getStableMtbf() { - return this.stableMtbf; - } - - private long fastBandwidth; - @Override - public long getFastBandwidth() { - return this.fastBandwidth; - } - - private double guardWfu; - @Override - public double getGuardWfu() { - return this.guardWfu; - } - - private long guardTk; - @Override - public long getGuardTk() { - return this.guardTk; - } - - private long guardBandwidthIncludingExits; - @Override - public long getGuardBandwidthIncludingExits() { - return this.guardBandwidthIncludingExits; - } - - private long guardBandwidthExcludingExits; - @Override - public long getGuardBandwidthExcludingExits() { - return this.guardBandwidthExcludingExits; - } - - private int enoughMtbfInfo; - @Override - public int getEnoughMtbfInfo() { - return this.enoughMtbfInfo; - } - - private int ignoringAdvertisedBws; - @Override - public int getIgnoringAdvertisedBws() { - return this.ignoringAdvertisedBws; - } - - private SortedMap<String, Integer> consensusParams; - @Override - public SortedMap<String, Integer> getConsensusParams() { - return this.consensusParams == null ? null: - new TreeMap<>(this.consensusParams); - } -} - diff --git a/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java deleted file mode 100644 index 4957072..0000000 --- a/src/org/torproject/descriptor/impl/RelayServerDescriptorImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.RelayServerDescriptor; -import org.torproject.descriptor.ServerDescriptor; - -public class RelayServerDescriptorImpl extends ServerDescriptorImpl - implements RelayServerDescriptor { - - protected static List<ServerDescriptor> parseDescriptors( - byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - List<ServerDescriptor> parsedDescriptors = new ArrayList<>(); - List<byte[]> splitDescriptorsBytes = - DescriptorImpl.splitRawDescriptorBytes(descriptorsBytes, - "router "); - for (byte[] descriptorBytes : splitDescriptorsBytes) { - ServerDescriptor parsedDescriptor = - new RelayServerDescriptorImpl(descriptorBytes, - failUnrecognizedDescriptorLines); - parsedDescriptors.add(parsedDescriptor); - } - return parsedDescriptors; - } - - protected RelayServerDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines); - } -} - diff --git a/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java b/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java deleted file mode 100644 index a359c50..0000000 --- a/src/org/torproject/descriptor/impl/RouterStatusEntryImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2012--2015 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; - @Override - public String getNickname() { - return this.nickname; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private boolean isLive; - @Override - public boolean isLive() { - return this.isLive; - } - - private boolean isVerified; - @Override - public boolean isVerified() { - return this.isVerified; - } -} - diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java deleted file mode 100644 index 1805dca..0000000 --- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java +++ /dev/null @@ -1,985 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -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 javax.xml.bind.DatatypeConverter; - -import org.torproject.descriptor.BandwidthHistory; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ServerDescriptor; - -/* Contains a server descriptor. */ -public abstract class ServerDescriptorImpl extends DescriptorImpl - implements ServerDescriptor { - - protected ServerDescriptorImpl(byte[] descriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(descriptorBytes, failUnrecognizedDescriptorLines, false); - this.parseDescriptorBytes(); - this.calculateDigest(); - this.calculateDigestSha256(); - Set<String> exactlyOnceKeywords = new HashSet<>(Arrays.asList( - "router,bandwidth,published".split(","))); - this.checkExactlyOnceKeywords(exactlyOnceKeywords); - Set<String> atMostOnceKeywords = new HashSet<>(Arrays.asList(( - "identity-ed25519,master-key-ed25519,platform,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," - + "signing-key,ipv6-policy,ntor-onion-key,onion-key-crosscert," - + "ntor-onion-key-crosscert,tunnelled-dir-server," - + "router-sig-ed25519,router-signature,router-digest-sha256," - + "router-digest").split(","))); - this.checkAtMostOnceKeywords(atMostOnceKeywords); - this.checkFirstKeyword("router"); - if (this.getKeywordCount("accept") == 0 && - this.getKeywordCount("reject") == 0) { - throw new DescriptorParseException("Either keyword 'accept' or " - + "'reject' must be contained at least once."); - } - this.clearParsedKeywords(); - return; - } - - private void parseDescriptorBytes() throws DescriptorParseException { - Scanner s = new Scanner(new String(this.rawDescriptorBytes)). - useDelimiter("\n"); - String nextCrypto = ""; - List<String> cryptoLines = null; - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("@")) { - continue; - } - String lineNoOpt = line.startsWith("opt ") ? - line.substring("opt ".length()) : line; - String[] partsNoOpt = lineNoOpt.split("[ \t]+"); - String keyword = partsNoOpt[0]; - switch (keyword) { - case "router": - this.parseRouterLine(line, lineNoOpt, partsNoOpt); - break; - case "or-address": - this.parseOrAddressLine(line, lineNoOpt, partsNoOpt); - break; - case "bandwidth": - this.parseBandwidthLine(line, lineNoOpt, partsNoOpt); - break; - case "platform": - this.parsePlatformLine(line, lineNoOpt, partsNoOpt); - break; - case "published": - this.parsePublishedLine(line, lineNoOpt, partsNoOpt); - break; - case "fingerprint": - this.parseFingerprintLine(line, lineNoOpt, partsNoOpt); - break; - case "hibernating": - this.parseHibernatingLine(line, lineNoOpt, partsNoOpt); - break; - case "uptime": - this.parseUptimeLine(line, lineNoOpt, partsNoOpt); - break; - case "onion-key": - this.parseOnionKeyLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "onion-key"; - break; - case "signing-key": - this.parseSigningKeyLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "signing-key"; - break; - case "accept": - this.parseAcceptLine(line, lineNoOpt, partsNoOpt); - break; - case "reject": - this.parseRejectLine(line, lineNoOpt, partsNoOpt); - break; - case "router-signature": - this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt); - nextCrypto = "router-signature"; - break; - case "contact": - this.parseContactLine(line, lineNoOpt, partsNoOpt); - break; - case "family": - this.parseFamilyLine(line, lineNoOpt, partsNoOpt); - break; - case "read-history": - this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "write-history": - this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt); - break; - case "eventdns": - this.parseEventdnsLine(line, lineNoOpt, partsNoOpt); - break; - case "caches-extra-info": - this.parseCachesExtraInfoLine(line, lineNoOpt, partsNoOpt); - break; - case "extra-info-digest": - this.parseExtraInfoDigestLine(line, lineNoOpt, partsNoOpt); - break; - case "hidden-service-dir": - this.parseHiddenServiceDirLine(line, lineNoOpt, partsNoOpt); - break; - case "protocols": - this.parseProtocolsLine(line, lineNoOpt, partsNoOpt); - break; - case "allow-single-hop-exits": - this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt); - break; - case "dircacheport": - this.parseDircacheportLine(line, lineNoOpt, partsNoOpt); - break; - case "router-digest": - this.parseRouterDigestLine(line, lineNoOpt, partsNoOpt); - break; - case "router-digest-sha256": - this.parseRouterDigestSha256Line(line, lineNoOpt, partsNoOpt); - break; - case "ipv6-policy": - this.parseIpv6PolicyLine(line, lineNoOpt, partsNoOpt); - break; - case "ntor-onion-key": - this.parseNtorOnionKeyLine(line, lineNoOpt, partsNoOpt); - break; - case "identity-ed25519": - this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt); - nextCrypto = "identity-ed25519"; - break; - case "master-key-ed25519": - this.parseMasterKeyEd25519Line(line, lineNoOpt, partsNoOpt); - break; - case "router-sig-ed25519": - this.parseRouterSigEd25519Line(line, lineNoOpt, partsNoOpt); - break; - case "onion-key-crosscert": - this.parseOnionKeyCrosscert(line, lineNoOpt, partsNoOpt); - nextCrypto = "onion-key-crosscert"; - break; - case "ntor-onion-key-crosscert": - this.parseNtorOnionKeyCrosscert(line, lineNoOpt, partsNoOpt); - nextCrypto = "ntor-onion-key-crosscert"; - break; - case "tunnelled-dir-server": - this.parseTunnelledDirServerLine(line, lineNoOpt, partsNoOpt); - break; - case "-----BEGIN": - cryptoLines = new ArrayList<>(); - cryptoLines.add(line); - break; - case "-----END": - cryptoLines.add(line); - StringBuilder sb = new StringBuilder(); - for (String cryptoLine : cryptoLines) { - sb.append("\n").append(cryptoLine); - } - String cryptoString = sb.toString().substring(1); - switch (nextCrypto) { - case "onion-key": - this.onionKey = cryptoString; - break; - case "signing-key": - this.signingKey = cryptoString; - break; - case "router-signature": - this.routerSignature = cryptoString; - break; - case "identity-ed25519": - this.identityEd25519 = cryptoString; - this.parseIdentityEd25519CryptoBlock(cryptoString); - break; - case "onion-key-crosscert": - this.onionKeyCrosscert = cryptoString; - break; - case "ntor-onion-key-crosscert": - this.ntorOnionKeyCrosscert = cryptoString; - break; - default: - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized crypto " - + "block '" + cryptoString + "' in server descriptor."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.addAll(cryptoLines); - } - } - cryptoLines = null; - nextCrypto = ""; - break; - default: - if (cryptoLines != null) { - cryptoLines.add(line); - } else { - ParseHelper.parseKeyword(line, partsNoOpt[0]); - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in server descriptor."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - this.unrecognizedLines.add(line); - } - } - } - } - } - - private void parseRouterLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 6) { - throw new DescriptorParseException("Illegal line '" + line - + "' in server descriptor."); - } - this.nickname = ParseHelper.parseNickname(line, partsNoOpt[1]); - this.address = ParseHelper.parseIpv4Address(line, partsNoOpt[2]); - this.orPort = ParseHelper.parsePort(line, partsNoOpt[3]); - this.socksPort = ParseHelper.parsePort(line, partsNoOpt[4]); - this.dirPort = ParseHelper.parsePort(line, partsNoOpt[5]); - } - - private void parseOrAddressLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + line + "'."); - } - /* TODO Add more checks. */ - /* TODO Add tests. */ - this.orAddresses.add(partsNoOpt[1]); - } - - private void parseBandwidthLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length < 3 || partsNoOpt.length > 4) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + line + "'."); - } - boolean isValid = false; - try { - this.bandwidthRate = Integer.parseInt(partsNoOpt[1]); - this.bandwidthBurst = Integer.parseInt(partsNoOpt[2]); - 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. */ - } - if (!isValid) { - throw new DescriptorParseException("Illegal values in line '" + line - + "'."); - } - } - - private void parsePlatformLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (lineNoOpt.length() > "platform ".length()) { - this.platform = lineNoOpt.substring("platform ".length()); - } else { - this.platform = ""; - } - } - - private void parsePublishedLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.publishedMillis = ParseHelper.parseTimestampAtIndex(line, - partsNoOpt, 1, 2); - } - - private void parseFingerprintLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (lineNoOpt.length() != "fingerprint".length() + 5 * 10) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.fingerprint = ParseHelper.parseTwentyByteHexString(line, - lineNoOpt.substring("fingerprint ".length()).replaceAll(" ", "")); - } - - private void parseHibernatingLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.hibernating = ParseHelper.parseBoolean(partsNoOpt[1], line); - } - - private void parseUptimeLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Wrong number of values in line " - + "'" + line + "'."); - } - boolean isValid = false; - try { - this.uptime = Long.parseLong(partsNoOpt[1]); - isValid = true; - } catch (NumberFormatException e) { - /* Handle below. */ - } - if (!isValid) { - throw new DescriptorParseException("Illegal value in line '" + line - + "'."); - } - } - - private void parseOnionKeyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("onion-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseSigningKeyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("signing-key")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseAcceptLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt); - } - - private void parseRejectLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.parseExitPolicyLine(line, lineNoOpt, partsNoOpt); - } - - private void parseExitPolicyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - ParseHelper.parseExitPattern(line, partsNoOpt[1]); - this.exitPolicyLines.add(lineNoOpt); - } - - private void parseRouterSignatureLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("router-signature")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseContactLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (lineNoOpt.length() > "contact ".length()) { - this.contact = lineNoOpt.substring("contact ".length()); - } else { - this.contact = ""; - } - } - - private void parseFamilyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - String[] familyEntries = new String[partsNoOpt.length - 1]; - for (int i = 1; i < partsNoOpt.length; i++) { - if (partsNoOpt[i].startsWith("$")) { - if (partsNoOpt[i].contains("=") ^ partsNoOpt[i].contains("~")) { - String separator = partsNoOpt[i].contains("=") ? "=" : "~"; - String fingerprint = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[i].substring(1, partsNoOpt[i].indexOf( - separator))); - String nickname = ParseHelper.parseNickname(line, - partsNoOpt[i].substring(partsNoOpt[i].indexOf( - separator) + 1)); - familyEntries[i - 1] = "$" + fingerprint + separator + nickname; - } else { - familyEntries[i - 1] = "$" - + ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[i].substring(1)); - } - } else { - familyEntries[i - 1] = ParseHelper.parseNickname(line, - partsNoOpt[i]); - } - } - this.familyEntries = familyEntries; - } - - private void parseReadHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.readHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseWriteHistoryLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - this.writeHistory = new BandwidthHistoryImpl(line, lineNoOpt, - partsNoOpt); - } - - private void parseEventdnsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.usesEnhancedDnsLogic = ParseHelper.parseBoolean(partsNoOpt[1], line); - } - - private void parseCachesExtraInfoLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("caches-extra-info")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.cachesExtraInfo = true; - } - - private void parseExtraInfoDigestLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length < 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line, - partsNoOpt[1]); - if (partsNoOpt.length >= 3) { - ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[2]); - this.extraInfoDigestSha256 = partsNoOpt[2]; - } - } - - private void parseHiddenServiceDirLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length == 1) { - this.hiddenServiceDirVersions = new Integer[] { 2 }; - } else { - try { - Integer[] result = new Integer[partsNoOpt.length - 1]; - for (int i = 1; i < partsNoOpt.length; i++) { - result[i - 1] = Integer.parseInt(partsNoOpt[i]); - } - this.hiddenServiceDirVersions = result; - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal value in line '" - + line + "'."); - } - } - } - - private void parseProtocolsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - int linkIndex = -1, circuitIndex = -1; - for (int i = 1; i < partsNoOpt.length; i++) { - switch (partsNoOpt[i]) { - case "Link": - linkIndex = i; - break; - case "Circuit": - circuitIndex = i; - break; - default: - // empty - } - } - if (linkIndex < 0 || circuitIndex < 0 || circuitIndex < linkIndex) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - try { - Integer[] linkProtocolVersions = - new Integer[circuitIndex - linkIndex - 1]; - for (int i = linkIndex + 1, j = 0; i < circuitIndex; i++, j++) { - linkProtocolVersions[j] = Integer.parseInt(partsNoOpt[i]); - } - Integer[] circuitProtocolVersions = - new Integer[partsNoOpt.length - circuitIndex - 1]; - for (int i = circuitIndex + 1, j = 0; i < partsNoOpt.length; - i++, j++) { - circuitProtocolVersions[j] = Integer.parseInt(partsNoOpt[i]); - } - this.linkProtocolVersions = linkProtocolVersions; - this.circuitProtocolVersions = circuitProtocolVersions; - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseAllowSingleHopExitsLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("allow-single-hop-exits")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - 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 parseRouterDigestLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.serverDescriptorDigest = ParseHelper.parseTwentyByteHexString( - line, partsNoOpt[1]); - } - - private void parseIpv6PolicyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - boolean isValid = true; - if (partsNoOpt.length != 3) { - isValid = false; - } else { - switch (partsNoOpt[1]) { - case "accept": - case "reject": - this.ipv6DefaultPolicy = partsNoOpt[1]; - this.ipv6PortList = partsNoOpt[2]; - String[] ports = partsNoOpt[2].split(",", -1); - for (int i = 0; i < ports.length; i++) { - if (ports[i].length() < 1) { - isValid = false; - break; - } - } - break; - default: - isValid = false; - } - } - if (!isValid) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseNtorOnionKeyLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.ntorOnionKey = partsNoOpt[1].replaceAll("=", ""); - } - - private void parseIdentityEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 1) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseOnionKeyCrosscert(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 1) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseNtorOnionKeyCrosscert(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - try { - this.ntorOnionKeyCrosscertSign = Integer.parseInt(partsNoOpt[1]); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - } - - private void parseTunnelledDirServerLine(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (!lineNoOpt.equals("tunnelled-dir-server")) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.tunnelledDirServer = true; - } - - private void parseIdentityEd25519CryptoBlock(String cryptoString) - throws DescriptorParseException { - String masterKeyEd25519FromIdentityEd25519 = - ParseHelper.parseMasterKeyEd25519FromIdentityEd25519CryptoBlock( - cryptoString); - if (this.masterKeyEd25519 != null && !this.masterKeyEd25519.equals( - masterKeyEd25519FromIdentityEd25519)) { - throw new DescriptorParseException("Mismatch between " - + "identity-ed25519 and master-key-ed25519."); - } - this.masterKeyEd25519 = masterKeyEd25519FromIdentityEd25519; - } - - private void parseMasterKeyEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - String masterKeyEd25519FromMasterKeyEd25519Line = partsNoOpt[1]; - if (this.masterKeyEd25519 != null && !masterKeyEd25519.equals( - masterKeyEd25519FromMasterKeyEd25519Line)) { - throw new DescriptorParseException("Mismatch between " - + "identity-ed25519 and master-key-ed25519."); - } - this.masterKeyEd25519 = masterKeyEd25519FromMasterKeyEd25519Line; - } - - private void parseRouterSigEd25519Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - this.routerSignatureEd25519 = partsNoOpt[1]; - } - - private void parseRouterDigestSha256Line(String line, String lineNoOpt, - String[] partsNoOpt) throws DescriptorParseException { - if (partsNoOpt.length != 2) { - throw new DescriptorParseException("Illegal line '" + line + "'."); - } - ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]); - this.serverDescriptorDigestSha256 = partsNoOpt[1]; - } - - private void calculateDigest() throws DescriptorParseException { - if (this.serverDescriptorDigest != null) { - /* We already learned the descriptor digest of this bridge - * descriptor from a "router-digest" line. */ - return; - } - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "router "; - String sigToken = "\nrouter-signature\n"; - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, - forDigest, 0, sig - start); - this.serverDescriptorDigest = DatatypeConverter.printHexBinary( - MessageDigest.getInstance("SHA-1").digest(forDigest)). - toLowerCase(); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.serverDescriptorDigest == null) { - throw new DescriptorParseException("Could not calculate server " - + "descriptor digest."); - } - } - - private void calculateDigestSha256() throws DescriptorParseException { - if (this.serverDescriptorDigestSha256 != null) { - /* We already learned the descriptor digest of this bridge - * descriptor from a "router-digest-sha256" line. */ - return; - } - try { - String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); - String startToken = "router "; - String sigToken = "\n-----END SIGNATURE-----\n"; - int start = ascii.indexOf(startToken); - int sig = ascii.indexOf(sigToken) + sigToken.length(); - if (start >= 0 && sig >= 0 && sig > start) { - byte[] forDigest = new byte[sig - start]; - System.arraycopy(this.getRawDescriptorBytes(), start, forDigest, - 0, sig - start); - this.serverDescriptorDigestSha256 = - DatatypeConverter.printBase64Binary( - MessageDigest.getInstance("SHA-256").digest(forDigest)). - replaceAll("=", ""); - } - } catch (UnsupportedEncodingException e) { - /* Handle below. */ - } catch (NoSuchAlgorithmException e) { - /* Handle below. */ - } - if (this.serverDescriptorDigestSha256 == null) { - throw new DescriptorParseException("Could not calculate server " - + "descriptor SHA-256 digest."); - } - } - - private String serverDescriptorDigest; - @Override - public String getServerDescriptorDigest() { - return this.serverDescriptorDigest; - } - - private String serverDescriptorDigestSha256; - @Override - public String getServerDescriptorDigestSha256() { - return this.serverDescriptorDigestSha256; - } - - private String nickname; - @Override - public String getNickname() { - return this.nickname; - } - - private String address; - @Override - public String getAddress() { - return this.address; - } - - private int orPort; - @Override - public int getOrPort() { - return this.orPort; - } - - private int socksPort; - @Override - public int getSocksPort() { - return this.socksPort; - } - - private int dirPort; - @Override - public int getDirPort() { - return this.dirPort; - } - - private List<String> orAddresses = new ArrayList<>(); - @Override - public List<String> getOrAddresses() { - return new ArrayList<>(this.orAddresses); - } - - private int bandwidthRate; - @Override - public int getBandwidthRate() { - return this.bandwidthRate; - } - - private int bandwidthBurst; - @Override - public int getBandwidthBurst() { - return this.bandwidthBurst; - } - - private int bandwidthObserved; - @Override - public int getBandwidthObserved() { - return this.bandwidthObserved; - } - - private String platform; - @Override - public String getPlatform() { - return this.platform; - } - - private long publishedMillis; - @Override - public long getPublishedMillis() { - return this.publishedMillis; - } - - private String fingerprint; - @Override - public String getFingerprint() { - return this.fingerprint; - } - - private boolean hibernating; - @Override - public boolean isHibernating() { - return this.hibernating; - } - - private Long uptime; - @Override - public Long getUptime() { - return this.uptime; - } - - private String onionKey; - @Override - public String getOnionKey() { - return this.onionKey; - } - - private String signingKey; - @Override - public String getSigningKey() { - return this.signingKey; - } - - private List<String> exitPolicyLines = new ArrayList<>(); - @Override - public List<String> getExitPolicyLines() { - return new ArrayList<>(this.exitPolicyLines); - } - - private String routerSignature; - @Override - public String getRouterSignature() { - return this.routerSignature; - } - - private String contact; - @Override - public String getContact() { - return this.contact; - } - - private String[] familyEntries; - @Override - public List<String> getFamilyEntries() { - return this.familyEntries == null ? null : - Arrays.asList(this.familyEntries); - } - - private BandwidthHistory readHistory; - @Override - public BandwidthHistory getReadHistory() { - return this.readHistory; - } - - private BandwidthHistory writeHistory; - @Override - public BandwidthHistory getWriteHistory() { - return this.writeHistory; - } - - private boolean usesEnhancedDnsLogic; - @Override - public boolean getUsesEnhancedDnsLogic() { - return this.usesEnhancedDnsLogic; - } - - private boolean cachesExtraInfo; - @Override - public boolean getCachesExtraInfo() { - return this.cachesExtraInfo; - } - - private String extraInfoDigest; - @Override - public String getExtraInfoDigest() { - return this.extraInfoDigest; - } - - private String extraInfoDigestSha256; - @Override - public String getExtraInfoDigestSha256() { - return this.extraInfoDigestSha256; - } - - private Integer[] hiddenServiceDirVersions; - @Override - public List<Integer> getHiddenServiceDirVersions() { - return this.hiddenServiceDirVersions == null ? null : - Arrays.asList(this.hiddenServiceDirVersions); - } - - private Integer[] linkProtocolVersions; - @Override - public List<Integer> getLinkProtocolVersions() { - return this.linkProtocolVersions == null ? null : - Arrays.asList(this.linkProtocolVersions); - } - - private Integer[] circuitProtocolVersions; - @Override - public List<Integer> getCircuitProtocolVersions() { - return this.circuitProtocolVersions == null ? null : - Arrays.asList(this.circuitProtocolVersions); - } - - private boolean allowSingleHopExits; - @Override - public boolean getAllowSingleHopExits() { - return this.allowSingleHopExits; - } - - private String ipv6DefaultPolicy; - @Override - public String getIpv6DefaultPolicy() { - return this.ipv6DefaultPolicy; - } - - private String ipv6PortList; - @Override - public String getIpv6PortList() { - return this.ipv6PortList; - } - - private String ntorOnionKey; - @Override - public String getNtorOnionKey() { - return this.ntorOnionKey; - } - - private String identityEd25519; - @Override - public String getIdentityEd25519() { - return this.identityEd25519; - } - - private String masterKeyEd25519; - @Override - public String getMasterKeyEd25519() { - return this.masterKeyEd25519; - } - - private String routerSignatureEd25519; - @Override - public String getRouterSignatureEd25519() { - return this.routerSignatureEd25519; - } - - private String onionKeyCrosscert; - @Override - public String getOnionKeyCrosscert() { - return this.onionKeyCrosscert; - } - - private String ntorOnionKeyCrosscert; - @Override - public String getNtorOnionKeyCrosscert() { - return this.ntorOnionKeyCrosscert; - } - - private int ntorOnionKeyCrosscertSign = -1; - @Override - public int getNtorOnionKeyCrosscertSign() { - return ntorOnionKeyCrosscertSign; - } - - private boolean tunnelledDirServer; - @Override - public boolean getTunnelledDirServer() { - return this.tunnelledDirServer; - } -} - diff --git a/src/org/torproject/descriptor/impl/TorperfResultImpl.java b/src/org/torproject/descriptor/impl/TorperfResultImpl.java deleted file mode 100644 index 0800de0..0000000 --- a/src/org/torproject/descriptor/impl/TorperfResultImpl.java +++ /dev/null @@ -1,546 +0,0 @@ -/* Copyright 2012--2016 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -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 java.util.SortedMap; -import java.util.TreeMap; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.TorperfResult; - -public class TorperfResultImpl extends DescriptorImpl - implements TorperfResult { - - protected static List<Descriptor> parseTorperfResults( - byte[] rawDescriptorBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - if (rawDescriptorBytes.length == 0) { - throw new DescriptorParseException("Descriptor is empty."); - } - List<Descriptor> parsedDescriptors = new ArrayList<>(); - String descriptorString = new String(rawDescriptorBytes); - Scanner s = new Scanner(descriptorString).useDelimiter("\r?\n"); - String typeAnnotation = ""; - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("@type torperf ")) { - String[] parts = line.split(" "); - if (parts.length != 3) { - throw new DescriptorParseException("Illegal line '" + line - + "'."); - } - String version = parts[2]; - if (!version.startsWith("1.")) { - throw new DescriptorParseException("Unsupported version in " - + " line '" + line + "'."); - } - typeAnnotation = line + "\n"; - } else { - parsedDescriptors.add(new TorperfResultImpl( - (typeAnnotation + line).getBytes(), - failUnrecognizedDescriptorLines)); - typeAnnotation = ""; - } - } - return parsedDescriptors; - } - - protected TorperfResultImpl(byte[] rawDescriptorBytes, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false); - this.parseTorperfResultLine(new String(rawDescriptorBytes)); - } - - private void parseTorperfResultLine(String inputLine) - throws DescriptorParseException { - String line = inputLine; - while (line.startsWith("@") && line.contains("\n")) { - line = line.split("\n")[1]; - } - if (line.isEmpty()) { - throw new DescriptorParseException("Blank lines are not allowed."); - } - String[] parts = line.split(" "); - for (int i = 0; i < parts.length; i++) { - String keyValue = parts[i]; - String[] keyValueParts = keyValue.split("="); - if (keyValueParts.length != 2) { - throw new DescriptorParseException("Illegal key-value pair in " - + "line '" + line + "'."); - } - String key = keyValueParts[0]; - this.markKeyAsParsed(key, line); - String value = keyValueParts[1]; - switch (key) { - case "SOURCE": - this.parseSource(value, keyValue, line); - break; - case "FILESIZE": - this.parseFileSize(value, keyValue, line); - break; - case "START": - this.parseStart(value, keyValue, line); - break; - case "SOCKET": - this.parseSocket(value, keyValue, line); - break; - case "CONNECT": - this.parseConnect(value, keyValue, line); - break; - case "NEGOTIATE": - this.parseNegotiate(value, keyValue, line); - break; - case "REQUEST": - this.parseRequest(value, keyValue, line); - break; - case "RESPONSE": - this.parseResponse(value, keyValue, line); - break; - case "DATAREQUEST": - this.parseDataRequest(value, keyValue, line); - break; - case "DATARESPONSE": - this.parseDataResponse(value, keyValue, line); - break; - case "DATACOMPLETE": - this.parseDataComplete(value, keyValue, line); - break; - case "WRITEBYTES": - this.parseWriteBytes(value, keyValue, line); - break; - case "READBYTES": - this.parseReadBytes(value, keyValue, line); - break; - case "DIDTIMEOUT": - this.parseDidTimeout(value, keyValue, line); - break; - case "LAUNCH": - this.parseLaunch(value, keyValue, line); - break; - case "USED_AT": - this.parseUsedAt(value, keyValue, line); - break; - case "PATH": - this.parsePath(value, keyValue, line); - break; - case "BUILDTIMES": - this.parseBuildTimes(value, keyValue, line); - break; - case "TIMEOUT": - this.parseTimeout(value, keyValue, line); - break; - case "QUANTILE": - this.parseQuantile(value, keyValue, line); - break; - case "CIRC_ID": - this.parseCircId(value, keyValue, line); - break; - case "USED_BY": - this.parseUsedBy(value, keyValue, line); - break; - default: - if (key.startsWith("DATAPERC")) { - this.parseDataPercentile(value, keyValue, line); - } else if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized key '" + key - + "' in line '" + line + "'."); - } else { - if (this.unrecognizedKeys == null) { - this.unrecognizedKeys = new TreeMap<>(); - } - this.unrecognizedKeys.put(key, value); - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<>(); - } - if (!this.unrecognizedLines.contains(line)) { - this.unrecognizedLines.add(line); - } - } - } - } - this.checkAllRequiredKeysParsed(line); - } - - private Set<String> parsedKeys = new HashSet<>(); - private Set<String> requiredKeys = new HashSet<>(Arrays.asList( - ("SOURCE,FILESIZE,START,SOCKET,CONNECT,NEGOTIATE,REQUEST,RESPONSE," - + "DATAREQUEST,DATARESPONSE,DATACOMPLETE,WRITEBYTES,READBYTES"). - split(","))); - private void markKeyAsParsed(String key, String line) - throws DescriptorParseException { - if (this.parsedKeys.contains(key)) { - throw new DescriptorParseException("Key '" + key + "' is contained " - + "at least twice in line '" + line + "', but must be " - + "contained at most once."); - } - this.parsedKeys.add(key); - this.requiredKeys.remove(key); - } - private void checkAllRequiredKeysParsed(String line) - throws DescriptorParseException { - for (String key : this.requiredKeys) { - throw new DescriptorParseException("Key '" + key + "' is contained " - + "contained 0 times in line '" + line + "', but must be " - + "contained exactly once."); - } - } - - private void parseSource(String value, String keyValue, String line) - throws DescriptorParseException { - this.source = value; - } - - private void parseFileSize(String value, String keyValue, String line) - throws DescriptorParseException { - try { - this.fileSize = Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal value in '" + keyValue - + "' in line '" + line + "'."); - } - } - - private void parseStart(String value, String keyValue, String line) - throws DescriptorParseException { - this.startMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseSocket(String value, String keyValue, String line) - throws DescriptorParseException { - this.socketMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseConnect(String value, String keyValue, String line) - throws DescriptorParseException { - this.connectMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseNegotiate(String value, String keyValue, String line) - throws DescriptorParseException { - this.negotiateMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseRequest(String value, String keyValue, String line) - throws DescriptorParseException { - this.requestMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseResponse(String value, String keyValue, String line) - throws DescriptorParseException { - this.responseMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseDataRequest(String value, String keyValue, - String line) throws DescriptorParseException { - this.dataRequestMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseDataResponse(String value, String keyValue, - String line) throws DescriptorParseException { - this.dataResponseMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseDataComplete(String value, String keyValue, - String line) throws DescriptorParseException { - this.dataCompleteMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseWriteBytes(String value, String keyValue, String line) - throws DescriptorParseException { - this.writeBytes = parseInt(value, keyValue, line); - } - - private void parseReadBytes(String value, String keyValue, String line) - throws DescriptorParseException { - this.readBytes = parseInt(value, keyValue, line); - } - - private void parseDidTimeout(String value, String keyValue, String line) - throws DescriptorParseException { - if (value.equals("1")) { - this.didTimeout = true; - } else if (value.equals("0")) { - this.didTimeout = false; - } else { - throw new DescriptorParseException("Illegal value in '" + keyValue - + "' in line '" + line + "'."); - } - } - - private void parseDataPercentile(String value, String keyValue, - String line) throws DescriptorParseException { - String key = keyValue.substring(0, keyValue.indexOf("=")); - String percentileString = key.substring("DATAPERC".length()); - int percentile = -1; - try { - percentile = Integer.parseInt(percentileString); - } catch (NumberFormatException e) { - /* Treat key as unrecognized below. */ - percentile = -1; - } - if (percentile < 0 || percentile > 100) { - if (this.unrecognizedKeys == null) { - this.unrecognizedKeys = new TreeMap<>(); - } - this.unrecognizedKeys.put(key, value); - } else { - long timestamp = this.parseTimestamp(value, keyValue, line); - if (this.dataPercentiles == null) { - this.dataPercentiles = new TreeMap<>(); - } - this.dataPercentiles.put(percentile, timestamp); - } - } - - private void parseLaunch(String value, String keyValue, String line) - throws DescriptorParseException { - this.launchMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parseUsedAt(String value, String keyValue, String line) - throws DescriptorParseException { - this.usedAtMillis = this.parseTimestamp(value, keyValue, line); - } - - private void parsePath(String value, String keyValue, String line) - throws DescriptorParseException { - String[] valueParts = value.split(","); - String[] result = new String[valueParts.length]; - for (int i = 0; i < valueParts.length; i++) { - if (valueParts[i].length() != 41) { - throw new DescriptorParseException("Illegal value in '" + keyValue - + "' in line '" + line + "'."); - } - result[i] = ParseHelper.parseTwentyByteHexString(line, - valueParts[i].substring(1)); - } - this.path = result; - } - - private void parseBuildTimes(String value, String keyValue, String line) - throws DescriptorParseException { - String[] valueParts = value.split(","); - Long[] result = new Long[valueParts.length]; - for (int i = 0; i < valueParts.length; i++) { - result[i] = this.parseTimestamp(valueParts[i], keyValue, line); - } - this.buildTimes = result; - } - - private void parseTimeout(String value, String keyValue, String line) - throws DescriptorParseException { - this.timeout = this.parseInt(value, keyValue, line); - } - - private void parseQuantile(String value, String keyValue, String line) - throws DescriptorParseException { - this.quantile = this.parseDouble(value, keyValue, line); - } - - private void parseCircId(String value, String keyValue, String line) - throws DescriptorParseException { - this.circId = this.parseInt(value, keyValue, line); - } - - private void parseUsedBy(String value, String keyValue, String line) - throws DescriptorParseException { - this.usedBy = this.parseInt(value, keyValue, line); - } - - private long parseTimestamp(String value, String keyValue, String line) - throws DescriptorParseException { - long timestamp = -1L; - if (value.contains(".") && value.split("\.").length == 2) { - String zeroPaddedValue = (value + "000"); - String threeDecimalPlaces = zeroPaddedValue.substring(0, - zeroPaddedValue.indexOf(".") + 4); - String millisString = threeDecimalPlaces.replaceAll("\.", ""); - try { - timestamp = Long.parseLong(millisString); - } catch (NumberFormatException e) { - /* Handle below. */ - } - } - if (timestamp < 0L) { - throw new DescriptorParseException("Illegal timestamp '" + value - + "' in '" + keyValue + "' in line '" + line + "'."); - } - return timestamp; - } - - private int parseInt(String value, String keyValue, String line) - throws DescriptorParseException { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal value in '" + keyValue - + "' in line '" + line + "'."); - } - } - - private double parseDouble(String value, String keyValue, String line) - throws DescriptorParseException { - try { - return Double.parseDouble(value); - } catch (NumberFormatException e) { - throw new DescriptorParseException("Illegal value in '" + keyValue - + "' in line '" + line + "'."); - } - } - - private SortedMap<String, String> unrecognizedKeys; - @Override - public SortedMap<String, String> getUnrecognizedKeys() { - return this.unrecognizedKeys == null ? null - : new TreeMap<>(this.unrecognizedKeys); - } - - private String source; - @Override - public String getSource() { - return this.source; - } - - private int fileSize; - @Override - public int getFileSize() { - return this.fileSize; - } - - private long startMillis; - @Override - public long getStartMillis() { - return this.startMillis; - } - - private long socketMillis; - @Override - public long getSocketMillis() { - return this.socketMillis; - } - - private long connectMillis; - @Override - public long getConnectMillis() { - return this.connectMillis; - } - - private long negotiateMillis; - @Override - public long getNegotiateMillis() { - return this.negotiateMillis; - } - - private long requestMillis; - @Override - public long getRequestMillis() { - return this.requestMillis; - } - - private long responseMillis; - @Override - public long getResponseMillis() { - return this.responseMillis; - } - - private long dataRequestMillis; - @Override - public long getDataRequestMillis() { - return this.dataRequestMillis; - } - - private long dataResponseMillis; - @Override - public long getDataResponseMillis() { - return this.dataResponseMillis; - } - - private long dataCompleteMillis; - @Override - public long getDataCompleteMillis() { - return this.dataCompleteMillis; - } - - private int writeBytes; - @Override - public int getWriteBytes() { - return this.writeBytes; - } - - private int readBytes; - @Override - public int getReadBytes() { - return this.readBytes; - } - - private boolean didTimeout; - @Override - public Boolean didTimeout() { - return this.didTimeout; - } - - private SortedMap<Integer, Long> dataPercentiles; - @Override - public SortedMap<Integer, Long> getDataPercentiles() { - return this.dataPercentiles == null ? null - : new TreeMap<>(this.dataPercentiles); - } - - private long launchMillis = -1L; - @Override - public long getLaunchMillis() { - return this.launchMillis; - } - - private long usedAtMillis = -1L; - @Override - public long getUsedAtMillis() { - return this.usedAtMillis; - } - - private String[] path; - @Override - public List<String> getPath() { - return this.path == null ? null : Arrays.asList(this.path); - } - - private Long[] buildTimes; - @Override - public List<Long> getBuildTimes() { - return this.buildTimes == null ? null : - Arrays.asList(this.buildTimes); - } - - private long timeout = -1L; - @Override - public long getTimeout() { - return this.timeout; - } - - private double quantile = -1.0; - @Override - public double getQuantile() { - return this.quantile; - } - - private int circId = -1; - @Override - public int getCircId() { - return this.circId; - } - - private int usedBy = -1; - @Override - public int getUsedBy() { - return this.usedBy; - } -} - diff --git a/src/org/torproject/descriptor/package-info.java b/src/org/torproject/descriptor/package-info.java deleted file mode 100644 index 5b34554..0000000 --- a/src/org/torproject/descriptor/package-info.java +++ /dev/null @@ -1,80 +0,0 @@ -/* Copyright 2016 The Tor Project - * See LICENSE for licensing information */ - -/** - * Interfaces and essential classes for obtaining and processing Tor - * descriptors. - * - * <p>This package contains all relevant interfaces and - * classes that an application would need to use this library. - * Applications are strongly discouraged from accessing types from the - * implementation package ({@code org.torproject.descriptor.impl}) - * directly, because those may change without prior notice.</p> - * - * <p>Interfaces and classes in this package can be grouped into - * general-purpose types to obtain and process any type of descriptor and - * descriptors produced by different components of the Tor network:</p> - * - * <ol> - * <li>General-purpose types comprise - * {@link org.torproject.descriptor.DescriptorSourceFactory} which is the - * main entry point into using this library. This factory is used to - * create the descriptor sources for obtaining remote descriptor data - * ({@link org.torproject.descriptor.DescriptorDownloader} and - * {@link org.torproject.descriptor.DescriptorCollector}) and descriptor - * sources for processing local descriptor data - * ({@link org.torproject.descriptor.DescriptorReader} and - * {@link org.torproject.descriptor.DescriptorParser}). General-purpose - * types also include descriptor containers - * ({@link org.torproject.descriptor.DescriptorRequest} and - * {@link org.torproject.descriptor.DescriptorFile}) and the - * superinterface for all provided descriptors - * ({@link org.torproject.descriptor.Descriptor}).</li> - * - * <li>The first group of descriptors is published by relays and servers - * in the Tor network. These interfaces include server descriptors - * ({@link org.torproject.descriptor.ServerDescriptor} with subinterfaces - * {@link org.torproject.descriptor.RelayServerDescriptor} and - * {@link org.torproject.descriptor.BridgeServerDescriptor}), extra-info - * descriptors ({@link org.torproject.descriptor.ExtraInfoDescriptor} with - * subinterfaces - * {@link org.torproject.descriptor.RelayExtraInfoDescriptor} and - * {@link org.torproject.descriptor.BridgeExtraInfoDescriptor}), - * microdescriptors which are derived from server descriptors by the - * directory authorities - * ({@link org.torproject.descriptor.Microdescriptor}), and helper types - * for parts of the aforementioned descriptors - * ({@link org.torproject.descriptor.BandwidthHistory}).</li> - * - * <li>The second group of descriptors is generated by authoritative - * directory servers that form an opinion about relays and bridges in the - * Tor network. These include descriptors specified in version 3 of the - * directory protocol - * ({@link org.torproject.descriptor.RelayNetworkStatusConsensus}, - * {@link org.torproject.descriptor.RelayNetworkStatusVote}, - * {@link org.torproject.descriptor.DirectoryKeyCertificate}, and helper - * types for descriptor parts - * {@link org.torproject.descriptor.DirSourceEntry}, - * {@link org.torproject.descriptor.NetworkStatusEntry}, and - * {@link org.torproject.descriptor.DirectorySignature}), descriptors from - * earlier directory protocol version 2 - * ({@link org.torproject.descriptor.RelayNetworkStatus}) and version 1 - * ({@link org.torproject.descriptor.RelayDirectory} and - * {@link org.torproject.descriptor.RouterStatusEntry}), as well as - * descriptors published by the bridge authority and sanitized by the - * CollecTor service - * ({@link org.torproject.descriptor.BridgeNetworkStatus}).</li> - * - * <li>The third group of descriptors is created by auxiliary services - * connected to the Tor network rather than by the Tor software. This - * group comprises descriptors by the bridge distribution service BridgeDB - * ({@link org.torproject.descriptor.BridgePoolAssignment}), the exit list - * service TorDNSEL ({@link org.torproject.descriptor.ExitList}), and the - * performance measurement service Torperf - * ({@link org.torproject.descriptor.TorperfResult}).</li> - * </ol> - * - * @since 1.0.0 - */ -package org.torproject.descriptor; - diff --git a/src/test/java/org/torproject/descriptor/benchmark/MeasurePerformance.java b/src/test/java/org/torproject/descriptor/benchmark/MeasurePerformance.java new file mode 100644 index 0000000..a52020a --- /dev/null +++ b/src/test/java/org/torproject/descriptor/benchmark/MeasurePerformance.java @@ -0,0 +1,278 @@ +/* Copyright 2016 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.benchmark; + +import org.torproject.descriptor.Descriptor; +import org.torproject.descriptor.DescriptorFile; +import org.torproject.descriptor.DescriptorReader; +import org.torproject.descriptor.DescriptorSourceFactory; +import org.torproject.descriptor.ExtraInfoDescriptor; +import org.torproject.descriptor.Microdescriptor; +import org.torproject.descriptor.NetworkStatusEntry; +import org.torproject.descriptor.RelayNetworkStatusConsensus; +import org.torproject.descriptor.ServerDescriptor; + +import java.io.File; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.SortedMap; + +public class MeasurePerformance { + + /* Check if all necessary files are available and then measure + * performance of some more or less common use cases. */ + public static void main(String[] args) { + if (!filesAvailable()) { + return; + } + measureAverageAdvertisedBandwidth(new File(resDir, resPaths[0])); + pause(); + measureAverageAdvertisedBandwidth(new File(resDir, resPaths[1])); + pause(); + measureAverageAdvertisedBandwidth(new File(resDir, resPaths[2])); + pause(); + measureCountriesV3Requests(new File(resDir, resPaths[3])); + pause(); + measureCountriesV3Requests(new File(resDir, resPaths[4])); + pause(); + measureAverageRelaysExit(new File(resDir, resPaths[5])); + pause(); + measureAverageRelaysExit(new File(resDir, resPaths[6])); + pause(); + measureAverageRelaysExit(new File(resDir, resPaths[7])); + measureFractionRelaysExit80Microdescriptors( + new File(resDir, resPaths[8])); + measureFractionRelaysExit80Microdescriptors( + new File(resDir, resPaths[9])); + } + + private static File resDir = new File("res"); + private static String[] resPaths = new String[] { + "archive/relay-descriptors/server-descriptors/" + + "server-descriptors-2015-11.tar.xz", + "archive/relay-descriptors/server-descriptors/" + + "server-descriptors-2015-11.tar", + "archive/relay-descriptors/server-descriptors/" + + "server-descriptors-2015-11", + "archive/relay-descriptors/extra-infos/extra-infos-2015-11.tar.xz", + "archive/relay-descriptors/extra-infos/extra-infos-2015-11.tar", + "archive/relay-descriptors/consensuses/consensuses-2015-11.tar.xz", + "archive/relay-descriptors/consensuses/consensuses-2015-11.tar", + "archive/relay-descriptors/consensuses/consensuses-2015-11", + "archive/relay-descriptors/microdescs/microdescs-2015-11.tar.xz", + "archive/relay-descriptors/microdescs/microdescs-2015-11.tar" + }; + + private static boolean filesAvailable() { + if (!resDir.exists() || !resDir.isDirectory()) { + return false; + } + for (String resPath : resPaths) { + if (!(new File(resDir, resPath).exists())) { + System.err.println("Missing resource: " + resDir + "/" + resPath); + return false; + } + } + return true; + } + + private static void pause() { + try { + Thread.sleep(15L * 1000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static void measureAverageAdvertisedBandwidth( + File tarballFileOrDirectory) { + System.out.println("Starting measureAverageAdvertisedBandwidth"); + long startedMillis = System.currentTimeMillis(); + long sumAdvertisedBandwidth = 0, countedServerDescriptors = 0; + DescriptorReader descriptorReader = + DescriptorSourceFactory.createDescriptorReader(); + descriptorReader.addTarball(tarballFileOrDirectory); + descriptorReader.addDirectory(tarballFileOrDirectory); + Iterator<DescriptorFile> descriptorFiles = + descriptorReader.readDescriptors(); + while (descriptorFiles.hasNext()) { + DescriptorFile descriptorFile = descriptorFiles.next(); + for (Descriptor descriptor : descriptorFile.getDescriptors()) { + if (!(descriptor instanceof ServerDescriptor)) { + continue; + } + ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor; + sumAdvertisedBandwidth += (long) Math.min(Math.min( + serverDescriptor.getBandwidthRate(), + serverDescriptor.getBandwidthBurst()), + serverDescriptor.getBandwidthObserved()); + countedServerDescriptors++; + } + } + long endedMillis = System.currentTimeMillis(); + System.out.println("Ending measureAverageAdvertisedBandwidth"); + System.out.printf("Total time: %d millis%n", + endedMillis - startedMillis); + System.out.printf("Processed server descriptors: %d%n", + countedServerDescriptors); + System.out.printf("Average advertised bandwidth: %d%n", + sumAdvertisedBandwidth / countedServerDescriptors); + System.out.printf("Time per server descriptor: %.6f millis%n", + ((double) (endedMillis - startedMillis)) + / ((double) countedServerDescriptors)); + } + + private static void measureCountriesV3Requests(File tarballFile) { + System.out.println("Starting measureCountriesV3Requests"); + long startedMillis = System.currentTimeMillis(); + Set<String> countries = new HashSet<>(); + long countedExtraInfoDescriptors = 0; + DescriptorReader descriptorReader = + DescriptorSourceFactory.createDescriptorReader(); + descriptorReader.addTarball(tarballFile); + Iterator<DescriptorFile> descriptorFiles = + descriptorReader.readDescriptors(); + while (descriptorFiles.hasNext()) { + DescriptorFile descriptorFile = descriptorFiles.next(); + for (Descriptor descriptor : descriptorFile.getDescriptors()) { + if (!(descriptor instanceof ExtraInfoDescriptor)) { + continue; + } + ExtraInfoDescriptor extraInfoDescriptor = + (ExtraInfoDescriptor) descriptor; + SortedMap<String, Integer> dirreqV3Reqs = + extraInfoDescriptor.getDirreqV3Reqs(); + if (dirreqV3Reqs != null) { + countries.addAll(dirreqV3Reqs.keySet()); + } + countedExtraInfoDescriptors++; + } + } + long endedMillis = System.currentTimeMillis(); + System.out.println("Ending measureCountriesV3Requests"); + System.out.printf("Total time: %d millis%n", + endedMillis - startedMillis); + System.out.printf("Processed extra-info descriptors: %d%n", + countedExtraInfoDescriptors); + System.out.printf("Number of countries: %d%n", + countries.size()); + System.out.printf("Time per extra-info descriptor: %.6f millis%n", + ((double) (endedMillis - startedMillis)) + / ((double) countedExtraInfoDescriptors)); + } + + private static void measureAverageRelaysExit( + File tarballFileOrDirectory) { + System.out.println("Starting measureAverageRelaysExit"); + long startedMillis = System.currentTimeMillis(); + long totalRelaysWithExitFlag = 0L, totalRelays = 0L, + countedConsensuses = 0L; + DescriptorReader descriptorReader = + DescriptorSourceFactory.createDescriptorReader(); + descriptorReader.addTarball(tarballFileOrDirectory); + descriptorReader.addDirectory(tarballFileOrDirectory); + Iterator<DescriptorFile> descriptorFiles = + descriptorReader.readDescriptors(); + while (descriptorFiles.hasNext()) { + DescriptorFile descriptorFile = descriptorFiles.next(); + for (Descriptor descriptor : descriptorFile.getDescriptors()) { + if (!(descriptor instanceof RelayNetworkStatusConsensus)) { + continue; + } + RelayNetworkStatusConsensus consensus = + (RelayNetworkStatusConsensus) descriptor; + for (NetworkStatusEntry entry : + consensus.getStatusEntries().values()) { + if (entry.getFlags().contains("Exit")) { + totalRelaysWithExitFlag++; + } + totalRelays++; + } + countedConsensuses++; + } + } + long endedMillis = System.currentTimeMillis(); + System.out.println("Ending measureAverageRelaysExit"); + System.out.printf("Total time: %d millis%n", + endedMillis - startedMillis); + System.out.printf("Processed consensuses: %d%n", countedConsensuses); + System.out.printf("Total number of status entries: %d%n", + totalRelays); + System.out.printf("Total number of status entries with Exit flag: " + + "%d%n", totalRelaysWithExitFlag); + System.out.printf("Average number of relays with Exit Flag: %.2f%n", + (double) totalRelaysWithExitFlag / (double) totalRelays); + System.out.printf("Time per consensus: %.6f millis%n", + ((double) (endedMillis - startedMillis)) + / ((double) countedConsensuses)); + } + + private static void measureFractionRelaysExit80Microdescriptors( + File tarballFile) { + System.out.println("Starting " + + "measureFractionRelaysExit80Microdescriptors"); + long startedMillis = System.currentTimeMillis(); + long totalRelaysWithExitFlag = 0L, countedMicrodescriptors = 0L; + DescriptorReader descriptorReader = + DescriptorSourceFactory.createDescriptorReader(); + descriptorReader.addTarball(tarballFile); + Iterator<DescriptorFile> descriptorFiles = + descriptorReader.readDescriptors(); + while (descriptorFiles.hasNext()) { + DescriptorFile descriptorFile = descriptorFiles.next(); + for (Descriptor descriptor : descriptorFile.getDescriptors()) { + if (!(descriptor instanceof Microdescriptor)) { + continue; + } + countedMicrodescriptors++; + Microdescriptor microdescriptor = + (Microdescriptor) descriptor; + String defaultPolicy = microdescriptor.getDefaultPolicy(); + if (defaultPolicy == null) { + continue; + } + boolean accept = "accept".equals( + microdescriptor.getDefaultPolicy()); + for (String ports : microdescriptor.getPortList().split(",")) { + if (ports.contains("-")) { + String[] parts = ports.split("-"); + int from = Integer.parseInt(parts[0]); + int to = Integer.parseInt(parts[1]); + if (from <= 80 && to >= 80) { + if (accept) { + totalRelaysWithExitFlag++; + } + } else if (to > 80) { + if (!accept) { + totalRelaysWithExitFlag++; + } + break; + } + } else if ("80".equals(ports)) { + if (accept) { + totalRelaysWithExitFlag++; + } + break; + } + } + } + } + long endedMillis = System.currentTimeMillis(); + System.out.println("Ending " + + "measureFractionRelaysExit80Microdescriptors"); + System.out.printf("Total time: %d millis%n", + endedMillis - startedMillis); + System.out.printf("Processed microdescriptors: %d%n", + countedMicrodescriptors); + System.out.printf("Total number of microdescriptors that exit to 80: " + + "%d%n", totalRelaysWithExitFlag); + System.out.printf("Average number of relays that exit to 80: %.2f%n", + (double) totalRelaysWithExitFlag + / (double) countedMicrodescriptors); + System.out.printf("Time per microdescriptor: %.6f millis%n", + ((double) (endedMillis - startedMillis)) + / ((double) countedMicrodescriptors)); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java b/src/test/java/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java new file mode 100644 index 0000000..0847e13 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java @@ -0,0 +1,151 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.torproject.descriptor.BridgeNetworkStatus; +import org.torproject.descriptor.DescriptorParseException; + +/* Test parsing of bridge network statuses. Some of the parsing code is + * already tested in the consensus/vote-parsing tests. */ +public class BridgeNetworkStatusTest { + + /* Helper class to build a bridge network status based on default data + * and modifications requested by test methods. */ + private static class StatusBuilder { + private String fileName = "20151121-173936-" + + "4A0CCD2DDC7995083D73F5D667100C8A5831F16D"; + private static BridgeNetworkStatus + createWithFileName(String fileName) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.fileName = fileName; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private String publishedLine = "published 2015-11-21 17:39:36"; + private static BridgeNetworkStatus + createWithPublishedLine(String line) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.publishedLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private String flagThresholdsLine = "flag-thresholds " + + "stable-uptime=3105080 stable-mtbf=2450615 fast-speed=55000 " + + "guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=337000 " + + "guard-bw-exc-exits=339000 enough-mtbf=1 " + + "ignoring-advertised-bws=0"; + private static BridgeNetworkStatus + createWithFlagThresholdsLine(String line) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.flagThresholdsLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + true); + } + private List<String> statusEntries = new ArrayList<>(); + private String unrecognizedHeaderLine = null; + protected static BridgeNetworkStatus + createWithUnrecognizedHeaderLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.unrecognizedHeaderLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + failUnrecognizedDescriptorLines); + } + private String unrecognizedStatusEntryLine = null; + protected static BridgeNetworkStatus + createWithUnrecognizedStatusEntryLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + sb.unrecognizedStatusEntryLine = line; + return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, + failUnrecognizedDescriptorLines); + } + + private StatusBuilder() { + this.statusEntries.add("r Unnamed ABk0wg4j6BLCdZKleVtmNrfzJGI " + + "bh7gVU1Cz6+JG+7j4qGsF4prDi8 2015-11-21 15:46:25 " + + "10.153.163.200 443 0\ns Fast Running Stable Valid\n" + + "w Bandwidth=264\np reject 1-65535"); + } + private byte[] buildStatus() { + StringBuilder sb = new StringBuilder(); + this.appendHeader(sb); + this.appendStatusEntries(sb); + return sb.toString().getBytes(); + } + private void appendHeader(StringBuilder sb) { + if (this.publishedLine != null) { + sb.append(this.publishedLine).append("\n"); + } + if (this.flagThresholdsLine != null) { + sb.append(this.flagThresholdsLine).append("\n"); + } + if (this.unrecognizedHeaderLine != null) { + sb.append(this.unrecognizedHeaderLine).append("\n"); + } + } + private void appendStatusEntries(StringBuilder sb) { + for (String statusEntry : this.statusEntries) { + sb.append(statusEntry).append("\n"); + } + if (this.unrecognizedStatusEntryLine != null) { + sb.append(this.unrecognizedStatusEntryLine).append("\n"); + } + } + } + + @Test() + public void testSampleStatus() throws DescriptorParseException { + StatusBuilder sb = new StatusBuilder(); + BridgeNetworkStatus status = + new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, true); + assertEquals(1448127576000L, status.getPublishedMillis()); + assertEquals(3105080L, status.getStableUptime()); + assertEquals(2450615L, status.getStableMtbf()); + assertEquals(55000L, status.getFastBandwidth()); + assertEquals(98.0, status.getGuardWfu(), 0.001); + assertEquals(691200L, status.getGuardTk()); + assertEquals(337000L, status.getGuardBandwidthIncludingExits()); + assertEquals(339000L, status.getGuardBandwidthExcludingExits()); + assertEquals(1, status.getEnoughMtbfInfo()); + assertEquals(0, status.getIgnoringAdvertisedBws()); + assertEquals(264, status.getStatusEntries().get( + "001934C20E23E812C27592A5795B6636B7F32462").getBandwidth()); + assertTrue(status.getUnrecognizedLines().isEmpty()); + } + + @Test() + public void testPublishedNoLine() throws DescriptorParseException { + BridgeNetworkStatus status = + StatusBuilder.createWithPublishedLine(null); + assertEquals(1448127576000L, status.getPublishedMillis()); + } + + @Test() + public void testFlagThresholdsNoLine() throws DescriptorParseException { + BridgeNetworkStatus status = + StatusBuilder.createWithFlagThresholdsLine(null); + assertEquals(-1L, status.getStableUptime()); + assertEquals(-1L, status.getStableMtbf()); + assertEquals(-1L, status.getFastBandwidth()); + assertEquals(-1.0, status.getGuardWfu(), 0.001); + assertEquals(-1L, status.getGuardTk()); + assertEquals(-1L, status.getGuardBandwidthIncludingExits()); + assertEquals(-1L, status.getGuardBandwidthExcludingExits()); + assertEquals(-1, status.getEnoughMtbfInfo()); + assertEquals(-1, status.getIgnoringAdvertisedBws()); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/ConsensusBuilder.java b/src/test/java/org/torproject/descriptor/impl/ConsensusBuilder.java new file mode 100644 index 0000000..29a2d47 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/ConsensusBuilder.java @@ -0,0 +1,321 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import java.util.ArrayList; +import java.util.List; + +import org.torproject.descriptor.RelayNetworkStatusConsensus; + +/* Helper class to build a consensus based on default data and + * modifications requested by test methods. */ +public class ConsensusBuilder { + String networkStatusVersionLine = "network-status-version 3"; + protected static RelayNetworkStatusConsensus + createWithNetworkStatusVersionLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.networkStatusVersionLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String voteStatusLine = "vote-status consensus"; + protected static RelayNetworkStatusConsensus + createWithVoteStatusLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.voteStatusLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String consensusMethodLine = "consensus-method 11"; + protected static RelayNetworkStatusConsensus + createWithConsensusMethodLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.consensusMethodLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String validAfterLine = "valid-after 2011-11-30 09:00:00"; + protected static RelayNetworkStatusConsensus + createWithValidAfterLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.validAfterLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String freshUntilLine = "fresh-until 2011-11-30 10:00:00"; + protected static RelayNetworkStatusConsensus + createWithFreshUntilLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.freshUntilLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String validUntilLine = "valid-until 2011-11-30 12:00:00"; + protected static RelayNetworkStatusConsensus + createWithValidUntilLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.validUntilLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String votingDelayLine = "voting-delay 300 300"; + protected static RelayNetworkStatusConsensus + createWithVotingDelayLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.votingDelayLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + String clientVersionsLine = "client-versions 0.2.1.31," + + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; + protected static RelayNetworkStatusConsensus + createWithClientVersionsLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.clientVersionsLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + String serverVersionsLine = "server-versions 0.2.1.31," + + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; + protected static RelayNetworkStatusConsensus + createWithServerVersionsLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.serverVersionsLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String packageLines = null; + protected static RelayNetworkStatusConsensus + createWithPackageLines(String lines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.packageLines = lines; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String knownFlagsLine = "known-flags Authority BadExit Exit " + + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid"; + protected static RelayNetworkStatusConsensus + createWithKnownFlagsLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.knownFlagsLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String paramsLine = "params " + + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 " + + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 " + + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 " + + "cbtquantile=80 circwindow=1000 refuseunknownexits=1"; + protected static RelayNetworkStatusConsensus + createWithParamsLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.paramsLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + List<String> dirSources = new ArrayList<>(); + List<String> statusEntries = new ArrayList<>(); + private String directoryFooterLine = "directory-footer"; + protected void setDirectoryFooterLine(String line) { + this.directoryFooterLine = line; + } + protected static RelayNetworkStatusConsensus + createWithDirectoryFooterLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.directoryFooterLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private String bandwidthWeightsLine = "bandwidth-weights Wbd=285 " + + "Wbe=0 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=1021 Wee=10000 " + + "Weg=1021 Wem=10000 Wgb=10000 Wgd=8694 Wgg=10000 Wgm=10000 " + + "Wmb=10000 Wmd=285 Wme=0 Wmg=0 Wmm=10000"; + protected void setBandwidthWeightsLine(String line) { + this.bandwidthWeightsLine = line; + } + protected static RelayNetworkStatusConsensus + createWithBandwidthWeightsLine(String line) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.bandwidthWeightsLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + private List<String> directorySignatures = new ArrayList<>(); + protected void addDirectorySignature(String directorySignatureString) { + this.directorySignatures.add(directorySignatureString); + } + private String unrecognizedHeaderLine = null; + protected static RelayNetworkStatusConsensus + createWithUnrecognizedHeaderLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.unrecognizedHeaderLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedDirSourceLine = null; + protected static RelayNetworkStatusConsensus + createWithUnrecognizedDirSourceLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.unrecognizedDirSourceLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedStatusEntryLine = null; + protected static RelayNetworkStatusConsensus + createWithUnrecognizedStatusEntryLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.unrecognizedStatusEntryLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedFooterLine = null; + protected static RelayNetworkStatusConsensus + createWithUnrecognizedFooterLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.unrecognizedFooterLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedDirectorySignatureLine = null; + protected static RelayNetworkStatusConsensus + createWithUnrecognizedDirectorySignatureLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.unrecognizedDirectorySignatureLine = line; + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + failUnrecognizedDescriptorLines); + } + + protected ConsensusBuilder() { + this.dirSources.add("dir-source tor26 " + + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 86.59.21.38 " + + "86.59.21.38 80 443\ncontact Peter Palfrader\nvote-digest " + + "0333880AA67ED7E07C11108656D0C8D6DD1C7E5D"); + this.dirSources.add("dir-source ides " + + "27B6B5996C426270A5C95488AA5BCEB6BCC86956 216.224.124.114 " + + "216.224.124.114 9030 9090\ncontact Mike Perry " + + "<mikeperryTAfsckedTODorg>\nvote-digest " + + "1A8827ECD53184F7A771EFA9B3D30DC473FE8670"); + this.statusEntries.add("r ANONIONROUTER " + + "AHhuQ8zFQJdT8l42Axxc6m6kNwI yEMZ5B/JQixNZgC1+2rLe0pR9rU " + + "2011-11-30 02:52:58 93.128.66.111 24051 24052\ns Exit Fast " + + "Named Running V2Dir Valid\nv Tor 0.2.2.34\nw " + + "Bandwidth=1100\np reject 25,119,135-139,6881-6999"); + this.statusEntries.add("r Magellan AHlabo2RwnD8I7MPOIpJVVPgGJQ " + + "rB/7uzI4mU38bZ9cSXEy+Z/4Cuk 2011-11-30 05:37:35 " + + "188.177.149.216 9001 9030\ns Fast Named Running V2Dir " + + "Valid\nv Tor 0.2.2.34\nw Bandwidth=367\np reject 1-65535"); + this.directorySignatures.add("directory-signature " + + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " + + "3509BA5A624403A905C74DA5C8A0CEC9E0D3AF86\n" + + "-----BEGIN SIGNATURE-----\n" + + "NYRcTWAMRiYYiGW0hIbzeZKU6sefg98AwwXrQUCudO8wfA1cfgttTDoscB9I" + + "TbOY\nr+c30jV/qQCMamTAEDGgJTw8KghI32vytupKallI1EjCOF8UvL1UnA" + + "LgpaR7sZ3W\n7WQZVVrWDtnYaULOEKfwnGnRC7WwE+YRSysbzwwCVs0=\n" + + "-----END SIGNATURE-----"); + this.directorySignatures.add("directory-signature " + + "27B6B5996C426270A5C95488AA5BCEB6BCC86956 " + + "D5C30C15BB3F1DA27669C2D88439939E8F418FCF\n" + + "-----BEGIN SIGNATURE-----\n" + + "DzFPj3vyYrCv0W3r8qDPJPlmeLnadY+drjWkdOqO66Ih/hAWBb9KcBJAX1sX" + + "aDA7\n/iSaDhduBXuJdcu8lbmMP8d6uYBdRjHXqWDXySUZAkSfPB4JJPNGvf" + + "oQA/qeby7E\n5374pPPL6WwCLJHkKtk21S9oHDmFBdlZq7JWQelWlVM=\n" + + "-----END SIGNATURE-----"); + } + protected byte[] buildConsensus() { + StringBuilder sb = new StringBuilder(); + this.appendHeader(sb); + this.appendDirSources(sb); + this.appendStatusEntries(sb); + this.appendFooter(sb); + this.appendDirectorySignatures(sb); + return sb.toString().getBytes(); + } + private void appendHeader(StringBuilder sb) { + if (this.networkStatusVersionLine != null) { + sb.append(this.networkStatusVersionLine).append("\n"); + } + if (this.voteStatusLine != null) { + sb.append(this.voteStatusLine).append("\n"); + } + if (this.consensusMethodLine != null) { + sb.append(this.consensusMethodLine).append("\n"); + } + if (this.validAfterLine != null) { + sb.append(this.validAfterLine).append("\n"); + } + if (this.freshUntilLine != null) { + sb.append(this.freshUntilLine).append("\n"); + } + if (this.validUntilLine != null) { + sb.append(this.validUntilLine).append("\n"); + } + if (this.votingDelayLine != null) { + sb.append(this.votingDelayLine).append("\n"); + } + if (this.clientVersionsLine != null) { + sb.append(this.clientVersionsLine).append("\n"); + } + if (this.serverVersionsLine != null) { + sb.append(this.serverVersionsLine).append("\n"); + } + if (this.packageLines != null) { + sb.append(this.packageLines).append("\n"); + } + if (this.knownFlagsLine != null) { + sb.append(this.knownFlagsLine).append("\n"); + } + if (this.paramsLine != null) { + sb.append(this.paramsLine).append("\n"); + } + if (this.unrecognizedHeaderLine != null) { + sb.append(this.unrecognizedHeaderLine).append("\n"); + } + } + private void appendDirSources(StringBuilder sb) { + for (String dirSource : this.dirSources) { + sb.append(dirSource).append("\n"); + } + if (this.unrecognizedDirSourceLine != null) { + sb.append(this.unrecognizedDirSourceLine).append("\n"); + } + } + private void appendStatusEntries(StringBuilder sb) { + for (String statusEntry : this.statusEntries) { + sb.append(statusEntry).append("\n"); + } + if (this.unrecognizedStatusEntryLine != null) { + sb.append(this.unrecognizedStatusEntryLine).append("\n"); + } + } + private void appendFooter(StringBuilder sb) { + if (this.directoryFooterLine != null) { + sb.append(this.directoryFooterLine).append("\n"); + } + if (this.bandwidthWeightsLine != null) { + sb.append(this.bandwidthWeightsLine).append("\n"); + } + if (this.unrecognizedFooterLine != null) { + sb.append(this.unrecognizedFooterLine).append("\n"); + } + } + private void appendDirectorySignatures(StringBuilder sb) { + for (String directorySignature : this.directorySignatures) { + sb.append(directorySignature).append("\n"); + } + if (this.unrecognizedDirectorySignatureLine != null) { + sb.append(this.unrecognizedDirectorySignatureLine).append("\n"); + } + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java b/src/test/java/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java new file mode 100644 index 0000000..fde8e57 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java @@ -0,0 +1,134 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.SortedMap; + +import org.junit.Test; + +public class DescriptorCollectorImplTest { + + private static final String REMOTE_DIRECTORY_CONSENSUSES = + "/recent/relay-descriptors/consensuses/"; + + @Test() + public void testOneFile() { + String remoteFilename = "2015-05-24-12-00-00-consensus"; + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" + + "<a href="" + remoteFilename + "">" + + "2015-05-24-12-00-00-consensus</a></td>" + + "<td align="right">24-May-2015 12:08 </td>" + + "<td align="right">1.5M</td><td> </td></tr>"; + SortedMap<String, Long> remoteFiles = + new DescriptorCollectorImpl().parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNotNull(remoteFiles); + assertSame(1, remoteFiles.size()); + assertEquals(REMOTE_DIRECTORY_CONSENSUSES + remoteFilename, + remoteFiles.firstKey()); + assertEquals((Long) 1432469280000L, + remoteFiles.get(remoteFiles.firstKey())); + } + + @Test() + public void testSameFileTwoTimestampsLastWins() { + String remoteFilename = "2015-05-24-12-00-00-consensus"; + String firstTimestamp = "24-May-2015 12:04"; + String secondTimestamp = "24-May-2015 12:08"; + String lineFormat = "<tr><td valign="top">" + + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" + + "<a href="%s">2015-05-24-12-00-00-consensus</a></td>" + + "<td align="right">%s </td>" + + "<td align="right">1.5M</td><td> </td></tr>\n"; + String directoryListing = String.format(lineFormat + lineFormat, + remoteFilename, firstTimestamp, remoteFilename, secondTimestamp); + SortedMap<String, Long> remoteFiles = + new DescriptorCollectorImpl().parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNotNull(remoteFiles); + assertSame(1, remoteFiles.size()); + assertEquals(REMOTE_DIRECTORY_CONSENSUSES + remoteFilename, + remoteFiles.firstKey()); + assertEquals((Long) 1432469280000L, + remoteFiles.get(remoteFiles.firstKey())); + } + + @Test() + public void testSubDirectoryOnly() { + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/folder.gif" alt="[DIR]"></td><td>" + + "<a href="subdir/">subdir/</a></td>" + + "<td align="right">27-May-2015 14:07 </td>" + + "<td align="right"> - </td><td> </td></tr>"; + DescriptorCollectorImpl collector = new DescriptorCollectorImpl(); + SortedMap<String, Long> remoteFiles = collector.parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNotNull(remoteFiles); + assertTrue(remoteFiles.isEmpty()); + } + + @Test() + public void testParentDirectoryOnly() { + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/back.gif" alt="[DIR]"></td><td>" + + "<a href="/recent/relay-descriptors/">Parent Directory</a>" + + "</td><td> </td><td align="right"> - </td>" + + "<td> </td></tr>"; + DescriptorCollectorImpl collector = new DescriptorCollectorImpl(); + SortedMap<String, Long> remoteFiles = collector.parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNotNull(remoteFiles); + assertTrue(remoteFiles.isEmpty()); + } + + @Test() + public void testUnexpectedDateFormat() { + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" + + "<a href="2015-05-24-12-00-00-consensus">" + + "2015-05-24-12-00-00-consensus</a></td>" + + "<td align="right">2015-05-24 12:08 </td>" + + "<td align="right">1.5M</td><td> </td></tr>"; + SortedMap<String, Long> remoteFiles = + new DescriptorCollectorImpl().parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNotNull(remoteFiles); + assertTrue(remoteFiles.isEmpty()); + } + + @Test() + public void testInvalidDate() { + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" + + "<a href="2015-05-24-12-00-00-consensus">" + + "2015-05-24-12-00-00-consensus</a></td>" + + "<td align="right">34-May-2015 12:08 </td>" + + "<td align="right">1.5M</td><td> </td></tr>"; + SortedMap<String, Long> remoteFiles = + new DescriptorCollectorImpl().parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNull(remoteFiles); + } + + @Test() + public void testInvalidLocaleDe() { + String directoryListing = "<tr><td valign="top">" + + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" + + "<a href="2015-05-24-12-00-00-consensus">" + + "2015-05-24-12-00-00-consensus</a></td>" + + "<td align="right">24-Mai-2015 12:08 </td>" + + "<td align="right">1.5M</td><td> </td></tr>"; + SortedMap<String, Long> remoteFiles = + new DescriptorCollectorImpl().parseDirectoryListing( + REMOTE_DIRECTORY_CONSENSUSES, directoryListing); + assertNull(remoteFiles); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java b/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java new file mode 100644 index 0000000..a563857 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/ExitListImplTest.java @@ -0,0 +1,131 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExitListEntry; + +public class ExitListImplTest { + + @Test() + public void testAnnotatedInput() throws Exception { + ExitListImpl result = new ExitListImpl((tordnselAnnotation + input) + .getBytes("US-ASCII"), fileName, false); + assertEquals("Expected one annotation.", 1, + result.getAnnotations().size()); + assertEquals(tordnselAnnotation.substring(0, 18), + result.getAnnotations().get(0)); + assertEquals(1441065722000L, result.getDownloadedMillis()); + assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), + result.getUnrecognizedLines().isEmpty()); + assertEquals("Found: " + result.getExitListEntries(), 7, + result.getExitListEntries().size()); + assertEquals("Found: " + result.getEntries(), 5, + result.getEntries().size()); + } + + @Test() + public void testMultipleOldExitAddresses() throws Exception { + ExitListImpl result = new ExitListImpl( + (tordnselAnnotation + multiExitAddressInput) + .getBytes("US-ASCII"), fileName, false); + assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), + result.getUnrecognizedLines().isEmpty()); + assertEquals("Found: " + result.getExitListEntries(), + 3, result.getExitListEntries().size()); + Map<String, Long> testMap = new HashMap(); + testMap.put("81.7.17.171", 1441044592000L); + testMap.put("81.7.17.172", 1441044652000L); + testMap.put("81.7.17.173", 1441044712000L); + for (ExitListEntry ele : result.getExitListEntries()) { + Map<String, Long> map = ele.getExitAddresses(); + assertEquals("Found: " + map, 1, map.size()); + Map.Entry<String, Long> ea = map.entrySet().iterator().next(); + assertTrue("Map: " + testMap, + testMap.keySet().contains(ea.getKey())); + assertTrue("Map: " + testMap + " exitaddress: " + ea, + testMap.values().contains(ea.getValue())); + testMap.remove(ea.getKey()); + } + assertTrue("Map: " + testMap, testMap.isEmpty()); + } + + @Test() + public void testMultipleExitAddresses() throws Exception { + ExitListImpl result = new ExitListImpl( + (tordnselAnnotation + multiExitAddressInput) + .getBytes("US-ASCII"), fileName, false); + assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), + result.getUnrecognizedLines().isEmpty()); + Map<String, Long> map = result.getEntries() + .iterator().next().getExitAddresses(); + assertEquals("Found: " + map, 3, map.size()); + assertTrue("Map: " + map, map.containsKey("81.7.17.171")); + assertTrue("Map: " + map, map.containsKey("81.7.17.172")); + assertTrue("Map: " + map, map.containsKey("81.7.17.173")); + } + + @Test(expected = DescriptorParseException.class) + public void testInsufficientInput0() throws Exception { + new ExitListImpl((tordnselAnnotation + insufficientInput[0]) + .getBytes("US-ASCII"), fileName, false); + } + + @Test(expected = DescriptorParseException.class) + public void testInsufficientInput1() throws Exception { + new ExitListImpl((tordnselAnnotation + insufficientInput[1]) + .getBytes("US-ASCII"), fileName, false); + } + + private static final String tordnselAnnotation = "@type tordnsel 1.0\n"; + private static final String fileName = "2015-09-01-00-02-02"; + private static final String[] insufficientInput = new String[] { + "Downloaded 2015-09-01 00:02:02\n" + + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" + + "Published 2015-08-31 16:17:30\n" + + "LastStatus 2015-08-31 17:03:18\n", + "Downloaded 2015-09-01 00:02:02\n" + + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" + + "LastStatus 2015-08-31 17:03:18\n" + + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" }; + + private static final String multiExitAddressInput = + "Downloaded 2015-09-01 00:02:02\n" + + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" + + "Published 2015-08-31 16:17:30\n" + + "LastStatus 2015-08-31 17:03:18\n" + + "ExitAddress 81.7.17.171 2015-08-31 18:09:52\n" + + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" + + "ExitAddress 81.7.17.173 2015-08-31 18:11:52\n"; + private static final String input = "Downloaded 2015-09-01 00:02:02\n" + + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" + + "Published 2015-08-31 16:17:30\n" + + "LastStatus 2015-08-31 17:03:18\n" + + "ExitAddress 162.247.72.201 2015-08-31 17:09:23\n" + + "ExitNode 0098C475875ABC4AA864738B1D1079F711C38287\n" + + "Published 2015-08-31 13:59:24\n" + + "LastStatus 2015-08-31 15:03:20\n" + + "ExitAddress 162.248.160.151 2015-08-31 15:07:27\n" + + "ExitNode 00C4B4731658D3B4987132A3F77100CFCB190D97\n" + + "Published 2015-08-31 17:47:52\n" + + "LastStatus 2015-08-31 18:03:17\n" + + "ExitAddress 81.7.17.171 2015-08-31 18:09:52\n" + + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" + + "ExitAddress 81.7.17.173 2015-08-31 18:11:52\n" + + "ExitNode 00F2D93EBAF2F51D6EE4DCB0F37D91D72F824B16\n" + + "Published 2015-08-31 14:39:05\n" + + "LastStatus 2015-08-31 16:02:18\n" + + "ExitAddress 23.239.18.57 2015-08-31 16:06:07\n" + + "ExitNode 011B1D1E876B2C835D01FB9D407F2E00B28077F6\n" + + "Published 2015-08-31 05:14:35\n" + + "LastStatus 2015-08-31 06:03:29\n" + + "ExitAddress 104.131.51.150 2015-08-31 06:04:07\n"; +} + diff --git a/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java new file mode 100644 index 0000000..6843196 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java @@ -0,0 +1,1737 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; + +import org.junit.Test; +import org.torproject.descriptor.BridgeExtraInfoDescriptor; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.ExtraInfoDescriptor; +import org.torproject.descriptor.RelayExtraInfoDescriptor; + +/* Test parsing of extra-info descriptors. */ +public class ExtraInfoDescriptorImplTest { + + /* Helper class to build a descriptor based on default data and + * modifications requested by test methods. */ + private static class DescriptorBuilder { + private String extraInfoLine = "extra-info chaoscomputerclub5 " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"; + private static ExtraInfoDescriptor createWithExtraInfoLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.extraInfoLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String publishedLine = "published 2012-02-11 09:08:36"; + private static ExtraInfoDescriptor createWithPublishedLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.publishedLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String writeHistoryLine = "write-history 2012-02-11 09:03:39 " + + "(900 s) 4713350144,4723824640,4710717440,4572675072"; + private static ExtraInfoDescriptor createWithWriteHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.writeHistoryLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String readHistoryLine = "read-history 2012-02-11 09:03:39 " + + "(900 s) 4707695616,4699666432,4650004480,4489718784"; + private static ExtraInfoDescriptor createWithReadHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.readHistoryLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String dirreqWriteHistoryLine = "dirreq-write-history " + + "2012-02-11 09:03:39 (900 s) 81281024,64996352,60625920," + + "67922944"; + private static ExtraInfoDescriptor createWithDirreqWriteHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.dirreqWriteHistoryLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String dirreqReadHistoryLine = "dirreq-read-history " + + "2012-02-11 09:03:39 (900 s) 17074176,16235520,16005120," + + "16209920"; + private static ExtraInfoDescriptor createWithDirreqReadHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.dirreqReadHistoryLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String geoipDbDigestLine = null; + private static ExtraInfoDescriptor createWithGeoipDbDigestLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.geoipDbDigestLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String geoip6DbDigestLine = null; + private static ExtraInfoDescriptor createWithGeoip6DbDigestLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.geoip6DbDigestLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String geoipStatsLines = null; + private static ExtraInfoDescriptor createWithGeoipStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.geoipStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String dirreqStatsLines = null; + private static ExtraInfoDescriptor createWithDirreqStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.dirreqStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String entryStatsLines = null; + private static ExtraInfoDescriptor createWithEntryStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.entryStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String cellStatsLines = null; + private static ExtraInfoDescriptor createWithCellStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.cellStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String connBiDirectLine = null; + private static ExtraInfoDescriptor createWithConnBiDirectLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.connBiDirectLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String exitStatsLines = null; + private static ExtraInfoDescriptor createWithExitStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.exitStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String bridgeStatsLines = null; + private static ExtraInfoDescriptor createWithBridgeStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.bridgeStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String hidservStatsLines = null; + private static ExtraInfoDescriptor createWithHidservStatsLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.hidservStatsLines = lines; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String unrecognizedLine = null; + private static ExtraInfoDescriptor createWithUnrecognizedLine( + String line, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.unrecognizedLine = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), + failUnrecognizedDescriptorLines); + } + private byte[] nonAsciiLineBytes = null; + private static ExtraInfoDescriptor createWithNonAsciiLineBytes( + byte[] lineBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.nonAsciiLineBytes = lineBytes; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), + failUnrecognizedDescriptorLines); + } + private String routerSignatureLines = "router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" + + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" + + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" + + "-----END SIGNATURE-----"; + private static ExtraInfoDescriptor createWithRouterSignatureLines( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.routerSignatureLines = line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private String identityEd25519Lines = null, + masterKeyEd25519Line = null, routerSigEd25519Line = null; + private static ExtraInfoDescriptor createWithEd25519Lines( + String identityEd25519Lines, String masterKeyEd25519Line, + String routerSigEd25519Line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.identityEd25519Lines = identityEd25519Lines; + db.masterKeyEd25519Line = masterKeyEd25519Line; + db.routerSigEd25519Line = routerSigEd25519Line; + return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + } + private byte[] buildDescriptor() { + StringBuilder sb = new StringBuilder(); + if (this.extraInfoLine != null) { + sb.append(this.extraInfoLine).append("\n"); + } + if (this.identityEd25519Lines != null) { + sb.append(this.identityEd25519Lines).append("\n"); + } + if (this.masterKeyEd25519Line != null) { + sb.append(this.masterKeyEd25519Line).append("\n"); + } + if (this.publishedLine != null) { + sb.append(this.publishedLine).append("\n"); + } + if (this.writeHistoryLine != null) { + sb.append(this.writeHistoryLine).append("\n"); + } + if (this.readHistoryLine != null) { + sb.append(this.readHistoryLine).append("\n"); + } + if (this.dirreqWriteHistoryLine != null) { + sb.append(this.dirreqWriteHistoryLine).append("\n"); + } + if (this.dirreqReadHistoryLine != null) { + sb.append(this.dirreqReadHistoryLine).append("\n"); + } + if (this.geoipDbDigestLine != null) { + sb.append(this.geoipDbDigestLine).append("\n"); + } + if (this.geoip6DbDigestLine != null) { + sb.append(this.geoip6DbDigestLine).append("\n"); + } + if (this.geoipStatsLines != null) { + sb.append(this.geoipStatsLines).append("\n"); + } + if (this.dirreqStatsLines != null) { + sb.append(this.dirreqStatsLines).append("\n"); + } + if (this.entryStatsLines != null) { + sb.append(this.entryStatsLines).append("\n"); + } + if (this.cellStatsLines != null) { + sb.append(this.cellStatsLines).append("\n"); + } + if (this.connBiDirectLine != null) { + sb.append(this.connBiDirectLine).append("\n"); + } + if (this.exitStatsLines != null) { + sb.append(this.exitStatsLines).append("\n"); + } + if (this.bridgeStatsLines != null) { + sb.append(this.bridgeStatsLines).append("\n"); + } + if (this.hidservStatsLines != null) { + sb.append(this.hidservStatsLines).append("\n"); + } + if (this.unrecognizedLine != null) { + sb.append(this.unrecognizedLine).append("\n"); + } + if (this.nonAsciiLineBytes != null) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(sb.toString().getBytes()); + baos.write(this.nonAsciiLineBytes); + baos.write("\n".getBytes()); + if (this.routerSignatureLines != null) { + baos.write(this.routerSignatureLines.getBytes()); + } + return baos.toByteArray(); + } catch (IOException e) { + return null; + } + } + if (this.routerSigEd25519Line != null) { + sb.append(this.routerSigEd25519Line).append("\n"); + } + if (this.routerSignatureLines != null) { + sb.append(this.routerSignatureLines).append("\n"); + } + return sb.toString().getBytes(); + } + } + + /* Helper class to build a set of geoip-stats lines based on default + * data and modifications requested by test methods. */ + private static class GeoipStatsBuilder { + private String geoipStartTimeLine = "geoip-start-time 2012-02-10 " + + "18:32:51"; + private static ExtraInfoDescriptor createWithGeoipStartTimeLine( + String line) throws DescriptorParseException { + GeoipStatsBuilder gsb = new GeoipStatsBuilder(); + gsb.geoipStartTimeLine = line; + return DescriptorBuilder.createWithGeoipStatsLines( + gsb.buildGeoipStatsLines()); + } + private String geoipClientOriginsLine = "geoip-client-origins " + + "de=1152,cn=896,us=712,it=504,ru=352,fr=208,gb=208,ir=200"; + private static ExtraInfoDescriptor createWithGeoipClientOriginsLine( + String line) throws DescriptorParseException { + GeoipStatsBuilder gsb = new GeoipStatsBuilder(); + gsb.geoipClientOriginsLine = line; + return DescriptorBuilder.createWithGeoipStatsLines( + gsb.buildGeoipStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithGeoipStatsLines( + new GeoipStatsBuilder().buildGeoipStatsLines()); + } + private String buildGeoipStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.geoipStartTimeLine != null) { + sb.append(this.geoipStartTimeLine).append("\n"); + } + if (this.geoipClientOriginsLine != null) { + sb.append(this.geoipClientOriginsLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of dirreq-stats lines based on default + * data and modifications requested by test methods. */ + private static class DirreqStatsBuilder { + private String dirreqStatsEndLine = "dirreq-stats-end 2012-02-11 " + + "00:59:53 (86400 s)"; + private static ExtraInfoDescriptor createWithDirreqStatsEndLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqStatsEndLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3IpsLine = "dirreq-v3-ips us=1544,de=1056," + + "it=1032,fr=784,es=640,ru=440,br=312,gb=272,kr=224,sy=192"; + private static ExtraInfoDescriptor createWithDirreqV3IpsLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3IpsLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2IpsLine = "dirreq-v2-ips "; + private static ExtraInfoDescriptor createWithDirreqV2IpsLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2IpsLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3ReqsLine = "dirreq-v3-reqs us=1744,de=1224," + + "it=1080,fr=832,es=664,ru=536,br=344,gb=296,kr=272,in=216"; + private static ExtraInfoDescriptor createWithDirreqV3ReqsLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3ReqsLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2ReqsLine = "dirreq-v2-reqs "; + private static ExtraInfoDescriptor createWithDirreqV2ReqsLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2ReqsLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3RespLine = "dirreq-v3-resp ok=10848," + + "not-enough-sigs=8,unavailable=0,not-found=0,not-modified=0," + + "busy=80"; + private static ExtraInfoDescriptor createWithDirreqV3RespLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3RespLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2RespLine = "dirreq-v2-resp ok=0,unavailable=0," + + "not-found=1576,not-modified=0,busy=0"; + private static ExtraInfoDescriptor createWithDirreqV2RespLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2RespLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2ShareLine = "dirreq-v2-share 0.37%"; + private static ExtraInfoDescriptor createWithDirreqV2ShareLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2ShareLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3ShareLine = "dirreq-v3-share 0.37%"; + private static ExtraInfoDescriptor createWithDirreqV3ShareLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3ShareLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3DirectDlLine = "dirreq-v3-direct-dl " + + "complete=36,timeout=4,running=0,min=7538,d1=20224,d2=28950," + + "q1=40969,d3=55786,d4=145813,md=199164,d6=267230,d7=480900," + + "q3=481049,d8=531276,d9=778086,max=15079428"; + private static ExtraInfoDescriptor createWithDirreqV3DirectDlLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3DirectDlLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2DirectDlLine = "dirreq-v2-direct-dl " + + "complete=0,timeout=0,running=0"; + private static ExtraInfoDescriptor createWithDirreqV2DirectDlLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2DirectDlLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV3TunneledDlLine = "dirreq-v3-tunneled-dl " + + "complete=10608,timeout=204,running=4,min=507,d1=20399," + + "d2=27588,q1=29292,d3=30889,d4=40624,md=59967,d6=103333," + + "d7=161170,q3=209415,d8=256711,d9=452503,max=23417777"; + private static ExtraInfoDescriptor createWithDirreqV3TunneledDlLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV3TunneledDlLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private String dirreqV2TunneledDlLine = "dirreq-v2-tunneled-dl " + + "complete=0,timeout=0,running=0"; + private static ExtraInfoDescriptor createWithDirreqV2TunneledDlLine( + String line) throws DescriptorParseException { + DirreqStatsBuilder dsb = new DirreqStatsBuilder(); + dsb.dirreqV2TunneledDlLine = line; + return DescriptorBuilder.createWithDirreqStatsLines( + dsb.buildDirreqStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithDirreqStatsLines( + new DirreqStatsBuilder().buildDirreqStatsLines()); + } + private String buildDirreqStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.dirreqStatsEndLine != null) { + sb.append(this.dirreqStatsEndLine).append("\n"); + } + if (this.dirreqV3IpsLine != null) { + sb.append(this.dirreqV3IpsLine).append("\n"); + } + if (this.dirreqV2IpsLine != null) { + sb.append(this.dirreqV2IpsLine).append("\n"); + } + if (this.dirreqV3ReqsLine != null) { + sb.append(this.dirreqV3ReqsLine).append("\n"); + } + if (this.dirreqV2ReqsLine != null) { + sb.append(this.dirreqV2ReqsLine).append("\n"); + } + if (this.dirreqV3RespLine != null) { + sb.append(this.dirreqV3RespLine).append("\n"); + } + if (this.dirreqV2RespLine != null) { + sb.append(this.dirreqV2RespLine).append("\n"); + } + if (this.dirreqV2ShareLine != null) { + sb.append(this.dirreqV2ShareLine).append("\n"); + } + if (this.dirreqV3ShareLine != null) { + sb.append(this.dirreqV3ShareLine).append("\n"); + } + if (this.dirreqV3DirectDlLine != null) { + sb.append(this.dirreqV3DirectDlLine).append("\n"); + } + if (this.dirreqV2DirectDlLine != null) { + sb.append(this.dirreqV2DirectDlLine).append("\n"); + } + if (this.dirreqV3TunneledDlLine != null) { + sb.append(this.dirreqV3TunneledDlLine).append("\n"); + } + if (this.dirreqV2TunneledDlLine != null) { + sb.append(this.dirreqV2TunneledDlLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of entry-stats lines based on default + * data and modifications requested by test methods. */ + private static class EntryStatsBuilder { + private String entryStatsEndLine = "entry-stats-end 2012-02-11 " + + "01:59:39 (86400 s)"; + private static ExtraInfoDescriptor createWithEntryStatsEndLine( + String line) throws DescriptorParseException { + EntryStatsBuilder esb = new EntryStatsBuilder(); + esb.entryStatsEndLine = line; + return DescriptorBuilder.createWithEntryStatsLines( + esb.buildEntryStatsLines()); + } + private String entryIpsLine = "entry-ips ir=25368,us=15744,it=14816," + + "de=13256,es=8280,fr=8120,br=5176,sy=4760,ru=4504,sa=4216," + + "gb=3152,pl=2928,nl=2208,kr=1856,ca=1792,ua=1272,in=1192"; + private static ExtraInfoDescriptor createWithEntryIpsLine( + String line) throws DescriptorParseException { + EntryStatsBuilder esb = new EntryStatsBuilder(); + esb.entryIpsLine = line; + return DescriptorBuilder.createWithEntryStatsLines( + esb.buildEntryStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithEntryStatsLines( + new EntryStatsBuilder().buildEntryStatsLines()); + } + private String buildEntryStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.entryStatsEndLine != null) { + sb.append(this.entryStatsEndLine).append("\n"); + } + if (this.entryIpsLine != null) { + sb.append(this.entryIpsLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of cell-stats lines based on default + * data and modifications requested by test methods. */ + private static class CellStatsBuilder { + private String cellStatsEndLine = "cell-stats-end 2012-02-11 " + + "01:59:39 (86400 s)"; + private static ExtraInfoDescriptor createWithCellStatsEndLine( + String line) throws DescriptorParseException { + CellStatsBuilder csb = new CellStatsBuilder(); + csb.cellStatsEndLine = line; + return DescriptorBuilder.createWithCellStatsLines( + csb.buildCellStatsLines()); + } + private String cellProcessedCellsLine = "cell-processed-cells " + + "1441,11,6,4,2,1,1,1,1,1"; + private static ExtraInfoDescriptor createWithCellProcessedCellsLine( + String line) throws DescriptorParseException { + CellStatsBuilder csb = new CellStatsBuilder(); + csb.cellProcessedCellsLine = line; + return DescriptorBuilder.createWithCellStatsLines( + csb.buildCellStatsLines()); + } + private String cellQueuedCellsLine = "cell-queued-cells " + + "3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00"; + private static ExtraInfoDescriptor createWithCellQueuedCellsLine( + String line) throws DescriptorParseException { + CellStatsBuilder csb = new CellStatsBuilder(); + csb.cellQueuedCellsLine = line; + return DescriptorBuilder.createWithCellStatsLines( + csb.buildCellStatsLines()); + } + private String cellTimeInQueueLine = "cell-time-in-queue " + + "524,1,1,0,0,25,0,0,0,0"; + private static ExtraInfoDescriptor createWithCellTimeInQueueLine( + String line) throws DescriptorParseException { + CellStatsBuilder csb = new CellStatsBuilder(); + csb.cellTimeInQueueLine = line; + return DescriptorBuilder.createWithCellStatsLines( + csb.buildCellStatsLines()); + } + private String cellCircuitsPerDecileLine = "cell-circuits-per-decile " + + "866"; + private static ExtraInfoDescriptor + createWithCellCircuitsPerDecileLine(String line) + throws DescriptorParseException { + CellStatsBuilder csb = new CellStatsBuilder(); + csb.cellCircuitsPerDecileLine = line; + return DescriptorBuilder.createWithCellStatsLines( + csb.buildCellStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithCellStatsLines( + new CellStatsBuilder().buildCellStatsLines()); + } + private String buildCellStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.cellStatsEndLine != null) { + sb.append(this.cellStatsEndLine).append("\n"); + } + if (this.cellProcessedCellsLine != null) { + sb.append(this.cellProcessedCellsLine).append("\n"); + } + if (this.cellQueuedCellsLine != null) { + sb.append(this.cellQueuedCellsLine).append("\n"); + } + if (this.cellTimeInQueueLine != null) { + sb.append(this.cellTimeInQueueLine).append("\n"); + } + if (this.cellCircuitsPerDecileLine != null) { + sb.append(this.cellCircuitsPerDecileLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of exit-stats lines based on default + * data and modifications requested by test methods. */ + private static class ExitStatsBuilder { + private String exitStatsEndLine = "exit-stats-end 2012-02-11 " + + "01:59:39 (86400 s)"; + private static ExtraInfoDescriptor createWithExitStatsEndLine( + String line) throws DescriptorParseException { + ExitStatsBuilder esb = new ExitStatsBuilder(); + esb.exitStatsEndLine = line; + return DescriptorBuilder.createWithExitStatsLines( + esb.buildExitStatsLines()); + } + private String exitKibibytesWrittenLine = "exit-kibibytes-written " + + "25=74647,80=31370,443=20577,49755=23,52563=12,52596=1111," + + "57528=4,60912=11,61351=6,64811=3365,other=2592"; + private static ExtraInfoDescriptor createWithExitKibibytesWrittenLine( + String line) throws DescriptorParseException { + ExitStatsBuilder esb = new ExitStatsBuilder(); + esb.exitKibibytesWrittenLine = line; + return DescriptorBuilder.createWithExitStatsLines( + esb.buildExitStatsLines()); + } + private String exitKibibytesReadLine = "exit-kibibytes-read " + + "25=35562,80=1254256,443=110279,49755=9396,52563=1911," + + "52596=648,57528=1188,60912=1427,61351=1824,64811=14," + + "other=3054"; + private static ExtraInfoDescriptor createWithExitKibibytesReadLine( + String line) throws DescriptorParseException { + ExitStatsBuilder esb = new ExitStatsBuilder(); + esb.exitKibibytesReadLine = line; + return DescriptorBuilder.createWithExitStatsLines( + esb.buildExitStatsLines()); + } + private String exitStreamsOpenedLine = "exit-streams-opened " + + "25=369748,80=64212,443=151660,49755=4,52563=4,52596=4,57528=4," + + "60912=4,61351=4,64811=4,other=1212"; + private static ExtraInfoDescriptor createWithExitStreamsOpenedLine( + String line) throws DescriptorParseException { + ExitStatsBuilder esb = new ExitStatsBuilder(); + esb.exitStreamsOpenedLine = line; + return DescriptorBuilder.createWithExitStatsLines( + esb.buildExitStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithExitStatsLines( + new ExitStatsBuilder().buildExitStatsLines()); + } + private String buildExitStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.exitStatsEndLine != null) { + sb.append(this.exitStatsEndLine).append("\n"); + } + if (this.exitKibibytesWrittenLine != null) { + sb.append(this.exitKibibytesWrittenLine).append("\n"); + } + if (this.exitKibibytesReadLine != null) { + sb.append(this.exitKibibytesReadLine).append("\n"); + } + if (this.exitStreamsOpenedLine != null) { + sb.append(this.exitStreamsOpenedLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of bridge-stats lines based on default + * data and modifications requested by test methods. */ + private static class BridgeStatsBuilder { + private String bridgeStatsEndLine = "bridge-stats-end 2012-02-11 " + + "01:59:39 (86400 s)"; + private static ExtraInfoDescriptor createWithBridgeStatsEndLine( + String line) throws DescriptorParseException { + BridgeStatsBuilder bsb = new BridgeStatsBuilder(); + bsb.bridgeStatsEndLine = line; + return DescriptorBuilder.createWithBridgeStatsLines( + bsb.buildBridgeStatsLines()); + } + private String bridgeIpsLine = "bridge-ips ir=24,sy=16,??=8,cn=8," + + "de=8,es=8,fr=8,gb=8,in=8,jp=8,kz=8,nl=8,ua=8,us=8,vn=8,za=8"; + private static ExtraInfoDescriptor createWithBridgeIpsLine( + String line) throws DescriptorParseException { + BridgeStatsBuilder bsb = new BridgeStatsBuilder(); + bsb.bridgeIpsLine = line; + return DescriptorBuilder.createWithBridgeStatsLines( + bsb.buildBridgeStatsLines()); + } + private String bridgeIpVersionsLine = "bridge-ip-versions v4=8,v6=16"; + private static ExtraInfoDescriptor createWithBridgeIpVersionsLine( + String line) throws DescriptorParseException { + BridgeStatsBuilder bsb = new BridgeStatsBuilder(); + bsb.bridgeIpVersionsLine = line; + return DescriptorBuilder.createWithBridgeStatsLines( + bsb.buildBridgeStatsLines()); + } + private String bridgeIpTransportsLine = "bridge-ip-transports " + + "<OR>=8,obfs2=792,obfs3=1728"; + private static ExtraInfoDescriptor createWithBridgeIpTransportsLine( + String line) throws DescriptorParseException { + BridgeStatsBuilder bsb = new BridgeStatsBuilder(); + bsb.bridgeIpTransportsLine = line; + return DescriptorBuilder.createWithBridgeStatsLines( + bsb.buildBridgeStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithBridgeStatsLines( + new BridgeStatsBuilder().buildBridgeStatsLines()); + } + private String buildBridgeStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.bridgeStatsEndLine != null) { + sb.append(this.bridgeStatsEndLine).append("\n"); + } + if (this.bridgeIpsLine != null) { + sb.append(this.bridgeIpsLine).append("\n"); + } + if (this.bridgeIpVersionsLine != null) { + sb.append(this.bridgeIpVersionsLine).append("\n"); + } + if (this.bridgeIpTransportsLine != null) { + sb.append(this.bridgeIpTransportsLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + /* Helper class to build a set of hidserv-stats lines based on default + * data and modifications requested by test methods. */ + private static class HidservStatsBuilder { + private String hidservStatsEndLine = "hidserv-stats-end 2015-12-03 " + + "14:26:56 (86400 s)"; + private static ExtraInfoDescriptor createWithHidservStatsEndLine( + String line) throws DescriptorParseException { + HidservStatsBuilder hsb = new HidservStatsBuilder(); + hsb.hidservStatsEndLine = line; + return DescriptorBuilder.createWithHidservStatsLines( + hsb.buildHidservStatsLines()); + } + private String hidservRendRelayedCellsLine = + "hidserv-rend-relayed-cells 36474281 delta_f=2048 epsilon=0.30 " + + "bin_size=1024"; + private static ExtraInfoDescriptor + createWithHidservRendRelayedCellsLine(String line) + throws DescriptorParseException { + HidservStatsBuilder hsb = new HidservStatsBuilder(); + hsb.hidservRendRelayedCellsLine = line; + return DescriptorBuilder.createWithHidservStatsLines( + hsb.buildHidservStatsLines()); + } + private String hidservDirOnionsSeenLine = "hidserv-dir-onions-seen " + + "-3 delta_f=8 epsilon=0.30 bin_size=8"; + private static ExtraInfoDescriptor createWithHidservDirOnionsSeenLine( + String line) throws DescriptorParseException { + HidservStatsBuilder hsb = new HidservStatsBuilder(); + hsb.hidservDirOnionsSeenLine = line; + return DescriptorBuilder.createWithHidservStatsLines( + hsb.buildHidservStatsLines()); + } + private static ExtraInfoDescriptor createWithDefaultLines() + throws DescriptorParseException { + return DescriptorBuilder.createWithHidservStatsLines( + new HidservStatsBuilder().buildHidservStatsLines()); + } + private String buildHidservStatsLines() { + StringBuilder sb = new StringBuilder(); + if (this.hidservStatsEndLine != null) { + sb.append(this.hidservStatsEndLine).append("\n"); + } + if (this.hidservRendRelayedCellsLine != null) { + sb.append(this.hidservRendRelayedCellsLine).append("\n"); + } + if (this.hidservDirOnionsSeenLine != null) { + sb.append(this.hidservDirOnionsSeenLine).append("\n"); + } + String lines = sb.toString(); + if (lines.endsWith("\n")) { + lines = lines.substring(0, lines.length() - 1); + } + return lines; + } + } + + @Test() + public void testSampleDescriptor() throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + ExtraInfoDescriptor descriptor = + new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); + assertEquals("chaoscomputerclub5", descriptor.getNickname()); + assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", + descriptor.getFingerprint()); + assertEquals(1328951316000L, descriptor.getPublishedMillis()); + assertNotNull(descriptor.getWriteHistory()); + assertEquals(1328951019000L, descriptor.getWriteHistory(). + getHistoryEndMillis()); + assertEquals(900L, descriptor.getWriteHistory().getIntervalLength()); + assertEquals(4572675072L, (long) descriptor.getWriteHistory(). + getBandwidthValues().get(1328951019000L)); + assertNotNull(descriptor.getReadHistory()); + assertNotNull(descriptor.getDirreqWriteHistory()); + assertNotNull(descriptor.getDirreqReadHistory()); + } + + @Test(expected = DescriptorParseException.class) + public void testExtraInfoLineMissing() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine(null); + } + + @Test() + public void testExtraInfoOpt() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithExtraInfoLine("opt extra-info chaoscomputerclub5 " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + assertEquals("chaoscomputerclub5", descriptor.getNickname()); + assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", + descriptor.getFingerprint()); + } + + @Test() + public void testExtraInfoNicknameTwoSpaces() + throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithExtraInfoLine("opt extra-info chaoscomputerclub5 " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + assertEquals("chaoscomputerclub5", descriptor.getNickname()); + assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", + descriptor.getFingerprint()); + } + + @Test(expected = DescriptorParseException.class) + public void testExtraInfoLineNotFirst() + throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("geoip-db-digest " + + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8\n" + + "extra-info chaoscomputerclub5 " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameMissing() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameInvalidChar() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "chaoscomputerclub% A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameTooLong() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "chaoscomputerclub5ReallyLongNickname " + + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintG() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "chaoscomputerclub5 G9C039A5FD02FCA06303DCFAABE25C5912C63B26"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooShort() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C6"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooLong() throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoLine("extra-info " + + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C63B26" + + "A9C0"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublishedMissing() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine(null); + } + + @Test() + public void testPublishedOpt() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithPublishedLine("opt published 2012-02-11 09:08:36"); + assertEquals(1328951316000L, descriptor.getPublishedMillis()); + } + + @Test() + public void testPublishedMillis() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithPublishedLine("opt published 2012-02-11 09:08:36.123"); + assertEquals(1328951316000L, descriptor.getPublishedMillis()); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryNegativeBytes() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-02-11 09:03:39 (900 s) " + + "-4713350144,-4723824640,-4710717440,-4572675072"); + } + + @Test() + public void testReadHistoryTabInterval() + throws DescriptorParseException { + DescriptorBuilder.createWithReadHistoryLine("read-history " + + "2012-02-11 09:03:39 (900\ts) " + + "4707695616,4699666432,4650004480,4489718784"); + } + + @Test() + public void testReadHistoryTabIntervalBytes() + throws DescriptorParseException { + DescriptorBuilder.createWithReadHistoryLine("read-history " + + "2012-02-11 09:03:39 (900 s)\t" + + "4707695616,4699666432,4650004480,4489718784"); + } + + @Test(expected = DescriptorParseException.class) + public void testReadHistoryNegativeInterval() + throws DescriptorParseException { + DescriptorBuilder.createWithReadHistoryLine("read-history " + + "2012-02-11 09:03:39 (-900 s) " + + "4707695616,4699666432,4650004480,4489718784"); + } + + @Test() + public void testReadHistoryNonStandardInterval() + throws DescriptorParseException { + DescriptorBuilder.createWithReadHistoryLine("read-history " + + "2012-02-11 09:03:39 (1800 s) " + + "4707695616,4699666432,4650004480,4489718784"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqWriteHistoryMissingBytesBegin() + throws DescriptorParseException { + DescriptorBuilder.createWithDirreqWriteHistoryLine( + "dirreq-write-history 2012-02-11 09:03:39 (900 s) " + + ",64996352,60625920,67922944"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqWriteHistoryMissingBytesMiddle() + throws DescriptorParseException { + DescriptorBuilder.createWithDirreqWriteHistoryLine( + "dirreq-write-history 2012-02-11 09:03:39 (900 s) " + + "81281024,,60625920,67922944"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqReadHistoryMissingBytesEnd() + throws DescriptorParseException { + DescriptorBuilder.createWithDirreqReadHistoryLine( + "dirreq-read-history 2012-02-11 09:03:39 (900 s) " + + "17074176,16235520,16005120,"); + } + + @Test() + public void testGeoipDbDigestValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithGeoipDbDigestLine("geoip-db-digest " + + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); + assertEquals("916A3CA8B7DF61473D5AE5B21711F35F301CE9E8", + descriptor.getGeoipDbDigest()); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipDbDigestTooShort() + throws DescriptorParseException { + DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest " + + "916A3CA8B7DF61473D5AE5B21711F35F301C"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipDbDigestIllegalChars() + throws DescriptorParseException { + DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest " + + "&%6A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipDbDigestMissing() + throws DescriptorParseException { + DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest"); + } + + @Test() + public void testGeoip6DbDigestValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithGeoip6DbDigestLine("geoip6-db-digest " + + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); + assertEquals("916A3CA8B7DF61473D5AE5B21711F35F301CE9E8", + descriptor.getGeoip6DbDigest()); + } + + @Test() + public void testGeoipStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = GeoipStatsBuilder. + createWithDefaultLines(); + assertEquals(1328898771000L, descriptor.getGeoipStartTimeMillis()); + SortedMap<String, Integer> ips = descriptor.getGeoipClientOrigins(); + assertNotNull(ips); + assertEquals(1152, ips.get("de").intValue()); + assertEquals(896, ips.get("cn").intValue()); + assertFalse(ips.containsKey("pl")); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipStartTimeDateOnly() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipStartTimeLine("geoip-start-time " + + "2012-02-10"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsDash() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de-1152,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,ir=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsZero() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=zero,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,ir=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsNone() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=none,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,ir=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsOther() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,other=200"); + } + + @Test() + public void testGeoipClientOriginsQuestionMarks() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,??=200"); + } + + @Test() + public void testGeoipClientOriginsCapital() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins DE=1152,CN=896,US=712,IT=504,RU=352,FR=208," + + "GB=208,IR=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsMissingBegin() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins ,cn=896,us=712,it=504,ru=352,fr=208,gb=208," + + "ir=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsMissingMiddle() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=1152,,us=712,it=504,ru=352,fr=208," + + "gb=208,ir=200"); + } + + @Test(expected = DescriptorParseException.class) + public void testGeoipClientOriginsMissingEnd() + throws DescriptorParseException { + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," + + "gb=208,"); + } + + @Test() + public void testGeoipClientOriginsDuplicate() + throws DescriptorParseException { + /* dir-spec.txt doesn't say anything about duplicate country codes, so + * this line is valid, even though it leads to a somewhat undefined + * parse result. */ + GeoipStatsBuilder.createWithGeoipClientOriginsLine( + "geoip-client-origins de=1152,de=952,cn=896,us=712,it=504," + + "ru=352,fr=208,gb=208,ir=200"); + } + + @Test() + public void testDirreqStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DirreqStatsBuilder. + createWithDefaultLines(); + assertEquals(1328921993000L, descriptor.getDirreqStatsEndMillis()); + assertEquals(86400L, descriptor.getDirreqStatsIntervalLength()); + SortedMap<String, Integer> ips = descriptor.getDirreqV3Ips(); + assertNotNull(ips); + assertEquals(1544, ips.get("us").intValue()); + assertFalse(ips.containsKey("no")); + assertTrue(descriptor.getDirreqV2Ips().isEmpty()); + SortedMap<String, Integer> reqs = descriptor.getDirreqV3Reqs(); + assertEquals(832, reqs.get("fr").intValue()); + assertTrue(descriptor.getDirreqV2Reqs().isEmpty()); + SortedMap<String, Integer> resp = descriptor.getDirreqV3Resp(); + assertEquals(10848, resp.get("ok").intValue()); + assertEquals(8, resp.get("not-enough-sigs").intValue()); + resp = descriptor.getDirreqV2Resp(); + assertEquals(1576, resp.get("not-found").intValue()); + assertEquals(0.37, descriptor.getDirreqV2Share(), 0.0001); + assertEquals(0.37, descriptor.getDirreqV3Share(), 0.0001); + SortedMap<String, Integer> dl = descriptor.getDirreqV3DirectDl(); + assertEquals(36, dl.get("complete").intValue()); + dl = descriptor.getDirreqV2DirectDl(); + assertEquals(0, dl.get("timeout").intValue()); + dl = descriptor.getDirreqV3TunneledDl(); + assertEquals(10608, dl.get("complete").intValue()); + dl = descriptor.getDirreqV2TunneledDl(); + assertEquals(0, dl.get("complete").intValue()); + } + + @Test() + public void testDirreqStatsIntervalTwoDays() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqStatsEndLine("dirreq-stats-end " + + "2012-02-11 00:59:53 (172800 s)"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3IpsThreeLetterCountry() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3IpsLine("dirreq-v3-ips " + + "usa=1544"); + } + + @Test() + public void testDirreqV2IpsDigitCountry() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2IpsLine("dirreq-v2-ips 00=8"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3ReqsOneLetterCountry() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3ReqsLine("dirreq-v3-reqs " + + "u=1744"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV2ReqsNoNumber() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2ReqsLine("dirreq-v2-reqs us="); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3RespTwoEqualSigns() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3RespLine("dirreq-v3-resp " + + "ok==10848"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV2RespNull() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2RespLine("dirreq-v2-resp " + + "ok=null"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV2ShareComma() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2ShareLine("dirreq-v2-share " + + "0,37%"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3ShareNoPercent() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3ShareLine("dirreq-v3-share " + + "0.37"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3DirectDlSpace() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3DirectDlLine( + "dirreq-v3-direct-dl complete 36"); + } + + @Test() + public void testDirreqV2DirectDlNegative() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2DirectDlLine( + "dirreq-v2-direct-dl complete=-8"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3TunneledDlTooLarge() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV3TunneledDlLine( + "dirreq-v3-tunneled-dl complete=2147483648"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirreqV3TunneledDlDouble() + throws DescriptorParseException { + DirreqStatsBuilder.createWithDirreqV2TunneledDlLine( + "dirreq-v2-tunneled-dl complete=0.001"); + } + + @Test() + public void testEntryStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = EntryStatsBuilder. + createWithDefaultLines(); + assertEquals(1328925579000L, descriptor.getEntryStatsEndMillis()); + assertEquals(86400L, descriptor.getEntryStatsIntervalLength()); + SortedMap<String, Integer> ips = descriptor.getEntryIps(); + assertNotNull(ips); + assertEquals(25368, ips.get("ir").intValue()); + assertFalse(ips.containsKey("no")); + } + + @Test(expected = DescriptorParseException.class) + public void testEntryStatsEndNoDate() throws DescriptorParseException { + EntryStatsBuilder.createWithEntryStatsEndLine("entry-stats-end " + + "01:59:39 (86400 s)"); + } + + @Test(expected = DescriptorParseException.class) + public void testEntryStatsIpsSemicolon() + throws DescriptorParseException { + EntryStatsBuilder.createWithEntryIpsLine("entry-ips " + + "ir=25368;us=15744"); + } + + @Test() + public void testCellStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = CellStatsBuilder. + createWithDefaultLines(); + assertEquals(1328925579000L, descriptor.getCellStatsEndMillis()); + assertEquals(86400L, descriptor.getCellStatsIntervalLength()); + List<Integer> processedCells = descriptor.getCellProcessedCells(); + assertEquals(10, processedCells.size()); + assertEquals(1441, processedCells.get(0).intValue()); + assertEquals(11, processedCells.get(1).intValue()); + List<Double> queuedCells = descriptor.getCellQueuedCells(); + assertEquals(10, queuedCells.size()); + assertEquals(3.29, queuedCells.get(0), 0.001); + assertEquals(0.00, queuedCells.get(1), 0.001); + List<Integer> timeInQueue = descriptor.getCellTimeInQueue(); + assertEquals(10, timeInQueue.size()); + assertEquals(524, timeInQueue.get(0).intValue()); + assertEquals(1, timeInQueue.get(1).intValue()); + assertEquals(866, descriptor.getCellCircuitsPerDecile()); + } + + @Test(expected = DescriptorParseException.class) + public void testCellStatsEndNoSeconds() + throws DescriptorParseException { + CellStatsBuilder.createWithCellStatsEndLine("cell-stats-end " + + "2012-02-11 01:59:39 (86400)"); + } + + @Test(expected = DescriptorParseException.class) + public void testCellProcessedCellsNineComma() + throws DescriptorParseException { + CellStatsBuilder.createWithCellProcessedCellsLine( + "cell-processed-cells 1441,11,6,4,2,1,1,1,1,"); + } + + @Test(expected = DescriptorParseException.class) + public void testCellProcessedCellsEleven() + throws DescriptorParseException { + CellStatsBuilder.createWithCellQueuedCellsLine("cell-queued-cells " + + "3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00"); + } + + @Test(expected = DescriptorParseException.class) + public void testCellTimeInQueueDouble() + throws DescriptorParseException { + CellStatsBuilder.createWithCellTimeInQueueLine("cell-time-in-queue " + + "524.0,1.0,1.0,0.0,0.0,25.0,0.0,0.0,0.0,0.0"); + } + + @Test(expected = DescriptorParseException.class) + public void testCellCircuitsPerDecileNegative() + throws DescriptorParseException { + CellStatsBuilder.createWithCellCircuitsPerDecileLine( + "cell-circuits-per-decile -866"); + } + + @Test() + public void testConnBiDirectValid() + throws DescriptorParseException { + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithConnBiDirectLine("conn-bi-direct 2012-02-11 01:59:39 " + + "(86400 s) 42173,1591,1310,1744"); + assertEquals(1328925579000L, + descriptor.getConnBiDirectStatsEndMillis()); + assertEquals(86400L, descriptor.getConnBiDirectStatsIntervalLength()); + assertEquals(42173, descriptor.getConnBiDirectBelow()); + assertEquals(1591, descriptor.getConnBiDirectRead()); + assertEquals(1310, descriptor.getConnBiDirectWrite()); + assertEquals(1744, descriptor.getConnBiDirectBoth()); + } + + @Test(expected = DescriptorParseException.class) + public void testConnBiDirectStatsFive() + throws DescriptorParseException { + DescriptorBuilder.createWithConnBiDirectLine("conn-bi-direct " + + "2012-02-11 01:59:39 (86400 s) 42173,1591,1310,1744,42"); + } + + @Test() + public void testExitStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = ExitStatsBuilder. + createWithDefaultLines(); + assertEquals(1328925579000L, descriptor.getExitStatsEndMillis()); + assertEquals(86400L, descriptor.getExitStatsIntervalLength()); + String[] ports = new String[] { "25", "80", "443", "49755", + "52563", "52596", "57528", "60912", "61351", "64811", "other" }; + int[] writtenValues = new int[] { 74647, 31370, 20577, 23, 12, 1111, + 4, 11, 6, 3365, 2592 }; + int i = 0; + for (Map.Entry<String, Long> e : + descriptor.getExitKibibytesWritten().entrySet()) { + assertEquals(ports[i], e.getKey()); + assertEquals(writtenValues[i++], e.getValue().intValue()); + } + int[] readValues = new int[] { 35562, 1254256, 110279, 9396, 1911, + 648, 1188, 1427, 1824, 14, 3054 }; + i = 0; + for (Map.Entry<String, Long> e : + descriptor.getExitKibibytesRead().entrySet()) { + assertEquals(ports[i], e.getKey()); + assertEquals(readValues[i++], e.getValue().intValue()); + } + int[] streamsValues = new int[] { 369748, 64212, 151660, 4, 4, 4, 4, + 4, 4, 4, 1212 }; + i = 0; + for (Map.Entry<String, Long> e : + descriptor.getExitStreamsOpened().entrySet()) { + assertEquals(ports[i], e.getKey()); + assertEquals(streamsValues[i++], e.getValue().intValue()); + } + } + + @Test(expected = DescriptorParseException.class) + public void testExitStatsEndNoSeconds() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitStatsEndLine("exit-stats-end " + + "2012-02-11 01:59 (86400 s)"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitStatsWrittenNegativePort() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitKibibytesWrittenLine( + "exit-kibibytes-written -25=74647"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitStatsWrittenUnknown() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitKibibytesWrittenLine( + "exit-kibibytes-written unknown=74647"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitStatsReadNegativeBytes() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitKibibytesReadLine( + "exit-kibibytes-read 25=-35562"); + } + + @Test() + public void testExitStatsReadTooLarge() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitKibibytesReadLine( + "exit-kibibytes-read other=2282907805"); + } + + @Test() + public void testExitStatsStreamsTooLarge() + throws DescriptorParseException { + ExitStatsBuilder.createWithExitStreamsOpenedLine( + "exit-streams-opened 25=2147483648"); + } + + @Test() + public void testBridgeStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = BridgeStatsBuilder. + createWithDefaultLines(); + assertEquals(1328925579000L, descriptor.getBridgeStatsEndMillis()); + assertEquals(86400L, descriptor.getBridgeStatsIntervalLength()); + SortedMap<String, Integer> ips = descriptor.getBridgeIps(); + assertNotNull(ips); + assertEquals(24, ips.get("ir").intValue()); + assertEquals(16, ips.get("sy").intValue()); + assertFalse(ips.containsKey("no")); + SortedMap<String, Integer> ver = descriptor.getBridgeIpVersions(); + assertNotNull(ver); + assertEquals(8, ver.get("v4").intValue()); + assertEquals(16, ver.get("v6").intValue()); + assertFalse(ver.containsKey("v8")); + SortedMap<String, Integer> trans = descriptor.getBridgeIpTransports(); + assertNotNull(trans); + assertEquals(8, trans.get("<OR>").intValue()); + assertEquals(792, trans.get("obfs2").intValue()); + assertEquals(1728, trans.get("obfs3").intValue()); + } + + @Test(expected = DescriptorParseException.class) + public void testBridgeStatsEndIntervalZero() + throws DescriptorParseException { + BridgeStatsBuilder.createWithBridgeStatsEndLine("bridge-stats-end " + + "2012-02-11 01:59:39 (0 s)"); + } + + @Test(expected = DescriptorParseException.class) + public void testBridgeIpsDouble() + throws DescriptorParseException { + BridgeStatsBuilder.createWithBridgeIpsLine("bridge-ips ir=24.5"); + } + + @Test(expected = DescriptorParseException.class) + public void testBridgeIpsNonAsciiKeyword() + throws DescriptorParseException { + DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] { + 0x14, (byte) 0xfe, 0x18, // non-ascii chars + 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2d, // "bridge-" + 0x69, 0x70, 0x73 }, false); // "ips" (no newline) + } + + @Test(expected = DescriptorParseException.class) + public void testBridgeIpVersionsDouble() + throws DescriptorParseException { + BridgeStatsBuilder.createWithBridgeIpVersionsLine( + "bridge-ip-versions v4=24.5"); + } + + @Test(expected = DescriptorParseException.class) + public void testBridgeIpTransportsDouble() + throws DescriptorParseException { + BridgeStatsBuilder.createWithBridgeIpTransportsLine( + "bridge-ip-transports obfs2=24.5"); + } + + @Test() + public void testBridgeIpTransportsUnderscore() + throws DescriptorParseException { + BridgeStatsBuilder.createWithBridgeIpTransportsLine( + "bridge-ip-transports meek=32,obfs3_websocket=8,websocket=64"); + } + + @Test() + public void testHidservStatsValid() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = HidservStatsBuilder. + createWithDefaultLines(); + assertEquals(1449152816000L, descriptor.getHidservStatsEndMillis()); + assertEquals(86400L, descriptor.getHidservStatsIntervalLength()); + assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(), + 0.0001); + Map<String, Double> params = + descriptor.getHidservRendRelayedCellsParameters(); + assertTrue(params.containsKey("delta_f")); + assertEquals(2048.0, params.remove("delta_f"), 0.0001); + assertTrue(params.containsKey("epsilon")); + assertEquals(0.3, params.remove("epsilon"), 0.0001); + assertTrue(params.containsKey("bin_size")); + assertEquals(1024.0, params.remove("bin_size"), 0.0001); + assertTrue(params.isEmpty()); + assertEquals(-3.0, descriptor.getHidservDirOnionsSeen(), 0.0001); + params = descriptor.getHidservDirOnionsSeenParameters(); + assertTrue(params.containsKey("delta_f")); + assertEquals(8.0, params.remove("delta_f"), 0.0001); + assertTrue(params.containsKey("epsilon")); + assertEquals(0.3, params.remove("epsilon"), 0.0001); + assertTrue(params.containsKey("bin_size")); + assertEquals(8.0, params.remove("bin_size"), 0.0001); + assertTrue(params.isEmpty()); + } + + @Test() + public void testHidservStatsEndLineMissing() + throws DescriptorParseException { + ExtraInfoDescriptor descriptor = + HidservStatsBuilder.createWithHidservStatsEndLine(null); + assertEquals(-1L, descriptor.getHidservStatsEndMillis()); + assertEquals(-1L, descriptor.getHidservStatsIntervalLength()); + } + + @Test() + public void testHidservRendRelayedCellsNoParams() + throws DescriptorParseException { + ExtraInfoDescriptor descriptor = + HidservStatsBuilder.createWithHidservRendRelayedCellsLine( + "hidserv-rend-relayed-cells 36474281"); + assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(), + 0.0001); + assertTrue( + descriptor.getHidservRendRelayedCellsParameters().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testHidservDirOnionsSeenCommaSeparatedParams() + throws DescriptorParseException { + HidservStatsBuilder.createWithHidservDirOnionsSeenLine( + "hidserv-dir-onions-seen -3 delta_f=8,epsilon=0.30,bin_size=8"); + } + + @Test(expected = DescriptorParseException.class) + public void testHidservDirOnionsSeenNoDoubleParams() + throws DescriptorParseException { + HidservStatsBuilder.createWithHidservDirOnionsSeenLine( + "hidserv-dir-onions-seen -3 delta_f=A epsilon=B bin_size=C"); + } + + @Test() + public void testRouterSignatureOpt() + throws DescriptorParseException { + DescriptorBuilder.createWithRouterSignatureLines("opt " + + "router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "crypto lines are ignored anyway\n" + + "-----END SIGNATURE-----"); + } + + @Test(expected = DescriptorParseException.class) + public void testRouterSignatureNotLastLine() + throws DescriptorParseException { + DescriptorBuilder.createWithRouterSignatureLines("router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" + + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" + + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" + + "-----END SIGNATURE-----\npublished 2012-02-11 09:08:36"); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ExtraInfoDescriptor descriptor = DescriptorBuilder. + createWithUnrecognizedLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); + } + + private static final String IDENTITY_ED25519_LINES = + "identity-ed25519\n" + + "-----BEGIN ED25519 CERT-----\n" + + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr" + + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1" + + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA=" + + "\n" + + "-----END ED25519 CERT-----"; + + private static final String MASTER_KEY_ED25519_LINE = + "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc"; + + private static final String ROUTER_SIG_ED25519_LINE = + "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ" + + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw"; + + @Test() + public void testEd25519() throws DescriptorParseException { + ExtraInfoDescriptor descriptor = + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + assertEquals(IDENTITY_ED25519_LINES.substring( + IDENTITY_ED25519_LINES.indexOf("\n") + 1), + descriptor.getIdentityEd25519()); + assertEquals(MASTER_KEY_ED25519_LINE.substring( + MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), + descriptor.getMasterKeyEd25519()); + assertEquals(ROUTER_SIG_ED25519_LINE.substring( + ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1), + descriptor.getRouterSignatureEd25519()); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityMasterKeyMismatch() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519IdentityMissing() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(null, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n" + + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE, + ROUTER_SIG_ED25519_LINE); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityEmptyCrypto() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n" + + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----", + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519MasterKeyMissing() + throws DescriptorParseException { + ExtraInfoDescriptor descriptor = + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + null, ROUTER_SIG_ED25519_LINE); + assertEquals(MASTER_KEY_ED25519_LINE.substring( + MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), + descriptor.getMasterKeyEd25519()); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519MasterKeyDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE, + ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519RouterSigMissing() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, null); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519RouterSigDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n" + + ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testExtraInfoDigestSha256Relay() + throws DescriptorParseException { + byte[] descriptorBytes = ("extra-info Unnamed " + + "EA5B335055D2F03013FF030381F02B1C631EC723\n" + + "identity-ed25519\n" + + "-----BEGIN ED25519 CERT-----\n" + + "AQQABiZRAenzZorGtx6xapoEeaqcLLOk3uWwJXTvOVLluSXXbRSZAQAgBADLN5" + + "wp\nCEOrRbshSbj1NDAUgc6cxU65M/Vx1x+b5+EXbkQZ5uiyB4pphVF5kPPT1P" + + "SleYqM\n8j+tlKh2i6+Xr0xScSPpmtG00/D0MoRlT7ZdaaaT5iw1DWDQCZ8BHG" + + "lAZwU=\n" + + "-----END ED25519 CERT-----\n" + + "published 2015-12-01 04:38:12\n" + + "write-history 2015-12-01 01:40:37 (14400 s) 88704000,60825600," + + "61747200,76953600,61516800,59443200\n" + + "read-history 2015-12-01 01:40:37 (14400 s) 87321600,59443200," + + "59904000,74880000,60364800,58060800\n" + + "router-sig-ed25519 c6eUeJs/SVjun3JhmjByEeWdRDyunSMAnGVhx71JiRj" + + "YzR8x5IcPebylG7m10wiolFxinvw78UhrrGo9Sq5ZBw\n" + + "router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "oC2qFHCDOKSRoIPR86jdRxEYia390Z4d8fT0yr/1mg4RQ7lHmxlzFT6QxCswdX" + + "Ry\nvGNGR0wARySgyE+YKKWYn/Hp547JhhWd9Oc7BuFMY0XMvl/HOo+B9VjW+l" + + "nv6UBE\niqxx3C3Iw0ymohvOenyCUa/7TmsT7eVotDO57uIoGEc=\n" + + "-----END SIGNATURE-----\n" + + "").getBytes(); + RelayExtraInfoDescriptor descriptor = + new RelayExtraInfoDescriptorImpl(descriptorBytes, true); + assertEquals("Pt1BtzfRwhYqGCDo8jjchS8nJP3ovrDyHGn+dqPbMgw", + descriptor.getExtraInfoDigestSha256()); + } + + @Test() + public void testExtraInfoDigestSha256Bridge() + throws DescriptorParseException { + byte[] descriptorBytes = ("extra-info idideditheconfig " + + "DC28749EC9E26E61DE492E46CD830379E9931B09\n" + + "master-key-ed25519 " + + "38FzmOIE6Mm85Ytx0MhFM6X9EuxWRUgb6HjyMGuO2AU\n" + + "published 2015-12-03 13:23:19\n" + + "write-history 2015-12-03 09:59:32 (14400 s) 53913600,52992000," + + "53222400,53222400,53452800,53222400\n" + + "read-history 2015-12-03 09:59:32 (14400 s) 61056000,60364800," + + "60364800,60134400,60595200,60364800\n" + + "geoip-db-digest 5BF366AD4A0572D82A1A0F6628AF8EF7725E3AB9\n" + + "geoip6-db-digest 212DE17D5A368DCAFA19B95F168BFFA101145A93\n" + + "router-digest-sha256 " + + "TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4\n" + + "router-digest 00B98F076B586272C3172B7F3DA29ADEE75F2ED8\n").getBytes(); + BridgeExtraInfoDescriptor descriptor = + new BridgeExtraInfoDescriptorImpl(descriptorBytes, true); + assertEquals("TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4", + descriptor.getExtraInfoDigestSha256()); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java new file mode 100644 index 0000000..abb51db --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/MicrodescriptorImplTest.java @@ -0,0 +1,82 @@ +package org.torproject.descriptor.impl; + +import org.junit.Test; +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.Microdescriptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class MicrodescriptorImplTest { + + /* Helper class to build a microdescriptor based on default data and + * modifications requested by test methods. */ + private static class DescriptorBuilder { + private String onionKeyLines = "onion-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBALNZ4pNsHHkl7a+kFWbBmPHNAepjjvuhjTr1TaMB3UKuCRaXJmS2Qr" + + "CW\nkTmINqdQUccwb3ghb7EBZfDtCUvjcwMSEsRRTVIZqVQsYj6m3n1CegOc4o" + + "UutXaZ\nfkyty5XOgV4Qucx9wokzTMCHlO0V0x9y0FwFsK5Nb6ugqfQLLQ6XAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + private static Microdescriptor createWithDefaultLines() + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + return new MicrodescriptorImpl(db.buildDescriptor(), true); + } + private String ntorOnionKeyLine = + "ntor-onion-key PXLa7IGE+TzPDMsM5j9rFnDa37rd6kfZa5QuzqqJukw="; + private String idLine = "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8"; + private static Microdescriptor createWithIdLine(String line) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.idLine = line; + return new MicrodescriptorImpl(db.buildDescriptor(), true); + } + private byte[] buildDescriptor() { + StringBuilder sb = new StringBuilder(); + if (this.onionKeyLines != null) { + sb.append(this.onionKeyLines).append("\n"); + } + if (this.ntorOnionKeyLine != null) { + sb.append(this.ntorOnionKeyLine).append("\n"); + } + if (this.idLine != null) { + sb.append(this.idLine).append("\n"); + } + return sb.toString().getBytes(); + } + } + + @Test() + public void testDefaults() throws DescriptorParseException { + DescriptorBuilder.createWithDefaultLines(); + } + + @Test(expected = DescriptorParseException.class) + public void testIdRsa1024TooShort() throws DescriptorParseException { + DescriptorBuilder.createWithIdLine("id rsa1024 AAAA"); + } + + @Test(expected = DescriptorParseException.class) + public void testIdRsa1024TooLong() throws DescriptorParseException { + DescriptorBuilder.createWithIdLine("id ed25519 AAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + } + + @Test(expected = DescriptorParseException.class) + public void testIdRsa512() throws DescriptorParseException { + DescriptorBuilder.createWithIdLine("id rsa512 " + + "bvegfGxp8k7T9QFpjPTrPaJTa/8"); + } + + @Test(expected = DescriptorParseException.class) + public void testIdEd25519Duplicate() throws DescriptorParseException { + DescriptorBuilder.createWithIdLine( + "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8\n" + + "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8"); + } +} diff --git a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java new file mode 100644 index 0000000..d864337 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java @@ -0,0 +1,1272 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.DirectorySignature; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +import org.junit.Test; +import org.torproject.descriptor.NetworkStatusEntry; +import org.torproject.descriptor.RelayNetworkStatusConsensus; + +/* TODO Add test cases for all lines starting with "opt ". */ + +/* Test parsing of network status consensuses. The main focus is on + * making sure that the parser is as robust as possible and doesn't break, + * no matter what gets fed into it. A secondary focus is to ensure that + * a parsed consensus is fully compatible to dir-spec.txt. */ +public class RelayNetworkStatusConsensusImplTest { + + /* Helper class to build a directory source based on default data and + * modifications requested by test methods. */ + private static class DirSourceBuilder { + private static RelayNetworkStatusConsensus + createWithDirSource(String dirSourceString) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.dirSources.add(dirSourceString); + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + true); + } + private String nickname = "gabelmoo"; + private static RelayNetworkStatusConsensus + createWithNickname(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.nickname = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String identity = "ED03BB616EB2F60BEC80151114BB25CEF515B226"; + private static RelayNetworkStatusConsensus + createWithIdentity(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.identity = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String hostName = "212.112.245.170"; + private static RelayNetworkStatusConsensus + createWithHostName(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.hostName = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String address = "212.112.245.170"; + private static RelayNetworkStatusConsensus + createWithAddress(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.address = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String dirPort = "80"; + private static RelayNetworkStatusConsensus + createWithDirPort(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.dirPort = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String orPort = "443"; + private static RelayNetworkStatusConsensus + createWithOrPort(String string) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.orPort = string; + return createWithDirSource(dsb.buildDirSource()); + } + private String contactLine = "contact 4096R/C5AA446D Sebastian Hahn " + + "tor@sebastianhahn.net"; + private static RelayNetworkStatusConsensus + createWithContactLine(String line) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.contactLine = line; + return createWithDirSource(dsb.buildDirSource()); + } + private String voteDigestLine = + "vote-digest 0F398A5834D2C139E1D92310B09F814F243354D1"; + private static RelayNetworkStatusConsensus + createWithVoteDigestLine(String line) + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.voteDigestLine = line; + return createWithDirSource(dsb.buildDirSource()); + } + private String buildDirSource() { + StringBuilder sb = new StringBuilder(); + String dirSourceLine = "dir-source " + this.nickname + " " + + this.identity + " " + this.hostName + " " + this.address + " " + + this.dirPort + " " + this.orPort; + sb.append(dirSourceLine).append("\n"); + if (this.contactLine != null) { + sb.append(this.contactLine).append("\n"); + } + if (this.voteDigestLine != null) { + sb.append(this.voteDigestLine).append("\n"); + } + String dirSourceWithTrailingNewLine = sb.toString(); + String dirSource = dirSourceWithTrailingNewLine.substring(0, + dirSourceWithTrailingNewLine.length() - 1); + return dirSource; + } + } + + /* Helper class to build a status entry based on default data and + * modifications requested by test methods. */ + private static class StatusEntryBuilder { + private static RelayNetworkStatusConsensus + createWithStatusEntry(String statusEntryString) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.add(statusEntryString); + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + true); + } + private String nickname = "right2privassy3"; + private static RelayNetworkStatusConsensus + createWithNickname(String string) + throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.nickname = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String fingerprintBase64 = "ADQ6gCT3DiFHKPDFr3rODBUI8HM"; + private static RelayNetworkStatusConsensus + createWithFingerprintBase64(String string) + throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.fingerprintBase64 = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String descriptorBase64 = "Yiti+nayuT2Efe2X1+M4nslwVuU"; + private static RelayNetworkStatusConsensus + createWithDescriptorBase64(String string) + throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.descriptorBase64 = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String publishedString = "2011-11-29 21:34:27"; + private static RelayNetworkStatusConsensus + createWithPublishedString(String string) + throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.publishedString = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String address = "50.63.8.215"; + private static RelayNetworkStatusConsensus + createWithAddress(String string) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.address = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String orPort = "9023"; + private static RelayNetworkStatusConsensus + createWithOrPort(String string) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.orPort = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String dirPort = "0"; + private static RelayNetworkStatusConsensus + createWithDirPort(String string) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.dirPort = string; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String sLine = "s Exit Fast Named Running Stable Valid"; + private static RelayNetworkStatusConsensus + createWithSLine(String line) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.sLine = line; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String vLine = "v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)"; + private static RelayNetworkStatusConsensus + createWithVLine(String line) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.vLine = line; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String wLine = "w Bandwidth=1"; + private static RelayNetworkStatusConsensus + createWithWLine(String line) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.wLine = line; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String pLine = "p accept 80,1194,1220,1293"; + private static RelayNetworkStatusConsensus + createWithPLine(String line) throws DescriptorParseException { + StatusEntryBuilder seb = new StatusEntryBuilder(); + seb.pLine = line; + return createWithStatusEntry(seb.buildStatusEntry()); + } + private String buildStatusEntry() { + StringBuilder sb = new StringBuilder(); + String rLine = "r " + nickname + " " + fingerprintBase64 + " " + + descriptorBase64 + " " + publishedString + " " + address + " " + + orPort + " " + dirPort; + sb.append(rLine).append("\n"); + if (this.sLine != null) { + sb.append(this.sLine).append("\n"); + } + if (this.vLine != null) { + sb.append(this.vLine).append("\n"); + } + if (this.wLine != null) { + sb.append(this.wLine).append("\n"); + } + if (this.pLine != null) { + sb.append(this.pLine).append("\n"); + } + String statusEntryWithTrailingNewLine = sb.toString(); + String statusEntry = statusEntryWithTrailingNewLine.substring(0, + statusEntryWithTrailingNewLine.length() - 1); + return statusEntry; + } + } + + /* Helper class to build a directory signature based on default data and + * modifications requested by test methods. */ + private static class DirectorySignatureBuilder { + private static RelayNetworkStatusConsensus + createWithDirectorySignature(String directorySignatureString) + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.addDirectorySignature(directorySignatureString); + return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), + true); + } + private String identity = "ED03BB616EB2F60BEC80151114BB25CEF515B226"; + private static RelayNetworkStatusConsensus + createWithIdentity(String string) + throws DescriptorParseException { + DirectorySignatureBuilder dsb = new DirectorySignatureBuilder(); + dsb.identity = string; + return createWithDirectorySignature(dsb.buildDirectorySignature()); + } + private String signingKey = + "845CF1D0B370CA443A8579D18E7987E7E532F639"; + private static RelayNetworkStatusConsensus + createWithSigningKey(String string) + throws DescriptorParseException { + DirectorySignatureBuilder dsb = new DirectorySignatureBuilder(); + dsb.signingKey = string; + return createWithDirectorySignature(dsb.buildDirectorySignature()); + } + private String buildDirectorySignature() { + String directorySignature = "directory-signature " + identity + " " + + signingKey + "\n" + + "-----BEGIN SIGNATURE-----\n" + + "gE64+/4BH43v1+7jS9FK1tu2+94at8xhVSPn4O/PpOx7b0Yb+S1hac1QHAiS" + + "Ll+k\n" + + "6OiANKzhj54WHSrUswBPrOzjmKj0OhGXSAe5nHZUFX9a1MDQLDCoZBj536X9" + + "P3JG\n" + + "z89A+wrsN17I5490y66AEvws54BYZMbgRfp8HXn/0Ss=\n" + + "-----END SIGNATURE-----"; + return directorySignature; + } + } + + @Test() + public void testSampleConsensus() throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + RelayNetworkStatusConsensus consensus = + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + assertEquals(3, consensus.getNetworkStatusVersion()); + assertEquals(11, consensus.getConsensusMethod()); + assertEquals(1322643600000L, consensus.getValidAfterMillis()); + assertEquals(1322647200000L, consensus.getFreshUntilMillis()); + assertEquals(1322654400000L, consensus.getValidUntilMillis()); + assertEquals(300L, consensus.getVoteSeconds()); + assertEquals(300L, consensus.getDistSeconds()); + assertTrue(consensus.getRecommendedClientVersions().contains( + "0.2.3.8-alpha")); + assertTrue(consensus.getRecommendedServerVersions().contains( + "0.2.3.8-alpha")); + assertTrue(consensus.getKnownFlags().contains("Running")); + assertEquals(30000, (int) consensus.getConsensusParams().get( + "CircuitPriorityHalflifeMsec")); + assertEquals("86.59.21.38", consensus.getDirSourceEntries().get( + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4").getHostname()); + assertEquals("86.59.21.38", consensus.getDirSourceEntries().get( + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4").getIp()); + assertTrue(consensus.containsStatusEntry( + "00795A6E8D91C270FC23B30F388A495553E01894")); + assertEquals("188.177.149.216", consensus.getStatusEntry( + "00795A6E8D91C270FC23B30F388A495553E01894").getAddress()); + for (DirectorySignature signature : consensus.getSignatures()) { + if ("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4".equals( + signature.getIdentity())) { + assertEquals("3509BA5A624403A905C74DA5C8A0CEC9E0D3AF86", + signature.getSigningKeyDigest()); + } + } + assertEquals(285, (int) consensus.getBandwidthWeights().get("Wbd")); + assertTrue(consensus.getUnrecognizedLines().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNoLine() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNewLine() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version 3\n"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNewLineSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version 3\n "); + } + + @Test() + public void testNetworkStatusVersionPrefixLineAtChar() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "@consensus\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionPrefixLine() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "directory-footer\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionPrefixLinePoundChar() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "#consensus\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNoSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionOneSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version "); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersion42() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version 42"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionFourtyTwo() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + "network-status-version FourtyTwo"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusNoLine() throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionSpaceBefore() + throws DescriptorParseException { + ConsensusBuilder.createWithNetworkStatusVersionLine( + " network-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusSpaceBefore() throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine(" vote-status consensus"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusNoSpace() throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine("vote-status"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusOneSpace() throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine("vote-status "); + } + + @Test() + public void testVoteStatusConsensusOneSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine("vote-status consensus "); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusVote() throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine("vote-status vote"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusTheMagicVoteStatus() + throws DescriptorParseException { + ConsensusBuilder.createWithVoteStatusLine( + "vote-status TheMagicVoteStatus"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodNoLine() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodNoSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine("consensus-method"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodOneSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine("consensus-method "); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodEleven() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine( + "consensus-method eleven"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodMinusOne() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine("consensus-method -1"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodNinePeriod() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine("consensus-method " + + "999999999999999999999999999999999999999999999999999999999999"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodTwoLines() + throws DescriptorParseException { + ConsensusBuilder.createWithConsensusMethodLine( + "consensus-method 1\nconsensus-method 1"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterNoLine() throws DescriptorParseException { + ConsensusBuilder.createWithValidAfterLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterNoSpace() throws DescriptorParseException { + ConsensusBuilder.createWithValidAfterLine("valid-after"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterOneSpace() throws DescriptorParseException { + ConsensusBuilder.createWithValidAfterLine("valid-after "); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterLongAgo() throws DescriptorParseException { + ConsensusBuilder.createWithValidAfterLine("valid-after long ago"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterFeb30() throws DescriptorParseException { + ConsensusBuilder.createWithValidAfterLine( + "valid-after 2011-02-30 09:00:00"); + } + + @Test(expected = DescriptorParseException.class) + public void testFreshUntilNoLine() throws DescriptorParseException { + ConsensusBuilder.createWithFreshUntilLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testFreshUntilAroundTen() throws DescriptorParseException { + ConsensusBuilder.createWithFreshUntilLine( + "fresh-until 2011-11-30 around ten"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidUntilTomorrowMorning() + throws DescriptorParseException { + ConsensusBuilder.createWithValidUntilLine( + "valid-until tomorrow morning"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayNoLine() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayNoSpace() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine("voting-delay"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayOneSpace() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine("voting-delay "); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayTriple() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine( + "voting-delay 300 300 300"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelaySingle() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine("voting-delay 300"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayOneTwo() throws DescriptorParseException { + ConsensusBuilder.createWithVotingDelayLine("voting-delay one two"); + } + + @Test() + public void testClientServerVersionsNoLine() + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.clientVersionsLine = null; + cb.serverVersionsLine = null; + RelayNetworkStatusConsensus consensus = + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + assertNull(consensus.getRecommendedClientVersions()); + assertNull(consensus.getRecommendedServerVersions()); + } + + @Test() + public void testServerVersionsNoLine() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithServerVersionsLine(null); + assertNotNull(consensus.getRecommendedClientVersions()); + assertNull(consensus.getRecommendedServerVersions()); + } + + @Test() + public void testClientVersionsNoLine() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithClientVersionsLine(null); + assertNull(consensus.getRecommendedClientVersions()); + assertNotNull(consensus.getRecommendedServerVersions()); + } + + @Test() + public void testClientVersionsNoSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithClientVersionsLine("client-versions"); + assertNotNull(consensus.getRecommendedClientVersions()); + assertTrue(consensus.getRecommendedClientVersions().isEmpty()); + } + + @Test() + public void testClientVersionsOneSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithClientVersionsLine("client-versions "); + assertNotNull(consensus.getRecommendedClientVersions()); + assertTrue(consensus.getRecommendedClientVersions().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testClientVersionsComma() throws DescriptorParseException { + ConsensusBuilder.createWithClientVersionsLine("client-versions ,"); + } + + @Test(expected = DescriptorParseException.class) + public void testClientVersionsCommaVersion() + throws DescriptorParseException { + ConsensusBuilder.createWithClientVersionsLine( + "client-versions ,0.2.2.34"); + } + + @Test() + public void testPackageNone() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithPackageLines(null); + assertNull(consensus.getPackageLines()); + } + + @Test() + public void testPackageOne() throws DescriptorParseException { + String packageLine = "package shouldbesecond 0 http digest=digest"; + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithPackageLines(packageLine); + assertEquals(packageLine.substring("package ".length()), + consensus.getPackageLines().get(0)); + } + + @Test() + public void testPackageTwo() throws DescriptorParseException { + List<String> packageLines = Arrays.asList( + "package shouldbesecond 0 http digest=digest", + "package outoforder 0 http digest=digest"); + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithPackageLines(packageLines.get(0) + + "\n" + packageLines.get(1)); + for (int i = 0; i < packageLines.size(); i++) { + assertEquals(packageLines.get(i).substring("package ".length()), + consensus.getPackageLines().get(i)); + } + } + + @Test(expected = DescriptorParseException.class) + public void testPackageIncomplete() throws DescriptorParseException { + String packageLine = "package shouldbesecond 0 http"; + ConsensusBuilder.createWithPackageLines(packageLine); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsNoLine() throws DescriptorParseException { + ConsensusBuilder.createWithKnownFlagsLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsNoSpace() throws DescriptorParseException { + ConsensusBuilder.createWithKnownFlagsLine("known-flags"); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsOneSpace() throws DescriptorParseException { + ConsensusBuilder.createWithKnownFlagsLine("known-flags "); + } + + @Test() + public void testParamsNoLine() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine(null); + assertNull(consensus.getConsensusParams()); + } + + @Test() + public void testParamsNoSpace() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine("params"); + assertNotNull(consensus.getConsensusParams()); + assertTrue(consensus.getConsensusParams().isEmpty()); + } + + @Test() + public void testParamsOneSpace() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine("params "); + assertNotNull(consensus.getConsensusParams()); + assertTrue(consensus.getConsensusParams().isEmpty()); + } + + @Test() + public void testParamsThreeSpaces() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine("params "); + assertNotNull(consensus.getConsensusParams()); + assertTrue(consensus.getConsensusParams().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testParamsNoEqualSign() throws DescriptorParseException { + ConsensusBuilder.createWithParamsLine("params key-value"); + } + + @Test(expected = DescriptorParseException.class) + public void testParamsOneTooLargeNegative() + throws DescriptorParseException { + ConsensusBuilder.createWithParamsLine("params min=-2147483649"); + } + + @Test() + public void testParamsLargestNegative() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine("params min=-2147483648"); + assertEquals(1, consensus.getConsensusParams().size()); + assertEquals(-2147483648, + (int) consensus.getConsensusParams().get("min")); + } + + @Test() + public void testParamsLargestPositive() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithParamsLine("params max=2147483647"); + assertEquals(1, consensus.getConsensusParams().size()); + assertEquals(2147483647, + (int) consensus.getConsensusParams().get("max")); + } + + @Test(expected = DescriptorParseException.class) + public void testParamsOneTooLargePositive() + throws DescriptorParseException { + ConsensusBuilder.createWithParamsLine("params max=2147483648"); + } + + @Test() + public void testDirSourceLegacyNickname() + throws DescriptorParseException { + DirSourceBuilder dsb = new DirSourceBuilder(); + dsb.nickname = "gabelmoo-legacy"; + dsb.identity = "81349FC1F2DBA2C2C11B45CB9706637D480AB913"; + dsb.contactLine = null; + dsb.voteDigestLine = null; + RelayNetworkStatusConsensus consensus = + DirSourceBuilder.createWithDirSource(dsb.buildDirSource()); + assertEquals(3, consensus.getDirSourceEntries().size()); + assertTrue(consensus.getDirSourceEntries().get( + "81349FC1F2DBA2C2C11B45CB9706637D480AB913").isLegacy()); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceNicknameTooLong() + throws DescriptorParseException { + DirSourceBuilder.createWithNickname("gabelmooisfinebutthisistoolong"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceIdentityTooShort() + throws DescriptorParseException { + DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceIdentityTooLong() + throws DescriptorParseException { + DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111" + + "4BB25CEF515B226ED03BB616EB2F60BEC8"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceHostnameMissing() + throws DescriptorParseException { + DirSourceBuilder.createWithHostName(""); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceAddress24() throws DescriptorParseException { + DirSourceBuilder.createWithAddress("212.112.245"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceAddress40() throws DescriptorParseException { + DirSourceBuilder.createWithAddress("212.112.245.170.123"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceDirPortMinusOne() + throws DescriptorParseException { + DirSourceBuilder.createWithDirPort("-1"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceDirPort66666() + throws DescriptorParseException { + DirSourceBuilder.createWithDirPort("66666"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceDirPortOnions() + throws DescriptorParseException { + DirSourceBuilder.createWithDirPort("onions"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceOrPortOnions() + throws DescriptorParseException { + DirSourceBuilder.createWithOrPort("onions"); + } + + @Test() + public void testDirSourceContactNoLine() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + DirSourceBuilder.createWithContactLine(null); + assertNull(consensus.getDirSourceEntries().get( + "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); + } + + @Test() + public void testDirSourceContactLineNoSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + DirSourceBuilder.createWithContactLine("contact"); + assertNotNull(consensus.getDirSourceEntries().get( + "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); + } + + @Test() + public void testDirSourceContactLineOneSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + DirSourceBuilder.createWithContactLine("contact "); + assertNotNull(consensus.getDirSourceEntries().get( + "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceVoteDigestNoLine() + throws DescriptorParseException { + DirSourceBuilder.createWithVoteDigestLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceVoteDigestLineNoSpace() + throws DescriptorParseException { + DirSourceBuilder.createWithVoteDigestLine("vote-digest"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceVoteDigestLineOneSpace() + throws DescriptorParseException { + DirSourceBuilder.createWithVoteDigestLine("vote-digest "); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameNotAllowedChars() + throws DescriptorParseException { + StatusEntryBuilder.createWithNickname("notAll()wed"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameTooLong() throws DescriptorParseException { + StatusEntryBuilder.createWithNickname("1234567890123456789tooLong"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooShort() throws DescriptorParseException { + StatusEntryBuilder.createWithFingerprintBase64("TooShort"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintEndsWithEqualSign() + throws DescriptorParseException { + StatusEntryBuilder.createWithFingerprintBase64( + "ADQ6gCT3DiFHKPDFr3rODBUI8H="); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooLong() throws DescriptorParseException { + StatusEntryBuilder.createWithFingerprintBase64( + "ADQ6gCT3DiFHKPDFr3rODBUI8HMAAAA"); + } + + @Test(expected = DescriptorParseException.class) + public void testDescriptorTooShort() throws DescriptorParseException { + StatusEntryBuilder.createWithDescriptorBase64("TooShort"); + } + + @Test(expected = DescriptorParseException.class) + public void testDescriptorEndsWithEqualSign() + throws DescriptorParseException { + StatusEntryBuilder.createWithDescriptorBase64( + "ADQ6gCT3DiFHKPDFr3rODBUI8H="); + } + + @Test(expected = DescriptorParseException.class) + public void testDescriptorTooLong() throws DescriptorParseException { + StatusEntryBuilder.createWithDescriptorBase64( + "Yiti+nayuT2Efe2X1+M4nslwVuUAAAA"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublished1960() throws DescriptorParseException { + StatusEntryBuilder.createWithPublishedString("1960-11-29 21:34:27"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublished9999() throws DescriptorParseException { + StatusEntryBuilder.createWithPublishedString("9999-11-29 21:34:27"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddress256() throws DescriptorParseException { + StatusEntryBuilder.createWithAddress("256.63.8.215"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddress24() throws DescriptorParseException { + StatusEntryBuilder.createWithAddress("50.63.8/24"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddressV6() throws DescriptorParseException { + StatusEntryBuilder.createWithAddress("::1"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPort66666() throws DescriptorParseException { + StatusEntryBuilder.createWithOrPort("66666"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPortEighty() throws DescriptorParseException { + StatusEntryBuilder.createWithOrPort("eighty"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirPortMinusOne() throws DescriptorParseException { + StatusEntryBuilder.createWithDirPort("-1"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirPortZero() throws DescriptorParseException { + StatusEntryBuilder.createWithDirPort("zero"); + } + + @Test() + public void testSLineNoSpace() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + StatusEntryBuilder.createWithSLine("s"); + assertTrue(consensus.getStatusEntry( + "00343A8024F70E214728F0C5AF7ACE0C1508F073").getFlags().isEmpty()); + } + + @Test() + public void testSLineOneSpace() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + StatusEntryBuilder.createWithSLine("s "); + assertTrue(consensus.getStatusEntry( + "00343A8024F70E214728F0C5AF7ACE0C1508F073").getFlags().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testTwoSLines() throws DescriptorParseException { + StatusEntryBuilder sb = new StatusEntryBuilder(); + sb.sLine = sb.sLine + "\n" + sb.sLine; + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.add(sb.buildStatusEntry()); + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + + @Test(expected = DescriptorParseException.class) + public void testWLineNoSpace() throws DescriptorParseException { + StatusEntryBuilder.createWithWLine("w"); + } + + @Test(expected = DescriptorParseException.class) + public void testWLineOneSpace() throws DescriptorParseException { + StatusEntryBuilder.createWithWLine("w "); + } + + @Test() + public void testWLineWarpSeven() throws DescriptorParseException { + StatusEntryBuilder.createWithWLine("w Warp=7"); + } + + @Test(expected = DescriptorParseException.class) + public void testTwoWLines() throws DescriptorParseException { + StatusEntryBuilder sb = new StatusEntryBuilder(); + sb.wLine = sb.wLine + "\n" + sb.wLine; + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.add(sb.buildStatusEntry()); + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + + @Test() + public void testWLineUnmeasured() throws DescriptorParseException { + StatusEntryBuilder sb = new StatusEntryBuilder(); + sb.wLine = "w Bandwidth=42424242 Unmeasured=1"; + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.add(sb.buildStatusEntry()); + RelayNetworkStatusConsensus consensus = + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + for (NetworkStatusEntry s : consensus.getStatusEntries().values()) { + if (s.getBandwidth() == 42424242L) { + assertTrue(s.getUnmeasured()); + } + } + } + + @Test() + public void testWLineNotUnmeasured() throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + StatusEntryBuilder.createWithWLine("w Bandwidth=20"); + for (NetworkStatusEntry s : consensus.getStatusEntries().values()) { + assertFalse(s.getUnmeasured()); + } + } + + @Test(expected = DescriptorParseException.class) + public void testPLineNoPolicy() throws DescriptorParseException { + StatusEntryBuilder.createWithPLine("p 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testPLineNoPorts() throws DescriptorParseException { + StatusEntryBuilder.createWithPLine("p accept"); + } + + @Test(expected = DescriptorParseException.class) + public void testPLineNoPolicyNoPorts() throws DescriptorParseException { + StatusEntryBuilder.createWithPLine("p "); + } + + @Test(expected = DescriptorParseException.class) + public void testPLineProject() throws DescriptorParseException { + StatusEntryBuilder.createWithPLine("p project 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testTwoPLines() throws DescriptorParseException { + StatusEntryBuilder sb = new StatusEntryBuilder(); + sb.pLine = sb.pLine + "\n" + sb.pLine; + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.add(sb.buildStatusEntry()); + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + } + + @Test() + public void testNoStatusEntries() throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.statusEntries.clear(); + RelayNetworkStatusConsensus consensus = + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + assertFalse(consensus.containsStatusEntry( + "00795A6E8D91C270FC23B30F388A495553E01894")); + } + + @Test(expected = DescriptorParseException.class) + public void testDirectoryFooterNoLine() + throws DescriptorParseException { + /* This breaks, because a bandwidth-weights line without a preceding + * directory-footer line is not allowed. */ + ConsensusBuilder.createWithDirectoryFooterLine(null); + } + + @Test() + public void testDirectoryFooterMissing() + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.setDirectoryFooterLine(null); + cb.setBandwidthWeightsLine(null); + /* This does not break, because directory footers were optional before + * consensus method 9. */ + RelayNetworkStatusConsensus consensus = + new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); + assertNull(consensus.getBandwidthWeights()); + } + + @Test() + public void testDirectoryFooterLineSpace() + throws DescriptorParseException { + ConsensusBuilder.createWithDirectoryFooterLine("directory-footer "); + } + + @Test() + public void testBandwidthWeightsNoLine() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = + ConsensusBuilder.createWithBandwidthWeightsLine(null); + assertNull(consensus.getBandwidthWeights()); + } + + @Test() + public void testBandwidthWeightsLineNoSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithBandwidthWeightsLine("bandwidth-weights"); + assertNotNull(consensus.getBandwidthWeights()); + } + + @Test() + public void testBandwidthWeightsLineOneSpace() + throws DescriptorParseException { + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithBandwidthWeightsLine("bandwidth-weights "); + assertNotNull(consensus.getBandwidthWeights()); + } + + @Test(expected = DescriptorParseException.class) + public void testBandwidthWeightsLineNoEqualSign() + throws DescriptorParseException { + ConsensusBuilder.createWithBandwidthWeightsLine( + "bandwidth-weights Wbd-285"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirectorySignatureIdentityTooShort() + throws DescriptorParseException { + DirectorySignatureBuilder.createWithIdentity("ED03BB616EB2F60"); + } + + @Test() + public void testDirectorySignatureIdentityTooLong() + throws DescriptorParseException { + /* This hex string has an unusual length of 58 hex characters, but + * dir-spec.txt only requires a hex string, and we can't know all hex + * string lengths for all future digest algorithms, so let's just + * accept this. */ + DirectorySignatureBuilder.createWithIdentity( + "ED03BB616EB2F60BEC80151114BB25CEF515B226ED03BB616EB2F60BEC"); + } + + @Test() + public void testDirectorySignatureSigningKeyTooShort() + throws DescriptorParseException { + /* See above, we accept this hex string even though it's unusually + * short. */ + DirectorySignatureBuilder.createWithSigningKey("845CF1D0B370CA"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirectorySignatureSigningKeyTooShortOddNumber() + throws DescriptorParseException { + /* We don't accept this hex string, because it contains an odd number + * of hex characters. */ + DirectorySignatureBuilder.createWithSigningKey("845"); + } + + @Test() + public void testDirectorySignatureSigningKeyTooLong() + throws DescriptorParseException { + /* See above, we accept this hex string even though it's unusually + * long. */ + DirectorySignatureBuilder.createWithSigningKey( + "845CF1D0B370CA443A8579D18E7987E7E532F639845CF1D0B370CA443A"); + } + + @Test(expected = DescriptorParseException.class) + public void testNonAsciiByte20() throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + byte[] consensusBytes = cb.buildConsensus(); + consensusBytes[20] = (byte) 200; + new RelayNetworkStatusConsensusImpl(consensusBytes, true); + } + + @Test(expected = DescriptorParseException.class) + public void testNonAsciiByteMinusOne() + throws DescriptorParseException { + ConsensusBuilder cb = new ConsensusBuilder(); + cb.networkStatusVersionLine = "Xnetwork-status-version 3"; + byte[] consensusBytes = cb.buildConsensus(); + consensusBytes[0] = (byte) 200; + new RelayNetworkStatusConsensusImpl(consensusBytes, true); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedHeaderLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ConsensusBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine, + true); + } + + @Test() + public void testUnrecognizedHeaderLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithUnrecognizedHeaderLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedDirSourceLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ConsensusBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine, + true); + } + + @Test() + public void testUnrecognizedDirSourceLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithUnrecognizedDirSourceLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedStatusEntryLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ConsensusBuilder.createWithUnrecognizedStatusEntryLine( + unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedStatusEntryLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithUnrecognizedStatusEntryLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedDirectoryFooterLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ConsensusBuilder.createWithUnrecognizedFooterLine(unrecognizedLine, + true); + } + + @Test() + public void testUnrecognizedDirectoryFooterLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithUnrecognizedFooterLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedDirectorySignatureLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ConsensusBuilder.createWithUnrecognizedDirectorySignatureLine( + unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedDirectorySignatureLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusConsensus consensus = ConsensusBuilder. + createWithUnrecognizedDirectorySignatureLine(unrecognizedLine, + false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java new file mode 100644 index 0000000..1c840f5 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java @@ -0,0 +1,1373 @@ +/* Copyright 2011--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import org.torproject.descriptor.DirectorySignature; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.torproject.descriptor.RelayNetworkStatusVote; + +/* TODO Add test cases for all lines starting with "opt ". */ + +/* Test parsing of network status votes. Some of the vote-parsing code is + * already tested in the consensus-parsing tests. The tests in this class + * focus on the differences between votes and consensuses that are mostly + * in the directory header. */ +public class RelayNetworkStatusVoteImplTest { + + /* Helper class to build a vote based on default data and modifications + * requested by test methods. */ + private static class VoteBuilder { + private String networkStatusVersionLine = "network-status-version 3"; + private static RelayNetworkStatusVote + createWithNetworkStatusVersionLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.networkStatusVersionLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String voteStatusLine = "vote-status vote"; + private static RelayNetworkStatusVote + createWithVoteStatusLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.voteStatusLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String consensusMethodsLine = + "consensus-methods 1 2 3 4 5 6 7 8 9 10 11"; + private static RelayNetworkStatusVote + createWithConsensusMethodsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.consensusMethodsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String publishedLine = "published 2011-11-30 08:50:01"; + private static RelayNetworkStatusVote + createWithPublishedLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.publishedLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String validAfterLine = "valid-after 2011-11-30 09:00:00"; + private static RelayNetworkStatusVote + createWithValidAfterLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.validAfterLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String freshUntilLine = "fresh-until 2011-11-30 10:00:00"; + private static RelayNetworkStatusVote + createWithFreshUntilLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.freshUntilLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String validUntilLine = "valid-until 2011-11-30 12:00:00"; + private static RelayNetworkStatusVote + createWithValidUntilLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.validUntilLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String votingDelayLine = "voting-delay 300 300"; + private static RelayNetworkStatusVote + createWithVotingDelayLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.votingDelayLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String clientVersionsLine = "client-versions 0.2.1.31," + + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; + private static RelayNetworkStatusVote + createWithClientVersionsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.clientVersionsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String serverVersionsLine = "server-versions 0.2.1.31," + + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; + private static RelayNetworkStatusVote + createWithServerVersionsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.serverVersionsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String packageLines = null; + protected static RelayNetworkStatusVote + createWithPackageLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.packageLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String knownFlagsLine = "known-flags Authority BadExit Exit " + + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid"; + private static RelayNetworkStatusVote + createWithKnownFlagsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.knownFlagsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String flagThresholdsLine = "flag-thresholds " + + "stable-uptime=693369 stable-mtbf=153249 fast-speed=40960 " + + "guard-wfu=94.669% guard-tk=691200 guard-bw-inc-exits=174080 " + + "guard-bw-exc-exits=184320 enough-mtbf=1"; + private static RelayNetworkStatusVote + createWithFlagThresholdsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.flagThresholdsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String paramsLine = "params " + + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 " + + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 " + + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 " + + "cbtquantile=80 circwindow=1000 refuseunknownexits=1"; + private static RelayNetworkStatusVote + createWithParamsLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.paramsLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirSourceLine = "dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"; + private static RelayNetworkStatusVote + createWithDirSourceLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirSourceLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String contactLine = "contact 4096R/E012B42D Jacob Appelbaum " + + "jacob@appelbaum.net"; + private static RelayNetworkStatusVote + createWithContactLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.contactLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String legacyDirKeyLine = null; + private static RelayNetworkStatusVote + createWithLegacyDirKeyLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.legacyDirKeyLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirKeyCertificateVersionLine = + "dir-key-certificate-version 3"; + private static RelayNetworkStatusVote + createWithDirKeyCertificateVersionLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirKeyCertificateVersionLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String fingerprintLine = "fingerprint " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C"; + private static RelayNetworkStatusVote + createWithFingerprintLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.fingerprintLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirKeyPublishedLine = "dir-key-published 2011-04-27 " + + "05:34:37"; + private static RelayNetworkStatusVote + createWithDirKeyPublishedLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirKeyPublishedLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirKeyExpiresLine = "dir-key-expires 2012-04-27 " + + "05:34:37"; + private static RelayNetworkStatusVote + createWithDirKeyExpiresLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirKeyExpiresLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirIdentityKeyLines = "dir-identity-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIIBigKCAYEAtKpuLgVK25sfScjsxfVU1ljofrDygt9GP7bNJl/rghX42KUT97" + + "5W\nrGp/fbhF7p+FcKCzNOhJFINQbRf/5E3lN8mzoamIU43QqQ9RRVf94688Us" + + "azVsAN\nNVT0v9J0cr387WePjenRuIE1MmiP0nmw/XdvbPTayqax7VYlcUMXGH" + + "l8DnWix1EN\nRwmeig+JBte0JS12oo2HG9zcSfjLJVjY6ZmvRrVycXiRxGc/Jg" + + "NlSrV4cxUNykaB\nJ6pO6J499OZfQu7m1vAPTENrVJ4yEfRGRwFIY+d/s8BkKc" + + "aiWtXAfTe31uBI6GEH\nmS3HNu1JVSuoaUiQIvVYDLMfBvMcNyAx97UT1l6E0T" + + "n6a7pgChrquGwXai1xGzk8\n58aXwdSFoFBSTCkyemopq5H20p/nkPAO0pHL1k" + + "TvcaKz9CEj4XcKm+kOmzejYmIa\nkbWNcRpXPiUZ+xmwGtsq30xrzqiONmERkx" + + "qlmf7bVQPFvh3Kz6hGcmTBhTbHSe9h\nzDgmdaTNn3EHAgMBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + private static RelayNetworkStatusVote + createWithDirIdentityKeyLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirIdentityKeyLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirSigningKeyLines = "dir-signing-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBAN05qyHFQlTqykMP8yLuD4G2UuYulD4Xs8iSX5uqF+WGsUA1E4zZh4" + + "8h\nDFj8+drFiCu3EqhMEmVG4ACtJK2uz6D1XohUsbPWTR6LSnWJ8q6/zfTSLu" + + "mBGsN7\nPUXyMNjwRKL6UvrcbYk1d2mRBLO7SAP/sFW5fHhIBVeLIWrzQ19rAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + private static RelayNetworkStatusVote + createWithDirSigningKeyLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirSigningKeyLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirKeyCrosscertLines = "dir-key-crosscert\n" + + "-----BEGIN ID SIGNATURE-----\n" + + "rPBFn6IJ6TvAHj4pSwlg+RTn1fP89JGSVa08wuyJr5dAvZsdakQXvRjamT9oJU" + + "aZ\nnY5Rl/tRlGuSQ0BglTPPKoXdKERK0FUr9f0EKrQy7NDUgE2j9losiRuyKz" + + "hA3neZ\nK4yF8bhqAwM51u7fzAhIjNeRif9c04rhFJJCseco84w=\n" + + "-----END ID SIGNATURE-----"; + private static RelayNetworkStatusVote + createWithDirKeyCrosscertLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirKeyCrosscertLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String dirKeyCertificationLines = "dir-key-certification\n" + + "-----BEGIN SIGNATURE-----\n" + + "hPSh6FuohNF5ccjiMbkvr8cZJwGFuL11cNtwN9k0X3pUdFZVATIEkqBe7z+rE2" + + "PX\nPw+BGyC6wYAieoTVIhLpwKqd7DXLYjuhPZ28+7MQaDL01AqYeRp5PT01Px" + + "rFY0Um\nlVf95uqUitgvDT76Ne4ExWk6UvGlYB9OBgBySZz8VWe9znoMqb0uHn" + + "/p8IzqTApT\nAxRWXBHClntMeRqtGxaj8DcdJFn8yMxQiZG7MfDg2sq2ySPJyG" + + "lN+neoVDVhZiDI\n9LTNmw60gWlUp2erFeam8Mo1ZBC4DPNjQEm6QeHZFZMkhD" + + "uO6SwS/FL712A42+Co\nYtMaVot/p5FG2ZSBXbgl2XP5/z8ELnpmXqMbPAoWRo" + + "3BPNSJkIQQNog8Q5ZrK+av\nZDw5eGPltGKsXOkvuzIMM8nBeAnDPDgYvzrIFO" + + "bEGbvY/P8mzVAZxp3Yz+sRtNel\nC1SWz/Fx+Saex5oI7DJ3xtSD4XqKb/wYwZ" + + "FT8IxDYq1t2tFXdHxd4QPRVcvc0zYC\n" + + "-----END SIGNATURE-----"; + private static RelayNetworkStatusVote + createWithDirKeyCertificationLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.dirKeyCertificationLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private List<String> statusEntries = null; + private static RelayNetworkStatusVote createWithStatusEntries( + List<String> statusEntries) throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.statusEntries = statusEntries; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String directoryFooterLine = "directory-footer"; + private static RelayNetworkStatusVote + createWithDirectoryFooterLine(String line) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.directoryFooterLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String directorySignatureLines = "directory-signature " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C " + + "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19\n" + + "-----BEGIN SIGNATURE-----\n" + + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn" + + "F3Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi" + + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" + + "-----END SIGNATURE-----"; + private static RelayNetworkStatusVote + createWithDirectorySignatureLines(String lines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.directorySignatureLines = lines; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + private String unrecognizedHeaderLine = null; + protected static RelayNetworkStatusVote + createWithUnrecognizedHeaderLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.unrecognizedHeaderLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedDirSourceLine = null; + protected static RelayNetworkStatusVote + createWithUnrecognizedDirSourceLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.unrecognizedDirSourceLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedStatusEntryLine = null; + protected static RelayNetworkStatusVote + createWithUnrecognizedStatusEntryLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.unrecognizedStatusEntryLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedFooterLine = null; + protected static RelayNetworkStatusVote + createWithUnrecognizedFooterLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.unrecognizedFooterLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), + failUnrecognizedDescriptorLines); + } + private String unrecognizedDirectorySignatureLine = null; + protected static RelayNetworkStatusVote + createWithUnrecognizedDirectorySignatureLine(String line, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.unrecognizedDirectorySignatureLine = line; + return new RelayNetworkStatusVoteImpl(vb.buildVote(), + failUnrecognizedDescriptorLines); + } + + private VoteBuilder() { + if (this.statusEntries != null) { + return; + } + this.statusEntries = new ArrayList<>(); + this.statusEntries.add("r right2privassy3 " + + "ADQ6gCT3DiFHKPDFr3rODBUI8HM lJY5Vf7kXec+VdkGW2flEsfkFC8 " + + "2011-11-12 00:03:40 50.63.8.215 9023 0\n" + + "s Exit Fast Guard Running Stable Valid\n" + + "opt v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)\n" + + "w Bandwidth=297 Measured=73\n" + + "p accept 80,1194,1220,1293,1500,1533,1677,1723,1863," + + "2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321," + + "4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000," + + "8008,8074,8080,8087-8088,8443,8888,9418,9999-10000,19294," + + "19638\n" + + "m 8,9,10,11 " + + "sha256=9ciEx9t0McXk9A06I7qwN7pxuNOdpCP64RV/6cx2Zkc"); + } + private byte[] buildVote() { + StringBuilder sb = new StringBuilder(); + this.appendHeader(sb); + this.appendDirSource(sb); + this.appendStatusEntries(sb); + this.appendFooter(sb); + this.appendDirectorySignature(sb); + return sb.toString().getBytes(); + } + private void appendHeader(StringBuilder sb) { + if (this.networkStatusVersionLine != null) { + sb.append(this.networkStatusVersionLine).append("\n"); + } + if (this.voteStatusLine != null) { + sb.append(this.voteStatusLine).append("\n"); + } + if (this.consensusMethodsLine != null) { + sb.append(this.consensusMethodsLine).append("\n"); + } + if (this.publishedLine != null) { + sb.append(this.publishedLine).append("\n"); + } + if (this.validAfterLine != null) { + sb.append(this.validAfterLine).append("\n"); + } + if (this.freshUntilLine != null) { + sb.append(this.freshUntilLine).append("\n"); + } + if (this.validUntilLine != null) { + sb.append(this.validUntilLine).append("\n"); + } + if (this.votingDelayLine != null) { + sb.append(this.votingDelayLine).append("\n"); + } + if (this.clientVersionsLine != null) { + sb.append(this.clientVersionsLine).append("\n"); + } + if (this.serverVersionsLine != null) { + sb.append(this.serverVersionsLine).append("\n"); + } + if (this.packageLines != null) { + sb.append(this.packageLines).append("\n"); + } + if (this.knownFlagsLine != null) { + sb.append(this.knownFlagsLine).append("\n"); + } + if (this.flagThresholdsLine != null) { + sb.append(this.flagThresholdsLine).append("\n"); + } + if (this.paramsLine != null) { + sb.append(this.paramsLine).append("\n"); + } + if (this.unrecognizedHeaderLine != null) { + sb.append(this.unrecognizedHeaderLine).append("\n"); + } + } + private void appendDirSource(StringBuilder sb) { + if (this.dirSourceLine != null) { + sb.append(this.dirSourceLine).append("\n"); + } + if (this.contactLine != null) { + sb.append(this.contactLine).append("\n"); + } + if (this.legacyDirKeyLine != null) { + sb.append(this.legacyDirKeyLine).append("\n"); + } + if (this.dirKeyCertificateVersionLine != null) { + sb.append(this.dirKeyCertificateVersionLine).append("\n"); + } + if (this.fingerprintLine != null) { + sb.append(this.fingerprintLine).append("\n"); + } + if (this.dirKeyPublishedLine != null) { + sb.append(this.dirKeyPublishedLine).append("\n"); + } + if (this.dirKeyExpiresLine != null) { + sb.append(this.dirKeyExpiresLine).append("\n"); + } + if (this.dirIdentityKeyLines != null) { + sb.append(this.dirIdentityKeyLines).append("\n"); + } + if (this.dirSigningKeyLines != null) { + sb.append(this.dirSigningKeyLines).append("\n"); + } + if (this.dirKeyCrosscertLines != null) { + sb.append(this.dirKeyCrosscertLines).append("\n"); + } + if (this.dirKeyCertificationLines != null) { + sb.append(this.dirKeyCertificationLines).append("\n"); + } + if (this.unrecognizedDirSourceLine != null) { + sb.append(this.unrecognizedDirSourceLine).append("\n"); + } + } + private void appendStatusEntries(StringBuilder sb) { + for (String statusEntry : this.statusEntries) { + sb.append(statusEntry).append("\n"); + } + if (this.unrecognizedStatusEntryLine != null) { + sb.append(this.unrecognizedStatusEntryLine).append("\n"); + } + } + private void appendFooter(StringBuilder sb) { + if (this.directoryFooterLine != null) { + sb.append(this.directoryFooterLine).append("\n"); + } + if (this.unrecognizedFooterLine != null) { + sb.append(this.unrecognizedFooterLine).append("\n"); + } + } + private void appendDirectorySignature(StringBuilder sb) { + if (this.directorySignatureLines != null) { + sb.append(directorySignatureLines).append("\n"); + } + if (this.unrecognizedDirectorySignatureLine != null) { + sb.append(this.unrecognizedDirectorySignatureLine).append("\n"); + } + } + } + + @Test() + public void testSampleVote() throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + RelayNetworkStatusVote vote = + new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + assertEquals(3, vote.getNetworkStatusVersion()); + List<Integer> consensusMethods = Arrays.asList( + new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); + assertEquals(vote.getConsensusMethods(), consensusMethods); + assertEquals(1322643001000L, vote.getPublishedMillis()); + assertEquals(1322643600000L, vote.getValidAfterMillis()); + assertEquals(1322647200000L, vote.getFreshUntilMillis()); + assertEquals(1322654400000L, vote.getValidUntilMillis()); + assertEquals(300L, vote.getVoteSeconds()); + assertEquals(300L, vote.getDistSeconds()); + assertTrue(vote.getKnownFlags().contains("Running")); + assertEquals(30000, (int) vote.getConsensusParams().get( + "CircuitPriorityHalflifeMsec")); + assertEquals("Tor 0.2.1.29 (r8e9b25e6c7a2e70c)", + vote.getStatusEntry("00343A8024F70E214728F0C5AF7ACE0C1508F073"). + getVersion()); + assertEquals(3, vote.getDirKeyCertificateVersion()); + assertEquals("80550987E1D626E3EBA5E5E75A458DE0626D088C", + vote.getIdentity()); + assertEquals(1303882477000L, /* 2011-04-27 05:34:37 */ + vote.getDirKeyPublishedMillis()); + assertEquals(1335504877000L, /* 2012-04-27 05:34:37 */ + vote.getDirKeyExpiresMillis()); + assertEquals("-----BEGIN RSA PUBLIC KEY-----", + vote.getDirIdentityKey().split("\n")[0]); + assertEquals("-----BEGIN RSA PUBLIC KEY-----", + vote.getDirSigningKey().split("\n")[0]); + assertEquals("-----BEGIN ID SIGNATURE-----", + vote.getDirKeyCrosscert().split("\n")[0]); + assertEquals("-----BEGIN SIGNATURE-----", + vote.getDirKeyCertification().split("\n")[0]); + assertEquals(1, vote.getSignatures().size()); + DirectorySignature signature = vote.getSignatures().get(0); + assertEquals("sha1", signature.getAlgorithm()); + assertEquals("80550987E1D626E3EBA5E5E75A458DE0626D088C", + signature.getIdentity()); + assertEquals("EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19", + signature.getSigningKeyDigest()); + assertEquals("-----BEGIN SIGNATURE-----\n" + + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn" + + "F3Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi" + + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" + + "-----END SIGNATURE-----\n", signature.getSignature()); + assertTrue(vote.getUnrecognizedLines().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNoLine() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNewLine() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version 3\n"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNewLineSpace() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version 3\n "); + } + + @Test() + public void testNetworkStatusVersionPrefixLineAtChar() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "@vote\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionPrefixLine() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "directory-footer\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionPrefixLinePoundChar() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "#vote\nnetwork-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionNoSpace() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionOneSpace() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version "); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersion42() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version 42"); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionFourtyTwo() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + "network-status-version FourtyTwo"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusNoLine() throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testNetworkStatusVersionSpaceBefore() + throws DescriptorParseException { + VoteBuilder.createWithNetworkStatusVersionLine( + " network-status-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusSpaceBefore() throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine(" vote-status vote"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusNoSpace() throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine("vote-status"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusOneSpace() throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine("vote-status "); + } + + @Test() + public void testVoteStatusVoteOneSpace() + throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine("vote-status vote "); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusConsensus() throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine("vote-status consensus"); + } + + @Test(expected = DescriptorParseException.class) + public void testVoteStatusTheMagicVoteStatus() + throws DescriptorParseException { + VoteBuilder.createWithVoteStatusLine( + "vote-status TheMagicVoteStatus"); + } + + @Test() + public void testConsensusMethodNoLine() + throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithConsensusMethodsLine(null); + assertNull(vote.getConsensusMethods()); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodNoSpace() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine("consensus-methods"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodOneSpace() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine("consensus-methods "); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodEleven() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine( + "consensus-methods eleven"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodMinusOne() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine("consensus-methods -1"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodNinePeriod() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine("consensus-methods " + + "999999999999999999999999999999999999999999999999999999999999"); + } + + @Test(expected = DescriptorParseException.class) + public void testConsensusMethodTwoLines() + throws DescriptorParseException { + VoteBuilder.createWithConsensusMethodsLine( + "consensus-method 1\nconsensus-method 1"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublishedNoLine() throws DescriptorParseException { + VoteBuilder.createWithPublishedLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterNoLine() throws DescriptorParseException { + VoteBuilder.createWithValidAfterLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterNoSpace() throws DescriptorParseException { + VoteBuilder.createWithValidAfterLine("valid-after"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterOneSpace() throws DescriptorParseException { + VoteBuilder.createWithValidAfterLine("valid-after "); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterLongAgo() throws DescriptorParseException { + VoteBuilder.createWithValidAfterLine("valid-after long ago"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidAfterFeb30() throws DescriptorParseException { + VoteBuilder.createWithValidAfterLine( + "valid-after 2011-02-30 09:00:00"); + } + + @Test(expected = DescriptorParseException.class) + public void testFreshUntilNoLine() throws DescriptorParseException { + VoteBuilder.createWithFreshUntilLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testFreshUntilAroundTen() throws DescriptorParseException { + VoteBuilder.createWithFreshUntilLine( + "fresh-until 2011-11-30 around ten"); + } + + @Test(expected = DescriptorParseException.class) + public void testValidUntilTomorrowMorning() + throws DescriptorParseException { + VoteBuilder.createWithValidUntilLine( + "valid-until tomorrow morning"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayNoLine() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayNoSpace() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine("voting-delay"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayOneSpace() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine("voting-delay "); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayTriple() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine( + "voting-delay 300 300 300"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelaySingle() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine("voting-delay 300"); + } + + @Test(expected = DescriptorParseException.class) + public void testVotingDelayOneTwo() throws DescriptorParseException { + VoteBuilder.createWithVotingDelayLine("voting-delay one two"); + } + + @Test(expected = DescriptorParseException.class) + public void testClientVersionsComma() throws DescriptorParseException { + VoteBuilder.createWithClientVersionsLine("client-versions ,"); + } + + @Test(expected = DescriptorParseException.class) + public void testClientVersionsCommaVersion() + throws DescriptorParseException { + VoteBuilder.createWithClientVersionsLine( + "client-versions ,0.2.2.34"); + } + + @Test() + public void testPackageNone() throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithPackageLines(null); + assertNull(vote.getPackageLines()); + } + + @Test() + public void testPackageOne() throws DescriptorParseException { + String packageLine = "package shouldbesecond 0 http digest=digest"; + RelayNetworkStatusVote vote = + VoteBuilder.createWithPackageLines(packageLine); + assertEquals(packageLine.substring("package ".length()), + vote.getPackageLines().get(0)); + } + + @Test() + public void testPackageTwo() throws DescriptorParseException { + List<String> packageLines = Arrays.asList( + "package shouldbesecond 0 http digest=digest", + "package outoforder 0 http digest=digest"); + RelayNetworkStatusVote vote = + VoteBuilder.createWithPackageLines(packageLines.get(0) + + "\n" + packageLines.get(1)); + for (int i = 0; i < packageLines.size(); i++) { + assertEquals(packageLines.get(i).substring("package ".length()), + vote.getPackageLines().get(i)); + } + } + + @Test(expected = DescriptorParseException.class) + public void testPackageIncomplete() throws DescriptorParseException { + String packageLine = "package shouldbesecond 0 http"; + ConsensusBuilder.createWithPackageLines(packageLine); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsNoLine() throws DescriptorParseException { + VoteBuilder.createWithKnownFlagsLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsNoSpace() throws DescriptorParseException { + VoteBuilder.createWithKnownFlagsLine("known-flags"); + } + + @Test(expected = DescriptorParseException.class) + public void testKnownFlagsOneSpace() throws DescriptorParseException { + VoteBuilder.createWithKnownFlagsLine("known-flags "); + } + + @Test() + public void testFlagThresholdsLine() throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + RelayNetworkStatusVote vote = + new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + assertEquals(693369L, vote.getStableUptime()); + assertEquals(153249L, vote.getStableMtbf()); + assertEquals(40960L, vote.getFastBandwidth()); + assertEquals(94.669, vote.getGuardWfu(), 0.001); + assertEquals(691200L, vote.getGuardTk()); + assertEquals(174080L, vote.getGuardBandwidthIncludingExits()); + assertEquals(184320L, vote.getGuardBandwidthExcludingExits()); + assertEquals(1, vote.getEnoughMtbfInfo()); + } + + @Test() + public void testFlagThresholdsNoLine() throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithFlagThresholdsLine(null); + assertEquals(-1L, vote.getStableUptime()); + assertEquals(-1L, vote.getStableMtbf()); + assertEquals(-1L, vote.getFastBandwidth()); + assertEquals(-1.0, vote.getGuardWfu(), 0.001); + assertEquals(-1L, vote.getGuardTk()); + assertEquals(-1L, vote.getGuardBandwidthIncludingExits()); + assertEquals(-1L, vote.getGuardBandwidthExcludingExits()); + assertEquals(-1, vote.getEnoughMtbfInfo()); + } + + @Test() + public void testFlagThresholdsAllZeroes() + throws DescriptorParseException { + RelayNetworkStatusVote vote = + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds " + + "stable-uptime=0 stable-mtbf=0 fast-speed=0 guard-wfu=0.0% " + + "guard-tk=0 guard-bw-inc-exits=0 guard-bw-exc-exits=0 " + + "enough-mtbf=0"); + assertEquals(0L, vote.getStableUptime()); + assertEquals(0L, vote.getStableMtbf()); + assertEquals(0L, vote.getFastBandwidth()); + assertEquals(0.0, vote.getGuardWfu(), 0.001); + assertEquals(0L, vote.getGuardTk()); + assertEquals(0L, vote.getGuardBandwidthIncludingExits()); + assertEquals(0L, vote.getGuardBandwidthExcludingExits()); + assertEquals(0, vote.getEnoughMtbfInfo()); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdsNoSpace() + throws DescriptorParseException { + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds"); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdsOneSpace() + throws DescriptorParseException { + VoteBuilder.createWithFlagThresholdsLine("flag-thresholds "); + } + + @Test(expected = DescriptorParseException.class) + public void testFlagThresholdDuplicate() + throws DescriptorParseException { + VoteBuilder vb = new VoteBuilder(); + vb.flagThresholdsLine = vb.flagThresholdsLine + "\n" + + vb.flagThresholdsLine; + new RelayNetworkStatusVoteImpl(vb.buildVote(), true); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameMissing() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameTooLong() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source " + + "urrassssssssssssssssssssssssssssssssssssssssssssssss " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameIllegalCharacters() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urra$ " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test() + public void testFingerprintLowerCase() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987e1d626e3eba5e5e75a458de0626d088c 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooShort() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooLong() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintIllegalCharacters() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "ABCDEFGHIJKLM6E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + " 208.83.223.34 208.83.223.34 443 80"); + } + + @Test() + public void testHostname256() + throws DescriptorParseException { + /* This test doesn't fail, because we're not parsing the hostname. */ + RelayNetworkStatusVote vote = + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 256.256.256.256 " + + "208.83.223.34 443 80"); + assertEquals("256.256.256.256", vote.getHostname()); + } + + @Test(expected = DescriptorParseException.class) + public void testHostnameMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 443 " + + "80"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddress256() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "256.256.256.256 443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddressMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 443 " + + "80"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirPortMinus443() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 -443 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirPortFourFourThree() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 four-four-three 80"); + } + + @Test() + public void testDirPort0() throws DescriptorParseException { + /* This test doesn't fail, because we're accepting DirPort 0, even + * though it doesn't make sense from Tor's view. */ + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 0 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPortMissing() throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 "); + } + + @Test() + public void testDirPortOrPortIdentical() + throws DescriptorParseException { + /* This test doesn't fail, even though identical OR and Dir port don't + * make much sense from Tor's view. */ + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 80 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSourceLineDuplicate() + throws DescriptorParseException { + VoteBuilder.createWithDirSourceLine("dir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80\ndir-source urras " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " + + "208.83.223.34 443 80"); + } + + @Test() + public void testContactLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithContactLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testContactLineDuplicate() + throws DescriptorParseException { + VoteBuilder.createWithContactLine("contact 4096R/E012B42D Jacob " + + "Appelbaum jacob@appelbaum.net\ncontact 4096R/E012B42D Jacob " + + "Appelbaum jacob@appelbaum.net"); + } + + @Test() + public void testLegacyDirKeyLine() throws DescriptorParseException { + RelayNetworkStatusVote vote = VoteBuilder.createWithLegacyDirKeyLine( + "legacy-dir-key 81349FC1F2DBA2C2C11B45CB9706637D480AB913"); + assertEquals("81349FC1F2DBA2C2C11B45CB9706637D480AB913", + vote.getLegacyDirKey()); + } + + @Test(expected = DescriptorParseException.class) + public void testLegacyDirKeyLineNoId() throws DescriptorParseException { + VoteBuilder.createWithLegacyDirKeyLine("legacy-dir-key "); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyCertificateVersionLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyCertificateVersionLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyCertificateVersionLineDuplicate() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyCertificateVersionLine( + "dir-key-certificate-version 3\ndir-key-certificate-version 3"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithFingerprintLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintLineDuplicate() + throws DescriptorParseException { + VoteBuilder.createWithFingerprintLine("fingerprint " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C\nfingerprint " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintLineTooLong() + throws DescriptorParseException { + VoteBuilder.createWithFingerprintLine("fingerprint " + + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintLineTooShort() + throws DescriptorParseException { + VoteBuilder.createWithFingerprintLine("fingerprint " + + "80550987E1D626E3EBA5E5E75A458DE0626D"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyPublished3011() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " + + "3011-04-27 05:34:37"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyPublishedRecentlyAtNoon() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " + + "recently 12:00:00"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyPublishedRecentlyNoTime() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " + + "recently"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyExpiresSoonAtNoon() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires " + + "soon 12:00:00"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyExpiresLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyExpiresLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyExpiresLineDuplicate() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires 2012-04-27 " + + "05:34:37\ndir-key-expires 2012-04-27 05:34:37"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirIdentityKeyLinesMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirIdentityKeyLines(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirSigningKeyLinesMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirSigningKeyLines(null); + } + + @Test() + public void testDirKeyCrosscertLinesMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyCrosscertLines(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirKeyCertificationLinesMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirKeyCertificationLines(null); + } + + @Test() + public void testDirectoryFooterLineMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirectoryFooterLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testDirectorySignaturesLinesMissing() + throws DescriptorParseException { + VoteBuilder.createWithDirectorySignatureLines(null); + } + + @Test() + public void testDirectorySignaturesLinesTwoAlgorithms() + throws DescriptorParseException { + String identitySha256 = "32519E5CB7254AB5A94CC9925EC7676E53D5D52EEAB7" + + "914BD3ED751E537CAFCC"; + String signingKeyDigestSha256 = "5A59D99C17831B9254422B6C5AA10CC59381" + + "6CAA5241E22ECAE8BBB4E8E9D1FC"; + String signatureSha256 = "-----BEGIN SIGNATURE-----\n" + + "x57Alc424/zHS73SHokghGtNBVrBjtUz+gSL5w9AHGKUQcMyfw4Z9aDlKpTbFc" + + "5W\nnyIvFmM9C2OAH0S1+a647HHIxhE0zKf4+yKSwzqSyL6sbKQygVlJsRHNRr" + + "cFg8lp\nqBxEwvxQoA4xEDqnerR92pbK9l42nNLiKOcoReUqbbQ=\n" + + "-----END SIGNATURE-----"; + String identitySha1 = "80550987E1D626E3EBA5E5E75A458DE0626D088C"; + String signingKeyDigestSha1 = + "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19"; + String signatureSha1 = "-----BEGIN SIGNATURE-----\n" + + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxnF3" + + "Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40OikfOI" + + "wEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" + + "-----END SIGNATURE-----"; + String signaturesLines = String.format( + "directory-signature sha256 %s %s\n%s\n" + + "directory-signature %s %s\n%s", identitySha256, + signingKeyDigestSha256, signatureSha256, identitySha1, + signingKeyDigestSha1, signatureSha1); + RelayNetworkStatusVote vote = + VoteBuilder.createWithDirectorySignatureLines(signaturesLines); + assertEquals(2, vote.getSignatures().size()); + DirectorySignature firstSignature = vote.getSignatures().get(0); + assertEquals("sha256", firstSignature.getAlgorithm()); + assertEquals(identitySha256, firstSignature.getIdentity()); + assertEquals(signingKeyDigestSha256, + firstSignature.getSigningKeyDigest()); + assertEquals(signatureSha256 + "\n", firstSignature.getSignature()); + DirectorySignature secondSignature = vote.getSignatures().get(1); + assertEquals("sha1", secondSignature.getAlgorithm()); + assertEquals(identitySha1, secondSignature.getIdentity()); + assertEquals(signingKeyDigestSha1, + secondSignature.getSigningKeyDigest()); + assertEquals(signatureSha1 + "\n", secondSignature.getSignature()); + assertEquals(signingKeyDigestSha1, vote.getSigningKeyDigest()); + } + + @Test() + public void testDirectorySignaturesLinesTwoAlgorithmsSameDigests() + throws DescriptorParseException { + String signaturesLines = "directory-signature 00 00\n" + + "-----BEGIN SIGNATURE-----\n00\n-----END SIGNATURE-----\n" + + "directory-signature sha256 00 00\n" + + "-----BEGIN SIGNATURE-----\n00\n-----END SIGNATURE-----"; + RelayNetworkStatusVote vote = + VoteBuilder.createWithDirectorySignatureLines(signaturesLines); + assertEquals(2, vote.getSignatures().size()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedHeaderLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + VoteBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedHeaderLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusVote vote = VoteBuilder. + createWithUnrecognizedHeaderLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedDirSourceLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + VoteBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine, + true); + } + + @Test() + public void testUnrecognizedDirSourceLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusVote vote = VoteBuilder. + createWithUnrecognizedDirSourceLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedFooterLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + VoteBuilder.createWithUnrecognizedFooterLine(unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedFooterLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + RelayNetworkStatusVote vote = VoteBuilder. + createWithUnrecognizedFooterLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); + } + + @Test() + public void testIdEd25519MasterKey() + throws DescriptorParseException { + String masterKey25519 = "8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8"; + List<String> statusEntries = new ArrayList<>(); + statusEntries.add("r PDrelay1 AAFJ5u9xAqrKlpDW6N0pMhJLlKs " + + "bgJiI/la3e9u0K7cQ5pMSXhigHI 2015-12-01 04:54:30 95.215.44.189 " + + "8080 0\n" + + "id ed25519 " + masterKey25519); + RelayNetworkStatusVote vote = + VoteBuilder.createWithStatusEntries(statusEntries); + String fingerprint = vote.getStatusEntries().firstKey(); + assertEquals(masterKey25519, + vote.getStatusEntry(fingerprint).getMasterKeyEd25519()); + } + + @Test() + public void testIdEd25519None() + throws DescriptorParseException { + List<String> statusEntries = new ArrayList<>(); + statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A " + + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 " + + "443 9030\n" + + "id ed25519 none"); + RelayNetworkStatusVote vote = + VoteBuilder.createWithStatusEntries(statusEntries); + String fingerprint = vote.getStatusEntries().firstKey(); + assertEquals("none", + vote.getStatusEntry(fingerprint).getMasterKeyEd25519()); + } + + @Test(expected = DescriptorParseException.class) + public void testIdRsa1024None() + throws DescriptorParseException { + List<String> statusEntries = new ArrayList<>(); + statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A " + + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 " + + "443 9030\n" + + "id rsa1024 none"); + VoteBuilder.createWithStatusEntries(statusEntries); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java b/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java new file mode 100644 index 0000000..cd3f1a5 --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/ServerDescriptorImplTest.java @@ -0,0 +1,1605 @@ +/* Copyright 2012--2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import org.torproject.descriptor.DescriptorParseException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.SortedMap; + +import org.junit.Test; +import org.torproject.descriptor.BandwidthHistory; +import org.torproject.descriptor.ServerDescriptor; + +/* Test parsing of relay server descriptors. */ +public class ServerDescriptorImplTest { + + /* Helper class to build a descriptor based on default data and + * modifications requested by test methods. */ + private static class DescriptorBuilder { + private String routerLine = "router saberrider2008 94.134.192.243 " + + "9001 0 0"; + private static ServerDescriptor createWithRouterLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.routerLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String bandwidthLine = "bandwidth 51200 51200 53470"; + private static ServerDescriptor createWithBandwidthLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.bandwidthLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String platformLine = "platform Tor 0.2.2.35 " + + "(git-b04388f9e7546a9f) on Linux i686"; + private static ServerDescriptor createWithPlatformLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.platformLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String publishedLine = "published 2012-01-01 04:03:19"; + private static ServerDescriptor createWithPublishedLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.publishedLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String fingerprintLine = "opt fingerprint D873 3048 FC8E " + + "C910 2466 AD8F 3098 622B F1BF 71FD"; + private static ServerDescriptor createWithFingerprintLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.fingerprintLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String hibernatingLine = null; + private static ServerDescriptor createWithHibernatingLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.hibernatingLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String uptimeLine = "uptime 48"; + private static ServerDescriptor createWithUptimeLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.uptimeLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String onionKeyLines = "onion-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp" + + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE" + + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + private static ServerDescriptor createWithOnionKeyLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.onionKeyLines = lines; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String signingKeyLines = "signing-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb" + + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN" + + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + private static ServerDescriptor createWithSigningKeyLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.signingKeyLines = lines; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String onionKeyCrosscertLines = null; + private static ServerDescriptor createWithOnionKeyCrosscertLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.onionKeyCrosscertLines = lines; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String ntorOnionKeyCrosscertLines = null; + private static ServerDescriptor createWithNtorOnionKeyCrosscertLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.ntorOnionKeyCrosscertLines = lines; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String exitPolicyLines = "reject *:*"; + private static ServerDescriptor createWithExitPolicyLines( + String lines) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.exitPolicyLines = lines; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String contactLine = "contact Random Person <nobody AT " + + "example dot com>"; + private static ServerDescriptor createWithContactLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.contactLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String familyLine = null; + private static ServerDescriptor createWithFamilyLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.familyLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String readHistoryLine = null; + private static ServerDescriptor createWithReadHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.readHistoryLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String writeHistoryLine = null; + private static ServerDescriptor createWithWriteHistoryLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.writeHistoryLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String eventdnsLine = null; + private static ServerDescriptor createWithEventdnsLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.eventdnsLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String cachesExtraInfoLine = null; + private static ServerDescriptor createWithCachesExtraInfoLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.cachesExtraInfoLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String extraInfoDigestLine = "opt extra-info-digest " + + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74"; + private static ServerDescriptor createWithExtraInfoDigestLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.extraInfoDigestLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String hiddenServiceDirLine = "opt hidden-service-dir"; + private static ServerDescriptor createWithHiddenServiceDirLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.hiddenServiceDirLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String protocolsLine = "opt protocols Link 1 2 Circuit 1"; + private static ServerDescriptor createWithProtocolsLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.protocolsLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String allowSingleHopExitsLine = null; + private static ServerDescriptor + createWithAllowSingleHopExitsLine(String line) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.allowSingleHopExitsLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String ipv6PolicyLine = null; + private static ServerDescriptor createWithIpv6PolicyLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.ipv6PolicyLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String ntorOnionKeyLine = null; + private static ServerDescriptor createWithNtorOnionKeyLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.ntorOnionKeyLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String tunnelledDirServerLine = null; + private static ServerDescriptor createWithTunnelledDirServerLine( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.tunnelledDirServerLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String routerSignatureLines = "router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" + + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" + + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" + + "-----END SIGNATURE-----"; + private static ServerDescriptor createWithRouterSignatureLines( + String line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.routerSignatureLines = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private String unrecognizedLine = null; + private static ServerDescriptor createWithUnrecognizedLine( + String line, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.unrecognizedLine = line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), + failUnrecognizedDescriptorLines); + } + private byte[] nonAsciiLineBytes = null; + private static ServerDescriptor createWithNonAsciiLineBytes( + byte[] lineBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.nonAsciiLineBytes = lineBytes; + return new RelayServerDescriptorImpl(db.buildDescriptor(), + failUnrecognizedDescriptorLines); + } + private String identityEd25519Lines = null, + masterKeyEd25519Line = null, routerSigEd25519Line = null; + private static ServerDescriptor createWithEd25519Lines( + String identityEd25519Lines, String masterKeyEd25519Line, + String routerSigEd25519Line) throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + db.identityEd25519Lines = identityEd25519Lines; + db.masterKeyEd25519Line = masterKeyEd25519Line; + db.routerSigEd25519Line = routerSigEd25519Line; + return new RelayServerDescriptorImpl(db.buildDescriptor(), true); + } + private byte[] buildDescriptor() { + StringBuilder sb = new StringBuilder(); + if (this.routerLine != null) { + sb.append(this.routerLine).append("\n"); + } + if (this.identityEd25519Lines != null) { + sb.append(this.identityEd25519Lines).append("\n"); + } + if (this.masterKeyEd25519Line != null) { + sb.append(this.masterKeyEd25519Line).append("\n"); + } + if (this.bandwidthLine != null) { + sb.append(this.bandwidthLine).append("\n"); + } + if (this.platformLine != null) { + sb.append(this.platformLine).append("\n"); + } + if (this.publishedLine != null) { + sb.append(this.publishedLine).append("\n"); + } + if (this.fingerprintLine != null) { + sb.append(this.fingerprintLine).append("\n"); + } + if (this.hibernatingLine != null) { + sb.append(this.hibernatingLine).append("\n"); + } + if (this.uptimeLine != null) { + sb.append(this.uptimeLine).append("\n"); + } + if (this.onionKeyLines != null) { + sb.append(this.onionKeyLines).append("\n"); + } + if (this.signingKeyLines != null) { + sb.append(this.signingKeyLines).append("\n"); + } + if (this.onionKeyCrosscertLines != null) { + sb.append(this.onionKeyCrosscertLines).append("\n"); + } + if (this.ntorOnionKeyCrosscertLines != null) { + sb.append(this.ntorOnionKeyCrosscertLines).append("\n"); + } + if (this.exitPolicyLines != null) { + sb.append(this.exitPolicyLines).append("\n"); + } + if (this.contactLine != null) { + sb.append(this.contactLine).append("\n"); + } + if (this.familyLine != null) { + sb.append(this.familyLine).append("\n"); + } + if (this.readHistoryLine != null) { + sb.append(this.readHistoryLine).append("\n"); + } + if (this.writeHistoryLine != null) { + sb.append(this.writeHistoryLine).append("\n"); + } + if (this.eventdnsLine != null) { + sb.append(this.eventdnsLine).append("\n"); + } + if (this.cachesExtraInfoLine != null) { + sb.append(this.cachesExtraInfoLine).append("\n"); + } + if (this.extraInfoDigestLine != null) { + sb.append(this.extraInfoDigestLine).append("\n"); + } + if (this.hiddenServiceDirLine != null) { + sb.append(this.hiddenServiceDirLine).append("\n"); + } + if (this.protocolsLine != null) { + sb.append(this.protocolsLine).append("\n"); + } + if (this.allowSingleHopExitsLine != null) { + sb.append(this.allowSingleHopExitsLine).append("\n"); + } + if (this.ipv6PolicyLine != null) { + sb.append(this.ipv6PolicyLine).append("\n"); + } + if (this.ntorOnionKeyLine != null) { + sb.append(this.ntorOnionKeyLine).append("\n"); + } + if (this.tunnelledDirServerLine != null) { + sb.append(this.tunnelledDirServerLine).append("\n"); + } + if (this.unrecognizedLine != null) { + sb.append(this.unrecognizedLine).append("\n"); + } + if (this.nonAsciiLineBytes != null) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(sb.toString().getBytes()); + baos.write(this.nonAsciiLineBytes); + baos.write("\n".getBytes()); + if (this.routerSignatureLines != null) { + baos.write(this.routerSignatureLines.getBytes()); + } + return baos.toByteArray(); + } catch (IOException e) { + return null; + } + } + if (this.routerSigEd25519Line != null) { + sb.append(this.routerSigEd25519Line).append("\n"); + } + if (this.routerSignatureLines != null) { + sb.append(this.routerSignatureLines).append("\n"); + } + return sb.toString().getBytes(); + } + } + + @Test() + public void testSampleDescriptor() throws DescriptorParseException { + DescriptorBuilder db = new DescriptorBuilder(); + ServerDescriptor descriptor = + new RelayServerDescriptorImpl(db.buildDescriptor(), true); + assertEquals("saberrider2008", descriptor.getNickname()); + assertEquals("94.134.192.243", descriptor.getAddress()); + assertEquals(9001, (int) descriptor.getOrPort()); + assertEquals(0, (int) descriptor.getSocksPort()); + assertEquals(0, (int) descriptor.getDirPort()); + assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686", + descriptor.getPlatform()); + assertEquals(Arrays.asList(new Integer[] {1, 2}), + descriptor.getLinkProtocolVersions()); + assertEquals(Arrays.asList(new Integer[] {1}), + descriptor.getCircuitProtocolVersions()); + assertEquals(1325390599000L, descriptor.getPublishedMillis()); + assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD", + descriptor.getFingerprint()); + assertEquals(48, descriptor.getUptime().longValue()); + assertEquals(51200, (int) descriptor.getBandwidthRate()); + assertEquals(51200, (int) descriptor.getBandwidthBurst()); + assertEquals(53470, (int) descriptor.getBandwidthObserved()); + assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74", + descriptor.getExtraInfoDigest()); + assertEquals(Arrays.asList(new Integer[] {2}), + descriptor.getHiddenServiceDirVersions()); + assertEquals("Random Person <nobody AT example dot com>", + descriptor.getContact()); + assertEquals(Arrays.asList(new String[] {"reject *:*"}), + descriptor.getExitPolicyLines()); + assertFalse(descriptor.isHibernating()); + assertNull(descriptor.getFamilyEntries()); + assertNull(descriptor.getReadHistory()); + assertNull(descriptor.getWriteHistory()); + assertFalse(descriptor.getUsesEnhancedDnsLogic()); + assertFalse(descriptor.getCachesExtraInfo()); + assertFalse(descriptor.getAllowSingleHopExits()); + assertTrue(descriptor.getUnrecognizedLines().isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testRouterLineMissing() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine(null); + } + + @Test() + public void testRouterOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithRouterLine("opt router saberrider2008 " + + "94.134.192.243 9001 0 0"); + assertEquals("saberrider2008", descriptor.getNickname()); + assertEquals("94.134.192.243", descriptor.getAddress()); + assertEquals(9001, (int) descriptor.getOrPort()); + assertEquals(0, (int) descriptor.getSocksPort()); + assertEquals(0, (int) descriptor.getDirPort()); + } + + @Test(expected = DescriptorParseException.class) + public void testRouterLinePrecedingHibernatingLine() + throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("hibernating 1\nrouter " + + "saberrider2008 94.134.192.243 9001 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameMissing() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router 94.134.192.243 9001 " + + "0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameInvalidChar() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router $aberrider2008 " + + "94.134.192.243 9001 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testNicknameTooLong() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router " + + "saberrider2008ReallyLongNickname 94.134.192.243 9001 0 0"); + } + + @Test() + public void testNicknameTwoSpaces() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithRouterLine("router saberrider2008 " + + "94.134.192.243 9001 0 0"); + assertEquals("saberrider2008", descriptor.getNickname()); + assertEquals("94.134.192.243", descriptor.getAddress()); + } + + @Test(expected = DescriptorParseException.class) + public void testAddress24() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192/24 9001 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddress294() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "294.134.192.243 9001 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testAddressMissing() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 9001 " + + "0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPort99001() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192.243 99001 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPortMissing() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192.243 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPortOne() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192.243 one 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testOrPortNewline() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192.243 0\n 0 0"); + } + + @Test(expected = DescriptorParseException.class) + public void testDirPortMissing() throws DescriptorParseException { + DescriptorBuilder.createWithRouterLine("router saberrider2008 " + + "94.134.192.243 9001 0 "); + } + + @Test() + public void testPlatformMissing() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPlatformLine(null); + assertNull(descriptor.getPlatform()); + } + + @Test() + public void testPlatformOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPlatformLine("opt platform Tor 0.2.2.35 " + + "(git-b04388f9e7546a9f) on Linux i686"); + assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686", + descriptor.getPlatform()); + } + + @Test() + public void testPlatformNoSpace() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPlatformLine("platform"); + assertEquals("", descriptor.getPlatform()); + } + + @Test() + public void testPlatformSpace() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPlatformLine("platform "); + assertEquals("", descriptor.getPlatform()); + } + + @Test() + public void testProtocolsNoOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithProtocolsLine("protocols Link 1 2 Circuit 1"); + assertEquals(Arrays.asList(new Integer[] {1, 2}), + descriptor.getLinkProtocolVersions()); + assertEquals(Arrays.asList(new Integer[] {1}), + descriptor.getCircuitProtocolVersions()); + } + + @Test(expected = DescriptorParseException.class) + public void testProtocolsAB() throws DescriptorParseException { + DescriptorBuilder.createWithProtocolsLine("opt protocols Link A B " + + "Circuit 1"); + } + + @Test(expected = DescriptorParseException.class) + public void testProtocolsNoCircuitVersions() + throws DescriptorParseException { + DescriptorBuilder.createWithProtocolsLine("opt protocols Link 1 2"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublishedMissing() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine(null); + } + + @Test() + public void testPublishedOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPublishedLine("opt published 2012-01-01 04:03:19"); + assertEquals(1325390599000L, descriptor.getPublishedMillis()); + } + + @Test(expected = DescriptorParseException.class) + public void testPublished2039() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine("published 2039-01-01 " + + "04:03:19"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublished1912() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine("published 1912-01-01 " + + "04:03:19"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublishedFeb31() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine("published 2012-02-31 " + + "04:03:19"); + } + + @Test(expected = DescriptorParseException.class) + public void testPublishedNoTime() throws DescriptorParseException { + DescriptorBuilder.createWithPublishedLine("published 2012-01-01"); + } + + @Test() + public void testPublishedMillis() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithPublishedLine("opt published 2012-01-01 04:03:19.123"); + assertEquals(1325390599000L, descriptor.getPublishedMillis()); + } + + @Test() + public void testFingerprintNoOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFingerprintLine("fingerprint D873 3048 FC8E C910 2466 " + + "AD8F 3098 622B F1BF 71FD"); + assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD", + descriptor.getFingerprint()); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintG() throws DescriptorParseException { + DescriptorBuilder.createWithFingerprintLine("opt fingerprint G873 " + + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooShort() throws DescriptorParseException { + DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 " + + "3048 FC8E C910 2466 AD8F 3098 622B F1BF"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintTooLong() throws DescriptorParseException { + DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 " + + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD D873"); + } + + @Test(expected = DescriptorParseException.class) + public void testFingerprintNoSpaces() throws DescriptorParseException { + DescriptorBuilder.createWithFingerprintLine("opt fingerprint " + + "D8733048FC8EC9102466AD8F3098622BF1BF71FD"); + } + + @Test() + public void testUptimeMissing() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithUptimeLine(null); + assertNull(descriptor.getUptime()); + } + + @Test() + public void testUptimeOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithUptimeLine("opt uptime 48"); + assertEquals(48, descriptor.getUptime().longValue()); + } + + @Test(expected = DescriptorParseException.class) + public void testUptimeFourtyEight() throws DescriptorParseException { + DescriptorBuilder.createWithUptimeLine("uptime fourty-eight"); + } + + @Test() + public void testUptimeMinusOne() throws DescriptorParseException { + DescriptorBuilder.createWithUptimeLine("uptime -1"); + } + + @Test(expected = DescriptorParseException.class) + public void testUptimeSpace() throws DescriptorParseException { + DescriptorBuilder.createWithUptimeLine("uptime "); + } + + @Test(expected = DescriptorParseException.class) + public void testUptimeNoSpace() throws DescriptorParseException { + DescriptorBuilder.createWithUptimeLine("uptime"); + } + + @Test(expected = DescriptorParseException.class) + public void testUptimeFourEight() throws DescriptorParseException { + DescriptorBuilder.createWithUptimeLine("uptime 4 8"); + } + + @Test() + public void testBandwidthOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithBandwidthLine("opt bandwidth 51200 51200 53470"); + assertEquals(51200, (int) descriptor.getBandwidthRate()); + assertEquals(51200, (int) descriptor.getBandwidthBurst()); + assertEquals(53470, (int) descriptor.getBandwidthObserved()); + } + + @Test(expected = DescriptorParseException.class) + public void testBandwidthMissing() throws DescriptorParseException { + DescriptorBuilder.createWithBandwidthLine(null); + } + + @Test(expected = DescriptorParseException.class) + public void testBandwidthOneValue() throws DescriptorParseException { + DescriptorBuilder.createWithBandwidthLine("bandwidth 51200"); + } + + @Test() + public void testBandwidthTwoValues() throws DescriptorParseException { + /* This is allowed, because Tor versions 0.0.8 and older only wrote + * bandwidth lines with rate and burst values, but no observed + * value. */ + ServerDescriptor descriptor = DescriptorBuilder. + createWithBandwidthLine("bandwidth 51200 51200"); + assertEquals(51200, (int) descriptor.getBandwidthRate()); + assertEquals(51200, (int) descriptor.getBandwidthBurst()); + assertEquals(-1, (int) descriptor.getBandwidthObserved()); + } + + @Test(expected = DescriptorParseException.class) + public void testBandwidthFourValues() throws DescriptorParseException { + DescriptorBuilder.createWithBandwidthLine("bandwidth 51200 51200 " + + "53470 53470"); + } + + @Test(expected = DescriptorParseException.class) + public void testBandwidthMinusOneTwoThree() + throws DescriptorParseException { + DescriptorBuilder.createWithBandwidthLine("bandwidth -1 -2 -3"); + } + + @Test() + public void testExtraInfoDigestNoOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExtraInfoDigestLine("extra-info-digest " + + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74"); + assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74", + descriptor.getExtraInfoDigest()); + } + + @Test(expected = DescriptorParseException.class) + public void testExtraInfoDigestNoSpace() + throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoDigestLine("opt " + + "extra-info-digest"); + } + + @Test(expected = DescriptorParseException.class) + public void testExtraInfoDigestTooShort() + throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoDigestLine("opt " + + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F5"); + } + + @Test(expected = DescriptorParseException.class) + public void testExtraInfoDigestTooLong() + throws DescriptorParseException { + DescriptorBuilder.createWithExtraInfoDigestLine("opt " + + "extra-info-digest " + + "1469D1550738A25B1E7B47CDDBCD7B2899F51B741469"); + } + + @Test() + public void testExtraInfoDigestMissing() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExtraInfoDigestLine(null); + assertNull(descriptor.getExtraInfoDigest()); + } + + @Test() + public void testExtraInfoDigestAdditionalDigest() + throws DescriptorParseException { + String extraInfoDigest = "0879DB7B765218D7B3AE7557669D20307BB21CAA"; + String additionalExtraInfoDigest = + "V609l+N6ActBveebfNbH5lQ6wHDNstDkFgyqEhBHwtA"; + String extraInfoDigestLine = String.format("extra-info-digest %s %s", + extraInfoDigest, additionalExtraInfoDigest); + ServerDescriptor descriptor = DescriptorBuilder. + createWithExtraInfoDigestLine(extraInfoDigestLine); + assertEquals(extraInfoDigest, descriptor.getExtraInfoDigest()); + } + + @Test() + public void testOnionKeyOpt() throws DescriptorParseException { + DescriptorBuilder.createWithOnionKeyLines("opt onion-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp" + + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE" + + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"); + } + + @Test() + public void testSigningKeyOpt() throws DescriptorParseException { + DescriptorBuilder.createWithSigningKeyLines("opt signing-key\n" + + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb" + + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN" + + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg" + + "MBAAE=\n" + + "-----END RSA PUBLIC KEY-----"); + } + + @Test() + public void testHiddenServiceDirMissing() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHiddenServiceDirLine(null); + assertNull(descriptor.getHiddenServiceDirVersions()); + } + + @Test() + public void testHiddenServiceDirNoOpt() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHiddenServiceDirLine("hidden-service-dir"); + assertEquals(Arrays.asList(new Integer[] {2}), + descriptor.getHiddenServiceDirVersions()); + } + + @Test() + public void testHiddenServiceDirVersions2And3() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHiddenServiceDirLine("hidden-service-dir 2 3"); + assertEquals(Arrays.asList(new Integer[] {2, 3}), + descriptor.getHiddenServiceDirVersions()); + } + + @Test() + public void testContactMissing() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithContactLine(null); + assertNull(descriptor.getContact()); + } + + @Test() + public void testContactOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithContactLine("opt contact Random Person"); + assertEquals("Random Person", descriptor.getContact()); + } + + @Test(expected = DescriptorParseException.class) + public void testContactDuplicate() throws DescriptorParseException { + DescriptorBuilder.createWithContactLine("contact Random " + + "Person\ncontact Random Person"); + } + + @Test() + public void testContactNoSpace() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithContactLine("contact"); + assertEquals("", descriptor.getContact()); + } + + @Test() + public void testContactCarriageReturn() + throws DescriptorParseException { + String contactString = "Random " + + "Person -----BEGIN PGP PUBLIC KEY BLOCK-----\r" + + "Version: GnuPG v1 dot 4 dot 7 (Darwin)\r\r" + + "mQGiBEbb0rcRBADqBiUXsmtpJifh74irNnkHbhKMj8O4TqenaZYhdjLWouZsZd" + + "07\rmTQoP40G4zqOrVEOOcXpdSiRnHWJYfgTnkibNZrOZEZLn3H1ywpovEgESm" + + "oGEdAX\roid3XuIYRpRnqoafbFg9sg+OofX/mGrO+5ACfagQ9rlfx2oxCWijYw" + + "pYFRk3NhCY=\r=Xaw3\r-----END PGP PUBLIC KEY BLOCK-----"; + ServerDescriptor descriptor = DescriptorBuilder. + createWithContactLine("contact " + contactString); + assertEquals(contactString, descriptor.getContact()); + } + + @Test() + public void testExitPolicyRejectAllAcceptAll() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExitPolicyLines("reject *:*\naccept *:*"); + assertEquals(Arrays.asList(new String[] {"reject *:*", "accept *:*"}), + descriptor.getExitPolicyLines()); + } + + @Test() + public void testExitPolicyOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExitPolicyLines("opt reject *:*"); + assertEquals(Arrays.asList(new String[] {"reject *:*"}), + descriptor.getExitPolicyLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testExitPolicyNoPort() throws DescriptorParseException { + DescriptorBuilder.createWithExitPolicyLines("reject *"); + } + + @Test() + public void testExitPolicyAccept80RejectAll() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExitPolicyLines("accept *:80\nreject *:*"); + assertEquals(Arrays.asList(new String[] {"accept *:80", + "reject *:*"}), descriptor.getExitPolicyLines()); + } + + @Test(expected = DescriptorParseException.class) + public void testExitPolicyReject321() throws DescriptorParseException { + DescriptorBuilder.createWithExitPolicyLines("reject " + + "123.123.123.321:80"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitPolicyRejectPort66666() + throws DescriptorParseException { + DescriptorBuilder.createWithExitPolicyLines("reject *:66666"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitPolicyProjectAll() throws DescriptorParseException { + DescriptorBuilder.createWithExitPolicyLines("project *:*"); + } + + @Test(expected = DescriptorParseException.class) + public void testExitPolicyMissing() throws DescriptorParseException { + DescriptorBuilder.createWithExitPolicyLines(null); + } + + @Test() + public void testExitPolicyMaskTypes() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithExitPolicyLines("reject 192.168.0.0/16:*\n" + + "reject 94.134.192.243/255.255.255.0:*"); + assertEquals(Arrays.asList(new String[] { "reject 192.168.0.0/16:*", + "reject 94.134.192.243/255.255.255.0:*"}), + descriptor.getExitPolicyLines()); + } + + @Test() + public void testRouterSignatureOpt() + throws DescriptorParseException { + DescriptorBuilder.createWithRouterSignatureLines("opt " + + "router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "crypto lines are ignored anyway\n" + + "-----END SIGNATURE-----"); + } + + @Test(expected = DescriptorParseException.class) + public void testRouterSignatureNotLastLine() + throws DescriptorParseException { + DescriptorBuilder.createWithRouterSignatureLines("router-signature\n" + + "-----BEGIN SIGNATURE-----\n" + + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" + + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" + + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" + + "-----END SIGNATURE-----\ncontact me"); + } + + @Test() + public void testHibernatingOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHibernatingLine("opt hibernating 1"); + assertTrue(descriptor.isHibernating()); + } + + @Test() + public void testHibernatingFalse() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHibernatingLine("hibernating 0"); + assertFalse(descriptor.isHibernating()); + } + + @Test() + public void testHibernatingTrue() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithHibernatingLine("hibernating 1"); + assertTrue(descriptor.isHibernating()); + } + + @Test(expected = DescriptorParseException.class) + public void testHibernatingYep() throws DescriptorParseException { + DescriptorBuilder.createWithHibernatingLine("hibernating yep"); + } + + @Test(expected = DescriptorParseException.class) + public void testHibernatingNoSpace() throws DescriptorParseException { + DescriptorBuilder.createWithHibernatingLine("hibernating"); + } + + @Test() + public void testFamilyOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFamilyLine("opt family saberrider2008"); + assertEquals(Arrays.asList(new String[] {"saberrider2008"}), + descriptor.getFamilyEntries()); + } + + @Test() + public void testFamilyFingerprint() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFamilyLine("family " + + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD"); + assertEquals(Arrays.asList(new String[] { + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD"}), + descriptor.getFamilyEntries()); + } + + @Test() + public void testFamilyNickname() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFamilyLine("family saberrider2008"); + assertEquals(Arrays.asList(new String[] {"saberrider2008"}), + descriptor.getFamilyEntries()); + } + + @Test(expected = DescriptorParseException.class) + public void testFamilyDuplicate() throws DescriptorParseException { + DescriptorBuilder.createWithFamilyLine("family " + + "saberrider2008\nfamily saberrider2008"); + } + + @Test(expected = DescriptorParseException.class) + public void testFamilyNicknamePrefix() throws DescriptorParseException { + DescriptorBuilder.createWithFamilyLine("family $saberrider2008"); + } + + @Test(expected = DescriptorParseException.class) + public void testFamilyFingerprintNoPrefix() + throws DescriptorParseException { + DescriptorBuilder.createWithFamilyLine("family " + + "D8733048FC8EC9102466AD8F3098622BF1BF71FD"); + } + + @Test() + public void testFamilyFingerprintNicknameNamed() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFamilyLine("family " + + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD=saberrider2008"); + assertEquals(Arrays.asList(new String[] + { "$D8733048FC8EC9102466AD8F3098622BF1BF71FD=saberrider2008" }), + descriptor.getFamilyEntries()); + } + + @Test() + public void testFamilyFingerprintNicknameUnnamed() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithFamilyLine("family " + + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD~saberrider2008"); + assertEquals(Arrays.asList(new String[] + { "$D8733048FC8EC9102466AD8F3098622BF1BF71FD~saberrider2008" }), + descriptor.getFamilyEntries()); + } + + @Test() + public void testWriteHistory() throws DescriptorParseException { + String writeHistoryLine = "write-history 2012-01-01 03:51:44 (900 s) " + + "4345856,261120,7591936,1748992"; + ServerDescriptor descriptor = DescriptorBuilder. + createWithWriteHistoryLine(writeHistoryLine); + assertNotNull(descriptor.getWriteHistory()); + BandwidthHistory parsedWriteHistory = descriptor.getWriteHistory(); + assertEquals(writeHistoryLine, parsedWriteHistory.getLine()); + assertEquals(1325389904000L, (long) parsedWriteHistory. + getHistoryEndMillis()); + assertEquals(900L, (long) parsedWriteHistory.getIntervalLength()); + SortedMap<Long, Long> bandwidthValues = parsedWriteHistory. + getBandwidthValues(); + assertEquals(4345856L, (long) bandwidthValues.remove(1325387204000L)); + assertEquals(261120L, (long) bandwidthValues.remove(1325388104000L)); + assertEquals(7591936L, (long) bandwidthValues.remove(1325389004000L)); + assertEquals(1748992L, (long) bandwidthValues.remove(1325389904000L)); + assertTrue(bandwidthValues.isEmpty()); + } + + @Test() + public void testWriteHistoryOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithWriteHistoryLine("opt write-history 2012-01-01 " + + "03:51:44 (900 s) 4345856,261120,7591936,1748992"); + assertNotNull(descriptor.getWriteHistory()); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistory3012() throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "3012-01-01 03:51:44 (900 s) 4345856,261120,7591936,1748992"); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryNoSeconds() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51 (900 s) 4345856,261120,7591936,1748992"); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryNoParathenses() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51:44 900 s 4345856,261120,7591936,1748992"); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryNoSpaceSeconds() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51:44 (900s) 4345856,261120,7591936,1748992"); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryTrailingComma() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51:44 (900 s) 4345856,261120,7591936,"); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryOneTwoThree() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51:44 (900 s) one,two,three"); + } + + @Test() + public void testWriteHistoryNoValuesSpace() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " + + "(900 s) "); + assertEquals(900, (long) descriptor.getWriteHistory(). + getIntervalLength()); + assertTrue(descriptor.getWriteHistory().getBandwidthValues(). + isEmpty()); + } + + @Test() + public void testWriteHistoryNoValuesNoSpace() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " + + "(900 s)"); + assertEquals(900, (long) descriptor.getWriteHistory(). + getIntervalLength()); + assertTrue(descriptor.getWriteHistory().getBandwidthValues(). + isEmpty()); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryNoS() throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine( + "write-history 2012-01-01 03:51:44 (900 "); + } + + @Test(expected = DescriptorParseException.class) + public void testWriteHistoryTrailingNumber() + throws DescriptorParseException { + DescriptorBuilder.createWithWriteHistoryLine("write-history " + + "2012-01-01 03:51:44 (900 s) 4345856 1"); + } + + @Test() + public void testWriteHistory1800Seconds() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " + + "(1800 s) 4345856"); + assertEquals(1800L, (long) descriptor.getWriteHistory(). + getIntervalLength()); + } + + @Test() + public void testReadHistory() throws DescriptorParseException { + String readHistoryLine = "read-history 2012-01-01 03:51:44 (900 s) " + + "4268032,139264,7797760,1415168"; + ServerDescriptor descriptor = DescriptorBuilder. + createWithReadHistoryLine(readHistoryLine); + assertNotNull(descriptor.getReadHistory()); + BandwidthHistory parsedReadHistory = descriptor.getReadHistory(); + assertEquals(readHistoryLine, parsedReadHistory.getLine()); + assertEquals(1325389904000L, (long) parsedReadHistory. + getHistoryEndMillis()); + assertEquals(900L, (long) parsedReadHistory.getIntervalLength()); + SortedMap<Long, Long> bandwidthValues = parsedReadHistory. + getBandwidthValues(); + assertEquals(4268032L, (long) bandwidthValues.remove(1325387204000L)); + assertEquals(139264L, (long) bandwidthValues.remove(1325388104000L)); + assertEquals(7797760L, (long) bandwidthValues.remove(1325389004000L)); + assertEquals(1415168L, (long) bandwidthValues.remove(1325389904000L)); + assertTrue(bandwidthValues.isEmpty()); + } + + @Test() + public void testReadHistoryTwoSpaces() throws DescriptorParseException { + /* There are some server descriptors from older Tor versions that + * contain "opt read-history " lines. */ + String readHistoryLine = "opt read-history 2012-01-01 03:51:44 " + + "(900 s) 4268032,139264,7797760,1415168"; + DescriptorBuilder.createWithReadHistoryLine(readHistoryLine); + } + + @Test() + public void testEventdnsOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithEventdnsLine("opt eventdns 1"); + assertTrue(descriptor.getUsesEnhancedDnsLogic()); + } + + @Test() + public void testEventdns1() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithEventdnsLine("eventdns 1"); + assertTrue(descriptor.getUsesEnhancedDnsLogic()); + } + + @Test() + public void testEventdns0() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithEventdnsLine("eventdns 0"); + assertFalse(descriptor.getUsesEnhancedDnsLogic()); + } + + @Test(expected = DescriptorParseException.class) + public void testEventdnsTrue() throws DescriptorParseException { + DescriptorBuilder.createWithEventdnsLine("eventdns true"); + } + + @Test(expected = DescriptorParseException.class) + public void testEventdnsNo() throws DescriptorParseException { + DescriptorBuilder.createWithEventdnsLine("eventdns no"); + } + + @Test() + public void testCachesExtraInfoOpt() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithCachesExtraInfoLine("opt caches-extra-info"); + assertTrue(descriptor.getCachesExtraInfo()); + } + + @Test() + public void testCachesExtraInfoNoSpace() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithCachesExtraInfoLine("caches-extra-info"); + assertTrue(descriptor.getCachesExtraInfo()); + } + + @Test(expected = DescriptorParseException.class) + public void testCachesExtraInfoTrue() throws DescriptorParseException { + DescriptorBuilder.createWithCachesExtraInfoLine("caches-extra-info " + + "true"); + } + + @Test() + public void testAllowSingleHopExitsOpt() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithAllowSingleHopExitsLine("opt allow-single-hop-exits"); + assertTrue(descriptor.getAllowSingleHopExits()); + } + + @Test() + public void testAllowSingleHopExitsNoSpace() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithAllowSingleHopExitsLine("allow-single-hop-exits"); + assertTrue(descriptor.getAllowSingleHopExits()); + } + + @Test(expected = DescriptorParseException.class) + public void testAllowSingleHopExitsTrue() + throws DescriptorParseException { + DescriptorBuilder.createWithAllowSingleHopExitsLine( + "allow-single-hop-exits true"); + } + + @Test(expected = DescriptorParseException.class) + public void testAllowSingleHopExitsNonAsciiKeyword() + throws DescriptorParseException { + DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] { + 0x14, (byte) 0xfe, 0x18, // non-ascii chars + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, // "allow-" + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x2d, // "single-" + 0x68, 0x6f, 0x70, 0x2d, // "hop-" + 0x65, 0x78, 0x69, 0x74, 0x73 }, // "exits" (no newline) + false); + } + + @Test() + public void testIpv6PolicyLine() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithIpv6PolicyLine("ipv6-policy accept 80,1194,1220,1293"); + assertEquals("accept", descriptor.getIpv6DefaultPolicy()); + assertEquals("80,1194,1220,1293", descriptor.getIpv6PortList()); + } + + @Test(expected = DescriptorParseException.class) + public void testIpv6PolicyLineNoPolicy() + throws DescriptorParseException { + DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testIpv6PolicyLineNoPorts() + throws DescriptorParseException { + DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy accept"); + } + + @Test(expected = DescriptorParseException.class) + public void testIpv6PolicyLineNoPolicyNoPorts() + throws DescriptorParseException { + DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy "); + } + + @Test(expected = DescriptorParseException.class) + public void testIpv6PolicyLineProject() + throws DescriptorParseException { + DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy project 80"); + } + + @Test(expected = DescriptorParseException.class) + public void testTwoIpv6PolicyLines() throws DescriptorParseException { + DescriptorBuilder.createWithIpv6PolicyLine( + "ipv6-policy accept 80,1194,1220,1293\n" + + "ipv6-policy accept 80,1194,1220,1293"); + } + + @Test() + public void testNtorOnionKeyLine() throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithNtorOnionKeyLine("ntor-onion-key " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY="); + assertEquals("Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY", + descriptor.getNtorOnionKey()); + } + + @Test() + public void testNtorOnionKeyLineNoPadding() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder. + createWithNtorOnionKeyLine("ntor-onion-key " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY"); + assertEquals("Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY", + descriptor.getNtorOnionKey()); + } + + @Test(expected = DescriptorParseException.class) + public void testNtorOnionKeyLineNoKey() + throws DescriptorParseException { + DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key "); + } + + @Test(expected = DescriptorParseException.class) + public void testNtorOnionKeyLineTwoKeys() + throws DescriptorParseException { + DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY"); + } + + @Test(expected = DescriptorParseException.class) + public void testTwoNtorOnionKeyLines() throws DescriptorParseException { + DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\nntor-onion-key " + + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\n"); + } + + @Test() + public void testTunnelledDirServerTrue() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder + .createWithTunnelledDirServerLine("tunnelled-dir-server"); + assertTrue(descriptor.getTunnelledDirServer()); + } + + @Test() + public void testTunnelledDirServerFalse() + throws DescriptorParseException { + ServerDescriptor descriptor = DescriptorBuilder + .createWithTunnelledDirServerLine(null); + assertFalse(descriptor.getTunnelledDirServer()); + } + + @Test(expected = DescriptorParseException.class) + public void testTunnelledDirServerTypo() + throws DescriptorParseException { + DescriptorBuilder.createWithTunnelledDirServerLine( + "tunneled-dir-server"); + } + + @Test(expected = DescriptorParseException.class) + public void testTunnelledDirServerTwice() + throws DescriptorParseException { + DescriptorBuilder.createWithTunnelledDirServerLine( + "tunnelled-dir-server\ntunnelled-dir-server"); + } + + @Test(expected = DescriptorParseException.class) + public void testTunnelledDirServerArgs() + throws DescriptorParseException { + DescriptorBuilder.createWithTunnelledDirServerLine( + "tunnelled-dir-server 1"); + } + + @Test(expected = DescriptorParseException.class) + public void testUnrecognizedLineFail() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true); + } + + @Test() + public void testUnrecognizedLineIgnore() + throws DescriptorParseException { + String unrecognizedLine = "unrecognized-line 1"; + ServerDescriptor descriptor = DescriptorBuilder. + createWithUnrecognizedLine(unrecognizedLine, false); + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add(unrecognizedLine); + assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); + } + + @Test() + public void testSomeOtherKey() throws DescriptorParseException { + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add("some-other-key"); + unrecognizedLines.add("-----BEGIN RSA PUBLIC KEY-----"); + unrecognizedLines.add("MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ" + + "1U4V9SeiKooSo5BpPL"); + unrecognizedLines.add("o3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3ol" + + "IynCI4QryfCEuC3cTF"); + unrecognizedLines.add("9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKF" + + "facOkpAgMBAAE="); + unrecognizedLines.add("-----END RSA PUBLIC KEY-----"); + StringBuilder sb = new StringBuilder(); + for (String line : unrecognizedLines) { + sb.append("\n").append(line); + } + ServerDescriptor descriptor = DescriptorBuilder. + createWithUnrecognizedLine(sb.toString().substring(1), false); + assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); + } + + @Test() + public void testUnrecognizedCryptoBlockNoKeyword() + throws DescriptorParseException { + List<String> unrecognizedLines = new ArrayList<>(); + unrecognizedLines.add("-----BEGIN RSA PUBLIC KEY-----"); + unrecognizedLines.add("MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ" + + "1U4V9SeiKooSo5BpPL"); + unrecognizedLines.add("o3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3ol" + + "IynCI4QryfCEuC3cTF"); + unrecognizedLines.add("9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKF" + + "facOkpAgMBAAE="); + unrecognizedLines.add("-----END RSA PUBLIC KEY-----"); + StringBuilder sb = new StringBuilder(); + for (String line : unrecognizedLines) { + sb.append("\n").append(line); + } + ServerDescriptor descriptor = DescriptorBuilder. + createWithUnrecognizedLine(sb.toString().substring(1), false); + assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); + } + + private static final String IDENTITY_ED25519_LINES = + "identity-ed25519\n" + + "-----BEGIN ED25519 CERT-----\n" + + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr" + + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1" + + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA=" + + "\n" + + "-----END ED25519 CERT-----"; + + private static final String MASTER_KEY_ED25519_LINE = + "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc"; + + private static final String ROUTER_SIG_ED25519_LINE = + "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ" + + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw"; + + @Test() + public void testEd25519() throws DescriptorParseException { + ServerDescriptor descriptor = + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + assertEquals(IDENTITY_ED25519_LINES.substring( + IDENTITY_ED25519_LINES.indexOf("\n") + 1), + descriptor.getIdentityEd25519()); + assertEquals(MASTER_KEY_ED25519_LINE.substring( + MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), + descriptor.getMasterKeyEd25519()); + assertEquals(ROUTER_SIG_ED25519_LINE.substring( + ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1), + descriptor.getRouterSignatureEd25519()); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityMasterKeyMismatch() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519IdentityMissing() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(null, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n" + + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE, + ROUTER_SIG_ED25519_LINE); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519IdentityEmptyCrypto() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n" + + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----", + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519MasterKeyMissing() + throws DescriptorParseException { + ServerDescriptor descriptor = + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + null, ROUTER_SIG_ED25519_LINE); + assertEquals(MASTER_KEY_ED25519_LINE.substring( + MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), + descriptor.getMasterKeyEd25519()); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519MasterKeyDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE, + ROUTER_SIG_ED25519_LINE); + } + + @Test() + public void testEd25519RouterSigMissing() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, null); + } + + @Test(expected = DescriptorParseException.class) + public void testEd25519RouterSigDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, + MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n" + + ROUTER_SIG_ED25519_LINE); + } + + private static final String ONION_KEY_CROSSCERT_LINES = + "onion-key-crosscert\n" + + "-----BEGIN CROSSCERT-----\n" + + "gVWpiNgG2FekW1uonr4KKoqykjr4bqUBKGZfu6s9rvsV1TThnquZNP6ZhX2IPdQA" + + "\nlfKtzFggGu/4BiJ5oTSDj2sK2DMjY3rjrMQZ3I/wJ25yhc9gxjqYqUYO9MmJwA" + + "Lp\nfYkqp/t4WchJpyva/4hK8vITsI6eT2BfY/DWMy/suIE=\n" + + "-----END CROSSCERT-----"; + + private static final String NTOR_ONION_KEY_CROSSCERT_LINES = + "ntor-onion-key-crosscert 1\n" + + "-----BEGIN ED25519 CERT-----\n" + + "AQoABiUeAdauu1MxYGMmGLTCPaoes0RvW7udeLc1t8LZ4P3CDo5bAN4nrRfbCfOt" + + "\nz2Nwqn8tER1a+Ry6Vs+ilMZA55Rag4+f6Zdb1fmHWknCxbQlLHpqHACMtemPda" + + "Ka\nErPtMuiEqAc=\n" + + "-----END ED25519 CERT-----"; + + @Test() + public void testOnionKeyCrosscert() throws DescriptorParseException { + ServerDescriptor descriptor = + DescriptorBuilder.createWithOnionKeyCrosscertLines( + ONION_KEY_CROSSCERT_LINES); + assertEquals(ONION_KEY_CROSSCERT_LINES.substring( + ONION_KEY_CROSSCERT_LINES.indexOf("\n") + 1), + descriptor.getOnionKeyCrosscert()); + } + + @Test(expected = DescriptorParseException.class) + public void testOnionKeyCrosscertDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithOnionKeyCrosscertLines( + ONION_KEY_CROSSCERT_LINES + "\n" + ONION_KEY_CROSSCERT_LINES); + } + + @Test() + public void testNtorOnionKeyCrosscert() + throws DescriptorParseException { + ServerDescriptor descriptor = + DescriptorBuilder.createWithNtorOnionKeyCrosscertLines( + NTOR_ONION_KEY_CROSSCERT_LINES); + assertEquals(NTOR_ONION_KEY_CROSSCERT_LINES.substring( + NTOR_ONION_KEY_CROSSCERT_LINES.indexOf("\n") + 1), + descriptor.getNtorOnionKeyCrosscert()); + assertEquals(1, descriptor.getNtorOnionKeyCrosscertSign()); + } + + @Test(expected = DescriptorParseException.class) + public void testNtorOnionKeyCrosscertDuplicate() + throws DescriptorParseException { + DescriptorBuilder.createWithOnionKeyCrosscertLines( + NTOR_ONION_KEY_CROSSCERT_LINES + "\n" + + NTOR_ONION_KEY_CROSSCERT_LINES); + } +} + diff --git a/src/test/java/org/torproject/descriptor/impl/TorperfResultImplTest.java b/src/test/java/org/torproject/descriptor/impl/TorperfResultImplTest.java new file mode 100644 index 0000000..b5cde0a --- /dev/null +++ b/src/test/java/org/torproject/descriptor/impl/TorperfResultImplTest.java @@ -0,0 +1,97 @@ +/* Copyright 2015 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.junit.Test; +import org.torproject.descriptor.Descriptor; + +public class TorperfResultImplTest { + + @Test() + public void testAnnotatedInput() throws Exception{ + TorperfResultImpl result = (TorperfResultImpl) + (TorperfResultImpl.parseTorperfResults((torperfAnnotation + input) + .getBytes("US-ASCII"), false).get(0)); + assertEquals("Expected one annotation.", 1, + result.getAnnotations().size()); + assertEquals(torperfAnnotation.substring(0, 17), + result.getAnnotations().get(0)); + int count = 0; + for (Long l: result.getDataPercentiles().values()) { + assertNotNull(l); + assertEquals(l.longValue(), deciles[count++]); + } + } + + @Test() + public void testPartiallyAnnotatedInput() throws Exception{ + byte[] asciiBytes = (torperfAnnotation + + input + input + input).getBytes("US-ASCII"); + List<Descriptor> result = TorperfResultImpl.parseTorperfResults( + asciiBytes, false); + assertEquals("Expected one annotation.", 1, + ((TorperfResultImpl)(result.get(0))).getAnnotations().size()); + assertEquals(3, result.size()); + assertEquals("Expected zero annotations.", 0, + ((TorperfResultImpl)(result.get(1))).getAnnotations().size()); + assertEquals("Expected zero annotations.", 0, + ((TorperfResultImpl)(result.get(2))).getAnnotations().size()); + } + + @Test() + public void testAllAnnotatedInput() throws Exception { + byte[] asciiBytes = (torperfAnnotation + input + + torperfAnnotation + input + + torperfAnnotation + input).getBytes("US-ASCII"); + List<Descriptor> result = TorperfResultImpl.parseTorperfResults( + asciiBytes, false); + assertEquals("Expected one annotation.", 1, + ((TorperfResultImpl)(result.get(0))).getAnnotations().size()); + assertEquals(3, result.size()); + assertEquals("Expected one annotation.", 1, + ((TorperfResultImpl)(result.get(1))).getAnnotations().size()); + assertEquals("Expected one annotation.", 1, + ((TorperfResultImpl)(result.get(2))).getAnnotations().size()); + } + + private static long[] deciles = new long[] { + 1441065602980L, 1441065603030L, 1441065603090L, 1441065603120L, + 1441065603230L, 1441065603250L, 1441065603310L, 1441065603370L, + 1441065603370L }; + + private static final String torperfAnnotation = "@type torperf 1.0\n"; + + private static final String input = + "BUILDTIMES=0.872834920883,1.09103679657,1.49180984497 " + + "CIRC_ID=1228 CONNECT=1441065601.86 DATACOMPLETE=1441065603.39 " + + "DATAPERC10=1441065602.98 DATAPERC20=1441065603.03 " + + "DATAPERC30=1441065603.09 DATAPERC40=1441065603.12 " + + "DATAPERC50=1441065603.23 DATAPERC60=1441065603.25 " + + "DATAPERC70=1441065603.31 DATAPERC80=1441065603.37 " + + "DATAPERC90=1441065603.37 DATAREQUEST=1441065602.38 " + + "DATARESPONSE=1441065602.84 DIDTIMEOUT=0 FILESIZE=51200 " + + "LAUNCH=1441065361.30 NEGOTIATE=1441065601.86 " + + "PATH=$C4C9C332D25B3546BEF4E1250CF410E97EF996E6," + + "$C43FA6474A9F071E9120DF63ED6EB8FDBA105234," + + "$7C0AA4E3B73E407E9F5FEB1912F8BE26D8AA124D QUANTILE=0.800000 " + + "READBYTES=51416 REQUEST=1441065601.86 RESPONSE=1441065602.38 " + + "SOCKET=1441065601.86 SOURCE=moria START=1441065601.86 " + + "TIMEOUT=1500 USED_AT=1441065603.40 USED_BY=2475 WRITEBYTES=75\n"; + + @Test() + public void testDatapercNonNumeric() throws Exception { + List<Descriptor> result = TorperfResultImpl.parseTorperfResults( + ("DATAPERMILLE=2.0 " + input).getBytes(), false); + assertEquals(1, result.size()); + TorperfResultImpl torperfResult = (TorperfResultImpl) result.get(0); + assertEquals(1, torperfResult.getUnrecognizedKeys().size()); + assertEquals("DATAPERMILLE", + torperfResult.getUnrecognizedKeys().firstKey()); + } +} + diff --git a/test/org/torproject/descriptor/benchmark/MeasurePerformance.java b/test/org/torproject/descriptor/benchmark/MeasurePerformance.java deleted file mode 100644 index a52020a..0000000 --- a/test/org/torproject/descriptor/benchmark/MeasurePerformance.java +++ /dev/null @@ -1,278 +0,0 @@ -/* Copyright 2016 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.benchmark; - -import org.torproject.descriptor.Descriptor; -import org.torproject.descriptor.DescriptorFile; -import org.torproject.descriptor.DescriptorReader; -import org.torproject.descriptor.DescriptorSourceFactory; -import org.torproject.descriptor.ExtraInfoDescriptor; -import org.torproject.descriptor.Microdescriptor; -import org.torproject.descriptor.NetworkStatusEntry; -import org.torproject.descriptor.RelayNetworkStatusConsensus; -import org.torproject.descriptor.ServerDescriptor; - -import java.io.File; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import java.util.SortedMap; - -public class MeasurePerformance { - - /* Check if all necessary files are available and then measure - * performance of some more or less common use cases. */ - public static void main(String[] args) { - if (!filesAvailable()) { - return; - } - measureAverageAdvertisedBandwidth(new File(resDir, resPaths[0])); - pause(); - measureAverageAdvertisedBandwidth(new File(resDir, resPaths[1])); - pause(); - measureAverageAdvertisedBandwidth(new File(resDir, resPaths[2])); - pause(); - measureCountriesV3Requests(new File(resDir, resPaths[3])); - pause(); - measureCountriesV3Requests(new File(resDir, resPaths[4])); - pause(); - measureAverageRelaysExit(new File(resDir, resPaths[5])); - pause(); - measureAverageRelaysExit(new File(resDir, resPaths[6])); - pause(); - measureAverageRelaysExit(new File(resDir, resPaths[7])); - measureFractionRelaysExit80Microdescriptors( - new File(resDir, resPaths[8])); - measureFractionRelaysExit80Microdescriptors( - new File(resDir, resPaths[9])); - } - - private static File resDir = new File("res"); - private static String[] resPaths = new String[] { - "archive/relay-descriptors/server-descriptors/" - + "server-descriptors-2015-11.tar.xz", - "archive/relay-descriptors/server-descriptors/" - + "server-descriptors-2015-11.tar", - "archive/relay-descriptors/server-descriptors/" - + "server-descriptors-2015-11", - "archive/relay-descriptors/extra-infos/extra-infos-2015-11.tar.xz", - "archive/relay-descriptors/extra-infos/extra-infos-2015-11.tar", - "archive/relay-descriptors/consensuses/consensuses-2015-11.tar.xz", - "archive/relay-descriptors/consensuses/consensuses-2015-11.tar", - "archive/relay-descriptors/consensuses/consensuses-2015-11", - "archive/relay-descriptors/microdescs/microdescs-2015-11.tar.xz", - "archive/relay-descriptors/microdescs/microdescs-2015-11.tar" - }; - - private static boolean filesAvailable() { - if (!resDir.exists() || !resDir.isDirectory()) { - return false; - } - for (String resPath : resPaths) { - if (!(new File(resDir, resPath).exists())) { - System.err.println("Missing resource: " + resDir + "/" + resPath); - return false; - } - } - return true; - } - - private static void pause() { - try { - Thread.sleep(15L * 1000L); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private static void measureAverageAdvertisedBandwidth( - File tarballFileOrDirectory) { - System.out.println("Starting measureAverageAdvertisedBandwidth"); - long startedMillis = System.currentTimeMillis(); - long sumAdvertisedBandwidth = 0, countedServerDescriptors = 0; - DescriptorReader descriptorReader = - DescriptorSourceFactory.createDescriptorReader(); - descriptorReader.addTarball(tarballFileOrDirectory); - descriptorReader.addDirectory(tarballFileOrDirectory); - Iterator<DescriptorFile> descriptorFiles = - descriptorReader.readDescriptors(); - while (descriptorFiles.hasNext()) { - DescriptorFile descriptorFile = descriptorFiles.next(); - for (Descriptor descriptor : descriptorFile.getDescriptors()) { - if (!(descriptor instanceof ServerDescriptor)) { - continue; - } - ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor; - sumAdvertisedBandwidth += (long) Math.min(Math.min( - serverDescriptor.getBandwidthRate(), - serverDescriptor.getBandwidthBurst()), - serverDescriptor.getBandwidthObserved()); - countedServerDescriptors++; - } - } - long endedMillis = System.currentTimeMillis(); - System.out.println("Ending measureAverageAdvertisedBandwidth"); - System.out.printf("Total time: %d millis%n", - endedMillis - startedMillis); - System.out.printf("Processed server descriptors: %d%n", - countedServerDescriptors); - System.out.printf("Average advertised bandwidth: %d%n", - sumAdvertisedBandwidth / countedServerDescriptors); - System.out.printf("Time per server descriptor: %.6f millis%n", - ((double) (endedMillis - startedMillis)) - / ((double) countedServerDescriptors)); - } - - private static void measureCountriesV3Requests(File tarballFile) { - System.out.println("Starting measureCountriesV3Requests"); - long startedMillis = System.currentTimeMillis(); - Set<String> countries = new HashSet<>(); - long countedExtraInfoDescriptors = 0; - DescriptorReader descriptorReader = - DescriptorSourceFactory.createDescriptorReader(); - descriptorReader.addTarball(tarballFile); - Iterator<DescriptorFile> descriptorFiles = - descriptorReader.readDescriptors(); - while (descriptorFiles.hasNext()) { - DescriptorFile descriptorFile = descriptorFiles.next(); - for (Descriptor descriptor : descriptorFile.getDescriptors()) { - if (!(descriptor instanceof ExtraInfoDescriptor)) { - continue; - } - ExtraInfoDescriptor extraInfoDescriptor = - (ExtraInfoDescriptor) descriptor; - SortedMap<String, Integer> dirreqV3Reqs = - extraInfoDescriptor.getDirreqV3Reqs(); - if (dirreqV3Reqs != null) { - countries.addAll(dirreqV3Reqs.keySet()); - } - countedExtraInfoDescriptors++; - } - } - long endedMillis = System.currentTimeMillis(); - System.out.println("Ending measureCountriesV3Requests"); - System.out.printf("Total time: %d millis%n", - endedMillis - startedMillis); - System.out.printf("Processed extra-info descriptors: %d%n", - countedExtraInfoDescriptors); - System.out.printf("Number of countries: %d%n", - countries.size()); - System.out.printf("Time per extra-info descriptor: %.6f millis%n", - ((double) (endedMillis - startedMillis)) - / ((double) countedExtraInfoDescriptors)); - } - - private static void measureAverageRelaysExit( - File tarballFileOrDirectory) { - System.out.println("Starting measureAverageRelaysExit"); - long startedMillis = System.currentTimeMillis(); - long totalRelaysWithExitFlag = 0L, totalRelays = 0L, - countedConsensuses = 0L; - DescriptorReader descriptorReader = - DescriptorSourceFactory.createDescriptorReader(); - descriptorReader.addTarball(tarballFileOrDirectory); - descriptorReader.addDirectory(tarballFileOrDirectory); - Iterator<DescriptorFile> descriptorFiles = - descriptorReader.readDescriptors(); - while (descriptorFiles.hasNext()) { - DescriptorFile descriptorFile = descriptorFiles.next(); - for (Descriptor descriptor : descriptorFile.getDescriptors()) { - if (!(descriptor instanceof RelayNetworkStatusConsensus)) { - continue; - } - RelayNetworkStatusConsensus consensus = - (RelayNetworkStatusConsensus) descriptor; - for (NetworkStatusEntry entry : - consensus.getStatusEntries().values()) { - if (entry.getFlags().contains("Exit")) { - totalRelaysWithExitFlag++; - } - totalRelays++; - } - countedConsensuses++; - } - } - long endedMillis = System.currentTimeMillis(); - System.out.println("Ending measureAverageRelaysExit"); - System.out.printf("Total time: %d millis%n", - endedMillis - startedMillis); - System.out.printf("Processed consensuses: %d%n", countedConsensuses); - System.out.printf("Total number of status entries: %d%n", - totalRelays); - System.out.printf("Total number of status entries with Exit flag: " - + "%d%n", totalRelaysWithExitFlag); - System.out.printf("Average number of relays with Exit Flag: %.2f%n", - (double) totalRelaysWithExitFlag / (double) totalRelays); - System.out.printf("Time per consensus: %.6f millis%n", - ((double) (endedMillis - startedMillis)) - / ((double) countedConsensuses)); - } - - private static void measureFractionRelaysExit80Microdescriptors( - File tarballFile) { - System.out.println("Starting " - + "measureFractionRelaysExit80Microdescriptors"); - long startedMillis = System.currentTimeMillis(); - long totalRelaysWithExitFlag = 0L, countedMicrodescriptors = 0L; - DescriptorReader descriptorReader = - DescriptorSourceFactory.createDescriptorReader(); - descriptorReader.addTarball(tarballFile); - Iterator<DescriptorFile> descriptorFiles = - descriptorReader.readDescriptors(); - while (descriptorFiles.hasNext()) { - DescriptorFile descriptorFile = descriptorFiles.next(); - for (Descriptor descriptor : descriptorFile.getDescriptors()) { - if (!(descriptor instanceof Microdescriptor)) { - continue; - } - countedMicrodescriptors++; - Microdescriptor microdescriptor = - (Microdescriptor) descriptor; - String defaultPolicy = microdescriptor.getDefaultPolicy(); - if (defaultPolicy == null) { - continue; - } - boolean accept = "accept".equals( - microdescriptor.getDefaultPolicy()); - for (String ports : microdescriptor.getPortList().split(",")) { - if (ports.contains("-")) { - String[] parts = ports.split("-"); - int from = Integer.parseInt(parts[0]); - int to = Integer.parseInt(parts[1]); - if (from <= 80 && to >= 80) { - if (accept) { - totalRelaysWithExitFlag++; - } - } else if (to > 80) { - if (!accept) { - totalRelaysWithExitFlag++; - } - break; - } - } else if ("80".equals(ports)) { - if (accept) { - totalRelaysWithExitFlag++; - } - break; - } - } - } - } - long endedMillis = System.currentTimeMillis(); - System.out.println("Ending " - + "measureFractionRelaysExit80Microdescriptors"); - System.out.printf("Total time: %d millis%n", - endedMillis - startedMillis); - System.out.printf("Processed microdescriptors: %d%n", - countedMicrodescriptors); - System.out.printf("Total number of microdescriptors that exit to 80: " - + "%d%n", totalRelaysWithExitFlag); - System.out.printf("Average number of relays that exit to 80: %.2f%n", - (double) totalRelaysWithExitFlag - / (double) countedMicrodescriptors); - System.out.printf("Time per microdescriptor: %.6f millis%n", - ((double) (endedMillis - startedMillis)) - / ((double) countedMicrodescriptors)); - } -} - diff --git a/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java b/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java deleted file mode 100644 index 0847e13..0000000 --- a/test/org/torproject/descriptor/impl/BridgeNetworkStatusTest.java +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; -import org.torproject.descriptor.BridgeNetworkStatus; -import org.torproject.descriptor.DescriptorParseException; - -/* Test parsing of bridge network statuses. Some of the parsing code is - * already tested in the consensus/vote-parsing tests. */ -public class BridgeNetworkStatusTest { - - /* Helper class to build a bridge network status based on default data - * and modifications requested by test methods. */ - private static class StatusBuilder { - private String fileName = "20151121-173936-" - + "4A0CCD2DDC7995083D73F5D667100C8A5831F16D"; - private static BridgeNetworkStatus - createWithFileName(String fileName) - throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - sb.fileName = fileName; - return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, - true); - } - private String publishedLine = "published 2015-11-21 17:39:36"; - private static BridgeNetworkStatus - createWithPublishedLine(String line) - throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - sb.publishedLine = line; - return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, - true); - } - private String flagThresholdsLine = "flag-thresholds " - + "stable-uptime=3105080 stable-mtbf=2450615 fast-speed=55000 " - + "guard-wfu=98.000% guard-tk=691200 guard-bw-inc-exits=337000 " - + "guard-bw-exc-exits=339000 enough-mtbf=1 " - + "ignoring-advertised-bws=0"; - private static BridgeNetworkStatus - createWithFlagThresholdsLine(String line) - throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - sb.flagThresholdsLine = line; - return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, - true); - } - private List<String> statusEntries = new ArrayList<>(); - private String unrecognizedHeaderLine = null; - protected static BridgeNetworkStatus - createWithUnrecognizedHeaderLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - sb.unrecognizedHeaderLine = line; - return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, - failUnrecognizedDescriptorLines); - } - private String unrecognizedStatusEntryLine = null; - protected static BridgeNetworkStatus - createWithUnrecognizedStatusEntryLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - sb.unrecognizedStatusEntryLine = line; - return new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, - failUnrecognizedDescriptorLines); - } - - private StatusBuilder() { - this.statusEntries.add("r Unnamed ABk0wg4j6BLCdZKleVtmNrfzJGI " - + "bh7gVU1Cz6+JG+7j4qGsF4prDi8 2015-11-21 15:46:25 " - + "10.153.163.200 443 0\ns Fast Running Stable Valid\n" - + "w Bandwidth=264\np reject 1-65535"); - } - private byte[] buildStatus() { - StringBuilder sb = new StringBuilder(); - this.appendHeader(sb); - this.appendStatusEntries(sb); - return sb.toString().getBytes(); - } - private void appendHeader(StringBuilder sb) { - if (this.publishedLine != null) { - sb.append(this.publishedLine).append("\n"); - } - if (this.flagThresholdsLine != null) { - sb.append(this.flagThresholdsLine).append("\n"); - } - if (this.unrecognizedHeaderLine != null) { - sb.append(this.unrecognizedHeaderLine).append("\n"); - } - } - private void appendStatusEntries(StringBuilder sb) { - for (String statusEntry : this.statusEntries) { - sb.append(statusEntry).append("\n"); - } - if (this.unrecognizedStatusEntryLine != null) { - sb.append(this.unrecognizedStatusEntryLine).append("\n"); - } - } - } - - @Test() - public void testSampleStatus() throws DescriptorParseException { - StatusBuilder sb = new StatusBuilder(); - BridgeNetworkStatus status = - new BridgeNetworkStatusImpl(sb.buildStatus(), sb.fileName, true); - assertEquals(1448127576000L, status.getPublishedMillis()); - assertEquals(3105080L, status.getStableUptime()); - assertEquals(2450615L, status.getStableMtbf()); - assertEquals(55000L, status.getFastBandwidth()); - assertEquals(98.0, status.getGuardWfu(), 0.001); - assertEquals(691200L, status.getGuardTk()); - assertEquals(337000L, status.getGuardBandwidthIncludingExits()); - assertEquals(339000L, status.getGuardBandwidthExcludingExits()); - assertEquals(1, status.getEnoughMtbfInfo()); - assertEquals(0, status.getIgnoringAdvertisedBws()); - assertEquals(264, status.getStatusEntries().get( - "001934C20E23E812C27592A5795B6636B7F32462").getBandwidth()); - assertTrue(status.getUnrecognizedLines().isEmpty()); - } - - @Test() - public void testPublishedNoLine() throws DescriptorParseException { - BridgeNetworkStatus status = - StatusBuilder.createWithPublishedLine(null); - assertEquals(1448127576000L, status.getPublishedMillis()); - } - - @Test() - public void testFlagThresholdsNoLine() throws DescriptorParseException { - BridgeNetworkStatus status = - StatusBuilder.createWithFlagThresholdsLine(null); - assertEquals(-1L, status.getStableUptime()); - assertEquals(-1L, status.getStableMtbf()); - assertEquals(-1L, status.getFastBandwidth()); - assertEquals(-1.0, status.getGuardWfu(), 0.001); - assertEquals(-1L, status.getGuardTk()); - assertEquals(-1L, status.getGuardBandwidthIncludingExits()); - assertEquals(-1L, status.getGuardBandwidthExcludingExits()); - assertEquals(-1, status.getEnoughMtbfInfo()); - assertEquals(-1, status.getIgnoringAdvertisedBws()); - } -} - diff --git a/test/org/torproject/descriptor/impl/ConsensusBuilder.java b/test/org/torproject/descriptor/impl/ConsensusBuilder.java deleted file mode 100644 index 29a2d47..0000000 --- a/test/org/torproject/descriptor/impl/ConsensusBuilder.java +++ /dev/null @@ -1,321 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import java.util.ArrayList; -import java.util.List; - -import org.torproject.descriptor.RelayNetworkStatusConsensus; - -/* Helper class to build a consensus based on default data and - * modifications requested by test methods. */ -public class ConsensusBuilder { - String networkStatusVersionLine = "network-status-version 3"; - protected static RelayNetworkStatusConsensus - createWithNetworkStatusVersionLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.networkStatusVersionLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String voteStatusLine = "vote-status consensus"; - protected static RelayNetworkStatusConsensus - createWithVoteStatusLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.voteStatusLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String consensusMethodLine = "consensus-method 11"; - protected static RelayNetworkStatusConsensus - createWithConsensusMethodLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.consensusMethodLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String validAfterLine = "valid-after 2011-11-30 09:00:00"; - protected static RelayNetworkStatusConsensus - createWithValidAfterLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.validAfterLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String freshUntilLine = "fresh-until 2011-11-30 10:00:00"; - protected static RelayNetworkStatusConsensus - createWithFreshUntilLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.freshUntilLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String validUntilLine = "valid-until 2011-11-30 12:00:00"; - protected static RelayNetworkStatusConsensus - createWithValidUntilLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.validUntilLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String votingDelayLine = "voting-delay 300 300"; - protected static RelayNetworkStatusConsensus - createWithVotingDelayLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.votingDelayLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - String clientVersionsLine = "client-versions 0.2.1.31," - + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; - protected static RelayNetworkStatusConsensus - createWithClientVersionsLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.clientVersionsLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - String serverVersionsLine = "server-versions 0.2.1.31," - + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; - protected static RelayNetworkStatusConsensus - createWithServerVersionsLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.serverVersionsLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String packageLines = null; - protected static RelayNetworkStatusConsensus - createWithPackageLines(String lines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.packageLines = lines; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String knownFlagsLine = "known-flags Authority BadExit Exit " - + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid"; - protected static RelayNetworkStatusConsensus - createWithKnownFlagsLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.knownFlagsLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String paramsLine = "params " - + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 " - + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 " - + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 " - + "cbtquantile=80 circwindow=1000 refuseunknownexits=1"; - protected static RelayNetworkStatusConsensus - createWithParamsLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.paramsLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - List<String> dirSources = new ArrayList<>(); - List<String> statusEntries = new ArrayList<>(); - private String directoryFooterLine = "directory-footer"; - protected void setDirectoryFooterLine(String line) { - this.directoryFooterLine = line; - } - protected static RelayNetworkStatusConsensus - createWithDirectoryFooterLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.directoryFooterLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private String bandwidthWeightsLine = "bandwidth-weights Wbd=285 " - + "Wbe=0 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=1021 Wee=10000 " - + "Weg=1021 Wem=10000 Wgb=10000 Wgd=8694 Wgg=10000 Wgm=10000 " - + "Wmb=10000 Wmd=285 Wme=0 Wmg=0 Wmm=10000"; - protected void setBandwidthWeightsLine(String line) { - this.bandwidthWeightsLine = line; - } - protected static RelayNetworkStatusConsensus - createWithBandwidthWeightsLine(String line) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.bandwidthWeightsLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - private List<String> directorySignatures = new ArrayList<>(); - protected void addDirectorySignature(String directorySignatureString) { - this.directorySignatures.add(directorySignatureString); - } - private String unrecognizedHeaderLine = null; - protected static RelayNetworkStatusConsensus - createWithUnrecognizedHeaderLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.unrecognizedHeaderLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedDirSourceLine = null; - protected static RelayNetworkStatusConsensus - createWithUnrecognizedDirSourceLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.unrecognizedDirSourceLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedStatusEntryLine = null; - protected static RelayNetworkStatusConsensus - createWithUnrecognizedStatusEntryLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.unrecognizedStatusEntryLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedFooterLine = null; - protected static RelayNetworkStatusConsensus - createWithUnrecognizedFooterLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.unrecognizedFooterLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedDirectorySignatureLine = null; - protected static RelayNetworkStatusConsensus - createWithUnrecognizedDirectorySignatureLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.unrecognizedDirectorySignatureLine = line; - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - failUnrecognizedDescriptorLines); - } - - protected ConsensusBuilder() { - this.dirSources.add("dir-source tor26 " - + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 86.59.21.38 " - + "86.59.21.38 80 443\ncontact Peter Palfrader\nvote-digest " - + "0333880AA67ED7E07C11108656D0C8D6DD1C7E5D"); - this.dirSources.add("dir-source ides " - + "27B6B5996C426270A5C95488AA5BCEB6BCC86956 216.224.124.114 " - + "216.224.124.114 9030 9090\ncontact Mike Perry " - + "<mikeperryTAfsckedTODorg>\nvote-digest " - + "1A8827ECD53184F7A771EFA9B3D30DC473FE8670"); - this.statusEntries.add("r ANONIONROUTER " - + "AHhuQ8zFQJdT8l42Axxc6m6kNwI yEMZ5B/JQixNZgC1+2rLe0pR9rU " - + "2011-11-30 02:52:58 93.128.66.111 24051 24052\ns Exit Fast " - + "Named Running V2Dir Valid\nv Tor 0.2.2.34\nw " - + "Bandwidth=1100\np reject 25,119,135-139,6881-6999"); - this.statusEntries.add("r Magellan AHlabo2RwnD8I7MPOIpJVVPgGJQ " - + "rB/7uzI4mU38bZ9cSXEy+Z/4Cuk 2011-11-30 05:37:35 " - + "188.177.149.216 9001 9030\ns Fast Named Running V2Dir " - + "Valid\nv Tor 0.2.2.34\nw Bandwidth=367\np reject 1-65535"); - this.directorySignatures.add("directory-signature " - + "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " - + "3509BA5A624403A905C74DA5C8A0CEC9E0D3AF86\n" - + "-----BEGIN SIGNATURE-----\n" - + "NYRcTWAMRiYYiGW0hIbzeZKU6sefg98AwwXrQUCudO8wfA1cfgttTDoscB9I" - + "TbOY\nr+c30jV/qQCMamTAEDGgJTw8KghI32vytupKallI1EjCOF8UvL1UnA" - + "LgpaR7sZ3W\n7WQZVVrWDtnYaULOEKfwnGnRC7WwE+YRSysbzwwCVs0=\n" - + "-----END SIGNATURE-----"); - this.directorySignatures.add("directory-signature " - + "27B6B5996C426270A5C95488AA5BCEB6BCC86956 " - + "D5C30C15BB3F1DA27669C2D88439939E8F418FCF\n" - + "-----BEGIN SIGNATURE-----\n" - + "DzFPj3vyYrCv0W3r8qDPJPlmeLnadY+drjWkdOqO66Ih/hAWBb9KcBJAX1sX" - + "aDA7\n/iSaDhduBXuJdcu8lbmMP8d6uYBdRjHXqWDXySUZAkSfPB4JJPNGvf" - + "oQA/qeby7E\n5374pPPL6WwCLJHkKtk21S9oHDmFBdlZq7JWQelWlVM=\n" - + "-----END SIGNATURE-----"); - } - protected byte[] buildConsensus() { - StringBuilder sb = new StringBuilder(); - this.appendHeader(sb); - this.appendDirSources(sb); - this.appendStatusEntries(sb); - this.appendFooter(sb); - this.appendDirectorySignatures(sb); - return sb.toString().getBytes(); - } - private void appendHeader(StringBuilder sb) { - if (this.networkStatusVersionLine != null) { - sb.append(this.networkStatusVersionLine).append("\n"); - } - if (this.voteStatusLine != null) { - sb.append(this.voteStatusLine).append("\n"); - } - if (this.consensusMethodLine != null) { - sb.append(this.consensusMethodLine).append("\n"); - } - if (this.validAfterLine != null) { - sb.append(this.validAfterLine).append("\n"); - } - if (this.freshUntilLine != null) { - sb.append(this.freshUntilLine).append("\n"); - } - if (this.validUntilLine != null) { - sb.append(this.validUntilLine).append("\n"); - } - if (this.votingDelayLine != null) { - sb.append(this.votingDelayLine).append("\n"); - } - if (this.clientVersionsLine != null) { - sb.append(this.clientVersionsLine).append("\n"); - } - if (this.serverVersionsLine != null) { - sb.append(this.serverVersionsLine).append("\n"); - } - if (this.packageLines != null) { - sb.append(this.packageLines).append("\n"); - } - if (this.knownFlagsLine != null) { - sb.append(this.knownFlagsLine).append("\n"); - } - if (this.paramsLine != null) { - sb.append(this.paramsLine).append("\n"); - } - if (this.unrecognizedHeaderLine != null) { - sb.append(this.unrecognizedHeaderLine).append("\n"); - } - } - private void appendDirSources(StringBuilder sb) { - for (String dirSource : this.dirSources) { - sb.append(dirSource).append("\n"); - } - if (this.unrecognizedDirSourceLine != null) { - sb.append(this.unrecognizedDirSourceLine).append("\n"); - } - } - private void appendStatusEntries(StringBuilder sb) { - for (String statusEntry : this.statusEntries) { - sb.append(statusEntry).append("\n"); - } - if (this.unrecognizedStatusEntryLine != null) { - sb.append(this.unrecognizedStatusEntryLine).append("\n"); - } - } - private void appendFooter(StringBuilder sb) { - if (this.directoryFooterLine != null) { - sb.append(this.directoryFooterLine).append("\n"); - } - if (this.bandwidthWeightsLine != null) { - sb.append(this.bandwidthWeightsLine).append("\n"); - } - if (this.unrecognizedFooterLine != null) { - sb.append(this.unrecognizedFooterLine).append("\n"); - } - } - private void appendDirectorySignatures(StringBuilder sb) { - for (String directorySignature : this.directorySignatures) { - sb.append(directorySignature).append("\n"); - } - if (this.unrecognizedDirectorySignatureLine != null) { - sb.append(this.unrecognizedDirectorySignatureLine).append("\n"); - } - } -} - diff --git a/test/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java b/test/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java deleted file mode 100644 index fde8e57..0000000 --- a/test/org/torproject/descriptor/impl/DescriptorCollectorImplTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.util.SortedMap; - -import org.junit.Test; - -public class DescriptorCollectorImplTest { - - private static final String REMOTE_DIRECTORY_CONSENSUSES = - "/recent/relay-descriptors/consensuses/"; - - @Test() - public void testOneFile() { - String remoteFilename = "2015-05-24-12-00-00-consensus"; - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" - + "<a href="" + remoteFilename + "">" - + "2015-05-24-12-00-00-consensus</a></td>" - + "<td align="right">24-May-2015 12:08 </td>" - + "<td align="right">1.5M</td><td> </td></tr>"; - SortedMap<String, Long> remoteFiles = - new DescriptorCollectorImpl().parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNotNull(remoteFiles); - assertSame(1, remoteFiles.size()); - assertEquals(REMOTE_DIRECTORY_CONSENSUSES + remoteFilename, - remoteFiles.firstKey()); - assertEquals((Long) 1432469280000L, - remoteFiles.get(remoteFiles.firstKey())); - } - - @Test() - public void testSameFileTwoTimestampsLastWins() { - String remoteFilename = "2015-05-24-12-00-00-consensus"; - String firstTimestamp = "24-May-2015 12:04"; - String secondTimestamp = "24-May-2015 12:08"; - String lineFormat = "<tr><td valign="top">" - + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" - + "<a href="%s">2015-05-24-12-00-00-consensus</a></td>" - + "<td align="right">%s </td>" - + "<td align="right">1.5M</td><td> </td></tr>\n"; - String directoryListing = String.format(lineFormat + lineFormat, - remoteFilename, firstTimestamp, remoteFilename, secondTimestamp); - SortedMap<String, Long> remoteFiles = - new DescriptorCollectorImpl().parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNotNull(remoteFiles); - assertSame(1, remoteFiles.size()); - assertEquals(REMOTE_DIRECTORY_CONSENSUSES + remoteFilename, - remoteFiles.firstKey()); - assertEquals((Long) 1432469280000L, - remoteFiles.get(remoteFiles.firstKey())); - } - - @Test() - public void testSubDirectoryOnly() { - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/folder.gif" alt="[DIR]"></td><td>" - + "<a href="subdir/">subdir/</a></td>" - + "<td align="right">27-May-2015 14:07 </td>" - + "<td align="right"> - </td><td> </td></tr>"; - DescriptorCollectorImpl collector = new DescriptorCollectorImpl(); - SortedMap<String, Long> remoteFiles = collector.parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNotNull(remoteFiles); - assertTrue(remoteFiles.isEmpty()); - } - - @Test() - public void testParentDirectoryOnly() { - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/back.gif" alt="[DIR]"></td><td>" - + "<a href="/recent/relay-descriptors/">Parent Directory</a>" - + "</td><td> </td><td align="right"> - </td>" - + "<td> </td></tr>"; - DescriptorCollectorImpl collector = new DescriptorCollectorImpl(); - SortedMap<String, Long> remoteFiles = collector.parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNotNull(remoteFiles); - assertTrue(remoteFiles.isEmpty()); - } - - @Test() - public void testUnexpectedDateFormat() { - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" - + "<a href="2015-05-24-12-00-00-consensus">" - + "2015-05-24-12-00-00-consensus</a></td>" - + "<td align="right">2015-05-24 12:08 </td>" - + "<td align="right">1.5M</td><td> </td></tr>"; - SortedMap<String, Long> remoteFiles = - new DescriptorCollectorImpl().parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNotNull(remoteFiles); - assertTrue(remoteFiles.isEmpty()); - } - - @Test() - public void testInvalidDate() { - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" - + "<a href="2015-05-24-12-00-00-consensus">" - + "2015-05-24-12-00-00-consensus</a></td>" - + "<td align="right">34-May-2015 12:08 </td>" - + "<td align="right">1.5M</td><td> </td></tr>"; - SortedMap<String, Long> remoteFiles = - new DescriptorCollectorImpl().parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNull(remoteFiles); - } - - @Test() - public void testInvalidLocaleDe() { - String directoryListing = "<tr><td valign="top">" - + "<img src="/icons/unknown.gif" alt="[ ]"></td><td>" - + "<a href="2015-05-24-12-00-00-consensus">" - + "2015-05-24-12-00-00-consensus</a></td>" - + "<td align="right">24-Mai-2015 12:08 </td>" - + "<td align="right">1.5M</td><td> </td></tr>"; - SortedMap<String, Long> remoteFiles = - new DescriptorCollectorImpl().parseDirectoryListing( - REMOTE_DIRECTORY_CONSENSUSES, directoryListing); - assertNull(remoteFiles); - } -} - diff --git a/test/org/torproject/descriptor/impl/ExitListImplTest.java b/test/org/torproject/descriptor/impl/ExitListImplTest.java deleted file mode 100644 index a563857..0000000 --- a/test/org/torproject/descriptor/impl/ExitListImplTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExitListEntry; - -public class ExitListImplTest { - - @Test() - public void testAnnotatedInput() throws Exception { - ExitListImpl result = new ExitListImpl((tordnselAnnotation + input) - .getBytes("US-ASCII"), fileName, false); - assertEquals("Expected one annotation.", 1, - result.getAnnotations().size()); - assertEquals(tordnselAnnotation.substring(0, 18), - result.getAnnotations().get(0)); - assertEquals(1441065722000L, result.getDownloadedMillis()); - assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), - result.getUnrecognizedLines().isEmpty()); - assertEquals("Found: " + result.getExitListEntries(), 7, - result.getExitListEntries().size()); - assertEquals("Found: " + result.getEntries(), 5, - result.getEntries().size()); - } - - @Test() - public void testMultipleOldExitAddresses() throws Exception { - ExitListImpl result = new ExitListImpl( - (tordnselAnnotation + multiExitAddressInput) - .getBytes("US-ASCII"), fileName, false); - assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), - result.getUnrecognizedLines().isEmpty()); - assertEquals("Found: " + result.getExitListEntries(), - 3, result.getExitListEntries().size()); - Map<String, Long> testMap = new HashMap(); - testMap.put("81.7.17.171", 1441044592000L); - testMap.put("81.7.17.172", 1441044652000L); - testMap.put("81.7.17.173", 1441044712000L); - for (ExitListEntry ele : result.getExitListEntries()) { - Map<String, Long> map = ele.getExitAddresses(); - assertEquals("Found: " + map, 1, map.size()); - Map.Entry<String, Long> ea = map.entrySet().iterator().next(); - assertTrue("Map: " + testMap, - testMap.keySet().contains(ea.getKey())); - assertTrue("Map: " + testMap + " exitaddress: " + ea, - testMap.values().contains(ea.getValue())); - testMap.remove(ea.getKey()); - } - assertTrue("Map: " + testMap, testMap.isEmpty()); - } - - @Test() - public void testMultipleExitAddresses() throws Exception { - ExitListImpl result = new ExitListImpl( - (tordnselAnnotation + multiExitAddressInput) - .getBytes("US-ASCII"), fileName, false); - assertTrue("Unrecognized lines: " + result.getUnrecognizedLines(), - result.getUnrecognizedLines().isEmpty()); - Map<String, Long> map = result.getEntries() - .iterator().next().getExitAddresses(); - assertEquals("Found: " + map, 3, map.size()); - assertTrue("Map: " + map, map.containsKey("81.7.17.171")); - assertTrue("Map: " + map, map.containsKey("81.7.17.172")); - assertTrue("Map: " + map, map.containsKey("81.7.17.173")); - } - - @Test(expected = DescriptorParseException.class) - public void testInsufficientInput0() throws Exception { - new ExitListImpl((tordnselAnnotation + insufficientInput[0]) - .getBytes("US-ASCII"), fileName, false); - } - - @Test(expected = DescriptorParseException.class) - public void testInsufficientInput1() throws Exception { - new ExitListImpl((tordnselAnnotation + insufficientInput[1]) - .getBytes("US-ASCII"), fileName, false); - } - - private static final String tordnselAnnotation = "@type tordnsel 1.0\n"; - private static final String fileName = "2015-09-01-00-02-02"; - private static final String[] insufficientInput = new String[] { - "Downloaded 2015-09-01 00:02:02\n" - + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" - + "Published 2015-08-31 16:17:30\n" - + "LastStatus 2015-08-31 17:03:18\n", - "Downloaded 2015-09-01 00:02:02\n" - + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" - + "LastStatus 2015-08-31 17:03:18\n" - + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" }; - - private static final String multiExitAddressInput = - "Downloaded 2015-09-01 00:02:02\n" - + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" - + "Published 2015-08-31 16:17:30\n" - + "LastStatus 2015-08-31 17:03:18\n" - + "ExitAddress 81.7.17.171 2015-08-31 18:09:52\n" - + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" - + "ExitAddress 81.7.17.173 2015-08-31 18:11:52\n"; - private static final String input = "Downloaded 2015-09-01 00:02:02\n" - + "ExitNode 0011BD2485AD45D984EC4159C88FC066E5E3300E\n" - + "Published 2015-08-31 16:17:30\n" - + "LastStatus 2015-08-31 17:03:18\n" - + "ExitAddress 162.247.72.201 2015-08-31 17:09:23\n" - + "ExitNode 0098C475875ABC4AA864738B1D1079F711C38287\n" - + "Published 2015-08-31 13:59:24\n" - + "LastStatus 2015-08-31 15:03:20\n" - + "ExitAddress 162.248.160.151 2015-08-31 15:07:27\n" - + "ExitNode 00C4B4731658D3B4987132A3F77100CFCB190D97\n" - + "Published 2015-08-31 17:47:52\n" - + "LastStatus 2015-08-31 18:03:17\n" - + "ExitAddress 81.7.17.171 2015-08-31 18:09:52\n" - + "ExitAddress 81.7.17.172 2015-08-31 18:10:52\n" - + "ExitAddress 81.7.17.173 2015-08-31 18:11:52\n" - + "ExitNode 00F2D93EBAF2F51D6EE4DCB0F37D91D72F824B16\n" - + "Published 2015-08-31 14:39:05\n" - + "LastStatus 2015-08-31 16:02:18\n" - + "ExitAddress 23.239.18.57 2015-08-31 16:06:07\n" - + "ExitNode 011B1D1E876B2C835D01FB9D407F2E00B28077F6\n" - + "Published 2015-08-31 05:14:35\n" - + "LastStatus 2015-08-31 06:03:29\n" - + "ExitAddress 104.131.51.150 2015-08-31 06:04:07\n"; -} - diff --git a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java deleted file mode 100644 index 6843196..0000000 --- a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java +++ /dev/null @@ -1,1737 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.SortedMap; - -import org.junit.Test; -import org.torproject.descriptor.BridgeExtraInfoDescriptor; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.ExtraInfoDescriptor; -import org.torproject.descriptor.RelayExtraInfoDescriptor; - -/* Test parsing of extra-info descriptors. */ -public class ExtraInfoDescriptorImplTest { - - /* Helper class to build a descriptor based on default data and - * modifications requested by test methods. */ - private static class DescriptorBuilder { - private String extraInfoLine = "extra-info chaoscomputerclub5 " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"; - private static ExtraInfoDescriptor createWithExtraInfoLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.extraInfoLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String publishedLine = "published 2012-02-11 09:08:36"; - private static ExtraInfoDescriptor createWithPublishedLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.publishedLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String writeHistoryLine = "write-history 2012-02-11 09:03:39 " - + "(900 s) 4713350144,4723824640,4710717440,4572675072"; - private static ExtraInfoDescriptor createWithWriteHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.writeHistoryLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String readHistoryLine = "read-history 2012-02-11 09:03:39 " - + "(900 s) 4707695616,4699666432,4650004480,4489718784"; - private static ExtraInfoDescriptor createWithReadHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.readHistoryLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String dirreqWriteHistoryLine = "dirreq-write-history " - + "2012-02-11 09:03:39 (900 s) 81281024,64996352,60625920," - + "67922944"; - private static ExtraInfoDescriptor createWithDirreqWriteHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.dirreqWriteHistoryLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String dirreqReadHistoryLine = "dirreq-read-history " - + "2012-02-11 09:03:39 (900 s) 17074176,16235520,16005120," - + "16209920"; - private static ExtraInfoDescriptor createWithDirreqReadHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.dirreqReadHistoryLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String geoipDbDigestLine = null; - private static ExtraInfoDescriptor createWithGeoipDbDigestLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.geoipDbDigestLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String geoip6DbDigestLine = null; - private static ExtraInfoDescriptor createWithGeoip6DbDigestLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.geoip6DbDigestLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String geoipStatsLines = null; - private static ExtraInfoDescriptor createWithGeoipStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.geoipStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String dirreqStatsLines = null; - private static ExtraInfoDescriptor createWithDirreqStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.dirreqStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String entryStatsLines = null; - private static ExtraInfoDescriptor createWithEntryStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.entryStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String cellStatsLines = null; - private static ExtraInfoDescriptor createWithCellStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.cellStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String connBiDirectLine = null; - private static ExtraInfoDescriptor createWithConnBiDirectLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.connBiDirectLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String exitStatsLines = null; - private static ExtraInfoDescriptor createWithExitStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.exitStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String bridgeStatsLines = null; - private static ExtraInfoDescriptor createWithBridgeStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.bridgeStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String hidservStatsLines = null; - private static ExtraInfoDescriptor createWithHidservStatsLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.hidservStatsLines = lines; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String unrecognizedLine = null; - private static ExtraInfoDescriptor createWithUnrecognizedLine( - String line, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.unrecognizedLine = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), - failUnrecognizedDescriptorLines); - } - private byte[] nonAsciiLineBytes = null; - private static ExtraInfoDescriptor createWithNonAsciiLineBytes( - byte[] lineBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.nonAsciiLineBytes = lineBytes; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), - failUnrecognizedDescriptorLines); - } - private String routerSignatureLines = "router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" - + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" - + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" - + "-----END SIGNATURE-----"; - private static ExtraInfoDescriptor createWithRouterSignatureLines( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.routerSignatureLines = line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private String identityEd25519Lines = null, - masterKeyEd25519Line = null, routerSigEd25519Line = null; - private static ExtraInfoDescriptor createWithEd25519Lines( - String identityEd25519Lines, String masterKeyEd25519Line, - String routerSigEd25519Line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.identityEd25519Lines = identityEd25519Lines; - db.masterKeyEd25519Line = masterKeyEd25519Line; - db.routerSigEd25519Line = routerSigEd25519Line; - return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - } - private byte[] buildDescriptor() { - StringBuilder sb = new StringBuilder(); - if (this.extraInfoLine != null) { - sb.append(this.extraInfoLine).append("\n"); - } - if (this.identityEd25519Lines != null) { - sb.append(this.identityEd25519Lines).append("\n"); - } - if (this.masterKeyEd25519Line != null) { - sb.append(this.masterKeyEd25519Line).append("\n"); - } - if (this.publishedLine != null) { - sb.append(this.publishedLine).append("\n"); - } - if (this.writeHistoryLine != null) { - sb.append(this.writeHistoryLine).append("\n"); - } - if (this.readHistoryLine != null) { - sb.append(this.readHistoryLine).append("\n"); - } - if (this.dirreqWriteHistoryLine != null) { - sb.append(this.dirreqWriteHistoryLine).append("\n"); - } - if (this.dirreqReadHistoryLine != null) { - sb.append(this.dirreqReadHistoryLine).append("\n"); - } - if (this.geoipDbDigestLine != null) { - sb.append(this.geoipDbDigestLine).append("\n"); - } - if (this.geoip6DbDigestLine != null) { - sb.append(this.geoip6DbDigestLine).append("\n"); - } - if (this.geoipStatsLines != null) { - sb.append(this.geoipStatsLines).append("\n"); - } - if (this.dirreqStatsLines != null) { - sb.append(this.dirreqStatsLines).append("\n"); - } - if (this.entryStatsLines != null) { - sb.append(this.entryStatsLines).append("\n"); - } - if (this.cellStatsLines != null) { - sb.append(this.cellStatsLines).append("\n"); - } - if (this.connBiDirectLine != null) { - sb.append(this.connBiDirectLine).append("\n"); - } - if (this.exitStatsLines != null) { - sb.append(this.exitStatsLines).append("\n"); - } - if (this.bridgeStatsLines != null) { - sb.append(this.bridgeStatsLines).append("\n"); - } - if (this.hidservStatsLines != null) { - sb.append(this.hidservStatsLines).append("\n"); - } - if (this.unrecognizedLine != null) { - sb.append(this.unrecognizedLine).append("\n"); - } - if (this.nonAsciiLineBytes != null) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(sb.toString().getBytes()); - baos.write(this.nonAsciiLineBytes); - baos.write("\n".getBytes()); - if (this.routerSignatureLines != null) { - baos.write(this.routerSignatureLines.getBytes()); - } - return baos.toByteArray(); - } catch (IOException e) { - return null; - } - } - if (this.routerSigEd25519Line != null) { - sb.append(this.routerSigEd25519Line).append("\n"); - } - if (this.routerSignatureLines != null) { - sb.append(this.routerSignatureLines).append("\n"); - } - return sb.toString().getBytes(); - } - } - - /* Helper class to build a set of geoip-stats lines based on default - * data and modifications requested by test methods. */ - private static class GeoipStatsBuilder { - private String geoipStartTimeLine = "geoip-start-time 2012-02-10 " - + "18:32:51"; - private static ExtraInfoDescriptor createWithGeoipStartTimeLine( - String line) throws DescriptorParseException { - GeoipStatsBuilder gsb = new GeoipStatsBuilder(); - gsb.geoipStartTimeLine = line; - return DescriptorBuilder.createWithGeoipStatsLines( - gsb.buildGeoipStatsLines()); - } - private String geoipClientOriginsLine = "geoip-client-origins " - + "de=1152,cn=896,us=712,it=504,ru=352,fr=208,gb=208,ir=200"; - private static ExtraInfoDescriptor createWithGeoipClientOriginsLine( - String line) throws DescriptorParseException { - GeoipStatsBuilder gsb = new GeoipStatsBuilder(); - gsb.geoipClientOriginsLine = line; - return DescriptorBuilder.createWithGeoipStatsLines( - gsb.buildGeoipStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithGeoipStatsLines( - new GeoipStatsBuilder().buildGeoipStatsLines()); - } - private String buildGeoipStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.geoipStartTimeLine != null) { - sb.append(this.geoipStartTimeLine).append("\n"); - } - if (this.geoipClientOriginsLine != null) { - sb.append(this.geoipClientOriginsLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of dirreq-stats lines based on default - * data and modifications requested by test methods. */ - private static class DirreqStatsBuilder { - private String dirreqStatsEndLine = "dirreq-stats-end 2012-02-11 " - + "00:59:53 (86400 s)"; - private static ExtraInfoDescriptor createWithDirreqStatsEndLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqStatsEndLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3IpsLine = "dirreq-v3-ips us=1544,de=1056," - + "it=1032,fr=784,es=640,ru=440,br=312,gb=272,kr=224,sy=192"; - private static ExtraInfoDescriptor createWithDirreqV3IpsLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3IpsLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2IpsLine = "dirreq-v2-ips "; - private static ExtraInfoDescriptor createWithDirreqV2IpsLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2IpsLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3ReqsLine = "dirreq-v3-reqs us=1744,de=1224," - + "it=1080,fr=832,es=664,ru=536,br=344,gb=296,kr=272,in=216"; - private static ExtraInfoDescriptor createWithDirreqV3ReqsLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3ReqsLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2ReqsLine = "dirreq-v2-reqs "; - private static ExtraInfoDescriptor createWithDirreqV2ReqsLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2ReqsLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3RespLine = "dirreq-v3-resp ok=10848," - + "not-enough-sigs=8,unavailable=0,not-found=0,not-modified=0," - + "busy=80"; - private static ExtraInfoDescriptor createWithDirreqV3RespLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3RespLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2RespLine = "dirreq-v2-resp ok=0,unavailable=0," - + "not-found=1576,not-modified=0,busy=0"; - private static ExtraInfoDescriptor createWithDirreqV2RespLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2RespLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2ShareLine = "dirreq-v2-share 0.37%"; - private static ExtraInfoDescriptor createWithDirreqV2ShareLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2ShareLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3ShareLine = "dirreq-v3-share 0.37%"; - private static ExtraInfoDescriptor createWithDirreqV3ShareLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3ShareLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3DirectDlLine = "dirreq-v3-direct-dl " - + "complete=36,timeout=4,running=0,min=7538,d1=20224,d2=28950," - + "q1=40969,d3=55786,d4=145813,md=199164,d6=267230,d7=480900," - + "q3=481049,d8=531276,d9=778086,max=15079428"; - private static ExtraInfoDescriptor createWithDirreqV3DirectDlLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3DirectDlLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2DirectDlLine = "dirreq-v2-direct-dl " - + "complete=0,timeout=0,running=0"; - private static ExtraInfoDescriptor createWithDirreqV2DirectDlLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2DirectDlLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV3TunneledDlLine = "dirreq-v3-tunneled-dl " - + "complete=10608,timeout=204,running=4,min=507,d1=20399," - + "d2=27588,q1=29292,d3=30889,d4=40624,md=59967,d6=103333," - + "d7=161170,q3=209415,d8=256711,d9=452503,max=23417777"; - private static ExtraInfoDescriptor createWithDirreqV3TunneledDlLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV3TunneledDlLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private String dirreqV2TunneledDlLine = "dirreq-v2-tunneled-dl " - + "complete=0,timeout=0,running=0"; - private static ExtraInfoDescriptor createWithDirreqV2TunneledDlLine( - String line) throws DescriptorParseException { - DirreqStatsBuilder dsb = new DirreqStatsBuilder(); - dsb.dirreqV2TunneledDlLine = line; - return DescriptorBuilder.createWithDirreqStatsLines( - dsb.buildDirreqStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithDirreqStatsLines( - new DirreqStatsBuilder().buildDirreqStatsLines()); - } - private String buildDirreqStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.dirreqStatsEndLine != null) { - sb.append(this.dirreqStatsEndLine).append("\n"); - } - if (this.dirreqV3IpsLine != null) { - sb.append(this.dirreqV3IpsLine).append("\n"); - } - if (this.dirreqV2IpsLine != null) { - sb.append(this.dirreqV2IpsLine).append("\n"); - } - if (this.dirreqV3ReqsLine != null) { - sb.append(this.dirreqV3ReqsLine).append("\n"); - } - if (this.dirreqV2ReqsLine != null) { - sb.append(this.dirreqV2ReqsLine).append("\n"); - } - if (this.dirreqV3RespLine != null) { - sb.append(this.dirreqV3RespLine).append("\n"); - } - if (this.dirreqV2RespLine != null) { - sb.append(this.dirreqV2RespLine).append("\n"); - } - if (this.dirreqV2ShareLine != null) { - sb.append(this.dirreqV2ShareLine).append("\n"); - } - if (this.dirreqV3ShareLine != null) { - sb.append(this.dirreqV3ShareLine).append("\n"); - } - if (this.dirreqV3DirectDlLine != null) { - sb.append(this.dirreqV3DirectDlLine).append("\n"); - } - if (this.dirreqV2DirectDlLine != null) { - sb.append(this.dirreqV2DirectDlLine).append("\n"); - } - if (this.dirreqV3TunneledDlLine != null) { - sb.append(this.dirreqV3TunneledDlLine).append("\n"); - } - if (this.dirreqV2TunneledDlLine != null) { - sb.append(this.dirreqV2TunneledDlLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of entry-stats lines based on default - * data and modifications requested by test methods. */ - private static class EntryStatsBuilder { - private String entryStatsEndLine = "entry-stats-end 2012-02-11 " - + "01:59:39 (86400 s)"; - private static ExtraInfoDescriptor createWithEntryStatsEndLine( - String line) throws DescriptorParseException { - EntryStatsBuilder esb = new EntryStatsBuilder(); - esb.entryStatsEndLine = line; - return DescriptorBuilder.createWithEntryStatsLines( - esb.buildEntryStatsLines()); - } - private String entryIpsLine = "entry-ips ir=25368,us=15744,it=14816," - + "de=13256,es=8280,fr=8120,br=5176,sy=4760,ru=4504,sa=4216," - + "gb=3152,pl=2928,nl=2208,kr=1856,ca=1792,ua=1272,in=1192"; - private static ExtraInfoDescriptor createWithEntryIpsLine( - String line) throws DescriptorParseException { - EntryStatsBuilder esb = new EntryStatsBuilder(); - esb.entryIpsLine = line; - return DescriptorBuilder.createWithEntryStatsLines( - esb.buildEntryStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithEntryStatsLines( - new EntryStatsBuilder().buildEntryStatsLines()); - } - private String buildEntryStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.entryStatsEndLine != null) { - sb.append(this.entryStatsEndLine).append("\n"); - } - if (this.entryIpsLine != null) { - sb.append(this.entryIpsLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of cell-stats lines based on default - * data and modifications requested by test methods. */ - private static class CellStatsBuilder { - private String cellStatsEndLine = "cell-stats-end 2012-02-11 " - + "01:59:39 (86400 s)"; - private static ExtraInfoDescriptor createWithCellStatsEndLine( - String line) throws DescriptorParseException { - CellStatsBuilder csb = new CellStatsBuilder(); - csb.cellStatsEndLine = line; - return DescriptorBuilder.createWithCellStatsLines( - csb.buildCellStatsLines()); - } - private String cellProcessedCellsLine = "cell-processed-cells " - + "1441,11,6,4,2,1,1,1,1,1"; - private static ExtraInfoDescriptor createWithCellProcessedCellsLine( - String line) throws DescriptorParseException { - CellStatsBuilder csb = new CellStatsBuilder(); - csb.cellProcessedCellsLine = line; - return DescriptorBuilder.createWithCellStatsLines( - csb.buildCellStatsLines()); - } - private String cellQueuedCellsLine = "cell-queued-cells " - + "3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00"; - private static ExtraInfoDescriptor createWithCellQueuedCellsLine( - String line) throws DescriptorParseException { - CellStatsBuilder csb = new CellStatsBuilder(); - csb.cellQueuedCellsLine = line; - return DescriptorBuilder.createWithCellStatsLines( - csb.buildCellStatsLines()); - } - private String cellTimeInQueueLine = "cell-time-in-queue " - + "524,1,1,0,0,25,0,0,0,0"; - private static ExtraInfoDescriptor createWithCellTimeInQueueLine( - String line) throws DescriptorParseException { - CellStatsBuilder csb = new CellStatsBuilder(); - csb.cellTimeInQueueLine = line; - return DescriptorBuilder.createWithCellStatsLines( - csb.buildCellStatsLines()); - } - private String cellCircuitsPerDecileLine = "cell-circuits-per-decile " - + "866"; - private static ExtraInfoDescriptor - createWithCellCircuitsPerDecileLine(String line) - throws DescriptorParseException { - CellStatsBuilder csb = new CellStatsBuilder(); - csb.cellCircuitsPerDecileLine = line; - return DescriptorBuilder.createWithCellStatsLines( - csb.buildCellStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithCellStatsLines( - new CellStatsBuilder().buildCellStatsLines()); - } - private String buildCellStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.cellStatsEndLine != null) { - sb.append(this.cellStatsEndLine).append("\n"); - } - if (this.cellProcessedCellsLine != null) { - sb.append(this.cellProcessedCellsLine).append("\n"); - } - if (this.cellQueuedCellsLine != null) { - sb.append(this.cellQueuedCellsLine).append("\n"); - } - if (this.cellTimeInQueueLine != null) { - sb.append(this.cellTimeInQueueLine).append("\n"); - } - if (this.cellCircuitsPerDecileLine != null) { - sb.append(this.cellCircuitsPerDecileLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of exit-stats lines based on default - * data and modifications requested by test methods. */ - private static class ExitStatsBuilder { - private String exitStatsEndLine = "exit-stats-end 2012-02-11 " - + "01:59:39 (86400 s)"; - private static ExtraInfoDescriptor createWithExitStatsEndLine( - String line) throws DescriptorParseException { - ExitStatsBuilder esb = new ExitStatsBuilder(); - esb.exitStatsEndLine = line; - return DescriptorBuilder.createWithExitStatsLines( - esb.buildExitStatsLines()); - } - private String exitKibibytesWrittenLine = "exit-kibibytes-written " - + "25=74647,80=31370,443=20577,49755=23,52563=12,52596=1111," - + "57528=4,60912=11,61351=6,64811=3365,other=2592"; - private static ExtraInfoDescriptor createWithExitKibibytesWrittenLine( - String line) throws DescriptorParseException { - ExitStatsBuilder esb = new ExitStatsBuilder(); - esb.exitKibibytesWrittenLine = line; - return DescriptorBuilder.createWithExitStatsLines( - esb.buildExitStatsLines()); - } - private String exitKibibytesReadLine = "exit-kibibytes-read " - + "25=35562,80=1254256,443=110279,49755=9396,52563=1911," - + "52596=648,57528=1188,60912=1427,61351=1824,64811=14," - + "other=3054"; - private static ExtraInfoDescriptor createWithExitKibibytesReadLine( - String line) throws DescriptorParseException { - ExitStatsBuilder esb = new ExitStatsBuilder(); - esb.exitKibibytesReadLine = line; - return DescriptorBuilder.createWithExitStatsLines( - esb.buildExitStatsLines()); - } - private String exitStreamsOpenedLine = "exit-streams-opened " - + "25=369748,80=64212,443=151660,49755=4,52563=4,52596=4,57528=4," - + "60912=4,61351=4,64811=4,other=1212"; - private static ExtraInfoDescriptor createWithExitStreamsOpenedLine( - String line) throws DescriptorParseException { - ExitStatsBuilder esb = new ExitStatsBuilder(); - esb.exitStreamsOpenedLine = line; - return DescriptorBuilder.createWithExitStatsLines( - esb.buildExitStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithExitStatsLines( - new ExitStatsBuilder().buildExitStatsLines()); - } - private String buildExitStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.exitStatsEndLine != null) { - sb.append(this.exitStatsEndLine).append("\n"); - } - if (this.exitKibibytesWrittenLine != null) { - sb.append(this.exitKibibytesWrittenLine).append("\n"); - } - if (this.exitKibibytesReadLine != null) { - sb.append(this.exitKibibytesReadLine).append("\n"); - } - if (this.exitStreamsOpenedLine != null) { - sb.append(this.exitStreamsOpenedLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of bridge-stats lines based on default - * data and modifications requested by test methods. */ - private static class BridgeStatsBuilder { - private String bridgeStatsEndLine = "bridge-stats-end 2012-02-11 " - + "01:59:39 (86400 s)"; - private static ExtraInfoDescriptor createWithBridgeStatsEndLine( - String line) throws DescriptorParseException { - BridgeStatsBuilder bsb = new BridgeStatsBuilder(); - bsb.bridgeStatsEndLine = line; - return DescriptorBuilder.createWithBridgeStatsLines( - bsb.buildBridgeStatsLines()); - } - private String bridgeIpsLine = "bridge-ips ir=24,sy=16,??=8,cn=8," - + "de=8,es=8,fr=8,gb=8,in=8,jp=8,kz=8,nl=8,ua=8,us=8,vn=8,za=8"; - private static ExtraInfoDescriptor createWithBridgeIpsLine( - String line) throws DescriptorParseException { - BridgeStatsBuilder bsb = new BridgeStatsBuilder(); - bsb.bridgeIpsLine = line; - return DescriptorBuilder.createWithBridgeStatsLines( - bsb.buildBridgeStatsLines()); - } - private String bridgeIpVersionsLine = "bridge-ip-versions v4=8,v6=16"; - private static ExtraInfoDescriptor createWithBridgeIpVersionsLine( - String line) throws DescriptorParseException { - BridgeStatsBuilder bsb = new BridgeStatsBuilder(); - bsb.bridgeIpVersionsLine = line; - return DescriptorBuilder.createWithBridgeStatsLines( - bsb.buildBridgeStatsLines()); - } - private String bridgeIpTransportsLine = "bridge-ip-transports " - + "<OR>=8,obfs2=792,obfs3=1728"; - private static ExtraInfoDescriptor createWithBridgeIpTransportsLine( - String line) throws DescriptorParseException { - BridgeStatsBuilder bsb = new BridgeStatsBuilder(); - bsb.bridgeIpTransportsLine = line; - return DescriptorBuilder.createWithBridgeStatsLines( - bsb.buildBridgeStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithBridgeStatsLines( - new BridgeStatsBuilder().buildBridgeStatsLines()); - } - private String buildBridgeStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.bridgeStatsEndLine != null) { - sb.append(this.bridgeStatsEndLine).append("\n"); - } - if (this.bridgeIpsLine != null) { - sb.append(this.bridgeIpsLine).append("\n"); - } - if (this.bridgeIpVersionsLine != null) { - sb.append(this.bridgeIpVersionsLine).append("\n"); - } - if (this.bridgeIpTransportsLine != null) { - sb.append(this.bridgeIpTransportsLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - /* Helper class to build a set of hidserv-stats lines based on default - * data and modifications requested by test methods. */ - private static class HidservStatsBuilder { - private String hidservStatsEndLine = "hidserv-stats-end 2015-12-03 " - + "14:26:56 (86400 s)"; - private static ExtraInfoDescriptor createWithHidservStatsEndLine( - String line) throws DescriptorParseException { - HidservStatsBuilder hsb = new HidservStatsBuilder(); - hsb.hidservStatsEndLine = line; - return DescriptorBuilder.createWithHidservStatsLines( - hsb.buildHidservStatsLines()); - } - private String hidservRendRelayedCellsLine = - "hidserv-rend-relayed-cells 36474281 delta_f=2048 epsilon=0.30 " - + "bin_size=1024"; - private static ExtraInfoDescriptor - createWithHidservRendRelayedCellsLine(String line) - throws DescriptorParseException { - HidservStatsBuilder hsb = new HidservStatsBuilder(); - hsb.hidservRendRelayedCellsLine = line; - return DescriptorBuilder.createWithHidservStatsLines( - hsb.buildHidservStatsLines()); - } - private String hidservDirOnionsSeenLine = "hidserv-dir-onions-seen " - + "-3 delta_f=8 epsilon=0.30 bin_size=8"; - private static ExtraInfoDescriptor createWithHidservDirOnionsSeenLine( - String line) throws DescriptorParseException { - HidservStatsBuilder hsb = new HidservStatsBuilder(); - hsb.hidservDirOnionsSeenLine = line; - return DescriptorBuilder.createWithHidservStatsLines( - hsb.buildHidservStatsLines()); - } - private static ExtraInfoDescriptor createWithDefaultLines() - throws DescriptorParseException { - return DescriptorBuilder.createWithHidservStatsLines( - new HidservStatsBuilder().buildHidservStatsLines()); - } - private String buildHidservStatsLines() { - StringBuilder sb = new StringBuilder(); - if (this.hidservStatsEndLine != null) { - sb.append(this.hidservStatsEndLine).append("\n"); - } - if (this.hidservRendRelayedCellsLine != null) { - sb.append(this.hidservRendRelayedCellsLine).append("\n"); - } - if (this.hidservDirOnionsSeenLine != null) { - sb.append(this.hidservDirOnionsSeenLine).append("\n"); - } - String lines = sb.toString(); - if (lines.endsWith("\n")) { - lines = lines.substring(0, lines.length() - 1); - } - return lines; - } - } - - @Test() - public void testSampleDescriptor() throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - ExtraInfoDescriptor descriptor = - new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true); - assertEquals("chaoscomputerclub5", descriptor.getNickname()); - assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", - descriptor.getFingerprint()); - assertEquals(1328951316000L, descriptor.getPublishedMillis()); - assertNotNull(descriptor.getWriteHistory()); - assertEquals(1328951019000L, descriptor.getWriteHistory(). - getHistoryEndMillis()); - assertEquals(900L, descriptor.getWriteHistory().getIntervalLength()); - assertEquals(4572675072L, (long) descriptor.getWriteHistory(). - getBandwidthValues().get(1328951019000L)); - assertNotNull(descriptor.getReadHistory()); - assertNotNull(descriptor.getDirreqWriteHistory()); - assertNotNull(descriptor.getDirreqReadHistory()); - } - - @Test(expected = DescriptorParseException.class) - public void testExtraInfoLineMissing() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine(null); - } - - @Test() - public void testExtraInfoOpt() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithExtraInfoLine("opt extra-info chaoscomputerclub5 " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - assertEquals("chaoscomputerclub5", descriptor.getNickname()); - assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", - descriptor.getFingerprint()); - } - - @Test() - public void testExtraInfoNicknameTwoSpaces() - throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithExtraInfoLine("opt extra-info chaoscomputerclub5 " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - assertEquals("chaoscomputerclub5", descriptor.getNickname()); - assertEquals("A9C039A5FD02FCA06303DCFAABE25C5912C63B26", - descriptor.getFingerprint()); - } - - @Test(expected = DescriptorParseException.class) - public void testExtraInfoLineNotFirst() - throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("geoip-db-digest " - + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8\n" - + "extra-info chaoscomputerclub5 " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameMissing() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameInvalidChar() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "chaoscomputerclub% A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameTooLong() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "chaoscomputerclub5ReallyLongNickname " - + "A9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintG() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "chaoscomputerclub5 G9C039A5FD02FCA06303DCFAABE25C5912C63B26"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooShort() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C6"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooLong() throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoLine("extra-info " - + "chaoscomputerclub5 A9C039A5FD02FCA06303DCFAABE25C5912C63B26" - + "A9C0"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublishedMissing() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine(null); - } - - @Test() - public void testPublishedOpt() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithPublishedLine("opt published 2012-02-11 09:08:36"); - assertEquals(1328951316000L, descriptor.getPublishedMillis()); - } - - @Test() - public void testPublishedMillis() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithPublishedLine("opt published 2012-02-11 09:08:36.123"); - assertEquals(1328951316000L, descriptor.getPublishedMillis()); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryNegativeBytes() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-02-11 09:03:39 (900 s) " - + "-4713350144,-4723824640,-4710717440,-4572675072"); - } - - @Test() - public void testReadHistoryTabInterval() - throws DescriptorParseException { - DescriptorBuilder.createWithReadHistoryLine("read-history " - + "2012-02-11 09:03:39 (900\ts) " - + "4707695616,4699666432,4650004480,4489718784"); - } - - @Test() - public void testReadHistoryTabIntervalBytes() - throws DescriptorParseException { - DescriptorBuilder.createWithReadHistoryLine("read-history " - + "2012-02-11 09:03:39 (900 s)\t" - + "4707695616,4699666432,4650004480,4489718784"); - } - - @Test(expected = DescriptorParseException.class) - public void testReadHistoryNegativeInterval() - throws DescriptorParseException { - DescriptorBuilder.createWithReadHistoryLine("read-history " - + "2012-02-11 09:03:39 (-900 s) " - + "4707695616,4699666432,4650004480,4489718784"); - } - - @Test() - public void testReadHistoryNonStandardInterval() - throws DescriptorParseException { - DescriptorBuilder.createWithReadHistoryLine("read-history " - + "2012-02-11 09:03:39 (1800 s) " - + "4707695616,4699666432,4650004480,4489718784"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqWriteHistoryMissingBytesBegin() - throws DescriptorParseException { - DescriptorBuilder.createWithDirreqWriteHistoryLine( - "dirreq-write-history 2012-02-11 09:03:39 (900 s) " - + ",64996352,60625920,67922944"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqWriteHistoryMissingBytesMiddle() - throws DescriptorParseException { - DescriptorBuilder.createWithDirreqWriteHistoryLine( - "dirreq-write-history 2012-02-11 09:03:39 (900 s) " - + "81281024,,60625920,67922944"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqReadHistoryMissingBytesEnd() - throws DescriptorParseException { - DescriptorBuilder.createWithDirreqReadHistoryLine( - "dirreq-read-history 2012-02-11 09:03:39 (900 s) " - + "17074176,16235520,16005120,"); - } - - @Test() - public void testGeoipDbDigestValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithGeoipDbDigestLine("geoip-db-digest " - + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); - assertEquals("916A3CA8B7DF61473D5AE5B21711F35F301CE9E8", - descriptor.getGeoipDbDigest()); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipDbDigestTooShort() - throws DescriptorParseException { - DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest " - + "916A3CA8B7DF61473D5AE5B21711F35F301C"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipDbDigestIllegalChars() - throws DescriptorParseException { - DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest " - + "&%6A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipDbDigestMissing() - throws DescriptorParseException { - DescriptorBuilder.createWithGeoipDbDigestLine("geoip-db-digest"); - } - - @Test() - public void testGeoip6DbDigestValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithGeoip6DbDigestLine("geoip6-db-digest " - + "916A3CA8B7DF61473D5AE5B21711F35F301CE9E8"); - assertEquals("916A3CA8B7DF61473D5AE5B21711F35F301CE9E8", - descriptor.getGeoip6DbDigest()); - } - - @Test() - public void testGeoipStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = GeoipStatsBuilder. - createWithDefaultLines(); - assertEquals(1328898771000L, descriptor.getGeoipStartTimeMillis()); - SortedMap<String, Integer> ips = descriptor.getGeoipClientOrigins(); - assertNotNull(ips); - assertEquals(1152, ips.get("de").intValue()); - assertEquals(896, ips.get("cn").intValue()); - assertFalse(ips.containsKey("pl")); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipStartTimeDateOnly() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipStartTimeLine("geoip-start-time " - + "2012-02-10"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsDash() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de-1152,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,ir=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsZero() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=zero,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,ir=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsNone() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=none,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,ir=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsOther() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,other=200"); - } - - @Test() - public void testGeoipClientOriginsQuestionMarks() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,??=200"); - } - - @Test() - public void testGeoipClientOriginsCapital() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins DE=1152,CN=896,US=712,IT=504,RU=352,FR=208," - + "GB=208,IR=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsMissingBegin() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins ,cn=896,us=712,it=504,ru=352,fr=208,gb=208," - + "ir=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsMissingMiddle() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=1152,,us=712,it=504,ru=352,fr=208," - + "gb=208,ir=200"); - } - - @Test(expected = DescriptorParseException.class) - public void testGeoipClientOriginsMissingEnd() - throws DescriptorParseException { - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=1152,cn=896,us=712,it=504,ru=352,fr=208," - + "gb=208,"); - } - - @Test() - public void testGeoipClientOriginsDuplicate() - throws DescriptorParseException { - /* dir-spec.txt doesn't say anything about duplicate country codes, so - * this line is valid, even though it leads to a somewhat undefined - * parse result. */ - GeoipStatsBuilder.createWithGeoipClientOriginsLine( - "geoip-client-origins de=1152,de=952,cn=896,us=712,it=504," - + "ru=352,fr=208,gb=208,ir=200"); - } - - @Test() - public void testDirreqStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DirreqStatsBuilder. - createWithDefaultLines(); - assertEquals(1328921993000L, descriptor.getDirreqStatsEndMillis()); - assertEquals(86400L, descriptor.getDirreqStatsIntervalLength()); - SortedMap<String, Integer> ips = descriptor.getDirreqV3Ips(); - assertNotNull(ips); - assertEquals(1544, ips.get("us").intValue()); - assertFalse(ips.containsKey("no")); - assertTrue(descriptor.getDirreqV2Ips().isEmpty()); - SortedMap<String, Integer> reqs = descriptor.getDirreqV3Reqs(); - assertEquals(832, reqs.get("fr").intValue()); - assertTrue(descriptor.getDirreqV2Reqs().isEmpty()); - SortedMap<String, Integer> resp = descriptor.getDirreqV3Resp(); - assertEquals(10848, resp.get("ok").intValue()); - assertEquals(8, resp.get("not-enough-sigs").intValue()); - resp = descriptor.getDirreqV2Resp(); - assertEquals(1576, resp.get("not-found").intValue()); - assertEquals(0.37, descriptor.getDirreqV2Share(), 0.0001); - assertEquals(0.37, descriptor.getDirreqV3Share(), 0.0001); - SortedMap<String, Integer> dl = descriptor.getDirreqV3DirectDl(); - assertEquals(36, dl.get("complete").intValue()); - dl = descriptor.getDirreqV2DirectDl(); - assertEquals(0, dl.get("timeout").intValue()); - dl = descriptor.getDirreqV3TunneledDl(); - assertEquals(10608, dl.get("complete").intValue()); - dl = descriptor.getDirreqV2TunneledDl(); - assertEquals(0, dl.get("complete").intValue()); - } - - @Test() - public void testDirreqStatsIntervalTwoDays() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqStatsEndLine("dirreq-stats-end " - + "2012-02-11 00:59:53 (172800 s)"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3IpsThreeLetterCountry() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3IpsLine("dirreq-v3-ips " - + "usa=1544"); - } - - @Test() - public void testDirreqV2IpsDigitCountry() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2IpsLine("dirreq-v2-ips 00=8"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3ReqsOneLetterCountry() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3ReqsLine("dirreq-v3-reqs " - + "u=1744"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV2ReqsNoNumber() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2ReqsLine("dirreq-v2-reqs us="); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3RespTwoEqualSigns() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3RespLine("dirreq-v3-resp " - + "ok==10848"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV2RespNull() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2RespLine("dirreq-v2-resp " - + "ok=null"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV2ShareComma() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2ShareLine("dirreq-v2-share " - + "0,37%"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3ShareNoPercent() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3ShareLine("dirreq-v3-share " - + "0.37"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3DirectDlSpace() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3DirectDlLine( - "dirreq-v3-direct-dl complete 36"); - } - - @Test() - public void testDirreqV2DirectDlNegative() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2DirectDlLine( - "dirreq-v2-direct-dl complete=-8"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3TunneledDlTooLarge() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV3TunneledDlLine( - "dirreq-v3-tunneled-dl complete=2147483648"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirreqV3TunneledDlDouble() - throws DescriptorParseException { - DirreqStatsBuilder.createWithDirreqV2TunneledDlLine( - "dirreq-v2-tunneled-dl complete=0.001"); - } - - @Test() - public void testEntryStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = EntryStatsBuilder. - createWithDefaultLines(); - assertEquals(1328925579000L, descriptor.getEntryStatsEndMillis()); - assertEquals(86400L, descriptor.getEntryStatsIntervalLength()); - SortedMap<String, Integer> ips = descriptor.getEntryIps(); - assertNotNull(ips); - assertEquals(25368, ips.get("ir").intValue()); - assertFalse(ips.containsKey("no")); - } - - @Test(expected = DescriptorParseException.class) - public void testEntryStatsEndNoDate() throws DescriptorParseException { - EntryStatsBuilder.createWithEntryStatsEndLine("entry-stats-end " - + "01:59:39 (86400 s)"); - } - - @Test(expected = DescriptorParseException.class) - public void testEntryStatsIpsSemicolon() - throws DescriptorParseException { - EntryStatsBuilder.createWithEntryIpsLine("entry-ips " - + "ir=25368;us=15744"); - } - - @Test() - public void testCellStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = CellStatsBuilder. - createWithDefaultLines(); - assertEquals(1328925579000L, descriptor.getCellStatsEndMillis()); - assertEquals(86400L, descriptor.getCellStatsIntervalLength()); - List<Integer> processedCells = descriptor.getCellProcessedCells(); - assertEquals(10, processedCells.size()); - assertEquals(1441, processedCells.get(0).intValue()); - assertEquals(11, processedCells.get(1).intValue()); - List<Double> queuedCells = descriptor.getCellQueuedCells(); - assertEquals(10, queuedCells.size()); - assertEquals(3.29, queuedCells.get(0), 0.001); - assertEquals(0.00, queuedCells.get(1), 0.001); - List<Integer> timeInQueue = descriptor.getCellTimeInQueue(); - assertEquals(10, timeInQueue.size()); - assertEquals(524, timeInQueue.get(0).intValue()); - assertEquals(1, timeInQueue.get(1).intValue()); - assertEquals(866, descriptor.getCellCircuitsPerDecile()); - } - - @Test(expected = DescriptorParseException.class) - public void testCellStatsEndNoSeconds() - throws DescriptorParseException { - CellStatsBuilder.createWithCellStatsEndLine("cell-stats-end " - + "2012-02-11 01:59:39 (86400)"); - } - - @Test(expected = DescriptorParseException.class) - public void testCellProcessedCellsNineComma() - throws DescriptorParseException { - CellStatsBuilder.createWithCellProcessedCellsLine( - "cell-processed-cells 1441,11,6,4,2,1,1,1,1,"); - } - - @Test(expected = DescriptorParseException.class) - public void testCellProcessedCellsEleven() - throws DescriptorParseException { - CellStatsBuilder.createWithCellQueuedCellsLine("cell-queued-cells " - + "3.29,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00"); - } - - @Test(expected = DescriptorParseException.class) - public void testCellTimeInQueueDouble() - throws DescriptorParseException { - CellStatsBuilder.createWithCellTimeInQueueLine("cell-time-in-queue " - + "524.0,1.0,1.0,0.0,0.0,25.0,0.0,0.0,0.0,0.0"); - } - - @Test(expected = DescriptorParseException.class) - public void testCellCircuitsPerDecileNegative() - throws DescriptorParseException { - CellStatsBuilder.createWithCellCircuitsPerDecileLine( - "cell-circuits-per-decile -866"); - } - - @Test() - public void testConnBiDirectValid() - throws DescriptorParseException { - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithConnBiDirectLine("conn-bi-direct 2012-02-11 01:59:39 " - + "(86400 s) 42173,1591,1310,1744"); - assertEquals(1328925579000L, - descriptor.getConnBiDirectStatsEndMillis()); - assertEquals(86400L, descriptor.getConnBiDirectStatsIntervalLength()); - assertEquals(42173, descriptor.getConnBiDirectBelow()); - assertEquals(1591, descriptor.getConnBiDirectRead()); - assertEquals(1310, descriptor.getConnBiDirectWrite()); - assertEquals(1744, descriptor.getConnBiDirectBoth()); - } - - @Test(expected = DescriptorParseException.class) - public void testConnBiDirectStatsFive() - throws DescriptorParseException { - DescriptorBuilder.createWithConnBiDirectLine("conn-bi-direct " - + "2012-02-11 01:59:39 (86400 s) 42173,1591,1310,1744,42"); - } - - @Test() - public void testExitStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = ExitStatsBuilder. - createWithDefaultLines(); - assertEquals(1328925579000L, descriptor.getExitStatsEndMillis()); - assertEquals(86400L, descriptor.getExitStatsIntervalLength()); - String[] ports = new String[] { "25", "80", "443", "49755", - "52563", "52596", "57528", "60912", "61351", "64811", "other" }; - int[] writtenValues = new int[] { 74647, 31370, 20577, 23, 12, 1111, - 4, 11, 6, 3365, 2592 }; - int i = 0; - for (Map.Entry<String, Long> e : - descriptor.getExitKibibytesWritten().entrySet()) { - assertEquals(ports[i], e.getKey()); - assertEquals(writtenValues[i++], e.getValue().intValue()); - } - int[] readValues = new int[] { 35562, 1254256, 110279, 9396, 1911, - 648, 1188, 1427, 1824, 14, 3054 }; - i = 0; - for (Map.Entry<String, Long> e : - descriptor.getExitKibibytesRead().entrySet()) { - assertEquals(ports[i], e.getKey()); - assertEquals(readValues[i++], e.getValue().intValue()); - } - int[] streamsValues = new int[] { 369748, 64212, 151660, 4, 4, 4, 4, - 4, 4, 4, 1212 }; - i = 0; - for (Map.Entry<String, Long> e : - descriptor.getExitStreamsOpened().entrySet()) { - assertEquals(ports[i], e.getKey()); - assertEquals(streamsValues[i++], e.getValue().intValue()); - } - } - - @Test(expected = DescriptorParseException.class) - public void testExitStatsEndNoSeconds() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitStatsEndLine("exit-stats-end " - + "2012-02-11 01:59 (86400 s)"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitStatsWrittenNegativePort() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitKibibytesWrittenLine( - "exit-kibibytes-written -25=74647"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitStatsWrittenUnknown() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitKibibytesWrittenLine( - "exit-kibibytes-written unknown=74647"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitStatsReadNegativeBytes() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitKibibytesReadLine( - "exit-kibibytes-read 25=-35562"); - } - - @Test() - public void testExitStatsReadTooLarge() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitKibibytesReadLine( - "exit-kibibytes-read other=2282907805"); - } - - @Test() - public void testExitStatsStreamsTooLarge() - throws DescriptorParseException { - ExitStatsBuilder.createWithExitStreamsOpenedLine( - "exit-streams-opened 25=2147483648"); - } - - @Test() - public void testBridgeStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = BridgeStatsBuilder. - createWithDefaultLines(); - assertEquals(1328925579000L, descriptor.getBridgeStatsEndMillis()); - assertEquals(86400L, descriptor.getBridgeStatsIntervalLength()); - SortedMap<String, Integer> ips = descriptor.getBridgeIps(); - assertNotNull(ips); - assertEquals(24, ips.get("ir").intValue()); - assertEquals(16, ips.get("sy").intValue()); - assertFalse(ips.containsKey("no")); - SortedMap<String, Integer> ver = descriptor.getBridgeIpVersions(); - assertNotNull(ver); - assertEquals(8, ver.get("v4").intValue()); - assertEquals(16, ver.get("v6").intValue()); - assertFalse(ver.containsKey("v8")); - SortedMap<String, Integer> trans = descriptor.getBridgeIpTransports(); - assertNotNull(trans); - assertEquals(8, trans.get("<OR>").intValue()); - assertEquals(792, trans.get("obfs2").intValue()); - assertEquals(1728, trans.get("obfs3").intValue()); - } - - @Test(expected = DescriptorParseException.class) - public void testBridgeStatsEndIntervalZero() - throws DescriptorParseException { - BridgeStatsBuilder.createWithBridgeStatsEndLine("bridge-stats-end " - + "2012-02-11 01:59:39 (0 s)"); - } - - @Test(expected = DescriptorParseException.class) - public void testBridgeIpsDouble() - throws DescriptorParseException { - BridgeStatsBuilder.createWithBridgeIpsLine("bridge-ips ir=24.5"); - } - - @Test(expected = DescriptorParseException.class) - public void testBridgeIpsNonAsciiKeyword() - throws DescriptorParseException { - DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] { - 0x14, (byte) 0xfe, 0x18, // non-ascii chars - 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2d, // "bridge-" - 0x69, 0x70, 0x73 }, false); // "ips" (no newline) - } - - @Test(expected = DescriptorParseException.class) - public void testBridgeIpVersionsDouble() - throws DescriptorParseException { - BridgeStatsBuilder.createWithBridgeIpVersionsLine( - "bridge-ip-versions v4=24.5"); - } - - @Test(expected = DescriptorParseException.class) - public void testBridgeIpTransportsDouble() - throws DescriptorParseException { - BridgeStatsBuilder.createWithBridgeIpTransportsLine( - "bridge-ip-transports obfs2=24.5"); - } - - @Test() - public void testBridgeIpTransportsUnderscore() - throws DescriptorParseException { - BridgeStatsBuilder.createWithBridgeIpTransportsLine( - "bridge-ip-transports meek=32,obfs3_websocket=8,websocket=64"); - } - - @Test() - public void testHidservStatsValid() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = HidservStatsBuilder. - createWithDefaultLines(); - assertEquals(1449152816000L, descriptor.getHidservStatsEndMillis()); - assertEquals(86400L, descriptor.getHidservStatsIntervalLength()); - assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(), - 0.0001); - Map<String, Double> params = - descriptor.getHidservRendRelayedCellsParameters(); - assertTrue(params.containsKey("delta_f")); - assertEquals(2048.0, params.remove("delta_f"), 0.0001); - assertTrue(params.containsKey("epsilon")); - assertEquals(0.3, params.remove("epsilon"), 0.0001); - assertTrue(params.containsKey("bin_size")); - assertEquals(1024.0, params.remove("bin_size"), 0.0001); - assertTrue(params.isEmpty()); - assertEquals(-3.0, descriptor.getHidservDirOnionsSeen(), 0.0001); - params = descriptor.getHidservDirOnionsSeenParameters(); - assertTrue(params.containsKey("delta_f")); - assertEquals(8.0, params.remove("delta_f"), 0.0001); - assertTrue(params.containsKey("epsilon")); - assertEquals(0.3, params.remove("epsilon"), 0.0001); - assertTrue(params.containsKey("bin_size")); - assertEquals(8.0, params.remove("bin_size"), 0.0001); - assertTrue(params.isEmpty()); - } - - @Test() - public void testHidservStatsEndLineMissing() - throws DescriptorParseException { - ExtraInfoDescriptor descriptor = - HidservStatsBuilder.createWithHidservStatsEndLine(null); - assertEquals(-1L, descriptor.getHidservStatsEndMillis()); - assertEquals(-1L, descriptor.getHidservStatsIntervalLength()); - } - - @Test() - public void testHidservRendRelayedCellsNoParams() - throws DescriptorParseException { - ExtraInfoDescriptor descriptor = - HidservStatsBuilder.createWithHidservRendRelayedCellsLine( - "hidserv-rend-relayed-cells 36474281"); - assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(), - 0.0001); - assertTrue( - descriptor.getHidservRendRelayedCellsParameters().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testHidservDirOnionsSeenCommaSeparatedParams() - throws DescriptorParseException { - HidservStatsBuilder.createWithHidservDirOnionsSeenLine( - "hidserv-dir-onions-seen -3 delta_f=8,epsilon=0.30,bin_size=8"); - } - - @Test(expected = DescriptorParseException.class) - public void testHidservDirOnionsSeenNoDoubleParams() - throws DescriptorParseException { - HidservStatsBuilder.createWithHidservDirOnionsSeenLine( - "hidserv-dir-onions-seen -3 delta_f=A epsilon=B bin_size=C"); - } - - @Test() - public void testRouterSignatureOpt() - throws DescriptorParseException { - DescriptorBuilder.createWithRouterSignatureLines("opt " - + "router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "crypto lines are ignored anyway\n" - + "-----END SIGNATURE-----"); - } - - @Test(expected = DescriptorParseException.class) - public void testRouterSignatureNotLastLine() - throws DescriptorParseException { - DescriptorBuilder.createWithRouterSignatureLines("router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" - + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" - + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" - + "-----END SIGNATURE-----\npublished 2012-02-11 09:08:36"); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ExtraInfoDescriptor descriptor = DescriptorBuilder. - createWithUnrecognizedLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); - } - - private static final String IDENTITY_ED25519_LINES = - "identity-ed25519\n" - + "-----BEGIN ED25519 CERT-----\n" - + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr" - + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1" - + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA=" - + "\n" - + "-----END ED25519 CERT-----"; - - private static final String MASTER_KEY_ED25519_LINE = - "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc"; - - private static final String ROUTER_SIG_ED25519_LINE = - "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ" - + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw"; - - @Test() - public void testEd25519() throws DescriptorParseException { - ExtraInfoDescriptor descriptor = - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - assertEquals(IDENTITY_ED25519_LINES.substring( - IDENTITY_ED25519_LINES.indexOf("\n") + 1), - descriptor.getIdentityEd25519()); - assertEquals(MASTER_KEY_ED25519_LINE.substring( - MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), - descriptor.getMasterKeyEd25519()); - assertEquals(ROUTER_SIG_ED25519_LINE.substring( - ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1), - descriptor.getRouterSignatureEd25519()); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityMasterKeyMismatch() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519IdentityMissing() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(null, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n" - + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE, - ROUTER_SIG_ED25519_LINE); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityEmptyCrypto() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n" - + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----", - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519MasterKeyMissing() - throws DescriptorParseException { - ExtraInfoDescriptor descriptor = - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - null, ROUTER_SIG_ED25519_LINE); - assertEquals(MASTER_KEY_ED25519_LINE.substring( - MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), - descriptor.getMasterKeyEd25519()); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519MasterKeyDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE, - ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519RouterSigMissing() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, null); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519RouterSigDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n" - + ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testExtraInfoDigestSha256Relay() - throws DescriptorParseException { - byte[] descriptorBytes = ("extra-info Unnamed " - + "EA5B335055D2F03013FF030381F02B1C631EC723\n" - + "identity-ed25519\n" - + "-----BEGIN ED25519 CERT-----\n" - + "AQQABiZRAenzZorGtx6xapoEeaqcLLOk3uWwJXTvOVLluSXXbRSZAQAgBADLN5" - + "wp\nCEOrRbshSbj1NDAUgc6cxU65M/Vx1x+b5+EXbkQZ5uiyB4pphVF5kPPT1P" - + "SleYqM\n8j+tlKh2i6+Xr0xScSPpmtG00/D0MoRlT7ZdaaaT5iw1DWDQCZ8BHG" - + "lAZwU=\n" - + "-----END ED25519 CERT-----\n" - + "published 2015-12-01 04:38:12\n" - + "write-history 2015-12-01 01:40:37 (14400 s) 88704000,60825600," - + "61747200,76953600,61516800,59443200\n" - + "read-history 2015-12-01 01:40:37 (14400 s) 87321600,59443200," - + "59904000,74880000,60364800,58060800\n" - + "router-sig-ed25519 c6eUeJs/SVjun3JhmjByEeWdRDyunSMAnGVhx71JiRj" - + "YzR8x5IcPebylG7m10wiolFxinvw78UhrrGo9Sq5ZBw\n" - + "router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "oC2qFHCDOKSRoIPR86jdRxEYia390Z4d8fT0yr/1mg4RQ7lHmxlzFT6QxCswdX" - + "Ry\nvGNGR0wARySgyE+YKKWYn/Hp547JhhWd9Oc7BuFMY0XMvl/HOo+B9VjW+l" - + "nv6UBE\niqxx3C3Iw0ymohvOenyCUa/7TmsT7eVotDO57uIoGEc=\n" - + "-----END SIGNATURE-----\n" - + "").getBytes(); - RelayExtraInfoDescriptor descriptor = - new RelayExtraInfoDescriptorImpl(descriptorBytes, true); - assertEquals("Pt1BtzfRwhYqGCDo8jjchS8nJP3ovrDyHGn+dqPbMgw", - descriptor.getExtraInfoDigestSha256()); - } - - @Test() - public void testExtraInfoDigestSha256Bridge() - throws DescriptorParseException { - byte[] descriptorBytes = ("extra-info idideditheconfig " - + "DC28749EC9E26E61DE492E46CD830379E9931B09\n" - + "master-key-ed25519 " - + "38FzmOIE6Mm85Ytx0MhFM6X9EuxWRUgb6HjyMGuO2AU\n" - + "published 2015-12-03 13:23:19\n" - + "write-history 2015-12-03 09:59:32 (14400 s) 53913600,52992000," - + "53222400,53222400,53452800,53222400\n" - + "read-history 2015-12-03 09:59:32 (14400 s) 61056000,60364800," - + "60364800,60134400,60595200,60364800\n" - + "geoip-db-digest 5BF366AD4A0572D82A1A0F6628AF8EF7725E3AB9\n" - + "geoip6-db-digest 212DE17D5A368DCAFA19B95F168BFFA101145A93\n" - + "router-digest-sha256 " - + "TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4\n" - + "router-digest 00B98F076B586272C3172B7F3DA29ADEE75F2ED8\n").getBytes(); - BridgeExtraInfoDescriptor descriptor = - new BridgeExtraInfoDescriptorImpl(descriptorBytes, true); - assertEquals("TvrqpjI7OmCtwGwair/NHUxg5ROVVQYz6/EDyXsDHR4", - descriptor.getExtraInfoDigestSha256()); - } -} - diff --git a/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java b/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java deleted file mode 100644 index abb51db..0000000 --- a/test/org/torproject/descriptor/impl/MicrodescriptorImplTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.torproject.descriptor.impl; - -import org.junit.Test; -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.Microdescriptor; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class MicrodescriptorImplTest { - - /* Helper class to build a microdescriptor based on default data and - * modifications requested by test methods. */ - private static class DescriptorBuilder { - private String onionKeyLines = "onion-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBALNZ4pNsHHkl7a+kFWbBmPHNAepjjvuhjTr1TaMB3UKuCRaXJmS2Qr" - + "CW\nkTmINqdQUccwb3ghb7EBZfDtCUvjcwMSEsRRTVIZqVQsYj6m3n1CegOc4o" - + "UutXaZ\nfkyty5XOgV4Qucx9wokzTMCHlO0V0x9y0FwFsK5Nb6ugqfQLLQ6XAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"; - private static Microdescriptor createWithDefaultLines() - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - return new MicrodescriptorImpl(db.buildDescriptor(), true); - } - private String ntorOnionKeyLine = - "ntor-onion-key PXLa7IGE+TzPDMsM5j9rFnDa37rd6kfZa5QuzqqJukw="; - private String idLine = "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8"; - private static Microdescriptor createWithIdLine(String line) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.idLine = line; - return new MicrodescriptorImpl(db.buildDescriptor(), true); - } - private byte[] buildDescriptor() { - StringBuilder sb = new StringBuilder(); - if (this.onionKeyLines != null) { - sb.append(this.onionKeyLines).append("\n"); - } - if (this.ntorOnionKeyLine != null) { - sb.append(this.ntorOnionKeyLine).append("\n"); - } - if (this.idLine != null) { - sb.append(this.idLine).append("\n"); - } - return sb.toString().getBytes(); - } - } - - @Test() - public void testDefaults() throws DescriptorParseException { - DescriptorBuilder.createWithDefaultLines(); - } - - @Test(expected = DescriptorParseException.class) - public void testIdRsa1024TooShort() throws DescriptorParseException { - DescriptorBuilder.createWithIdLine("id rsa1024 AAAA"); - } - - @Test(expected = DescriptorParseException.class) - public void testIdRsa1024TooLong() throws DescriptorParseException { - DescriptorBuilder.createWithIdLine("id ed25519 AAAAAAAAAAAAAAAAAAAAAA" - + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); - } - - @Test(expected = DescriptorParseException.class) - public void testIdRsa512() throws DescriptorParseException { - DescriptorBuilder.createWithIdLine("id rsa512 " - + "bvegfGxp8k7T9QFpjPTrPaJTa/8"); - } - - @Test(expected = DescriptorParseException.class) - public void testIdEd25519Duplicate() throws DescriptorParseException { - DescriptorBuilder.createWithIdLine( - "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8\n" - + "id rsa1024 bvegfGxp8k7T9QFpjPTrPaJTa/8"); - } -} diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java deleted file mode 100644 index d864337..0000000 --- a/test/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImplTest.java +++ /dev/null @@ -1,1272 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.DirectorySignature; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - - -import org.junit.Test; -import org.torproject.descriptor.NetworkStatusEntry; -import org.torproject.descriptor.RelayNetworkStatusConsensus; - -/* TODO Add test cases for all lines starting with "opt ". */ - -/* Test parsing of network status consensuses. The main focus is on - * making sure that the parser is as robust as possible and doesn't break, - * no matter what gets fed into it. A secondary focus is to ensure that - * a parsed consensus is fully compatible to dir-spec.txt. */ -public class RelayNetworkStatusConsensusImplTest { - - /* Helper class to build a directory source based on default data and - * modifications requested by test methods. */ - private static class DirSourceBuilder { - private static RelayNetworkStatusConsensus - createWithDirSource(String dirSourceString) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.dirSources.add(dirSourceString); - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - true); - } - private String nickname = "gabelmoo"; - private static RelayNetworkStatusConsensus - createWithNickname(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.nickname = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String identity = "ED03BB616EB2F60BEC80151114BB25CEF515B226"; - private static RelayNetworkStatusConsensus - createWithIdentity(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.identity = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String hostName = "212.112.245.170"; - private static RelayNetworkStatusConsensus - createWithHostName(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.hostName = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String address = "212.112.245.170"; - private static RelayNetworkStatusConsensus - createWithAddress(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.address = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String dirPort = "80"; - private static RelayNetworkStatusConsensus - createWithDirPort(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.dirPort = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String orPort = "443"; - private static RelayNetworkStatusConsensus - createWithOrPort(String string) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.orPort = string; - return createWithDirSource(dsb.buildDirSource()); - } - private String contactLine = "contact 4096R/C5AA446D Sebastian Hahn " - + "tor@sebastianhahn.net"; - private static RelayNetworkStatusConsensus - createWithContactLine(String line) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.contactLine = line; - return createWithDirSource(dsb.buildDirSource()); - } - private String voteDigestLine = - "vote-digest 0F398A5834D2C139E1D92310B09F814F243354D1"; - private static RelayNetworkStatusConsensus - createWithVoteDigestLine(String line) - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.voteDigestLine = line; - return createWithDirSource(dsb.buildDirSource()); - } - private String buildDirSource() { - StringBuilder sb = new StringBuilder(); - String dirSourceLine = "dir-source " + this.nickname + " " - + this.identity + " " + this.hostName + " " + this.address + " " - + this.dirPort + " " + this.orPort; - sb.append(dirSourceLine).append("\n"); - if (this.contactLine != null) { - sb.append(this.contactLine).append("\n"); - } - if (this.voteDigestLine != null) { - sb.append(this.voteDigestLine).append("\n"); - } - String dirSourceWithTrailingNewLine = sb.toString(); - String dirSource = dirSourceWithTrailingNewLine.substring(0, - dirSourceWithTrailingNewLine.length() - 1); - return dirSource; - } - } - - /* Helper class to build a status entry based on default data and - * modifications requested by test methods. */ - private static class StatusEntryBuilder { - private static RelayNetworkStatusConsensus - createWithStatusEntry(String statusEntryString) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.add(statusEntryString); - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - true); - } - private String nickname = "right2privassy3"; - private static RelayNetworkStatusConsensus - createWithNickname(String string) - throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.nickname = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String fingerprintBase64 = "ADQ6gCT3DiFHKPDFr3rODBUI8HM"; - private static RelayNetworkStatusConsensus - createWithFingerprintBase64(String string) - throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.fingerprintBase64 = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String descriptorBase64 = "Yiti+nayuT2Efe2X1+M4nslwVuU"; - private static RelayNetworkStatusConsensus - createWithDescriptorBase64(String string) - throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.descriptorBase64 = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String publishedString = "2011-11-29 21:34:27"; - private static RelayNetworkStatusConsensus - createWithPublishedString(String string) - throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.publishedString = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String address = "50.63.8.215"; - private static RelayNetworkStatusConsensus - createWithAddress(String string) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.address = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String orPort = "9023"; - private static RelayNetworkStatusConsensus - createWithOrPort(String string) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.orPort = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String dirPort = "0"; - private static RelayNetworkStatusConsensus - createWithDirPort(String string) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.dirPort = string; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String sLine = "s Exit Fast Named Running Stable Valid"; - private static RelayNetworkStatusConsensus - createWithSLine(String line) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.sLine = line; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String vLine = "v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)"; - private static RelayNetworkStatusConsensus - createWithVLine(String line) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.vLine = line; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String wLine = "w Bandwidth=1"; - private static RelayNetworkStatusConsensus - createWithWLine(String line) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.wLine = line; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String pLine = "p accept 80,1194,1220,1293"; - private static RelayNetworkStatusConsensus - createWithPLine(String line) throws DescriptorParseException { - StatusEntryBuilder seb = new StatusEntryBuilder(); - seb.pLine = line; - return createWithStatusEntry(seb.buildStatusEntry()); - } - private String buildStatusEntry() { - StringBuilder sb = new StringBuilder(); - String rLine = "r " + nickname + " " + fingerprintBase64 + " " - + descriptorBase64 + " " + publishedString + " " + address + " " - + orPort + " " + dirPort; - sb.append(rLine).append("\n"); - if (this.sLine != null) { - sb.append(this.sLine).append("\n"); - } - if (this.vLine != null) { - sb.append(this.vLine).append("\n"); - } - if (this.wLine != null) { - sb.append(this.wLine).append("\n"); - } - if (this.pLine != null) { - sb.append(this.pLine).append("\n"); - } - String statusEntryWithTrailingNewLine = sb.toString(); - String statusEntry = statusEntryWithTrailingNewLine.substring(0, - statusEntryWithTrailingNewLine.length() - 1); - return statusEntry; - } - } - - /* Helper class to build a directory signature based on default data and - * modifications requested by test methods. */ - private static class DirectorySignatureBuilder { - private static RelayNetworkStatusConsensus - createWithDirectorySignature(String directorySignatureString) - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.addDirectorySignature(directorySignatureString); - return new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), - true); - } - private String identity = "ED03BB616EB2F60BEC80151114BB25CEF515B226"; - private static RelayNetworkStatusConsensus - createWithIdentity(String string) - throws DescriptorParseException { - DirectorySignatureBuilder dsb = new DirectorySignatureBuilder(); - dsb.identity = string; - return createWithDirectorySignature(dsb.buildDirectorySignature()); - } - private String signingKey = - "845CF1D0B370CA443A8579D18E7987E7E532F639"; - private static RelayNetworkStatusConsensus - createWithSigningKey(String string) - throws DescriptorParseException { - DirectorySignatureBuilder dsb = new DirectorySignatureBuilder(); - dsb.signingKey = string; - return createWithDirectorySignature(dsb.buildDirectorySignature()); - } - private String buildDirectorySignature() { - String directorySignature = "directory-signature " + identity + " " - + signingKey + "\n" - + "-----BEGIN SIGNATURE-----\n" - + "gE64+/4BH43v1+7jS9FK1tu2+94at8xhVSPn4O/PpOx7b0Yb+S1hac1QHAiS" - + "Ll+k\n" - + "6OiANKzhj54WHSrUswBPrOzjmKj0OhGXSAe5nHZUFX9a1MDQLDCoZBj536X9" - + "P3JG\n" - + "z89A+wrsN17I5490y66AEvws54BYZMbgRfp8HXn/0Ss=\n" - + "-----END SIGNATURE-----"; - return directorySignature; - } - } - - @Test() - public void testSampleConsensus() throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - RelayNetworkStatusConsensus consensus = - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - assertEquals(3, consensus.getNetworkStatusVersion()); - assertEquals(11, consensus.getConsensusMethod()); - assertEquals(1322643600000L, consensus.getValidAfterMillis()); - assertEquals(1322647200000L, consensus.getFreshUntilMillis()); - assertEquals(1322654400000L, consensus.getValidUntilMillis()); - assertEquals(300L, consensus.getVoteSeconds()); - assertEquals(300L, consensus.getDistSeconds()); - assertTrue(consensus.getRecommendedClientVersions().contains( - "0.2.3.8-alpha")); - assertTrue(consensus.getRecommendedServerVersions().contains( - "0.2.3.8-alpha")); - assertTrue(consensus.getKnownFlags().contains("Running")); - assertEquals(30000, (int) consensus.getConsensusParams().get( - "CircuitPriorityHalflifeMsec")); - assertEquals("86.59.21.38", consensus.getDirSourceEntries().get( - "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4").getHostname()); - assertEquals("86.59.21.38", consensus.getDirSourceEntries().get( - "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4").getIp()); - assertTrue(consensus.containsStatusEntry( - "00795A6E8D91C270FC23B30F388A495553E01894")); - assertEquals("188.177.149.216", consensus.getStatusEntry( - "00795A6E8D91C270FC23B30F388A495553E01894").getAddress()); - for (DirectorySignature signature : consensus.getSignatures()) { - if ("14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4".equals( - signature.getIdentity())) { - assertEquals("3509BA5A624403A905C74DA5C8A0CEC9E0D3AF86", - signature.getSigningKeyDigest()); - } - } - assertEquals(285, (int) consensus.getBandwidthWeights().get("Wbd")); - assertTrue(consensus.getUnrecognizedLines().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNoLine() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNewLine() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version 3\n"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNewLineSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version 3\n "); - } - - @Test() - public void testNetworkStatusVersionPrefixLineAtChar() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "@consensus\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionPrefixLine() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "directory-footer\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionPrefixLinePoundChar() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "#consensus\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNoSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionOneSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version "); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersion42() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version 42"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionFourtyTwo() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - "network-status-version FourtyTwo"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusNoLine() throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionSpaceBefore() - throws DescriptorParseException { - ConsensusBuilder.createWithNetworkStatusVersionLine( - " network-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusSpaceBefore() throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine(" vote-status consensus"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusNoSpace() throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine("vote-status"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusOneSpace() throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine("vote-status "); - } - - @Test() - public void testVoteStatusConsensusOneSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine("vote-status consensus "); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusVote() throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine("vote-status vote"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusTheMagicVoteStatus() - throws DescriptorParseException { - ConsensusBuilder.createWithVoteStatusLine( - "vote-status TheMagicVoteStatus"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodNoLine() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodNoSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine("consensus-method"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodOneSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine("consensus-method "); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodEleven() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine( - "consensus-method eleven"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodMinusOne() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine("consensus-method -1"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodNinePeriod() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine("consensus-method " - + "999999999999999999999999999999999999999999999999999999999999"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodTwoLines() - throws DescriptorParseException { - ConsensusBuilder.createWithConsensusMethodLine( - "consensus-method 1\nconsensus-method 1"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterNoLine() throws DescriptorParseException { - ConsensusBuilder.createWithValidAfterLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterNoSpace() throws DescriptorParseException { - ConsensusBuilder.createWithValidAfterLine("valid-after"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterOneSpace() throws DescriptorParseException { - ConsensusBuilder.createWithValidAfterLine("valid-after "); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterLongAgo() throws DescriptorParseException { - ConsensusBuilder.createWithValidAfterLine("valid-after long ago"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterFeb30() throws DescriptorParseException { - ConsensusBuilder.createWithValidAfterLine( - "valid-after 2011-02-30 09:00:00"); - } - - @Test(expected = DescriptorParseException.class) - public void testFreshUntilNoLine() throws DescriptorParseException { - ConsensusBuilder.createWithFreshUntilLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testFreshUntilAroundTen() throws DescriptorParseException { - ConsensusBuilder.createWithFreshUntilLine( - "fresh-until 2011-11-30 around ten"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidUntilTomorrowMorning() - throws DescriptorParseException { - ConsensusBuilder.createWithValidUntilLine( - "valid-until tomorrow morning"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayNoLine() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayNoSpace() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine("voting-delay"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayOneSpace() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine("voting-delay "); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayTriple() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine( - "voting-delay 300 300 300"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelaySingle() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine("voting-delay 300"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayOneTwo() throws DescriptorParseException { - ConsensusBuilder.createWithVotingDelayLine("voting-delay one two"); - } - - @Test() - public void testClientServerVersionsNoLine() - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.clientVersionsLine = null; - cb.serverVersionsLine = null; - RelayNetworkStatusConsensus consensus = - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - assertNull(consensus.getRecommendedClientVersions()); - assertNull(consensus.getRecommendedServerVersions()); - } - - @Test() - public void testServerVersionsNoLine() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithServerVersionsLine(null); - assertNotNull(consensus.getRecommendedClientVersions()); - assertNull(consensus.getRecommendedServerVersions()); - } - - @Test() - public void testClientVersionsNoLine() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithClientVersionsLine(null); - assertNull(consensus.getRecommendedClientVersions()); - assertNotNull(consensus.getRecommendedServerVersions()); - } - - @Test() - public void testClientVersionsNoSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithClientVersionsLine("client-versions"); - assertNotNull(consensus.getRecommendedClientVersions()); - assertTrue(consensus.getRecommendedClientVersions().isEmpty()); - } - - @Test() - public void testClientVersionsOneSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithClientVersionsLine("client-versions "); - assertNotNull(consensus.getRecommendedClientVersions()); - assertTrue(consensus.getRecommendedClientVersions().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testClientVersionsComma() throws DescriptorParseException { - ConsensusBuilder.createWithClientVersionsLine("client-versions ,"); - } - - @Test(expected = DescriptorParseException.class) - public void testClientVersionsCommaVersion() - throws DescriptorParseException { - ConsensusBuilder.createWithClientVersionsLine( - "client-versions ,0.2.2.34"); - } - - @Test() - public void testPackageNone() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithPackageLines(null); - assertNull(consensus.getPackageLines()); - } - - @Test() - public void testPackageOne() throws DescriptorParseException { - String packageLine = "package shouldbesecond 0 http digest=digest"; - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithPackageLines(packageLine); - assertEquals(packageLine.substring("package ".length()), - consensus.getPackageLines().get(0)); - } - - @Test() - public void testPackageTwo() throws DescriptorParseException { - List<String> packageLines = Arrays.asList( - "package shouldbesecond 0 http digest=digest", - "package outoforder 0 http digest=digest"); - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithPackageLines(packageLines.get(0) - + "\n" + packageLines.get(1)); - for (int i = 0; i < packageLines.size(); i++) { - assertEquals(packageLines.get(i).substring("package ".length()), - consensus.getPackageLines().get(i)); - } - } - - @Test(expected = DescriptorParseException.class) - public void testPackageIncomplete() throws DescriptorParseException { - String packageLine = "package shouldbesecond 0 http"; - ConsensusBuilder.createWithPackageLines(packageLine); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsNoLine() throws DescriptorParseException { - ConsensusBuilder.createWithKnownFlagsLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsNoSpace() throws DescriptorParseException { - ConsensusBuilder.createWithKnownFlagsLine("known-flags"); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsOneSpace() throws DescriptorParseException { - ConsensusBuilder.createWithKnownFlagsLine("known-flags "); - } - - @Test() - public void testParamsNoLine() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine(null); - assertNull(consensus.getConsensusParams()); - } - - @Test() - public void testParamsNoSpace() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine("params"); - assertNotNull(consensus.getConsensusParams()); - assertTrue(consensus.getConsensusParams().isEmpty()); - } - - @Test() - public void testParamsOneSpace() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine("params "); - assertNotNull(consensus.getConsensusParams()); - assertTrue(consensus.getConsensusParams().isEmpty()); - } - - @Test() - public void testParamsThreeSpaces() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine("params "); - assertNotNull(consensus.getConsensusParams()); - assertTrue(consensus.getConsensusParams().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testParamsNoEqualSign() throws DescriptorParseException { - ConsensusBuilder.createWithParamsLine("params key-value"); - } - - @Test(expected = DescriptorParseException.class) - public void testParamsOneTooLargeNegative() - throws DescriptorParseException { - ConsensusBuilder.createWithParamsLine("params min=-2147483649"); - } - - @Test() - public void testParamsLargestNegative() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine("params min=-2147483648"); - assertEquals(1, consensus.getConsensusParams().size()); - assertEquals(-2147483648, - (int) consensus.getConsensusParams().get("min")); - } - - @Test() - public void testParamsLargestPositive() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithParamsLine("params max=2147483647"); - assertEquals(1, consensus.getConsensusParams().size()); - assertEquals(2147483647, - (int) consensus.getConsensusParams().get("max")); - } - - @Test(expected = DescriptorParseException.class) - public void testParamsOneTooLargePositive() - throws DescriptorParseException { - ConsensusBuilder.createWithParamsLine("params max=2147483648"); - } - - @Test() - public void testDirSourceLegacyNickname() - throws DescriptorParseException { - DirSourceBuilder dsb = new DirSourceBuilder(); - dsb.nickname = "gabelmoo-legacy"; - dsb.identity = "81349FC1F2DBA2C2C11B45CB9706637D480AB913"; - dsb.contactLine = null; - dsb.voteDigestLine = null; - RelayNetworkStatusConsensus consensus = - DirSourceBuilder.createWithDirSource(dsb.buildDirSource()); - assertEquals(3, consensus.getDirSourceEntries().size()); - assertTrue(consensus.getDirSourceEntries().get( - "81349FC1F2DBA2C2C11B45CB9706637D480AB913").isLegacy()); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceNicknameTooLong() - throws DescriptorParseException { - DirSourceBuilder.createWithNickname("gabelmooisfinebutthisistoolong"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceIdentityTooShort() - throws DescriptorParseException { - DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceIdentityTooLong() - throws DescriptorParseException { - DirSourceBuilder.createWithIdentity("ED03BB616EB2F60BEC8015111" - + "4BB25CEF515B226ED03BB616EB2F60BEC8"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceHostnameMissing() - throws DescriptorParseException { - DirSourceBuilder.createWithHostName(""); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceAddress24() throws DescriptorParseException { - DirSourceBuilder.createWithAddress("212.112.245"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceAddress40() throws DescriptorParseException { - DirSourceBuilder.createWithAddress("212.112.245.170.123"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceDirPortMinusOne() - throws DescriptorParseException { - DirSourceBuilder.createWithDirPort("-1"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceDirPort66666() - throws DescriptorParseException { - DirSourceBuilder.createWithDirPort("66666"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceDirPortOnions() - throws DescriptorParseException { - DirSourceBuilder.createWithDirPort("onions"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceOrPortOnions() - throws DescriptorParseException { - DirSourceBuilder.createWithOrPort("onions"); - } - - @Test() - public void testDirSourceContactNoLine() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - DirSourceBuilder.createWithContactLine(null); - assertNull(consensus.getDirSourceEntries().get( - "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); - } - - @Test() - public void testDirSourceContactLineNoSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - DirSourceBuilder.createWithContactLine("contact"); - assertNotNull(consensus.getDirSourceEntries().get( - "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); - } - - @Test() - public void testDirSourceContactLineOneSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - DirSourceBuilder.createWithContactLine("contact "); - assertNotNull(consensus.getDirSourceEntries().get( - "ED03BB616EB2F60BEC80151114BB25CEF515B226").getContactLine()); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceVoteDigestNoLine() - throws DescriptorParseException { - DirSourceBuilder.createWithVoteDigestLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceVoteDigestLineNoSpace() - throws DescriptorParseException { - DirSourceBuilder.createWithVoteDigestLine("vote-digest"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceVoteDigestLineOneSpace() - throws DescriptorParseException { - DirSourceBuilder.createWithVoteDigestLine("vote-digest "); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameNotAllowedChars() - throws DescriptorParseException { - StatusEntryBuilder.createWithNickname("notAll()wed"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameTooLong() throws DescriptorParseException { - StatusEntryBuilder.createWithNickname("1234567890123456789tooLong"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooShort() throws DescriptorParseException { - StatusEntryBuilder.createWithFingerprintBase64("TooShort"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintEndsWithEqualSign() - throws DescriptorParseException { - StatusEntryBuilder.createWithFingerprintBase64( - "ADQ6gCT3DiFHKPDFr3rODBUI8H="); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooLong() throws DescriptorParseException { - StatusEntryBuilder.createWithFingerprintBase64( - "ADQ6gCT3DiFHKPDFr3rODBUI8HMAAAA"); - } - - @Test(expected = DescriptorParseException.class) - public void testDescriptorTooShort() throws DescriptorParseException { - StatusEntryBuilder.createWithDescriptorBase64("TooShort"); - } - - @Test(expected = DescriptorParseException.class) - public void testDescriptorEndsWithEqualSign() - throws DescriptorParseException { - StatusEntryBuilder.createWithDescriptorBase64( - "ADQ6gCT3DiFHKPDFr3rODBUI8H="); - } - - @Test(expected = DescriptorParseException.class) - public void testDescriptorTooLong() throws DescriptorParseException { - StatusEntryBuilder.createWithDescriptorBase64( - "Yiti+nayuT2Efe2X1+M4nslwVuUAAAA"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublished1960() throws DescriptorParseException { - StatusEntryBuilder.createWithPublishedString("1960-11-29 21:34:27"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublished9999() throws DescriptorParseException { - StatusEntryBuilder.createWithPublishedString("9999-11-29 21:34:27"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddress256() throws DescriptorParseException { - StatusEntryBuilder.createWithAddress("256.63.8.215"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddress24() throws DescriptorParseException { - StatusEntryBuilder.createWithAddress("50.63.8/24"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddressV6() throws DescriptorParseException { - StatusEntryBuilder.createWithAddress("::1"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPort66666() throws DescriptorParseException { - StatusEntryBuilder.createWithOrPort("66666"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPortEighty() throws DescriptorParseException { - StatusEntryBuilder.createWithOrPort("eighty"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirPortMinusOne() throws DescriptorParseException { - StatusEntryBuilder.createWithDirPort("-1"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirPortZero() throws DescriptorParseException { - StatusEntryBuilder.createWithDirPort("zero"); - } - - @Test() - public void testSLineNoSpace() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - StatusEntryBuilder.createWithSLine("s"); - assertTrue(consensus.getStatusEntry( - "00343A8024F70E214728F0C5AF7ACE0C1508F073").getFlags().isEmpty()); - } - - @Test() - public void testSLineOneSpace() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - StatusEntryBuilder.createWithSLine("s "); - assertTrue(consensus.getStatusEntry( - "00343A8024F70E214728F0C5AF7ACE0C1508F073").getFlags().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testTwoSLines() throws DescriptorParseException { - StatusEntryBuilder sb = new StatusEntryBuilder(); - sb.sLine = sb.sLine + "\n" + sb.sLine; - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.add(sb.buildStatusEntry()); - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - - @Test(expected = DescriptorParseException.class) - public void testWLineNoSpace() throws DescriptorParseException { - StatusEntryBuilder.createWithWLine("w"); - } - - @Test(expected = DescriptorParseException.class) - public void testWLineOneSpace() throws DescriptorParseException { - StatusEntryBuilder.createWithWLine("w "); - } - - @Test() - public void testWLineWarpSeven() throws DescriptorParseException { - StatusEntryBuilder.createWithWLine("w Warp=7"); - } - - @Test(expected = DescriptorParseException.class) - public void testTwoWLines() throws DescriptorParseException { - StatusEntryBuilder sb = new StatusEntryBuilder(); - sb.wLine = sb.wLine + "\n" + sb.wLine; - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.add(sb.buildStatusEntry()); - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - - @Test() - public void testWLineUnmeasured() throws DescriptorParseException { - StatusEntryBuilder sb = new StatusEntryBuilder(); - sb.wLine = "w Bandwidth=42424242 Unmeasured=1"; - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.add(sb.buildStatusEntry()); - RelayNetworkStatusConsensus consensus = - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - for (NetworkStatusEntry s : consensus.getStatusEntries().values()) { - if (s.getBandwidth() == 42424242L) { - assertTrue(s.getUnmeasured()); - } - } - } - - @Test() - public void testWLineNotUnmeasured() throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - StatusEntryBuilder.createWithWLine("w Bandwidth=20"); - for (NetworkStatusEntry s : consensus.getStatusEntries().values()) { - assertFalse(s.getUnmeasured()); - } - } - - @Test(expected = DescriptorParseException.class) - public void testPLineNoPolicy() throws DescriptorParseException { - StatusEntryBuilder.createWithPLine("p 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testPLineNoPorts() throws DescriptorParseException { - StatusEntryBuilder.createWithPLine("p accept"); - } - - @Test(expected = DescriptorParseException.class) - public void testPLineNoPolicyNoPorts() throws DescriptorParseException { - StatusEntryBuilder.createWithPLine("p "); - } - - @Test(expected = DescriptorParseException.class) - public void testPLineProject() throws DescriptorParseException { - StatusEntryBuilder.createWithPLine("p project 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testTwoPLines() throws DescriptorParseException { - StatusEntryBuilder sb = new StatusEntryBuilder(); - sb.pLine = sb.pLine + "\n" + sb.pLine; - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.add(sb.buildStatusEntry()); - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - } - - @Test() - public void testNoStatusEntries() throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.statusEntries.clear(); - RelayNetworkStatusConsensus consensus = - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - assertFalse(consensus.containsStatusEntry( - "00795A6E8D91C270FC23B30F388A495553E01894")); - } - - @Test(expected = DescriptorParseException.class) - public void testDirectoryFooterNoLine() - throws DescriptorParseException { - /* This breaks, because a bandwidth-weights line without a preceding - * directory-footer line is not allowed. */ - ConsensusBuilder.createWithDirectoryFooterLine(null); - } - - @Test() - public void testDirectoryFooterMissing() - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.setDirectoryFooterLine(null); - cb.setBandwidthWeightsLine(null); - /* This does not break, because directory footers were optional before - * consensus method 9. */ - RelayNetworkStatusConsensus consensus = - new RelayNetworkStatusConsensusImpl(cb.buildConsensus(), true); - assertNull(consensus.getBandwidthWeights()); - } - - @Test() - public void testDirectoryFooterLineSpace() - throws DescriptorParseException { - ConsensusBuilder.createWithDirectoryFooterLine("directory-footer "); - } - - @Test() - public void testBandwidthWeightsNoLine() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = - ConsensusBuilder.createWithBandwidthWeightsLine(null); - assertNull(consensus.getBandwidthWeights()); - } - - @Test() - public void testBandwidthWeightsLineNoSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithBandwidthWeightsLine("bandwidth-weights"); - assertNotNull(consensus.getBandwidthWeights()); - } - - @Test() - public void testBandwidthWeightsLineOneSpace() - throws DescriptorParseException { - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithBandwidthWeightsLine("bandwidth-weights "); - assertNotNull(consensus.getBandwidthWeights()); - } - - @Test(expected = DescriptorParseException.class) - public void testBandwidthWeightsLineNoEqualSign() - throws DescriptorParseException { - ConsensusBuilder.createWithBandwidthWeightsLine( - "bandwidth-weights Wbd-285"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirectorySignatureIdentityTooShort() - throws DescriptorParseException { - DirectorySignatureBuilder.createWithIdentity("ED03BB616EB2F60"); - } - - @Test() - public void testDirectorySignatureIdentityTooLong() - throws DescriptorParseException { - /* This hex string has an unusual length of 58 hex characters, but - * dir-spec.txt only requires a hex string, and we can't know all hex - * string lengths for all future digest algorithms, so let's just - * accept this. */ - DirectorySignatureBuilder.createWithIdentity( - "ED03BB616EB2F60BEC80151114BB25CEF515B226ED03BB616EB2F60BEC"); - } - - @Test() - public void testDirectorySignatureSigningKeyTooShort() - throws DescriptorParseException { - /* See above, we accept this hex string even though it's unusually - * short. */ - DirectorySignatureBuilder.createWithSigningKey("845CF1D0B370CA"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirectorySignatureSigningKeyTooShortOddNumber() - throws DescriptorParseException { - /* We don't accept this hex string, because it contains an odd number - * of hex characters. */ - DirectorySignatureBuilder.createWithSigningKey("845"); - } - - @Test() - public void testDirectorySignatureSigningKeyTooLong() - throws DescriptorParseException { - /* See above, we accept this hex string even though it's unusually - * long. */ - DirectorySignatureBuilder.createWithSigningKey( - "845CF1D0B370CA443A8579D18E7987E7E532F639845CF1D0B370CA443A"); - } - - @Test(expected = DescriptorParseException.class) - public void testNonAsciiByte20() throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - byte[] consensusBytes = cb.buildConsensus(); - consensusBytes[20] = (byte) 200; - new RelayNetworkStatusConsensusImpl(consensusBytes, true); - } - - @Test(expected = DescriptorParseException.class) - public void testNonAsciiByteMinusOne() - throws DescriptorParseException { - ConsensusBuilder cb = new ConsensusBuilder(); - cb.networkStatusVersionLine = "Xnetwork-status-version 3"; - byte[] consensusBytes = cb.buildConsensus(); - consensusBytes[0] = (byte) 200; - new RelayNetworkStatusConsensusImpl(consensusBytes, true); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedHeaderLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ConsensusBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine, - true); - } - - @Test() - public void testUnrecognizedHeaderLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithUnrecognizedHeaderLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedDirSourceLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ConsensusBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine, - true); - } - - @Test() - public void testUnrecognizedDirSourceLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithUnrecognizedDirSourceLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedStatusEntryLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ConsensusBuilder.createWithUnrecognizedStatusEntryLine( - unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedStatusEntryLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithUnrecognizedStatusEntryLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedDirectoryFooterLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ConsensusBuilder.createWithUnrecognizedFooterLine(unrecognizedLine, - true); - } - - @Test() - public void testUnrecognizedDirectoryFooterLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithUnrecognizedFooterLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedDirectorySignatureLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ConsensusBuilder.createWithUnrecognizedDirectorySignatureLine( - unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedDirectorySignatureLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusConsensus consensus = ConsensusBuilder. - createWithUnrecognizedDirectorySignatureLine(unrecognizedLine, - false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, consensus.getUnrecognizedLines()); - } -} - diff --git a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java b/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java deleted file mode 100644 index 1c840f5..0000000 --- a/test/org/torproject/descriptor/impl/RelayNetworkStatusVoteImplTest.java +++ /dev/null @@ -1,1373 +0,0 @@ -/* Copyright 2011--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import org.torproject.descriptor.DirectorySignature; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; -import org.torproject.descriptor.RelayNetworkStatusVote; - -/* TODO Add test cases for all lines starting with "opt ". */ - -/* Test parsing of network status votes. Some of the vote-parsing code is - * already tested in the consensus-parsing tests. The tests in this class - * focus on the differences between votes and consensuses that are mostly - * in the directory header. */ -public class RelayNetworkStatusVoteImplTest { - - /* Helper class to build a vote based on default data and modifications - * requested by test methods. */ - private static class VoteBuilder { - private String networkStatusVersionLine = "network-status-version 3"; - private static RelayNetworkStatusVote - createWithNetworkStatusVersionLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.networkStatusVersionLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String voteStatusLine = "vote-status vote"; - private static RelayNetworkStatusVote - createWithVoteStatusLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.voteStatusLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String consensusMethodsLine = - "consensus-methods 1 2 3 4 5 6 7 8 9 10 11"; - private static RelayNetworkStatusVote - createWithConsensusMethodsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.consensusMethodsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String publishedLine = "published 2011-11-30 08:50:01"; - private static RelayNetworkStatusVote - createWithPublishedLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.publishedLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String validAfterLine = "valid-after 2011-11-30 09:00:00"; - private static RelayNetworkStatusVote - createWithValidAfterLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.validAfterLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String freshUntilLine = "fresh-until 2011-11-30 10:00:00"; - private static RelayNetworkStatusVote - createWithFreshUntilLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.freshUntilLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String validUntilLine = "valid-until 2011-11-30 12:00:00"; - private static RelayNetworkStatusVote - createWithValidUntilLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.validUntilLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String votingDelayLine = "voting-delay 300 300"; - private static RelayNetworkStatusVote - createWithVotingDelayLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.votingDelayLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String clientVersionsLine = "client-versions 0.2.1.31," - + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; - private static RelayNetworkStatusVote - createWithClientVersionsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.clientVersionsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String serverVersionsLine = "server-versions 0.2.1.31," - + "0.2.2.34,0.2.3.6-alpha,0.2.3.7-alpha,0.2.3.8-alpha"; - private static RelayNetworkStatusVote - createWithServerVersionsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.serverVersionsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String packageLines = null; - protected static RelayNetworkStatusVote - createWithPackageLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.packageLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String knownFlagsLine = "known-flags Authority BadExit Exit " - + "Fast Guard HSDir Named Running Stable Unnamed V2Dir Valid"; - private static RelayNetworkStatusVote - createWithKnownFlagsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.knownFlagsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String flagThresholdsLine = "flag-thresholds " - + "stable-uptime=693369 stable-mtbf=153249 fast-speed=40960 " - + "guard-wfu=94.669% guard-tk=691200 guard-bw-inc-exits=174080 " - + "guard-bw-exc-exits=184320 enough-mtbf=1"; - private static RelayNetworkStatusVote - createWithFlagThresholdsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.flagThresholdsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String paramsLine = "params " - + "CircuitPriorityHalflifeMsec=30000 bwauthbestratio=1 " - + "bwauthcircs=1 bwauthdescbw=0 bwauthkp=10000 bwauthpid=1 " - + "bwauthtd=5000 bwauthti=50000 bwauthtidecay=5000 cbtnummodes=3 " - + "cbtquantile=80 circwindow=1000 refuseunknownexits=1"; - private static RelayNetworkStatusVote - createWithParamsLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.paramsLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirSourceLine = "dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"; - private static RelayNetworkStatusVote - createWithDirSourceLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirSourceLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String contactLine = "contact 4096R/E012B42D Jacob Appelbaum " - + "jacob@appelbaum.net"; - private static RelayNetworkStatusVote - createWithContactLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.contactLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String legacyDirKeyLine = null; - private static RelayNetworkStatusVote - createWithLegacyDirKeyLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.legacyDirKeyLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirKeyCertificateVersionLine = - "dir-key-certificate-version 3"; - private static RelayNetworkStatusVote - createWithDirKeyCertificateVersionLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirKeyCertificateVersionLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String fingerprintLine = "fingerprint " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C"; - private static RelayNetworkStatusVote - createWithFingerprintLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.fingerprintLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirKeyPublishedLine = "dir-key-published 2011-04-27 " - + "05:34:37"; - private static RelayNetworkStatusVote - createWithDirKeyPublishedLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirKeyPublishedLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirKeyExpiresLine = "dir-key-expires 2012-04-27 " - + "05:34:37"; - private static RelayNetworkStatusVote - createWithDirKeyExpiresLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirKeyExpiresLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirIdentityKeyLines = "dir-identity-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIIBigKCAYEAtKpuLgVK25sfScjsxfVU1ljofrDygt9GP7bNJl/rghX42KUT97" - + "5W\nrGp/fbhF7p+FcKCzNOhJFINQbRf/5E3lN8mzoamIU43QqQ9RRVf94688Us" - + "azVsAN\nNVT0v9J0cr387WePjenRuIE1MmiP0nmw/XdvbPTayqax7VYlcUMXGH" - + "l8DnWix1EN\nRwmeig+JBte0JS12oo2HG9zcSfjLJVjY6ZmvRrVycXiRxGc/Jg" - + "NlSrV4cxUNykaB\nJ6pO6J499OZfQu7m1vAPTENrVJ4yEfRGRwFIY+d/s8BkKc" - + "aiWtXAfTe31uBI6GEH\nmS3HNu1JVSuoaUiQIvVYDLMfBvMcNyAx97UT1l6E0T" - + "n6a7pgChrquGwXai1xGzk8\n58aXwdSFoFBSTCkyemopq5H20p/nkPAO0pHL1k" - + "TvcaKz9CEj4XcKm+kOmzejYmIa\nkbWNcRpXPiUZ+xmwGtsq30xrzqiONmERkx" - + "qlmf7bVQPFvh3Kz6hGcmTBhTbHSe9h\nzDgmdaTNn3EHAgMBAAE=\n" - + "-----END RSA PUBLIC KEY-----"; - private static RelayNetworkStatusVote - createWithDirIdentityKeyLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirIdentityKeyLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirSigningKeyLines = "dir-signing-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBAN05qyHFQlTqykMP8yLuD4G2UuYulD4Xs8iSX5uqF+WGsUA1E4zZh4" - + "8h\nDFj8+drFiCu3EqhMEmVG4ACtJK2uz6D1XohUsbPWTR6LSnWJ8q6/zfTSLu" - + "mBGsN7\nPUXyMNjwRKL6UvrcbYk1d2mRBLO7SAP/sFW5fHhIBVeLIWrzQ19rAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"; - private static RelayNetworkStatusVote - createWithDirSigningKeyLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirSigningKeyLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirKeyCrosscertLines = "dir-key-crosscert\n" - + "-----BEGIN ID SIGNATURE-----\n" - + "rPBFn6IJ6TvAHj4pSwlg+RTn1fP89JGSVa08wuyJr5dAvZsdakQXvRjamT9oJU" - + "aZ\nnY5Rl/tRlGuSQ0BglTPPKoXdKERK0FUr9f0EKrQy7NDUgE2j9losiRuyKz" - + "hA3neZ\nK4yF8bhqAwM51u7fzAhIjNeRif9c04rhFJJCseco84w=\n" - + "-----END ID SIGNATURE-----"; - private static RelayNetworkStatusVote - createWithDirKeyCrosscertLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirKeyCrosscertLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String dirKeyCertificationLines = "dir-key-certification\n" - + "-----BEGIN SIGNATURE-----\n" - + "hPSh6FuohNF5ccjiMbkvr8cZJwGFuL11cNtwN9k0X3pUdFZVATIEkqBe7z+rE2" - + "PX\nPw+BGyC6wYAieoTVIhLpwKqd7DXLYjuhPZ28+7MQaDL01AqYeRp5PT01Px" - + "rFY0Um\nlVf95uqUitgvDT76Ne4ExWk6UvGlYB9OBgBySZz8VWe9znoMqb0uHn" - + "/p8IzqTApT\nAxRWXBHClntMeRqtGxaj8DcdJFn8yMxQiZG7MfDg2sq2ySPJyG" - + "lN+neoVDVhZiDI\n9LTNmw60gWlUp2erFeam8Mo1ZBC4DPNjQEm6QeHZFZMkhD" - + "uO6SwS/FL712A42+Co\nYtMaVot/p5FG2ZSBXbgl2XP5/z8ELnpmXqMbPAoWRo" - + "3BPNSJkIQQNog8Q5ZrK+av\nZDw5eGPltGKsXOkvuzIMM8nBeAnDPDgYvzrIFO" - + "bEGbvY/P8mzVAZxp3Yz+sRtNel\nC1SWz/Fx+Saex5oI7DJ3xtSD4XqKb/wYwZ" - + "FT8IxDYq1t2tFXdHxd4QPRVcvc0zYC\n" - + "-----END SIGNATURE-----"; - private static RelayNetworkStatusVote - createWithDirKeyCertificationLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.dirKeyCertificationLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private List<String> statusEntries = null; - private static RelayNetworkStatusVote createWithStatusEntries( - List<String> statusEntries) throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.statusEntries = statusEntries; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String directoryFooterLine = "directory-footer"; - private static RelayNetworkStatusVote - createWithDirectoryFooterLine(String line) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.directoryFooterLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String directorySignatureLines = "directory-signature " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C " - + "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19\n" - + "-----BEGIN SIGNATURE-----\n" - + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn" - + "F3Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi" - + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" - + "-----END SIGNATURE-----"; - private static RelayNetworkStatusVote - createWithDirectorySignatureLines(String lines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.directorySignatureLines = lines; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - private String unrecognizedHeaderLine = null; - protected static RelayNetworkStatusVote - createWithUnrecognizedHeaderLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.unrecognizedHeaderLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedDirSourceLine = null; - protected static RelayNetworkStatusVote - createWithUnrecognizedDirSourceLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.unrecognizedDirSourceLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedStatusEntryLine = null; - protected static RelayNetworkStatusVote - createWithUnrecognizedStatusEntryLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.unrecognizedStatusEntryLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedFooterLine = null; - protected static RelayNetworkStatusVote - createWithUnrecognizedFooterLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.unrecognizedFooterLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), - failUnrecognizedDescriptorLines); - } - private String unrecognizedDirectorySignatureLine = null; - protected static RelayNetworkStatusVote - createWithUnrecognizedDirectorySignatureLine(String line, - boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.unrecognizedDirectorySignatureLine = line; - return new RelayNetworkStatusVoteImpl(vb.buildVote(), - failUnrecognizedDescriptorLines); - } - - private VoteBuilder() { - if (this.statusEntries != null) { - return; - } - this.statusEntries = new ArrayList<>(); - this.statusEntries.add("r right2privassy3 " - + "ADQ6gCT3DiFHKPDFr3rODBUI8HM lJY5Vf7kXec+VdkGW2flEsfkFC8 " - + "2011-11-12 00:03:40 50.63.8.215 9023 0\n" - + "s Exit Fast Guard Running Stable Valid\n" - + "opt v Tor 0.2.1.29 (r8e9b25e6c7a2e70c)\n" - + "w Bandwidth=297 Measured=73\n" - + "p accept 80,1194,1220,1293,1500,1533,1677,1723,1863," - + "2082-2083,2086-2087,2095-2096,2102-2104,3128,3389,3690,4321," - + "4643,5050,5190,5222-5223,5228,5900,6660-6669,6679,6697,8000," - + "8008,8074,8080,8087-8088,8443,8888,9418,9999-10000,19294," - + "19638\n" - + "m 8,9,10,11 " - + "sha256=9ciEx9t0McXk9A06I7qwN7pxuNOdpCP64RV/6cx2Zkc"); - } - private byte[] buildVote() { - StringBuilder sb = new StringBuilder(); - this.appendHeader(sb); - this.appendDirSource(sb); - this.appendStatusEntries(sb); - this.appendFooter(sb); - this.appendDirectorySignature(sb); - return sb.toString().getBytes(); - } - private void appendHeader(StringBuilder sb) { - if (this.networkStatusVersionLine != null) { - sb.append(this.networkStatusVersionLine).append("\n"); - } - if (this.voteStatusLine != null) { - sb.append(this.voteStatusLine).append("\n"); - } - if (this.consensusMethodsLine != null) { - sb.append(this.consensusMethodsLine).append("\n"); - } - if (this.publishedLine != null) { - sb.append(this.publishedLine).append("\n"); - } - if (this.validAfterLine != null) { - sb.append(this.validAfterLine).append("\n"); - } - if (this.freshUntilLine != null) { - sb.append(this.freshUntilLine).append("\n"); - } - if (this.validUntilLine != null) { - sb.append(this.validUntilLine).append("\n"); - } - if (this.votingDelayLine != null) { - sb.append(this.votingDelayLine).append("\n"); - } - if (this.clientVersionsLine != null) { - sb.append(this.clientVersionsLine).append("\n"); - } - if (this.serverVersionsLine != null) { - sb.append(this.serverVersionsLine).append("\n"); - } - if (this.packageLines != null) { - sb.append(this.packageLines).append("\n"); - } - if (this.knownFlagsLine != null) { - sb.append(this.knownFlagsLine).append("\n"); - } - if (this.flagThresholdsLine != null) { - sb.append(this.flagThresholdsLine).append("\n"); - } - if (this.paramsLine != null) { - sb.append(this.paramsLine).append("\n"); - } - if (this.unrecognizedHeaderLine != null) { - sb.append(this.unrecognizedHeaderLine).append("\n"); - } - } - private void appendDirSource(StringBuilder sb) { - if (this.dirSourceLine != null) { - sb.append(this.dirSourceLine).append("\n"); - } - if (this.contactLine != null) { - sb.append(this.contactLine).append("\n"); - } - if (this.legacyDirKeyLine != null) { - sb.append(this.legacyDirKeyLine).append("\n"); - } - if (this.dirKeyCertificateVersionLine != null) { - sb.append(this.dirKeyCertificateVersionLine).append("\n"); - } - if (this.fingerprintLine != null) { - sb.append(this.fingerprintLine).append("\n"); - } - if (this.dirKeyPublishedLine != null) { - sb.append(this.dirKeyPublishedLine).append("\n"); - } - if (this.dirKeyExpiresLine != null) { - sb.append(this.dirKeyExpiresLine).append("\n"); - } - if (this.dirIdentityKeyLines != null) { - sb.append(this.dirIdentityKeyLines).append("\n"); - } - if (this.dirSigningKeyLines != null) { - sb.append(this.dirSigningKeyLines).append("\n"); - } - if (this.dirKeyCrosscertLines != null) { - sb.append(this.dirKeyCrosscertLines).append("\n"); - } - if (this.dirKeyCertificationLines != null) { - sb.append(this.dirKeyCertificationLines).append("\n"); - } - if (this.unrecognizedDirSourceLine != null) { - sb.append(this.unrecognizedDirSourceLine).append("\n"); - } - } - private void appendStatusEntries(StringBuilder sb) { - for (String statusEntry : this.statusEntries) { - sb.append(statusEntry).append("\n"); - } - if (this.unrecognizedStatusEntryLine != null) { - sb.append(this.unrecognizedStatusEntryLine).append("\n"); - } - } - private void appendFooter(StringBuilder sb) { - if (this.directoryFooterLine != null) { - sb.append(this.directoryFooterLine).append("\n"); - } - if (this.unrecognizedFooterLine != null) { - sb.append(this.unrecognizedFooterLine).append("\n"); - } - } - private void appendDirectorySignature(StringBuilder sb) { - if (this.directorySignatureLines != null) { - sb.append(directorySignatureLines).append("\n"); - } - if (this.unrecognizedDirectorySignatureLine != null) { - sb.append(this.unrecognizedDirectorySignatureLine).append("\n"); - } - } - } - - @Test() - public void testSampleVote() throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - RelayNetworkStatusVote vote = - new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - assertEquals(3, vote.getNetworkStatusVersion()); - List<Integer> consensusMethods = Arrays.asList( - new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); - assertEquals(vote.getConsensusMethods(), consensusMethods); - assertEquals(1322643001000L, vote.getPublishedMillis()); - assertEquals(1322643600000L, vote.getValidAfterMillis()); - assertEquals(1322647200000L, vote.getFreshUntilMillis()); - assertEquals(1322654400000L, vote.getValidUntilMillis()); - assertEquals(300L, vote.getVoteSeconds()); - assertEquals(300L, vote.getDistSeconds()); - assertTrue(vote.getKnownFlags().contains("Running")); - assertEquals(30000, (int) vote.getConsensusParams().get( - "CircuitPriorityHalflifeMsec")); - assertEquals("Tor 0.2.1.29 (r8e9b25e6c7a2e70c)", - vote.getStatusEntry("00343A8024F70E214728F0C5AF7ACE0C1508F073"). - getVersion()); - assertEquals(3, vote.getDirKeyCertificateVersion()); - assertEquals("80550987E1D626E3EBA5E5E75A458DE0626D088C", - vote.getIdentity()); - assertEquals(1303882477000L, /* 2011-04-27 05:34:37 */ - vote.getDirKeyPublishedMillis()); - assertEquals(1335504877000L, /* 2012-04-27 05:34:37 */ - vote.getDirKeyExpiresMillis()); - assertEquals("-----BEGIN RSA PUBLIC KEY-----", - vote.getDirIdentityKey().split("\n")[0]); - assertEquals("-----BEGIN RSA PUBLIC KEY-----", - vote.getDirSigningKey().split("\n")[0]); - assertEquals("-----BEGIN ID SIGNATURE-----", - vote.getDirKeyCrosscert().split("\n")[0]); - assertEquals("-----BEGIN SIGNATURE-----", - vote.getDirKeyCertification().split("\n")[0]); - assertEquals(1, vote.getSignatures().size()); - DirectorySignature signature = vote.getSignatures().get(0); - assertEquals("sha1", signature.getAlgorithm()); - assertEquals("80550987E1D626E3EBA5E5E75A458DE0626D088C", - signature.getIdentity()); - assertEquals("EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19", - signature.getSigningKeyDigest()); - assertEquals("-----BEGIN SIGNATURE-----\n" - + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxn" - + "F3Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40Oi" - + "kfOIwEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" - + "-----END SIGNATURE-----\n", signature.getSignature()); - assertTrue(vote.getUnrecognizedLines().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNoLine() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNewLine() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version 3\n"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNewLineSpace() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version 3\n "); - } - - @Test() - public void testNetworkStatusVersionPrefixLineAtChar() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "@vote\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionPrefixLine() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "directory-footer\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionPrefixLinePoundChar() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "#vote\nnetwork-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionNoSpace() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionOneSpace() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version "); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersion42() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version 42"); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionFourtyTwo() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - "network-status-version FourtyTwo"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusNoLine() throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testNetworkStatusVersionSpaceBefore() - throws DescriptorParseException { - VoteBuilder.createWithNetworkStatusVersionLine( - " network-status-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusSpaceBefore() throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine(" vote-status vote"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusNoSpace() throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine("vote-status"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusOneSpace() throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine("vote-status "); - } - - @Test() - public void testVoteStatusVoteOneSpace() - throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine("vote-status vote "); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusConsensus() throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine("vote-status consensus"); - } - - @Test(expected = DescriptorParseException.class) - public void testVoteStatusTheMagicVoteStatus() - throws DescriptorParseException { - VoteBuilder.createWithVoteStatusLine( - "vote-status TheMagicVoteStatus"); - } - - @Test() - public void testConsensusMethodNoLine() - throws DescriptorParseException { - RelayNetworkStatusVote vote = - VoteBuilder.createWithConsensusMethodsLine(null); - assertNull(vote.getConsensusMethods()); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodNoSpace() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine("consensus-methods"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodOneSpace() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine("consensus-methods "); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodEleven() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine( - "consensus-methods eleven"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodMinusOne() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine("consensus-methods -1"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodNinePeriod() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine("consensus-methods " - + "999999999999999999999999999999999999999999999999999999999999"); - } - - @Test(expected = DescriptorParseException.class) - public void testConsensusMethodTwoLines() - throws DescriptorParseException { - VoteBuilder.createWithConsensusMethodsLine( - "consensus-method 1\nconsensus-method 1"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublishedNoLine() throws DescriptorParseException { - VoteBuilder.createWithPublishedLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterNoLine() throws DescriptorParseException { - VoteBuilder.createWithValidAfterLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterNoSpace() throws DescriptorParseException { - VoteBuilder.createWithValidAfterLine("valid-after"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterOneSpace() throws DescriptorParseException { - VoteBuilder.createWithValidAfterLine("valid-after "); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterLongAgo() throws DescriptorParseException { - VoteBuilder.createWithValidAfterLine("valid-after long ago"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidAfterFeb30() throws DescriptorParseException { - VoteBuilder.createWithValidAfterLine( - "valid-after 2011-02-30 09:00:00"); - } - - @Test(expected = DescriptorParseException.class) - public void testFreshUntilNoLine() throws DescriptorParseException { - VoteBuilder.createWithFreshUntilLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testFreshUntilAroundTen() throws DescriptorParseException { - VoteBuilder.createWithFreshUntilLine( - "fresh-until 2011-11-30 around ten"); - } - - @Test(expected = DescriptorParseException.class) - public void testValidUntilTomorrowMorning() - throws DescriptorParseException { - VoteBuilder.createWithValidUntilLine( - "valid-until tomorrow morning"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayNoLine() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayNoSpace() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine("voting-delay"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayOneSpace() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine("voting-delay "); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayTriple() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine( - "voting-delay 300 300 300"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelaySingle() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine("voting-delay 300"); - } - - @Test(expected = DescriptorParseException.class) - public void testVotingDelayOneTwo() throws DescriptorParseException { - VoteBuilder.createWithVotingDelayLine("voting-delay one two"); - } - - @Test(expected = DescriptorParseException.class) - public void testClientVersionsComma() throws DescriptorParseException { - VoteBuilder.createWithClientVersionsLine("client-versions ,"); - } - - @Test(expected = DescriptorParseException.class) - public void testClientVersionsCommaVersion() - throws DescriptorParseException { - VoteBuilder.createWithClientVersionsLine( - "client-versions ,0.2.2.34"); - } - - @Test() - public void testPackageNone() throws DescriptorParseException { - RelayNetworkStatusVote vote = - VoteBuilder.createWithPackageLines(null); - assertNull(vote.getPackageLines()); - } - - @Test() - public void testPackageOne() throws DescriptorParseException { - String packageLine = "package shouldbesecond 0 http digest=digest"; - RelayNetworkStatusVote vote = - VoteBuilder.createWithPackageLines(packageLine); - assertEquals(packageLine.substring("package ".length()), - vote.getPackageLines().get(0)); - } - - @Test() - public void testPackageTwo() throws DescriptorParseException { - List<String> packageLines = Arrays.asList( - "package shouldbesecond 0 http digest=digest", - "package outoforder 0 http digest=digest"); - RelayNetworkStatusVote vote = - VoteBuilder.createWithPackageLines(packageLines.get(0) - + "\n" + packageLines.get(1)); - for (int i = 0; i < packageLines.size(); i++) { - assertEquals(packageLines.get(i).substring("package ".length()), - vote.getPackageLines().get(i)); - } - } - - @Test(expected = DescriptorParseException.class) - public void testPackageIncomplete() throws DescriptorParseException { - String packageLine = "package shouldbesecond 0 http"; - ConsensusBuilder.createWithPackageLines(packageLine); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsNoLine() throws DescriptorParseException { - VoteBuilder.createWithKnownFlagsLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsNoSpace() throws DescriptorParseException { - VoteBuilder.createWithKnownFlagsLine("known-flags"); - } - - @Test(expected = DescriptorParseException.class) - public void testKnownFlagsOneSpace() throws DescriptorParseException { - VoteBuilder.createWithKnownFlagsLine("known-flags "); - } - - @Test() - public void testFlagThresholdsLine() throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - RelayNetworkStatusVote vote = - new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - assertEquals(693369L, vote.getStableUptime()); - assertEquals(153249L, vote.getStableMtbf()); - assertEquals(40960L, vote.getFastBandwidth()); - assertEquals(94.669, vote.getGuardWfu(), 0.001); - assertEquals(691200L, vote.getGuardTk()); - assertEquals(174080L, vote.getGuardBandwidthIncludingExits()); - assertEquals(184320L, vote.getGuardBandwidthExcludingExits()); - assertEquals(1, vote.getEnoughMtbfInfo()); - } - - @Test() - public void testFlagThresholdsNoLine() throws DescriptorParseException { - RelayNetworkStatusVote vote = - VoteBuilder.createWithFlagThresholdsLine(null); - assertEquals(-1L, vote.getStableUptime()); - assertEquals(-1L, vote.getStableMtbf()); - assertEquals(-1L, vote.getFastBandwidth()); - assertEquals(-1.0, vote.getGuardWfu(), 0.001); - assertEquals(-1L, vote.getGuardTk()); - assertEquals(-1L, vote.getGuardBandwidthIncludingExits()); - assertEquals(-1L, vote.getGuardBandwidthExcludingExits()); - assertEquals(-1, vote.getEnoughMtbfInfo()); - } - - @Test() - public void testFlagThresholdsAllZeroes() - throws DescriptorParseException { - RelayNetworkStatusVote vote = - VoteBuilder.createWithFlagThresholdsLine("flag-thresholds " - + "stable-uptime=0 stable-mtbf=0 fast-speed=0 guard-wfu=0.0% " - + "guard-tk=0 guard-bw-inc-exits=0 guard-bw-exc-exits=0 " - + "enough-mtbf=0"); - assertEquals(0L, vote.getStableUptime()); - assertEquals(0L, vote.getStableMtbf()); - assertEquals(0L, vote.getFastBandwidth()); - assertEquals(0.0, vote.getGuardWfu(), 0.001); - assertEquals(0L, vote.getGuardTk()); - assertEquals(0L, vote.getGuardBandwidthIncludingExits()); - assertEquals(0L, vote.getGuardBandwidthExcludingExits()); - assertEquals(0, vote.getEnoughMtbfInfo()); - } - - @Test(expected = DescriptorParseException.class) - public void testFlagThresholdsNoSpace() - throws DescriptorParseException { - VoteBuilder.createWithFlagThresholdsLine("flag-thresholds"); - } - - @Test(expected = DescriptorParseException.class) - public void testFlagThresholdsOneSpace() - throws DescriptorParseException { - VoteBuilder.createWithFlagThresholdsLine("flag-thresholds "); - } - - @Test(expected = DescriptorParseException.class) - public void testFlagThresholdDuplicate() - throws DescriptorParseException { - VoteBuilder vb = new VoteBuilder(); - vb.flagThresholdsLine = vb.flagThresholdsLine + "\n" - + vb.flagThresholdsLine; - new RelayNetworkStatusVoteImpl(vb.buildVote(), true); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameMissing() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameTooLong() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source " - + "urrassssssssssssssssssssssssssssssssssssssssssssssss " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameIllegalCharacters() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urra$ " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test() - public void testFingerprintLowerCase() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987e1d626e3eba5e5e75a458de0626d088c 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooShort() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooLong() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintIllegalCharacters() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "ABCDEFGHIJKLM6E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + " 208.83.223.34 208.83.223.34 443 80"); - } - - @Test() - public void testHostname256() - throws DescriptorParseException { - /* This test doesn't fail, because we're not parsing the hostname. */ - RelayNetworkStatusVote vote = - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 256.256.256.256 " - + "208.83.223.34 443 80"); - assertEquals("256.256.256.256", vote.getHostname()); - } - - @Test(expected = DescriptorParseException.class) - public void testHostnameMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 443 " - + "80"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddress256() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "256.256.256.256 443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddressMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 443 " - + "80"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirPortMinus443() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 -443 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirPortFourFourThree() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 four-four-three 80"); - } - - @Test() - public void testDirPort0() throws DescriptorParseException { - /* This test doesn't fail, because we're accepting DirPort 0, even - * though it doesn't make sense from Tor's view. */ - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 0 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPortMissing() throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 "); - } - - @Test() - public void testDirPortOrPortIdentical() - throws DescriptorParseException { - /* This test doesn't fail, even though identical OR and Dir port don't - * make much sense from Tor's view. */ - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 80 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSourceLineDuplicate() - throws DescriptorParseException { - VoteBuilder.createWithDirSourceLine("dir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80\ndir-source urras " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34 " - + "208.83.223.34 443 80"); - } - - @Test() - public void testContactLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithContactLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testContactLineDuplicate() - throws DescriptorParseException { - VoteBuilder.createWithContactLine("contact 4096R/E012B42D Jacob " - + "Appelbaum jacob@appelbaum.net\ncontact 4096R/E012B42D Jacob " - + "Appelbaum jacob@appelbaum.net"); - } - - @Test() - public void testLegacyDirKeyLine() throws DescriptorParseException { - RelayNetworkStatusVote vote = VoteBuilder.createWithLegacyDirKeyLine( - "legacy-dir-key 81349FC1F2DBA2C2C11B45CB9706637D480AB913"); - assertEquals("81349FC1F2DBA2C2C11B45CB9706637D480AB913", - vote.getLegacyDirKey()); - } - - @Test(expected = DescriptorParseException.class) - public void testLegacyDirKeyLineNoId() throws DescriptorParseException { - VoteBuilder.createWithLegacyDirKeyLine("legacy-dir-key "); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyCertificateVersionLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyCertificateVersionLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyCertificateVersionLineDuplicate() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyCertificateVersionLine( - "dir-key-certificate-version 3\ndir-key-certificate-version 3"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithFingerprintLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintLineDuplicate() - throws DescriptorParseException { - VoteBuilder.createWithFingerprintLine("fingerprint " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C\nfingerprint " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintLineTooLong() - throws DescriptorParseException { - VoteBuilder.createWithFingerprintLine("fingerprint " - + "80550987E1D626E3EBA5E5E75A458DE0626D088C8055"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintLineTooShort() - throws DescriptorParseException { - VoteBuilder.createWithFingerprintLine("fingerprint " - + "80550987E1D626E3EBA5E5E75A458DE0626D"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyPublished3011() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " - + "3011-04-27 05:34:37"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyPublishedRecentlyAtNoon() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " - + "recently 12:00:00"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyPublishedRecentlyNoTime() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyPublishedLine("dir-key-published " - + "recently"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyExpiresSoonAtNoon() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires " - + "soon 12:00:00"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyExpiresLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyExpiresLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyExpiresLineDuplicate() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyExpiresLine("dir-key-expires 2012-04-27 " - + "05:34:37\ndir-key-expires 2012-04-27 05:34:37"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirIdentityKeyLinesMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirIdentityKeyLines(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirSigningKeyLinesMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirSigningKeyLines(null); - } - - @Test() - public void testDirKeyCrosscertLinesMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyCrosscertLines(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirKeyCertificationLinesMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirKeyCertificationLines(null); - } - - @Test() - public void testDirectoryFooterLineMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirectoryFooterLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testDirectorySignaturesLinesMissing() - throws DescriptorParseException { - VoteBuilder.createWithDirectorySignatureLines(null); - } - - @Test() - public void testDirectorySignaturesLinesTwoAlgorithms() - throws DescriptorParseException { - String identitySha256 = "32519E5CB7254AB5A94CC9925EC7676E53D5D52EEAB7" - + "914BD3ED751E537CAFCC"; - String signingKeyDigestSha256 = "5A59D99C17831B9254422B6C5AA10CC59381" - + "6CAA5241E22ECAE8BBB4E8E9D1FC"; - String signatureSha256 = "-----BEGIN SIGNATURE-----\n" - + "x57Alc424/zHS73SHokghGtNBVrBjtUz+gSL5w9AHGKUQcMyfw4Z9aDlKpTbFc" - + "5W\nnyIvFmM9C2OAH0S1+a647HHIxhE0zKf4+yKSwzqSyL6sbKQygVlJsRHNRr" - + "cFg8lp\nqBxEwvxQoA4xEDqnerR92pbK9l42nNLiKOcoReUqbbQ=\n" - + "-----END SIGNATURE-----"; - String identitySha1 = "80550987E1D626E3EBA5E5E75A458DE0626D088C"; - String signingKeyDigestSha1 = - "EEB9299D295C1C815E289FBF2F2BBEA5F52FDD19"; - String signatureSha1 = "-----BEGIN SIGNATURE-----\n" - + "iHEU3Iidya5RIrjyYgv8tlU0R+rF56/3/MmaaZi0a67e7ZkISfQ4dghScHxnF3" - + "Yh\nrXVaaoP07r6Ta+s0g1Zijm3lms50Nk/4tV2p8Y63c3F4Q3DAnK40OikfOI" - + "wEj+Ny\n+zBRQssP3hPhTPOj/A7o3mZZwtL6x1sxpeu/nME1l5E=\n" - + "-----END SIGNATURE-----"; - String signaturesLines = String.format( - "directory-signature sha256 %s %s\n%s\n" - + "directory-signature %s %s\n%s", identitySha256, - signingKeyDigestSha256, signatureSha256, identitySha1, - signingKeyDigestSha1, signatureSha1); - RelayNetworkStatusVote vote = - VoteBuilder.createWithDirectorySignatureLines(signaturesLines); - assertEquals(2, vote.getSignatures().size()); - DirectorySignature firstSignature = vote.getSignatures().get(0); - assertEquals("sha256", firstSignature.getAlgorithm()); - assertEquals(identitySha256, firstSignature.getIdentity()); - assertEquals(signingKeyDigestSha256, - firstSignature.getSigningKeyDigest()); - assertEquals(signatureSha256 + "\n", firstSignature.getSignature()); - DirectorySignature secondSignature = vote.getSignatures().get(1); - assertEquals("sha1", secondSignature.getAlgorithm()); - assertEquals(identitySha1, secondSignature.getIdentity()); - assertEquals(signingKeyDigestSha1, - secondSignature.getSigningKeyDigest()); - assertEquals(signatureSha1 + "\n", secondSignature.getSignature()); - assertEquals(signingKeyDigestSha1, vote.getSigningKeyDigest()); - } - - @Test() - public void testDirectorySignaturesLinesTwoAlgorithmsSameDigests() - throws DescriptorParseException { - String signaturesLines = "directory-signature 00 00\n" - + "-----BEGIN SIGNATURE-----\n00\n-----END SIGNATURE-----\n" - + "directory-signature sha256 00 00\n" - + "-----BEGIN SIGNATURE-----\n00\n-----END SIGNATURE-----"; - RelayNetworkStatusVote vote = - VoteBuilder.createWithDirectorySignatureLines(signaturesLines); - assertEquals(2, vote.getSignatures().size()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedHeaderLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - VoteBuilder.createWithUnrecognizedHeaderLine(unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedHeaderLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusVote vote = VoteBuilder. - createWithUnrecognizedHeaderLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedDirSourceLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - VoteBuilder.createWithUnrecognizedDirSourceLine(unrecognizedLine, - true); - } - - @Test() - public void testUnrecognizedDirSourceLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusVote vote = VoteBuilder. - createWithUnrecognizedDirSourceLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedFooterLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - VoteBuilder.createWithUnrecognizedFooterLine(unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedFooterLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - RelayNetworkStatusVote vote = VoteBuilder. - createWithUnrecognizedFooterLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, vote.getUnrecognizedLines()); - } - - @Test() - public void testIdEd25519MasterKey() - throws DescriptorParseException { - String masterKey25519 = "8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8"; - List<String> statusEntries = new ArrayList<>(); - statusEntries.add("r PDrelay1 AAFJ5u9xAqrKlpDW6N0pMhJLlKs " - + "bgJiI/la3e9u0K7cQ5pMSXhigHI 2015-12-01 04:54:30 95.215.44.189 " - + "8080 0\n" - + "id ed25519 " + masterKey25519); - RelayNetworkStatusVote vote = - VoteBuilder.createWithStatusEntries(statusEntries); - String fingerprint = vote.getStatusEntries().firstKey(); - assertEquals(masterKey25519, - vote.getStatusEntry(fingerprint).getMasterKeyEd25519()); - } - - @Test() - public void testIdEd25519None() - throws DescriptorParseException { - List<String> statusEntries = new ArrayList<>(); - statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A " - + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 " - + "443 9030\n" - + "id ed25519 none"); - RelayNetworkStatusVote vote = - VoteBuilder.createWithStatusEntries(statusEntries); - String fingerprint = vote.getStatusEntries().firstKey(); - assertEquals("none", - vote.getStatusEntry(fingerprint).getMasterKeyEd25519()); - } - - @Test(expected = DescriptorParseException.class) - public void testIdRsa1024None() - throws DescriptorParseException { - List<String> statusEntries = new ArrayList<>(); - statusEntries.add("r MathematicalApology AAPJIrV9nhfgTYQs0vsTghFaP2A " - + "uA7p0m68O8ILXsf3aLZUj0EvDNE 2015-12-01 18:01:49 172.99.69.177 " - + "443 9030\n" - + "id rsa1024 none"); - VoteBuilder.createWithStatusEntries(statusEntries); - } -} - diff --git a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java deleted file mode 100644 index cd3f1a5..0000000 --- a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java +++ /dev/null @@ -1,1605 +0,0 @@ -/* Copyright 2012--2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import org.torproject.descriptor.DescriptorParseException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.SortedMap; - -import org.junit.Test; -import org.torproject.descriptor.BandwidthHistory; -import org.torproject.descriptor.ServerDescriptor; - -/* Test parsing of relay server descriptors. */ -public class ServerDescriptorImplTest { - - /* Helper class to build a descriptor based on default data and - * modifications requested by test methods. */ - private static class DescriptorBuilder { - private String routerLine = "router saberrider2008 94.134.192.243 " - + "9001 0 0"; - private static ServerDescriptor createWithRouterLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.routerLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String bandwidthLine = "bandwidth 51200 51200 53470"; - private static ServerDescriptor createWithBandwidthLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.bandwidthLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String platformLine = "platform Tor 0.2.2.35 " - + "(git-b04388f9e7546a9f) on Linux i686"; - private static ServerDescriptor createWithPlatformLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.platformLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String publishedLine = "published 2012-01-01 04:03:19"; - private static ServerDescriptor createWithPublishedLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.publishedLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String fingerprintLine = "opt fingerprint D873 3048 FC8E " - + "C910 2466 AD8F 3098 622B F1BF 71FD"; - private static ServerDescriptor createWithFingerprintLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.fingerprintLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String hibernatingLine = null; - private static ServerDescriptor createWithHibernatingLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.hibernatingLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String uptimeLine = "uptime 48"; - private static ServerDescriptor createWithUptimeLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.uptimeLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String onionKeyLines = "onion-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp" - + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE" - + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"; - private static ServerDescriptor createWithOnionKeyLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.onionKeyLines = lines; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String signingKeyLines = "signing-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb" - + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN" - + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"; - private static ServerDescriptor createWithSigningKeyLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.signingKeyLines = lines; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String onionKeyCrosscertLines = null; - private static ServerDescriptor createWithOnionKeyCrosscertLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.onionKeyCrosscertLines = lines; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String ntorOnionKeyCrosscertLines = null; - private static ServerDescriptor createWithNtorOnionKeyCrosscertLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.ntorOnionKeyCrosscertLines = lines; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String exitPolicyLines = "reject *:*"; - private static ServerDescriptor createWithExitPolicyLines( - String lines) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.exitPolicyLines = lines; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String contactLine = "contact Random Person <nobody AT " - + "example dot com>"; - private static ServerDescriptor createWithContactLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.contactLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String familyLine = null; - private static ServerDescriptor createWithFamilyLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.familyLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String readHistoryLine = null; - private static ServerDescriptor createWithReadHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.readHistoryLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String writeHistoryLine = null; - private static ServerDescriptor createWithWriteHistoryLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.writeHistoryLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String eventdnsLine = null; - private static ServerDescriptor createWithEventdnsLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.eventdnsLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String cachesExtraInfoLine = null; - private static ServerDescriptor createWithCachesExtraInfoLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.cachesExtraInfoLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String extraInfoDigestLine = "opt extra-info-digest " - + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74"; - private static ServerDescriptor createWithExtraInfoDigestLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.extraInfoDigestLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String hiddenServiceDirLine = "opt hidden-service-dir"; - private static ServerDescriptor createWithHiddenServiceDirLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.hiddenServiceDirLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String protocolsLine = "opt protocols Link 1 2 Circuit 1"; - private static ServerDescriptor createWithProtocolsLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.protocolsLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String allowSingleHopExitsLine = null; - private static ServerDescriptor - createWithAllowSingleHopExitsLine(String line) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.allowSingleHopExitsLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String ipv6PolicyLine = null; - private static ServerDescriptor createWithIpv6PolicyLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.ipv6PolicyLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String ntorOnionKeyLine = null; - private static ServerDescriptor createWithNtorOnionKeyLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.ntorOnionKeyLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String tunnelledDirServerLine = null; - private static ServerDescriptor createWithTunnelledDirServerLine( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.tunnelledDirServerLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String routerSignatureLines = "router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" - + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" - + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" - + "-----END SIGNATURE-----"; - private static ServerDescriptor createWithRouterSignatureLines( - String line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.routerSignatureLines = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private String unrecognizedLine = null; - private static ServerDescriptor createWithUnrecognizedLine( - String line, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.unrecognizedLine = line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), - failUnrecognizedDescriptorLines); - } - private byte[] nonAsciiLineBytes = null; - private static ServerDescriptor createWithNonAsciiLineBytes( - byte[] lineBytes, boolean failUnrecognizedDescriptorLines) - throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.nonAsciiLineBytes = lineBytes; - return new RelayServerDescriptorImpl(db.buildDescriptor(), - failUnrecognizedDescriptorLines); - } - private String identityEd25519Lines = null, - masterKeyEd25519Line = null, routerSigEd25519Line = null; - private static ServerDescriptor createWithEd25519Lines( - String identityEd25519Lines, String masterKeyEd25519Line, - String routerSigEd25519Line) throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - db.identityEd25519Lines = identityEd25519Lines; - db.masterKeyEd25519Line = masterKeyEd25519Line; - db.routerSigEd25519Line = routerSigEd25519Line; - return new RelayServerDescriptorImpl(db.buildDescriptor(), true); - } - private byte[] buildDescriptor() { - StringBuilder sb = new StringBuilder(); - if (this.routerLine != null) { - sb.append(this.routerLine).append("\n"); - } - if (this.identityEd25519Lines != null) { - sb.append(this.identityEd25519Lines).append("\n"); - } - if (this.masterKeyEd25519Line != null) { - sb.append(this.masterKeyEd25519Line).append("\n"); - } - if (this.bandwidthLine != null) { - sb.append(this.bandwidthLine).append("\n"); - } - if (this.platformLine != null) { - sb.append(this.platformLine).append("\n"); - } - if (this.publishedLine != null) { - sb.append(this.publishedLine).append("\n"); - } - if (this.fingerprintLine != null) { - sb.append(this.fingerprintLine).append("\n"); - } - if (this.hibernatingLine != null) { - sb.append(this.hibernatingLine).append("\n"); - } - if (this.uptimeLine != null) { - sb.append(this.uptimeLine).append("\n"); - } - if (this.onionKeyLines != null) { - sb.append(this.onionKeyLines).append("\n"); - } - if (this.signingKeyLines != null) { - sb.append(this.signingKeyLines).append("\n"); - } - if (this.onionKeyCrosscertLines != null) { - sb.append(this.onionKeyCrosscertLines).append("\n"); - } - if (this.ntorOnionKeyCrosscertLines != null) { - sb.append(this.ntorOnionKeyCrosscertLines).append("\n"); - } - if (this.exitPolicyLines != null) { - sb.append(this.exitPolicyLines).append("\n"); - } - if (this.contactLine != null) { - sb.append(this.contactLine).append("\n"); - } - if (this.familyLine != null) { - sb.append(this.familyLine).append("\n"); - } - if (this.readHistoryLine != null) { - sb.append(this.readHistoryLine).append("\n"); - } - if (this.writeHistoryLine != null) { - sb.append(this.writeHistoryLine).append("\n"); - } - if (this.eventdnsLine != null) { - sb.append(this.eventdnsLine).append("\n"); - } - if (this.cachesExtraInfoLine != null) { - sb.append(this.cachesExtraInfoLine).append("\n"); - } - if (this.extraInfoDigestLine != null) { - sb.append(this.extraInfoDigestLine).append("\n"); - } - if (this.hiddenServiceDirLine != null) { - sb.append(this.hiddenServiceDirLine).append("\n"); - } - if (this.protocolsLine != null) { - sb.append(this.protocolsLine).append("\n"); - } - if (this.allowSingleHopExitsLine != null) { - sb.append(this.allowSingleHopExitsLine).append("\n"); - } - if (this.ipv6PolicyLine != null) { - sb.append(this.ipv6PolicyLine).append("\n"); - } - if (this.ntorOnionKeyLine != null) { - sb.append(this.ntorOnionKeyLine).append("\n"); - } - if (this.tunnelledDirServerLine != null) { - sb.append(this.tunnelledDirServerLine).append("\n"); - } - if (this.unrecognizedLine != null) { - sb.append(this.unrecognizedLine).append("\n"); - } - if (this.nonAsciiLineBytes != null) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(sb.toString().getBytes()); - baos.write(this.nonAsciiLineBytes); - baos.write("\n".getBytes()); - if (this.routerSignatureLines != null) { - baos.write(this.routerSignatureLines.getBytes()); - } - return baos.toByteArray(); - } catch (IOException e) { - return null; - } - } - if (this.routerSigEd25519Line != null) { - sb.append(this.routerSigEd25519Line).append("\n"); - } - if (this.routerSignatureLines != null) { - sb.append(this.routerSignatureLines).append("\n"); - } - return sb.toString().getBytes(); - } - } - - @Test() - public void testSampleDescriptor() throws DescriptorParseException { - DescriptorBuilder db = new DescriptorBuilder(); - ServerDescriptor descriptor = - new RelayServerDescriptorImpl(db.buildDescriptor(), true); - assertEquals("saberrider2008", descriptor.getNickname()); - assertEquals("94.134.192.243", descriptor.getAddress()); - assertEquals(9001, (int) descriptor.getOrPort()); - assertEquals(0, (int) descriptor.getSocksPort()); - assertEquals(0, (int) descriptor.getDirPort()); - assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686", - descriptor.getPlatform()); - assertEquals(Arrays.asList(new Integer[] {1, 2}), - descriptor.getLinkProtocolVersions()); - assertEquals(Arrays.asList(new Integer[] {1}), - descriptor.getCircuitProtocolVersions()); - assertEquals(1325390599000L, descriptor.getPublishedMillis()); - assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD", - descriptor.getFingerprint()); - assertEquals(48, descriptor.getUptime().longValue()); - assertEquals(51200, (int) descriptor.getBandwidthRate()); - assertEquals(51200, (int) descriptor.getBandwidthBurst()); - assertEquals(53470, (int) descriptor.getBandwidthObserved()); - assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74", - descriptor.getExtraInfoDigest()); - assertEquals(Arrays.asList(new Integer[] {2}), - descriptor.getHiddenServiceDirVersions()); - assertEquals("Random Person <nobody AT example dot com>", - descriptor.getContact()); - assertEquals(Arrays.asList(new String[] {"reject *:*"}), - descriptor.getExitPolicyLines()); - assertFalse(descriptor.isHibernating()); - assertNull(descriptor.getFamilyEntries()); - assertNull(descriptor.getReadHistory()); - assertNull(descriptor.getWriteHistory()); - assertFalse(descriptor.getUsesEnhancedDnsLogic()); - assertFalse(descriptor.getCachesExtraInfo()); - assertFalse(descriptor.getAllowSingleHopExits()); - assertTrue(descriptor.getUnrecognizedLines().isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testRouterLineMissing() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine(null); - } - - @Test() - public void testRouterOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithRouterLine("opt router saberrider2008 " - + "94.134.192.243 9001 0 0"); - assertEquals("saberrider2008", descriptor.getNickname()); - assertEquals("94.134.192.243", descriptor.getAddress()); - assertEquals(9001, (int) descriptor.getOrPort()); - assertEquals(0, (int) descriptor.getSocksPort()); - assertEquals(0, (int) descriptor.getDirPort()); - } - - @Test(expected = DescriptorParseException.class) - public void testRouterLinePrecedingHibernatingLine() - throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("hibernating 1\nrouter " - + "saberrider2008 94.134.192.243 9001 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameMissing() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router 94.134.192.243 9001 " - + "0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameInvalidChar() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router $aberrider2008 " - + "94.134.192.243 9001 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testNicknameTooLong() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router " - + "saberrider2008ReallyLongNickname 94.134.192.243 9001 0 0"); - } - - @Test() - public void testNicknameTwoSpaces() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithRouterLine("router saberrider2008 " - + "94.134.192.243 9001 0 0"); - assertEquals("saberrider2008", descriptor.getNickname()); - assertEquals("94.134.192.243", descriptor.getAddress()); - } - - @Test(expected = DescriptorParseException.class) - public void testAddress24() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192/24 9001 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddress294() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "294.134.192.243 9001 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testAddressMissing() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 9001 " - + "0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPort99001() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192.243 99001 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPortMissing() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192.243 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPortOne() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192.243 one 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testOrPortNewline() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192.243 0\n 0 0"); - } - - @Test(expected = DescriptorParseException.class) - public void testDirPortMissing() throws DescriptorParseException { - DescriptorBuilder.createWithRouterLine("router saberrider2008 " - + "94.134.192.243 9001 0 "); - } - - @Test() - public void testPlatformMissing() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPlatformLine(null); - assertNull(descriptor.getPlatform()); - } - - @Test() - public void testPlatformOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPlatformLine("opt platform Tor 0.2.2.35 " - + "(git-b04388f9e7546a9f) on Linux i686"); - assertEquals("Tor 0.2.2.35 (git-b04388f9e7546a9f) on Linux i686", - descriptor.getPlatform()); - } - - @Test() - public void testPlatformNoSpace() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPlatformLine("platform"); - assertEquals("", descriptor.getPlatform()); - } - - @Test() - public void testPlatformSpace() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPlatformLine("platform "); - assertEquals("", descriptor.getPlatform()); - } - - @Test() - public void testProtocolsNoOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithProtocolsLine("protocols Link 1 2 Circuit 1"); - assertEquals(Arrays.asList(new Integer[] {1, 2}), - descriptor.getLinkProtocolVersions()); - assertEquals(Arrays.asList(new Integer[] {1}), - descriptor.getCircuitProtocolVersions()); - } - - @Test(expected = DescriptorParseException.class) - public void testProtocolsAB() throws DescriptorParseException { - DescriptorBuilder.createWithProtocolsLine("opt protocols Link A B " - + "Circuit 1"); - } - - @Test(expected = DescriptorParseException.class) - public void testProtocolsNoCircuitVersions() - throws DescriptorParseException { - DescriptorBuilder.createWithProtocolsLine("opt protocols Link 1 2"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublishedMissing() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine(null); - } - - @Test() - public void testPublishedOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPublishedLine("opt published 2012-01-01 04:03:19"); - assertEquals(1325390599000L, descriptor.getPublishedMillis()); - } - - @Test(expected = DescriptorParseException.class) - public void testPublished2039() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine("published 2039-01-01 " - + "04:03:19"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublished1912() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine("published 1912-01-01 " - + "04:03:19"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublishedFeb31() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine("published 2012-02-31 " - + "04:03:19"); - } - - @Test(expected = DescriptorParseException.class) - public void testPublishedNoTime() throws DescriptorParseException { - DescriptorBuilder.createWithPublishedLine("published 2012-01-01"); - } - - @Test() - public void testPublishedMillis() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithPublishedLine("opt published 2012-01-01 04:03:19.123"); - assertEquals(1325390599000L, descriptor.getPublishedMillis()); - } - - @Test() - public void testFingerprintNoOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFingerprintLine("fingerprint D873 3048 FC8E C910 2466 " - + "AD8F 3098 622B F1BF 71FD"); - assertEquals("D8733048FC8EC9102466AD8F3098622BF1BF71FD", - descriptor.getFingerprint()); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintG() throws DescriptorParseException { - DescriptorBuilder.createWithFingerprintLine("opt fingerprint G873 " - + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooShort() throws DescriptorParseException { - DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 " - + "3048 FC8E C910 2466 AD8F 3098 622B F1BF"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintTooLong() throws DescriptorParseException { - DescriptorBuilder.createWithFingerprintLine("opt fingerprint D873 " - + "3048 FC8E C910 2466 AD8F 3098 622B F1BF 71FD D873"); - } - - @Test(expected = DescriptorParseException.class) - public void testFingerprintNoSpaces() throws DescriptorParseException { - DescriptorBuilder.createWithFingerprintLine("opt fingerprint " - + "D8733048FC8EC9102466AD8F3098622BF1BF71FD"); - } - - @Test() - public void testUptimeMissing() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithUptimeLine(null); - assertNull(descriptor.getUptime()); - } - - @Test() - public void testUptimeOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithUptimeLine("opt uptime 48"); - assertEquals(48, descriptor.getUptime().longValue()); - } - - @Test(expected = DescriptorParseException.class) - public void testUptimeFourtyEight() throws DescriptorParseException { - DescriptorBuilder.createWithUptimeLine("uptime fourty-eight"); - } - - @Test() - public void testUptimeMinusOne() throws DescriptorParseException { - DescriptorBuilder.createWithUptimeLine("uptime -1"); - } - - @Test(expected = DescriptorParseException.class) - public void testUptimeSpace() throws DescriptorParseException { - DescriptorBuilder.createWithUptimeLine("uptime "); - } - - @Test(expected = DescriptorParseException.class) - public void testUptimeNoSpace() throws DescriptorParseException { - DescriptorBuilder.createWithUptimeLine("uptime"); - } - - @Test(expected = DescriptorParseException.class) - public void testUptimeFourEight() throws DescriptorParseException { - DescriptorBuilder.createWithUptimeLine("uptime 4 8"); - } - - @Test() - public void testBandwidthOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithBandwidthLine("opt bandwidth 51200 51200 53470"); - assertEquals(51200, (int) descriptor.getBandwidthRate()); - assertEquals(51200, (int) descriptor.getBandwidthBurst()); - assertEquals(53470, (int) descriptor.getBandwidthObserved()); - } - - @Test(expected = DescriptorParseException.class) - public void testBandwidthMissing() throws DescriptorParseException { - DescriptorBuilder.createWithBandwidthLine(null); - } - - @Test(expected = DescriptorParseException.class) - public void testBandwidthOneValue() throws DescriptorParseException { - DescriptorBuilder.createWithBandwidthLine("bandwidth 51200"); - } - - @Test() - public void testBandwidthTwoValues() throws DescriptorParseException { - /* This is allowed, because Tor versions 0.0.8 and older only wrote - * bandwidth lines with rate and burst values, but no observed - * value. */ - ServerDescriptor descriptor = DescriptorBuilder. - createWithBandwidthLine("bandwidth 51200 51200"); - assertEquals(51200, (int) descriptor.getBandwidthRate()); - assertEquals(51200, (int) descriptor.getBandwidthBurst()); - assertEquals(-1, (int) descriptor.getBandwidthObserved()); - } - - @Test(expected = DescriptorParseException.class) - public void testBandwidthFourValues() throws DescriptorParseException { - DescriptorBuilder.createWithBandwidthLine("bandwidth 51200 51200 " - + "53470 53470"); - } - - @Test(expected = DescriptorParseException.class) - public void testBandwidthMinusOneTwoThree() - throws DescriptorParseException { - DescriptorBuilder.createWithBandwidthLine("bandwidth -1 -2 -3"); - } - - @Test() - public void testExtraInfoDigestNoOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExtraInfoDigestLine("extra-info-digest " - + "1469D1550738A25B1E7B47CDDBCD7B2899F51B74"); - assertEquals("1469D1550738A25B1E7B47CDDBCD7B2899F51B74", - descriptor.getExtraInfoDigest()); - } - - @Test(expected = DescriptorParseException.class) - public void testExtraInfoDigestNoSpace() - throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoDigestLine("opt " - + "extra-info-digest"); - } - - @Test(expected = DescriptorParseException.class) - public void testExtraInfoDigestTooShort() - throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoDigestLine("opt " - + "extra-info-digest 1469D1550738A25B1E7B47CDDBCD7B2899F5"); - } - - @Test(expected = DescriptorParseException.class) - public void testExtraInfoDigestTooLong() - throws DescriptorParseException { - DescriptorBuilder.createWithExtraInfoDigestLine("opt " - + "extra-info-digest " - + "1469D1550738A25B1E7B47CDDBCD7B2899F51B741469"); - } - - @Test() - public void testExtraInfoDigestMissing() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExtraInfoDigestLine(null); - assertNull(descriptor.getExtraInfoDigest()); - } - - @Test() - public void testExtraInfoDigestAdditionalDigest() - throws DescriptorParseException { - String extraInfoDigest = "0879DB7B765218D7B3AE7557669D20307BB21CAA"; - String additionalExtraInfoDigest = - "V609l+N6ActBveebfNbH5lQ6wHDNstDkFgyqEhBHwtA"; - String extraInfoDigestLine = String.format("extra-info-digest %s %s", - extraInfoDigest, additionalExtraInfoDigest); - ServerDescriptor descriptor = DescriptorBuilder. - createWithExtraInfoDigestLine(extraInfoDigestLine); - assertEquals(extraInfoDigest, descriptor.getExtraInfoDigest()); - } - - @Test() - public void testOnionKeyOpt() throws DescriptorParseException { - DescriptorBuilder.createWithOnionKeyLines("opt onion-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ1U4V9SeiKooSo5Bp" - + "PL\no3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3olIynCI4QryfCE" - + "uC3cTF\n9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKFfacOkpAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"); - } - - @Test() - public void testSigningKeyOpt() throws DescriptorParseException { - DescriptorBuilder.createWithSigningKeyLines("opt signing-key\n" - + "-----BEGIN RSA PUBLIC KEY-----\n" - + "MIGJAoGBALMm3r3QDh482Ewe6Ub9wvRIfmEkoNX6q5cEAtQRNHSDcNx41gjELb" - + "cl\nEniVMParBYACKfOxkS+mTTnIRDKVNEJTsDOwryNrc4X9JnPc/nn6ymYPiN" - + "DhUROG\n8URDIhQoixcUeyyrVB8sxliSstKimulGnB7xpjYOlO8JKaHLNL4TAg" - + "MBAAE=\n" - + "-----END RSA PUBLIC KEY-----"); - } - - @Test() - public void testHiddenServiceDirMissing() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHiddenServiceDirLine(null); - assertNull(descriptor.getHiddenServiceDirVersions()); - } - - @Test() - public void testHiddenServiceDirNoOpt() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHiddenServiceDirLine("hidden-service-dir"); - assertEquals(Arrays.asList(new Integer[] {2}), - descriptor.getHiddenServiceDirVersions()); - } - - @Test() - public void testHiddenServiceDirVersions2And3() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHiddenServiceDirLine("hidden-service-dir 2 3"); - assertEquals(Arrays.asList(new Integer[] {2, 3}), - descriptor.getHiddenServiceDirVersions()); - } - - @Test() - public void testContactMissing() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithContactLine(null); - assertNull(descriptor.getContact()); - } - - @Test() - public void testContactOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithContactLine("opt contact Random Person"); - assertEquals("Random Person", descriptor.getContact()); - } - - @Test(expected = DescriptorParseException.class) - public void testContactDuplicate() throws DescriptorParseException { - DescriptorBuilder.createWithContactLine("contact Random " - + "Person\ncontact Random Person"); - } - - @Test() - public void testContactNoSpace() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithContactLine("contact"); - assertEquals("", descriptor.getContact()); - } - - @Test() - public void testContactCarriageReturn() - throws DescriptorParseException { - String contactString = "Random " - + "Person -----BEGIN PGP PUBLIC KEY BLOCK-----\r" - + "Version: GnuPG v1 dot 4 dot 7 (Darwin)\r\r" - + "mQGiBEbb0rcRBADqBiUXsmtpJifh74irNnkHbhKMj8O4TqenaZYhdjLWouZsZd" - + "07\rmTQoP40G4zqOrVEOOcXpdSiRnHWJYfgTnkibNZrOZEZLn3H1ywpovEgESm" - + "oGEdAX\roid3XuIYRpRnqoafbFg9sg+OofX/mGrO+5ACfagQ9rlfx2oxCWijYw" - + "pYFRk3NhCY=\r=Xaw3\r-----END PGP PUBLIC KEY BLOCK-----"; - ServerDescriptor descriptor = DescriptorBuilder. - createWithContactLine("contact " + contactString); - assertEquals(contactString, descriptor.getContact()); - } - - @Test() - public void testExitPolicyRejectAllAcceptAll() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExitPolicyLines("reject *:*\naccept *:*"); - assertEquals(Arrays.asList(new String[] {"reject *:*", "accept *:*"}), - descriptor.getExitPolicyLines()); - } - - @Test() - public void testExitPolicyOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExitPolicyLines("opt reject *:*"); - assertEquals(Arrays.asList(new String[] {"reject *:*"}), - descriptor.getExitPolicyLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testExitPolicyNoPort() throws DescriptorParseException { - DescriptorBuilder.createWithExitPolicyLines("reject *"); - } - - @Test() - public void testExitPolicyAccept80RejectAll() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExitPolicyLines("accept *:80\nreject *:*"); - assertEquals(Arrays.asList(new String[] {"accept *:80", - "reject *:*"}), descriptor.getExitPolicyLines()); - } - - @Test(expected = DescriptorParseException.class) - public void testExitPolicyReject321() throws DescriptorParseException { - DescriptorBuilder.createWithExitPolicyLines("reject " - + "123.123.123.321:80"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitPolicyRejectPort66666() - throws DescriptorParseException { - DescriptorBuilder.createWithExitPolicyLines("reject *:66666"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitPolicyProjectAll() throws DescriptorParseException { - DescriptorBuilder.createWithExitPolicyLines("project *:*"); - } - - @Test(expected = DescriptorParseException.class) - public void testExitPolicyMissing() throws DescriptorParseException { - DescriptorBuilder.createWithExitPolicyLines(null); - } - - @Test() - public void testExitPolicyMaskTypes() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithExitPolicyLines("reject 192.168.0.0/16:*\n" - + "reject 94.134.192.243/255.255.255.0:*"); - assertEquals(Arrays.asList(new String[] { "reject 192.168.0.0/16:*", - "reject 94.134.192.243/255.255.255.0:*"}), - descriptor.getExitPolicyLines()); - } - - @Test() - public void testRouterSignatureOpt() - throws DescriptorParseException { - DescriptorBuilder.createWithRouterSignatureLines("opt " - + "router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "crypto lines are ignored anyway\n" - + "-----END SIGNATURE-----"); - } - - @Test(expected = DescriptorParseException.class) - public void testRouterSignatureNotLastLine() - throws DescriptorParseException { - DescriptorBuilder.createWithRouterSignatureLines("router-signature\n" - + "-----BEGIN SIGNATURE-----\n" - + "o4j+kH8UQfjBwepUnr99v0ebN8RpzHJ/lqYsTojXHy9kMr1RNI9IDeSzA7PSqT" - + "uV\n4PL8QsGtlfwthtIoZpB2srZeyN/mcpA9fa1JXUrt/UN9K/+32Cyaad7h0n" - + "HE6Xfb\njqpXDpnBpvk4zjmzjjKYnIsUWTnADmu0fo3xTRqXi7g=\n" - + "-----END SIGNATURE-----\ncontact me"); - } - - @Test() - public void testHibernatingOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHibernatingLine("opt hibernating 1"); - assertTrue(descriptor.isHibernating()); - } - - @Test() - public void testHibernatingFalse() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHibernatingLine("hibernating 0"); - assertFalse(descriptor.isHibernating()); - } - - @Test() - public void testHibernatingTrue() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithHibernatingLine("hibernating 1"); - assertTrue(descriptor.isHibernating()); - } - - @Test(expected = DescriptorParseException.class) - public void testHibernatingYep() throws DescriptorParseException { - DescriptorBuilder.createWithHibernatingLine("hibernating yep"); - } - - @Test(expected = DescriptorParseException.class) - public void testHibernatingNoSpace() throws DescriptorParseException { - DescriptorBuilder.createWithHibernatingLine("hibernating"); - } - - @Test() - public void testFamilyOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFamilyLine("opt family saberrider2008"); - assertEquals(Arrays.asList(new String[] {"saberrider2008"}), - descriptor.getFamilyEntries()); - } - - @Test() - public void testFamilyFingerprint() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFamilyLine("family " - + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD"); - assertEquals(Arrays.asList(new String[] { - "$D8733048FC8EC9102466AD8F3098622BF1BF71FD"}), - descriptor.getFamilyEntries()); - } - - @Test() - public void testFamilyNickname() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFamilyLine("family saberrider2008"); - assertEquals(Arrays.asList(new String[] {"saberrider2008"}), - descriptor.getFamilyEntries()); - } - - @Test(expected = DescriptorParseException.class) - public void testFamilyDuplicate() throws DescriptorParseException { - DescriptorBuilder.createWithFamilyLine("family " - + "saberrider2008\nfamily saberrider2008"); - } - - @Test(expected = DescriptorParseException.class) - public void testFamilyNicknamePrefix() throws DescriptorParseException { - DescriptorBuilder.createWithFamilyLine("family $saberrider2008"); - } - - @Test(expected = DescriptorParseException.class) - public void testFamilyFingerprintNoPrefix() - throws DescriptorParseException { - DescriptorBuilder.createWithFamilyLine("family " - + "D8733048FC8EC9102466AD8F3098622BF1BF71FD"); - } - - @Test() - public void testFamilyFingerprintNicknameNamed() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFamilyLine("family " - + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD=saberrider2008"); - assertEquals(Arrays.asList(new String[] - { "$D8733048FC8EC9102466AD8F3098622BF1BF71FD=saberrider2008" }), - descriptor.getFamilyEntries()); - } - - @Test() - public void testFamilyFingerprintNicknameUnnamed() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithFamilyLine("family " - + "$D8733048FC8EC9102466AD8F3098622BF1BF71FD~saberrider2008"); - assertEquals(Arrays.asList(new String[] - { "$D8733048FC8EC9102466AD8F3098622BF1BF71FD~saberrider2008" }), - descriptor.getFamilyEntries()); - } - - @Test() - public void testWriteHistory() throws DescriptorParseException { - String writeHistoryLine = "write-history 2012-01-01 03:51:44 (900 s) " - + "4345856,261120,7591936,1748992"; - ServerDescriptor descriptor = DescriptorBuilder. - createWithWriteHistoryLine(writeHistoryLine); - assertNotNull(descriptor.getWriteHistory()); - BandwidthHistory parsedWriteHistory = descriptor.getWriteHistory(); - assertEquals(writeHistoryLine, parsedWriteHistory.getLine()); - assertEquals(1325389904000L, (long) parsedWriteHistory. - getHistoryEndMillis()); - assertEquals(900L, (long) parsedWriteHistory.getIntervalLength()); - SortedMap<Long, Long> bandwidthValues = parsedWriteHistory. - getBandwidthValues(); - assertEquals(4345856L, (long) bandwidthValues.remove(1325387204000L)); - assertEquals(261120L, (long) bandwidthValues.remove(1325388104000L)); - assertEquals(7591936L, (long) bandwidthValues.remove(1325389004000L)); - assertEquals(1748992L, (long) bandwidthValues.remove(1325389904000L)); - assertTrue(bandwidthValues.isEmpty()); - } - - @Test() - public void testWriteHistoryOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithWriteHistoryLine("opt write-history 2012-01-01 " - + "03:51:44 (900 s) 4345856,261120,7591936,1748992"); - assertNotNull(descriptor.getWriteHistory()); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistory3012() throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "3012-01-01 03:51:44 (900 s) 4345856,261120,7591936,1748992"); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryNoSeconds() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51 (900 s) 4345856,261120,7591936,1748992"); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryNoParathenses() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51:44 900 s 4345856,261120,7591936,1748992"); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryNoSpaceSeconds() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51:44 (900s) 4345856,261120,7591936,1748992"); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryTrailingComma() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51:44 (900 s) 4345856,261120,7591936,"); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryOneTwoThree() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51:44 (900 s) one,two,three"); - } - - @Test() - public void testWriteHistoryNoValuesSpace() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " - + "(900 s) "); - assertEquals(900, (long) descriptor.getWriteHistory(). - getIntervalLength()); - assertTrue(descriptor.getWriteHistory().getBandwidthValues(). - isEmpty()); - } - - @Test() - public void testWriteHistoryNoValuesNoSpace() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " - + "(900 s)"); - assertEquals(900, (long) descriptor.getWriteHistory(). - getIntervalLength()); - assertTrue(descriptor.getWriteHistory().getBandwidthValues(). - isEmpty()); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryNoS() throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine( - "write-history 2012-01-01 03:51:44 (900 "); - } - - @Test(expected = DescriptorParseException.class) - public void testWriteHistoryTrailingNumber() - throws DescriptorParseException { - DescriptorBuilder.createWithWriteHistoryLine("write-history " - + "2012-01-01 03:51:44 (900 s) 4345856 1"); - } - - @Test() - public void testWriteHistory1800Seconds() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithWriteHistoryLine("write-history 2012-01-01 03:51:44 " - + "(1800 s) 4345856"); - assertEquals(1800L, (long) descriptor.getWriteHistory(). - getIntervalLength()); - } - - @Test() - public void testReadHistory() throws DescriptorParseException { - String readHistoryLine = "read-history 2012-01-01 03:51:44 (900 s) " - + "4268032,139264,7797760,1415168"; - ServerDescriptor descriptor = DescriptorBuilder. - createWithReadHistoryLine(readHistoryLine); - assertNotNull(descriptor.getReadHistory()); - BandwidthHistory parsedReadHistory = descriptor.getReadHistory(); - assertEquals(readHistoryLine, parsedReadHistory.getLine()); - assertEquals(1325389904000L, (long) parsedReadHistory. - getHistoryEndMillis()); - assertEquals(900L, (long) parsedReadHistory.getIntervalLength()); - SortedMap<Long, Long> bandwidthValues = parsedReadHistory. - getBandwidthValues(); - assertEquals(4268032L, (long) bandwidthValues.remove(1325387204000L)); - assertEquals(139264L, (long) bandwidthValues.remove(1325388104000L)); - assertEquals(7797760L, (long) bandwidthValues.remove(1325389004000L)); - assertEquals(1415168L, (long) bandwidthValues.remove(1325389904000L)); - assertTrue(bandwidthValues.isEmpty()); - } - - @Test() - public void testReadHistoryTwoSpaces() throws DescriptorParseException { - /* There are some server descriptors from older Tor versions that - * contain "opt read-history " lines. */ - String readHistoryLine = "opt read-history 2012-01-01 03:51:44 " - + "(900 s) 4268032,139264,7797760,1415168"; - DescriptorBuilder.createWithReadHistoryLine(readHistoryLine); - } - - @Test() - public void testEventdnsOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithEventdnsLine("opt eventdns 1"); - assertTrue(descriptor.getUsesEnhancedDnsLogic()); - } - - @Test() - public void testEventdns1() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithEventdnsLine("eventdns 1"); - assertTrue(descriptor.getUsesEnhancedDnsLogic()); - } - - @Test() - public void testEventdns0() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithEventdnsLine("eventdns 0"); - assertFalse(descriptor.getUsesEnhancedDnsLogic()); - } - - @Test(expected = DescriptorParseException.class) - public void testEventdnsTrue() throws DescriptorParseException { - DescriptorBuilder.createWithEventdnsLine("eventdns true"); - } - - @Test(expected = DescriptorParseException.class) - public void testEventdnsNo() throws DescriptorParseException { - DescriptorBuilder.createWithEventdnsLine("eventdns no"); - } - - @Test() - public void testCachesExtraInfoOpt() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithCachesExtraInfoLine("opt caches-extra-info"); - assertTrue(descriptor.getCachesExtraInfo()); - } - - @Test() - public void testCachesExtraInfoNoSpace() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithCachesExtraInfoLine("caches-extra-info"); - assertTrue(descriptor.getCachesExtraInfo()); - } - - @Test(expected = DescriptorParseException.class) - public void testCachesExtraInfoTrue() throws DescriptorParseException { - DescriptorBuilder.createWithCachesExtraInfoLine("caches-extra-info " - + "true"); - } - - @Test() - public void testAllowSingleHopExitsOpt() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithAllowSingleHopExitsLine("opt allow-single-hop-exits"); - assertTrue(descriptor.getAllowSingleHopExits()); - } - - @Test() - public void testAllowSingleHopExitsNoSpace() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithAllowSingleHopExitsLine("allow-single-hop-exits"); - assertTrue(descriptor.getAllowSingleHopExits()); - } - - @Test(expected = DescriptorParseException.class) - public void testAllowSingleHopExitsTrue() - throws DescriptorParseException { - DescriptorBuilder.createWithAllowSingleHopExitsLine( - "allow-single-hop-exits true"); - } - - @Test(expected = DescriptorParseException.class) - public void testAllowSingleHopExitsNonAsciiKeyword() - throws DescriptorParseException { - DescriptorBuilder.createWithNonAsciiLineBytes(new byte[] { - 0x14, (byte) 0xfe, 0x18, // non-ascii chars - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x2d, // "allow-" - 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x2d, // "single-" - 0x68, 0x6f, 0x70, 0x2d, // "hop-" - 0x65, 0x78, 0x69, 0x74, 0x73 }, // "exits" (no newline) - false); - } - - @Test() - public void testIpv6PolicyLine() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithIpv6PolicyLine("ipv6-policy accept 80,1194,1220,1293"); - assertEquals("accept", descriptor.getIpv6DefaultPolicy()); - assertEquals("80,1194,1220,1293", descriptor.getIpv6PortList()); - } - - @Test(expected = DescriptorParseException.class) - public void testIpv6PolicyLineNoPolicy() - throws DescriptorParseException { - DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testIpv6PolicyLineNoPorts() - throws DescriptorParseException { - DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy accept"); - } - - @Test(expected = DescriptorParseException.class) - public void testIpv6PolicyLineNoPolicyNoPorts() - throws DescriptorParseException { - DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy "); - } - - @Test(expected = DescriptorParseException.class) - public void testIpv6PolicyLineProject() - throws DescriptorParseException { - DescriptorBuilder.createWithIpv6PolicyLine("ipv6-policy project 80"); - } - - @Test(expected = DescriptorParseException.class) - public void testTwoIpv6PolicyLines() throws DescriptorParseException { - DescriptorBuilder.createWithIpv6PolicyLine( - "ipv6-policy accept 80,1194,1220,1293\n" - + "ipv6-policy accept 80,1194,1220,1293"); - } - - @Test() - public void testNtorOnionKeyLine() throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithNtorOnionKeyLine("ntor-onion-key " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY="); - assertEquals("Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY", - descriptor.getNtorOnionKey()); - } - - @Test() - public void testNtorOnionKeyLineNoPadding() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder. - createWithNtorOnionKeyLine("ntor-onion-key " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY"); - assertEquals("Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY", - descriptor.getNtorOnionKey()); - } - - @Test(expected = DescriptorParseException.class) - public void testNtorOnionKeyLineNoKey() - throws DescriptorParseException { - DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key "); - } - - @Test(expected = DescriptorParseException.class) - public void testNtorOnionKeyLineTwoKeys() - throws DescriptorParseException { - DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY"); - } - - @Test(expected = DescriptorParseException.class) - public void testTwoNtorOnionKeyLines() throws DescriptorParseException { - DescriptorBuilder.createWithNtorOnionKeyLine("ntor-onion-key " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\nntor-onion-key " - + "Y/XgaHcPIJVa4D55kir9QLH8rEYAaLXuv3c3sm8jYhY\n"); - } - - @Test() - public void testTunnelledDirServerTrue() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder - .createWithTunnelledDirServerLine("tunnelled-dir-server"); - assertTrue(descriptor.getTunnelledDirServer()); - } - - @Test() - public void testTunnelledDirServerFalse() - throws DescriptorParseException { - ServerDescriptor descriptor = DescriptorBuilder - .createWithTunnelledDirServerLine(null); - assertFalse(descriptor.getTunnelledDirServer()); - } - - @Test(expected = DescriptorParseException.class) - public void testTunnelledDirServerTypo() - throws DescriptorParseException { - DescriptorBuilder.createWithTunnelledDirServerLine( - "tunneled-dir-server"); - } - - @Test(expected = DescriptorParseException.class) - public void testTunnelledDirServerTwice() - throws DescriptorParseException { - DescriptorBuilder.createWithTunnelledDirServerLine( - "tunnelled-dir-server\ntunnelled-dir-server"); - } - - @Test(expected = DescriptorParseException.class) - public void testTunnelledDirServerArgs() - throws DescriptorParseException { - DescriptorBuilder.createWithTunnelledDirServerLine( - "tunnelled-dir-server 1"); - } - - @Test(expected = DescriptorParseException.class) - public void testUnrecognizedLineFail() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - DescriptorBuilder.createWithUnrecognizedLine(unrecognizedLine, true); - } - - @Test() - public void testUnrecognizedLineIgnore() - throws DescriptorParseException { - String unrecognizedLine = "unrecognized-line 1"; - ServerDescriptor descriptor = DescriptorBuilder. - createWithUnrecognizedLine(unrecognizedLine, false); - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add(unrecognizedLine); - assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); - } - - @Test() - public void testSomeOtherKey() throws DescriptorParseException { - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add("some-other-key"); - unrecognizedLines.add("-----BEGIN RSA PUBLIC KEY-----"); - unrecognizedLines.add("MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ" - + "1U4V9SeiKooSo5BpPL"); - unrecognizedLines.add("o3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3ol" - + "IynCI4QryfCEuC3cTF"); - unrecognizedLines.add("9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKF" - + "facOkpAgMBAAE="); - unrecognizedLines.add("-----END RSA PUBLIC KEY-----"); - StringBuilder sb = new StringBuilder(); - for (String line : unrecognizedLines) { - sb.append("\n").append(line); - } - ServerDescriptor descriptor = DescriptorBuilder. - createWithUnrecognizedLine(sb.toString().substring(1), false); - assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); - } - - @Test() - public void testUnrecognizedCryptoBlockNoKeyword() - throws DescriptorParseException { - List<String> unrecognizedLines = new ArrayList<>(); - unrecognizedLines.add("-----BEGIN RSA PUBLIC KEY-----"); - unrecognizedLines.add("MIGJAoGBAKM+iiHhO6eHsvd6Xjws9z9EQB1V/Bpuy5ciGJ" - + "1U4V9SeiKooSo5BpPL"); - unrecognizedLines.add("o3XT+6PIgzl3R6uycjS3Ejk47vLEJdcVTm/VG6E0ppu3ol" - + "IynCI4QryfCEuC3cTF"); - unrecognizedLines.add("9wE4WXY4nX7w0RTN18UVLxrt1A9PP0cobFNiPs9rzJCbKF" - + "facOkpAgMBAAE="); - unrecognizedLines.add("-----END RSA PUBLIC KEY-----"); - StringBuilder sb = new StringBuilder(); - for (String line : unrecognizedLines) { - sb.append("\n").append(line); - } - ServerDescriptor descriptor = DescriptorBuilder. - createWithUnrecognizedLine(sb.toString().substring(1), false); - assertEquals(unrecognizedLines, descriptor.getUnrecognizedLines()); - } - - private static final String IDENTITY_ED25519_LINES = - "identity-ed25519\n" - + "-----BEGIN ED25519 CERT-----\n" - + "AQQABiX1AVGv5BuzJroQXbOh6vv1nbwc5rh2S13PyRFuLhTiifK4AQAgBACBCMwr" - + "\n4qgIlFDIzoC9ieJOtSkwrK+yXJPKlP8ojvgkx8cGKvhokOwA1eYDombzfwHcJ1" - + "EV\nbhEn/6g8i7wzO3LoqefIUrSAeEExOAOmm5mNmUIzL8EtnT6JHCr/sqUTUgA=" - + "\n" - + "-----END ED25519 CERT-----"; - - private static final String MASTER_KEY_ED25519_LINE = - "master-key-ed25519 gQjMK+KoCJRQyM6AvYniTrUpMKyvslyTypT/KI74JMc"; - - private static final String ROUTER_SIG_ED25519_LINE = - "router-sig-ed25519 y7WF9T2GFwkSDPZEhB55HgquIFOl5uXUFMYJPq3CXXUTKeJ" - + "kSrtaZUB5s34fWdHQNtl84mH4dVaFMunHnwgYAw"; - - @Test() - public void testEd25519() throws DescriptorParseException { - ServerDescriptor descriptor = - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - assertEquals(IDENTITY_ED25519_LINES.substring( - IDENTITY_ED25519_LINES.indexOf("\n") + 1), - descriptor.getIdentityEd25519()); - assertEquals(MASTER_KEY_ED25519_LINE.substring( - MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), - descriptor.getMasterKeyEd25519()); - assertEquals(ROUTER_SIG_ED25519_LINE.substring( - ROUTER_SIG_ED25519_LINE.indexOf(" ") + 1), - descriptor.getRouterSignatureEd25519()); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityMasterKeyMismatch() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - "master-key-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519IdentityMissing() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(null, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES + "\n" - + IDENTITY_ED25519_LINES, MASTER_KEY_ED25519_LINE, - ROUTER_SIG_ED25519_LINE); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519IdentityEmptyCrypto() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines("identity-ed25519\n" - + "-----BEGIN ED25519 CERT-----\n-----END ED25519 CERT-----", - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519MasterKeyMissing() - throws DescriptorParseException { - ServerDescriptor descriptor = - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - null, ROUTER_SIG_ED25519_LINE); - assertEquals(MASTER_KEY_ED25519_LINE.substring( - MASTER_KEY_ED25519_LINE.indexOf(" ") + 1), - descriptor.getMasterKeyEd25519()); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519MasterKeyDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE + "\n" + MASTER_KEY_ED25519_LINE, - ROUTER_SIG_ED25519_LINE); - } - - @Test() - public void testEd25519RouterSigMissing() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, null); - } - - @Test(expected = DescriptorParseException.class) - public void testEd25519RouterSigDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithEd25519Lines(IDENTITY_ED25519_LINES, - MASTER_KEY_ED25519_LINE, ROUTER_SIG_ED25519_LINE + "\n" - + ROUTER_SIG_ED25519_LINE); - } - - private static final String ONION_KEY_CROSSCERT_LINES = - "onion-key-crosscert\n" - + "-----BEGIN CROSSCERT-----\n" - + "gVWpiNgG2FekW1uonr4KKoqykjr4bqUBKGZfu6s9rvsV1TThnquZNP6ZhX2IPdQA" - + "\nlfKtzFggGu/4BiJ5oTSDj2sK2DMjY3rjrMQZ3I/wJ25yhc9gxjqYqUYO9MmJwA" - + "Lp\nfYkqp/t4WchJpyva/4hK8vITsI6eT2BfY/DWMy/suIE=\n" - + "-----END CROSSCERT-----"; - - private static final String NTOR_ONION_KEY_CROSSCERT_LINES = - "ntor-onion-key-crosscert 1\n" - + "-----BEGIN ED25519 CERT-----\n" - + "AQoABiUeAdauu1MxYGMmGLTCPaoes0RvW7udeLc1t8LZ4P3CDo5bAN4nrRfbCfOt" - + "\nz2Nwqn8tER1a+Ry6Vs+ilMZA55Rag4+f6Zdb1fmHWknCxbQlLHpqHACMtemPda" - + "Ka\nErPtMuiEqAc=\n" - + "-----END ED25519 CERT-----"; - - @Test() - public void testOnionKeyCrosscert() throws DescriptorParseException { - ServerDescriptor descriptor = - DescriptorBuilder.createWithOnionKeyCrosscertLines( - ONION_KEY_CROSSCERT_LINES); - assertEquals(ONION_KEY_CROSSCERT_LINES.substring( - ONION_KEY_CROSSCERT_LINES.indexOf("\n") + 1), - descriptor.getOnionKeyCrosscert()); - } - - @Test(expected = DescriptorParseException.class) - public void testOnionKeyCrosscertDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithOnionKeyCrosscertLines( - ONION_KEY_CROSSCERT_LINES + "\n" + ONION_KEY_CROSSCERT_LINES); - } - - @Test() - public void testNtorOnionKeyCrosscert() - throws DescriptorParseException { - ServerDescriptor descriptor = - DescriptorBuilder.createWithNtorOnionKeyCrosscertLines( - NTOR_ONION_KEY_CROSSCERT_LINES); - assertEquals(NTOR_ONION_KEY_CROSSCERT_LINES.substring( - NTOR_ONION_KEY_CROSSCERT_LINES.indexOf("\n") + 1), - descriptor.getNtorOnionKeyCrosscert()); - assertEquals(1, descriptor.getNtorOnionKeyCrosscertSign()); - } - - @Test(expected = DescriptorParseException.class) - public void testNtorOnionKeyCrosscertDuplicate() - throws DescriptorParseException { - DescriptorBuilder.createWithOnionKeyCrosscertLines( - NTOR_ONION_KEY_CROSSCERT_LINES + "\n" - + NTOR_ONION_KEY_CROSSCERT_LINES); - } -} - diff --git a/test/org/torproject/descriptor/impl/TorperfResultImplTest.java b/test/org/torproject/descriptor/impl/TorperfResultImplTest.java deleted file mode 100644 index b5cde0a..0000000 --- a/test/org/torproject/descriptor/impl/TorperfResultImplTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* Copyright 2015 The Tor Project - * See LICENSE for licensing information */ -package org.torproject.descriptor.impl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.util.List; - -import org.junit.Test; -import org.torproject.descriptor.Descriptor; - -public class TorperfResultImplTest { - - @Test() - public void testAnnotatedInput() throws Exception{ - TorperfResultImpl result = (TorperfResultImpl) - (TorperfResultImpl.parseTorperfResults((torperfAnnotation + input) - .getBytes("US-ASCII"), false).get(0)); - assertEquals("Expected one annotation.", 1, - result.getAnnotations().size()); - assertEquals(torperfAnnotation.substring(0, 17), - result.getAnnotations().get(0)); - int count = 0; - for (Long l: result.getDataPercentiles().values()) { - assertNotNull(l); - assertEquals(l.longValue(), deciles[count++]); - } - } - - @Test() - public void testPartiallyAnnotatedInput() throws Exception{ - byte[] asciiBytes = (torperfAnnotation - + input + input + input).getBytes("US-ASCII"); - List<Descriptor> result = TorperfResultImpl.parseTorperfResults( - asciiBytes, false); - assertEquals("Expected one annotation.", 1, - ((TorperfResultImpl)(result.get(0))).getAnnotations().size()); - assertEquals(3, result.size()); - assertEquals("Expected zero annotations.", 0, - ((TorperfResultImpl)(result.get(1))).getAnnotations().size()); - assertEquals("Expected zero annotations.", 0, - ((TorperfResultImpl)(result.get(2))).getAnnotations().size()); - } - - @Test() - public void testAllAnnotatedInput() throws Exception { - byte[] asciiBytes = (torperfAnnotation + input - + torperfAnnotation + input - + torperfAnnotation + input).getBytes("US-ASCII"); - List<Descriptor> result = TorperfResultImpl.parseTorperfResults( - asciiBytes, false); - assertEquals("Expected one annotation.", 1, - ((TorperfResultImpl)(result.get(0))).getAnnotations().size()); - assertEquals(3, result.size()); - assertEquals("Expected one annotation.", 1, - ((TorperfResultImpl)(result.get(1))).getAnnotations().size()); - assertEquals("Expected one annotation.", 1, - ((TorperfResultImpl)(result.get(2))).getAnnotations().size()); - } - - private static long[] deciles = new long[] { - 1441065602980L, 1441065603030L, 1441065603090L, 1441065603120L, - 1441065603230L, 1441065603250L, 1441065603310L, 1441065603370L, - 1441065603370L }; - - private static final String torperfAnnotation = "@type torperf 1.0\n"; - - private static final String input = - "BUILDTIMES=0.872834920883,1.09103679657,1.49180984497 " - + "CIRC_ID=1228 CONNECT=1441065601.86 DATACOMPLETE=1441065603.39 " - + "DATAPERC10=1441065602.98 DATAPERC20=1441065603.03 " - + "DATAPERC30=1441065603.09 DATAPERC40=1441065603.12 " - + "DATAPERC50=1441065603.23 DATAPERC60=1441065603.25 " - + "DATAPERC70=1441065603.31 DATAPERC80=1441065603.37 " - + "DATAPERC90=1441065603.37 DATAREQUEST=1441065602.38 " - + "DATARESPONSE=1441065602.84 DIDTIMEOUT=0 FILESIZE=51200 " - + "LAUNCH=1441065361.30 NEGOTIATE=1441065601.86 " - + "PATH=$C4C9C332D25B3546BEF4E1250CF410E97EF996E6," - + "$C43FA6474A9F071E9120DF63ED6EB8FDBA105234," - + "$7C0AA4E3B73E407E9F5FEB1912F8BE26D8AA124D QUANTILE=0.800000 " - + "READBYTES=51416 REQUEST=1441065601.86 RESPONSE=1441065602.38 " - + "SOCKET=1441065601.86 SOURCE=moria START=1441065601.86 " - + "TIMEOUT=1500 USED_AT=1441065603.40 USED_BY=2475 WRITEBYTES=75\n"; - - @Test() - public void testDatapercNonNumeric() throws Exception { - List<Descriptor> result = TorperfResultImpl.parseTorperfResults( - ("DATAPERMILLE=2.0 " + input).getBytes(), false); - assertEquals(1, result.size()); - TorperfResultImpl torperfResult = (TorperfResultImpl) result.get(0); - assertEquals(1, torperfResult.getUnrecognizedKeys().size()); - assertEquals("DATAPERMILLE", - torperfResult.getUnrecognizedKeys().firstKey()); - } -} -
tor-commits@lists.torproject.org