Commit 76dec065 authored by Karsten Loesing's avatar Karsten Loesing
Browse files

Add version_status field to details documents.

Implements #24256.
parent 532ef347
# 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
......
......@@ -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) {
......
......@@ -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;
}
}
......@@ -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();
}
}
......
......@@ -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
......
......@@ -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);
......
/* 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;
}
}
}
}
/* 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;
}
}
......@@ -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);
}
......
/* 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);
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment