[tor-commits] [stem/master] Validating params values and including defaults

atagar at torproject.org atagar at torproject.org
Sat Oct 13 18:35:45 UTC 2012


commit f60e60006a54ba2fc1eba0cb2fa5fade55b670ff
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Sep 12 09:14:30 2012 -0700

    Validating params values and including defaults
    
    The 'params' line of a network status document has several known entries, with
    their own constraints on the value. Validating that the document obeys those
    constraints.
    
    Also, the path-spec has default values for a handfull of params so optionally
    defaulting our params attribute to that.
---
 stem/descriptor/networkstatus.py               |   70 +++++++++++++++++++++---
 test/unit/descriptor/networkstatus/document.py |   29 +++++++---
 2 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index d43c1d0..12396d0 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -88,6 +88,19 @@ FOOTER_STATUS_DOCUMENT_FIELDS = (
   ("directory-signature", True, True, True),
 )
 
+DEFAULT_PARAMS = {
+  "cbtdisabled": 0,
+  "cbtnummodes": 3,
+  "cbtrecentcount": 20,
+  "cbtmaxtimeouts": 18,
+  "cbtmincircs": 100,
+  "cbtquantile": 80,
+  "cbtclosequantile": 95,
+  "cbttestfreq": 60,
+  "cbtmintimeout": 2000,
+  "cbtinitialtimeout": 60000,
+}
+
 def parse_file(document_file, validate = True, is_microdescriptor = False):
   """
   Parses a network status and iterates over the RouterStatusEntry or
@@ -206,12 +219,13 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
   | **~** attribute appears only in consensuses
   """
   
-  def __init__(self, raw_content, validate = True):
+  def __init__(self, raw_content, validate = True, default_params = True):
     """
     Parse a v3 network status document and provide a new NetworkStatusDocument object.
     
     :param str raw_content: raw network status document data
     :param bool validate: True if the document is to be validated, False otherwise
+    :param bool default_params: includes defaults in our params dict, otherwise it just contains values from the document
     
     :raises: ValueError if the document is invalid
     """
@@ -235,7 +249,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
     self.client_versions = []
     self.server_versions = []
     self.known_flags = []
-    self.params = {}
+    self.params = dict(DEFAULT_PARAMS) if default_params else {}
     self.bandwidth_weights = {}
     
     document_file = StringIO(raw_content)
@@ -394,6 +408,7 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
         # skip if this is a blank line
         if value == "": continue
         
+        seen_keys = []
         for entry in value.split(" "):
           try:
             if not '=' in entry:
@@ -411,19 +426,19 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
               raise ValueError("'%s' is a non-numeric value" % entry_value)
             
             if validate:
-              # check int32 range
-              if entry_value < -2147483648 or entry_value > 2147483647:
-                raise ValueError("values must be between -2147483648 and 2147483647")
-              
               # parameters should be in ascending order by their key
-              for prior_key in self.params:
+              for prior_key in seen_keys:
                 if prior_key > entry_key:
                   raise ValueError("parameters must be sorted by their key")
             
             self.params[entry_key] = entry_value
+            seen_keys.append(entry_key)
           except ValueError, exc:
             if not validate: continue
             raise ValueError("Unable to parse network status document's 'params' line (%s): %s'" % (exc, line))
+        
+        if validate:
+          self._check_params_constraints()
     
     # doing this validation afterward so we know our 'is_consensus' and
     # 'is_vote' attributes
@@ -544,6 +559,47 @@ class NetworkStatusDocument(stem.descriptor.Descriptor):
         actual_label = ', '.join(actual)
         expected_label = ', '.join(expected)
         raise ValueError("The fields in the document's %s are misordered. It should be '%s' but was '%s'" % (label, actual_label, expected_label))
+  
+  def _check_params_constraints(self):
+    """
+    Checks that the params we know about are within their documented ranges.
+    """
+    
+    for key, value in self.params.items():
+      # all parameters are constrained to int32 range
+      minimum, maximum = -2147483648, 2147483647
+      
+      if key == "circwindow":
+        minimum, maximum = 100, 1000
+      elif key == "CircuitPriorityHalflifeMsec":
+        minimum = -1
+      elif key in ("perconnbwrate", "perconnbwburst"):
+        minimum = 1
+      elif key == "refuseunknownexits":
+        minimum, maximum = 0, 1
+      elif key == "cbtdisabled":
+        minimum, maximum = 0, 1
+      elif key == "cbtnummodes":
+        minimum, maximum = 1, 20
+      elif key == "cbtrecentcount":
+        minimum, maximum = 3, 1000
+      elif key == "cbtmaxtimeouts":
+        minimum, maximum = 3, 10000
+      elif key == "cbtmincircs":
+        minimum, maximum = 1, 10000
+      elif key == "cbtquantile":
+        minimum, maximum = 10, 99
+      elif key == "cbtclosequantile":
+        minimum, maximum = self.params.get("cbtquantile", minimum), 99
+      elif key == "cbttestfreq":
+        minimum = 1
+      elif key == "cbtmintimeout":
+        minimum = 500
+      elif key == "cbtinitialtimeout":
+        minimum = self.params.get("cbtmintimeout", minimum)
+      
+      if value < minimum or value > maximum:
+        raise ValueError("'%s' value on the params line must be in the range of %i - %i, was %i" % (key, minimum, maximum, value))
 
 class DirectoryAuthority(stem.descriptor.Descriptor):
   """
diff --git a/test/unit/descriptor/networkstatus/document.py b/test/unit/descriptor/networkstatus/document.py
index 01f08bf..1fa932a 100644
--- a/test/unit/descriptor/networkstatus/document.py
+++ b/test/unit/descriptor/networkstatus/document.py
@@ -7,7 +7,7 @@ import unittest
 
 import stem.version
 from stem.descriptor import Flag
-from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, NetworkStatusDocument, DirectorySignature
+from stem.descriptor.networkstatus import HEADER_STATUS_DOCUMENT_FIELDS, FOOTER_STATUS_DOCUMENT_FIELDS, DEFAULT_PARAMS, NetworkStatusDocument, DirectorySignature
 
 NETWORK_STATUS_DOCUMENT_ATTR = {
   "network-status-version": "3",
@@ -116,7 +116,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
     self.assertEqual([], document.client_versions)
     self.assertEqual([], document.server_versions)
     self.assertEqual(expected_known_flags, document.known_flags)
-    self.assertEqual({}, document.params)
+    self.assertEqual(DEFAULT_PARAMS, document.params)
     self.assertEqual([], document.directory_authorities)
     self.assertEqual(None, document.bandwidth_weights)
     self.assertEqual([sig], document.directory_signatures)
@@ -150,7 +150,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
     self.assertEqual([], document.client_versions)
     self.assertEqual([], document.server_versions)
     self.assertEqual(expected_known_flags, document.known_flags)
-    self.assertEqual({}, document.params)
+    self.assertEqual(DEFAULT_PARAMS, document.params)
     self.assertEqual([], document.directory_authorities)
     self.assertEqual({}, document.bandwidth_weights)
     self.assertEqual([sig], document.directory_signatures)
@@ -455,7 +455,11 @@ class TestNetworkStatusDocument(unittest.TestCase):
     
     # empty params line
     content = get_network_status_document({"params": ""})
-    document = NetworkStatusDocument(content)
+    document = NetworkStatusDocument(content, default_params = True)
+    self.assertEquals(DEFAULT_PARAMS, document.params)
+    
+    content = get_network_status_document({"params": ""})
+    document = NetworkStatusDocument(content, default_params = False)
     self.assertEquals({}, document.params)
   
   def test_params_malformed(self):
@@ -475,7 +479,7 @@ class TestNetworkStatusDocument(unittest.TestCase):
       self.assertRaises(ValueError, NetworkStatusDocument, content)
       
       document = NetworkStatusDocument(content, False)
-      self.assertEquals({}, document.params)
+      self.assertEquals(DEFAULT_PARAMS, document.params)
   
   def test_params_range(self):
     """
@@ -488,16 +492,25 @@ class TestNetworkStatusDocument(unittest.TestCase):
       ("foo=-2147483649", {"foo": -2147483649}, False),
       ("foo=2147483647", {"foo": 2147483647}, True),
       ("foo=-2147483648", {"foo": -2147483648}, True),
+      
+      # param with special range constraints
+      ("circwindow=99", {"circwindow": 99}, False),
+      ("circwindow=1001", {"circwindow": 1001}, False),
+      ("circwindow=500", {"circwindow": 500}, True),
+      
+      # param that relies on another param for its constraints
+      ("cbtclosequantile=79 cbtquantile=80", {"cbtclosequantile": 79, "cbtquantile": 80}, False),
+      ("cbtclosequantile=80 cbtquantile=80", {"cbtclosequantile": 80, "cbtquantile": 80}, True),
     )
     
     for test_value, expected_value, is_ok in test_values:
       content = get_network_status_document({"params": test_value})
       
       if is_ok:
-        document = NetworkStatusDocument(content)
+        document = NetworkStatusDocument(content, default_params = False)
       else:
         self.assertRaises(ValueError, NetworkStatusDocument, content)
-        document = NetworkStatusDocument(content, False)
+        document = NetworkStatusDocument(content, False, default_params = False)
       
       self.assertEquals(expected_value, document.params)
   
@@ -509,6 +522,6 @@ class TestNetworkStatusDocument(unittest.TestCase):
     content = get_network_status_document({"params": "unrecognized=-122 bwauthpid=1"})
     self.assertRaises(ValueError, NetworkStatusDocument, content)
     
-    document = NetworkStatusDocument(content, False)
+    document = NetworkStatusDocument(content, False, default_params = False)
     self.assertEquals({"unrecognized": -122, "bwauthpid": 1}, document.params)
 





More information about the tor-commits mailing list