[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [onionoo/master] Add "alleged_family" and "indirect_family" fields.



commit 7a8f1ffd2d95388892efe0f4198ffae0c076aa34
Author: Karsten Loesing <karsten.loesing@xxxxxxx>
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@xxxxxxxxxxxxxxxxx> - 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>

_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits