commit d3590d9bd48c0d8980360fb476bac9cc7a9f15bd Author: Karsten Loesing karsten.loesing@gmx.net Date: Fri Jul 3 10:20:34 2015 +0200
Add "effective_family" field to details documents.
Implements #16276. --- build.xml | 2 +- .../torproject/onionoo/docs/DetailsDocument.java | 8 ++ .../org/torproject/onionoo/docs/DetailsStatus.java | 8 ++ .../org/torproject/onionoo/docs/DocumentStore.java | 2 +- .../org/torproject/onionoo/docs/NodeStatus.java | 34 ++++++- .../torproject/onionoo/docs/SummaryDocument.java | 17 +++- .../org/torproject/onionoo/server/NodeIndexer.java | 30 ++++-- .../torproject/onionoo/server/ResponseBuilder.java | 4 +- .../onionoo/updater/NodeDetailsStatusUpdater.java | 102 ++++++++++++++++---- .../onionoo/writer/DetailsDocumentWriter.java | 8 ++ .../onionoo/writer/SummaryDocumentWriter.java | 3 +- .../onionoo/server/ResourceServletTest.java | 15 ++- web/protocol.html | 15 +++ 13 files changed, 209 insertions(+), 39 deletions(-)
diff --git a/build.xml b/build.xml index b10987b..75dfe1c 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ <project default="dist" name="onionoo" basedir=".">
- <property name="onionoo.protocol.version" value="2.3"/> + <property name="onionoo.protocol.version" value="2.4"/> <property name="release.version" value="${onionoo.protocol.version}.0"/> <property name="javasources" value="src/main/java"/> diff --git a/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java b/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java index d4efdc0..2b65e50 100644 --- a/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java +++ b/src/main/java/org/torproject/onionoo/docs/DetailsDocument.java @@ -289,6 +289,14 @@ public class DetailsDocument extends Document { return this.family; }
+ private SortedSet<String> effective_family; + public void setEffectiveFamily(SortedSet<String> effectiveFamily) { + this.effective_family = effectiveFamily; + } + public SortedSet<String> getEffectiveFamily() { + return this.effective_family; + } + private Float consensus_weight_fraction; public void setConsensusWeightFraction(Float consensusWeightFraction) { if (consensusWeightFraction == null || diff --git a/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java b/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java index fe46416..09f0824 100644 --- a/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java +++ b/src/main/java/org/torproject/onionoo/docs/DetailsStatus.java @@ -110,6 +110,14 @@ public class DetailsStatus extends Document { return this.family; }
+ private SortedSet<String> effective_family; + public void setEffectiveFamily(SortedSet<String> effectiveFamily) { + this.effective_family = effectiveFamily; + } + public SortedSet<String> getEffectiveFamily() { + return this.effective_family; + } + private Map<String, List<String>> exit_policy_v6_summary; public void setExitPolicyV6Summary( Map<String, List<String>> exitPolicyV6Summary) { diff --git a/src/main/java/org/torproject/onionoo/docs/DocumentStore.java b/src/main/java/org/torproject/onionoo/docs/DocumentStore.java index 3be6fdb..8e2162c 100644 --- a/src/main/java/org/torproject/onionoo/docs/DocumentStore.java +++ b/src/main/java/org/torproject/onionoo/docs/DocumentStore.java @@ -422,7 +422,7 @@ public class DocumentStore { SummaryDocument summaryDocument = new SummaryDocument(isRelay, nickname, fingerprint, addresses, lastSeenMillis, running, relayFlags, consensusWeight, countryCode, firstSeenMillis, - aSNumber, contact, family); + aSNumber, contact, family, family); return summaryDocument; }
diff --git a/src/main/java/org/torproject/onionoo/docs/NodeStatus.java b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java index 0985491..51fc678 100644 --- a/src/main/java/org/torproject/onionoo/docs/NodeStatus.java +++ b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java @@ -301,6 +301,16 @@ public class NodeStatus extends Document { return this.lastRdnsLookup; }
+ /* Computed effective family */ + + private String[] effectiveFamily; + public void setEffectiveFamily(SortedSet<String> effectiveFamily) { + this.effectiveFamily = collectionToStringArray(effectiveFamily); + } + public SortedSet<String> getEffectiveFamily() { + return stringArrayToSortedSet(this.effectiveFamily); + } + /* Constructor and (de-)serialization methods: */
public NodeStatus(String fingerprint) { @@ -402,8 +412,17 @@ public class NodeStatus extends Document { nodeStatus.setRecommendedVersion(parts[21].equals("true")); } if (!parts[22].equals("null")) { - nodeStatus.setFamilyFingerprints(new TreeSet<String>( - Arrays.asList(parts[22].split(";")))); + SortedSet<String> familyFingerprints = new TreeSet<String>(); + for (String familyMember : parts[22].split("[;:]")) { + if (familyMember.length() > 0) { + familyFingerprints.add(familyMember); + } + } + nodeStatus.setFamilyFingerprints(familyFingerprints); + if (parts[22].contains(":")) { + nodeStatus.setEffectiveFamily(new TreeSet<String>( + Arrays.asList(parts[22].split(":", 2)[1].split(";")))); + } } return nodeStatus; } catch (NumberFormatException e) { @@ -464,7 +483,16 @@ public class NodeStatus extends Document { sb.append("\t" + (this.contact != null ? this.contact : "")); sb.append("\t" + (this.recommendedVersion == null ? "null" : this.recommendedVersion ? "true" : "false")); - sb.append("\t" + StringUtils.join(this.familyFingerprints, ";")); + if (this.effectiveFamily != null && this.effectiveFamily.length > 0) { + SortedSet<String> mutual = this.getEffectiveFamily(); + SortedSet<String> notMutual = new TreeSet<String>( + this.getFamilyFingerprints()); + notMutual.removeAll(mutual); + sb.append("\t" + StringUtils.join(notMutual, ";") + ":" + + StringUtils.join(mutual, ";")); + } else { + sb.append("\t" + StringUtils.join(this.familyFingerprints, ";")); + } return sb.toString(); } } diff --git a/src/main/java/org/torproject/onionoo/docs/SummaryDocument.java b/src/main/java/org/torproject/onionoo/docs/SummaryDocument.java index 8e325f3..ae7202b 100644 --- a/src/main/java/org/torproject/onionoo/docs/SummaryDocument.java +++ b/src/main/java/org/torproject/onionoo/docs/SummaryDocument.java @@ -185,6 +185,9 @@ public class SummaryDocument extends Document { return this.c; }
+ /* This attribute can go away once all Onionoo services had their hourly + * updater write effective families to summary documents at least once. + * Remove this code after September 8, 2015. */ private String[] ff; public void setFamilyFingerprints( SortedSet<String> familyFingerprints) { @@ -194,11 +197,22 @@ public class SummaryDocument extends Document { return this.stringArrayToSortedSet(this.ff); }
+ private String[] ef; + public void setEffectiveFamily(SortedSet<String> effectiveFamily) { + this.ef = this.collectionToStringArray(effectiveFamily); + } + public SortedSet<String> getEffectiveFamily() { + return this.stringArrayToSortedSet(this.ef); + } + + /* The familyFingerprints parameter can go away after September 8, 2015. + * See above. */ public SummaryDocument(boolean isRelay, String nickname, String fingerprint, List<String> addresses, long lastSeenMillis, boolean running, SortedSet<String> relayFlags, long consensusWeight, String countryCode, long firstSeenMillis, String aSNumber, - String contact, SortedSet<String> familyFingerprints) { + String contact, SortedSet<String> familyFingerprints, + SortedSet<String> effectiveFamily) { this.setRelay(isRelay); this.setNickname(nickname); this.setFingerprint(fingerprint); @@ -212,6 +226,7 @@ public class SummaryDocument extends Document { this.setASNumber(aSNumber); this.setContact(contact); this.setFamilyFingerprints(familyFingerprints); + this.setEffectiveFamily(effectiveFamily); } }
diff --git a/src/main/java/org/torproject/onionoo/server/NodeIndexer.java b/src/main/java/org/torproject/onionoo/server/NodeIndexer.java index 5788d4e..347996b 100644 --- a/src/main/java/org/torproject/onionoo/server/NodeIndexer.java +++ b/src/main/java/org/torproject/onionoo/server/NodeIndexer.java @@ -1,7 +1,6 @@ package org.torproject.onionoo.server;
import java.io.File; -import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -19,7 +18,6 @@ import javax.servlet.ServletContextListener;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.torproject.onionoo.docs.DocumentStore; import org.torproject.onionoo.docs.DocumentStoreFactory; import org.torproject.onionoo.docs.SummaryDocument; @@ -160,6 +158,11 @@ public class NodeIndexer implements ServletContextListener, Runnable { } Time time = TimeFactory.getTime(); List<String> orderRelaysByConsensusWeight = new ArrayList<String>(); + /* This variable can go away once all Onionoo services had their + * hourly updater write effective families to summary documents at + * least once. Remove this code after September 8, 2015. */ + SortedMap<String, Set<String>> computedEffectiveFamilies = + new TreeMap<String, Set<String>>(); for (SummaryDocument entry : currentRelays) { String fingerprint = entry.getFingerprint().toUpperCase(); String hashedFingerprint = entry.getHashedFingerprint(). @@ -196,8 +199,16 @@ public class NodeIndexer implements ServletContextListener, Runnable { newRelaysByFlag.get(flagLowerCase).add(fingerprint); newRelaysByFlag.get(flagLowerCase).add(hashedFingerprint); } - if (entry.getFamilyFingerprints() != null) { - newRelaysByFamily.put(fingerprint, entry.getFamilyFingerprints()); + /* This condition can go away once all Onionoo services had their + * hourly updater write effective families to summary documents at + * least once. Remove this code after September 8, 2015. */ + if (entry.getFamilyFingerprints() != null && + !entry.getFamilyFingerprints().isEmpty()) { + computedEffectiveFamilies.put(fingerprint, + entry.getFamilyFingerprints()); + } + if (entry.getEffectiveFamily() != null) { + newRelaysByFamily.put(fingerprint, entry.getEffectiveFamily()); } int daysSinceFirstSeen = (int) ((time.currentTimeMillis() - entry.getFirstSeenMillis()) / ONE_DAY); @@ -229,18 +240,21 @@ public class NodeIndexer implements ServletContextListener, Runnable { for (String relay : orderRelaysByConsensusWeight) { newRelaysByConsensusWeight.add(relay.split(" ")[1]); } + /* This loop can go away once all Onionoo services had their hourly + * updater write effective families to summary documents at least + * once. Remove this code after September 8, 2015. */ for (Map.Entry<String, Set<String>> e : - newRelaysByFamily.entrySet()) { + computedEffectiveFamilies.entrySet()) { String fingerprint = e.getKey(); Set<String> inMutualFamilyRelation = new HashSet<String>(); for (String otherFingerprint : e.getValue()) { - if (newRelaysByFamily.containsKey(otherFingerprint) && - newRelaysByFamily.get(otherFingerprint).contains( + if (computedEffectiveFamilies.containsKey(otherFingerprint) && + computedEffectiveFamilies.get(otherFingerprint).contains( fingerprint)) { inMutualFamilyRelation.add(otherFingerprint); } } - e.getValue().retainAll(inMutualFamilyRelation); + newRelaysByFamily.put(fingerprint, inMutualFamilyRelation); } for (SummaryDocument entry : currentBridges) { String hashedFingerprint = entry.getFingerprint().toUpperCase(); diff --git a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java index af0f67e..c7bfd65 100644 --- a/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java +++ b/src/main/java/org/torproject/onionoo/server/ResponseBuilder.java @@ -70,7 +70,7 @@ public class ResponseBuilder { return this.charsWritten; }
- private static final String PROTOCOL_VERSION = "2.3"; + private static final String PROTOCOL_VERSION = "2.4";
private static final String NEXT_MAJOR_VERSION_SCHEDULED = null;
@@ -267,6 +267,8 @@ public class ResponseBuilder { dd.setHibernating(detailsDocument.getHibernating()); } else if (field.equals("transports")) { dd.setTransports(detailsDocument.getTransports()); + } else if (field.equals("effective_family")) { + dd.setEffectiveFamily(detailsDocument.getEffectiveFamily()); } } /* 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 be2bd9a..4fb0143 100644 --- a/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java +++ b/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java @@ -56,14 +56,14 @@ import org.torproject.onionoo.util.TimeFactory; * are not loaded from disk before the parse step in order to save * memory for parsed descriptors. * 3. Perform reverse DNS lookups, Look up relay IP addresses in a - * GeoIP database, and calculate path selection probabilities. - * Update node statuses accordingly. + * GeoIP database, calculate path selection probabilities, and + * compute effective families, and update node statuses accordingly. * 4. Retrieve details statuses corresponding to nodes that have been * changed since the start of the update process, possibly update the * node statuses with contents from newly parsed descriptors, update - * details statuses with results from lookup operations and new path - * selection probabilities, and store details statuses and node - * statuses back to disk. + * details statuses with results from lookup operations, new path + * selection probabilities, and effective families, and store details + * statuses and node statuses back to disk. */ public class NodeDetailsStatusUpdater implements DescriptorListener, StatusUpdater { @@ -140,6 +140,9 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } }
+ private Map<String, SortedSet<String>> familyFingerprints = + new HashMap<String, SortedSet<String>>(); + private void processRelayServerDescriptor( ServerDescriptor descriptor) { String fingerprint = descriptor.getFingerprint(); @@ -170,6 +173,16 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, detailsStatus.setContact(descriptor.getContact()); detailsStatus.setPlatform(descriptor.getPlatform()); detailsStatus.setFamily(descriptor.getFamilyEntries()); + if (descriptor.getFamilyEntries() != null) { + SortedSet<String> noPrefixUpperCase = new TreeSet<String>(); + for (String familyMember : descriptor.getFamilyEntries()) { + if (familyMember.startsWith("$") && familyMember.length() >= 41) { + noPrefixUpperCase.add( + familyMember.substring(1, 41).toUpperCase()); + } + } + this.familyFingerprints.put(fingerprint, noPrefixUpperCase); + } if (descriptor.getIpv6DefaultPolicy() != null && (descriptor.getIpv6DefaultPolicy().equals("accept") || descriptor.getIpv6DefaultPolicy().equals("reject")) && @@ -354,6 +367,8 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, log.info("Looked up cities and ASes"); this.calculatePathSelectionProbabilities(); log.info("Calculated path selection probabilities"); + this.computeEffectiveFamilies(); + log.info("Computed effective families"); this.finishReverseDomainNameLookups(); log.info("Finished reverse domain name lookups"); this.updateNodeDetailsStatuses(); @@ -449,6 +464,10 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } updatedNodeStatus.setLastRdnsLookup( nodeStatus.getLastRdnsLookup()); + updatedNodeStatus.setFamilyFingerprints( + nodeStatus.getFamilyFingerprints()); + updatedNodeStatus.setEffectiveFamily( + nodeStatus.getEffectiveFamily()); } else { updatedNodeStatus = nodeStatus; this.knownNodes.put(fingerprint, nodeStatus); @@ -471,6 +490,19 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } } } + /* Update family fingerprints in known nodes with any fingerprints we + * learned when parsing server descriptors in this run. These are + * guaranteed to come from more recent server descriptors, so it's + * safe to override whatever is in node statuses. */ + for (Map.Entry<String, NodeStatus> e : this.knownNodes.entrySet()) { + String fingerprint = e.getKey(); + if (this.familyFingerprints.containsKey(fingerprint)) { + NodeStatus nodeStatus = e.getValue(); + nodeStatus.setFamilyFingerprints( + this.familyFingerprints.get(fingerprint)); + } + } + this.familyFingerprints.clear(); }
/* Step 3: perform lookups and calculate path selection @@ -617,6 +649,52 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } }
+ private void computeEffectiveFamilies() { + SortedMap<String, SortedSet<String>> declaredFamilies = + new TreeMap<String, SortedSet<String>>(); + for (String fingerprint : this.currentRelays) { + NodeStatus nodeStatus = this.knownNodes.get(fingerprint); + if (nodeStatus != null && + nodeStatus.getFamilyFingerprints() != null && + !nodeStatus.getFamilyFingerprints().isEmpty()) { + declaredFamilies.put(fingerprint, + nodeStatus.getFamilyFingerprints()); + } + } + SortedMap<String, SortedSet<String>> effectiveFamilies = + new TreeMap<String, SortedSet<String>>(); + for (Map.Entry<String, SortedSet<String>> e : + declaredFamilies.entrySet()) { + String fingerprint = e.getKey(); + SortedSet<String> declaredFamily = e.getValue(); + SortedSet<String> effectiveFamily = new TreeSet<String>(); + for (String declaredFamilyMember : declaredFamily) { + if (declaredFamilies.containsKey(declaredFamilyMember) && + declaredFamilies.get(declaredFamilyMember).contains( + fingerprint)) { + effectiveFamily.add(declaredFamilyMember); + } + } + if (!effectiveFamily.isEmpty()) { + effectiveFamilies.put(fingerprint, effectiveFamily); + } + } + for (String fingerprint : this.currentRelays) { + NodeStatus nodeStatus = this.knownNodes.get(fingerprint); + if (nodeStatus == null) { + continue; + } + if (effectiveFamilies.containsKey(fingerprint)) { + nodeStatus.setEffectiveFamily(effectiveFamilies.get(fingerprint)); + this.updatedNodes.add(fingerprint); + } else if (nodeStatus.getEffectiveFamily() != null || + !nodeStatus.getEffectiveFamily().isEmpty()) { + nodeStatus.setEffectiveFamily(null); + this.updatedNodes.add(fingerprint); + } + } + } + private long startedRdnsLookups = -1L;
private SortedMap<String, String> rdnsLookupResults = @@ -681,19 +759,7 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, nodeStatus.getOrAddresses()); nodeStatus.setExitAddresses(exitAddressesWithoutOrAddresses);
- if (detailsStatus.getFamily() != null && - !detailsStatus.getFamily().isEmpty()) { - SortedSet<String> familyFingerprints = new TreeSet<String>(); - for (String familyMember : detailsStatus.getFamily()) { - if (familyMember.startsWith("$") && - familyMember.length() == 41) { - familyFingerprints.add(familyMember.substring(1)); - } - } - if (!familyFingerprints.isEmpty()) { - nodeStatus.setFamilyFingerprints(familyFingerprints); - } - } + detailsStatus.setEffectiveFamily(nodeStatus.getEffectiveFamily());
if (this.geoIpLookupResults.containsKey(fingerprint)) { LookupResult lookupResult = this.geoIpLookupResults.get( diff --git a/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java b/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java index 1a1ddc3..152b4cb 100644 --- a/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java +++ b/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java @@ -112,6 +112,14 @@ public class DetailsDocumentWriter implements DocumentWriter { detailsDocument.setContact(detailsStatus.getContact()); detailsDocument.setPlatform(detailsStatus.getPlatform()); detailsDocument.setFamily(detailsStatus.getFamily()); + if (detailsStatus.getEffectiveFamily() != null && + !detailsStatus.getEffectiveFamily().isEmpty()) { + SortedSet<String> effectiveFamily = new TreeSet<String>(); + for (String familyMember : detailsStatus.getEffectiveFamily()) { + effectiveFamily.add("$" + familyMember); + } + detailsDocument.setEffectiveFamily(effectiveFamily); + } detailsDocument.setExitPolicyV6Summary( detailsStatus.getExitPolicyV6Summary()); detailsDocument.setHibernating(detailsStatus.getHibernating()); diff --git a/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java b/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java index 6406a28..ddb3003 100644 --- a/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java +++ b/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java @@ -86,10 +86,11 @@ public class SummaryDocumentWriter implements DocumentWriter { String contact = nodeStatus.getContact(); SortedSet<String> familyFingerprints = nodeStatus.getFamilyFingerprints(); + SortedSet<String> effectiveFamily = nodeStatus.getEffectiveFamily(); SummaryDocument summaryDocument = new SummaryDocument(isRelay, nickname, fingerprint, addresses, lastSeenMillis, running, relayFlags, consensusWeight, countryCode, firstSeenMillis, - aSNumber, contact, familyFingerprints); + aSNumber, contact, familyFingerprints, effectiveFamily); if (this.documentStore.store(summaryDocument, fingerprint)) { this.writtenDocuments++; }; diff --git a/src/test/java/org/torproject/onionoo/server/ResourceServletTest.java b/src/test/java/org/torproject/onionoo/server/ResourceServletTest.java index c6ef9c8..f52413a 100644 --- a/src/test/java/org/torproject/onionoo/server/ResourceServletTest.java +++ b/src/test/java/org/torproject/onionoo/server/ResourceServletTest.java @@ -128,7 +128,9 @@ public class ResourceServletTest { "torkaz <klaus dot zufall at gmx dot de> " + "fb-token:np5_g_83jmf=", new TreeSet<String>(Arrays.asList( new String[] { "001C13B3A55A71B977CA65EC85539D79C653A3FC", - "0025C136C1F3A9EEFE2AE3F918F03BFA21B5070B" }))); + "0025C136C1F3A9EEFE2AE3F918F03BFA21B5070B" })), + new TreeSet<String>(Arrays.asList( + new String[] { "001C13B3A55A71B977CA65EC85539D79C653A3FC" }))); org.torproject.onionoo.docs.SummaryDocument relayFerrari458 = new org.torproject.onionoo.docs.SummaryDocument(true, "Ferrari458", "001C13B3A55A71B977CA65EC85539D79C653A3FC", Arrays.asList( @@ -138,6 +140,8 @@ public class ResourceServletTest { "Running", "V2Dir", "Valid" })), 1140L, "us", DateTimeHelper.parse("2013-02-12 16:00:00"), "AS7922", null, new TreeSet<String>(Arrays.asList(new String[] { + "000C5F55BD4814B917CC474BD537F1A3B33CCE2A" })), + new TreeSet<String>(Arrays.asList(new String[] { "000C5F55BD4814B917CC474BD537F1A3B33CCE2A" }))); org.torproject.onionoo.docs.SummaryDocument relayTimMayTribute = new org.torproject.onionoo.docs.SummaryDocument(true, "TimMayTribute", @@ -149,7 +153,7 @@ public class ResourceServletTest { DateTimeHelper.parse("2013-04-16 18:00:00"), "AS6830", "1024D/51E2A1C7 steven j. murdoch " + "tor+steven.murdoch@cl.cam.ac.uk fb-token:5sr_k_zs2wm=", - new TreeSet<String>()); + new TreeSet<String>(), new TreeSet<String>()); org.torproject.onionoo.docs.SummaryDocument bridgeec2bridgercc7f31fe = new org.torproject.onionoo.docs.SummaryDocument(false, "ec2bridgercc7f31fe", "0000831B236DFF73D409AD17B40E2A728A53994F", @@ -157,7 +161,7 @@ public class ResourceServletTest { DateTimeHelper.parse("2013-04-21 18:07:03"), false, new TreeSet<String>(Arrays.asList(new String[] { "Valid" })), -1L, null, DateTimeHelper.parse("2013-04-20 15:37:04"), null, null, - null); + null, null); org.torproject.onionoo.docs.SummaryDocument bridgeUnnamed = new org.torproject.onionoo.docs.SummaryDocument(false, "Unnamed", "0002D9BDBBC230BD9C78FF502A16E0033EF87E0C", Arrays.asList( @@ -165,7 +169,7 @@ public class ResourceServletTest { DateTimeHelper.parse("2013-04-20 17:37:04"), false, new TreeSet<String>(Arrays.asList(new String[] { "Valid" })), -1L, null, DateTimeHelper.parse("2013-04-14 07:07:05"), null, null, - null); + null, null); org.torproject.onionoo.docs.SummaryDocument bridgegummy = new org.torproject.onionoo.docs.SummaryDocument(false, "gummy", "1FEDE50ED8DBA1DD9F9165F78C8131E4A44AB756", Arrays.asList( @@ -173,7 +177,8 @@ public class ResourceServletTest { DateTimeHelper.parse("2013-04-24 01:07:04"), true, new TreeSet<String>(Arrays.asList(new String[] { "Running", "Valid" })), -1L, null, - DateTimeHelper.parse("2013-01-16 21:07:04"), null, null, null); + DateTimeHelper.parse("2013-01-16 21:07:04"), null, null, null, + null); this.relays = new TreeMap<String, org.torproject.onionoo.docs.SummaryDocument>(); this.relays.put("000C5F55BD4814B917CC474BD537F1A3B33CCE2A", diff --git a/web/protocol.html b/web/protocol.html index 00221b0..9f36e4d 100644 --- a/web/protocol.html +++ b/web/protocol.html @@ -176,6 +176,8 @@ added "transports" field to bridge details documents on December 8, 2014.</li> <li><strong>2.3</strong>: Added optional "flags" field to uptime documents on March 22, 2015.</li> +<li><strong>2.4</strong>: Added optional "effective_family" field to +details documents on July 3, 2015.</li> </ul>
</div> <!-- box --> @@ -1185,6 +1187,19 @@ found. </li>
<li> +<b><font color="blue">effective_family</font></b> +<code class="typeof">array of strings</code> +<span class="required-false">optional</span> +<p> +Array of $-prefixed fingerprints of relays that are in an effective, +mutual family relationship with this relay. +Omitted if empty or if descriptor containing this information cannot be +found. +<font color="blue">Added on July 3, 2015.</font> +</p> +</li> + +<li> <b>consensus_weight_fraction</b> <code class="typeof">number</code> <span class="required-false">optional</span>
tor-commits@lists.torproject.org