commit 76dec0651148e99d069ab798693b22a33162b6b8 Author: Karsten Loesing karsten.loesing@gmx.net Date: Thu Feb 22 20:59:31 2018 +0100
Add version_status field to details documents.
Implements #24256. --- CHANGELOG.md | 3 + .../torproject/onionoo/docs/DetailsDocument.java | 10 ++ .../org/torproject/onionoo/docs/DetailsStatus.java | 10 ++ .../org/torproject/onionoo/docs/NodeStatus.java | 17 +++ .../torproject/onionoo/server/ResponseBuilder.java | 2 + .../onionoo/updater/NodeDetailsStatusUpdater.java | 23 +++- .../org/torproject/onionoo/updater/TorVersion.java | 150 +++++++++++++++++++++ .../onionoo/updater/TorVersionStatus.java | 47 +++++++ .../onionoo/writer/DetailsDocumentWriter.java | 1 + .../torproject/onionoo/updater/TorVersionTest.java | 127 +++++++++++++++++ 10 files changed, 384 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a3c468..36d6471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changes in version 5.1-1.12.0 - 2018-??-??
+ * Medium changes + - Add version_status field to details documents. + * Minor changes - Don't attempt to un-escape character sequences in contact lines (like "\uk") that only happen to start like escaped utf-8 characters diff --git a/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java b/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java index c749fba..e112efe 100644 --- a/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java +++ b/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java @@ -356,6 +356,16 @@ public class DetailsDocument extends Document { return this.version; }
+ private String version_status; + + public void setVersionStatus(String versionStatus) { + this.version_status = versionStatus; + } + + public String getVersionStatus() { + return this.version_status; + } + private SortedSet<String> alleged_family;
public void setAllegedFamily(SortedSet<String> allegedFamily) { diff --git a/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java b/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java index c3cdc28..f838ec0 100644 --- a/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java +++ b/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java @@ -550,5 +550,15 @@ public class DetailsStatus extends Document { public String getVersion() { return this.version; } + + private String version_status; + + public void setVersionStatus(String versionStatus) { + this.version_status = versionStatus; + } + + public String getVersionStatus() { + return this.version_status; + } }
diff --git a/src/main/java/org/torproject/onionoo/docs/NodeStatus.java b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java index 94da8c8..1ea8b98 100644 --- a/src/main/java/org/torproject/onionoo/docs/NodeStatus.java +++ b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java @@ -3,6 +3,8 @@
package org.torproject.onionoo.docs;
+import org.torproject.onionoo.updater.TorVersionStatus; + import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -330,6 +332,16 @@ public class NodeStatus extends Document { return this.version; }
+ private TorVersionStatus versionStatus; + + public void setVersionStatus(TorVersionStatus versionStatus) { + this.versionStatus = versionStatus; + } + + public TorVersionStatus getVersionStatus() { + return this.versionStatus; + } + /* From exit lists: */
private SortedSet<String> exitAddresses; @@ -572,6 +584,9 @@ public class NodeStatus extends Document { if (parts.length >= 25 && !parts[24].isEmpty()) { nodeStatus.setHostName(parts[24]); } + if (parts.length >= 26) { + nodeStatus.setVersionStatus(TorVersionStatus.ofAbbreviation(parts[25])); + } return nodeStatus; } catch (NumberFormatException e) { log.error("Number format exception while parsing node " @@ -640,6 +655,8 @@ public class NodeStatus extends Document { .append((this.getVersion() != null ? this.getVersion() : "")); sb.append("\t") .append((this.getHostName() != null ? this.getHostName() : "")); + sb.append("\t").append(null != this.getVersionStatus() + ? this.getVersionStatus().getAbbreviation() : ""); return sb.toString(); } } diff --git a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java index e2bdf82..fcf3c8f 100644 --- a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java +++ b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java @@ -339,6 +339,8 @@ public class ResponseBuilder { detailsDocument.getUnreachableOrAddresses()); } else if (field.equals("version")) { dd.setVersion(detailsDocument.getVersion()); + } else if (field.equals("version_status")) { + dd.setVersionStatus(detailsDocument.getVersionStatus()); } } /* Don't escape HTML characters, like < and >, contained in diff --git a/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java b/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java index 4fdf98b..b8bd4f6 100644 --- a/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java +++ b/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java @@ -22,10 +22,8 @@ import org.slf4j.LoggerFactory;
import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; @@ -89,7 +87,7 @@ public class NodeDetailsStatusUpdater implements DescriptorListener,
private SortedMap<String, Integer> lastBandwidthWeights = null;
- private Set<String> lastRecommendedServerVersions = null; + private SortedSet<TorVersion> lastRecommendedServerVersions = null;
private int relayConsensusesProcessed = 0;
@@ -306,8 +304,15 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, this.relayConsensusesProcessed++; if (this.relaysLastValidAfterMillis == validAfterMillis) { this.lastBandwidthWeights = consensus.getBandwidthWeights(); - this.lastRecommendedServerVersions - = new HashSet<>(consensus.getRecommendedServerVersions()); + this.lastRecommendedServerVersions = new TreeSet<>(); + for (String recommendedServerVersion : + consensus.getRecommendedServerVersions()) { + TorVersion recommendedTorServerVersion + = TorVersion.of(recommendedServerVersion); + if (null != recommendedTorServerVersion) { + this.lastRecommendedServerVersions.add(recommendedTorServerVersion); + } + } } }
@@ -798,8 +803,13 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, * the recommended_version field accordingly. */ if (null != this.lastRecommendedServerVersions && null != nodeStatus.getVersion()) { + TorVersion torVersion = TorVersion.of(nodeStatus.getVersion()); nodeStatus.setRecommendedVersion(this.lastRecommendedServerVersions - .contains(nodeStatus.getVersion())); + .contains(torVersion)); + nodeStatus.setVersionStatus(null != torVersion + ? torVersion.determineVersionStatus( + this.lastRecommendedServerVersions) + : TorVersionStatus.UNRECOMMENDED); }
Map<String, Long> exitAddresses = new HashMap<>(); @@ -909,6 +919,7 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, detailsStatus.setLastChangedOrAddressOrPort( nodeStatus.getLastChangedOrAddressOrPort()); detailsStatus.setVersion(nodeStatus.getVersion()); + detailsStatus.setVersionStatus(nodeStatus.getVersionStatus().toString());
this.documentStore.store(detailsStatus, fingerprint); this.documentStore.store(nodeStatus, fingerprint); diff --git a/src/main/java/org/torproject/onionoo/updater/TorVersion.java b/src/main/java/org/torproject/onionoo/updater/TorVersion.java new file mode 100644 index 0000000..d8e1683 --- /dev/null +++ b/src/main/java/org/torproject/onionoo/updater/TorVersion.java @@ -0,0 +1,150 @@ +/* Copyright 2018 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.onionoo.updater; + +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; + +/** + * Helper class to compare Tor versions. + * + * <p>Based on "How Tor Version Numbers Work", available at + * https://gitweb.torproject.org/torspec.git/tree/version-spec.txt</p> + */ +public class TorVersion implements Comparable<TorVersion> { + + private int majorVersion; + + private int minorVersion; + + private int microVersion; + + private String releaseSeries; + + private Integer patchLevel = null; + + private String statusTag = null; + + private static Map<String, TorVersion> knownVersions = new HashMap<>(); + + private TorVersion() { + } + + /** Return a TorVersion instance from the given tor version string that can be + * compared to other tor version strings, or null if the given string is not a + * valid tor version. */ + public static TorVersion of(String versionString) { + if (null == versionString) { + return null; + } + if (!knownVersions.containsKey(versionString)) { + TorVersion result = new TorVersion(); + String[] components = versionString.split("-")[0].split("\."); + try { + result.majorVersion = Integer.parseInt(components[0]); + result.minorVersion = Integer.parseInt(components[1]); + result.microVersion = Integer.parseInt(components[2]); + result.releaseSeries = String.format("%d.%d.%d", + result.majorVersion, result.minorVersion, result.microVersion); + if (components.length == 4) { + result.patchLevel = Integer.parseInt(components[3]); + if (versionString.contains("-")) { + result.statusTag = versionString.split("-", 2)[1].split(" ")[0]; + } + } + } catch (ArrayIndexOutOfBoundsException + | NumberFormatException exception) { + result = null; + } + knownVersions.put(versionString, result); + } + return knownVersions.get(versionString); + } + + @Override + public int compareTo(TorVersion other) { + if (null == other) { + throw new NullPointerException(); + } + int result; + if ((result = Integer.compare(this.majorVersion, + other.majorVersion)) != 0) { + return result; + } + if ((result = Integer.compare(this.minorVersion, + other.minorVersion)) != 0) { + return result; + } + if ((result = Integer.compare(this.microVersion, + other.microVersion)) != 0) { + return result; + } + if (null == this.patchLevel && null == other.patchLevel) { + return 0; + } else if (null == patchLevel) { + return -1; + } else if (null == other.patchLevel) { + return 1; + } else if ((result = Integer.compare(this.patchLevel, + other.patchLevel)) != 0) { + return result; + } + if (null == this.statusTag && null == other.statusTag) { + return 0; + } else if (null == this.statusTag) { + return -1; + } else if (null == other.statusTag) { + return 1; + } else { + return this.statusTag.compareTo(other.statusTag); + } + } + + @Override + public boolean equals(Object other) { + return null != other && other instanceof TorVersion + && this.compareTo((TorVersion) other) == 0; + } + + @Override + public int hashCode() { + return 2 * Integer.hashCode(this.majorVersion) + + 3 * Integer.hashCode(this.minorVersion) + + 5 * Integer.hashCode(this.microVersion) + + 7 * (null == this.patchLevel ? 0 : this.patchLevel) + + 11 * (null == this.statusTag ? 0 : this.statusTag.hashCode()); + } + + /** Determine the version status of this tor version in the context of the + * given recommended tor versions. */ + public TorVersionStatus determineVersionStatus( + SortedSet<TorVersion> recommendedVersions) { + if (recommendedVersions.contains(this)) { + return TorVersionStatus.RECOMMENDED; + } else if (this.compareTo(recommendedVersions.last()) > 0) { + return TorVersionStatus.EXPERIMENTAL; + } else if (this.compareTo(recommendedVersions.first()) < 0) { + return TorVersionStatus.OBSOLETE; + } else { + boolean seriesHasRecommendedVersions = false; + boolean notNewInSeries = false; + for (TorVersion recommendedVersion : recommendedVersions) { + if (this.releaseSeries.equals( + recommendedVersion.releaseSeries)) { + seriesHasRecommendedVersions = true; + if (this.compareTo(recommendedVersion) < 0) { + notNewInSeries = true; + } + } + } + if (seriesHasRecommendedVersions && !notNewInSeries) { + return TorVersionStatus.NEW_IN_SERIES; + } else { + return TorVersionStatus.UNRECOMMENDED; + } + } + } +} + diff --git a/src/main/java/org/torproject/onionoo/updater/TorVersionStatus.java b/src/main/java/org/torproject/onionoo/updater/TorVersionStatus.java new file mode 100644 index 0000000..5825064 --- /dev/null +++ b/src/main/java/org/torproject/onionoo/updater/TorVersionStatus.java @@ -0,0 +1,47 @@ +/* Copyright 2018 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.onionoo.updater; + +import java.util.HashMap; +import java.util.Map; + +public enum TorVersionStatus { + + RECOMMENDED("recommended", "r"), + EXPERIMENTAL("experimental", "e"), + OBSOLETE("obsolete", "o"), + NEW_IN_SERIES("new in series", "n"), + UNRECOMMENDED("unrecommended", "u"); + + private final String statusString; + + private final String abbreviation; + + TorVersionStatus(String statusString, String abbreviation) { + this.statusString = statusString; + this.abbreviation = abbreviation; + } + + public String getAbbreviation() { + return this.abbreviation; + } + + private static Map<String, TorVersionStatus> byAbbreviation = new HashMap<>(); + + static { + for (TorVersionStatus status : TorVersionStatus.values()) { + byAbbreviation.put(status.abbreviation, status); + } + } + + public static TorVersionStatus ofAbbreviation(String abbrevation) { + return byAbbreviation.get(abbrevation); + } + + @Override + public String toString() { + return this.statusString; + } +} + diff --git a/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java b/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java index 8490f05..eca7874 100644 --- a/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java +++ b/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java @@ -162,6 +162,7 @@ public class DetailsDocumentWriter implements DocumentWriter { detailsDocument.setUnreachableOrAddresses(unreachableOrAddresses); } detailsDocument.setVersion(detailsStatus.getVersion()); + detailsDocument.setVersionStatus(detailsStatus.getVersionStatus()); this.documentStore.store(detailsDocument, fingerprint); }
diff --git a/src/test/java/org/torproject/onionoo/updater/TorVersionTest.java b/src/test/java/org/torproject/onionoo/updater/TorVersionTest.java new file mode 100644 index 0000000..b6a9764 --- /dev/null +++ b/src/test/java/org/torproject/onionoo/updater/TorVersionTest.java @@ -0,0 +1,127 @@ +/* Copyright 2018 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.onionoo.updater; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; +import java.util.SortedSet; +import java.util.TreeSet; + +@RunWith(Enclosed.class) +public class TorVersionTest { + + @RunWith(Parameterized.class) + public static class TorVersionStatusTest { + + private static String[] recommendedVersionStrings = new String[] { + "0.2.5.16", "0.2.5.17", "0.2.9.14", "0.2.9.15", "0.3.1.9", "0.3.1.10", + "0.3.2.8-rc", "0.3.2.9", "0.3.3.1-alpha", "0.3.3.2-alpha" }; + + /** Provide test data. */ + @Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { "Recommended version", "0.2.5.16", "recommended" }, + { "Recommended version", "0.3.2.8-rc", "recommended" }, + { "Recommended version", "0.3.3.2-alpha", "recommended" }, + { "Experimental version", "0.3.3.2-alpha-dev", "experimental" }, + { "Experimental version", "0.3.3.3-alpha", "experimental" }, + { "Experimental version", "0.3.4.0-alpha-dev", "experimental" }, + { "Obsolete version", "0.2.5.15", "obsolete" }, + { "Obsolete version", "0.1.0.1-rc", "obsolete" }, + { "New-in-series version", "0.2.5.18", "new in series" }, + { "New-in-series version", "0.3.2.9-dev", "new in series" }, + { "New-in-series version", "0.3.2.10", "new in series" }, + { "Unrecommended version", "0.2.9.13", "unrecommended" }, + { "Unrecommended version", "0.3.1.8-dev", "unrecommended" }, + { "Unrecommended version", "0.3.3.0-alpha-dev", "unrecommended" }, + { "Unrecognized (experimental) version", "1.0-final", + "unrecommended" }, + { "Unrecognized (obsolete) version", "0.0.2pre13", "unrecommended" } + }); + } + + @Parameter + public String testDescription; + + @Parameter(1) + public String versionString; + + @Parameter(2) + public String expectedVersionStatus; + + @Test + public void test() { + SortedSet<TorVersion> recommendedTorVersions = new TreeSet<>(); + for (String recommendedVersionString : recommendedVersionStrings) { + recommendedTorVersions.add(TorVersion.of(recommendedVersionString)); + } + TorVersion torVersion = TorVersion.of(this.versionString); + String determinedVersionStatus = "unrecommended"; + if (null != torVersion) { + determinedVersionStatus = torVersion + .determineVersionStatus(recommendedTorVersions).toString(); + } + assertEquals(this.testDescription, this.expectedVersionStatus, + determinedVersionStatus); + } + } + + @RunWith(Parameterized.class) + public static class TorVersionEqualsHashCodeCompareToTest { + + /** Provide test data. */ + @Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { "0.2.5.16", "0.2.5.16", true, true, 0 }, + { "0.2.5.16", "0.2.5.17", false, false, -1 }, + { "0.3.3.1-alpha", "0.3.3.1-alpha", true, true, 0 }, + { "0.1.2.3", "00.01.02.03", true, true, 0 }, + { "0.1.2.3-alpha", "00.01.02.03-aallpphhaa", false, false, 1 } + }); + } + + @Parameter + public String firstVersionString; + + @Parameter(1) + public String secondVersionString; + + @Parameter(2) + public boolean expectedEqualsResult; + + @Parameter(3) + public boolean expectedSameHashCodes; + + @Parameter(4) + public int expectedCompareToResult; + + @Test + public void test() { + TorVersion firstVersion = TorVersion.of(this.firstVersionString); + TorVersion secondVersion = TorVersion.of(this.secondVersionString); + assertEquals(this.expectedEqualsResult, + firstVersion.equals(secondVersion)); + if (this.expectedSameHashCodes) { + assertEquals(firstVersion.hashCode(), secondVersion.hashCode()); + } + int actualCompareToResult = firstVersion.compareTo(secondVersion); + assertTrue(this.expectedCompareToResult < 0 && actualCompareToResult < 0 + || this.expectedCompareToResult == 0 && actualCompareToResult == 0 + || this.expectedCompareToResult > 0 && actualCompareToResult > 0); + } + } +} +
tor-commits@lists.torproject.org