[tor-commits] [metrics-lib/master] Parse hidserv-stats in extra-info descriptors.

karsten at torproject.org karsten at torproject.org
Mon Dec 21 19:45:59 UTC 2015


commit f9762314f297d184a5c6e0b0f5209815c18a29bf
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Wed Dec 16 12:06:41 2015 +0100

    Parse hidserv-stats in extra-info descriptors.
    
    This patch is loosely based on metrics-web's hidserv module.
---
 CHANGELOG.md                                       |    1 +
 .../torproject/descriptor/ExtraInfoDescriptor.java |   32 +++++
 .../descriptor/impl/ExtraInfoDescriptorImpl.java   |   81 ++++++++++++
 .../torproject/descriptor/impl/ParseHelper.java    |   34 ++++++
 .../impl/ExtraInfoDescriptorImplTest.java          |  129 ++++++++++++++++++++
 5 files changed, 277 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f2bc0d..bfda75e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
      extra-info descriptors, and support Ed25519 master keys in votes.
    - Include RSA-1024 signatures of SHA-1 digests of extra-info
      descriptors, which were parsed and discarded before.
+   - Support hidden-service statistics in extra-info descriptors.
 
 
 # Changes in version 1.0.0 - 2015-12-05
diff --git a/src/org/torproject/descriptor/ExtraInfoDescriptor.java b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
index 1b978a4..ed0141d 100644
--- a/src/org/torproject/descriptor/ExtraInfoDescriptor.java
+++ b/src/org/torproject/descriptor/ExtraInfoDescriptor.java
@@ -3,6 +3,7 @@
 package org.torproject.descriptor;
 
 import java.util.List;
+import java.util.Map;
 import java.util.SortedMap;
 
 /* Contains a relay or bridge extra-info descriptor. */
@@ -265,6 +266,37 @@ public interface ExtraInfoDescriptor extends Descriptor {
    * bridge. */
   public List<String> getTransports();
 
+  /* Return the end of the included hidden-service statistics, or -1 if no
+   * hidden-service statistics are included. */
+  public long getHidservStatsEndMillis();
+
+  /* Return the interval length of the included hidden-service statistics
+  * in seconds, or -1 if no hidden-service statistics are included. */
+  public long getHidservStatsIntervalLength();
+
+  /* Return the approximate number of RELAY cells seen in either direction
+   * on a circuit after receiving and successfully processing a
+   * RENDEZVOUS1 cell, or null if no hidden-service statistics are
+   * included. */
+  public Double getHidservRendRelayedCells();
+
+  /* Return the obfuscation parameters applied to the original measurement
+   * value of RELAY cells seen in either direction on a circuit after
+   * receiving and successfully processing a RENDEZVOUS1 cell, or null if
+   * no hidden-service statistics are included.. */
+  public Map<String, Double> getHidservRendRelayedCellsParameters();
+
+  /* Return the approximate number of unique hidden-service identities
+   * seen in descriptors published to and accepted by this hidden-service
+   * directory, or null if no hidden-service statistics are included. */
+  public Double getHidservDirOnionsSeen();
+
+  /* Return the obfuscation parameters applied to the original measurement
+   * value of unique hidden-service identities seen in descriptors
+   * published to and accepted by this hidden-service directory, or null
+   * if no hidden-service statistics are included. */
+  public Map<String, Double> getHidservDirOnionsSeenParameters();
+
   /* Return the signature of the PKCS1-padded extra-info descriptor
    * digest, or null if the descriptor doesn't contain a signature (which
    * is the case in sanitized bridge descriptors). */
diff --git a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
index 4abace6..ef0c82c 100644
--- a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
@@ -9,8 +9,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Scanner;
 import java.util.Set;
 import java.util.SortedMap;
@@ -165,6 +167,13 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
         this.parseBridgeIpTransportsLine(line, lineNoOpt, partsNoOpt);
       } else if (keyword.equals("transport")) {
         this.parseTransportLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("hidserv-stats-end")) {
+        this.parseHidservStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("hidserv-rend-relayed-cells")) {
+        this.parseHidservRendRelayedCellsLine(line, lineNoOpt,
+            partsNoOpt);
+      } else if (keyword.equals("hidserv-dir-onions-seen")) {
+        this.parseHidservDirOnionsSeenLine(line, lineNoOpt, partsNoOpt);
       } else if (keyword.equals("identity-ed25519")) {
         this.parseIdentityEd25519Line(line, lineNoOpt, partsNoOpt);
         nextCrypto = "identity-ed25519";
@@ -642,6 +651,46 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
     this.transports.add(partsNoOpt[1]);
   }
 
+  private void parseHidservStatsEndLine(String line, String lineNoOpt,
+      String[] partsNoOpt) throws DescriptorParseException {
+    long[] parsedStatsEndData = this.parseStatsEndLine(line, partsNoOpt,
+        5);
+    this.hidservStatsEndMillis = parsedStatsEndData[0];
+    this.hidservStatsIntervalLength = parsedStatsEndData[1];
+  }
+
+  private void parseHidservRendRelayedCellsLine(String line,
+      String lineNoOpt, String[] partsNoOpt)
+      throws DescriptorParseException {
+    if (partsNoOpt.length < 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    try {
+      this.hidservRendRelayedCells = Double.parseDouble(partsNoOpt[1]);
+    } catch (NumberFormatException e) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    this.hidservRendRelayedCellsParameters =
+        ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line,
+        partsNoOpt, 2);
+  }
+
+  private void parseHidservDirOnionsSeenLine(String line,
+      String lineNoOpt, String[] partsNoOpt)
+      throws DescriptorParseException {
+    if (partsNoOpt.length < 2) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    try {
+      this.hidservDirOnionsSeen = Double.parseDouble(partsNoOpt[1]);
+    } catch (NumberFormatException e) {
+      throw new DescriptorParseException("Illegal line '" + line + "'.");
+    }
+    this.hidservDirOnionsSeenParameters =
+        ParseHelper.parseSpaceSeparatedStringKeyDoubleValueMap(line,
+        partsNoOpt, 2);
+  }
+
   private void parseRouterSignatureLine(String line, String lineNoOpt,
       String[] partsNoOpt) throws DescriptorParseException {
     if (!lineNoOpt.equals("router-signature")) {
@@ -1057,6 +1106,38 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
     return new ArrayList<String>(this.transports);
   }
 
+  private long hidservStatsEndMillis = -1L;
+  public long getHidservStatsEndMillis() {
+    return this.hidservStatsEndMillis;
+  }
+
+  private long hidservStatsIntervalLength = -1L;
+  public long getHidservStatsIntervalLength() {
+    return this.hidservStatsIntervalLength;
+  }
+
+  private Double hidservRendRelayedCells;
+  public Double getHidservRendRelayedCells() {
+    return this.hidservRendRelayedCells;
+  }
+
+  private Map<String, Double> hidservRendRelayedCellsParameters;
+  public Map<String, Double> getHidservRendRelayedCellsParameters() {
+    return this.hidservRendRelayedCellsParameters == null ? null :
+        new HashMap<>(this.hidservRendRelayedCellsParameters);
+  }
+
+  private Double hidservDirOnionsSeen;
+  public Double getHidservDirOnionsSeen() {
+    return this.hidservDirOnionsSeen;
+  }
+
+  private Map<String, Double> hidservDirOnionsSeenParameters;
+  public Map<String, Double> getHidservDirOnionsSeenParameters() {
+    return this.hidservDirOnionsSeenParameters == null ? null :
+      new HashMap<>(this.hidservDirOnionsSeenParameters);
+  }
+
   private String routerSignature;
   public String getRouterSignature() {
     return this.routerSignature;
diff --git a/src/org/torproject/descriptor/impl/ParseHelper.java b/src/org/torproject/descriptor/impl/ParseHelper.java
index a354831..15de5ee 100644
--- a/src/org/torproject/descriptor/impl/ParseHelper.java
+++ b/src/org/torproject/descriptor/impl/ParseHelper.java
@@ -6,6 +6,7 @@ import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.SortedMap;
@@ -435,6 +436,39 @@ public class ParseHelper {
     return result;
   }
 
+  protected static Map<String, Double>
+      parseSpaceSeparatedStringKeyDoubleValueMap(String line,
+      String[] partsNoOpt, int startIndex)
+      throws DescriptorParseException {
+    Map<String, Double> result = new LinkedHashMap<>();
+    if (partsNoOpt.length < startIndex) {
+      throw new DescriptorParseException("Line '" + line + "' does not "
+          + "contain a key-value list starting at index " + startIndex
+          + ".");
+    }
+    for (int i = startIndex; i < partsNoOpt.length; i++) {
+      String listElement = partsNoOpt[i];
+      String[] keyAndValue = listElement.split("=");
+      String key = null;
+      Double value = null;
+      if (keyAndValue.length == 2) {
+        try {
+          value = Double.parseDouble(keyAndValue[1]);
+          key = keyAndValue[0];
+        } catch (NumberFormatException e) {
+          /* Handle below. */
+        }
+      }
+      if (key == null) {
+        throw new DescriptorParseException("Line '" + line + "' contains "
+            + "an illegal key or value in list element '" + listElement
+            + "'.");
+      }
+      result.put(key, value);
+    }
+    return result;
+  }
+
   public static String
       parseMasterKeyEd25519FromIdentityEd25519CryptoBlock(
       String identityEd25519CryptoBlock) throws DescriptorParseException {
diff --git a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
index d70ac39..55e0578 100644
--- a/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
+++ b/test/org/torproject/descriptor/impl/ExtraInfoDescriptorImplTest.java
@@ -138,6 +138,13 @@ public class ExtraInfoDescriptorImplTest {
       db.bridgeStatsLines = lines;
       return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
     }
+    private String hidservStatsLines = null;
+    private static ExtraInfoDescriptor createWithHidservStatsLines(
+        String lines) throws DescriptorParseException {
+      DescriptorBuilder db = new DescriptorBuilder();
+      db.hidservStatsLines = lines;
+      return new RelayExtraInfoDescriptorImpl(db.buildDescriptor(), true);
+    }
     private String unrecognizedLine = null;
     private static ExtraInfoDescriptor createWithUnrecognizedLine(
         String line, boolean failUnrecognizedDescriptorLines)
@@ -232,6 +239,9 @@ public class ExtraInfoDescriptorImplTest {
       if (this.bridgeStatsLines != null) {
         sb.append(this.bridgeStatsLines + "\n");
       }
+      if (this.hidservStatsLines != null) {
+        sb.append(this.hidservStatsLines + "\n");
+      }
       if (this.unrecognizedLine != null) {
         sb.append(this.unrecognizedLine + "\n");
       }
@@ -733,6 +743,62 @@ public class ExtraInfoDescriptorImplTest {
     }
   }
 
+  /* Helper class to build a set of hidserv-stats lines based on default
+   * data and modifications requested by test methods. */
+  private static class HidservStatsBuilder {
+    private String hidservStatsEndLine = "hidserv-stats-end 2015-12-03 "
+        + "14:26:56 (86400 s)";
+    private static ExtraInfoDescriptor createWithHidservStatsEndLine(
+        String line) throws DescriptorParseException {
+      HidservStatsBuilder hsb = new HidservStatsBuilder();
+      hsb.hidservStatsEndLine = line;
+      return DescriptorBuilder.createWithHidservStatsLines(
+          hsb.buildHidservStatsLines());
+    }
+    private String hidservRendRelayedCellsLine =
+        "hidserv-rend-relayed-cells 36474281 delta_f=2048 epsilon=0.30 "
+        + "bin_size=1024";
+    private static ExtraInfoDescriptor
+        createWithHidservRendRelayedCellsLine(String line)
+        throws DescriptorParseException {
+      HidservStatsBuilder hsb = new HidservStatsBuilder();
+      hsb.hidservRendRelayedCellsLine = line;
+      return DescriptorBuilder.createWithHidservStatsLines(
+          hsb.buildHidservStatsLines());
+    }
+    private String hidservDirOnionsSeenLine = "hidserv-dir-onions-seen "
+        + "-3 delta_f=8 epsilon=0.30 bin_size=8";
+    private static ExtraInfoDescriptor createWithHidservDirOnionsSeenLine(
+        String line) throws DescriptorParseException {
+      HidservStatsBuilder hsb = new HidservStatsBuilder();
+      hsb.hidservDirOnionsSeenLine = line;
+      return DescriptorBuilder.createWithHidservStatsLines(
+          hsb.buildHidservStatsLines());
+    }
+    private static ExtraInfoDescriptor createWithDefaultLines()
+        throws DescriptorParseException {
+      return DescriptorBuilder.createWithHidservStatsLines(
+          new HidservStatsBuilder().buildHidservStatsLines());
+    }
+    private String buildHidservStatsLines() {
+      StringBuilder sb = new StringBuilder();
+      if (this.hidservStatsEndLine != null) {
+        sb.append(this.hidservStatsEndLine + "\n");
+      }
+      if (this.hidservRendRelayedCellsLine != null) {
+        sb.append(this.hidservRendRelayedCellsLine + "\n");
+      }
+      if (this.hidservDirOnionsSeenLine != null) {
+        sb.append(this.hidservDirOnionsSeenLine + "\n");
+      }
+      String lines = sb.toString();
+      if (lines.endsWith("\n")) {
+        lines = lines.substring(0, lines.length() - 1);
+      }
+      return lines;
+    }
+  }
+
   @Test()
   public void testSampleDescriptor() throws DescriptorParseException {
     DescriptorBuilder db = new DescriptorBuilder();
@@ -1415,6 +1481,69 @@ public class ExtraInfoDescriptorImplTest {
   }
 
   @Test()
+  public void testHidservStatsValid() throws DescriptorParseException {
+    ExtraInfoDescriptor descriptor = HidservStatsBuilder.
+        createWithDefaultLines();
+    assertEquals(1449152816000L, descriptor.getHidservStatsEndMillis());
+    assertEquals(86400L, descriptor.getHidservStatsIntervalLength());
+    assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(),
+        0.0001);
+    Map<String, Double> params =
+        descriptor.getHidservRendRelayedCellsParameters();
+    assertTrue(params.containsKey("delta_f"));
+    assertEquals(2048.0, params.remove("delta_f"), 0.0001);
+    assertTrue(params.containsKey("epsilon"));
+    assertEquals(0.3, params.remove("epsilon"), 0.0001);
+    assertTrue(params.containsKey("bin_size"));
+    assertEquals(1024.0, params.remove("bin_size"), 0.0001);
+    assertTrue(params.isEmpty());
+    assertEquals(-3.0, descriptor.getHidservDirOnionsSeen(), 0.0001);
+    params = descriptor.getHidservDirOnionsSeenParameters();
+    assertTrue(params.containsKey("delta_f"));
+    assertEquals(8.0, params.remove("delta_f"), 0.0001);
+    assertTrue(params.containsKey("epsilon"));
+    assertEquals(0.3, params.remove("epsilon"), 0.0001);
+    assertTrue(params.containsKey("bin_size"));
+    assertEquals(8.0, params.remove("bin_size"), 0.0001);
+    assertTrue(params.isEmpty());
+  }
+
+  @Test()
+  public void testHidservStatsEndLineMissing()
+      throws DescriptorParseException {
+    ExtraInfoDescriptor descriptor =
+        HidservStatsBuilder.createWithHidservStatsEndLine(null);
+    assertEquals(-1L, descriptor.getHidservStatsEndMillis());
+    assertEquals(-1L, descriptor.getHidservStatsIntervalLength());
+  }
+
+  @Test()
+  public void testHidservRendRelayedCellsNoParams()
+        throws DescriptorParseException {
+    ExtraInfoDescriptor descriptor =
+        HidservStatsBuilder.createWithHidservRendRelayedCellsLine(
+        "hidserv-rend-relayed-cells 36474281");
+    assertEquals(36474281.0, descriptor.getHidservRendRelayedCells(),
+        0.0001);
+    assertTrue(
+        descriptor.getHidservRendRelayedCellsParameters().isEmpty());
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testHidservDirOnionsSeenCommaSeparatedParams()
+      throws DescriptorParseException {
+    HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
+        "hidserv-dir-onions-seen -3 delta_f=8,epsilon=0.30,bin_size=8");
+  }
+
+  @Test(expected = DescriptorParseException.class)
+  public void testHidservDirOnionsSeenNoDoubleParams()
+      throws DescriptorParseException {
+    HidservStatsBuilder.createWithHidservDirOnionsSeenLine(
+        "hidserv-dir-onions-seen -3 delta_f=A epsilon=B bin_size=C");
+  }
+
+  @Test()
   public void testRouterSignatureOpt()
       throws DescriptorParseException {
     DescriptorBuilder.createWithRouterSignatureLines("opt "





More information about the tor-commits mailing list