[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