tor-commits
Threads by month
- ----- 2026 -----
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 216132 discussions
commit 1d2d684e3975e0e55369485b3d75155e90ea6aef
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Nov 5 08:36:05 2012 -0800
BW event unit tests
Adding a few simple unit tests for the BandwidthEvent class.
---
stem/response/events.py | 8 ++++++--
test/unit/response/events.py | 38 +++++++++++++++++++++++++++++++++++---
2 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/stem/response/events.py b/stem/response/events.py
index 7a2f9da..fa451c7 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -81,8 +81,12 @@ class BandwidthEvent(Event):
_POSITIONAL_ARGS = ("read", "written")
def _parse(self):
- if (self.read and not self.read.isdigit()) or (self.written and not self.written.isdigit()):
- raise stem.socket.ProtocolError("A BW event's bytes sent and received values should be numeric, received: %s" % self)
+ if not self.read:
+ raise stem.socket.ProtocolError("BW event is missing its read value")
+ elif not self.written:
+ raise stem.socket.ProtocolError("BW event is missing its written value")
+ elif not self.read.isdigit() or not self.written.isdigit():
+ raise stem.socket.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
self.read = long(self.read)
self.written = long(self.written)
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index b1111bc..1dea7c7 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -6,8 +6,16 @@ import threading
import unittest
import stem.response
+import stem.response.events
import test.mocking as mocking
+from stem.socket import ProtocolError
+
+def _get_event(content):
+ controller_event = mocking.get_message(content)
+ stem.response.convert("EVENT", controller_event, arrived_at = 25)
+ return controller_event
+
class TestEvents(unittest.TestCase):
def test_example(self):
"""
@@ -23,9 +31,7 @@ class TestEvents(unittest.TestCase):
def event_sender():
for i in xrange(3):
- controller_event = mocking.get_message("650 BW 15 25")
- stem.response.convert("EVENT", controller_event, arrived_at = 25)
- print_bw(controller_event)
+ print_bw(_get_event("650 BW 15 25"))
time.sleep(0.05)
controller = mocking.get_object(Controller, {
@@ -40,4 +46,30 @@ class TestEvents(unittest.TestCase):
events_thread.start()
time.sleep(0.2)
events_thread.join()
+
+ def test_bw_event(self):
+ event = _get_event("650 BW 15 25")
+
+ self.assertTrue(isinstance(event, stem.response.events.BandwidthEvent))
+ self.assertEqual(15, event.read)
+ self.assertEqual(25, event.written)
+
+ event = _get_event("650 BW 0 0")
+ self.assertEqual(0, event.read)
+ self.assertEqual(0, event.written)
+
+ # BW events are documented as possibly having various keywords including
+ # DIR, OR, EXIT, and APP in the future. This is kinda a pointless note
+ # since tor doesn't actually do it yet (and likely never will), but might
+ # as well sanity test that it'll be ok.
+
+ event = _get_event("650 BW 10 20 OR=5 EXIT=500")
+ self.assertEqual(10, event.read)
+ self.assertEqual(20, event.written)
+ self.assertEqual({'OR': '5', 'EXIT': '500'}, event.keyword_args)
+
+ self.assertRaises(ProtocolError, _get_event, "650 BW 15")
+ self.assertRaises(ProtocolError, _get_event, "650 BW -15 25")
+ self.assertRaises(ProtocolError, _get_event, "650 BW 15 -25")
+ self.assertRaises(ProtocolError, _get_event, "650 BW x 25")
1
0
commit 7ba579494215a5353cff1db9fbf7e59d6b666af9
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Nov 5 09:14:07 2012 -0800
Support for logging events
Adding an event class for DEBUG, INFO, NOTICE, WARN, and ERR.
---
docs/api/response.rst | 1 +
stem/control.py | 15 ++++++++++-----
stem/response/events.py | 27 +++++++++++++++++++++++++++
test/unit/response/events.py | 19 +++++++++++++++++++
4 files changed, 57 insertions(+), 5 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index 6e23c91..fd6e2cc 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -16,5 +16,6 @@ Events
------
.. autoclass:: stem.response.events.Event
+.. autoclass:: stem.response.events.LogEvent
.. autoclass:: stem.response.events.BandwidthEvent
diff --git a/stem/control.py b/stem/control.py
index f9f7def..baeb96e 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -590,11 +590,16 @@ class Controller(BaseController):
The EventType enumeration is mapped to event classes as follows...
- ========= ===========
- EventType Event Class
- ========= ===========
- **BW** :class:`stem.response.events.BandwidthEvent`
- ========= ===========
+ =========== ===========
+ EventType Event Class
+ =========== ===========
+ **DEBUG** :class:`stem.response.events.LogEvent`
+ **INFO** :class:`stem.response.events.LogEvent`
+ **NOTICE** :class:`stem.response.events.LogEvent`
+ **WARN** :class:`stem.response.events.LogEvent`
+ **ERR** :class:`stem.response.events.LogEvent`
+ **BW** :class:`stem.response.events.BandwidthEvent`
+ =========== ===========
:param functor listener: function to be called when an event is received
:param stem.control.EventType events: event types to be listened for
diff --git a/stem/response/events.py b/stem/response/events.py
index fa451c7..93c3191 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -14,6 +14,11 @@ class Event(stem.response.ControlMessage):
Base for events we receive asynchronously, as described in section 4.1 of the
`control-spec
<https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_.
+
+ :var str type: event type
+ :var int arrived_at: unix timestamp for when the message arrived
+ :var list positional_args: positional arguments of the event
+ :var dict keyword_args: key/value arguments of the event
"""
_POSITIONAL_ARGS = ()
@@ -91,7 +96,29 @@ class BandwidthEvent(Event):
self.read = long(self.read)
self.written = long(self.written)
+class LogEvent(Event):
+ """
+ Tor logging event. These are the most visible kind of event since, by
+ default, tor logs at the NOTICE runlevel to stdout.
+
+ :var str runlevel: runlevel of the logged message
+ :var str message: logged message
+ """
+
+ def _parse(self):
+ self.runlevel = self.type
+
+ # message is our content, minus the runlevel and ending "OK" if a
+ # multi-line message
+
+ self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
+
EVENT_TYPE_TO_CLASS = {
"BW": BandwidthEvent,
+ "DEBUG": LogEvent,
+ "INFO": LogEvent,
+ "NOTICE": LogEvent,
+ "WARN": LogEvent,
+ "ERR": LogEvent,
}
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 1dea7c7..1942b69 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -72,4 +72,23 @@ class TestEvents(unittest.TestCase):
self.assertRaises(ProtocolError, _get_event, "650 BW -15 25")
self.assertRaises(ProtocolError, _get_event, "650 BW 15 -25")
self.assertRaises(ProtocolError, _get_event, "650 BW x 25")
+
+ def test_log_events(self):
+ event = _get_event("650 DEBUG connection_edge_process_relay_cell(): Got an extended cell! Yay.")
+
+ self.assertTrue(isinstance(event, stem.response.events.LogEvent))
+ self.assertEqual("DEBUG", event.runlevel)
+ self.assertEqual("connection_edge_process_relay_cell(): Got an extended cell! Yay.", event.message)
+
+ event = _get_event("650 INFO circuit_finish_handshake(): Finished building circuit hop:")
+
+ self.assertTrue(isinstance(event, stem.response.events.LogEvent))
+ self.assertEqual("INFO", event.runlevel)
+ self.assertEqual("circuit_finish_handshake(): Finished building circuit hop:", event.message)
+
+ event = _get_event("650+WARN\na multi-line\nwarning message\n.\n650 OK\n")
+
+ self.assertTrue(isinstance(event, stem.response.events.LogEvent))
+ self.assertEqual("WARN", event.runlevel)
+ self.assertEqual("a multi-line\nwarning message", event.message)
1
0
commit 1d56eb59e256977d34f9d3d77e4e0b8c2df49f86
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Nov 5 08:21:30 2012 -0800
EventType to Event subclass table
Adding a table that'll list the EventType enumerations and map them to Event
subclasses.
---
docs/api/response.rst | 6 ++++++
stem/control.py | 18 ++++++++++++++++--
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index bf3b722..6e23c91 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -12,3 +12,9 @@ Responses
.. autoclass:: stem.response.mapaddress.MapAddressResponse
.. autoclass:: stem.response.protocolinfo.ProtocolInfoResponse
+Events
+------
+
+.. autoclass:: stem.response.events.Event
+.. autoclass:: stem.response.events.BandwidthEvent
+
diff --git a/stem/control.py b/stem/control.py
index c2a94aa..f9f7def 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -543,6 +543,15 @@ class Controller(BaseController):
self._event_listeners = {}
self._event_listeners_lock = threading.RLock()
+ # TODO: We want the capability of taking post-authentication actions, for
+ # instance to call SETEVENTS so our event listeners will work on new
+ # connections. The trouble is that the user could do this by a variety of
+ # methods (authenticate(), msg(), stem.connection.authenticete(), etc)...
+ #
+ # When we get it figured out we should add the pydoc comment:
+ # If a new control connection is initialized then this listener will be
+ # reattached.
+
# number of sequential 'GETINFO ip-to-country/*' lookups that have failed
self._geoip_failure_count = 0
self.enabled_features = []
@@ -579,8 +588,13 @@ class Controller(BaseController):
controller.add_event_listener(print_bw, EventType.BW)
time.sleep(5)
- If a new control connection is initialized then this listener will be
- reattached.
+ The EventType enumeration is mapped to event classes as follows...
+
+ ========= ===========
+ EventType Event Class
+ ========= ===========
+ **BW** :class:`stem.response.events.BandwidthEvent`
+ ========= ===========
:param functor listener: function to be called when an event is received
:param stem.control.EventType events: event types to be listened for
1
0
commit d7dc386533f0b7885f2966922b9cb051c1b89384
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 11 12:36:37 2012 -0800
Enum docs for stem.control.EventType
I had been doing a table for the EventType enum under the add_event_listener()
method. This made sense and matched with what I'm doing for enumeration
documentation except that this should be a interlinkable :data: field in our
header.
---
stem/control.py | 45 +++++++++++++++++++++++++--------------------
1 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 846aba7..1c1139f 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -54,13 +54,31 @@ providing its own for interacting at a higher level.
Enumeration for states that a controller can have.
- ====== ===========
- State Description
- ====== ===========
- INIT new control connection
- RESET received a reset/sighup signal
- CLOSED control connection closed
- ====== ===========
+ ========== ===========
+ State Description
+ ========== ===========
+ **INIT** new control connection
+ **RESET** received a reset/sighup signal
+ **CLOSED** control connection closed
+ ========== ===========
+
+.. data:: EventType (enum)
+
+ Known types of events that the
+ :func:`~stem.control.Controller.add_event_listener` method of the
+ :class:`~stem.control.Controller` can listen for. Enums are mapped to
+ :class:`~stem.response.events.Event` subclasses as follows...
+
+ =========== ===========
+ EventType Event Class
+ =========== ===========
+ **DEBUG** :class:`stem.response.events.LogEvent`
+ **INFO** :class:`stem.response.events.LogEvent`
+ **NOTICE** :class:`stem.response.events.LogEvent`
+ **WARN** :class:`stem.response.events.LogEvent`
+ **ERR** :class:`stem.response.events.LogEvent`
+ **BW** :class:`stem.response.events.BandwidthEvent`
+ =========== ===========
"""
from __future__ import with_statement
@@ -596,19 +614,6 @@ class Controller(BaseController):
controller.add_event_listener(print_bw, EventType.BW)
time.sleep(5)
- The EventType enumeration is mapped to event classes as follows...
-
- =========== ===========
- EventType Event Class
- =========== ===========
- **DEBUG** :class:`stem.response.events.LogEvent`
- **INFO** :class:`stem.response.events.LogEvent`
- **NOTICE** :class:`stem.response.events.LogEvent`
- **WARN** :class:`stem.response.events.LogEvent`
- **ERR** :class:`stem.response.events.LogEvent`
- **BW** :class:`stem.response.events.BandwidthEvent`
- =========== ===========
-
:param functor listener: function to be called when an event is received
:param stem.control.EventType events: event types to be listened for
1
0
commit a349a01fece5534195a6619d72df4944065001b8
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Nov 17 18:49:23 2012 -0800
Support for CIRC events
Implementation and testing for CIRC events. This work also concerns the
'GETINFO circuit-status' method, which is defined as providing the same output
as CIRC events. This is part of the reason why I put the enums for the event
attributes in 'stem.control'.
---
docs/api/response.rst | 1 +
run_tests.py | 2 +
stem/control.py | 227 +++++++++++++++++++++++++++++++++++++++
stem/response/events.py | 92 +++++++++++++++-
stem/util/str_tools.py | 36 ++++++
stem/util/tor_tools.py | 18 +++
test/unit/__init__.py | 1 +
test/unit/control/__init__.py | 6 +
test/unit/control/controller.py | 50 +++++++++
test/unit/response/events.py | 124 +++++++++++++++++++++-
test/unit/util/str_tools.py | 31 ++++++
11 files changed, 582 insertions(+), 6 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index fd6e2cc..043cae4 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -18,4 +18,5 @@ Events
.. autoclass:: stem.response.events.Event
.. autoclass:: stem.response.events.LogEvent
.. autoclass:: stem.response.events.BandwidthEvent
+.. autoclass:: stem.response.events.CircuitEvent
diff --git a/run_tests.py b/run_tests.py
index f490da7..a9aebbe 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -16,6 +16,7 @@ import test.output
import test.runner
import test.check_whitespace
import test.unit.connection.authentication
+import test.unit.control.controller
import test.unit.descriptor.export
import test.unit.descriptor.reader
import test.unit.descriptor.server_descriptor
@@ -142,6 +143,7 @@ UNIT_TESTS = (
test.unit.response.protocolinfo.TestProtocolInfoResponse,
test.unit.response.authchallenge.TestAuthChallengeResponse,
test.unit.connection.authentication.TestAuthenticate,
+ test.unit.control.controller.TestControl,
)
INTEG_TESTS = (
diff --git a/stem/control.py b/stem/control.py
index 1c1139f..083082e 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -78,7 +78,110 @@ providing its own for interacting at a higher level.
**WARN** :class:`stem.response.events.LogEvent`
**ERR** :class:`stem.response.events.LogEvent`
**BW** :class:`stem.response.events.BandwidthEvent`
+ **CIRC** :class:`stem.response.events.CircuitEvent`
=========== ===========
+
+.. data:: CircStatus (enum)
+
+ Statuses that a circuit can be in. Tor may provide statuses not in this enum.
+
+ ============ ===========
+ CircStatus Description
+ ============ ===========
+ **LAUNCHED** new circuit was created
+ **BUILT** circuit finished being created and can accept traffic
+ **EXTENDED** circuit has been extended by a hop
+ **FAILED** circuit construction failed
+ **CLOSED** circuit has been closed
+ ============ ===========
+
+.. data:: CircBuildFlag (enum)
+
+ Attributes about how a circuit is built. These were introduced in tor version
+ 0.2.3.11. Tor may provide flags not in this enum.
+
+ ================= ===========
+ CircBuildFlag Description
+ ================= ===========
+ **ONEHOP_TUNNEL** single hop circuit to fetch directory information
+ **IS_INTERNAL** circuit that won't be used for client traffic
+ **NEED_CAPACITY** circuit only includes high capacity relays
+ **NEED_UPTIME** circuit only includes relays with a high uptime
+ ================= ===========
+
+.. data:: CircPurpose (enum)
+
+ Description of what a circuit is intended for. These were introduced in tor
+ version 0.2.1.6. Tor may provide purposes not in this enum.
+
+ ==================== ===========
+ CircPurpose Description
+ ==================== ===========
+ **GENERAL** client traffic or fetching directory information
+ **HS_CLIENT_INTRO** client side introduction point for a hidden service circuit
+ **HS_CLIENT_REND** client side hidden service rendezvous circuit
+ **HS_SERVICE_INTRO** server side introduction point for a hidden service circuit
+ **HS_SERVICE_REND** server side hidden service rendezvous circuit
+ **TESTING** testing to see if we're reachable, so we can be used as a relay
+ **CONTROLLER** circuit that was built by a controller
+ ==================== ===========
+
+.. data:: CircClosureReason (enum)
+
+ Reason that a circuit is being closed or failed to be established. Tor may
+ provide purposes not in this enum.
+
+ ========================= ===========
+ CircClosureReason Description
+ ========================= ===========
+ **NONE** no reason given
+ **TORPROTOCOL** violation in the tor protocol
+ **INTERNAL** internal error
+ **REQUESTED** requested by the client via a TRUNCATE command
+ **HIBERNATING** relay is presently hibernating
+ **RESOURCELIMIT** relay is out of memory, sockets, or circuit IDs
+ **CONNECTFAILED** unable to contact the relay
+ **OR_IDENTITY** relay had the wrong OR identification
+ **OR_CONN_CLOSED** connection failed after being established
+ **FINISHED** circuit has expired (see tor's MaxCircuitDirtiness config option)
+ **TIMEOUT** circuit construction timed out
+ **DESTROYED** circuit unexpectedly closed
+ **NOPATH** not enough relays to make a circuit
+ **NOSUCHSERVICE** requested hidden service does not exist
+ **MEASUREMENT_EXPIRED** unknown (https://trac.torproject.org/7506)
+ ========================= ===========
+
+.. data:: HiddenServiceState (enum)
+
+ State that a hidden service circuit can have. These were introduced in tor
+ version 0.2.3.11. Tor may provide states not in this enum.
+
+ Enumerations fall into four groups based on their prefix...
+
+ ======= ===========
+ Prefix Description
+ ======= ===========
+ HSCI_* client-side introduction-point
+ HSCR_* client-side rendezvous-point
+ HSSI_* service-side introduction-point
+ HSSR_* service-side rendezvous-point
+ ======= ===========
+
+ ============================= ===========
+ HiddenServiceState Description
+ ============================= ===========
+ **HSCI_CONNECTING** connecting to the introductory point
+ **HSCI_INTRO_SENT** sent INTRODUCE1 and awaiting a reply
+ **HSCI_DONE** received a reply, circuit is closing
+ **HSCR_CONNECTING** connecting to the introductory point
+ **HSCR_ESTABLISHED_IDLE** rendezvous-point established, awaiting an introduction
+ **HSCR_ESTABLISHED_WAITING** introduction received, awaiting a rend
+ **HSCR_JOINED** connected to the hidden service
+ **HSSI_CONNECTING** connecting to the introductory point
+ **HSSI_ESTABLISHED** established introductory point
+ **HSSR_CONNECTING** connecting to the introductory point
+ **HSSR_JOINED** connected to the rendezvous-point
+ ============================= ===========
"""
from __future__ import with_statement
@@ -129,6 +232,63 @@ EventType = stem.util.enum.UppercaseEnum(
"CIRC_MINOR",
)
+CircStatus = stem.util.enum.UppercaseEnum(
+ "LAUNCHED",
+ "BUILT",
+ "EXTENDED",
+ "FAILED",
+ "CLOSED",
+)
+
+CircBuildFlag = stem.util.enum.UppercaseEnum(
+ "ONEHOP_TUNNEL",
+ "IS_INTERNAL",
+ "NEED_CAPACITY",
+ "NEED_UPTIME",
+)
+
+CircPurpose = stem.util.enum.UppercaseEnum(
+ "GENERAL",
+ "HS_CLIENT_INTRO",
+ "HS_CLIENT_REND",
+ "HS_SERVICE_INTRO",
+ "HS_SERVICE_REND",
+ "TESTING",
+ "CONTROLLER",
+)
+
+CircClosureReason = stem.util.enum.UppercaseEnum(
+ "NONE",
+ "TORPROTOCOL",
+ "INTERNAL",
+ "REQUESTED",
+ "HIBERNATING",
+ "RESOURCELIMIT",
+ "CONNECTFAILED",
+ "OR_IDENTITY",
+ "OR_CONN_CLOSED",
+ "FINISHED",
+ "TIMEOUT",
+ "DESTROYED",
+ "NOPATH",
+ "NOSUCHSERVICE",
+ "MEASUREMENT_EXPIRED",
+)
+
+HiddenServiceState = stem.util.enum.UppercaseEnum(
+ "HSCI_CONNECTING",
+ "HSCI_INTRO_SENT",
+ "HSCI_DONE",
+ "HSCR_CONNECTING",
+ "HSCR_ESTABLISHED_IDLE",
+ "HSCR_ESTABLISHED_WAITING",
+ "HSCR_JOINED",
+ "HSSI_CONNECTING",
+ "HSSI_ESTABLISHED",
+ "HSSR_CONNECTING",
+ "HSSR_JOINED",
+)
+
# Constant to indicate an undefined argument default. Usually we'd use None for
# this, but users will commonly provide None as the argument so need something
# else fairly unique...
@@ -521,6 +681,9 @@ class Controller(BaseController):
BaseController and provides a more user friendly API for library users.
"""
+ # TODO: We need a set_up() (and maybe tear_down()?) method, so we can
+ # reattach listeners and set VERBOSE_NAMES.
+
def from_port(control_addr = "127.0.0.1", control_port = 9051):
"""
Constructs a :class:`~stem.socket.ControlPort` based Controller.
@@ -1462,6 +1625,70 @@ class Controller(BaseController):
for listener in event_listeners:
listener(event_message)
+def _parse_circ_path(path):
+ """
+ Parses a circuit path as a list of **(fingerprint, nickname)** tuples. Tor
+ circuit paths are defined as being of the form...
+
+ ::
+
+ Path = LongName *("," LongName)
+ LongName = Fingerprint [ ( "=" / "~" ) Nickname ]
+
+ example:
+ $999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz
+
+ ... *unless* this is prior to tor version 0.2.2.1 with the VERBOSE_NAMES
+ feature turned off (or before version 0.1.2.2 where the feature was
+ introduced). In that case either the fingerprint or nickname in the tuple
+ will be **None**, depending on which is missing.
+
+ ::
+
+ Path = ServerID *("," ServerID)
+ ServerID = Nickname / Fingerprint
+
+ example:
+ $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14
+
+ :param str path: circuit path to be parsed
+
+ :returns: list of **(fingerprint, nickname)** tuples, fingerprints do not have a proceeding '$'
+
+ :raises: :class:`stem.ProtocolError` if the path is malformed
+ """
+
+ if not path: return []
+
+ circ_path = []
+
+ for path_component in path.split(','):
+ if '=' in path_component:
+ # common case
+ fingerprint, nickname = path_component.split('=')
+ elif '~' in path_component:
+ # this is allowed for by the spec, but I've never seen it used
+ fingerprint, nickname = path_component.split('~')
+ elif path_component[0] == '$':
+ # old style, fingerprint only
+ fingerprint, nickname = path_component, None
+ else:
+ # old style, nickname only
+ fingerprint, nickname = None, path_component
+
+ if fingerprint != None:
+ if not stem.util.tor_tools.is_valid_fingerprint(fingerprint, True):
+ raise stem.ProtocolError("Fingerprint in the circuit path is malformed (%s): %s" % (fingerprint, path))
+
+ fingerprint = fingerprint[1:] # strip off the leading '$'
+
+ if nickname != None and not stem.util.tor_tools.is_valid_nickname(nickname):
+ raise stem.ProtocolError("Nickname in the circuit path is malformed (%s): %s" % (fingerprint, path))
+
+ circ_path.append((fingerprint, nickname))
+
+ return circ_path
+
def _case_insensitive_lookup(entries, key, default = UNDEFINED):
"""
Makes a case insensitive lookup within a list or dictionary, providing the
diff --git a/stem/response/events.py b/stem/response/events.py
index 93c3191..4c8d8bd 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -1,7 +1,9 @@
import re
+import stem.control
import stem.response
-import stem.socket
+
+from stem.util import log, str_tools, tor_tools
# Matches keyword=value arguments. This can't be a simple "(.*)=(.*)" pattern
# because some positional arguments, like circuit paths, can have an equal
@@ -28,7 +30,7 @@ class Event(stem.response.ControlMessage):
fields = str(self).split()
if not fields:
- raise stem.socket.ProtocolError("Received a blank tor event. Events must at the very least have a type.")
+ raise stem.ProtocolError("Received a blank tor event. Events must at the very least have a type.")
self.type = fields.pop(0)
self.arrived_at = arrived_at
@@ -75,6 +77,85 @@ class Event(stem.response.ControlMessage):
def _parse(self):
pass
+class CircuitEvent(Event):
+ """
+ Event that indicates that a circuit has changed.
+
+ The fingerprint or nickname values in our path may be **None** if the
+ VERBOSE_NAMES feature is unavailable. The option was first introduced in tor
+ version 0.1.2.2.
+
+ :var str id: circuit identifier
+ :var stem.control.CircStatus status: reported status for the circuit
+ :var tuple path: relays involved in the circuit, these are
+ **(fingerprint, nickname)** tuples
+ :var tuple build_flags: :data:`~stem.control.CircBuildFlag` attributes
+ governing how the circuit is built
+ :var stem.control.CircPurpose purpose: purpose that the circuit is intended for
+ :var stem.control.HiddenServiceState hs_state: status if this is a hidden service circuit
+ :var str rend_query: circuit's rendezvous-point if this is hidden service related
+ :var datetime created: time when the circuit was created or cannibalized
+ :var stem.control.CircClosureReason reason: reason for the circuit to be closed
+ :var stem.control.CircClosureReason remote_reason: remote side's reason for the circuit to be closed
+ """
+
+ _POSITIONAL_ARGS = ("id", "status", "path")
+ _KEYWORD_ARGS = {
+ "BUILD_FLAGS": "build_flags",
+ "PURPOSE": "purpose",
+ "HS_STATE": "hs_state",
+ "REND_QUERY": "rend_query",
+ "TIME_CREATED": "created",
+ "REASON": "reason",
+ "REMOTE_REASON": "remote_reason",
+ }
+
+ def _parse(self):
+ self.path = tuple(stem.control._parse_circ_path(self.path))
+
+ if self.build_flags != None:
+ self.build_flags = tuple(self.build_flags.split(','))
+
+ if self.created != None:
+ try:
+ self.created = str_tools.parse_iso_timestamp(self.created)
+ except ValueError, exc:
+ raise stem.ProtocolError("Unable to parse create date (%s): %s" % (exc, self))
+
+ if self.id != None and not tor_tools.is_valid_circuit_id(self.id):
+ raise stem.ProtocolError("Circuit IDs must be one to sixteen alphanumeric characters, got '%s': %s" % (self.id, self))
+
+ # log if we have an unrecognized status, build flag, purpose, hidden
+ # service state, or closure reason
+
+ unrecognized_msg = "CIRC event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+
+ if self.status and (not self.status in stem.control.CircStatus):
+ log_id = "event.circ.unknown_status.%s" % self.status
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('status', self.status))
+
+ if self.build_flags:
+ for flag in self.build_flags:
+ if not flag in stem.control.CircBuildFlag:
+ log_id = "event.circ.unknown_build_flag.%s" % flag
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('build flag', flag))
+
+ if self.purpose and (not self.purpose in stem.control.CircPurpose):
+ log_id = "event.circ.unknown_purpose.%s" % self.purpose
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
+
+ if self.hs_state and (not self.hs_state in stem.control.HiddenServiceState):
+ log_id = "event.circ.unknown_hs_state.%s" % self.hs_state
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('hidden service state', self.hs_state))
+
+ if self.reason and (not self.reason in stem.control.CircClosureReason):
+ log_id = "event.circ.unknown_reason.%s" % self.reason
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
+
+ if self.remote_reason and (not self.remote_reason in stem.control.CircClosureReason):
+ log_id = "event.circ.unknown_remote_reason.%s" % self.remote_reason
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
+
class BandwidthEvent(Event):
"""
Event emitted every second with the bytes sent and received by tor.
@@ -87,11 +168,11 @@ class BandwidthEvent(Event):
def _parse(self):
if not self.read:
- raise stem.socket.ProtocolError("BW event is missing its read value")
+ raise stem.ProtocolError("BW event is missing its read value")
elif not self.written:
- raise stem.socket.ProtocolError("BW event is missing its written value")
+ raise stem.ProtocolError("BW event is missing its written value")
elif not self.read.isdigit() or not self.written.isdigit():
- raise stem.socket.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
+ raise stem.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
self.read = long(self.read)
self.written = long(self.written)
@@ -114,6 +195,7 @@ class LogEvent(Event):
self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
EVENT_TYPE_TO_CLASS = {
+ "CIRC": CircuitEvent,
"BW": BandwidthEvent,
"DEBUG": LogEvent,
"INFO": LogEvent,
diff --git a/stem/util/str_tools.py b/stem/util/str_tools.py
index f444760..6c36cd0 100644
--- a/stem/util/str_tools.py
+++ b/stem/util/str_tools.py
@@ -11,8 +11,12 @@ Toolkit for various string activity.
get_time_labels - human readable labels for each time unit
get_short_time_label - condensed time label output
parse_short_time_label - seconds represented by a short time label
+
+ parse_iso_timestamp - parses an ISO timestamp as a datetime value
"""
+import datetime
+
# label conversion tuples of the form...
# (bits / bytes / seconds, short label, long label)
SIZE_UNITS_BITS = (
@@ -235,6 +239,38 @@ def parse_short_time_label(label):
except ValueError:
raise ValueError("Non-numeric value in time entry: %s" % label)
+def parse_iso_timestamp(entry):
+ """
+ Parses the ISO 8601 standard that provides for timestamps like...
+
+ ::
+
+ 2012-11-08T16:48:41.420251
+
+ :param str entry: timestamp to be parsed
+
+ :returns: datetime for the time represented by the timestamp
+
+ :raises: ValueError if the timestamp is malformed
+ """
+
+ if not isinstance(entry, str):
+ raise ValueError("parse_iso_timestamp() input must be a str, got a %s" % type(entry))
+
+ # based after suggestions from...
+ # http://stackoverflow.com/questions/127803/how-to-parse-iso-formatted-date-i…
+
+ if '.' in entry:
+ timestamp_str, microseconds = entry.split('.')
+ else:
+ timestamp_str, microseconds = entry, '000000'
+
+ if len(microseconds) != 6 or not microseconds.isdigit():
+ raise ValueError("timestamp's microseconds should be six digits")
+
+ timestamp = datetime.datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S")
+ return timestamp + datetime.timedelta(microseconds = int(microseconds))
+
def _get_label(units, count, decimal, is_long):
"""
Provides label corresponding to units of the highest significance in the
diff --git a/stem/util/tor_tools.py b/stem/util/tor_tools.py
index c70fbce..3673be9 100644
--- a/stem/util/tor_tools.py
+++ b/stem/util/tor_tools.py
@@ -7,16 +7,21 @@ Miscellaneous utility functions for working with tor.
is_valid_fingerprint - checks if a string is a valid tor relay fingerprint
is_valid_nickname - checks if a string is a valid tor relay nickname
+ is_valid_circuit_id - checks if a string is a valid tor circuit id
is_hex_digits - checks if a string is only made up of hex digits
"""
import re
# The control-spec defines the following as...
+#
# Fingerprint = "$" 40*HEXDIG
# NicknameChar = "a"-"z" / "A"-"Z" / "0" - "9"
# Nickname = 1*19 NicknameChar
#
+# CircuitID = 1*16 IDChar
+# IDChar = ALPHA / DIGIT
+#
# HEXDIG is defined in RFC 5234 as being uppercase and used in RFC 5987 as
# case insensitive. Tor doesn't define this in the spec so flipping a coin
# and going with case insensitive.
@@ -24,6 +29,7 @@ import re
HEX_DIGIT = "[0-9a-fA-F]"
FINGERPRINT_PATTERN = re.compile("^%s{40}$" % HEX_DIGIT)
NICKNAME_PATTERN = re.compile("^[a-zA-Z0-9]{1,19}$")
+CIRC_ID_PATTERN = re.compile("^[a-zA-Z0-9]{1,16}$")
def is_valid_fingerprint(entry, check_prefix = False):
"""
@@ -59,6 +65,18 @@ def is_valid_nickname(entry):
return bool(NICKNAME_PATTERN.match(entry))
+def is_valid_circuit_id(entry):
+ """
+ Checks if a string is a valid format for being a circuit identifier.
+
+ :returns: **True** if the string could be a circuit id, **False** otherwise
+ """
+
+ if not isinstance(entry, str):
+ return False
+
+ return bool(CIRC_ID_PATTERN.match(entry))
+
def is_hex_digits(entry, count):
"""
Checks if a string is the given number of hex digits. Digits represented by
diff --git a/test/unit/__init__.py b/test/unit/__init__.py
index 15a6b88..a57679c 100644
--- a/test/unit/__init__.py
+++ b/test/unit/__init__.py
@@ -4,6 +4,7 @@ Unit tests for the stem library.
__all__ = [
"connection",
+ "control",
"descriptor",
"exit_policy",
"socket",
diff --git a/test/unit/control/__init__.py b/test/unit/control/__init__.py
new file mode 100644
index 0000000..448a597
--- /dev/null
+++ b/test/unit/control/__init__.py
@@ -0,0 +1,6 @@
+"""
+Unit tests for stem.control.
+"""
+
+__all__ = ["controller"]
+
diff --git a/test/unit/control/controller.py b/test/unit/control/controller.py
new file mode 100644
index 0000000..e25fbe0
--- /dev/null
+++ b/test/unit/control/controller.py
@@ -0,0 +1,50 @@
+"""
+Unit tests for the stem.control module. The module's primarily exercised via
+integ tests, but a few bits lend themselves to unit testing.
+"""
+
+import unittest
+
+from stem import ProtocolError
+from stem.control import _parse_circ_path
+
+class TestControl(unittest.TestCase):
+ def test_parse_circ_path(self):
+ """
+ Exercises the _parse_circ_path() helper function.
+ """
+
+ # empty input
+
+ self.assertEqual([], _parse_circ_path(None))
+ self.assertEqual([], _parse_circ_path(''))
+
+ # check the pydoc examples
+
+ pydoc_examples = {
+ '$999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz':
+ [('999A226EBED397F331B612FE1E4CFAE5C1F201BA', 'piyaz')],
+ '$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14':
+ [('E57A476CD4DFBD99B4EE52A100A58610AD6E80B9', None),
+ (None, 'hamburgerphone'),
+ (None, 'PrivacyRepublic14'),
+ ],
+ }
+
+ for test_input, expected in pydoc_examples.items():
+ self.assertEqual(expected, _parse_circ_path(test_input))
+
+ # exercise with some invalid inputs
+
+ malformed_inputs = [
+ '=piyaz', # no fingerprint
+ '999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz', # fingerprint missing prefix
+ '$999A226EBED397F331B612FE1E4CFAE5C1F201BAA=piyaz', # fingerprint too long
+ '$999A226EBED397F331B612FE1E4CFAE5C1F201B=piyaz', # fingerprint too short
+ '$999A226EBED397F331B612FE1E4CFAE5C1F201Bz=piyaz', # invalid character in fingerprint
+ '$999A226EBED397F331B612FE1E4CFAE5C1F201BA=', # no nickname
+ ]
+
+ for test_input in malformed_inputs:
+ self.assertRaises(ProtocolError, _parse_circ_path, test_input)
+
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 1942b69..d0c0b4e 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -2,6 +2,7 @@
Unit tests for the stem.response.events classes.
"""
+import datetime
import threading
import unittest
@@ -9,7 +10,37 @@ import stem.response
import stem.response.events
import test.mocking as mocking
-from stem.socket import ProtocolError
+from stem import ProtocolError
+from stem.control import CircStatus, CircBuildFlag, CircPurpose, CircClosureReason
+
+# CIRC events from tor v0.2.3.16
+
+CIRC_LAUNCHED = "650 CIRC 7 LAUNCHED \
+BUILD_FLAGS=NEED_CAPACITY \
+PURPOSE=GENERAL \
+TIME_CREATED=2012-11-08T16:48:38.417238"
+
+CIRC_EXTENDED = "650 CIRC 7 EXTENDED \
+$999A226EBED397F331B612FE1E4CFAE5C1F201BA=piyaz \
+BUILD_FLAGS=NEED_CAPACITY \
+PURPOSE=GENERAL \
+TIME_CREATED=2012-11-08T16:48:38.417238"
+
+CIRC_FAILED = "650 CIRC 5 FAILED \
+$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9=ergebnisoffen \
+BUILD_FLAGS=NEED_CAPACITY \
+PURPOSE=GENERAL \
+TIME_CREATED=2012-11-08T16:48:36.400959 \
+REASON=DESTROYED \
+REMOTE_REASON=OR_CONN_CLOSED"
+
+# CIRC events from tor v0.2.1.30 without the VERBOSE_NAMES feature
+
+CIRC_LAUNCHED_OLD = "650 CIRC 4 LAUNCHED"
+CIRC_EXTENDED_OLD = "650 CIRC 1 EXTENDED \
+$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone"
+CIRC_BUILT_OLD = "650 CIRC 1 BUILT \
+$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14"
def _get_event(content):
controller_event = mocking.get_message(content)
@@ -47,6 +78,97 @@ class TestEvents(unittest.TestCase):
time.sleep(0.2)
events_thread.join()
+ def test_circ_event(self):
+ event = _get_event(CIRC_LAUNCHED)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_LAUNCHED.lstrip("650 "), str(event))
+ self.assertEqual("7", event.id)
+ self.assertEqual(CircStatus.LAUNCHED, event.status)
+ self.assertEqual((), event.path)
+ self.assertEqual((CircBuildFlag.NEED_CAPACITY,), event.build_flags)
+ self.assertEqual(CircPurpose.GENERAL, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(datetime.datetime(2012, 11, 8, 16, 48, 38, 417238), event.created)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+
+ event = _get_event(CIRC_EXTENDED)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_EXTENDED.lstrip("650 "), str(event))
+ self.assertEqual("7", event.id)
+ self.assertEqual(CircStatus.EXTENDED, event.status)
+ self.assertEqual((("999A226EBED397F331B612FE1E4CFAE5C1F201BA", "piyaz"),), event.path)
+ self.assertEqual((CircBuildFlag.NEED_CAPACITY,), event.build_flags)
+ self.assertEqual(CircPurpose.GENERAL, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(datetime.datetime(2012, 11, 8, 16, 48, 38, 417238), event.created)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+
+ event = _get_event(CIRC_FAILED)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_FAILED.lstrip("650 "), str(event))
+ self.assertEqual("5", event.id)
+ self.assertEqual(CircStatus.FAILED, event.status)
+ self.assertEqual((("E57A476CD4DFBD99B4EE52A100A58610AD6E80B9", "ergebnisoffen"),), event.path)
+ self.assertEqual((CircBuildFlag.NEED_CAPACITY,), event.build_flags)
+ self.assertEqual(CircPurpose.GENERAL, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(datetime.datetime(2012, 11, 8, 16, 48, 36, 400959), event.created)
+ self.assertEqual(CircClosureReason.DESTROYED, event.reason)
+ self.assertEqual(CircClosureReason.OR_CONN_CLOSED, event.remote_reason)
+
+ event = _get_event(CIRC_LAUNCHED_OLD)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_LAUNCHED_OLD.lstrip("650 "), str(event))
+ self.assertEqual("4", event.id)
+ self.assertEqual(CircStatus.LAUNCHED, event.status)
+ self.assertEqual((), event.path)
+ self.assertEqual(None, event.build_flags)
+ self.assertEqual(None, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(None, event.created)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+
+ event = _get_event(CIRC_EXTENDED_OLD)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_EXTENDED_OLD.lstrip("650 "), str(event))
+ self.assertEqual("1", event.id)
+ self.assertEqual(CircStatus.EXTENDED, event.status)
+ self.assertEqual((("E57A476CD4DFBD99B4EE52A100A58610AD6E80B9", None), (None,"hamburgerphone")), event.path)
+ self.assertEqual(None, event.build_flags)
+ self.assertEqual(None, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(None, event.created)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+
+ event = _get_event(CIRC_BUILT_OLD)
+
+ self.assertTrue(isinstance(event, stem.response.events.CircuitEvent))
+ self.assertEqual(CIRC_BUILT_OLD.lstrip("650 "), str(event))
+ self.assertEqual("1", event.id)
+ self.assertEqual(CircStatus.BUILT, event.status)
+ self.assertEqual((("E57A476CD4DFBD99B4EE52A100A58610AD6E80B9", None), (None,"hamburgerphone"), (None, "PrivacyRepublic14")), event.path)
+ self.assertEqual(None, event.build_flags)
+ self.assertEqual(None, event.purpose)
+ self.assertEqual(None, event.hs_state)
+ self.assertEqual(None, event.rend_query)
+ self.assertEqual(None, event.created)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+
def test_bw_event(self):
event = _get_event("650 BW 15 25")
diff --git a/test/unit/util/str_tools.py b/test/unit/util/str_tools.py
index 3d42154..e91e324 100644
--- a/test/unit/util/str_tools.py
+++ b/test/unit/util/str_tools.py
@@ -2,7 +2,9 @@
Unit tests for the stem.util.str_tools functions.
"""
+import datetime
import unittest
+
from stem.util import str_tools
class TestStrTools(unittest.TestCase):
@@ -118,4 +120,33 @@ class TestStrTools(unittest.TestCase):
self.assertRaises(ValueError, str_tools.parse_short_time_label, '05:')
self.assertRaises(ValueError, str_tools.parse_short_time_label, '05a:00')
self.assertRaises(ValueError, str_tools.parse_short_time_label, '-05:00')
+
+ def test_parse_iso_timestamp(self):
+ """
+ Checks the parse_iso_timestamp() function.
+ """
+
+ test_inputs = {
+ '2012-11-08T16:48:41.420251':
+ datetime.datetime(2012, 11, 8, 16, 48, 41, 420251),
+ '2012-11-08T16:48:41.000000':
+ datetime.datetime(2012, 11, 8, 16, 48, 41, 0),
+ '2012-11-08T16:48:41':
+ datetime.datetime(2012, 11, 8, 16, 48, 41, 0),
+ }
+
+ for arg, expected in test_inputs.items():
+ self.assertEqual(expected, str_tools.parse_iso_timestamp(arg))
+
+ invalid_input = [
+ None,
+ 32,
+ 'hello world',
+ '2012-11-08T16:48:41.42025', # too few microsecond digits
+ '2012-11-08T16:48:41.4202511', # too many microsecond digits
+ '2012-11-08T16:48',
+ ]
+
+ for arg in invalid_input:
+ self.assertRaises(ValueError, str_tools.parse_iso_timestamp, arg)
1
0
commit 119579bd8c53c15e949e9a77a0c30e12be07d1cd
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 11 12:28:51 2012 -0800
Enum docs for stem.control.State
While documenting enums I must have messed up by search term for stem.control
since, rather than adding enum documentation, I concluded that there weren't
any enums and removed the import statement. Fixing this.
---
stem/control.py | 24 ++++++++++++++++--------
1 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index baeb96e..846aba7 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -49,11 +49,18 @@ providing its own for interacting at a higher level.
|- add_status_listener - notifies a callback of changes in our status
|- remove_status_listener - prevents further notification of status changes
+- __enter__ / __exit__ - manages socket connection
+
+.. data:: State (enum)
+
+ Enumeration for states that a controller can have.
- State - enumeration for states that a controller can have
- |- INIT - new control connection
- |- RESET - received a reset/sighup signal
- +- CLOSED - control connection closed
+ ====== ===========
+ State Description
+ ====== ===========
+ INIT new control connection
+ RESET received a reset/sighup signal
+ CLOSED control connection closed
+ ====== ===========
"""
from __future__ import with_statement
@@ -69,6 +76,7 @@ import stem.version
import stem.descriptor.router_status_entry
import stem.descriptor.server_descriptor
import stem.util.connection
+import stem.util.enum
import stem.util.log as log
# state changes a control socket can have
@@ -303,9 +311,9 @@ class BaseController(object):
my_function(controller, state, timestamp)
- The state is a value from stem.socket.State, functions **must** allow for
- new values in this field. The timestamp is a float for the unix time when
- the change occurred.
+ The state is a value from the :data:`stem.control.State` enum. Functions
+ **must** allow for new values. The timestamp is a float for the unix time
+ when the change occurred.
This class only provides **State.INIT** and **State.CLOSED** notifications.
Subclasses may provide others.
@@ -396,7 +404,7 @@ class BaseController(object):
If set, the expect_alive flag will discard our event if it conflicts with
our current :func:`~stem.control.BaseController.is_alive` state.
- :param stem.socket.State state: state change that has occurred
+ :param stem.control.State state: state change that has occurred
:param bool expect_alive: discard event if it conflicts with our
:func:`~stem.control.BaseController.is_alive` state
"""
1
0
commit d735e1e267877d9e9e7fa0686c2cd49c63930f0b
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 11:48:00 2012 -0800
Support for STREAM events
Implementaton and tests for STREAM events. I got the test data by...
* starting TBB
* used netstat to get the control port (shouldn't have needed to do this -
https://trac.torproject.org/7512)
* connecting to it with telnet
* AUTHENTICATE
* SETEVENTS STREAM
* visited google's front page in firefox
Full test data:
AUTHENTICATE
250 OK
SETEVENTS STREAM
250 OK
650 STREAM 18 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47849 PURPOSE=USER
650 STREAM 18 SENTCONNECT 26 encrypted.google.com:443
650 STREAM 19 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47850 PURPOSE=USER
650 STREAM 19 SENTCONNECT 26 encrypted.google.com:443
650 STREAM 18 REMAP 26 74.125.227.129:443 SOURCE=EXIT
650 STREAM 18 SUCCEEDED 26 74.125.227.129:443
650 STREAM 19 REMAP 26 74.125.227.129:443 SOURCE=EXIT
650 STREAM 19 SUCCEEDED 26 74.125.227.129:443
650 STREAM 20 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47851 PURPOSE=USER
650 STREAM 20 REMAP 0 74.125.227.129:443 SOURCE=CACHE
650 STREAM 20 SENTCONNECT 26 74.125.227.129:443
650 STREAM 21 NEW 0 encrypted.google.com:443 SOURCE_ADDR=127.0.0.1:47852 PURPOSE=USER
650 STREAM 21 REMAP 0 74.125.227.129:443 SOURCE=CACHE
650 STREAM 21 SENTCONNECT 26 74.125.227.129:443
650 STREAM 20 REMAP 26 74.125.227.129:443 SOURCE=EXIT
650 STREAM 20 SUCCEEDED 26 74.125.227.129:443
650 STREAM 21 REMAP 26 74.125.227.129:443 SOURCE=EXIT
650 STREAM 21 SUCCEEDED 26 74.125.227.129:443
650 STREAM 22 NEW 0 www.google.com:443 SOURCE_ADDR=127.0.0.1:47853 PURPOSE=USER
650 STREAM 22 SENTCONNECT 26 www.google.com:443
650 STREAM 23 NEW 0 www.google.com:443 SOURCE_ADDR=127.0.0.1:47854 PURPOSE=USER
650 STREAM 23 SENTCONNECT 26 www.google.com:443
650 STREAM 21 CLOSED 26 74.125.227.129:443 REASON=CONNRESET
650 STREAM 20 CLOSED 26 74.125.227.129:443 REASON=CONNRESET
650 STREAM 22 REMAP 26 74.125.227.147:443 SOURCE=EXIT
650 STREAM 22 SUCCEEDED 26 74.125.227.147:443
650 STREAM 23 REMAP 26 74.125.227.147:443 SOURCE=EXIT
650 STREAM 23 SUCCEEDED 26 74.125.227.147:443
650 STREAM 24 NEW 0 ocsp.thawte.com:80 SOURCE_ADDR=127.0.0.1:47855 PURPOSE=USER
650 STREAM 24 SENTCONNECT 26 ocsp.thawte.com:80
650 STREAM 25 NEW 0 ocsp.thawte.com:80 SOURCE_ADDR=127.0.0.1:47856 PURPOSE=USER
650 STREAM 25 SENTCONNECT 26 ocsp.thawte.com:80
650 STREAM 24 REMAP 26 199.7.52.72:80 SOURCE=EXIT
650 STREAM 24 SUCCEEDED 26 199.7.52.72:80
650 STREAM 25 REMAP 26 199.7.52.72:80 SOURCE=EXIT
650 STREAM 25 SUCCEEDED 26 199.7.52.72:80
650 STREAM 26 NEW 0 ssl.gstatic.com:443 SOURCE_ADDR=127.0.0.1:47857 PURPOSE=USER
650 STREAM 26 SENTCONNECT 26 ssl.gstatic.com:443
650 STREAM 27 NEW 0 ssl.gstatic.com:443 SOURCE_ADDR=127.0.0.1:47858 PURPOSE=USER
650 STREAM 27 SENTCONNECT 26 ssl.gstatic.com:443
650 STREAM 23 CLOSED 26 74.125.227.147:443 REASON=CONNRESET
650 STREAM 26 REMAP 26 74.125.227.143:443 SOURCE=EXIT
650 STREAM 26 SUCCEEDED 26 74.125.227.143:443
650 STREAM 27 REMAP 26 74.125.227.143:443 SOURCE=EXIT
650 STREAM 27 SUCCEEDED 26 74.125.227.143:443
650 STREAM 25 CLOSED 26 199.7.52.72:80 REASON=DONE
650 STREAM 27 CLOSED 26 74.125.227.143:443 REASON=CONNRESET
650 STREAM 26 CLOSED 26 74.125.227.143:443 REASON=DONE
650 STREAM 24 CLOSED 26 199.7.52.72:80 REASON=DONE
650 STREAM 22 CLOSED 26 74.125.227.147:443 REASON=DONE
650 STREAM 19 CLOSED 26 74.125.227.129:443 REASON=DONE
650 STREAM 18 CLOSED 26 74.125.227.129:443 REASON=DONE
Connection closed by foreign host.
---
stem/control.py | 115 ++++++++++++++++++++++++++++++++++++
stem/response/events.py | 83 ++++++++++++++++++++++++++-
test/unit/response/events.py | 131 +++++++++++++++++++++++++++++++++++++++++-
3 files changed, 327 insertions(+), 2 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 083082e..d0e8462 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -182,6 +182,77 @@ providing its own for interacting at a higher level.
**HSSR_CONNECTING** connecting to the introductory point
**HSSR_JOINED** connected to the rendezvous-point
============================= ===========
+
+.. data:: StreamStatus (enum)
+
+ State that a stream going through tor can have. Tor may provide states not in
+ this enum.
+
+ ================= ===========
+ StreamStatus Description
+ ================= ===========
+ **NEW** request for a new connection
+ **NEWRESOLVE** request to resolve an address
+ **REMAP** address is being re-mapped to another
+ **SENTCONNECT** sent a connect cell along a circuit
+ **SENTRESOLVE** sent a resolve cell along a circuit
+ **SUCCEEDED** stream has been established
+ **FAILED** stream is detached, and won't be re-established
+ **DETACHED** stream is detached, but might be re-established
+ **CLOSED** stream has closed
+ ================= ===========
+
+.. data:: StreamClosureReason (enum)
+
+ Reason that a stream is being closed or failed to be established. Tor may
+ provide purposes not in this enum.
+
+ ===================== ===========
+ StreamClosureReason Description
+ ===================== ===========
+ **MISC** none of the following reasons
+ **RESOLVEFAILED** unable to resolve the hostname
+ **CONNECTREFUSED** remote host refused the connection
+ **EXITPOLICY** rejected by the exit due to its exit policy
+ **DESTROY** circuit is being shut down
+ **DONE** connection has been closed
+ **TIMEOUT** connection timed out
+ **NOROUTE** routing error while contacting the destinaiton
+ **HIBERNATING** relay is hibernating
+ **INTERNAL** internal error
+ **RESOURCELIMIT** relay has insufficient resources to service the request
+ **CONNRESET** connection has been reset
+ **TORPROTOCOL** violation in the tor protocol
+ **NOTDIRECTORY** directory information requested from a relay that isn't mirroring it
+ **END** endpoint has sent a RELAY_END cell
+ **PRIVATE_ADDR** endpoint was a private address (127.0.0.1, 10.0.0.1, etc)
+ ===================== ===========
+
+.. data:: StreamSource (enum)
+
+ Cause of a stream being remapped to another address.
+
+ ============= ===========
+ StreamSource Description
+ ============= ===========
+ **CACHE** tor is remapping because of a cached answer
+ **EXIT** exit relay requested the remap
+ ============= ===========
+
+.. data:: StreamPurpose (enum)
+
+ Purpsoe of the stream. This is only provided with new streams and tor may
+ provide purposes not in this enum.
+
+ ================= ===========
+ StreamPurpose Description
+ ================= ===========
+ **DIR_FETCH** unknown (https://trac.torproject.org/7508)
+ **UPLOAD_DESC** unknown (https://trac.torproject.org/7508)
+ **DNS_REQUEST** unknown (https://trac.torproject.org/7508)
+ **USER** unknown (https://trac.torproject.org/7508)
+ **DIRPORT_TEST** unknown (https://trac.torproject.org/7508)
+ ================= ===========
"""
from __future__ import with_statement
@@ -289,6 +360,50 @@ HiddenServiceState = stem.util.enum.UppercaseEnum(
"HSSR_JOINED",
)
+StreamStatus = stem.util.enum.UppercaseEnum(
+ "NEW",
+ "NEWRESOLVE",
+ "REMAP",
+ "SENTCONNECT",
+ "SENTRESOLVE",
+ "SUCCEEDED",
+ "FAILED",
+ "DETACHED",
+ "CLOSED",
+)
+
+StreamClosureReason = stem.util.enum.UppercaseEnum(
+ "MISC",
+ "RESOLVEFAILED",
+ "CONNECTREFUSED",
+ "EXITPOLICY",
+ "DESTROY",
+ "DONE",
+ "TIMEOUT",
+ "NOROUTE",
+ "HIBERNATING",
+ "INTERNAL",
+ "RESOURCELIMIT",
+ "CONNRESET",
+ "TORPROTOCOL",
+ "NOTDIRECTORY",
+ "END",
+ "PRIVATE_ADDR",
+)
+
+StreamSource = stem.util.enum.UppercaseEnum(
+ "CACHE",
+ "EXIT",
+)
+
+StreamPurpose = stem.util.enum.UppercaseEnum(
+ "DIR_FETCH",
+ "UPLOAD_DESC",
+ "DNS_REQUEST",
+ "USER",
+ "DIRPORT_TEST",
+)
+
# Constant to indicate an undefined argument default. Usually we'd use None for
# this, but users will commonly provide None as the argument so need something
# else fairly unique...
diff --git a/stem/response/events.py b/stem/response/events.py
index 4c8d8bd..8a0e428 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -3,7 +3,7 @@ import re
import stem.control
import stem.response
-from stem.util import log, str_tools, tor_tools
+from stem.util import connection, log, str_tools, tor_tools
# Matches keyword=value arguments. This can't be a simple "(.*)=(.*)" pattern
# because some positional arguments, like circuit paths, can have an equal
@@ -156,6 +156,86 @@ class CircuitEvent(Event):
log_id = "event.circ.unknown_remote_reason.%s" % self.remote_reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
+class StreamEvent(Event):
+ """
+ Event that indicates that a stream has changed.
+
+ :var str id: stream identifier
+ :var stem.control.StreamStatus status: reported status for the stream
+ :var str circ_id: circuit that the stream is attached to
+ :var str target: destination of the stream
+ :var str target_address: destination address (ip or hostname)
+ :var int target_port: destination port
+ :var stem.control.StreamClosureReason reason: reason for the stream to be closed
+ :var stem.control.StreamClosureReason remote_reason: remote side's reason for the stream to be closed
+ :var stem.control.StreamSource source: origin of the REMAP request
+ :var str source_addr: requester of the connection
+ :var str source_address: requester address (ip or hostname)
+ :var int source_port: requester port
+ :var stem.control.StreamPurpose purpose: purpose for the stream
+ """
+
+ _POSITIONAL_ARGS = ("id", "status", "circ_id", "target")
+ _KEYWORD_ARGS = {
+ "REASON": "reason",
+ "REMOTE_REASON": "remote_reason",
+ "SOURCE": "source",
+ "SOURCE_ADDR": "source_addr",
+ "PURPOSE": "purpose",
+ }
+
+ def _parse(self):
+ if self.target is None:
+ self.target_address = None
+ self.target_port = None
+ else:
+ if not ':' in self.target:
+ raise stem.ProtocolError("Target location must be of the form 'address:port': %s" % self)
+
+ address, port = self.target.split(':')
+
+ if not connection.is_valid_port(port):
+ raise stem.ProtocolError("Target location's port is invalid: %s" % self)
+
+ self.target_address = address
+ self.target_port = int(port)
+
+ if self.source_addr is None:
+ self.source_address = None
+ self.source_port = None
+ else:
+ if not ':' in self.source_addr:
+ raise stem.ProtocolError("Source location must be of the form 'address:port': %s" % self)
+
+ address, port = self.source_addr.split(':')
+
+ if not connection.is_valid_port(port):
+ raise stem.ProtocolError("Source location's port is invalid: %s" % self)
+
+ self.source_address = address
+ self.source_port = int(port)
+
+ # spec specifies a circ_id of zero if the stream is unattached
+
+ if self.circ_id == "0":
+ self.circ_id = None
+
+ # log if we have an unrecognized closure reason or purpose
+
+ unrecognized_msg = "STREAM event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+
+ if self.reason and (not self.reason in stem.control.StreamClosureReason):
+ log_id = "event.stream.reason.%s" % self.reason
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
+
+ if self.remote_reason and (not self.remote_reason in stem.control.StreamClosureReason):
+ log_id = "event.stream.remote_reason.%s" % self.remote_reason
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
+
+ if self.purpose and (not self.purpose in stem.control.StreamPurpose):
+ log_id = "event.stream.purpose.%s" % self.purpose
+ log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
+
class BandwidthEvent(Event):
"""
Event emitted every second with the bytes sent and received by tor.
@@ -196,6 +276,7 @@ class LogEvent(Event):
EVENT_TYPE_TO_CLASS = {
"CIRC": CircuitEvent,
+ "STREAM": StreamEvent,
"BW": BandwidthEvent,
"DEBUG": LogEvent,
"INFO": LogEvent,
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index d0c0b4e..ac4a22f 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -11,7 +11,14 @@ import stem.response.events
import test.mocking as mocking
from stem import ProtocolError
-from stem.control import CircStatus, CircBuildFlag, CircPurpose, CircClosureReason
+from stem.control import CircStatus,\
+ CircBuildFlag,\
+ CircPurpose,\
+ CircClosureReason,\
+ StreamStatus,\
+ StreamClosureReason,\
+ StreamSource,\
+ StreamPurpose
# CIRC events from tor v0.2.3.16
@@ -42,6 +49,19 @@ $E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone"
CIRC_BUILT_OLD = "650 CIRC 1 BUILT \
$E57A476CD4DFBD99B4EE52A100A58610AD6E80B9,hamburgerphone,PrivacyRepublic14"
+# STREAM events from tor 0.2.3.16 for visiting the google front page
+
+STREAM_NEW = "650 STREAM 18 NEW 0 \
+encrypted.google.com:443 \
+SOURCE_ADDR=127.0.0.1:47849 \
+PURPOSE=USER"
+
+STREAM_SENTCONNECT = "650 STREAM 18 SENTCONNECT 26 encrypted.google.com:443"
+STREAM_REMAP = "650 STREAM 18 REMAP 26 74.125.227.129:443 SOURCE=EXIT"
+STREAM_SUCCEEDED = "650 STREAM 18 SUCCEEDED 26 74.125.227.129:443"
+STREAM_CLOSED_RESET = "650 STREAM 21 CLOSED 26 74.125.227.129:443 REASON=CONNRESET"
+STREAM_CLOSED_DONE = "650 STREAM 25 CLOSED 26 199.7.52.72:80 REASON=DONE"
+
def _get_event(content):
controller_event = mocking.get_message(content)
stem.response.convert("EVENT", controller_event, arrived_at = 25)
@@ -169,6 +189,115 @@ class TestEvents(unittest.TestCase):
self.assertEqual(None, event.reason)
self.assertEqual(None, event.remote_reason)
+ def test_stream_event(self):
+ event = _get_event(STREAM_NEW)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_NEW.lstrip("650 "), str(event))
+ self.assertEqual("18", event.id)
+ self.assertEqual(StreamStatus.NEW, event.status)
+ self.assertEqual(None, event.circ_id)
+ self.assertEqual("encrypted.google.com:443", event.target)
+ self.assertEqual("encrypted.google.com", event.target_address)
+ self.assertEqual(443, event.target_port)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(None, event.source)
+ self.assertEqual("127.0.0.1:47849", event.source_addr)
+ self.assertEqual("127.0.0.1", event.source_address)
+ self.assertEqual(47849, event.source_port)
+ self.assertEqual(StreamPurpose.USER, event.purpose)
+
+ event = _get_event(STREAM_SENTCONNECT)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_SENTCONNECT.lstrip("650 "), str(event))
+ self.assertEqual("18", event.id)
+ self.assertEqual(StreamStatus.SENTCONNECT, event.status)
+ self.assertEqual("26", event.circ_id)
+ self.assertEqual("encrypted.google.com:443", event.target)
+ self.assertEqual("encrypted.google.com", event.target_address)
+ self.assertEqual(443, event.target_port)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(None, event.source)
+ self.assertEqual(None, event.source_addr)
+ self.assertEqual(None, event.source_address)
+ self.assertEqual(None, event.source_port)
+ self.assertEqual(None, event.purpose)
+
+ event = _get_event(STREAM_REMAP)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_REMAP.lstrip("650 "), str(event))
+ self.assertEqual("18", event.id)
+ self.assertEqual(StreamStatus.REMAP, event.status)
+ self.assertEqual("26", event.circ_id)
+ self.assertEqual("74.125.227.129:443", event.target)
+ self.assertEqual("74.125.227.129", event.target_address)
+ self.assertEqual(443, event.target_port)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(StreamSource.EXIT, event.source)
+ self.assertEqual(None, event.source_addr)
+ self.assertEqual(None, event.source_address)
+ self.assertEqual(None, event.source_port)
+ self.assertEqual(None, event.purpose)
+
+ event = _get_event(STREAM_SUCCEEDED)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_SUCCEEDED.lstrip("650 "), str(event))
+ self.assertEqual("18", event.id)
+ self.assertEqual(StreamStatus.SUCCEEDED, event.status)
+ self.assertEqual("26", event.circ_id)
+ self.assertEqual("74.125.227.129:443", event.target)
+ self.assertEqual("74.125.227.129", event.target_address)
+ self.assertEqual(443, event.target_port)
+ self.assertEqual(None, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(None, event.source)
+ self.assertEqual(None, event.source_addr)
+ self.assertEqual(None, event.source_address)
+ self.assertEqual(None, event.source_port)
+ self.assertEqual(None, event.purpose)
+
+ event = _get_event(STREAM_CLOSED_RESET)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_CLOSED_RESET.lstrip("650 "), str(event))
+ self.assertEqual("21", event.id)
+ self.assertEqual(StreamStatus.CLOSED, event.status)
+ self.assertEqual("26", event.circ_id)
+ self.assertEqual("74.125.227.129:443", event.target)
+ self.assertEqual("74.125.227.129", event.target_address)
+ self.assertEqual(443, event.target_port)
+ self.assertEqual(StreamClosureReason.CONNRESET, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(None, event.source)
+ self.assertEqual(None, event.source_addr)
+ self.assertEqual(None, event.source_address)
+ self.assertEqual(None, event.source_port)
+ self.assertEqual(None, event.purpose)
+
+ event = _get_event(STREAM_CLOSED_DONE)
+
+ self.assertTrue(isinstance(event, stem.response.events.StreamEvent))
+ self.assertEqual(STREAM_CLOSED_DONE.lstrip("650 "), str(event))
+ self.assertEqual("25", event.id)
+ self.assertEqual(StreamStatus.CLOSED, event.status)
+ self.assertEqual("26", event.circ_id)
+ self.assertEqual("199.7.52.72:80", event.target)
+ self.assertEqual("199.7.52.72", event.target_address)
+ self.assertEqual(80, event.target_port)
+ self.assertEqual(StreamClosureReason.DONE, event.reason)
+ self.assertEqual(None, event.remote_reason)
+ self.assertEqual(None, event.source)
+ self.assertEqual(None, event.source_addr)
+ self.assertEqual(None, event.source_address)
+ self.assertEqual(None, event.source_port)
+ self.assertEqual(None, event.purpose)
+
def test_bw_event(self):
event = _get_event("650 BW 15 25")
1
0
commit d1cd96db70da3a737f6fee24354d61f5e52893e5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 13:00:50 2012 -0800
Few spelling corrections
---
stem/__init__.py | 2 +-
stem/response/events.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/stem/__init__.py b/stem/__init__.py
index c27df43..7b3f6ee 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -150,7 +150,7 @@ Library for working with the tor process.
**DESTROY** circuit is being shut down
**DONE** connection has been closed
**TIMEOUT** connection timed out
- **NOROUTE** routing error while contacting the destinaiton
+ **NOROUTE** routing error while contacting the destination
**HIBERNATING** relay is hibernating
**INTERNAL** internal error
**RESOURCELIMIT** relay has insufficient resources to service the request
diff --git a/stem/response/events.py b/stem/response/events.py
index 1b28eb3..e24b797 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -129,7 +129,7 @@ class CircuitEvent(Event):
# log if we have an unrecognized status, build flag, purpose, hidden
# service state, or closure reason
- unrecognized_msg = "CIRC event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+ unrecognized_msg = "CIRC event had an unrecognized %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
if self.status and (not self.status in stem.CircStatus):
log_id = "event.circ.unknown_status.%s" % self.status
@@ -223,7 +223,7 @@ class StreamEvent(Event):
# log if we have an unrecognized closure reason or purpose
- unrecognized_msg = "STREAM event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
+ unrecognized_msg = "STREAM event had an unrecognized %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
if self.reason and (not self.reason in stem.StreamClosureReason):
log_id = "event.stream.reason.%s" % self.reason
1
0
commit dff8a0edf35a6e52f2d0e1d52fee71f891146c23
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 13:03:46 2012 -0800
Alphabetizing event types
I had been following an order similar to the control spec but on reflection
that doesn't make sense. Ordering these alphabetically would be better.
---
stem/control.py | 1 +
stem/response/events.py | 76 +++++++++++++++++++++++-----------------------
2 files changed, 39 insertions(+), 38 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 0ddc63c..d7dec9a 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -79,6 +79,7 @@ providing its own for interacting at a higher level.
**ERR** :class:`stem.response.events.LogEvent`
**BW** :class:`stem.response.events.BandwidthEvent`
**CIRC** :class:`stem.response.events.CircuitEvent`
+ **STREAM** :class:`stem.response.events.StreamEvent`
=========== ===========
"""
diff --git a/stem/response/events.py b/stem/response/events.py
index e24b797..0e7a71e 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -78,6 +78,27 @@ class Event(stem.response.ControlMessage):
def _parse(self):
pass
+class BandwidthEvent(Event):
+ """
+ Event emitted every second with the bytes sent and received by tor.
+
+ :var long read: bytes received by tor that second
+ :var long written: bytes sent by tor that second
+ """
+
+ _POSITIONAL_ARGS = ("read", "written")
+
+ def _parse(self):
+ if not self.read:
+ raise stem.ProtocolError("BW event is missing its read value")
+ elif not self.written:
+ raise stem.ProtocolError("BW event is missing its written value")
+ elif not self.read.isdigit() or not self.written.isdigit():
+ raise stem.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
+
+ self.read = long(self.read)
+ self.written = long(self.written)
+
class CircuitEvent(Event):
"""
Event that indicates that a circuit has changed.
@@ -157,6 +178,23 @@ class CircuitEvent(Event):
log_id = "event.circ.unknown_remote_reason.%s" % self.remote_reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
+class LogEvent(Event):
+ """
+ Tor logging event. These are the most visible kind of event since, by
+ default, tor logs at the NOTICE runlevel to stdout.
+
+ :var str runlevel: runlevel of the logged message
+ :var str message: logged message
+ """
+
+ def _parse(self):
+ self.runlevel = self.type
+
+ # message is our content, minus the runlevel and ending "OK" if a
+ # multi-line message
+
+ self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
+
class StreamEvent(Event):
"""
Event that indicates that a stream has changed.
@@ -237,44 +275,6 @@ class StreamEvent(Event):
log_id = "event.stream.purpose.%s" % self.purpose
log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
-class BandwidthEvent(Event):
- """
- Event emitted every second with the bytes sent and received by tor.
-
- :var long read: bytes received by tor that second
- :var long written: bytes sent by tor that second
- """
-
- _POSITIONAL_ARGS = ("read", "written")
-
- def _parse(self):
- if not self.read:
- raise stem.ProtocolError("BW event is missing its read value")
- elif not self.written:
- raise stem.ProtocolError("BW event is missing its written value")
- elif not self.read.isdigit() or not self.written.isdigit():
- raise stem.ProtocolError("A BW event's bytes sent and received should be a positive numeric value, received: %s" % self)
-
- self.read = long(self.read)
- self.written = long(self.written)
-
-class LogEvent(Event):
- """
- Tor logging event. These are the most visible kind of event since, by
- default, tor logs at the NOTICE runlevel to stdout.
-
- :var str runlevel: runlevel of the logged message
- :var str message: logged message
- """
-
- def _parse(self):
- self.runlevel = self.type
-
- # message is our content, minus the runlevel and ending "OK" if a
- # multi-line message
-
- self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
-
EVENT_TYPE_TO_CLASS = {
"CIRC": CircuitEvent,
"STREAM": StreamEvent,
1
0
03 Dec '12
commit c96c76ce16d42aeef6641bfeaaf937673934dc5c
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 12:55:52 2012 -0800
Moving attribute enums to top level module
Enumerations for our event attributes don't really belong in the control
module. They're a bit more like our exceptions in that they're a data type that
might be used in multiple spots. Hence moving the enums to live with them.
---
docs/api/control.rst | 4 +-
stem/__init__.py | 285 ++++++++++++++++++++++++++++++++++++++++++
stem/control.py | 274 ----------------------------------------
stem/response/events.py | 41 +++---
test/unit/response/events.py | 10 +--
5 files changed, 309 insertions(+), 305 deletions(-)
diff --git a/docs/api/control.rst b/docs/api/control.rst
index 0a17d9e..4301374 100644
--- a/docs/api/control.rst
+++ b/docs/api/control.rst
@@ -3,8 +3,8 @@ Controller
.. automodule:: stem.control
-Exceptions
-----------
+Exceptions and Attribute Enums
+------------------------------
.. automodule:: stem
diff --git a/stem/__init__.py b/stem/__init__.py
index 961bc48..c27df43 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -13,6 +13,179 @@ Library for working with the tor process.
| +- InvalidArguments - Invalid request parameters.
+- SocketError - Communication with the socket failed.
+- SocketClosed - Socket has been shut down.
+
+.. data:: CircStatus (enum)
+
+ Statuses that a circuit can be in. Tor may provide statuses not in this enum.
+
+ ============ ===========
+ CircStatus Description
+ ============ ===========
+ **LAUNCHED** new circuit was created
+ **BUILT** circuit finished being created and can accept traffic
+ **EXTENDED** circuit has been extended by a hop
+ **FAILED** circuit construction failed
+ **CLOSED** circuit has been closed
+ ============ ===========
+
+.. data:: CircBuildFlag (enum)
+
+ Attributes about how a circuit is built. These were introduced in tor version
+ 0.2.3.11. Tor may provide flags not in this enum.
+
+ ================= ===========
+ CircBuildFlag Description
+ ================= ===========
+ **ONEHOP_TUNNEL** single hop circuit to fetch directory information
+ **IS_INTERNAL** circuit that won't be used for client traffic
+ **NEED_CAPACITY** circuit only includes high capacity relays
+ **NEED_UPTIME** circuit only includes relays with a high uptime
+ ================= ===========
+
+.. data:: CircPurpose (enum)
+
+ Description of what a circuit is intended for. These were introduced in tor
+ version 0.2.1.6. Tor may provide purposes not in this enum.
+
+ ==================== ===========
+ CircPurpose Description
+ ==================== ===========
+ **GENERAL** client traffic or fetching directory information
+ **HS_CLIENT_INTRO** client side introduction point for a hidden service circuit
+ **HS_CLIENT_REND** client side hidden service rendezvous circuit
+ **HS_SERVICE_INTRO** server side introduction point for a hidden service circuit
+ **HS_SERVICE_REND** server side hidden service rendezvous circuit
+ **TESTING** testing to see if we're reachable, so we can be used as a relay
+ **CONTROLLER** circuit that was built by a controller
+ ==================== ===========
+
+.. data:: CircClosureReason (enum)
+
+ Reason that a circuit is being closed or failed to be established. Tor may
+ provide purposes not in this enum.
+
+ ========================= ===========
+ CircClosureReason Description
+ ========================= ===========
+ **NONE** no reason given
+ **TORPROTOCOL** violation in the tor protocol
+ **INTERNAL** internal error
+ **REQUESTED** requested by the client via a TRUNCATE command
+ **HIBERNATING** relay is presently hibernating
+ **RESOURCELIMIT** relay is out of memory, sockets, or circuit IDs
+ **CONNECTFAILED** unable to contact the relay
+ **OR_IDENTITY** relay had the wrong OR identification
+ **OR_CONN_CLOSED** connection failed after being established
+ **FINISHED** circuit has expired (see tor's MaxCircuitDirtiness config option)
+ **TIMEOUT** circuit construction timed out
+ **DESTROYED** circuit unexpectedly closed
+ **NOPATH** not enough relays to make a circuit
+ **NOSUCHSERVICE** requested hidden service does not exist
+ **MEASUREMENT_EXPIRED** unknown (https://trac.torproject.org/7506)
+ ========================= ===========
+
+.. data:: HiddenServiceState (enum)
+
+ State that a hidden service circuit can have. These were introduced in tor
+ version 0.2.3.11. Tor may provide states not in this enum.
+
+ Enumerations fall into four groups based on their prefix...
+
+ ======= ===========
+ Prefix Description
+ ======= ===========
+ HSCI_* client-side introduction-point
+ HSCR_* client-side rendezvous-point
+ HSSI_* service-side introduction-point
+ HSSR_* service-side rendezvous-point
+ ======= ===========
+
+ ============================= ===========
+ HiddenServiceState Description
+ ============================= ===========
+ **HSCI_CONNECTING** connecting to the introductory point
+ **HSCI_INTRO_SENT** sent INTRODUCE1 and awaiting a reply
+ **HSCI_DONE** received a reply, circuit is closing
+ **HSCR_CONNECTING** connecting to the introductory point
+ **HSCR_ESTABLISHED_IDLE** rendezvous-point established, awaiting an introduction
+ **HSCR_ESTABLISHED_WAITING** introduction received, awaiting a rend
+ **HSCR_JOINED** connected to the hidden service
+ **HSSI_CONNECTING** connecting to the introductory point
+ **HSSI_ESTABLISHED** established introductory point
+ **HSSR_CONNECTING** connecting to the introductory point
+ **HSSR_JOINED** connected to the rendezvous-point
+ ============================= ===========
+
+.. data:: StreamStatus (enum)
+
+ State that a stream going through tor can have. Tor may provide states not in
+ this enum.
+
+ ================= ===========
+ StreamStatus Description
+ ================= ===========
+ **NEW** request for a new connection
+ **NEWRESOLVE** request to resolve an address
+ **REMAP** address is being re-mapped to another
+ **SENTCONNECT** sent a connect cell along a circuit
+ **SENTRESOLVE** sent a resolve cell along a circuit
+ **SUCCEEDED** stream has been established
+ **FAILED** stream is detached, and won't be re-established
+ **DETACHED** stream is detached, but might be re-established
+ **CLOSED** stream has closed
+ ================= ===========
+
+.. data:: StreamClosureReason (enum)
+
+ Reason that a stream is being closed or failed to be established. Tor may
+ provide purposes not in this enum.
+
+ ===================== ===========
+ StreamClosureReason Description
+ ===================== ===========
+ **MISC** none of the following reasons
+ **RESOLVEFAILED** unable to resolve the hostname
+ **CONNECTREFUSED** remote host refused the connection
+ **EXITPOLICY** rejected by the exit due to its exit policy
+ **DESTROY** circuit is being shut down
+ **DONE** connection has been closed
+ **TIMEOUT** connection timed out
+ **NOROUTE** routing error while contacting the destinaiton
+ **HIBERNATING** relay is hibernating
+ **INTERNAL** internal error
+ **RESOURCELIMIT** relay has insufficient resources to service the request
+ **CONNRESET** connection has been reset
+ **TORPROTOCOL** violation in the tor protocol
+ **NOTDIRECTORY** directory information requested from a relay that isn't mirroring it
+ **END** endpoint has sent a RELAY_END cell
+ **PRIVATE_ADDR** endpoint was a private address (127.0.0.1, 10.0.0.1, etc)
+ ===================== ===========
+
+.. data:: StreamSource (enum)
+
+ Cause of a stream being remapped to another address.
+
+ ============= ===========
+ StreamSource Description
+ ============= ===========
+ **CACHE** tor is remapping because of a cached answer
+ **EXIT** exit relay requested the remap
+ ============= ===========
+
+.. data:: StreamPurpose (enum)
+
+ Purpsoe of the stream. This is only provided with new streams and tor may
+ provide purposes not in this enum.
+
+ ================= ===========
+ StreamPurpose Description
+ ================= ===========
+ **DIR_FETCH** unknown (https://trac.torproject.org/7508)
+ **UPLOAD_DESC** unknown (https://trac.torproject.org/7508)
+ **DNS_REQUEST** unknown (https://trac.torproject.org/7508)
+ **USER** unknown (https://trac.torproject.org/7508)
+ **DIRPORT_TEST** unknown (https://trac.torproject.org/7508)
+ ================= ===========
"""
__version__ = '0.0.1'
@@ -40,8 +213,19 @@ __all__ = [
"InvalidArguments",
"SocketError",
"SocketClosed",
+ "CircStatus",
+ "CircBuildFlag",
+ "CircPurpose",
+ "CircClosureReason",
+ "HiddenServiceState",
+ "StreamStatus",
+ "StreamClosureReason",
+ "StreamSource",
+ "StreamPurpose",
]
+import stem.util.enum
+
class ControllerError(Exception):
"Base error for controller communication issues."
@@ -92,3 +276,104 @@ class SocketError(ControllerError):
class SocketClosed(SocketError):
"Control socket was closed before completing the message."
+CircStatus = stem.util.enum.UppercaseEnum(
+ "LAUNCHED",
+ "BUILT",
+ "EXTENDED",
+ "FAILED",
+ "CLOSED",
+)
+
+CircBuildFlag = stem.util.enum.UppercaseEnum(
+ "ONEHOP_TUNNEL",
+ "IS_INTERNAL",
+ "NEED_CAPACITY",
+ "NEED_UPTIME",
+)
+
+CircPurpose = stem.util.enum.UppercaseEnum(
+ "GENERAL",
+ "HS_CLIENT_INTRO",
+ "HS_CLIENT_REND",
+ "HS_SERVICE_INTRO",
+ "HS_SERVICE_REND",
+ "TESTING",
+ "CONTROLLER",
+)
+
+CircClosureReason = stem.util.enum.UppercaseEnum(
+ "NONE",
+ "TORPROTOCOL",
+ "INTERNAL",
+ "REQUESTED",
+ "HIBERNATING",
+ "RESOURCELIMIT",
+ "CONNECTFAILED",
+ "OR_IDENTITY",
+ "OR_CONN_CLOSED",
+ "FINISHED",
+ "TIMEOUT",
+ "DESTROYED",
+ "NOPATH",
+ "NOSUCHSERVICE",
+ "MEASUREMENT_EXPIRED",
+)
+
+HiddenServiceState = stem.util.enum.UppercaseEnum(
+ "HSCI_CONNECTING",
+ "HSCI_INTRO_SENT",
+ "HSCI_DONE",
+ "HSCR_CONNECTING",
+ "HSCR_ESTABLISHED_IDLE",
+ "HSCR_ESTABLISHED_WAITING",
+ "HSCR_JOINED",
+ "HSSI_CONNECTING",
+ "HSSI_ESTABLISHED",
+ "HSSR_CONNECTING",
+ "HSSR_JOINED",
+)
+
+StreamStatus = stem.util.enum.UppercaseEnum(
+ "NEW",
+ "NEWRESOLVE",
+ "REMAP",
+ "SENTCONNECT",
+ "SENTRESOLVE",
+ "SUCCEEDED",
+ "FAILED",
+ "DETACHED",
+ "CLOSED",
+)
+
+StreamClosureReason = stem.util.enum.UppercaseEnum(
+ "MISC",
+ "RESOLVEFAILED",
+ "CONNECTREFUSED",
+ "EXITPOLICY",
+ "DESTROY",
+ "DONE",
+ "TIMEOUT",
+ "NOROUTE",
+ "HIBERNATING",
+ "INTERNAL",
+ "RESOURCELIMIT",
+ "CONNRESET",
+ "TORPROTOCOL",
+ "NOTDIRECTORY",
+ "END",
+ "PRIVATE_ADDR",
+)
+
+StreamSource = stem.util.enum.UppercaseEnum(
+ "CACHE",
+ "EXIT",
+)
+
+StreamPurpose = stem.util.enum.UppercaseEnum(
+ "DIR_FETCH",
+ "UPLOAD_DESC",
+ "DNS_REQUEST",
+ "USER",
+ "DIRPORT_TEST",
+)
+
diff --git a/stem/control.py b/stem/control.py
index d0e8462..0ddc63c 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -80,179 +80,6 @@ providing its own for interacting at a higher level.
**BW** :class:`stem.response.events.BandwidthEvent`
**CIRC** :class:`stem.response.events.CircuitEvent`
=========== ===========
-
-.. data:: CircStatus (enum)
-
- Statuses that a circuit can be in. Tor may provide statuses not in this enum.
-
- ============ ===========
- CircStatus Description
- ============ ===========
- **LAUNCHED** new circuit was created
- **BUILT** circuit finished being created and can accept traffic
- **EXTENDED** circuit has been extended by a hop
- **FAILED** circuit construction failed
- **CLOSED** circuit has been closed
- ============ ===========
-
-.. data:: CircBuildFlag (enum)
-
- Attributes about how a circuit is built. These were introduced in tor version
- 0.2.3.11. Tor may provide flags not in this enum.
-
- ================= ===========
- CircBuildFlag Description
- ================= ===========
- **ONEHOP_TUNNEL** single hop circuit to fetch directory information
- **IS_INTERNAL** circuit that won't be used for client traffic
- **NEED_CAPACITY** circuit only includes high capacity relays
- **NEED_UPTIME** circuit only includes relays with a high uptime
- ================= ===========
-
-.. data:: CircPurpose (enum)
-
- Description of what a circuit is intended for. These were introduced in tor
- version 0.2.1.6. Tor may provide purposes not in this enum.
-
- ==================== ===========
- CircPurpose Description
- ==================== ===========
- **GENERAL** client traffic or fetching directory information
- **HS_CLIENT_INTRO** client side introduction point for a hidden service circuit
- **HS_CLIENT_REND** client side hidden service rendezvous circuit
- **HS_SERVICE_INTRO** server side introduction point for a hidden service circuit
- **HS_SERVICE_REND** server side hidden service rendezvous circuit
- **TESTING** testing to see if we're reachable, so we can be used as a relay
- **CONTROLLER** circuit that was built by a controller
- ==================== ===========
-
-.. data:: CircClosureReason (enum)
-
- Reason that a circuit is being closed or failed to be established. Tor may
- provide purposes not in this enum.
-
- ========================= ===========
- CircClosureReason Description
- ========================= ===========
- **NONE** no reason given
- **TORPROTOCOL** violation in the tor protocol
- **INTERNAL** internal error
- **REQUESTED** requested by the client via a TRUNCATE command
- **HIBERNATING** relay is presently hibernating
- **RESOURCELIMIT** relay is out of memory, sockets, or circuit IDs
- **CONNECTFAILED** unable to contact the relay
- **OR_IDENTITY** relay had the wrong OR identification
- **OR_CONN_CLOSED** connection failed after being established
- **FINISHED** circuit has expired (see tor's MaxCircuitDirtiness config option)
- **TIMEOUT** circuit construction timed out
- **DESTROYED** circuit unexpectedly closed
- **NOPATH** not enough relays to make a circuit
- **NOSUCHSERVICE** requested hidden service does not exist
- **MEASUREMENT_EXPIRED** unknown (https://trac.torproject.org/7506)
- ========================= ===========
-
-.. data:: HiddenServiceState (enum)
-
- State that a hidden service circuit can have. These were introduced in tor
- version 0.2.3.11. Tor may provide states not in this enum.
-
- Enumerations fall into four groups based on their prefix...
-
- ======= ===========
- Prefix Description
- ======= ===========
- HSCI_* client-side introduction-point
- HSCR_* client-side rendezvous-point
- HSSI_* service-side introduction-point
- HSSR_* service-side rendezvous-point
- ======= ===========
-
- ============================= ===========
- HiddenServiceState Description
- ============================= ===========
- **HSCI_CONNECTING** connecting to the introductory point
- **HSCI_INTRO_SENT** sent INTRODUCE1 and awaiting a reply
- **HSCI_DONE** received a reply, circuit is closing
- **HSCR_CONNECTING** connecting to the introductory point
- **HSCR_ESTABLISHED_IDLE** rendezvous-point established, awaiting an introduction
- **HSCR_ESTABLISHED_WAITING** introduction received, awaiting a rend
- **HSCR_JOINED** connected to the hidden service
- **HSSI_CONNECTING** connecting to the introductory point
- **HSSI_ESTABLISHED** established introductory point
- **HSSR_CONNECTING** connecting to the introductory point
- **HSSR_JOINED** connected to the rendezvous-point
- ============================= ===========
-
-.. data:: StreamStatus (enum)
-
- State that a stream going through tor can have. Tor may provide states not in
- this enum.
-
- ================= ===========
- StreamStatus Description
- ================= ===========
- **NEW** request for a new connection
- **NEWRESOLVE** request to resolve an address
- **REMAP** address is being re-mapped to another
- **SENTCONNECT** sent a connect cell along a circuit
- **SENTRESOLVE** sent a resolve cell along a circuit
- **SUCCEEDED** stream has been established
- **FAILED** stream is detached, and won't be re-established
- **DETACHED** stream is detached, but might be re-established
- **CLOSED** stream has closed
- ================= ===========
-
-.. data:: StreamClosureReason (enum)
-
- Reason that a stream is being closed or failed to be established. Tor may
- provide purposes not in this enum.
-
- ===================== ===========
- StreamClosureReason Description
- ===================== ===========
- **MISC** none of the following reasons
- **RESOLVEFAILED** unable to resolve the hostname
- **CONNECTREFUSED** remote host refused the connection
- **EXITPOLICY** rejected by the exit due to its exit policy
- **DESTROY** circuit is being shut down
- **DONE** connection has been closed
- **TIMEOUT** connection timed out
- **NOROUTE** routing error while contacting the destinaiton
- **HIBERNATING** relay is hibernating
- **INTERNAL** internal error
- **RESOURCELIMIT** relay has insufficient resources to service the request
- **CONNRESET** connection has been reset
- **TORPROTOCOL** violation in the tor protocol
- **NOTDIRECTORY** directory information requested from a relay that isn't mirroring it
- **END** endpoint has sent a RELAY_END cell
- **PRIVATE_ADDR** endpoint was a private address (127.0.0.1, 10.0.0.1, etc)
- ===================== ===========
-
-.. data:: StreamSource (enum)
-
- Cause of a stream being remapped to another address.
-
- ============= ===========
- StreamSource Description
- ============= ===========
- **CACHE** tor is remapping because of a cached answer
- **EXIT** exit relay requested the remap
- ============= ===========
-
-.. data:: StreamPurpose (enum)
-
- Purpsoe of the stream. This is only provided with new streams and tor may
- provide purposes not in this enum.
-
- ================= ===========
- StreamPurpose Description
- ================= ===========
- **DIR_FETCH** unknown (https://trac.torproject.org/7508)
- **UPLOAD_DESC** unknown (https://trac.torproject.org/7508)
- **DNS_REQUEST** unknown (https://trac.torproject.org/7508)
- **USER** unknown (https://trac.torproject.org/7508)
- **DIRPORT_TEST** unknown (https://trac.torproject.org/7508)
- ================= ===========
"""
from __future__ import with_statement
@@ -303,107 +130,6 @@ EventType = stem.util.enum.UppercaseEnum(
"CIRC_MINOR",
)
-CircStatus = stem.util.enum.UppercaseEnum(
- "LAUNCHED",
- "BUILT",
- "EXTENDED",
- "FAILED",
- "CLOSED",
-)
-
-CircBuildFlag = stem.util.enum.UppercaseEnum(
- "ONEHOP_TUNNEL",
- "IS_INTERNAL",
- "NEED_CAPACITY",
- "NEED_UPTIME",
-)
-
-CircPurpose = stem.util.enum.UppercaseEnum(
- "GENERAL",
- "HS_CLIENT_INTRO",
- "HS_CLIENT_REND",
- "HS_SERVICE_INTRO",
- "HS_SERVICE_REND",
- "TESTING",
- "CONTROLLER",
-)
-
-CircClosureReason = stem.util.enum.UppercaseEnum(
- "NONE",
- "TORPROTOCOL",
- "INTERNAL",
- "REQUESTED",
- "HIBERNATING",
- "RESOURCELIMIT",
- "CONNECTFAILED",
- "OR_IDENTITY",
- "OR_CONN_CLOSED",
- "FINISHED",
- "TIMEOUT",
- "DESTROYED",
- "NOPATH",
- "NOSUCHSERVICE",
- "MEASUREMENT_EXPIRED",
-)
-
-HiddenServiceState = stem.util.enum.UppercaseEnum(
- "HSCI_CONNECTING",
- "HSCI_INTRO_SENT",
- "HSCI_DONE",
- "HSCR_CONNECTING",
- "HSCR_ESTABLISHED_IDLE",
- "HSCR_ESTABLISHED_WAITING",
- "HSCR_JOINED",
- "HSSI_CONNECTING",
- "HSSI_ESTABLISHED",
- "HSSR_CONNECTING",
- "HSSR_JOINED",
-)
-
-StreamStatus = stem.util.enum.UppercaseEnum(
- "NEW",
- "NEWRESOLVE",
- "REMAP",
- "SENTCONNECT",
- "SENTRESOLVE",
- "SUCCEEDED",
- "FAILED",
- "DETACHED",
- "CLOSED",
-)
-
-StreamClosureReason = stem.util.enum.UppercaseEnum(
- "MISC",
- "RESOLVEFAILED",
- "CONNECTREFUSED",
- "EXITPOLICY",
- "DESTROY",
- "DONE",
- "TIMEOUT",
- "NOROUTE",
- "HIBERNATING",
- "INTERNAL",
- "RESOURCELIMIT",
- "CONNRESET",
- "TORPROTOCOL",
- "NOTDIRECTORY",
- "END",
- "PRIVATE_ADDR",
-)
-
-StreamSource = stem.util.enum.UppercaseEnum(
- "CACHE",
- "EXIT",
-)
-
-StreamPurpose = stem.util.enum.UppercaseEnum(
- "DIR_FETCH",
- "UPLOAD_DESC",
- "DNS_REQUEST",
- "USER",
- "DIRPORT_TEST",
-)
-
# Constant to indicate an undefined argument default. Usually we'd use None for
# this, but users will commonly provide None as the argument so need something
# else fairly unique...
diff --git a/stem/response/events.py b/stem/response/events.py
index 8a0e428..1b28eb3 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -1,5 +1,6 @@
import re
+import stem
import stem.control
import stem.response
@@ -86,17 +87,17 @@ class CircuitEvent(Event):
version 0.1.2.2.
:var str id: circuit identifier
- :var stem.control.CircStatus status: reported status for the circuit
+ :var stem.CircStatus status: reported status for the circuit
:var tuple path: relays involved in the circuit, these are
**(fingerprint, nickname)** tuples
- :var tuple build_flags: :data:`~stem.control.CircBuildFlag` attributes
+ :var tuple build_flags: :data:`~stem.CircBuildFlag` attributes
governing how the circuit is built
- :var stem.control.CircPurpose purpose: purpose that the circuit is intended for
- :var stem.control.HiddenServiceState hs_state: status if this is a hidden service circuit
+ :var stem.CircPurpose purpose: purpose that the circuit is intended for
+ :var stem.HiddenServiceState hs_state: status if this is a hidden service circuit
:var str rend_query: circuit's rendezvous-point if this is hidden service related
:var datetime created: time when the circuit was created or cannibalized
- :var stem.control.CircClosureReason reason: reason for the circuit to be closed
- :var stem.control.CircClosureReason remote_reason: remote side's reason for the circuit to be closed
+ :var stem.CircClosureReason reason: reason for the circuit to be closed
+ :var stem.CircClosureReason remote_reason: remote side's reason for the circuit to be closed
"""
_POSITIONAL_ARGS = ("id", "status", "path")
@@ -130,29 +131,29 @@ class CircuitEvent(Event):
unrecognized_msg = "CIRC event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
- if self.status and (not self.status in stem.control.CircStatus):
+ if self.status and (not self.status in stem.CircStatus):
log_id = "event.circ.unknown_status.%s" % self.status
log.log_once(log_id, log.INFO, unrecognized_msg % ('status', self.status))
if self.build_flags:
for flag in self.build_flags:
- if not flag in stem.control.CircBuildFlag:
+ if not flag in stem.CircBuildFlag:
log_id = "event.circ.unknown_build_flag.%s" % flag
log.log_once(log_id, log.INFO, unrecognized_msg % ('build flag', flag))
- if self.purpose and (not self.purpose in stem.control.CircPurpose):
+ if self.purpose and (not self.purpose in stem.CircPurpose):
log_id = "event.circ.unknown_purpose.%s" % self.purpose
log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
- if self.hs_state and (not self.hs_state in stem.control.HiddenServiceState):
+ if self.hs_state and (not self.hs_state in stem.HiddenServiceState):
log_id = "event.circ.unknown_hs_state.%s" % self.hs_state
log.log_once(log_id, log.INFO, unrecognized_msg % ('hidden service state', self.hs_state))
- if self.reason and (not self.reason in stem.control.CircClosureReason):
+ if self.reason and (not self.reason in stem.CircClosureReason):
log_id = "event.circ.unknown_reason.%s" % self.reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
- if self.remote_reason and (not self.remote_reason in stem.control.CircClosureReason):
+ if self.remote_reason and (not self.remote_reason in stem.CircClosureReason):
log_id = "event.circ.unknown_remote_reason.%s" % self.remote_reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
@@ -161,18 +162,18 @@ class StreamEvent(Event):
Event that indicates that a stream has changed.
:var str id: stream identifier
- :var stem.control.StreamStatus status: reported status for the stream
+ :var stem.StreamStatus status: reported status for the stream
:var str circ_id: circuit that the stream is attached to
:var str target: destination of the stream
:var str target_address: destination address (ip or hostname)
:var int target_port: destination port
- :var stem.control.StreamClosureReason reason: reason for the stream to be closed
- :var stem.control.StreamClosureReason remote_reason: remote side's reason for the stream to be closed
- :var stem.control.StreamSource source: origin of the REMAP request
+ :var stem.StreamClosureReason reason: reason for the stream to be closed
+ :var stem.StreamClosureReason remote_reason: remote side's reason for the stream to be closed
+ :var stem.StreamSource source: origin of the REMAP request
:var str source_addr: requester of the connection
:var str source_address: requester address (ip or hostname)
:var int source_port: requester port
- :var stem.control.StreamPurpose purpose: purpose for the stream
+ :var stem.StreamPurpose purpose: purpose for the stream
"""
_POSITIONAL_ARGS = ("id", "status", "circ_id", "target")
@@ -224,15 +225,15 @@ class StreamEvent(Event):
unrecognized_msg = "STREAM event had an unrecognised %%s (%%s). Maybe a new addition to the control protocol? Full Event: '%s'" % self
- if self.reason and (not self.reason in stem.control.StreamClosureReason):
+ if self.reason and (not self.reason in stem.StreamClosureReason):
log_id = "event.stream.reason.%s" % self.reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('reason', self.reason))
- if self.remote_reason and (not self.remote_reason in stem.control.StreamClosureReason):
+ if self.remote_reason and (not self.remote_reason in stem.StreamClosureReason):
log_id = "event.stream.remote_reason.%s" % self.remote_reason
log.log_once(log_id, log.INFO, unrecognized_msg % ('remote reason', self.remote_reason))
- if self.purpose and (not self.purpose in stem.control.StreamPurpose):
+ if self.purpose and (not self.purpose in stem.StreamPurpose):
log_id = "event.stream.purpose.%s" % self.purpose
log.log_once(log_id, log.INFO, unrecognized_msg % ('purpose', self.purpose))
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index ac4a22f..3fe22de 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -10,15 +10,7 @@ import stem.response
import stem.response.events
import test.mocking as mocking
-from stem import ProtocolError
-from stem.control import CircStatus,\
- CircBuildFlag,\
- CircPurpose,\
- CircClosureReason,\
- StreamStatus,\
- StreamClosureReason,\
- StreamSource,\
- StreamPurpose
+from stem import * # enums and exceptions
# CIRC events from tor v0.2.3.16
1
0