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
January 2019
- 15 participants
- 2081 discussions
commit bf49f7dddfa7500b2bb91efacf840d0c393d86fe
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jan 20 17:43:03 2019 -0800
Generate BandwidthFile api docs
---
docs/api.rst | 1 +
docs/api/descriptor/bandwidth_file.rst | 5 +++++
docs/change_log.rst | 1 +
docs/contents.rst | 1 +
4 files changed, 8 insertions(+)
diff --git a/docs/api.rst b/docs/api.rst
index 307184dc..5ada9ec8 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -36,6 +36,7 @@ remotely like Tor does.
* `stem.descriptor.networkstatus <api/descriptor/networkstatus.html>`_ - Network status documents which make up the Tor consensus.
* `stem.descriptor.router_status_entry <api/descriptor/router_status_entry.html>`_ - Relay entries within a network status document.
* `stem.descriptor.hidden_service_descriptor <api/descriptor/hidden_service_descriptor.html>`_ - Descriptors generated for hidden services.
+ * `stem.descriptor.bandwidth_file <api/descriptor/bandwidth_file.html>`_ - Bandwidth authority metrics.
* `stem.descriptor.tordnsel <api/descriptor/tordnsel.html>`_ - `TorDNSEL <https://www.torproject.org/projects/tordnsel.html.en>`_ exit lists.
* `stem.descriptor.certificate <api/descriptor/certificate.html>`_ - `Ed25519 certificates <https://gitweb.torproject.org/torspec.git/tree/cert-spec.txt>`_.
diff --git a/docs/api/descriptor/bandwidth_file.rst b/docs/api/descriptor/bandwidth_file.rst
new file mode 100644
index 00000000..dba56266
--- /dev/null
+++ b/docs/api/descriptor/bandwidth_file.rst
@@ -0,0 +1,5 @@
+Bandwidth File
+==============
+
+.. automodule:: stem.descriptor.bandwidth_file
+
diff --git a/docs/change_log.rst b/docs/change_log.rst
index bd954e42..c851f7fe 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -53,6 +53,7 @@ The following are only available within Stem's `git repository
* **Descriptors**
+ * `Bandwidth file support <api/descriptor/bandwidth_file.html>`_ (:trac:`29056`)
* Added :func:`stem.descriptor.remote.get_microdescriptors`
* Added :class:`~stem.descriptor.networkstatus.DetachedSignature` parsing (:trac:`28495`)
* Added :func:`~stem.descriptor.__init__.Descriptor.from_str` method (:trac:`28450`)
diff --git a/docs/contents.rst b/docs/contents.rst
index c9c38ebf..2a3d96b0 100644
--- a/docs/contents.rst
+++ b/docs/contents.rst
@@ -38,6 +38,7 @@ Contents
api/manual
api/version
+ api/descriptor/bandwidth_file
api/descriptor/certificate
api/descriptor/descriptor
api/descriptor/server_descriptor
1
0
commit 6e47ca6212e5933fe754db2f56766db77a26ddec
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon Jan 14 10:47:51 2019 -0800
Bandwidth authority file test data
Unit test data provided by juga on the following ticket, cropped to a
reasonable size.
https://trac.torproject.org/projects/tor/ticket/29056#comment:11
---
test/unit/descriptor/data/bwauth_v1.0 | 95 +++++++++++++++++++++++++++++++++++
test/unit/descriptor/data/bwauth_v1.2 | 95 +++++++++++++++++++++++++++++++++++
2 files changed, 190 insertions(+)
diff --git a/test/unit/descriptor/data/bwauth_v1.0 b/test/unit/descriptor/data/bwauth_v1.0
new file mode 100644
index 00000000..7335d0e8
--- /dev/null
+++ b/test/unit/descriptor/data/bwauth_v1.0
@@ -0,0 +1,95 @@
+1547487689
+node_id=$221C91D4C51E4C73CB6A8F0BEE01B0A6BB4A8476 bw=38000 nick=digitalocean1 measured_at=1546325250 updated_at=1546325250 pid_error=4.88593489094 pid_error_sum=4.88593489094 pid_bw=38037642 pid_delta=3.83770474524 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-29.1:29.9-done-2019-01-01-00:47:30
+node_id=$1F509589F7F70B69A38719A201451CF4B70F89C6 bw=589 nick=CulNoir measured_at=1547441722 updated_at=1547441722 pid_error=4.75555077211 pid_error_sum=4.75555077211 pid_bw=589368 pid_delta=2.54992296104 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$8FF24DC61EA335CAFDD5699A937AD96D594E5904 bw=3760 nick=rortor1 measured_at=1547441722 updated_at=1547441722 pid_error=4.73699620172 pid_error_sum=4.73699620172 pid_bw=3759797 pid_delta=2.4938404687 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$E167620E3A7AD1F2739135F060C78BD718FE80EB bw=12200 nick=Letsandwich measured_at=1547435204 updated_at=1547435204 pid_error=4.64885180428 pid_error_sum=4.64885180428 pid_bw=12155894 pid_delta=3.88728943224 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-12.8:13.5-done-2019-01-13-21:06:44
+node_id=$AC6A78BAE7BBCAC798C7AD17460147214F9EA0CD bw=38400 nick=TWCUGRelayNode measured_at=1546931568 updated_at=1547447078 pid_error=4.56407424673 pid_error_sum=4.56407424673 pid_bw=38316440 pid_delta=1.18388151318 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$0409605E8343562A790FFE846CB3BD7C04429F70 bw=65200 nick=jkO measured_at=1547441722 updated_at=1547441722 pid_error=4.42837068737 pid_error_sum=4.42837068737 pid_bw=65197424 pid_delta=3.41781411717 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$D048AC335E641BC22972D353B8F4F7257BC3793E bw=1370 nick=Unnamed measured_at=1546555442 updated_at=1546555442 pid_error=4.20812185853 pid_error_sum=4.20812185853 pid_bw=1365277 pid_delta=2.84806727486 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.6-done-2019-01-03-16:44:02
+node_id=$2880A4D6A33F9BECF2AE106D2951965A97527E96 bw=34100 nick=jacktheripper measured_at=1547257794 updated_at=1547466847 pid_error=4.18032598165 pid_error_sum=4.18032598165 pid_bw=112913643 pid_delta=2.66648341712 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-4.6:5.4-done-2019-01-14-05:54:07
+node_id=$D397BD9ECA62490518581C1B2154B3432064E4A4 bw=31100 nick=faithfulsand measured_at=1546650589 updated_at=1546650589 pid_error=4.01406767814 pid_error_sum=4.01406767814 pid_bw=31074574 pid_delta=2.50762620925 circ_fail=0.6 scanner=/scanner.5/scan-data/bws-50.0:50.8-done-2019-01-04-19:09:49
+node_id=$35BD292A0F86C06A4753FF6F66505FF04B561986 bw=1010 nick=tor6zXLCtAn measured_at=1547447078 updated_at=1547447078 pid_error=3.94071868057 pid_error_sum=3.94071868057 pid_bw=1011859 pid_delta=2.30853109195 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$BF735F669481EE1CCC348F0731551C933D1E2278 bw=57800 nick=Freeway1a1 measured_at=1547450511 updated_at=1547450511 pid_error=3.93901338067 pid_error_sum=3.93901338067 pid_bw=57813764 pid_delta=3.36797582569 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$F50106E1F667E23F658FC27F4834E75F32533A89 bw=7670 nick=Unnamed measured_at=1546304265 updated_at=1547441722 pid_error=3.45633932467 pid_error_sum=3.45633932467 pid_bw=13209302 pid_delta=2.81591476206 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$E235941C8403FB63EC91436FA4DCF6B020EB51A2 bw=18600 nick=VimRulzEmacsBlowz measured_at=1547450511 updated_at=1547450511 pid_error=3.44110402558 pid_error_sum=3.44110402558 pid_bw=18627340 pid_delta=2.14589454923 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$2037A6568C5771A5D8FF5C64FC1819247E6FEE7B bw=56500 nick=lunaTor measured_at=1547447078 updated_at=1547447078 pid_error=3.41757853948 pid_error_sum=3.41757853948 pid_bw=56545005 pid_delta=0.164116147339 circ_fail=0.136363636364 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$9A6599EA5CE10ABFB3446691A0CC83A8C5B61C39 bw=29600 nick=FlavorTown measured_at=1547134909 updated_at=1547441722 pid_error=3.39969515358 pid_error_sum=3.39969515358 pid_bw=42074966 pid_delta=2.24236849614 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$2EC042F4274CC8A54381C78E8D1BF322FA26A095 bw=4530 nick=ToBeAnnounced measured_at=1546583168 updated_at=1547487562 pid_error=3.35508468501 pid_error_sum=3.35508468501 pid_bw=13378820 pid_delta=1.54951031509 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-37.8:38.5-done-2019-01-14-11:39:22
+node_id=$CDDEC3CC4B0FD5054C99B2D2843DE8B609A4CABA bw=15300 nick=rotorstator measured_at=1547441722 updated_at=1547450511 pid_error=3.33481605058 pid_error_sum=3.33481605058 pid_bw=13636152 pid_delta=2.11111295226 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$9DF8ACA2D30860303F529316480CE65C77574CAA bw=21100 nick=Koljastorrelay measured_at=1546931752 updated_at=1546931752 pid_error=3.30340496018 pid_error_sum=3.30340496018 pid_bw=21067874 pid_delta=1.54164019637 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-37.0:37.8-done-2019-01-08-01:15:52
+node_id=$2936A5394D2DA14BF473545AEC7675C0C87B859E bw=26300 nick=glittershy measured_at=1547470234 updated_at=1547470234 pid_error=3.27828864541 pid_error_sum=3.27828864541 pid_bw=26285805 pid_delta=2.27833021889 circ_fail=0.0952380952381 scanner=/scanner.1/scan-data/bws-5.4:6.1-done-2019-01-14-06:50:34
+node_id=$D8B9CAA5B818DEFE80857F83FDABBB6429DCFCA0 bw=47600 nick=baldr measured_at=1547441722 updated_at=1547441722 pid_error=3.23746667827 pid_error_sum=3.23746667827 pid_bw=47625769 pid_delta=1.07534299311 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$2CDAC2D81720489C5618E8DD86C1376FEE518F19 bw=47600 nick=ididntedittheconfig measured_at=1547466038 updated_at=1547466038 pid_error=3.17181559659 pid_error_sum=3.17181559659 pid_bw=47560120 pid_delta=2.44795895652 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-31.1:31.9-done-2019-01-14-05:40:38
+node_id=$AD29F2D87DA141CCB4233F823DD189FD32B8BA14 bw=26400 nick=hosTor1 measured_at=1546349267 updated_at=1546349267 pid_error=3.15936521419 pid_error_sum=3.15936521419 pid_bw=26435956 pid_delta=1.9776867538 circ_fail=0.0 scanner=/scanner.9/scan-data/bws-0.0:100.0-done-2019-01-01-07:27:47
+node_id=$81C55D403A82BF6E7C3FBDBD41D102B7088900D9 bw=11200 nick=InMemoryOfJohnKerr measured_at=1546774396 updated_at=1547398830 pid_error=3.1416198155 pid_error_sum=3.1416198155 pid_bw=41853110 pid_delta=0.847068757274 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-16.5:17.3-done-2019-01-13-11:00:30
+node_id=$3BB34C63072D9D10E836EE42968713F7B9325F66 bw=6460 nick=caersidi measured_at=1546395056 updated_at=1547466847 pid_error=3.12792095007 pid_error_sum=3.12792095007 pid_bw=21642194 pid_delta=1.02999209727 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-4.6:5.4-done-2019-01-14-05:54:07
+node_id=$8343D92F1E1DF3BF823DB795988D8C324E8CE5DC bw=10300 nick=hollowsquirrel measured_at=1545436597 updated_at=1545436597 pid_error=3.11051484794 pid_error_sum=3.11051484794 pid_bw=10287192 pid_delta=1.36790489988 circ_fail=0.6 scanner=/scanner.9/scan-data/bws-0.0:100.0-done-2018-12-21-17:56:37
+node_id=$8BF5C0AE8290AED071450F6E11F22ADDE3069705 bw=628 nick=NtrcticSpider measured_at=1547450511 updated_at=1547450511 pid_error=3.0871379712 pid_error_sum=3.0871379712 pid_bw=627784 pid_delta=1.6406922689 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$06F8CA20F72D9DAC83E1661A3DD62BD5BC071CB2 bw=1060 nick=Unnamed measured_at=1546825000 updated_at=1546825000 pid_error=3.0372049982 pid_error_sum=3.0372049982 pid_bw=1058329 pid_delta=2.57241663289 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-20.8:21.6-done-2019-01-06-19:36:40
+node_id=$A69221A7EC7498D2F88A0FB795261013FA36CAAE bw=43200 nick=Truie measured_at=1546636735 updated_at=1547398176 pid_error=3.03253884069 pid_error_sum=3.03253884069 pid_bw=85001410 pid_delta=0.81158156835 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-6.0:6.8-done-2019-01-13-10:49:36
+node_id=$5E2659DD3759EFDEC7E2D5875B19DF8D0C721382 bw=16400 nick=AnonUS00 measured_at=1547431499 updated_at=1547450511 pid_error=3.02419683051 pid_error_sum=3.02419683051 pid_bw=22666638 pid_delta=2.86094775843 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$30009B6F5D86D0DECBF0ECC3B4BFA79DE599FFB7 bw=3840 nick=BigBiscuitRelay measured_at=1547335692 updated_at=1547475691 pid_error=2.9825636997 pid_error_sum=2.9825636997 pid_bw=8156290 pid_delta=0.233684641697 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-6.1:6.9-done-2019-01-14-08:21:31
+node_id=$60E4C5E306D2DB22890EE24A09F9B6C30AF396A8 bw=44200 nick=rgiad measured_at=1547431499 updated_at=1547431499 pid_error=2.92414716952 pid_error_sum=2.92414716952 pid_bw=44204984 pid_delta=2.70042661837 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-12.0:12.8-done-2019-01-13-20:04:59
+node_id=$8EA05E196BA2321863FDF995CE33498F67283F51 bw=19400 nick=deoTOR measured_at=1545535177 updated_at=1545535177 pid_error=2.86419067275 pid_error_sum=2.86419067275 pid_bw=19376358 pid_delta=-0.664508975613 circ_fail=0.2 scanner=/scanner.2/scan-data/bws-17.5:18.3-done-2018-12-22-21:19:37
+node_id=$1AE180250D80A692077EDA35CC8E7387A9FDF220 bw=6000 nick=Yossarian measured_at=1547464113 updated_at=1547464113 pid_error=2.81619483316 pid_error_sum=2.81619483316 pid_bw=6002355 pid_delta=0.784721374473 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-19.7:20.4-done-2019-01-14-05:08:33
+node_id=$5AFAC3D00E97D6733112CC9CA2A788691FA87125 bw=187000 nick=UOfMichigan measured_at=1547370922 updated_at=1547370922 pid_error=2.81360596433 pid_error_sum=2.81360596433 pid_bw=187164664 pid_delta=0.963226471284 circ_fail=0.0416666666667 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-13-03:15:22
+node_id=$F1C7FDF6AAFAB12C62D3CE2AD22735A837AB8BB5 bw=7970 nick=defusedotcatwo measured_at=1547344176 updated_at=1547344176 pid_error=2.79808693935 pid_error_sum=2.79808693935 pid_bw=7965165 pid_delta=-1.0106749269 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-25.8:26.5-done-2019-01-12-19:49:36
+node_id=$157AEF204E9594AB8FDB879A2644A4EAF64950D6 bw=310 nick=CdemyStruggle measured_at=1547438428 updated_at=1547438428 pid_error=2.77813228798 pid_error_sum=2.77813228798 pid_bw=309504 pid_delta=2.03525711743 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-13.5:14.3-done-2019-01-13-22:00:28
+node_id=$2CD336B8E6C0F0F74A27E07BA0DAAD9EE9CD21FA bw=16800 nick=gimli measured_at=1546206101 updated_at=1547391857 pid_error=2.74342074532 pid_error_sum=2.74342074532 pid_bw=27693530 pid_delta=2.29460549951 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-5.3:6.1-done-2019-01-13-09:04:17
+node_id=$9D07DFA6472B80277798D73234348CEF02F2E7D5 bw=383 nick=incircuitryrelay measured_at=1547447078 updated_at=1547447078 pid_error=2.74065850802 pid_error_sum=2.74065850802 pid_bw=383043 pid_delta=1.87044691149 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$3DF9F80FF3675B2CD0C45AF4E6804291E4CB85E2 bw=20300 nick=ServbrQuebec measured_at=1546212073 updated_at=1547371078 pid_error=2.73928331959 pid_error_sum=2.73928331959 pid_bw=44051068 pid_delta=1.43671309493 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-12.0:12.8-done-2019-01-13-03:17:58
+node_id=$1938EBACBB1A7BFA888D9623C90061130E63BB3F bw=32100 nick=Aerodynamik04 measured_at=1546555442 updated_at=1547464113 pid_error=2.73468717802 pid_error_sum=2.73468717802 pid_bw=44430945 pid_delta=1.31330651081 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-19.7:20.4-done-2019-01-14-05:08:33
+node_id=$70953563B6AC3EF22E0754B7345757C6FB205989 bw=36700 nick=myTorVer measured_at=1546631132 updated_at=1547441722 pid_error=2.70961571778 pid_error_sum=2.70961571778 pid_bw=36676006 pid_delta=-1.46085560129 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$4A0242BED06138AD6BB1FE103A1D5D3AA9E97C05 bw=33100 nick=cosmicsurfin measured_at=1546689101 updated_at=1546689101 pid_error=2.69238369136 pid_error_sum=2.69238369136 pid_bw=33050135 pid_delta=1.34773551358 circ_fail=0.2 scanner=/scanner.2/scan-data/bws-17.6:18.5-done-2019-01-05-05:51:41
+node_id=$D0D93D5E8B46BD72B68617B402E2DBC16F8AA040 bw=13500 nick=OROGOM measured_at=1547142918 updated_at=1547458057 pid_error=2.68843756622 pid_error_sum=2.68843756622 pid_bw=20787030 pid_delta=-0.0938413452904 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-3.1:3.8-done-2019-01-14-03:27:37
+node_id=$B79C84DCA1ABE21119997EEC77D3999009736EFB bw=11600 nick=Unnamed measured_at=1546547153 updated_at=1547447078 pid_error=2.66893318582 pid_error_sum=2.66893318582 pid_bw=18784937 pid_delta=2.06703957345 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$E0102A68A1A58E73371A8FAB46A5AC0F77CDADFF bw=7790 nick=bewilderedchain measured_at=1545436597 updated_at=1545436597 pid_error=2.66676878742 pid_error_sum=2.66676878742 pid_bw=7789943 pid_delta=2.09580989456 circ_fail=0.5 scanner=/scanner.9/scan-data/bws-0.0:100.0-done-2018-12-21-17:56:37
+node_id=$848B1A050DC35FC898EB3FA562D826703944FC5B bw=16500 nick=Mellisa measured_at=1547405236 updated_at=1547405236 pid_error=2.63925426713 pid_error_sum=2.63925426713 pid_bw=16537997 pid_delta=0.563960863454 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-28.8:29.5-done-2019-01-13-12:47:16
+node_id=$F004C51B7C3136D7DFC20469AA5329B8372A263A bw=2370 nick=rortor2 measured_at=1547447078 updated_at=1547447078 pid_error=2.62175449255 pid_error_sum=2.62175449255 pid_bw=2373553 pid_delta=1.13726291161 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$921EF3940950203B8AF9B5070F56B8DA812A170A bw=14800 nick=richard measured_at=1546656937 updated_at=1546656937 pid_error=2.61496843129 pid_error_sum=2.61496843129 pid_bw=14806910 pid_delta=-0.696588486883 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-23.3:24.1-done-2019-01-04-20:55:37
+node_id=$D210BF09162062C05E931F71BAECD93DD9102F1C bw=16300 nick=StopFossilFuels measured_at=1547370922 updated_at=1547447078 pid_error=2.59286482609 pid_error_sum=2.59286482609 pid_bw=22074561 pid_delta=0.795830793593 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$F58639DBA30CAE5949BE4D6C78C721E618C8D08F bw=7350 nick=dreggle measured_at=1545717405 updated_at=1545717405 pid_error=2.58867841372 pid_error_sum=2.58867841372 pid_bw=7349613 pid_delta=0.968312892999 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-26.6:27.4-done-2018-12-24-23:56:45
+node_id=$E254FA3A1E04C96C13ADA297DCD9BB03DE8B1971 bw=4690 nick=Infincia1 measured_at=1547475855 updated_at=1547475855 pid_error=2.57526768973 pid_error_sum=2.57526768973 pid_bw=4686174 pid_delta=1.38093551115 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-22.7:23.4-done-2019-01-14-08:24:15
+node_id=$09F64E00F34C88F604163F24D37BEAF9245702EA bw=14700 nick=Hermes measured_at=1546563687 updated_at=1547487689 pid_error=2.56190684112 pid_error_sum=2.56190684112 pid_bw=28011975 pid_delta=0.333064047768 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-7.7:8.4-done-2019-01-14-11:41:29
+node_id=$7B405EFF6629D0564D7A0DE4C62371C2B5BEF1D1 bw=11700 nick=k0nslNET10 measured_at=1546625602 updated_at=1546625602 pid_error=2.53176538305 pid_error_sum=2.53176538305 pid_bw=11741661 pid_delta=-1.90535853858 circ_fail=0.0 scanner=/scanner.5/scan-data/bws-57.3:58.1-done-2019-01-04-12:13:22
+node_id=$460E5B882770C19761BC5747541913DB2AD01E35 bw=22700 nick=FissionEx2 measured_at=1546582479 updated_at=1547447078 pid_error=2.51057798517 pid_error_sum=2.51057798517 pid_bw=41803207 pid_delta=2.0003453289 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$7679B57C2167C7DF72D7389A24DF88A97729FC24 bw=6330 nick=Unnamed measured_at=1546713823 updated_at=1547325559 pid_error=2.49168468923 pid_error_sum=2.49168468923 pid_bw=12102406 pid_delta=-0.0521337814059 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-33.3:34.1-done-2019-01-12-14:39:19
+node_id=$0FDFB4F8159D602188BC387B5FC763183FBC4FF9 bw=36400 nick=Andromeda measured_at=1547458057 updated_at=1547458057 pid_error=2.47439215619 pid_error_sum=2.47439215619 pid_bw=36431642 pid_delta=-2.67781006956 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-3.1:3.8-done-2019-01-14-03:27:37
+node_id=$A98B733CE499948965C645F59128F46CEED39164 bw=19700 nick=StayOutGuberment measured_at=1547441722 updated_at=1547441722 pid_error=2.47328610761 pid_error_sum=2.47328610761 pid_bw=19733992 pid_delta=0.795497656842 circ_fail=0.6 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$9AD12F0E3CC871D59ACA14BB4076CDD8CB28DE57 bw=3570 nick=UncleEnzo measured_at=1547287075 updated_at=1547287075 pid_error=2.40914063544 pid_error_sum=2.40914063544 pid_bw=3574743 pid_delta=1.75735435289 circ_fail=0.0454545454545 scanner=/scanner.2/scan-data/bws-19.7:20.5-done-2019-01-12-03:57:55
+node_id=$3C98FA4BA7A0F6082AAC380F5E6E23BFD540B5ED bw=17900 nick=eax measured_at=1547357121 updated_at=1547357121 pid_error=2.40674122198 pid_error_sum=2.40674122198 pid_bw=17861135 pid_delta=2.10444762684 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-29.7:30.4-done-2019-01-12-23:25:21
+node_id=$B908BBB2CED0B06B0F83093D6D1D78A4CEF06CD9 bw=15600 nick=c17h13cln4 measured_at=1546854593 updated_at=1546854593 pid_error=2.39128392521 pid_error_sum=2.39128392521 pid_bw=15608238 pid_delta=2.20492080344 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-48.2:49.0-done-2019-01-07-03:49:53
+node_id=$5B6B34D687D3F1AADC183635DE2EE6AAB5B5D863 bw=14300 nick=Unnamed measured_at=1547292408 updated_at=1547292408 pid_error=2.39081299077 pid_error_sum=2.39081299077 pid_bw=14284983 pid_delta=1.96461701505 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-28.1:28.8-done-2019-01-12-05:26:48
+node_id=$A81B6E42E491BB0639E76F7A61E702C4E85DE73D bw=520 nick=CommentCrpenter measured_at=1546414024 updated_at=1546414024 pid_error=2.38561437448 pid_error_sum=2.38561437448 pid_bw=520030 pid_delta=0.764116458463 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-45.9:46.8-done-2019-01-02-01:27:04
+node_id=$75784DD2BCB70CE13CEC93CFE24CD69C66E2C67B bw=29300 nick=TORxyz measured_at=1546654571 updated_at=1547312260 pid_error=2.38121392551 pid_error_sum=2.38121392551 pid_bw=38385494 pid_delta=0.895287996902 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-5.3:6.1-done-2019-01-12-10:57:40
+node_id=$5E2975B380F6E908AFB29E0D8D0B967AF73B41C2 bw=24100 nick=WombleNode01 measured_at=1546582479 updated_at=1547470234 pid_error=2.3660739726 pid_error_sum=2.3660739726 pid_bw=42325741 pid_delta=1.52524581825 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-5.4:6.1-done-2019-01-14-06:50:34
+node_id=$D1EB3C54229DCF936B5B6A817ECDBE3FCF2FA3AE bw=9880 nick=NeelTorExit1 measured_at=1547464113 updated_at=1547464113 pid_error=2.36268955105 pid_error_sum=2.36268955105 pid_bw=9881448 pid_delta=1.4567259005 circ_fail=0.0526315789474 scanner=/scanner.2/scan-data/bws-19.7:20.4-done-2019-01-14-05:08:33
+node_id=$FB07F10AE9DCDFC43E24C50D399D42DE89BD428E bw=88100 nick=stannaz measured_at=1547367756 updated_at=1547367756 pid_error=2.36015297822 pid_error_sum=2.36015297822 pid_bw=88084394 pid_delta=1.94151032693 circ_fail=0.8 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-13-02:22:36
+node_id=$475B34D76756910C11EB7752EB8285F6BE00C1EE bw=40000 nick=yewDelroth measured_at=1547445863 updated_at=1547445863 pid_error=2.35607360429 pid_error_sum=2.35607360429 pid_bw=39964446 pid_delta=1.25412474733 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-15.1:15.9-done-2019-01-14-00:04:23
+node_id=$FE296180018833AF03A8EACD5894A614623D3F76 bw=21800 nick=PyotrTorpotkinOne measured_at=1546325250 updated_at=1547421233 pid_error=2.35403562678 pid_error_sum=2.35403562678 pid_bw=24588914 pid_delta=0.394362497662 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-21.9:22.6-done-2019-01-13-17:13:53
+node_id=$5B97C204E7CC2776404835FE6D93185CDD9EBCFC bw=31000 nick=faentadeglinode measured_at=1546737289 updated_at=1546737289 pid_error=2.34488143951 pid_error_sum=2.34488143951 pid_bw=30962463 pid_delta=0.327595639275 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-14.4:15.2-done-2019-01-05-19:14:49
+node_id=$1E9E1F983A6919A5D1642BAF76474AD75D6369C4 bw=39600 nick=JusticeForMikeBrown measured_at=1547434451 updated_at=1547434451 pid_error=2.34328476825 pid_error_sum=2.34328476825 pid_bw=39574084 pid_delta=0.96855315236 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-11.5:12.3-done-2019-01-13-20:54:11
+node_id=$32D446F2069CE686D5767922FB6BD25BB6866FDA bw=18000 nick=lemonW measured_at=1546870019 updated_at=1546870019 pid_error=2.34288435429 pid_error_sum=2.34288435429 pid_bw=18042813 pid_delta=2.22256399723 circ_fail=0.0 scanner=/scanner.5/scan-data/bws-50.0:50.8-done-2019-01-07-08:06:59
+node_id=$9517E2D2872F74BD0B8313E031C6CE321314F244 bw=19800 nick=Unnamed measured_at=1547466038 updated_at=1547466038 pid_error=2.34118154459 pid_error_sum=2.34118154459 pid_bw=19768197 pid_delta=1.96472194765 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-31.1:31.9-done-2019-01-14-05:40:38
+node_id=$F81C9B79150D85D78B4DF5D70A1789FBE0D608A4 bw=6570 nick=IPreferFreedom measured_at=1547328340 updated_at=1547328340 pid_error=2.33174692442 pid_error_sum=2.33174692442 pid_bw=6572807 pid_delta=-0.426586946032 circ_fail=0.0 scanner=/scanner.3/scan-data/bws-34.1:34.8-done-2019-01-12-15:25:40
+node_id=$98473936F2257E53BB4397382CA53C26C6DA2EA1 bw=10900 nick=PulseboxExit measured_at=1547458057 updated_at=1547458057 pid_error=2.325102956 pid_error_sum=2.325102956 pid_bw=10895697 pid_delta=0.925888384101 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-3.1:3.8-done-2019-01-14-03:27:37
+node_id=$8FC8CA09759EAFE3DE1D5EC4D8C926B2376463CE bw=35900 nick=librarytest measured_at=1546914496 updated_at=1546914496 pid_error=2.29763567935 pid_error_sum=2.29763567935 pid_bw=35873933 pid_delta=0.810726631711 circ_fail=0.4 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-07-20:28:16
+node_id=$4AA5669E80CC6265C88A4A8ECE289778286E78C5 bw=16000 nick=nodvrelay10 measured_at=1545971513 updated_at=1545971513 pid_error=2.28629872722 pid_error_sum=2.28629872722 pid_bw=16030509 pid_delta=1.56264459204 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-24.8:25.6-done-2018-12-27-22:31:53
+node_id=$ACF6F6826CE35AE298CE17171B79FFF64560CBE0 bw=6830 nick=euporth measured_at=1546915291 updated_at=1546915291 pid_error=2.27737530527 pid_error_sum=2.27737530527 pid_bw=6827824 pid_delta=2.63114587417 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-46.6:47.4-done-2019-01-07-20:41:31
+node_id=$F00EC2E0A2CA79A57FE7A0918A087987747D772D bw=8910 nick=PancakeWhore measured_at=1546148651 updated_at=1547350028 pid_error=2.26852045076 pid_error_sum=2.26852045076 pid_bw=13709168 pid_delta=1.08312572558 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-11.6:12.3-done-2019-01-12-21:27:07
+node_id=$7CD234DFEFE8F215EC9AE2BC1D2CAFC9E0D1CC00 bw=36800 nick=KD8HLN measured_at=1547420794 updated_at=1547458057 pid_error=2.24421900896 pid_error_sum=2.24421900896 pid_bw=34571182 pid_delta=0.0236021986166 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-3.1:3.8-done-2019-01-14-03:27:37
+node_id=$8C35B15C1F138BDE4D50A975129E4773B44A503D bw=4900 nick=HappyNode11 measured_at=1547200387 updated_at=1547450511 pid_error=2.23255823804 pid_error_sum=2.23255823804 pid_bw=6779165 pid_delta=0.365782282637 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-14-01:21:51
+node_id=$A4570BC41CBC564895B5A50894598B2363679838 bw=492 nick=BlnketEnrich measured_at=1547447078 updated_at=1547447078 pid_error=2.20491048829 pid_error_sum=2.20491048829 pid_bw=492274 pid_delta=0.148325093505 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$9E2D7C6981269404AA1970B53891701A20424EF8 bw=109000 nick=Quintex47 measured_at=1547475855 updated_at=1547475855 pid_error=2.19395898033 pid_error_sum=2.19395898033 pid_bw=108847927 pid_delta=0.93760722065 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-22.7:23.4-done-2019-01-14-08:24:15
+node_id=$B9BC5383CF4E0C24AC18E805710E9B622B3AE780 bw=17000 nick=pawn measured_at=1546247644 updated_at=1547441722 pid_error=2.18324268477 pid_error_sum=2.18324268477 pid_bw=18762242 pid_delta=0.216953417837 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$9456BBB3FB923DD1CDEC5C8B45D9615BC937DC42 bw=43800 nick=Totonicapanp5 measured_at=1547109968 updated_at=1547441722 pid_error=2.18061104345 pid_error_sum=2.18061104345 pid_bw=58364467 pid_delta=0.547614586602 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$87E6E1473A846D6478731EAAB3F65AAA511A9BB3 bw=7490 nick=ab5k measured_at=1545322712 updated_at=1545322712 pid_error=2.1787801914 pid_error_sum=2.1787801914 pid_bw=7492791 pid_delta=1.76315203422 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-38.6:39.3-done-2018-12-20-10:18:32
+node_id=$01AE2DE314276C82FCCC3603A1C2F3238E6544C9 bw=172000 nick=rixtyminutes measured_at=1546827582 updated_at=1547441722 pid_error=2.17438742062 pid_error_sum=2.17438742062 pid_bw=81790793 pid_delta=2.81185108527 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22
+node_id=$D4A822F618B90F24B52FDAEFD24498CBC617C2C3 bw=18400 nick=Knetwerk measured_at=1546405602 updated_at=1547383868 pid_error=2.16517293319 pid_error_sum=2.16517293319 pid_bw=6706982 pid_delta=2.34853873514 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-3.8:4.6-done-2019-01-13-06:51:08
+node_id=$C7946D9A192BBE44C1C8A926A8B135AC495D6636 bw=87900 nick=t0adwarri0r measured_at=1546657461 updated_at=1547447078 pid_error=2.16060698212 pid_error_sum=2.16060698212 pid_bw=59606579 pid_delta=0.100561983105 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$A7A9F4B9D4157F0F4AB96AF83F3F188B7E685539 bw=39000 nick=NewYorkInternet0 measured_at=1547259868 updated_at=1547447078 pid_error=2.15314938014 pid_error_sum=2.15314938014 pid_bw=32288249 pid_delta=0.276960569633 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-0.8:1.5-done-2019-01-14-00:24:38
+node_id=$CC7731521D8906B32A846A9632876B6F4760DFD0 bw=6170 nick=warmgiants measured_at=1545436597 updated_at=1545436597 pid_error=2.14648146049 pid_error_sum=2.14648146049 pid_bw=6167997 pid_delta=0.573165860646 circ_fail=0.5 scanner=/scanner.9/scan-data/bws-0.0:100.0-done-2018-12-21-17:56:37
+node_id=$C1B8C6887867CED2564454058F9082311FAF1AC7 bw=47400 nick=MapleCrew measured_at=1546598836 updated_at=1547454881 pid_error=2.13221557608 pid_error_sum=2.13221557608 pid_bw=57236823 pid_delta=1.7428610141 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-2.3:3.1-done-2019-01-14-02:34:41
+node_id=$3D63B52E0305C32A7BC0988832BE26C568C19A37 bw=19300 nick=Abbey1 measured_at=1547455499 updated_at=1547455499 pid_error=2.13232754182 pid_error_sum=2.13232754182 pid_bw=19266060 pid_delta=1.3049755929 circ_fail=0.0 scanner=/scanner.2/scan-data/bws-17.4:18.2-done-2019-01-14-02:44:59
+node_id=$EE69F3F8262DBFB97110BDDEBE67072366DA8F8F bw=8770 nick=Unnamed measured_at=1546112951 updated_at=1547166220 pid_error=2.12494227861 pid_error_sum=2.12494227861 pid_bw=7677386 pid_delta=1.86501274676 circ_fail=0.0 scanner=/scanner.4/scan-data/bws-46.7:47.5-done-2019-01-10-18:23:40
+node_id=$05DD3981CAD87DEDBC8ADCF1AC6981C65585DA99 bw=6710 nick=cpu measured_at=1547370922 updated_at=1547370922 pid_error=2.1221923319 pid_error_sum=2.1221923319 pid_bw=6709763 pid_delta=-0.0151740262706 circ_fail=0.0 scanner=/scanner.1/scan-data/bws-1.5:2.3-done-2019-01-13-03:15:22
diff --git a/test/unit/descriptor/data/bwauth_v1.2 b/test/unit/descriptor/data/bwauth_v1.2
new file mode 100644
index 00000000..078f7501
--- /dev/null
+++ b/test/unit/descriptor/data/bwauth_v1.2
@@ -0,0 +1,95 @@
+1547444099
+version=1.2.0
+earliest_bandwidth=2019-01-04T05:35:29
+file_created=2019-01-14T05:35:06
+generator_started=2019-01-03T22:45:08
+latest_bandwidth=2019-01-14T05:34:59
+minimum_number_eligible_relays=3908
+minimum_percent_eligible_relays=60
+number_consensus_relays=6514
+number_eligible_relays=6256
+percent_eligible_relays=96
+software=sbws
+software_version=1.0.2
+=====
+bw=1 bw_mean=191643 bw_median=218251 desc_bw_avg=262144 desc_bw_obs_last=316578 desc_bw_obs_mean=311101 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=u4wsHWWosT+yKp1tZQ7UMAM4Pp5X+rIuwlhHJ5fojMg nick=mrkoolltor node_id=$92808CA58D8F32CA34A34C547610869BF4E2A6EC success=10 time=2019-01-12T15:00:59
+bw=1 bw_mean=93766 bw_median=93606 desc_bw_avg=102400 desc_bw_obs_last=120422 desc_bw_obs_mean=121104 error_circ=0 error_misc=0 error_stream=0 nick=ididnteditheconfig node_id=$A6443E49306288C7DAE9B8466568F08DA5BD58D4 success=12 time=2019-01-13T11:12:49
+bw=1 bw_mean=291995 bw_median=319875 desc_bw_avg=524288 desc_bw_obs_last=540643 desc_bw_obs_mean=541254 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=wquF5k6G47/FMhWicrwqtVWkmKqyrCM7k9j6KXe5Awg nick=onionmatic node_id=$BB9C5D15BC3B77C8AF5CBC733F7E54553A1B7BD5 success=13 time=2019-01-13T11:14:00
+bw=1 bw_mean=194765 bw_median=223050 desc_bw_avg=384000 desc_bw_obs_last=407911 desc_bw_obs_mean=403894 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=M8ziUfRRoCytvFNE0tr1VoDh1nd+fsCTLPIXERQC2Gs nick=notorious3 node_id=$3444F4ACA2A1AB4A5E9DC64295A6D414A03D3503 success=12 time=2019-01-13T11:14:30
+bw=1 bw_mean=177798 bw_median=200287 desc_bw_avg=262144 desc_bw_obs_last=260713 desc_bw_obs_mean=259066 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=c9YQeZWrD/mQs89J3B7img/hIahpyP9Gt/tOPJd1wu8 nick=unlobitophx0 node_id=$03A2F59C7415951F16EF5F0FA7A266E63656DFE6 success=13 time=2019-01-13T11:14:52
+bw=1 bw_mean=200972 bw_median=175347 desc_bw_avg=393216 desc_bw_obs_last=0 desc_bw_obs_mean=448679 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=OYCY+eMpI1GzAaODEEG3DvlvfYkO8lEE48Idb6l4u4c nick=oberon node_id=$B4486871EA062BAF2354F070FECC344CF3C20880 success=7 time=2019-01-13T15:34:10
+bw=1 bw_mean=209076 bw_median=229461 desc_bw_avg=262144 desc_bw_obs_last=314494 desc_bw_obs_mean=318895 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=eEWBK54PEh3Br8mZNU+uO3o4qS4SBz/h+nq2LfYOwdo nick=jaymzeurelay node_id=$B47C9A24AFFCCF19B84CE8C4020E2F6D3B3012EE success=6 time=2019-01-09T07:06:50
+bw=1 bw_mean=81848 bw_median=92458 desc_bw_avg=102400 desc_bw_obs_last=120377 desc_bw_obs_mean=118737 error_circ=0 error_misc=0 error_stream=0 nick=kangapi node_id=$6C11903D7D5C06D0872D889600E801E568DBB87B success=12 time=2019-01-13T11:23:49
+bw=1 bw_mean=92348 bw_median=93569 desc_bw_avg=102400 desc_bw_obs_last=123904 desc_bw_obs_mean=126445 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=oJd/o/A67ZPIkGF24pA6662PC32Y+i83Snwr1lJ0lJs nick=isnt node_id=$97056591E9E58172FF76CC8B2187B37516511C76 success=13 time=2019-01-13T11:30:38
+bw=1 bw_mean=83597 bw_median=84026 desc_bw_avg=102400 desc_bw_obs_last=111644 desc_bw_obs_mean=111675 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=PtXt6WjUG+mJF70XRiFYkvnxNw5cP0ojtZtFI5TuJxg nick=Unnamed node_id=$2957DB248552E00541BE84CB212DA7EF904DADE6 success=5 time=2019-01-13T11:24:42
+bw=1 bw_mean=88821 bw_median=93218 desc_bw_avg=102400 desc_bw_obs_last=120892 desc_bw_obs_mean=120246 error_circ=0 error_misc=0 error_stream=0 nick=FreeSnazzy node_id=$B4195E18E80C130041E05412F05B5FE7BCDD2AA8 success=13 time=2019-01-13T11:26:49
+bw=1 bw_mean=178177 bw_median=210259 desc_bw_avg=294912 desc_bw_obs_last=283648 desc_bw_obs_mean=283475 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=/ueCC06g0DrAtxJVLGm9Ua17VoadrcPU+UDeLnFai1w nick=upperMidwestHell node_id=$7466E2975B96225EFE7ADB9D719CD055F67D3FA4 success=13 time=2019-01-13T11:26:50
+bw=1 bw_mean=175635 bw_median=155201 desc_bw_avg=384000 desc_bw_obs_last=401408 desc_bw_obs_mean=401582 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=vKihMGuPRYHkQnx5j0dXiN8pVHC7mP/a1OtSBGGm2Dg nick=notorious4 node_id=$F19EEFDF2FB2EFFC00EB719FC5661114638BFB8C success=9 time=2019-01-12T15:13:40
+bw=1 bw_mean=102995 bw_median=105830 desc_bw_avg=131072 desc_bw_obs_last=143917 desc_bw_obs_mean=143264 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=n8SidhwQyWDkXXCpgeDHVOeLVic4/f7bXlo+Zrkagis nick=Unnamed node_id=$7BB7B4697E03462B4884E25D73462CE5AA18DF85 success=10 time=2019-01-13T05:11:19
+bw=1 bw_mean=45605 bw_median=49198 desc_bw_avg=1024000 desc_bw_obs_last=126432 desc_bw_obs_mean=114015 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=UBP2OqIg0+x1IRkXll4aFpNXmu4eDPM3JCElYPeXtKQ nick=WhatTheF0ckIsThis node_id=$8BA674CAD7D80D59231BA171DE70C471C585A716 success=12 time=2019-01-13T11:28:59
+bw=1 bw_mean=113778 bw_median=32927 desc_bw_avg=524288 desc_bw_obs_last=594653 desc_bw_obs_mean=586977 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=c/SrrwbI7FAvIWa5xXac/atApIs5RfzwLStqu7W98EI nick=ten05 node_id=$24DB33DBAB74633ACE7BAA9DC61230179BCF13E1 success=10 time=2019-01-13T11:30:46
+bw=1 bw_mean=28721 bw_median=30383 desc_bw_avg=76800 desc_bw_obs_last=103070 desc_bw_obs_mean=58432 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=VY8EuVyc6gnXkZs1LmXTT3BCc82gUtYeLPFZU8+noAE nick=Unnamed node_id=$99DEAFE8623642451EB8F7C1B8BC026457FAF85B success=11 time=2019-01-13T11:33:40
+bw=1 bw_mean=87017 bw_median=93135 desc_bw_avg=102400 desc_bw_obs_last=116907 desc_bw_obs_mean=117753 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=/jotU7dV0oYGf3ZK9kA13ju5RsuZvSOGmnoX7OodAAA nick=mdarder node_id=$B3BA35E2A6ECABFF801B4BE2E861053C0C4D694F success=12 time=2019-01-13T11:34:42
+bw=1 bw_mean=186545 bw_median=209249 desc_bw_avg=256000 desc_bw_obs_last=296605 desc_bw_obs_mean=297959 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=LBTo++vxfE26Ols7V+u+BWbLymkvUlpdmsGoJADVDSg nick=chieftain node_id=$72749A538E9ACA13114B38D2E6DDC6AFDE1859B5 success=12 time=2019-01-13T11:33:57
+bw=1 bw_mean=158154 bw_median=159939 desc_bw_avg=1073741824 desc_bw_obs_last=390295 desc_bw_obs_mean=376804 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=q6be/fbH9b5IPZ3odvJCIspgkWaxhrU+MzP28NriYa4 nick=idideditheconfig node_id=$B2EDD607E7A94E5FE56BBFA6C15F9EB4F6869DDA success=12 time=2019-01-13T11:35:40
+bw=1 bw_mean=282896 bw_median=305702 desc_bw_avg=204800 desc_bw_obs_last=449536 desc_bw_obs_mean=455392 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=Jrbjvjtq/IySPqh5qoW+HgfuX3tfbbUnC1le4MKg8ag nick=Unnamed node_id=$262CF64E5974DDEEA97BB8593878B5E5F9F75153 success=12 time=2019-01-13T11:35:31
+bw=1 bw_mean=87019 bw_median=92630 desc_bw_avg=102400 desc_bw_obs_last=121057 desc_bw_obs_mean=120692 error_circ=0 error_misc=0 error_stream=0 nick=Mischmaschine node_id=$33C31C2011271DE71FFA288F37A3AD24A6519C49 success=12 time=2019-01-13T11:35:39
+bw=1 bw_mean=240706 bw_median=181975 desc_bw_avg=2097152 desc_bw_obs_last=64512 desc_bw_obs_mean=824015 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=JArSJi19OsQVckfXpFXZnMuEA3X2DIkkQrtRhPX5ItU nick=noxdafox node_id=$D6C977DF0968848878AC02E21F78A57B84BE49B3 success=8 time=2019-01-12T20:58:32
+bw=1 bw_mean=232070 bw_median=234670 desc_bw_avg=393216 desc_bw_obs_last=471147 desc_bw_obs_mean=475130 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=bVPbA2UtosbqMYwUBTeHrN6zFe1gs260tZA6mcG8CPU nick=showpen node_id=$A18BF818557BD6C8A3546B157B7800B5F24A79A7 success=12 time=2019-01-13T11:36:20
+bw=1 bw_mean=113962 bw_median=112379 desc_bw_avg=1073741824 desc_bw_obs_last=179447 desc_bw_obs_mean=183898 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=X06Zdk5uSIrabY7qsGKyaJmyS6Lnq2R48dbZrz4+J3o nick=Unnamed node_id=$3EFCAF489734DADCD941CAA0DB615B43BD7BA74C success=11 time=2019-01-13T11:38:50
+bw=1 bw_mean=167892 bw_median=179346 desc_bw_avg=204800 desc_bw_obs_last=229610 desc_bw_obs_mean=228683 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=LcpNXBYe1Dgl8tw/09dEtS+OkLoJN2tpoJSZj/WzP6I nick=arg node_id=$6DA072B246D5B6A7A648ACA1F1F61B673243D556 success=11 time=2019-01-13T11:39:45
+bw=1 bw_mean=93346 bw_median=93509 desc_bw_avg=102400 desc_bw_obs_last=130087 desc_bw_obs_mean=130141 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=WRy3gOEJuRjN85xi7sIEEL1teQkHc2SN8VJ/GQ+kgaA nick=Unnamed node_id=$172375185493D9D6248B01CF9683A5409D0168FA success=13 time=2019-01-13T11:40:29
+bw=1 bw_mean=182524 bw_median=184954 desc_bw_avg=204800 desc_bw_obs_last=247713 desc_bw_obs_mean=247527 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=H5MT98W2Agyc2GKtRvk/UFI8ybxC3f9HSghG9+soLno nick=theredbaron node_id=$618B7A0FF82C36DB52F937F2B085FC0B588247FB success=13 time=2019-01-13T11:43:19
+bw=1 bw_mean=142866 bw_median=161557 desc_bw_avg=266240 desc_bw_obs_last=579240 desc_bw_obs_mean=546511 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=YbBYrs1fbBGKcTlMqzjEKdM7SQkXSDHb0NIwuPepNQs nick=gabriela node_id=$5D3A57F494FD0782762C508A6695F4FCFF161FA4 success=9 time=2019-01-10T02:13:31
+bw=1 bw_mean=110005 bw_median=112012 desc_bw_avg=122880 desc_bw_obs_last=150212 desc_bw_obs_mean=144967 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=Vbs+/l9Ut4/G0j6OwlSc6ZsZ+gwtmG3vAvC8jkuVpP8 nick=himbeerschnitte node_id=$38D08218624083D2A5AFAA161CFD62F7DB0231A4 success=13 time=2019-01-13T11:44:27
+bw=1 bw_mean=36899 bw_median=37652 desc_bw_avg=80896 desc_bw_obs_last=83530 desc_bw_obs_mean=86047 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=K9m8MIOa/NEIwmfwFJrm3p8SeW+lwzjqP66ygG/TP3o nick=salottisipuli node_id=$04ABF90AEF8556F3A7E0527722CDFA7FDCB66C59 success=13 time=2019-01-13T11:43:58
+bw=1 bw_mean=91449 bw_median=93522 desc_bw_avg=102400 desc_bw_obs_last=120832 desc_bw_obs_mean=122154 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=2TEiyoYdpgBO3Xi3h377CdFq/wdNpb+24ViEiG2Adgw nick=mrpye node_id=$0936D7646842FDAC735D1A8831C9F30DE68E8E52 success=11 time=2019-01-13T12:07:06
+bw=1 bw_mean=88803 bw_median=93466 desc_bw_avg=102400 desc_bw_obs_last=122439 desc_bw_obs_mean=123525 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=lxvLO1TcVxjokYZdGGxsNElI9YPTi0ovDLxi/XOlmjI nick=pollypurebred node_id=$EF37D150F3A851B32D9E843605D0EA8A1B938117 success=12 time=2019-01-13T11:46:16
+bw=1 bw_mean=84946 bw_median=93314 desc_bw_avg=102400 desc_bw_obs_last=132175 desc_bw_obs_mean=123225 error_circ=0 error_misc=0 error_stream=0 nick=deezrelays node_id=$43A78DBCE2550823DADB31B1497BA71FC93F8E17 success=11 time=2019-01-13T11:47:13
+bw=1 bw_mean=93156 bw_median=93090 desc_bw_avg=102400 desc_bw_obs_last=124659 desc_bw_obs_mean=124242 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=+uX3orprZyRKrQJa1FVDSTz+O4x7ymwREoj4NC6CvAc nick=Unnamed node_id=$27F890A58693EBB080184C8D8477F0281B2585F7 success=13 time=2019-01-13T11:49:10
+bw=1 bw_mean=89120 bw_median=93327 desc_bw_avg=102400 desc_bw_obs_last=121307 desc_bw_obs_mean=134821 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=mvPj0Zhw6ZrKSEqsS/8DNh/+S7UP+qSY1eR7akFYg0s nick=Unnamed node_id=$46E1C8048DABC5D0365CE87DEE715E443EC06F81 success=12 time=2019-01-13T11:50:24
+bw=1 bw_mean=166116 bw_median=187385 desc_bw_avg=256000 desc_bw_obs_last=277504 desc_bw_obs_mean=273870 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=9ZYgjGhL9XrpufK7g39UXJtpAdIots0M2i5APArwHFo nick=USA2001911 node_id=$80C7FD3964C8CB363D89D85FEA5856B6FD8CBB98 success=11 time=2019-01-13T11:48:21
+bw=1 bw_mean=202063 bw_median=226380 desc_bw_avg=358400 desc_bw_obs_last=345011 desc_bw_obs_mean=345931 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=n6CD17v4KNkc2x5qvAUCdlPMRjDBJ9u9mJUly+xO1WU nick=cyberspace1 node_id=$9155DBF11CA246908A5410EDA79FCF01DA44901B success=11 time=2019-01-13T14:31:58
+bw=1 bw_mean=191481 bw_median=177608 desc_bw_avg=512000 desc_bw_obs_last=513368 desc_bw_obs_mean=512206 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=SwKQ9vR9PPGXOHqEcy/TqOI4cfwGLjY3soNGXKq1gQY nick=amisraelchai node_id=$A09F25142569A899C2A5ACB3E1E9FA7E1CFCE4C2 success=5 time=2019-01-13T12:57:27
+bw=1 bw_mean=183391 bw_median=203507 desc_bw_avg=307200 desc_bw_obs_last=406853 desc_bw_obs_mean=405249 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=qWaizsvXrQhN+QWl4t03ACC7lOSzGLOkMPS6aGrLoIY nick=IM1TorRelay node_id=$F7C0E166F3CF89715E8AEC93E0DAA497649E3EDD success=4 time=2019-01-06T16:03:10
+bw=1 bw_mean=307132 bw_median=209588 desc_bw_avg=10485760 desc_bw_obs_last=0 desc_bw_obs_mean=6382901 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=Czc9qIZzJbWBh5rxuvc/KX3NF4biZWOIHri5FtWVPRs nick=F3Netze node_id=$E8C8667CAF3D5148E52ECF736A7B204982F78EAA success=13 time=2019-01-13T12:16:25
+bw=1 bw_mean=93021 bw_median=93091 desc_bw_avg=102400 desc_bw_obs_last=124984 desc_bw_obs_mean=123736 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=rE5ND61JaZnMv+9+y/5je8Xnh95f/ISNEQF25Hw9nTQ nick=wormhole node_id=$8B863F5CE8676EFE5BB070CE3CAF54A76AE74D1A success=13 time=2019-01-13T12:01:09
+bw=1 bw_mean=333157 bw_median=366652 desc_bw_avg=512000 desc_bw_obs_last=615280 desc_bw_obs_mean=606926 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=VFa9gpgEvYcRBt4HtDlMduOTPiHmBNAJOuZNOTVWAFg nick=nlpdk node_id=$F1855E09E867ECD00A4CB7D36D58170A7574C0FF success=13 time=2019-01-13T12:02:21
+bw=1 bw_mean=168271 bw_median=185301 desc_bw_avg=204800 desc_bw_obs_last=237494 desc_bw_obs_mean=231300 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=h6FaCKdGilVd5lcIjbS/T5vMhm3c5yL+2kel6qHqJVY nick=arg node_id=$0A25903A8F321D2F07B3C8CF03AE66D02BD5D1DB success=13 time=2019-01-13T12:03:01
+bw=1 bw_mean=104847 bw_median=69844 desc_bw_avg=512000 desc_bw_obs_last=428064 desc_bw_obs_mean=440259 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=E397a15VsA42YFkXUzkI9xI4Iou+YUnSWaOaisyMNN0 nick=dropsy node_id=$774555642FDC1E1D4FDF2E0C31B7CA9501C5C9C7 success=11 time=2019-01-13T12:03:23
+bw=1 bw_mean=80477 bw_median=80968 desc_bw_avg=10240000 desc_bw_obs_last=107374 desc_bw_obs_mean=111017 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=H+FN4p5Ra6xGnRmwNocowq6TEO4SVgryu5HMs9aTPPg nick=blblmawy33torrelay3 node_id=$2A8FC748AF325914C19A8AFC1DE248A37DAA5082 success=9 time=2019-01-11T16:57:15
+bw=1 bw_mean=288005 bw_median=308018 desc_bw_avg=358400 desc_bw_obs_last=527504 desc_bw_obs_mean=525863 error_circ=0 error_misc=0 error_stream=0 nick=k21hermes node_id=$D01956B555BA59D422D994D41AC9634183974597 success=13 time=2019-01-13T12:04:08
+bw=1 bw_mean=169833 bw_median=179997 desc_bw_avg=204800 desc_bw_obs_last=257614 desc_bw_obs_mean=259482 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=VyCjn54E04laJUvp8Rt2/oQ+6slSvRunnXqUyB/sm+c nick=furrynodes node_id=$37E4D922CFCDCFC7481CC34DB2AC7E7DE8C17E85 success=13 time=2019-01-13T11:45:48
+bw=1 bw_mean=150181 bw_median=183698 desc_bw_avg=204800 desc_bw_obs_last=255782 desc_bw_obs_mean=251693 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=/r3AmsebeYN9iC9JEqQJkN1UWq66d5/mNmrOYpguxeM nick=NorthTahoeTor2 node_id=$DC7614250C29ED6389BB1384A381CE60A3407B1E success=11 time=2019-01-13T12:05:37
+bw=1 bw_mean=244263 bw_median=262257 desc_bw_avg=524288 desc_bw_obs_last=496707 desc_bw_obs_mean=481959 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=TUI9zaIsL1IB3lyhgNygglOWRvtvdJJ3Kc3GT/HZVSU nick=botwing node_id=$00A8A90B091281D0A05830981F3CF8E7780B7736 success=12 time=2019-01-13T12:07:05
+bw=1 bw_mean=93569 bw_median=93768 desc_bw_avg=102400 desc_bw_obs_last=120958 desc_bw_obs_mean=121277 error_circ=0 error_misc=0 error_stream=0 nick=Saramaganta node_id=$0835A7733B3F1A89534E994A5A1111C65141AF53 success=11 time=2019-01-13T12:09:03
+bw=1 bw_mean=81301 bw_median=85024 desc_bw_avg=256000 desc_bw_obs_last=119797 desc_bw_obs_mean=117400 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=M3ilMnhaB5znu0J9SOQaRtWpywmvHo0woFAh0qj+FNE nick=FawkesSwissBlade node_id=$3146A339546E8D75D2BB4846C92C3698A40DCEF0 success=13 time=2019-01-13T12:10:21
+bw=1 bw_mean=292941 bw_median=199001 desc_bw_avg=10485760 desc_bw_obs_last=0 desc_bw_obs_mean=5393041 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=sSWyZJ/Ab5YjRO5R7Hh0v3/27BvIn4FXHsHgfMP8s0Q nick=F3Netze node_id=$98F793C7320CE3C15A45353AFCC165747A40366D success=9 time=2019-01-13T12:36:22
+bw=1 bw_mean=69844 bw_median=69712 desc_bw_avg=76800 desc_bw_obs_last=93245 desc_bw_obs_mean=93533 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=N149WWRq4x3FAxxXOGTLKJk9caBpgFV8Oybgj6IHKy8 nick=synapsethemole node_id=$DFB8A5A929D5C79FB47786026B25680B48AEA2A8 success=12 time=2019-01-13T12:15:31
+bw=1 bw_mean=83628 bw_median=87844 desc_bw_avg=102400 desc_bw_obs_last=108554 desc_bw_obs_mean=106485 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=rUrMaq1m7bEcHCO3sKWryYkTA6d7REXT6RXLACYEwuM nick=OneFreeInternet node_id=$042094D4D7AA24A2436534B373C838DF45D2EF5C success=11 time=2019-01-13T12:15:53
+bw=1 bw_mean=66578 bw_median=74544 desc_bw_avg=81920 desc_bw_obs_last=93755 desc_bw_obs_mean=94783 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=HMDzIHnvyzveJlPhVhaan2e5RUblq9q1tLvycuaV1U4 nick=THOR node_id=$93C6BE420FBD2327DB591A372C58807BB788D79C success=12 time=2019-01-13T12:15:51
+bw=1 bw_mean=127810 bw_median=127310 desc_bw_avg=153600 desc_bw_obs_last=172476 desc_bw_obs_mean=173584 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=foPUucEW1ZVns8cLN+0W/N22TXrYXxQu1xqFmexqVYk nick=micheletor node_id=$D4DA0ED8FFB177BE043F09628AAEDDCF93C8609A success=9 time=2019-01-13T12:16:41
+bw=1 bw_mean=162644 bw_median=162991 desc_bw_avg=204800 desc_bw_obs_last=221431 desc_bw_obs_mean=220284 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=oRgavbK5kRsCgcoT7wNtDVR1V4dxywW8hXZav9ygXXI nick=Dementor node_id=$46D79CCB83639718177B4BDB8AED6BE9378B1D0F success=4 time=2019-01-11T20:24:34
+bw=1 bw_mean=172503 bw_median=184810 desc_bw_avg=204800 desc_bw_obs_last=230600 desc_bw_obs_mean=230643 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=rLAC/dUsZqdZtqQmlNOJagrQy1ZNvQSOKbLzs4s/7ZQ nick=rwxrwxrwx node_id=$F7F50F492DF23FD82DF0BA351AC506D41FE810B3 success=10 time=2019-01-13T12:17:39
+bw=1 bw_mean=302992 bw_median=345820 desc_bw_avg=524288 desc_bw_obs_last=490185 desc_bw_obs_mean=487498 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=lI/euhK6+xEeVmwv4yz7epeQ3ZAl2IszSFeRcKjtNcc nick=pansomatik node_id=$5EBEE2C84DCD8C82D8B54E10BB9EE68F3443F3B3 success=12 time=2019-01-13T12:18:17
+bw=1 bw_mean=57297 bw_median=53829 desc_bw_avg=102400 desc_bw_obs_last=195863 desc_bw_obs_mean=224260 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=74ImRei+wQMPcMMSI/7sRC8PY/uKwU7W+o8yrhYbes0 nick=Unnamed node_id=$CF2511107D4F3AC78F89B66BFDDF684BE12D66FA success=6 time=2019-01-10T22:09:40
+bw=1 bw_mean=262273 bw_median=277051 desc_bw_avg=307200 desc_bw_obs_last=341305 desc_bw_obs_mean=340479 error_circ=0 error_misc=0 error_stream=0 nick=TastyCucumber node_id=$F2D630DE77336E752C429819D6C2F82FB57F78BE success=12 time=2019-01-14T00:03:00
+bw=1 bw_mean=74291 bw_median=74485 desc_bw_avg=81920 desc_bw_obs_last=93870 desc_bw_obs_mean=96785 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=XLJDymxi5r8ZxS+LETvRiIuXv2EkJKo3AFGKR7wbPUA nick=Unnamed node_id=$A0E4894218C66DCB08713D59D68DF40BC518028C success=11 time=2019-01-13T11:41:01
+bw=1 bw_mean=184647 bw_median=202438 desc_bw_avg=1024000 desc_bw_obs_last=473188 desc_bw_obs_mean=581671 error_circ=0 error_misc=0 error_stream=0 nick=Teinetteiine node_id=$9C7E1AFDACC53228F6FB57B3A08C7D36240B8F6F success=13 time=2019-01-13T12:21:29
+bw=1 bw_mean=39143 bw_median=36985 desc_bw_avg=76800 desc_bw_obs_last=38303 desc_bw_obs_mean=61240 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=b7CRikfTyrealg+PS5k6Vr0jyqu56v6grp6g4MYUtOM nick=gsrv node_id=$BF7DD349325B1BB82D1A90A13546A6EA9F7FD4BA success=11 time=2019-01-13T12:21:25
+bw=1 bw_mean=104498 bw_median=110671 desc_bw_avg=128000 desc_bw_obs_last=143044 desc_bw_obs_mean=143035 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=vTbl6DZ1lzzOmCYTwlatTaTf+UWcIO9gC2/NrVJDh6s nick=TORabrina node_id=$2217F8AF669BCE9B9B9017EF127B32320FCFC7DA success=5 time=2019-01-13T12:21:12
+bw=1 bw_mean=69626 bw_median=72567 desc_bw_avg=204800 desc_bw_obs_last=109945 desc_bw_obs_mean=104323 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=MXIEqCCgbH2CqxHdNJCHX6B9IAOHZj/bWWnAJ2A2v2Q nick=blitzen node_id=$3693458772F06FD1F8FE46C2CEEACF12F630F07C success=12 time=2019-01-13T12:22:02
+bw=1 bw_mean=171387 bw_median=223256 desc_bw_avg=262144 desc_bw_obs_last=323694 desc_bw_obs_mean=320283 error_circ=0 error_misc=0 error_stream=0 nick=KDFrelaySanClemente node_id=$D370611E00C52C5881CD91B9036B66DCE43730C3 success=11 time=2019-01-13T12:23:30
+bw=1 bw_mean=302891 bw_median=348786 desc_bw_avg=512000 desc_bw_obs_last=524927 desc_bw_obs_mean=530973 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=SdgepwmdKbD24s5noM5z2v6MU54/OIMKmFuP9tYbB74 nick=camouflage node_id=$A1B16EA72C5DF23346B3816581A30937B5EAFC93 success=13 time=2019-01-13T12:22:34
+bw=1 bw_mean=198407 bw_median=214872 desc_bw_avg=409600 desc_bw_obs_last=372398 desc_bw_obs_mean=389342 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=9JowdTlSZpnOI3IW5EwL0wfMN0xOigdphDUiZgolAMk nick=magnetic node_id=$D178799E9847A989A1356CEF2F79A1B1A4ABE687 success=13 time=2019-01-13T12:22:39
+bw=1 bw_mean=91919 bw_median=93473 desc_bw_avg=102400 desc_bw_obs_last=119691 desc_bw_obs_mean=121501 error_circ=0 error_misc=0 error_stream=0 nick=woprtor node_id=$DA55F3086F6B8B052BC816BD7DC5F4701D3C61E8 success=13 time=2019-01-13T12:25:05
+bw=1 bw_mean=255156 bw_median=279019 desc_bw_avg=409600 desc_bw_obs_last=435238 desc_bw_obs_mean=430682 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=7k8+P4OduZl/GpdJp0EUmwaGQWuZK6jP0o4GJ299Ab0 nick=explorer node_id=$4876AF556686935CC53864F95025AC6ED75A90E1 success=13 time=2019-01-13T12:32:07
+bw=1 bw_mean=63025 bw_median=70160 desc_bw_avg=102400 desc_bw_obs_last=113188 desc_bw_obs_mean=113928 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=2J4943aO+uXDLAZHTUH0Za+ecSYjLx/B6e+E8QbNnbA nick=chedusitanielle node_id=$2FF65C0EF7EC45E96DAEB12D94A74CA15D3ED453 success=11 time=2019-01-13T12:34:16
+bw=1 bw_mean=308215 bw_median=355512 desc_bw_avg=409600 desc_bw_obs_last=472800 desc_bw_obs_mean=474614 error_circ=0 error_misc=0 error_stream=0 nick=torwh node_id=$BBE55D06B589797FCF2A667240AC1D07DF763328 success=13 time=2019-01-13T12:33:55
+bw=1 bw_mean=350860 bw_median=363001 desc_bw_avg=409600 desc_bw_obs_last=491520 desc_bw_obs_mean=491553 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=ExMprOQQnFOrt7rXrHEHZtA+7U2GA/erXbdZcTnczAo nick=SicceggeOrion node_id=$DC6A66B14183CC376E89279FBA40FDE8249C440B success=5 time=2019-01-07T08:57:40
+bw=1 bw_mean=134663 bw_median=182089 desc_bw_avg=204800 desc_bw_obs_last=244822 desc_bw_obs_mean=242657 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=SLHKvxduuOEWuEqOVsIL4ZGQfVKdx3CnLS56cf8cfl8 nick=do2 node_id=$387A7D62EEC5C14FF45ED8AD9B2235BFE9FE9463 success=13 time=2019-01-13T12:37:03
+bw=1 bw_mean=55716 bw_median=55718 desc_bw_avg=61440 desc_bw_obs_last=125515 desc_bw_obs_mean=124718 error_circ=0 error_misc=0 error_stream=0 nick=ICSIL1xDEDISx1 node_id=$7CBBAB7867F939763D94FC6C2819E129971CB0E4 success=12 time=2019-01-13T12:37:47
+bw=1 bw_mean=95713 bw_median=98888 desc_bw_avg=131072 desc_bw_obs_last=169357 desc_bw_obs_mean=168630 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=J3SDjK4McGhnQGyYiI31IUJ3aUqgXYF7SKH2BSZKgE8 nick=amaterasu node_id=$D25417C040074E1A24FBF37B8233643731F0DBA5 success=10 time=2019-01-13T12:38:19
+bw=1 bw_mean=155556 bw_median=195621 desc_bw_avg=256000 desc_bw_obs_last=252391 desc_bw_obs_mean=251358 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=bFDug+FuPEdTiIEL6brjU5vGAPPfS+SKdYNATW7wxX8 nick=mentor node_id=$0FC12F2B7DD0E94930F9B3CDD321308996652957 success=8 time=2019-01-10T03:03:22
+bw=1 bw_mean=192401 bw_median=219491 desc_bw_avg=262144 desc_bw_obs_last=471831 desc_bw_obs_mean=472145 error_circ=0 error_misc=0 error_stream=0 master_key_ed25519=klwLlcn34Bi1TOBHSIRiO0tJKeX1UZkxzCvdyihf3RE nick=PompoRelay node_id=$319AC0126125DA6A557A416C1BB643E52E272AD0 success=12 time=2019-01-13T12:40:39
+bw=1 bw_mean=289494 bw_median=354226 desc_bw_avg=409600 desc_bw_obs_last=491019 desc_bw_obs_mean=500141 error_circ=0 error_misc=0 error_stream=0 nick=glenda2 node_id=$98FB7574932DBD6FE9E75169982EDFBA50F86175 success=11 time=2019-01-13T12:40:04
1
0
commit ecfdba922026cc56ca451d58e7933b42fab9ecc7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jan 19 16:11:36 2019 -0800
Parse bandwidth file headers
Reading all the header metadata fields. Honestly I'm a bit fuzzy on
the definition of 'eligible', but that's fine. Taking my best guess
at describing these.
00:08 <+atagar> juga: Minor thing that is crossing my mind while implementing
stem's bandwidth-file parser is "how does the 'eligible' count differ
from the number of relay measurements we list?". In other words, why
does the header provide a 'consensus, eligible, and mininum eligible'
count rather than a 'consensus, measured, and minimum measured' count.
'Eligible' is defined as 'having enough measurements' but not
necessarily having a metric included, so I'm guessing we chose this
term because there are relays that have metrics (making it 'eligible')
but are excluded from the results, and need to count toward the
'minimum percent of the consensus'.
00:09 <+atagar> Anywho, unimportant to parsing. Just a spot in the spec that
might benefit from a little clarification.
---
stem/descriptor/bandwidth_metric.py | 94 ++++++++++++++++++++++++++------
test/unit/descriptor/bandwidth_metric.py | 40 ++++++++++++++
2 files changed, 116 insertions(+), 18 deletions(-)
diff --git a/stem/descriptor/bandwidth_metric.py b/stem/descriptor/bandwidth_metric.py
index bb732f4a..900a9d2e 100644
--- a/stem/descriptor/bandwidth_metric.py
+++ b/stem/descriptor/bandwidth_metric.py
@@ -16,9 +16,52 @@ Parsing for Bandwidth Authority metrics as described in Tor's
import datetime
-from stem.descriptor import (
- Descriptor,
-)
+import stem.util.str_tools
+
+from stem.descriptor import Descriptor
+
+
+# Converters header attributes to a given type. Malformed fields should be
+# ignored according to the spec.
+
+def _str(val):
+ return val # already a str
+
+
+def _int(val):
+ return int(val) if (val and val.isdigit()) else None
+
+
+def _date(val):
+ try:
+ return stem.util.str_tools._parse_iso_timestamp(val)
+ except ValueError:
+ return None # not an iso formatted date
+
+
+# mapping of attributes => (header, type)
+
+HEADER_ATTR = {
+ 'version': ('version', _str),
+
+ 'software': ('software', _str),
+ 'software_version': ('software_version', _str),
+
+ 'earliest_bandwidth': ('earliest_bandwidth', _date),
+ 'latest_bandwidth': ('latest_bandwidth', _date),
+ 'created_at': ('file_created', _date),
+ 'generated_at': ('generator_started', _date),
+
+ 'consensus_size': ('number_consensus_relays', _int),
+ 'eligible_count': ('number_eligible_relays', _int),
+ 'eligible_percent': ('percent_eligible_relays', _int),
+ 'min_count': ('minimum_number_eligible_relays', _int),
+ 'min_percent': ('minimum_percent_eligible_relays', _int),
+}
+
+HEADER_DEFAULT = {
+ 'version': '1.0.0', # version field was added in 1.1.0
+}
def _parse_file(descriptor_file, validate = False, **kwargs):
@@ -42,8 +85,14 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
def _parse_header(descriptor, entries):
header = {}
+ lines = str(descriptor).split('\n')
+
+ # skip the first line, which should be the timestamp
+
+ if lines and lines[0].isdigit():
+ lines = lines[1:]
- for line in str(descriptor).split('\n'):
+ for line in lines:
if line == '=====':
break
elif line.startswith('node_id='):
@@ -51,15 +100,15 @@ def _parse_header(descriptor, entries):
if '=' in line:
key, value = line.split('=', 1)
- elif line.isdigit() and 'timestamp' not in header:
- key, value = 'timestamp', line
+ header[key] = value
else:
raise ValueError("Header expected to be key=value pairs, but had '%s'" % line)
- header[key] = value
-
descriptor.header = header
+ for attr, (keyword, cls) in HEADER_ATTR.items():
+ setattr(descriptor, attr, cls(header.get(keyword, HEADER_DEFAULT.get(attr))))
+
def _parse_timestamp(descriptor, entries):
first_line = str(descriptor).split('\n', 1)[0]
@@ -70,33 +119,42 @@ def _parse_timestamp(descriptor, entries):
raise ValueError("First line should be a unix timestamp, but was '%s'" % first_line)
-def _header_attr(name):
- def _parse(descriptor, entries):
- val = descriptor.header.get(name, None)
- setattr(descriptor, name, val)
-
- return _parse
-
-
class BandwidthMetric(Descriptor):
"""
Tor bandwidth authroity measurements.
:var datetime timestamp: **\*** time when these metrics were published
+ :var str version: **\*** document format version
+
+ :var str software: application that generated these metrics
+ :var str software_version: version of the application that generated these metrics
- :var dict header: **\*** header metadata attributes
+ :var datetime earliest_bandwidth: time of the first sampling
+ :var datetime latest_bandwidth: time of the last sampling
+ :var datetime created_at: time when this file was created
+ :var datetime generated_at: time when collection of these metrics started
+
+ :var int consensus_size: number of relays in the consensus
+ :var int eligible_count: relays with enough measurements to be included
+ :var int eligible_percent: percentage of consensus with enough measurements
+ :var int min_count: minimum eligible relays for results to be provided
+ :var int min_percent: minimum measured percentage of the consensus
+
+ :var dict header: **\*** header metadata
**\*** attribute is either required when we're parsed with validation or has
a default value, others are left as **None** if undefined
"""
- TYPE_ANNOTATION_NAME = 'badnwidth-file' # TODO: needs an official @type
+ TYPE_ANNOTATION_NAME = 'badnwidth-file' # TODO: needs an official @type, https://trac.torproject.org/projects/tor/ticket/28615
ATTRIBUTES = {
'timestamp': (None, _parse_timestamp),
'header': ({}, _parse_header),
}
+ ATTRIBUTES.update(dict([(k, (None, _parse_header)) for k in HEADER_ATTR.keys()]))
+
def __init__(self, raw_content, validate = False):
super(BandwidthMetric, self).__init__(raw_content, lazy_load = not validate)
diff --git a/test/unit/descriptor/bandwidth_metric.py b/test/unit/descriptor/bandwidth_metric.py
index 489f4d41..45739ed7 100644
--- a/test/unit/descriptor/bandwidth_metric.py
+++ b/test/unit/descriptor/bandwidth_metric.py
@@ -18,4 +18,44 @@ class TestBandwidthMetric(unittest.TestCase):
"""
desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.0'), 'badnwidth-file 1.0'))[0]
+
self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)
+ self.assertEqual('1.0.0', desc.version)
+
+ self.assertEqual(None, desc.software)
+ self.assertEqual(None, desc.software_version)
+
+ self.assertEqual(None, desc.earliest_bandwidth)
+ self.assertEqual(None, desc.latest_bandwidth)
+ self.assertEqual(None, desc.created_at)
+ self.assertEqual(None, desc.generated_at)
+
+ self.assertEqual(None, desc.consensus_size)
+ self.assertEqual(None, desc.eligible_count)
+ self.assertEqual(None, desc.eligible_percent)
+ self.assertEqual(None, desc.min_count)
+ self.assertEqual(None, desc.min_percent)
+
+ def test_format_v1_2(self):
+ """
+ Parse version 1.2 formatted metrics.
+ """
+
+ desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.2'), 'badnwidth-file 1.2'))[0]
+
+ self.assertEqual(datetime.datetime(2019, 1, 14, 5, 34, 59), desc.timestamp)
+ self.assertEqual('1.2.0', desc.version)
+
+ self.assertEqual('sbws', desc.software)
+ self.assertEqual('1.0.2', desc.software_version)
+
+ self.assertEqual(datetime.datetime(2019, 1, 4, 5, 35, 29), desc.earliest_bandwidth)
+ self.assertEqual(datetime.datetime(2019, 1, 14, 5, 34, 59), desc.latest_bandwidth)
+ self.assertEqual(datetime.datetime(2019, 1, 14, 5, 35, 6), desc.created_at)
+ self.assertEqual(datetime.datetime(2019, 1, 3, 22, 45, 8), desc.generated_at)
+
+ self.assertEqual(6514, desc.consensus_size)
+ self.assertEqual(6256, desc.eligible_count)
+ self.assertEqual(96, desc.eligible_percent)
+ self.assertEqual(3908, desc.min_count)
+ self.assertEqual(60, desc.min_percent)
1
0
commit cb8661ef66a2355ab0ed37e8da5e806362c2ab9b
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Jan 16 14:26:59 2019 -0800
Stub initial BandwidthMetric class
Just stubbing out an initial class and test.
---
stem/descriptor/__init__.py | 13 +++-
stem/descriptor/bandwidth_metric.py | 104 +++++++++++++++++++++++++++++++
test/settings.cfg | 8 ++-
test/unit/descriptor/__init__.py | 1 +
test/unit/descriptor/bandwidth_metric.py | 21 +++++++
5 files changed, 141 insertions(+), 6 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index a47c1508..e587f9d1 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -108,6 +108,7 @@ except ImportError:
from stem.util.ordereddict import OrderedDict
__all__ = [
+ 'bandwidth_metric',
'export',
'reader',
'remote',
@@ -441,6 +442,11 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
+ elif descriptor_type == stem.descriptor.bandwidth_metric.BandwidthMetric.TYPE_ANNOTATION_NAME and major_version == 1:
+ document_type = stem.descriptor.bandwidth_metric.BandwidthMetric
+
+ for desc in stem.descriptor.bandwidth_metric._parse_file(descriptor_file, validate = validate, **kwargs):
+ yield desc
else:
raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
@@ -1412,9 +1418,10 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
# importing at the end to avoid circular dependencies on our Descriptor class
-import stem.descriptor.server_descriptor
+import stem.descriptor.bandwidth_metric
import stem.descriptor.extrainfo_descriptor
-import stem.descriptor.networkstatus
+import stem.descriptor.hidden_service_descriptor
import stem.descriptor.microdescriptor
+import stem.descriptor.networkstatus
+import stem.descriptor.server_descriptor
import stem.descriptor.tordnsel
-import stem.descriptor.hidden_service_descriptor
diff --git a/stem/descriptor/bandwidth_metric.py b/stem/descriptor/bandwidth_metric.py
new file mode 100644
index 00000000..bb732f4a
--- /dev/null
+++ b/stem/descriptor/bandwidth_metric.py
@@ -0,0 +1,104 @@
+# Copyright 2019, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Parsing for Bandwidth Authority metrics as described in Tor's
+`bandwidth-file-spec <https://gitweb.torproject.org/torspec.git/tree/bandwidth-file-spec.txt>`_.
+
+**Module Overview:**
+
+::
+
+ BandwidthMetric - Tor bandwidth authority measurements.
+
+.. versionadded:: 1.8.0
+"""
+
+import datetime
+
+from stem.descriptor import (
+ Descriptor,
+)
+
+
+def _parse_file(descriptor_file, validate = False, **kwargs):
+ """
+ Iterates over the bandwidth authority metrics in a file.
+
+ :param file descriptor_file: file with descriptor content
+ :param bool validate: checks the validity of the descriptor's content if
+ **True**, skips these checks otherwise
+ :param dict kwargs: additional arguments for the descriptor constructor
+
+ :returns: :class:`stem.descriptor.bandwidth_file.BandwidthMetric` object
+
+ :raises:
+ * **ValueError** if the contents is malformed and validate is **True**
+ * **IOError** if the file can't be read
+ """
+
+ yield BandwidthMetric(descriptor_file.read(), validate, **kwargs)
+
+
+def _parse_header(descriptor, entries):
+ header = {}
+
+ for line in str(descriptor).split('\n'):
+ if line == '=====':
+ break
+ elif line.startswith('node_id='):
+ break # version 1.0 measurement
+
+ if '=' in line:
+ key, value = line.split('=', 1)
+ elif line.isdigit() and 'timestamp' not in header:
+ key, value = 'timestamp', line
+ else:
+ raise ValueError("Header expected to be key=value pairs, but had '%s'" % line)
+
+ header[key] = value
+
+ descriptor.header = header
+
+
+def _parse_timestamp(descriptor, entries):
+ first_line = str(descriptor).split('\n', 1)[0]
+
+ if first_line.isdigit():
+ descriptor.timestamp = datetime.datetime.utcfromtimestamp(int(first_line))
+ else:
+ raise ValueError("First line should be a unix timestamp, but was '%s'" % first_line)
+
+
+def _header_attr(name):
+ def _parse(descriptor, entries):
+ val = descriptor.header.get(name, None)
+ setattr(descriptor, name, val)
+
+ return _parse
+
+
+class BandwidthMetric(Descriptor):
+ """
+ Tor bandwidth authroity measurements.
+
+ :var datetime timestamp: **\*** time when these metrics were published
+
+ :var dict header: **\*** header metadata attributes
+
+ **\*** attribute is either required when we're parsed with validation or has
+ a default value, others are left as **None** if undefined
+ """
+
+ TYPE_ANNOTATION_NAME = 'badnwidth-file' # TODO: needs an official @type
+
+ ATTRIBUTES = {
+ 'timestamp': (None, _parse_timestamp),
+ 'header': ({}, _parse_header),
+ }
+
+ def __init__(self, raw_content, validate = False):
+ super(BandwidthMetric, self).__init__(raw_content, lazy_load = not validate)
+
+ if validate:
+ pass # TODO: implement eager load
diff --git a/test/settings.cfg b/test/settings.cfg
index 2ecd34ea..a5e60e08 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -138,12 +138,13 @@ pycodestyle.ignore E131
pycodestyle.ignore E722
pycodestyle.ignore stem/__init__.py => E402: import stem.util.connection
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_metric
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.extrainfo_descriptor
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.microdescriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.tordnsel
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
pycodestyle.ignore test/unit/util/connection.py => W291: _tor tor 15843 10 pipe 0x0 state:
pycodestyle.ignore test/unit/util/connection.py => W291: _tor tor 15843 11 pipe 0x0 state:
@@ -224,6 +225,7 @@ test.unit_tests
|test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
|test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor
|test.unit.descriptor.certificate.TestEd25519Certificate
+|test.unit.descriptor.bandwidth_metric.TestBandwidthMetric
|test.unit.exit_policy.rule.TestExitPolicyRule
|test.unit.exit_policy.policy.TestExitPolicy
|test.unit.endpoint.TestEndpoint
diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py
index bce8d3d0..0fe107dd 100644
--- a/test/unit/descriptor/__init__.py
+++ b/test/unit/descriptor/__init__.py
@@ -5,6 +5,7 @@ Unit tests for stem.descriptor.
import os
__all__ = [
+ 'bandwidth_metric',
'export',
'extrainfo_descriptor',
'microdescriptor',
diff --git a/test/unit/descriptor/bandwidth_metric.py b/test/unit/descriptor/bandwidth_metric.py
new file mode 100644
index 00000000..489f4d41
--- /dev/null
+++ b/test/unit/descriptor/bandwidth_metric.py
@@ -0,0 +1,21 @@
+"""
+Unit tests for stem.descriptor.bandwidth_metric.
+"""
+
+import datetime
+import unittest
+
+import stem.descriptor
+import stem.descriptor.bandwidth_metric
+
+import test.unit.descriptor
+
+
+class TestBandwidthMetric(unittest.TestCase):
+ def test_format_v1_0(self):
+ """
+ Parse version 1.0 formatted metrics.
+ """
+
+ desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.0'), 'badnwidth-file 1.0'))[0]
+ self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)
1
0
commit 3dcc573e7aca39f6b12d74885d51b1d01b483f90
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jan 20 15:41:42 2019 -0800
BandwidthFile creation
Nothing fancy, just implementing Descriptor's create() and content() method.
---
stem/descriptor/bandwidth_file.py | 60 ++++++++++++++++++++-
test/unit/descriptor/bandwidth_file.py | 98 ++++++++++++++++++++++++++++++++--
2 files changed, 152 insertions(+), 6 deletions(-)
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 97210d7b..0b2b211a 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -15,11 +15,14 @@ Parsing for Bandwidth Authority metrics as described in Tor's
"""
import datetime
+import time
import stem.util.str_tools
from stem.descriptor import Descriptor
+HEADER_DIV = '====='
+
# Converters header attributes to a given type. Malformed fields should be
# ignored according to the spec.
@@ -93,7 +96,7 @@ def _parse_header(descriptor, entries):
lines = lines[1:]
for line in lines:
- if line == '=====':
+ if line == HEADER_DIV:
break
elif line.startswith('node_id='):
break # version 1.0 measurement
@@ -155,8 +158,61 @@ class BandwidthFile(Descriptor):
ATTRIBUTES.update(dict([(k, (None, _parse_header)) for k in HEADER_ATTR.keys()]))
+ @classmethod
+ def content(cls, attr = None, exclude = (), sign = False):
+ """
+ Creates descriptor content with the given attributes. This descriptor type
+ differs somewhat from others and treats our attr/exclude attributes as
+ follows...
+
+ * 'timestamp' is a reserved key for our mandatory header unix timestamp.
+
+ * 'content' is a reserved key for a list of our bandwidth measurements.
+
+ * All other keys are treated as header fields.
+
+ For example...
+
+ ::
+
+ BandwidthFile.content({
+ 'timestamp': '12345',
+ 'version': '1.2.0',
+ 'content': [],
+ })
+ """
+
+ if sign:
+ raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+
+ header = dict(attr) if attr is not None else {}
+ timestamp = header.pop('timestamp', str(int(time.time())))
+ content = header.pop('content', [])
+ version = header.get('version', HEADER_DEFAULT.get('version'))
+
+ lines = []
+
+ if 'timestamp' not in exclude:
+ lines.append(timestamp)
+
+ if version == '1.0.0' and header:
+ raise ValueError('Headers require BandwidthFile version 1.1 or later')
+ elif version != '1.0.0':
+ for k, v in header.items():
+ lines.append('%s=%s' % (k, v))
+
+ lines.append(HEADER_DIV)
+
+ for measurement in content:
+ lines.append(measurement) # TODO: replace when we have a measurement struct
+
+ return '\n'.join(lines)
+
def __init__(self, raw_content, validate = False):
super(BandwidthFile, self).__init__(raw_content, lazy_load = not validate)
+ self.content = [] # TODO: implement
+
if validate:
- pass # TODO: implement eager load
+ _parse_timestamp(self, None)
+ _parse_header(self, None)
diff --git a/test/unit/descriptor/bandwidth_file.py b/test/unit/descriptor/bandwidth_file.py
index b8fdf01b..e1f8ffb4 100644
--- a/test/unit/descriptor/bandwidth_file.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -6,9 +6,22 @@ import datetime
import unittest
import stem.descriptor
-import stem.descriptor.bandwidth_file
-import test.unit.descriptor
+from stem.descriptor.bandwidth_file import BandwidthFile
+from test.unit.descriptor import get_resource
+
+try:
+ # added in python 3.3
+ from unittest.mock import Mock, patch
+except ImportError:
+ from mock import Mock, patch
+
+EXPECTED_NEW_HEADER_CONTENT = """
+1410723598
+version=1.1.0
+new_header=neat stuff
+=====
+""".strip()
class TestBandwidthFile(unittest.TestCase):
@@ -17,7 +30,7 @@ class TestBandwidthFile(unittest.TestCase):
Parse version 1.0 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.0'), 'badnwidth-file 1.0'))[0]
+ desc = list(stem.descriptor.parse_file(get_resource('bandwidth_file_v1.0'), 'badnwidth-file 1.0'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)
self.assertEqual('1.0.0', desc.version)
@@ -41,7 +54,7 @@ class TestBandwidthFile(unittest.TestCase):
Parse version 1.2 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.2'), 'badnwidth-file 1.2'))[0]
+ desc = list(stem.descriptor.parse_file(get_resource('bandwidth_file_v1.2'), 'badnwidth-file 1.2'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 5, 34, 59), desc.timestamp)
self.assertEqual('1.2.0', desc.version)
@@ -59,3 +72,80 @@ class TestBandwidthFile(unittest.TestCase):
self.assertEqual(96, desc.eligible_percent)
self.assertEqual(3908, desc.min_count)
self.assertEqual(60, desc.min_percent)
+
+ @patch('time.time', Mock(return_value = 1410723598.276578))
+ def test_minimal_bandwidth_file(self):
+ """
+ Basic sanity check that we can parse a bandwidth file with minimal
+ attributes.
+ """
+
+ desc = BandwidthFile.create()
+
+ self.assertEqual('1410723598', str(desc))
+
+ self.assertEqual(datetime.datetime(2014, 9, 14, 19, 39, 58), desc.timestamp)
+ self.assertEqual('1.0.0', desc.version)
+
+ self.assertEqual(None, desc.software)
+ self.assertEqual(None, desc.software_version)
+
+ self.assertEqual(None, desc.earliest_bandwidth)
+ self.assertEqual(None, desc.latest_bandwidth)
+ self.assertEqual(None, desc.created_at)
+ self.assertEqual(None, desc.generated_at)
+
+ self.assertEqual(None, desc.consensus_size)
+ self.assertEqual(None, desc.eligible_count)
+ self.assertEqual(None, desc.eligible_percent)
+ self.assertEqual(None, desc.min_count)
+ self.assertEqual(None, desc.min_percent)
+
+ self.assertEqual({}, desc.header)
+
+ def test_content_example(self):
+ """
+ Exercise the example in our content method's pydoc.
+ """
+
+ content = BandwidthFile.content({
+ 'timestamp': '12345',
+ 'version': '1.2.0',
+ 'content': [],
+ })
+
+ self.assertEqual('12345\nversion=1.2.0\n=====', content)
+
+ @patch('time.time', Mock(return_value = 1410723598.276578))
+ def test_new_header_attribute(self):
+ """
+ Include an unrecognized header field.
+ """
+
+ desc = BandwidthFile.create({'version': '1.1.0', 'new_header': 'neat stuff'})
+ self.assertEqual(EXPECTED_NEW_HEADER_CONTENT, str(desc))
+ self.assertEqual('1.1.0', desc.version)
+ self.assertEqual({'version': '1.1.0', 'new_header': 'neat stuff'}, desc.header)
+
+ def test_header_for_v1(self):
+ """
+ Document version 1.0 predates headers, and as such should be prohibited.
+ """
+
+ self.assertRaisesWith(ValueError, 'Headers require BandwidthFile version 1.1 or later', BandwidthFile.create, {'new_header': 'neat stuff'})
+
+ def test_invalid_timestamp(self):
+ """
+ Invalid timestamp values.
+ """
+
+ test_values = (
+ '',
+ 'boo',
+ '123.4',
+ '-123',
+ )
+
+ for value in test_values:
+ expected_exc = "First line should be a unix timestamp, but was '%s'" % value
+ self.assertRaisesWith(ValueError, expected_exc, BandwidthFile.create, {'timestamp': value})
1
0
commit f7575c6a1fb8b755a744c20318195c659a85d060
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jan 20 17:09:22 2019 -0800
Parse BandwidthFile as streams
String parsing was a great spot to start, but highly memory inefficient.
Parsing internally created multiple copies of our bandwidth file content
as processed rather than working from a single copy of the bytes.
---
stem/descriptor/bandwidth_file.py | 50 ++++++++++++++++++++-------------------
1 file changed, 26 insertions(+), 24 deletions(-)
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 9c87385c..2e9fc216 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -15,6 +15,7 @@ Parsing for Bandwidth Authority metrics as described in Tor's
"""
import datetime
+import io
import time
import stem.util.str_tools
@@ -91,16 +92,17 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
def _parse_header(descriptor, entries):
header = {}
- lines = str(descriptor).split('\n')
+ content = io.BytesIO(descriptor.get_bytes())
- # skip the first line, which should be the timestamp
+ content.readline() # skip the first line, which should be the timestamp
- if lines and lines[0].isdigit():
- lines = lines[1:]
+ while True:
+ line = content.readline().strip()
- for line in lines:
- if line == HEADER_DIV:
- break
+ if not line:
+ break # end of the content
+ elif line == HEADER_DIV:
+ break # end of header
elif line.startswith('node_id='):
break # version 1.0 measurement
@@ -117,7 +119,7 @@ def _parse_header(descriptor, entries):
def _parse_timestamp(descriptor, entries):
- first_line = str(descriptor).split('\n', 1)[0]
+ first_line = io.BytesIO(descriptor.get_bytes()).readline().strip()
if first_line.isdigit():
descriptor.timestamp = datetime.datetime.utcfromtimestamp(int(first_line))
@@ -129,30 +131,30 @@ def _parse_body(descriptor, entries):
# In version 1.0.0 the body is everything after the first line. Otherwise
# it's everything after the header's divider.
- div = '\n' if descriptor.version == '1.0.0' else HEADER_DIV
+ content = io.BytesIO(descriptor.get_bytes())
- if div in str(descriptor):
- body = str(descriptor).split(div, 1)[1].strip()
+ if descriptor.version == '1.0.0':
+ content.readline() # skip the first line
else:
- body = ''
+ while content.readline().strip() != HEADER_DIV:
+ pass # skip the header
measurements = {}
- if body:
- for line in body.split('\n'):
- attr = dict(_mappings_for('measurement', line))
+ for line in content.readlines():
+ attr = dict(_mappings_for('measurement', line.strip()))
- if 'node_id' not in attr:
- raise ValueError("Every meaurement must include 'node_id': %s" % line)
- elif attr['node_id'] in measurements:
- # Relay is listed multiple times. This is a bug for the bandwidth
- # authority that made this descriptor, but according to the spec
- # should be ignored by parsers.
+ if 'node_id' not in attr:
+ raise ValueError("Every meaurement must include 'node_id': %s" % line.strip())
+ elif attr['node_id'] in measurements:
+ # Relay is listed multiple times. This is a bug for the bandwidth
+ # authority that made this descriptor, but according to the spec
+ # should be ignored by parsers.
- continue
+ continue
- fingerprint = attr['node_id'].lstrip('$') # bwauths prefix fingerprints with '$'
- measurements[fingerprint] = attr
+ fingerprint = attr['node_id'].lstrip('$') # bwauths prefix fingerprints with '$'
+ measurements[fingerprint] = attr
descriptor.measurements = measurements
1
0

21 Jan '19
commit 6a122f615b9e8f09b2e56f06a6512ded48f9333f
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sat Jan 19 16:15:54 2019 -0800
Renaming 'BandwidthMetric' to 'BandwidthFile'
Juga and teor indicated they'd like for us to standardize on the name of
'BandwidthFile'. Tracking the spec update on...
https://trac.torproject.org/projects/tor/ticket/29137
---
stem/descriptor/__init__.py | 10 +++++-----
stem/descriptor/{bandwidth_metric.py => bandwidth_file.py} | 10 +++++-----
test/settings.cfg | 4 ++--
test/unit/descriptor/__init__.py | 2 +-
.../descriptor/{bandwidth_metric.py => bandwidth_file.py} | 14 +++++++-------
.../descriptor/data/{bwauth_v1.0 => bandwidth_file_v1.0} | 0
.../descriptor/data/{bwauth_v1.2 => bandwidth_file_v1.2} | 0
7 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index e587f9d1..f926c45a 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -108,7 +108,7 @@ except ImportError:
from stem.util.ordereddict import OrderedDict
__all__ = [
- 'bandwidth_metric',
+ 'bandwidth_file',
'export',
'reader',
'remote',
@@ -442,10 +442,10 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
- elif descriptor_type == stem.descriptor.bandwidth_metric.BandwidthMetric.TYPE_ANNOTATION_NAME and major_version == 1:
- document_type = stem.descriptor.bandwidth_metric.BandwidthMetric
+ elif descriptor_type == stem.descriptor.bandwidth_file.BandwidthFile.TYPE_ANNOTATION_NAME and major_version == 1:
+ document_type = stem.descriptor.bandwidth_file.BandwidthFile
- for desc in stem.descriptor.bandwidth_metric._parse_file(descriptor_file, validate = validate, **kwargs):
+ for desc in stem.descriptor.bandwidth_file._parse_file(descriptor_file, validate = validate, **kwargs):
yield desc
else:
raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
@@ -1418,7 +1418,7 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
# importing at the end to avoid circular dependencies on our Descriptor class
-import stem.descriptor.bandwidth_metric
+import stem.descriptor.bandwidth_file
import stem.descriptor.extrainfo_descriptor
import stem.descriptor.hidden_service_descriptor
import stem.descriptor.microdescriptor
diff --git a/stem/descriptor/bandwidth_metric.py b/stem/descriptor/bandwidth_file.py
similarity index 93%
rename from stem/descriptor/bandwidth_metric.py
rename to stem/descriptor/bandwidth_file.py
index 900a9d2e..97210d7b 100644
--- a/stem/descriptor/bandwidth_metric.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -9,7 +9,7 @@ Parsing for Bandwidth Authority metrics as described in Tor's
::
- BandwidthMetric - Tor bandwidth authority measurements.
+ BandwidthFile - Tor bandwidth authority measurements.
.. versionadded:: 1.8.0
"""
@@ -73,14 +73,14 @@ def _parse_file(descriptor_file, validate = False, **kwargs):
**True**, skips these checks otherwise
:param dict kwargs: additional arguments for the descriptor constructor
- :returns: :class:`stem.descriptor.bandwidth_file.BandwidthMetric` object
+ :returns: :class:`stem.descriptor.bandwidth_file.BandwidthFile` object
:raises:
* **ValueError** if the contents is malformed and validate is **True**
* **IOError** if the file can't be read
"""
- yield BandwidthMetric(descriptor_file.read(), validate, **kwargs)
+ yield BandwidthFile(descriptor_file.read(), validate, **kwargs)
def _parse_header(descriptor, entries):
@@ -119,7 +119,7 @@ def _parse_timestamp(descriptor, entries):
raise ValueError("First line should be a unix timestamp, but was '%s'" % first_line)
-class BandwidthMetric(Descriptor):
+class BandwidthFile(Descriptor):
"""
Tor bandwidth authroity measurements.
@@ -156,7 +156,7 @@ class BandwidthMetric(Descriptor):
ATTRIBUTES.update(dict([(k, (None, _parse_header)) for k in HEADER_ATTR.keys()]))
def __init__(self, raw_content, validate = False):
- super(BandwidthMetric, self).__init__(raw_content, lazy_load = not validate)
+ super(BandwidthFile, self).__init__(raw_content, lazy_load = not validate)
if validate:
pass # TODO: implement eager load
diff --git a/test/settings.cfg b/test/settings.cfg
index a5e60e08..d463cac9 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -138,7 +138,7 @@ pycodestyle.ignore E131
pycodestyle.ignore E722
pycodestyle.ignore stem/__init__.py => E402: import stem.util.connection
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_metric
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_file
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.extrainfo_descriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.microdescriptor
@@ -225,7 +225,7 @@ test.unit_tests
|test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
|test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor
|test.unit.descriptor.certificate.TestEd25519Certificate
-|test.unit.descriptor.bandwidth_metric.TestBandwidthMetric
+|test.unit.descriptor.bandwidth_file.TestBandwidthFile
|test.unit.exit_policy.rule.TestExitPolicyRule
|test.unit.exit_policy.policy.TestExitPolicy
|test.unit.endpoint.TestEndpoint
diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py
index 0fe107dd..a2c03f1d 100644
--- a/test/unit/descriptor/__init__.py
+++ b/test/unit/descriptor/__init__.py
@@ -5,7 +5,7 @@ Unit tests for stem.descriptor.
import os
__all__ = [
- 'bandwidth_metric',
+ 'bandwidth_file',
'export',
'extrainfo_descriptor',
'microdescriptor',
diff --git a/test/unit/descriptor/bandwidth_metric.py b/test/unit/descriptor/bandwidth_file.py
similarity index 84%
rename from test/unit/descriptor/bandwidth_metric.py
rename to test/unit/descriptor/bandwidth_file.py
index 45739ed7..b8fdf01b 100644
--- a/test/unit/descriptor/bandwidth_metric.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -1,23 +1,23 @@
"""
-Unit tests for stem.descriptor.bandwidth_metric.
+Unit tests for stem.descriptor.bandwidth_file.
"""
import datetime
import unittest
import stem.descriptor
-import stem.descriptor.bandwidth_metric
+import stem.descriptor.bandwidth_file
import test.unit.descriptor
-class TestBandwidthMetric(unittest.TestCase):
+class TestBandwidthFile(unittest.TestCase):
def test_format_v1_0(self):
"""
- Parse version 1.0 formatted metrics.
+ Parse version 1.0 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.0'), 'badnwidth-file 1.0'))[0]
+ desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.0'), 'badnwidth-file 1.0'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)
self.assertEqual('1.0.0', desc.version)
@@ -38,10 +38,10 @@ class TestBandwidthMetric(unittest.TestCase):
def test_format_v1_2(self):
"""
- Parse version 1.2 formatted metrics.
+ Parse version 1.2 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.2'), 'badnwidth-file 1.2'))[0]
+ desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.2'), 'badnwidth-file 1.2'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 5, 34, 59), desc.timestamp)
self.assertEqual('1.2.0', desc.version)
diff --git a/test/unit/descriptor/data/bwauth_v1.0 b/test/unit/descriptor/data/bandwidth_file_v1.0
similarity index 100%
rename from test/unit/descriptor/data/bwauth_v1.0
rename to test/unit/descriptor/data/bandwidth_file_v1.0
diff --git a/test/unit/descriptor/data/bwauth_v1.2 b/test/unit/descriptor/data/bandwidth_file_v1.2
similarity index 100%
rename from test/unit/descriptor/data/bwauth_v1.2
rename to test/unit/descriptor/data/bandwidth_file_v1.2
1
0
commit a9ad5ba07bc4de3e4368c9d9d814de62703c254d
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jan 20 16:43:07 2019 -0800
Parse BandwidthFile body
Huh. That was easy. Our spec is pretty sparse on what our body includes,
mandating that each line is a series of key=value pairs and includes a
'node_id' but not much beyond that.
Minimal specificity limits what our parser can provide, but also grants sbws
flexability and makes my work dead easy. Body content is vended to users as a
mapping of relay fingerprints to measurement metadata without any additional
processing (no mandatory fields, type casting, etc).
---
stem/descriptor/bandwidth_file.py | 47 ++++++++++++++++++++++++++++++----
test/unit/descriptor/bandwidth_file.py | 36 ++++++++++++++++++++++++++
2 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 0b2b211a..9c87385c 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -19,7 +19,10 @@ import time
import stem.util.str_tools
-from stem.descriptor import Descriptor
+from stem.descriptor import (
+ _mappings_for,
+ Descriptor,
+)
HEADER_DIV = '====='
@@ -122,10 +125,46 @@ def _parse_timestamp(descriptor, entries):
raise ValueError("First line should be a unix timestamp, but was '%s'" % first_line)
+def _parse_body(descriptor, entries):
+ # In version 1.0.0 the body is everything after the first line. Otherwise
+ # it's everything after the header's divider.
+
+ div = '\n' if descriptor.version == '1.0.0' else HEADER_DIV
+
+ if div in str(descriptor):
+ body = str(descriptor).split(div, 1)[1].strip()
+ else:
+ body = ''
+
+ measurements = {}
+
+ if body:
+ for line in body.split('\n'):
+ attr = dict(_mappings_for('measurement', line))
+
+ if 'node_id' not in attr:
+ raise ValueError("Every meaurement must include 'node_id': %s" % line)
+ elif attr['node_id'] in measurements:
+ # Relay is listed multiple times. This is a bug for the bandwidth
+ # authority that made this descriptor, but according to the spec
+ # should be ignored by parsers.
+
+ continue
+
+ fingerprint = attr['node_id'].lstrip('$') # bwauths prefix fingerprints with '$'
+ measurements[fingerprint] = attr
+
+ descriptor.measurements = measurements
+
+
class BandwidthFile(Descriptor):
"""
Tor bandwidth authroity measurements.
+ :var dict measurements: **\*** mapping of relay fingerprints to their
+ bandwidth measurement metadata
+
+ :var dict header: **\*** header metadata
:var datetime timestamp: **\*** time when these metrics were published
:var str version: **\*** document format version
@@ -143,8 +182,6 @@ class BandwidthFile(Descriptor):
:var int min_count: minimum eligible relays for results to be provided
:var int min_percent: minimum measured percentage of the consensus
- :var dict header: **\*** header metadata
-
**\*** attribute is either required when we're parsed with validation or has
a default value, others are left as **None** if undefined
"""
@@ -154,6 +191,7 @@ class BandwidthFile(Descriptor):
ATTRIBUTES = {
'timestamp': (None, _parse_timestamp),
'header': ({}, _parse_header),
+ 'measurements': ({}, _parse_body),
}
ATTRIBUTES.update(dict([(k, (None, _parse_header)) for k in HEADER_ATTR.keys()]))
@@ -211,8 +249,7 @@ class BandwidthFile(Descriptor):
def __init__(self, raw_content, validate = False):
super(BandwidthFile, self).__init__(raw_content, lazy_load = not validate)
- self.content = [] # TODO: implement
-
if validate:
_parse_timestamp(self, None)
_parse_header(self, None)
+ _parse_body(self, None)
diff --git a/test/unit/descriptor/bandwidth_file.py b/test/unit/descriptor/bandwidth_file.py
index e1f8ffb4..43af974b 100644
--- a/test/unit/descriptor/bandwidth_file.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -16,6 +16,36 @@ try:
except ImportError:
from mock import Mock, patch
+EXPECTED_MEASUREMENT_1 = {
+ 'scanner': '/scanner.1/scan-data/bws-0.0:0.8-done-2019-01-13-22:55:22',
+ 'measured_at': '1547441722',
+ 'pid_delta': '1.07534299311',
+ 'updated_at': '1547441722',
+ 'pid_error_sum': '3.23746667827',
+ 'nick': 'baldr',
+ 'node_id': '$D8B9CAA5B818DEFE80857F83FDABBB6429DCFCA0',
+ 'pid_bw': '47625769',
+ 'bw': '47600',
+ 'pid_error': '3.23746667827',
+ 'circ_fail': '0.0',
+}
+
+EXPECTED_MEASUREMENT_2 = {
+ 'desc_bw_obs_last': '473188',
+ 'success': '13',
+ 'desc_bw_obs_mean': '581671',
+ 'bw_median': '202438',
+ 'nick': 'Teinetteiine',
+ 'bw': '1',
+ 'desc_bw_avg': '1024000',
+ 'time': '2019-01-13T12:21:29',
+ 'bw_mean': '184647',
+ 'error_circ': '0',
+ 'error_stream': '0',
+ 'node_id': '$9C7E1AFDACC53228F6FB57B3A08C7D36240B8F6F',
+ 'error_misc': '0',
+}
+
EXPECTED_NEW_HEADER_CONTENT = """
1410723598
version=1.1.0
@@ -49,6 +79,9 @@ class TestBandwidthFile(unittest.TestCase):
self.assertEqual(None, desc.min_count)
self.assertEqual(None, desc.min_percent)
+ self.assertEqual(94, len(desc.measurements))
+ self.assertEqual(EXPECTED_MEASUREMENT_1, desc.measurements['D8B9CAA5B818DEFE80857F83FDABBB6429DCFCA0'])
+
def test_format_v1_2(self):
"""
Parse version 1.2 formatted files.
@@ -73,6 +106,9 @@ class TestBandwidthFile(unittest.TestCase):
self.assertEqual(3908, desc.min_count)
self.assertEqual(60, desc.min_percent)
+ self.assertEqual(81, len(desc.measurements))
+ self.assertEqual(EXPECTED_MEASUREMENT_2, desc.measurements['9C7E1AFDACC53228F6FB57B3A08C7D36240B8F6F'])
+
@patch('time.time', Mock(return_value = 1410723598.276578))
def test_minimal_bandwidth_file(self):
"""
1
0
commit e6ed3d1581be027f93113488a553a8582fde1108
Author: Damian Johnson <atagar(a)torproject.org>
Date: Sun Jan 20 17:34:59 2019 -0800
Python3 parsing for BandwidthFiles
Just the regular bytes vs unicode chore.
---
stem/descriptor/bandwidth_file.py | 23 ++++++++++++-----------
test/unit/descriptor/bandwidth_file.py | 10 +++++-----
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 2e9fc216..b51e9f82 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -25,7 +25,7 @@ from stem.descriptor import (
Descriptor,
)
-HEADER_DIV = '====='
+HEADER_DIV = b'====='
# Converters header attributes to a given type. Malformed fields should be
@@ -103,11 +103,11 @@ def _parse_header(descriptor, entries):
break # end of the content
elif line == HEADER_DIV:
break # end of header
- elif line.startswith('node_id='):
+ elif line.startswith(b'node_id='):
break # version 1.0 measurement
- if '=' in line:
- key, value = line.split('=', 1)
+ if b'=' in line:
+ key, value = stem.util.str_tools._to_unicode(line).split('=', 1)
header[key] = value
else:
raise ValueError("Header expected to be key=value pairs, but had '%s'" % line)
@@ -142,10 +142,11 @@ def _parse_body(descriptor, entries):
measurements = {}
for line in content.readlines():
- attr = dict(_mappings_for('measurement', line.strip()))
+ line = stem.util.str_tools._to_unicode(line.strip())
+ attr = dict(_mappings_for('measurement', line))
if 'node_id' not in attr:
- raise ValueError("Every meaurement must include 'node_id': %s" % line.strip())
+ raise ValueError("Every meaurement must include 'node_id': %s" % line)
elif attr['node_id'] in measurements:
# Relay is listed multiple times. This is a bug for the bandwidth
# authority that made this descriptor, but according to the spec
@@ -207,7 +208,7 @@ class BandwidthFile(Descriptor):
* 'timestamp' is a reserved key for our mandatory header unix timestamp.
- * 'content' is a reserved key for a list of our bandwidth measurements.
+ * 'content' is a reserved key for our bandwidth measurement lines.
* All other keys are treated as header fields.
@@ -233,20 +234,20 @@ class BandwidthFile(Descriptor):
lines = []
if 'timestamp' not in exclude:
- lines.append(timestamp)
+ lines.append(stem.util.str_tools._to_bytes(timestamp))
if version == '1.0.0' and header:
raise ValueError('Headers require BandwidthFile version 1.1 or later')
elif version != '1.0.0':
for k, v in header.items():
- lines.append('%s=%s' % (k, v))
+ lines.append(stem.util.str_tools._to_bytes('%s=%s' % (k, v)))
lines.append(HEADER_DIV)
for measurement in content:
- lines.append(measurement) # TODO: replace when we have a measurement struct
+ lines.append(stem.util.str_tools._to_bytes(measurement))
- return '\n'.join(lines)
+ return b'\n'.join(lines)
def __init__(self, raw_content, validate = False):
super(BandwidthFile, self).__init__(raw_content, lazy_load = not validate)
diff --git a/test/unit/descriptor/bandwidth_file.py b/test/unit/descriptor/bandwidth_file.py
index 43af974b..003f9fe8 100644
--- a/test/unit/descriptor/bandwidth_file.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -150,7 +150,7 @@ class TestBandwidthFile(unittest.TestCase):
'content': [],
})
- self.assertEqual('12345\nversion=1.2.0\n=====', content)
+ self.assertEqual(b'12345\nversion=1.2.0\n=====', content)
@patch('time.time', Mock(return_value = 1410723598.276578))
def test_new_header_attribute(self):
@@ -176,10 +176,10 @@ class TestBandwidthFile(unittest.TestCase):
"""
test_values = (
- '',
- 'boo',
- '123.4',
- '-123',
+ b'',
+ b'boo',
+ b'123.4',
+ b'-123',
)
for value in test_values:
1
0

[translation/tor-launcher-properties_completed] Update translations for tor-launcher-properties_completed
by translation@torproject.org 20 Jan '19
by translation@torproject.org 20 Jan '19
20 Jan '19
commit 03951416d8df2782b223eef04731acf7d6ac2361
Author: Translation commit bot <translation(a)torproject.org>
Date: Sun Jan 20 18:19:20 2019 +0000
Update translations for tor-launcher-properties_completed
---
es/torlauncher.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/es/torlauncher.properties b/es/torlauncher.properties
index 31ffb6ac0..3ca820816 100644
--- a/es/torlauncher.properties
+++ b/es/torlauncher.properties
@@ -4,7 +4,7 @@
torlauncher.error_title=Arranque de Tor
torlauncher.tor_exited_during_startup=Tor se cerró durante el arranque. Esto se podría deber a un error en tu fichero torrc, un fallo en Tor o en otro programa de tu sistema, o a hardware defectuoso. Hasta que soluciones el problema subyacente y reinices Tor, el Navegador Tor no se iniciará.
-torlauncher.tor_exited=Tor se cerró inesperadamente. Esto podría deberse a un fallo con el propio Tor, con otro programa de su sistema, o por hardware defectuoso. Hasta que reinicie Tor, el Navegador Tor no podrá abrir ningún sitio web. Si el problema persiste, por favor envíe una copia de su Registro de Tor (log) al equipo de soporte.
+torlauncher.tor_exited=Tor se cerró inesperadamente. Esto podría deberse a un fallo con el propio Tor, con otro programa de su sistema, o por hardware defectuoso. Hasta que reinicie Tor, el Tor Browser no podrá abrir ningún sitio web. Si el problema persiste, por favor envíe una copia de su Registro de Tor (log) al equipo de soporte.
torlauncher.tor_exited2=Al reiniciar Tor no se cerrarán las pestañas de tu navegador.
torlauncher.tor_controlconn_failed=No se pudo conectar al puerto de control de Tor
torlauncher.tor_failed_to_start=Tor no pudo iniciarse.
1
0