Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
1import os
2import time
3import io
4import math
5import hashlib
6import re
7import sys
8
9from Config import config
10from Crypt import CryptHash
11from Plugin import PluginManager
12from Debug import Debug
13from util import helper
14
15plugin_dir = os.path.dirname(__file__)
16
17benchmark_key = None
18
19
20@PluginManager.registerTo("UiRequest")
21class UiRequestPlugin(object):
22 @helper.encodeResponse
23 def actionBenchmark(self):
24 global benchmark_key
25 script_nonce = self.getScriptNonce()
26 if not benchmark_key:
27 benchmark_key = CryptHash.random(encoding="base64")
28 self.sendHeader(script_nonce=script_nonce)
29
30 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
31 yield "This function is disabled on this proxy"
32 return
33
34 data = self.render(
35 plugin_dir + "/media/benchmark.html",
36 script_nonce=script_nonce,
37 benchmark_key=benchmark_key,
38 filter=re.sub("[^A-Za-z0-9]", "", self.get.get("filter", ""))
39 )
40 yield data
41
42 @helper.encodeResponse
43 def actionBenchmarkResult(self):
44 global benchmark_key
45 if self.get.get("benchmark_key", "") != benchmark_key:
46 return self.error403("Invalid benchmark key")
47
48 self.sendHeader(content_type="text/plain", noscript=True)
49
50 if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
51 yield "This function is disabled on this proxy"
52 return
53
54 yield " " * 1024 # Head (required for streaming)
55
56 import main
57 s = time.time()
58
59 for part in main.actions.testBenchmark(filter=self.get.get("filter", "")):
60 yield part
61
62 yield "\n - Total time: %.3fs" % (time.time() - s)
63
64
65@PluginManager.registerTo("Actions")
66class ActionsPlugin:
67 def getMultiplerTitle(self, multipler):
68 if multipler < 0.3:
69 multipler_title = "Sloooow"
70 elif multipler < 0.6:
71 multipler_title = "Ehh"
72 elif multipler < 0.8:
73 multipler_title = "Goodish"
74 elif multipler < 1.2:
75 multipler_title = "OK"
76 elif multipler < 1.7:
77 multipler_title = "Fine"
78 elif multipler < 2.5:
79 multipler_title = "Fast"
80 elif multipler < 3.5:
81 multipler_title = "WOW"
82 else:
83 multipler_title = "Insane!!"
84 return multipler_title
85
86 def formatResult(self, taken, standard):
87 if not standard:
88 return " Done in %.3fs" % taken
89
90 if taken > 0:
91 multipler = standard / taken
92 else:
93 multipler = 99
94 multipler_title = self.getMultiplerTitle(multipler)
95
96 return " Done in %.3fs = %s (%.2fx)" % (taken, multipler_title, multipler)
97
98 def getBenchmarkTests(self, online=False):
99 if hasattr(super(), "getBenchmarkTests"):
100 tests = super().getBenchmarkTests(online)
101 else:
102 tests = []
103
104 tests.extend([
105 {"func": self.testHdPrivatekey, "num": 50, "time_standard": 0.57},
106 {"func": self.testSign, "num": 20, "time_standard": 0.46},
107 {"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto_fallback"}, "num": 20, "time_standard": 0.38},
108 {"func": self.testVerify, "kwargs": {"lib_verify": "sslcrypto"}, "num": 200, "time_standard": 0.30},
109 {"func": self.testVerify, "kwargs": {"lib_verify": "libsecp256k1"}, "num": 200, "time_standard": 0.10},
110
111 {"func": self.testPackMsgpack, "num": 100, "time_standard": 0.35},
112 {"func": self.testUnpackMsgpackStreaming, "kwargs": {"fallback": False}, "num": 100, "time_standard": 0.35},
113 {"func": self.testUnpackMsgpackStreaming, "kwargs": {"fallback": True}, "num": 10, "time_standard": 0.5},
114
115 {"func": self.testPackZip, "num": 5, "time_standard": 0.065},
116 {"func": self.testPackArchive, "kwargs": {"archive_type": "gz"}, "num": 5, "time_standard": 0.08},
117 {"func": self.testPackArchive, "kwargs": {"archive_type": "bz2"}, "num": 5, "time_standard": 0.68},
118 {"func": self.testPackArchive, "kwargs": {"archive_type": "xz"}, "num": 5, "time_standard": 0.47},
119 {"func": self.testUnpackZip, "num": 20, "time_standard": 0.25},
120 {"func": self.testUnpackArchive, "kwargs": {"archive_type": "gz"}, "num": 20, "time_standard": 0.28},
121 {"func": self.testUnpackArchive, "kwargs": {"archive_type": "bz2"}, "num": 20, "time_standard": 0.83},
122 {"func": self.testUnpackArchive, "kwargs": {"archive_type": "xz"}, "num": 20, "time_standard": 0.38},
123
124 {"func": self.testCryptHash, "kwargs": {"hash_type": "sha256"}, "num": 10, "time_standard": 0.50},
125 {"func": self.testCryptHash, "kwargs": {"hash_type": "sha512"}, "num": 10, "time_standard": 0.33},
126 {"func": self.testCryptHashlib, "kwargs": {"hash_type": "sha3_256"}, "num": 10, "time_standard": 0.33},
127 {"func": self.testCryptHashlib, "kwargs": {"hash_type": "sha3_512"}, "num": 10, "time_standard": 0.65},
128
129 {"func": self.testRandom, "num": 100, "time_standard": 0.08},
130 ])
131
132 if online:
133 tests += [
134 {"func": self.testHttps, "num": 1, "time_standard": 2.1}
135 ]
136 return tests
137
138 def testBenchmark(self, num_multipler=1, online=False, num_run=None, filter=None):
139 """
140 Run benchmark on client functions
141 """
142 tests = self.getBenchmarkTests(online=online)
143
144 if filter:
145 tests = [test for test in tests[:] if filter.lower() in test["func"].__name__.lower()]
146
147 yield "\n"
148 res = {}
149 res_time_taken = {}
150 multiplers = []
151 for test in tests:
152 s = time.time()
153 if num_run:
154 num_run_test = num_run
155 else:
156 num_run_test = math.ceil(test["num"] * num_multipler)
157 func = test["func"]
158 func_name = func.__name__
159 kwargs = test.get("kwargs", {})
160 key = "%s %s" % (func_name, kwargs)
161 if kwargs:
162 yield "* Running %s (%s) x %s " % (func_name, kwargs, num_run_test)
163 else:
164 yield "* Running %s x %s " % (func_name, num_run_test)
165 i = 0
166 try:
167 for progress in func(num_run_test, **kwargs):
168 i += 1
169 if num_run_test > 10:
170 should_print = i % (num_run_test / 10) == 0 or progress != "."
171 else:
172 should_print = True
173
174 if should_print:
175 if num_run_test == 1 and progress == ".":
176 progress = "..."
177 yield progress
178 time_taken = time.time() - s
179 if num_run:
180 time_standard = 0
181 else:
182 time_standard = test["time_standard"] * num_multipler
183 yield self.formatResult(time_taken, time_standard)
184 yield "\n"
185 res[key] = "ok"
186 res_time_taken[key] = time_taken
187 multiplers.append(time_standard / max(time_taken, 0.001))
188 except Exception as err:
189 res[key] = err
190 yield "Failed!\n! Error: %s\n\n" % Debug.formatException(err)
191
192 yield "\n== Result ==\n"
193
194 # Check verification speed
195 if "testVerify {'lib_verify': 'sslcrypto'}" in res_time_taken:
196 speed_order = ["sslcrypto_fallback", "sslcrypto", "libsecp256k1"]
197 time_taken = {}
198 for lib_verify in speed_order:
199 time_taken[lib_verify] = res_time_taken["testVerify {'lib_verify': '%s'}" % lib_verify]
200
201 time_taken["sslcrypto_fallback"] *= 10 # fallback benchmark only run 20 times instead of 200
202 speedup_sslcrypto = time_taken["sslcrypto_fallback"] / time_taken["sslcrypto"]
203 speedup_libsecp256k1 = time_taken["sslcrypto_fallback"] / time_taken["libsecp256k1"]
204
205 yield "\n* Verification speedup:\n"
206 yield " - OpenSSL: %.1fx (reference: 7.0x)\n" % speedup_sslcrypto
207 yield " - libsecp256k1: %.1fx (reference: 23.8x)\n" % speedup_libsecp256k1
208
209 if speedup_sslcrypto < 2:
210 res["Verification speed"] = "error: OpenSSL speedup low: %.1fx" % speedup_sslcrypto
211
212 if speedup_libsecp256k1 < speedup_sslcrypto:
213 res["Verification speed"] = "error: libsecp256k1 speedup low: %.1fx" % speedup_libsecp256k1
214
215 if not res:
216 yield "! No tests found"
217 if config.action == "test":
218 sys.exit(1)
219 else:
220 num_failed = len([res_key for res_key, res_val in res.items() if res_val != "ok"])
221 num_success = len([res_key for res_key, res_val in res.items() if res_val == "ok"])
222 yield "\n* Tests:\n"
223 yield " - Total: %s tests\n" % len(res)
224 yield " - Success: %s tests\n" % num_success
225 yield " - Failed: %s tests\n" % num_failed
226 if any(multiplers):
227 multipler_avg = sum(multiplers) / len(multiplers)
228 multipler_title = self.getMultiplerTitle(multipler_avg)
229 yield " - Average speed factor: %.2fx (%s)\n" % (multipler_avg, multipler_title)
230
231 # Display errors
232 for res_key, res_val in res.items():
233 if res_val != "ok":
234 yield " ! %s %s\n" % (res_key, res_val)
235
236 if num_failed != 0 and config.action == "test":
237 sys.exit(1)
238
239 def testHttps(self, num_run=1):
240 """
241 Test https connection with valid and invalid certs
242 """
243 import urllib.request
244 import urllib.error
245
246 body = urllib.request.urlopen("https://google.com").read()
247 assert len(body) > 100
248 yield "."
249
250 badssl_urls = [
251 "https://expired.badssl.com/",
252 "https://wrong.host.badssl.com/",
253 "https://self-signed.badssl.com/",
254 "https://untrusted-root.badssl.com/"
255 ]
256 for badssl_url in badssl_urls:
257 try:
258 body = urllib.request.urlopen(badssl_url).read()
259 https_err = None
260 except urllib.error.URLError as err:
261 https_err = err
262 assert https_err
263 yield "."
264
265 def testCryptHash(self, num_run=1, hash_type="sha256"):
266 """
267 Test hashing functions
268 """
269 yield "(5MB) "
270
271 from Crypt import CryptHash
272
273 hash_types = {
274 "sha256": {"func": CryptHash.sha256sum, "hash_valid": "8cd629d9d6aff6590da8b80782a5046d2673d5917b99d5603c3dcb4005c45ffa"},
275 "sha512": {"func": CryptHash.sha512sum, "hash_valid": "9ca7e855d430964d5b55b114e95c6bbb114a6d478f6485df93044d87b108904d"}
276 }
277 hash_func = hash_types[hash_type]["func"]
278 hash_valid = hash_types[hash_type]["hash_valid"]
279
280 data = io.BytesIO(b"Hello" * 1024 * 1024) # 5MB
281 for i in range(num_run):
282 data.seek(0)
283 hash = hash_func(data)
284 yield "."
285 assert hash == hash_valid, "%s != %s" % (hash, hash_valid)
286
287 def testCryptHashlib(self, num_run=1, hash_type="sha3_256"):
288 """
289 Test SHA3 hashing functions
290 """
291 yield "x 5MB "
292
293 hash_types = {
294 "sha3_256": {"func": hashlib.sha3_256, "hash_valid": "c8aeb3ef9fe5d6404871c0d2a4410a4d4e23268e06735648c9596f436c495f7e"},
295 "sha3_512": {"func": hashlib.sha3_512, "hash_valid": "b75dba9472d8af3cc945ce49073f3f8214d7ac12086c0453fb08944823dee1ae83b3ffbc87a53a57cc454521d6a26fe73ff0f3be38dddf3f7de5d7692ebc7f95"},
296 }
297
298 hash_func = hash_types[hash_type]["func"]
299 hash_valid = hash_types[hash_type]["hash_valid"]
300
301 data = io.BytesIO(b"Hello" * 1024 * 1024) # 5MB
302 for i in range(num_run):
303 data.seek(0)
304 h = hash_func()
305 while 1:
306 buff = data.read(1024 * 64)
307 if not buff:
308 break
309 h.update(buff)
310 hash = h.hexdigest()
311 yield "."
312 assert hash == hash_valid, "%s != %s" % (hash, hash_valid)
313
314 def testRandom(self, num_run=1):
315 """
316 Test generating random data
317 """
318 yield "x 1000 x 256 bytes "
319 for i in range(num_run):
320 data_last = None
321 for y in range(1000):
322 data = os.urandom(256)
323 assert data != data_last
324 assert len(data) == 256
325 data_last = data
326 yield "."
327
328 def testHdPrivatekey(self, num_run=2):
329 """
330 Test generating deterministic private keys from a master seed
331 """
332 from Crypt import CryptBitcoin
333 seed = "e180efa477c63b0f2757eac7b1cce781877177fe0966be62754ffd4c8592ce38"
334 privatekeys = []
335 for i in range(num_run):
336 privatekeys.append(CryptBitcoin.hdPrivatekey(seed, i * 10))
337 yield "."
338 valid = "5JSbeF5PevdrsYjunqpg7kAGbnCVYa1T4APSL3QRu8EoAmXRc7Y"
339 assert privatekeys[0] == valid, "%s != %s" % (privatekeys[0], valid)
340 if len(privatekeys) > 1:
341 assert privatekeys[0] != privatekeys[-1]
342
343 def testSign(self, num_run=1):
344 """
345 Test signing data using a private key
346 """
347 from Crypt import CryptBitcoin
348 data = "Hello" * 1024
349 privatekey = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
350 for i in range(num_run):
351 yield "."
352 sign = CryptBitcoin.sign(data, privatekey)
353 valid = "G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w="
354 assert sign == valid, "%s != %s" % (sign, valid)
355
356 def testVerify(self, num_run=1, lib_verify="sslcrypto"):
357 """
358 Test verification of generated signatures
359 """
360 from Crypt import CryptBitcoin
361 CryptBitcoin.loadLib(lib_verify, silent=True)
362
363
364 data = "Hello" * 1024
365 privatekey = "5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk"
366 address = CryptBitcoin.privatekeyToAddress(privatekey)
367 sign = "G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w="
368
369 for i in range(num_run):
370 ok = CryptBitcoin.verify(data, address, sign, lib_verify=lib_verify)
371 yield "."
372 assert ok, "does not verify from %s" % address
373
374 if lib_verify == "sslcrypto":
375 yield("(%s)" % CryptBitcoin.sslcrypto.ecc.get_backend())
376
377 def testPortCheckers(self):
378 """
379 Test all active open port checker
380 """
381 from Peer import PeerPortchecker
382 for ip_type, func_names in PeerPortchecker.PeerPortchecker.checker_functions.items():
383 yield "\n- %s:" % ip_type
384 for func_name in func_names:
385 yield "\n - Tracker %s: " % func_name
386 try:
387 for res in self.testPortChecker(func_name):
388 yield res
389 except Exception as err:
390 yield Debug.formatException(err)
391
392 def testPortChecker(self, func_name):
393 """
394 Test single open port checker
395 """
396 from Peer import PeerPortchecker
397 peer_portchecker = PeerPortchecker.PeerPortchecker(None)
398 announce_func = getattr(peer_portchecker, func_name)
399 res = announce_func(3894)
400 yield res
401
402 def testAll(self):
403 """
404 Run all tests to check system compatibility with ZeroNet functions
405 """
406 for progress in self.testBenchmark(online=not config.offline, num_run=1):
407 yield progress
408
409
410@PluginManager.registerTo("ConfigPlugin")
411class ConfigPlugin(object):
412 def createArguments(self):
413 back = super(ConfigPlugin, self).createArguments()
414 if self.getCmdlineValue("test") == "benchmark":
415 self.test_parser.add_argument(
416 '--num_multipler', help='Benchmark run time multipler',
417 default=1.0, type=float, metavar='num'
418 )
419 self.test_parser.add_argument(
420 '--filter', help='Filter running benchmark',
421 default=None, metavar='test name'
422 )
423 elif self.getCmdlineValue("test") == "portChecker":
424 self.test_parser.add_argument(
425 '--func_name', help='Name of open port checker function',
426 default=None, metavar='func_name'
427 )
428 return back