commit 7a8f1ffd2d95388892efe0f4198ffae0c076aa34 Author: Karsten Loesing karsten.loesing@gmx.net Date: Tue Aug 18 09:52:36 2015 +0200
Add "alleged_family" and "indirect_family" fields.
Implements #16750. --- build.xml | 2 +- .../torproject/onionoo/docs/DetailsDocument.java | 16 +++ .../org/torproject/onionoo/docs/DetailsStatus.java | 16 +++ .../org/torproject/onionoo/docs/NodeStatus.java | 83 ++++++++++----- .../torproject/onionoo/server/ResponseBuilder.java | 6 +- .../onionoo/updater/NodeDetailsStatusUpdater.java | 84 +++++++++++---- .../onionoo/writer/DetailsDocumentWriter.java | 20 ++++ .../onionoo/writer/SummaryDocumentWriter.java | 5 +- .../torproject/onionoo/docs/NodeStatusTest.java | 112 ++++++++++++++++++++ web/protocol.html | 34 ++++++ 10 files changed, 326 insertions(+), 52 deletions(-)
diff --git a/build.xml b/build.xml index f82b49d..03a97f1 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ <project default="dist" name="onionoo" basedir=".">
- <property name="onionoo.protocol.version" value="2.5"/> + <property name="onionoo.protocol.version" value="2.6"/> <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 aa410d8..69d8efe 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> alleged_family; + public void setAllegedFamily(SortedSet<String> allegedFamily) { + this.alleged_family = allegedFamily; + } + public SortedSet<String> getAllegedFamily() { + return this.alleged_family; + } + private SortedSet<String> effective_family; public void setEffectiveFamily(SortedSet<String> effectiveFamily) { this.effective_family = effectiveFamily; @@ -297,6 +305,14 @@ public class DetailsDocument extends Document { return this.effective_family; }
+ private SortedSet<String> indirect_family; + public void setIndirectFamily(SortedSet<String> indirectFamily) { + this.indirect_family = indirectFamily; + } + public SortedSet<String> getIndirectFamily() { + return this.indirect_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 0a97fd3..62e621b 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> alleged_family; + public void setAllegedFamily(SortedSet<String> allegedFamily) { + this.alleged_family = allegedFamily; + } + public SortedSet<String> getAllegedFamily() { + return this.alleged_family; + } + private SortedSet<String> effective_family; public void setEffectiveFamily(SortedSet<String> effectiveFamily) { this.effective_family = effectiveFamily; @@ -118,6 +126,14 @@ public class DetailsStatus extends Document { return this.effective_family; }
+ private SortedSet<String> indirect_family; + public void setIndirectFamily(SortedSet<String> indirectFamily) { + this.indirect_family = indirectFamily; + } + public SortedSet<String> getIndirectFamily() { + return this.indirect_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/NodeStatus.java b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java index 51fc678..6e51190 100644 --- a/src/main/java/org/torproject/onionoo/docs/NodeStatus.java +++ b/src/main/java/org/torproject/onionoo/docs/NodeStatus.java @@ -46,13 +46,12 @@ public class NodeStatus extends Document { return this.contact; }
- private String[] familyFingerprints; - public void setFamilyFingerprints( - SortedSet<String> familyFingerprints) { - this.familyFingerprints = collectionToStringArray(familyFingerprints); + private String[] declaredFamily; + public void setDeclaredFamily(SortedSet<String> declaredFamily) { + this.declaredFamily = collectionToStringArray(declaredFamily); } - public SortedSet<String> getFamilyFingerprints() { - return stringArrayToSortedSet(this.familyFingerprints); + public SortedSet<String> getDeclaredFamily() { + return stringArrayToSortedSet(this.declaredFamily); }
private static String[] collectionToStringArray( @@ -301,7 +300,8 @@ public class NodeStatus extends Document { return this.lastRdnsLookup; }
- /* Computed effective family */ + /* Computed effective and extended family and derived subsets alleged + * and indirect family */
private String[] effectiveFamily; public void setEffectiveFamily(SortedSet<String> effectiveFamily) { @@ -311,6 +311,28 @@ public class NodeStatus extends Document { return stringArrayToSortedSet(this.effectiveFamily); }
+ private String[] extendedFamily; + public void setExtendedFamily(SortedSet<String> extendedFamily) { + this.extendedFamily = collectionToStringArray(extendedFamily); + } + public SortedSet<String> getExtendedFamily() { + return stringArrayToSortedSet(this.extendedFamily); + } + + public SortedSet<String> getAllegedFamily() { + SortedSet<String> allegedFamily = new TreeSet<String>( + stringArrayToSortedSet(this.declaredFamily)); + allegedFamily.removeAll(stringArrayToSortedSet(this.effectiveFamily)); + return allegedFamily; + } + + public SortedSet<String> getIndirectFamily() { + SortedSet<String> indirectFamily = new TreeSet<String>( + stringArrayToSortedSet(this.extendedFamily)); + indirectFamily.removeAll(stringArrayToSortedSet(this.effectiveFamily)); + return indirectFamily; + } + /* Constructor and (de-)serialization methods: */
public NodeStatus(String fingerprint) { @@ -412,17 +434,33 @@ public class NodeStatus extends Document { nodeStatus.setRecommendedVersion(parts[21].equals("true")); } if (!parts[22].equals("null")) { - SortedSet<String> familyFingerprints = new TreeSet<String>(); - for (String familyMember : parts[22].split("[;:]")) { - if (familyMember.length() > 0) { - familyFingerprints.add(familyMember); - } + /* The relay's family is encoded in three ':'-separated groups: + * 0. declared, not-mutually agreed family members, + * 1. effective, mutually agreed family members, and + * 2. indirect members that can be reached via others only. + * Each group contains zero or more ';'-separated fingerprints. */ + String[] groups = parts[22].split(":", -1); + SortedSet<String> allegedFamily = new TreeSet<String>(), + effectiveFamily = new TreeSet<String>(), + indirectFamily = new TreeSet<String>(); + if (groups[0].length() > 0) { + allegedFamily.addAll(Arrays.asList(groups[0].split(";"))); + } + if (groups.length > 1 && groups[1].length() > 0) { + effectiveFamily.addAll(Arrays.asList(groups[1].split(";"))); } - nodeStatus.setFamilyFingerprints(familyFingerprints); - if (parts[22].contains(":")) { - nodeStatus.setEffectiveFamily(new TreeSet<String>( - Arrays.asList(parts[22].split(":", 2)[1].split(";")))); + if (groups.length > 2 && groups[2].length() > 0) { + indirectFamily.addAll(Arrays.asList(groups[2].split(";"))); } + SortedSet<String> declaredFamily = new TreeSet<String>(); + declaredFamily.addAll(allegedFamily); + declaredFamily.addAll(effectiveFamily); + nodeStatus.setDeclaredFamily(declaredFamily); + nodeStatus.setEffectiveFamily(effectiveFamily); + SortedSet<String> extendedFamily = new TreeSet<String>(); + extendedFamily.addAll(effectiveFamily); + extendedFamily.addAll(indirectFamily); + nodeStatus.setExtendedFamily(extendedFamily); } return nodeStatus; } catch (NumberFormatException e) { @@ -483,16 +521,9 @@ public class NodeStatus extends Document { sb.append("\t" + (this.contact != null ? this.contact : "")); sb.append("\t" + (this.recommendedVersion == null ? "null" : this.recommendedVersion ? "true" : "false")); - 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, ";")); - } + sb.append("\t" + StringUtils.join(this.getAllegedFamily(), ";") + ":" + + StringUtils.join(this.getEffectiveFamily(), ";") + ":" + + StringUtils.join(this.getIndirectFamily(), ";")); 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 ffd6ddb..b8aafc3 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.5"; + private static final String PROTOCOL_VERSION = "2.6";
private static final String NEXT_MAJOR_VERSION_SCHEDULED = null;
@@ -271,6 +271,10 @@ public class ResponseBuilder { dd.setEffectiveFamily(detailsDocument.getEffectiveFamily()); } else if (field.equals("measured")) { dd.setMeasured(detailsDocument.getMeasured()); + } else if (field.equals("alleged_family")) { + dd.setAllegedFamily(detailsDocument.getAllegedFamily()); + } else if (field.equals("indirect_family")) { + dd.setIndirectFamily(detailsDocument.getIndirectFamily()); } } /* 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 fdcd419..d28e249 100644 --- a/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java +++ b/src/main/java/org/torproject/onionoo/updater/NodeDetailsStatusUpdater.java @@ -140,7 +140,7 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } }
- private Map<String, SortedSet<String>> familyFingerprints = + private Map<String, SortedSet<String>> declaredFamilies = new HashMap<String, SortedSet<String>>();
private void processRelayServerDescriptor( @@ -174,14 +174,16 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, detailsStatus.setPlatform(descriptor.getPlatform()); detailsStatus.setFamily(descriptor.getFamilyEntries()); if (descriptor.getFamilyEntries() != null) { - SortedSet<String> noPrefixUpperCase = new TreeSet<String>(); + SortedSet<String> declaredFamily = new TreeSet<String>(); for (String familyMember : descriptor.getFamilyEntries()) { if (familyMember.startsWith("$") && familyMember.length() >= 41) { - noPrefixUpperCase.add( + declaredFamily.add( familyMember.substring(1, 41).toUpperCase()); + } else { + declaredFamily.add(familyMember); } } - this.familyFingerprints.put(fingerprint, noPrefixUpperCase); + this.declaredFamilies.put(fingerprint, declaredFamily); } if (descriptor.getIpv6DefaultPolicy() != null && (descriptor.getIpv6DefaultPolicy().equals("accept") || @@ -382,8 +384,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.computeEffectiveAndExtendedFamilies(); + log.info("Computed effective and extended families"); this.finishReverseDomainNameLookups(); log.info("Finished reverse domain name lookups"); this.updateNodeDetailsStatuses(); @@ -479,10 +481,12 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } updatedNodeStatus.setLastRdnsLookup( nodeStatus.getLastRdnsLookup()); - updatedNodeStatus.setFamilyFingerprints( - nodeStatus.getFamilyFingerprints()); + updatedNodeStatus.setDeclaredFamily( + nodeStatus.getDeclaredFamily()); updatedNodeStatus.setEffectiveFamily( nodeStatus.getEffectiveFamily()); + updatedNodeStatus.setExtendedFamily( + nodeStatus.getExtendedFamily()); } else { updatedNodeStatus = nodeStatus; this.knownNodes.put(fingerprint, nodeStatus); @@ -511,13 +515,13 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, * 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)) { + if (this.declaredFamilies.containsKey(fingerprint)) { NodeStatus nodeStatus = e.getValue(); - nodeStatus.setFamilyFingerprints( - this.familyFingerprints.get(fingerprint)); + nodeStatus.setDeclaredFamily( + this.declaredFamilies.get(fingerprint)); } } - this.familyFingerprints.clear(); + this.declaredFamilies.clear(); }
/* Step 3: perform lookups and calculate path selection @@ -664,16 +668,14 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, } }
- private void computeEffectiveFamilies() { + private void computeEffectiveAndExtendedFamilies() { 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()); + if (nodeStatus != null && nodeStatus.getDeclaredFamily() != null && + !nodeStatus.getDeclaredFamily().isEmpty()) { + declaredFamilies.put(fingerprint, nodeStatus.getDeclaredFamily()); } } SortedMap<String, SortedSet<String>> effectiveFamilies = @@ -694,17 +696,55 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, effectiveFamilies.put(fingerprint, effectiveFamily); } } + SortedMap<String, SortedSet<String>> extendedFamilies = + new TreeMap<String, SortedSet<String>>(); + SortedSet<String> visited = new TreeSet<String>(); + for (String fingerprint : effectiveFamilies.keySet()) { + if (visited.contains(fingerprint)) { + continue; + } + SortedSet<String> toVisit = new TreeSet<String>(); + toVisit.add(fingerprint); + SortedSet<String> extendedFamily = new TreeSet<String>(); + while (!toVisit.isEmpty()) { + String visiting = toVisit.first(); + toVisit.remove(visiting); + extendedFamily.add(visiting); + SortedSet<String> members = effectiveFamilies.get(visiting); + if (members != null) { + for (String member : members) { + if (!toVisit.contains(member) && !visited.contains(member)) { + toVisit.add(member); + } + } + } + visited.add(visiting); + } + if (extendedFamily.size() > 1) { + for (String member : extendedFamily) { + SortedSet<String> extendedFamilyWithoutMember = + new TreeSet<String>(extendedFamily); + extendedFamilyWithoutMember.remove(member); + extendedFamilies.put(member, extendedFamilyWithoutMember); + } + } + } for (String fingerprint : this.currentRelays) { NodeStatus nodeStatus = this.knownNodes.get(fingerprint); if (nodeStatus == null) { continue; } - if (effectiveFamilies.containsKey(fingerprint)) { + if (effectiveFamilies.containsKey(fingerprint) || + extendedFamilies.containsKey(fingerprint)) { nodeStatus.setEffectiveFamily(effectiveFamilies.get(fingerprint)); + nodeStatus.setExtendedFamily(extendedFamilies.get(fingerprint)); this.updatedNodes.add(fingerprint); - } else if (nodeStatus.getEffectiveFamily() != null || - !nodeStatus.getEffectiveFamily().isEmpty()) { + } else if ((nodeStatus.getEffectiveFamily() != null && + !nodeStatus.getEffectiveFamily().isEmpty()) || + (nodeStatus.getIndirectFamily() != null && + !nodeStatus.getIndirectFamily().isEmpty())) { nodeStatus.setEffectiveFamily(null); + nodeStatus.setExtendedFamily(null); this.updatedNodes.add(fingerprint); } } @@ -774,7 +814,9 @@ public class NodeDetailsStatusUpdater implements DescriptorListener, nodeStatus.getOrAddresses()); nodeStatus.setExitAddresses(exitAddressesWithoutOrAddresses);
+ detailsStatus.setAllegedFamily(nodeStatus.getAllegedFamily()); detailsStatus.setEffectiveFamily(nodeStatus.getEffectiveFamily()); + detailsStatus.setIndirectFamily(nodeStatus.getIndirectFamily());
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 6f60958..6747c2c 100644 --- a/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java +++ b/src/main/java/org/torproject/onionoo/writer/DetailsDocumentWriter.java @@ -112,6 +112,18 @@ public class DetailsDocumentWriter implements DocumentWriter { detailsDocument.setContact(detailsStatus.getContact()); detailsDocument.setPlatform(detailsStatus.getPlatform()); detailsDocument.setFamily(detailsStatus.getFamily()); + if (detailsStatus.getAllegedFamily() != null && + !detailsStatus.getAllegedFamily().isEmpty()) { + SortedSet<String> allegedFamily = new TreeSet<String>(); + for (String familyMember : detailsStatus.getAllegedFamily()) { + if (familyMember.length() >= 40) { + allegedFamily.add("$" + familyMember); + } else { + allegedFamily.add(familyMember); + } + } + detailsDocument.setAllegedFamily(allegedFamily); + } if (detailsStatus.getEffectiveFamily() != null && !detailsStatus.getEffectiveFamily().isEmpty()) { SortedSet<String> effectiveFamily = new TreeSet<String>(); @@ -120,6 +132,14 @@ public class DetailsDocumentWriter implements DocumentWriter { } detailsDocument.setEffectiveFamily(effectiveFamily); } + if (detailsStatus.getIndirectFamily() != null && + !detailsStatus.getIndirectFamily().isEmpty()) { + SortedSet<String> indirectFamily = new TreeSet<String>(); + for (String familyMember : detailsStatus.getIndirectFamily()) { + indirectFamily.add("$" + familyMember); + } + detailsDocument.setIndirectFamily(indirectFamily); + } 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 ddb3003..1be752a 100644 --- a/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java +++ b/src/main/java/org/torproject/onionoo/writer/SummaryDocumentWriter.java @@ -84,13 +84,12 @@ public class SummaryDocumentWriter implements DocumentWriter { long firstSeenMillis = nodeStatus.getFirstSeenMillis(); String aSNumber = nodeStatus.getASNumber(); String contact = nodeStatus.getContact(); - SortedSet<String> familyFingerprints = - nodeStatus.getFamilyFingerprints(); + SortedSet<String> declaredFamily = nodeStatus.getDeclaredFamily(); SortedSet<String> effectiveFamily = nodeStatus.getEffectiveFamily(); SummaryDocument summaryDocument = new SummaryDocument(isRelay, nickname, fingerprint, addresses, lastSeenMillis, running, relayFlags, consensusWeight, countryCode, firstSeenMillis, - aSNumber, contact, familyFingerprints, effectiveFamily); + aSNumber, contact, declaredFamily, effectiveFamily); if (this.documentStore.store(summaryDocument, fingerprint)) { this.writtenDocuments++; }; diff --git a/src/test/java/org/torproject/onionoo/docs/NodeStatusTest.java b/src/test/java/org/torproject/onionoo/docs/NodeStatusTest.java new file mode 100644 index 0000000..e3a6fca --- /dev/null +++ b/src/test/java/org/torproject/onionoo/docs/NodeStatusTest.java @@ -0,0 +1,112 @@ +package org.torproject.onionoo.docs; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.junit.Test; + +public class NodeStatusTest { + + private static final String GABELMOO_NODE_STATUS = + "r\tgabelmoo\tF2044413DAC2E02E3D6BCF4735A19BCA1DE97281\t" + + "131.188.40.189;[2001:638:a000:4140::ffff:189]:443;\t2015-08-13\t" + + "08:00:00\t443\t80\tAuthority,HSDir,Running,Stable,V2Dir,Valid\t" + + "20\tde\t\t-1\treject\t1-65535\t2015-08-04\t12:00:00\t" + + "2015-08-04\t12:00:00\tAS680\t" + + "4096r/261c5fbe77285f88fb0c343266c8c2d7c5aa446d sebastian hahn " + + "tor@sebastianhahn.net - 12nbrajag5u3llwetsf7fstcdaz32mu5cn\t" + + "true\tnull"; + + private void assertFamiliesCanBeDeSerialized( + String[] declaredFamilyArray, String[] effectiveFamilyArray, + String[] extendedFamilyArray) { + SortedSet<String> declaredFamily = new TreeSet<String>( + Arrays.asList(declaredFamilyArray)); + SortedSet<String> effectiveFamily = new TreeSet<String>( + Arrays.asList(effectiveFamilyArray)); + SortedSet<String> extendedFamily = new TreeSet<String>( + Arrays.asList(extendedFamilyArray)); + NodeStatus nodeStatus = NodeStatus.fromString(GABELMOO_NODE_STATUS); + nodeStatus.setDeclaredFamily(declaredFamily); + nodeStatus.setEffectiveFamily(effectiveFamily); + nodeStatus.setExtendedFamily(extendedFamily); + String serialized = nodeStatus.toString(); + NodeStatus deserialized = NodeStatus.fromString(serialized); + assertEquals("Declared families don't match", declaredFamily, + deserialized.getDeclaredFamily()); + assertEquals("Effective families don't match", effectiveFamily, + deserialized.getEffectiveFamily()); + assertEquals("Extended families don't match", extendedFamily, + deserialized.getExtendedFamily()); + } + + private final String A = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + B = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", + C = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", + D = "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", + E = "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", + F = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + N = "nickname"; + + @Test + public void testFamiliesEmpty() { + assertFamiliesCanBeDeSerialized( + new String[] {}, new String[] {}, new String[] {}); + } + + @Test + public void testFamiliesOneNotMutual() { + assertFamiliesCanBeDeSerialized( + new String[] { A }, new String[] {}, new String[] {}); + } + + @Test + public void testFamiliesTwoNotMutual() { + assertFamiliesCanBeDeSerialized( + new String[] { A, B }, new String[] {}, new String[] {}); + } + + @Test + public void testFamiliesOneNotMutualOneMutual() { + assertFamiliesCanBeDeSerialized( + new String[] { A, B }, new String[] { B }, new String[] { B }); + } + + @Test + public void testFamiliesOneMutualOneIndirect() { + assertFamiliesCanBeDeSerialized( + new String[] { A }, new String[] { A }, new String[] { A, B }); + } + + @Test + public void testFamiliesOneNotMutualOneIndirect() { + /* This case is special, because B is both in this relay's alleged and + * extended family, but it's not in an effective family relationship + * with this relay. It's a valid case, because B can be in a mutual + * family relationship with A. */ + assertFamiliesCanBeDeSerialized( + new String[] { A, B }, new String[] { A }, new String[] { A, B }); + } + + @Test + public void testFamiliesOneNotMutualOneMutualOneIndirect() { + assertFamiliesCanBeDeSerialized( + new String[] { A, B }, new String[] { B }, new String[] { B, C }); + } + + @Test + public void testFamiliesTwoNotMutualTwoMutualTwoIndirect() { + assertFamiliesCanBeDeSerialized( + new String[] { A, B, C, D }, new String[] { C, D }, + new String[] { C, D, E, F }); + } + + @Test + public void testFamiliesNickname() { + assertFamiliesCanBeDeSerialized( + new String[] { N }, new String[] {}, new String[] {}); + } +} diff --git a/web/protocol.html b/web/protocol.html index 6681faf..ab06fe9 100644 --- a/web/protocol.html +++ b/web/protocol.html @@ -180,6 +180,9 @@ documents on March 22, 2015.</li> details documents on July 3, 2015.</li> <li><strong>2.5</strong>: Added optional "measured" field to details documents on August 13, 2015.</li> +<li><strong>2.6</strong>: Added optional "alleged_family" and +"indirect_family" fields and deprecated optional "family" field in details +documents on August 25, 2015.</li> </ul>
</div> <!-- box --> @@ -1185,6 +1188,7 @@ relay part of their family, so that the effective family of this relay may be smaller. Omitted if empty or if descriptor containing this information cannot be found. +<font color="blue">Deprecated on August 25, 2015.</font> </p> </li>
@@ -1195,6 +1199,8 @@ found. <p> Array of $-prefixed fingerprints of relays that are in an effective, mutual family relationship with this relay. +These relays are part of this relay's family and they consider this relay +to be part of their family. Omitted if empty or if descriptor containing this information cannot be found. <font color="blue">Added on July 3, 2015.</font> @@ -1202,6 +1208,34 @@ found. </li>
<li> +<b><font color="blue">alleged_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 not in an effective, +mutual family relationship with this relay. +These relays are part of this relay's family but they don't consider this +relay to be part of their family. +Omitted if empty or if descriptor containing this information cannot be +found. +<font color="blue">Added on August 25, 2015.</font> +</p> +</li> + +<li> +<b><font color="blue">indirect_family</font></b> +<code class="typeof">array of strings</code> +<span class="required-false">optional</span> +Array of $-prefixed fingerprints of relays that are not in an effective, +mutual family relationship with this relay but that can be reached by +following effective, mutual family relationships starting at this relay. +Omitted if empty or if descriptor containing this information cannot be +found. +<font color="blue">Added on August 25, 2015.</font> +</p> +</li> + +<li> <b>consensus_weight_fraction</b> <code class="typeof">number</code> <span class="required-false">optional</span>