tor-commits
Threads by month
- ----- 2025 -----
- 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
December 2012
- 17 participants
- 1600 discussions
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
commit 7f73e1c2c92bd949c29b1430200778d46be4d0cc
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Nov 21 09:05:40 2012 -0800
Supporing DESCCHANGED events
Adding support for DESCCHANGED events. These are trivial since... well, they
don't contain any attributes. I'm also filling in some missing EventType enum
documentation and changing the API docs for events to automatically pick up new
event types (there's no point in using autoclass rather than automodule in this
case).
---
docs/api/response.rst | 9 +--------
stem/control.py | 31 +++++++++++++++++--------------
stem/response/events.py | 9 +++++++++
test/unit/response/events.py | 22 ++++++++++++++++++++++
4 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index a73f172..b2dcda0 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -15,12 +15,5 @@ Responses
Events
------
-.. autoclass:: stem.response.events.Event
-.. autoclass:: stem.response.events.LogEvent
-.. autoclass:: stem.response.events.AddrMapEvent
-.. autoclass:: stem.response.events.BandwidthEvent
-.. autoclass:: stem.response.events.CircuitEvent
-.. autoclass:: stem.response.events.NewDescEvent
-.. autoclass:: stem.response.events.ORConnEvent
-.. autoclass:: stem.response.events.StreamEvent
+.. automodule:: stem.response.events
diff --git a/stem/control.py b/stem/control.py
index 3e0a61c..29b77ef 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -69,20 +69,23 @@ providing its own for interacting at a higher level.
: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`
- **CIRC** :class:`stem.response.events.CircuitEvent`
- **NEWDESC** :class:`stem.response.events.NewDescEvent`
- **ORCONN** :class:`stem.response.events.ORConnEvent`
- **STREAM** :class:`stem.response.events.StreamEvent`
- =========== ===========
+ ===================== ===========
+ 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`
+ **ADDRMAP** :class:`stem.response.events.AddrMapEvent`
+ **AUTHDIR_NEWDESCS** :class:`stem.response.events.AuthDirNewDescEvent`
+ **BW** :class:`stem.response.events.BandwidthEvent`
+ **CIRC** :class:`stem.response.events.CircuitEvent`
+ **DESCCHANGED** :class:`stem.response.events.DescChangedEvent`
+ **NEWDESC** :class:`stem.response.events.NewDescEvent`
+ **ORCONN** :class:`stem.response.events.ORConnEvent`
+ **STREAM** :class:`stem.response.events.StreamEvent`
+ ===================== ===========
"""
from __future__ import with_statement
diff --git a/stem/response/events.py b/stem/response/events.py
index 9e13974..f6027aa 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -282,6 +282,14 @@ 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 DescChangedEvent(Event):
+ """
+ Event that indicates that our descriptor has changed. This was first added in
+ tor version 0.1.2.2.
+ """
+
+ pass
+
class LogEvent(Event):
"""
Tor logging event. These are the most visible kind of event since, by
@@ -473,6 +481,7 @@ EVENT_TYPE_TO_CLASS = {
"AUTHDIR_NEWDESCS": AuthDirNewDescEvent,
"BW": BandwidthEvent,
"CIRC": CircuitEvent,
+ "DESCCHANGED": DescChangedEvent,
"NEWDESC": NewDescEvent,
"ORCONN": ORConnEvent,
"STREAM": StreamEvent,
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index c5eeb5b..c0d1fca 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -138,6 +138,17 @@ class TestEvents(unittest.TestCase):
self.assertEqual(None, event.error)
self.assertEqual(datetime.datetime(2012, 11, 19, 8, 50, 13), event.gmt_expiry)
+ def test_authdir_newdesc_event(self):
+ # TODO: We aren't actually parsing the event yet. Until then we can only
+ # check that we properly get a AuthDirNewDescEvent for it.
+
+ event = _get_event("650 AUTHDIR_NEWDESCS")
+
+ self.assertTrue(isinstance(event, stem.response.events.AuthDirNewDescEvent))
+ self.assertEqual("AUTHDIR_NEWDESCS", str(event))
+ self.assertEqual([], event.positional_args)
+ self.assertEqual({}, event.keyword_args)
+
def test_bw_event(self):
event = _get_event("650 BW 15 25")
@@ -255,6 +266,17 @@ class TestEvents(unittest.TestCase):
self.assertEqual(None, event.reason)
self.assertEqual(None, event.remote_reason)
+ def test_descchanged_event(self):
+ # all we can check for is that the event is properly parsed as a
+ # DescChangedEvent instance
+
+ event = _get_event("650 DESCCHANGED")
+
+ self.assertTrue(isinstance(event, stem.response.events.DescChangedEvent))
+ self.assertEqual("DESCCHANGED", str(event))
+ self.assertEqual([], event.positional_args)
+ self.assertEqual({}, event.keyword_args)
+
def test_newdesc_event(self):
event = _get_event(NEWDESC_SINGLE)
expected_relays = (("B3FA3110CC6F42443F039220C134CBD2FC4F0493", "Sakura"),)
1
0
commit ab6e7a365cfa1e31b269f1df0e722ea11a2c53b1
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Nov 19 00:09:09 2012 -0800
Support for ADDRMAP events
There's a special spot in hell for whoever decided to allow for quoted values
in events. This implements and adds testing for ADDRMAP events. Unlike TorCtl
we aren't falling back on a regex for the... er, 'wonderful' quoted stuff, but
rather including quoted value support in the Event parser.
Got test data by visiting a few sites in TBB...
650 ADDRMAP check.torproject.org 38.229.72.22 "2012-11-18 22:48:34" EXPIRES="2012-11-19 06:48:34"
650 ADDRMAP ocsp.digicert.com 5.63.145.124 "2012-11-18 21:53:42" EXPIRES="2012-11-19 05:53:42"
650 ADDRMAP www.atagar.com 75.119.206.243 "2012-11-19 00:50:13" EXPIRES="2012-11-19 08:50:13"
---
docs/api/response.rst | 1 +
stem/response/events.py | 89 ++++++++++++++++++++++++++++++++++++++----
test/unit/response/events.py | 17 ++++++++
3 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index 2f71256..a73f172 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -17,6 +17,7 @@ Events
.. autoclass:: stem.response.events.Event
.. autoclass:: stem.response.events.LogEvent
+.. autoclass:: stem.response.events.AddrMapEvent
.. autoclass:: stem.response.events.BandwidthEvent
.. autoclass:: stem.response.events.CircuitEvent
.. autoclass:: stem.response.events.NewDescEvent
diff --git a/stem/response/events.py b/stem/response/events.py
index 3df5430..941e796 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -1,4 +1,5 @@
import re
+import datetime
import stem
import stem.control
@@ -30,14 +31,13 @@ class Event(stem.response.ControlMessage):
_POSITIONAL_ARGS = ()
_KEYWORD_ARGS = {}
+ _QUOTED = ()
def _parse_message(self, arrived_at):
- fields = str(self).split()
-
- if not fields:
+ if not str(self).strip():
raise stem.ProtocolError("Received a blank tor event. Events must at the very least have a type.")
- self.type = fields.pop(0)
+ self.type = str(self).split().pop(0)
self.arrived_at = arrived_at
# if we're a recognized event type then translate ourselves into that subclass
@@ -45,13 +45,32 @@ class Event(stem.response.ControlMessage):
if self.type in EVENT_TYPE_TO_CLASS:
self.__class__ = EVENT_TYPE_TO_CLASS[self.type]
+ self.positional_args = []
+ self.keyword_args = {}
+
+ # Whoever decided to allow for quoted attributes in events should be
+ # punished. Preferably under some of those maritime laws that allow for
+ # flogging. Event parsing was nice until we threw this crap in...
+ #
+ # Pulling quoted keyword arguments out here. Quoted positonal arguments
+ # are handled later.
+
+ content = str(self)
+
+ for keyword in set(self._QUOTED).intersection(set(self._KEYWORD_ARGS.keys())):
+ match = re.match("^(.*) %s=\"(.*)\"(.*)$" % keyword, content)
+
+ if match:
+ prefix, value, suffix = match.groups()
+ content = prefix + suffix
+ self.keyword_args[keyword] = value
+
+ fields = content.split()[1:]
+
# Tor events contain some number of positional arguments followed by
# key/value mappings. Parsing keyword arguments from the end until we hit
# something that isn't a key/value mapping. The rest are positional.
- self.positional_args = []
- self.keyword_args = {}
-
while fields:
kw_match = KW_ARG.match(fields[-1])
@@ -69,7 +88,25 @@ class Event(stem.response.ControlMessage):
for i in xrange(len(self._POSITIONAL_ARGS)):
attr_name = self._POSITIONAL_ARGS[i]
- attr_value = self.positional_args[i] if i < len(self.positional_args) else None
+ attr_value = None
+
+ if self.positional_args:
+ if attr_name in self._QUOTED:
+ attr_values = [self.positional_args.pop(0)]
+
+ if not attr_values[0].startswith('"'):
+ raise stem.ProtocolError("The %s value should be quoted, but didn't have a starting quote: %s" % self)
+
+ while True:
+ if not self.positional_args:
+ raise stem.ProtocolError("The %s value should be quoted, but didn't have an ending quote: %s" % self)
+
+ attr_values.append(self.positional_args.pop(0))
+ if attr_values[-1].endswith('"'): break
+
+ attr_value = " ".join(attr_values)[1:-1]
+ else:
+ attr_value = self.positional_args.pop(0)
setattr(self, attr_name, attr_value)
@@ -82,6 +119,41 @@ class Event(stem.response.ControlMessage):
def _parse(self):
pass
+class AddrMapEvent(Event):
+ """
+ Event that indicates a new address mapping.
+
+ :var str hostname: address being resolved
+ :var str destination: destionation of the resolution, this is usually an ip,
+ but could be a hostname if TrackHostExits is enabled or **NONE** if the
+ resolution failed
+ :var datetime expiry: expiration time of the resolution in local time
+ :var str error: error code if the resolution failed
+ :var datetime gmt_expiry: expiration time of the resolution in gmt
+ """
+
+ # TODO: The spec for this event is a little vague. Making a couple guesses
+ # about it...
+ #
+ # https://trac.torproject.org/7515
+
+ _POSITIONAL_ARGS = ("hostname", "destination", "expiry")
+ _KEYWORD_ARGS = {
+ "error": "error",
+ "EXPIRES": "gmt_expiry",
+ }
+ _QUOTED = ("expiry", "EXPIRES")
+
+ def _parse(self):
+ if self.destination == "<error>":
+ self.destination = None
+
+ if self.expiry != None:
+ self.expiry = datetime.datetime.strptime(self.expiry, "%Y-%m-%d %H:%M:%S")
+
+ if self.gmt_expiry != None:
+ self.gmt_expiry = datetime.datetime.strptime(self.gmt_expiry, "%Y-%m-%d %H:%M:%S")
+
class BandwidthEvent(Event):
"""
Event emitted every second with the bytes sent and received by tor.
@@ -367,6 +439,7 @@ EVENT_TYPE_TO_CLASS = {
"NOTICE": LogEvent,
"WARN": LogEvent,
"ERR": LogEvent,
+ "ADDRMAP": AddrMapEvent,
"BW": BandwidthEvent,
"CIRC": CircuitEvent,
"NEWDESC": NewDescEvent,
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 733d826..c5eeb5b 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -66,6 +66,12 @@ NEWDESC_SINGLE = "650 NEWDESC $B3FA3110CC6F42443F039220C134CBD2FC4F0493=Sakura"
NEWDESC_MULTIPLE = "650 NEWDESC $BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8=Moonshine \
$B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed"
+# ADDRMAP event
+# TODO: it would be nice to have an example of an error event
+
+ADDRMAP = '650 ADDRMAP www.atagar.com 75.119.206.243 "2012-11-19 00:50:13" \
+EXPIRES="2012-11-19 08:50:13"'
+
def _get_event(content):
controller_event = mocking.get_message(content)
stem.response.convert("EVENT", controller_event, arrived_at = 25)
@@ -121,6 +127,17 @@ class TestEvents(unittest.TestCase):
self.assertEqual("WARN", event.runlevel)
self.assertEqual("a multi-line\nwarning message", event.message)
+ def test_addrmap_event(self):
+ event = _get_event(ADDRMAP)
+
+ self.assertTrue(isinstance(event, stem.response.events.AddrMapEvent))
+ self.assertEqual(ADDRMAP.lstrip("650 "), str(event))
+ self.assertEqual("www.atagar.com", event.hostname)
+ self.assertEqual("75.119.206.243", event.destination)
+ self.assertEqual(datetime.datetime(2012, 11, 19, 0, 50, 13), event.expiry)
+ self.assertEqual(None, event.error)
+ self.assertEqual(datetime.datetime(2012, 11, 19, 8, 50, 13), event.gmt_expiry)
+
def test_bw_event(self):
event = _get_event("650 BW 15 25")
1
0
commit 62b6f153e66a3b1a8adff3ac837c161e43576f2d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 14:47:27 2012 -0800
Reordering event tests
We're using an alphabetical order elsewhere for the events (except logging
events which are batched together at the top), so matching that in the unit
tests.
---
test/unit/response/events.py | 90 +++++++++++++++++++++---------------------
1 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index c17e84d..5836498 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -95,6 +95,51 @@ class TestEvents(unittest.TestCase):
time.sleep(0.2)
events_thread.join()
+ 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)
+
+ 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")
+
def test_circ_event(self):
event = _get_event(CIRC_LAUNCHED)
@@ -321,49 +366,4 @@ class TestEvents(unittest.TestCase):
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")
-
- 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")
-
- 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 84ec250529b81cb86899897784602e6c41fb8d98
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Nov 20 09:37:54 2012 -0800
Stub for AUTHDIR_NEWDESCS events
Implementing the parts that I can for AUTHDIR_NEWDESCS events. The spec doesn't
provide enough detail for us to parse them (https://trac.torproject.org/7533)
and I need an example of an event before I can test them
(https://trac.torproject.org/7534)
---
stem/__init__.py | 22 ++++++++++++++++++++++
stem/response/events.py | 35 +++++++++++++++++++++++++++++++++--
2 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/stem/__init__.py b/stem/__init__.py
index f0fca41..c2c001a 100644
--- a/stem/__init__.py
+++ b/stem/__init__.py
@@ -230,6 +230,22 @@ Library for working with the tor process.
**IOERROR** unknown
**RESOURCELIMIT** unknown
=================== ===========
+
+.. data:: AuthDescriptorAction (enum)
+
+ Actions that directory authorities might take with relay descriptors. Tor may
+ provide reasons not in this enum.
+
+ Enum descriptions are pending...
+ https://trac.torproject.org/7533
+
+ ===================== ===========
+ AuthDescriptorAction Description
+ ===================== ===========
+ **ACCEPTED** unknown
+ **DROPPED** unknown
+ **REJECTED** unknown
+ ===================== ===========
"""
__version__ = '0.0.1'
@@ -443,3 +459,9 @@ ORClosureReason = stem.util.enum.UppercaseEnum(
"RESOURCELIMIT",
)
+AuthDescriptorAction = stem.util.enum.UppercaseEnum(
+ "ACCEPTED",
+ "DROPPED",
+ "REJECTED",
+)
+
diff --git a/stem/response/events.py b/stem/response/events.py
index 941e796..9e13974 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -32,6 +32,7 @@ class Event(stem.response.ControlMessage):
_POSITIONAL_ARGS = ()
_KEYWORD_ARGS = {}
_QUOTED = ()
+ _SKIP_PARSING = False
def _parse_message(self, arrived_at):
if not str(self).strip():
@@ -48,6 +49,21 @@ class Event(stem.response.ControlMessage):
self.positional_args = []
self.keyword_args = {}
+ if not self._SKIP_PARSING:
+ self._parse_standard_attr()
+
+ self._parse()
+
+ def _parse_standard_attr(self):
+ """
+ Most events are of the form...
+ 650 *( positional_args ) *( key "=" value )
+
+ This parses this standard format, populating our **positional_args** and
+ **keyword_args** attributes and creating attributes if it's in our event's
+ **_POSITIONAL_ARGS** and **_KEYWORD_ARGS**.
+ """
+
# Whoever decided to allow for quoted attributes in events should be
# punished. Preferably under some of those maritime laws that allow for
# flogging. Event parsing was nice until we threw this crap in...
@@ -112,8 +128,6 @@ class Event(stem.response.ControlMessage):
for controller_attr_name, attr_name in self._KEYWORD_ARGS.items():
setattr(self, attr_name, self.keyword_args.get(controller_attr_name))
-
- self._parse()
# method overwritten by our subclasses for special handling that they do
def _parse(self):
@@ -154,6 +168,20 @@ class AddrMapEvent(Event):
if self.gmt_expiry != None:
self.gmt_expiry = datetime.datetime.strptime(self.gmt_expiry, "%Y-%m-%d %H:%M:%S")
+class AuthDirNewDescEvent(Event):
+ """
+ Event specific to directory authorities, indicating that we just received new
+ descriptors.
+
+ **Unimplemented, waiting on 'https://trac.torproject.org/7534'.**
+
+ :var stem.AuthDescriptorAction action: what is being done with the descriptor
+ :var str message: unknown
+ :var stem.descriptor.Descriptor descriptor: unknown
+ """
+
+ _SKIP_PARSING = True
+
class BandwidthEvent(Event):
"""
Event emitted every second with the bytes sent and received by tor.
@@ -263,6 +291,8 @@ class LogEvent(Event):
:var str message: logged message
"""
+ _SKIP_PARSING = True
+
def _parse(self):
self.runlevel = self.type
@@ -440,6 +470,7 @@ EVENT_TYPE_TO_CLASS = {
"WARN": LogEvent,
"ERR": LogEvent,
"ADDRMAP": AddrMapEvent,
+ "AUTHDIR_NEWDESCS": AuthDirNewDescEvent,
"BW": BandwidthEvent,
"CIRC": CircuitEvent,
"NEWDESC": NewDescEvent,
1
0
commit 54f81be931196e92870b09119fa7e02a7a62cdbf
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Nov 18 21:06:29 2012 -0800
Support for NEWDESC events
Implementing and testing NEWDESC events. Got test data by listning for events
right after starting a TBB instance...
650 NEWDESC $9C2007BE0D3785D5BAB204F0CFE07CF29F4B75EB~Unnamed
650 NEWDESC $A10C4F666D27364036B562823E5830BC448E046A=DFRI1
650 NEWDESC $A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama
650 NEWDESC $A4E3D4A42E68D8302E4A3EA5D117EDCF0B157F72=tor1rufus
650 NEWDESC $B3BE230EEA146002C4A2A436B4EF7D897DE6C9B6~WombleNode01
650 NEWDESC $B3FA3110CC6F42443F039220C134CBD2FC4F0493=Sakura
650 NEWDESC $B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed
650 NEWDESC $B839D6CCC5438A21E547DB62043CB0192C068F51=brajah1
650 NEWDESC $B8B29766ACFADF832ABD1BCAA86497A20F54B584=stillhavenoipinfo
650 NEWDESC $BBE409F5791DAA52C2C3C9117CBA5AA55F3E2E88=Rarity
650 NEWDESC $BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8=Moonshine
650 NEWDESC $C863FB2A6109C9CE2993C8855BAC59583B15475B=coinet
650 NEWDESC $CA37697301883DE9E895F642D76F3CD31E855434=Bitcoin
650 NEWDESC $CBC5F626F16052B2B987106D4845DF0B5197901D=mergeslittletornode
650 NEWDESC $CC19411EDD0BE139E8828EECE2F11E5DFE5F5C09~RadiantPickle
650 NEWDESC $CF334A712B78A84C6E8F2FF71987122E17951176=skye
...
---
docs/api/response.rst | 3 +++
stem/control.py | 2 ++
stem/response/events.py | 22 +++++++++++++++++++---
test/unit/response/events.py | 23 +++++++++++++++++++++++
4 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/docs/api/response.rst b/docs/api/response.rst
index 043cae4..2f71256 100644
--- a/docs/api/response.rst
+++ b/docs/api/response.rst
@@ -19,4 +19,7 @@ Events
.. autoclass:: stem.response.events.LogEvent
.. autoclass:: stem.response.events.BandwidthEvent
.. autoclass:: stem.response.events.CircuitEvent
+.. autoclass:: stem.response.events.NewDescEvent
+.. autoclass:: stem.response.events.ORConnEvent
+.. autoclass:: stem.response.events.StreamEvent
diff --git a/stem/control.py b/stem/control.py
index 4a91129..3e0a61c 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -79,6 +79,8 @@ 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`
+ **NEWDESC** :class:`stem.response.events.NewDescEvent`
+ **ORCONN** :class:`stem.response.events.ORConnEvent`
**STREAM** :class:`stem.response.events.StreamEvent`
=========== ===========
"""
diff --git a/stem/response/events.py b/stem/response/events.py
index 6a98ef6..3df5430 100644
--- a/stem/response/events.py
+++ b/stem/response/events.py
@@ -107,9 +107,9 @@ 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.
+ The fingerprint or nickname values in our 'path' may be **None** if the
+ VERBOSE_NAMES feature isn't enabled. The option was first introduced in tor
+ version 0.1.2.2, and on by default after 0.2.2.1.
:var str id: circuit identifier
:var stem.CircStatus status: reported status for the circuit
@@ -199,6 +199,21 @@ class LogEvent(Event):
self.message = str(self)[len(self.runlevel) + 1:].rstrip("\nOK")
+class NewDescEvent(Event):
+ """
+ Event that indicates that a new descriptor is available.
+
+ The fingerprint or nickname values in our 'relays' may be **None** if the
+ VERBOSE_NAMES feature isn't enabled. The option was first introduced in tor
+ version 0.1.2.2, and on by default after 0.2.2.1.
+
+ :param tuple relays: **(fingerprint, nickname)** tuples for the relays with
+ new descriptors
+ """
+
+ def _parse(self):
+ self.relays = tuple([stem.control._parse_circ_entry(entry) for entry in str(self).split()[1:]])
+
class ORConnEvent(Event):
"""
Event that indicates a change in a relay connection. The 'endpoint' could be
@@ -354,6 +369,7 @@ EVENT_TYPE_TO_CLASS = {
"ERR": LogEvent,
"BW": BandwidthEvent,
"CIRC": CircuitEvent,
+ "NEWDESC": NewDescEvent,
"ORCONN": ORConnEvent,
"STREAM": StreamEvent,
}
diff --git a/test/unit/response/events.py b/test/unit/response/events.py
index 5836498..733d826 100644
--- a/test/unit/response/events.py
+++ b/test/unit/response/events.py
@@ -59,6 +59,13 @@ STREAM_CLOSED_DONE = "650 STREAM 25 CLOSED 26 199.7.52.72:80 REASON=DONE"
ORCONN_CONNECTED = "650 ORCONN $7ED90E2833EE38A75795BA9237B0A4560E51E1A0=GreenDragon CONNECTED"
ORCONN_CLOSED = "650 ORCONN $A1130635A0CDA6F60C276FBF6994EFBD4ECADAB1~tama CLOSED REASON=DONE"
+# NEWDESC events. I've never actually seen multiple descriptors in an event,
+# but the spec allows for it.
+
+NEWDESC_SINGLE = "650 NEWDESC $B3FA3110CC6F42443F039220C134CBD2FC4F0493=Sakura"
+NEWDESC_MULTIPLE = "650 NEWDESC $BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8=Moonshine \
+$B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF~Unnamed"
+
def _get_event(content):
controller_event = mocking.get_message(content)
stem.response.convert("EVENT", controller_event, arrived_at = 25)
@@ -231,6 +238,22 @@ class TestEvents(unittest.TestCase):
self.assertEqual(None, event.reason)
self.assertEqual(None, event.remote_reason)
+ def test_newdesc_event(self):
+ event = _get_event(NEWDESC_SINGLE)
+ expected_relays = (("B3FA3110CC6F42443F039220C134CBD2FC4F0493", "Sakura"),)
+
+ self.assertTrue(isinstance(event, stem.response.events.NewDescEvent))
+ self.assertEqual(NEWDESC_SINGLE.lstrip("650 "), str(event))
+ self.assertEqual(expected_relays, event.relays)
+
+ event = _get_event(NEWDESC_MULTIPLE)
+ expected_relays = (("BE938957B2CA5F804B3AFC2C1EE6673170CDBBF8", "Moonshine"),
+ ("B4BE08B22D4D2923EDC3970FD1B93D0448C6D8FF", "Unnamed"))
+
+ self.assertTrue(isinstance(event, stem.response.events.NewDescEvent))
+ self.assertEqual(NEWDESC_MULTIPLE.lstrip("650 "), str(event))
+ self.assertEqual(expected_relays, event.relays)
+
def test_orconn_event(self):
event = _get_event(ORCONN_CONNECTED)
1
0