Files
pylibmeshctrl/src/meshctrl/util.py

180 lines
6.2 KiB
Python
Raw Normal View History

2024-11-01 12:00:16 -07:00
import secrets
import time
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import json
import base64
import asyncio
import collections
import re
import websockets
import ssl
import functools
import urllib
import python_socks
from python_socks.async_.asyncio import Proxy
from . import exceptions
2024-11-01 12:00:16 -07:00
def _encode_cookie(o, key):
2024-11-01 12:00:16 -07:00
o["time"] = int(time.time()); # Add the cookie creation time
iv = secrets.token_bytes(12)
key = AESGCM(key)
crypted = key.encrypt(iv, json.dumps(o), None)
return base64.b64encode(crypted).replace(b"+", b'@').replace(b"/", b'$').decode("utf-8")
def _check_amt_password(p):
return (len(p) > 7) and\
(re.search(r"\d",p) is not None) and\
(re.search(r"[a-z]",p) is not None) and\
(re.search(r"[A-Z]",p) is not None) and\
(re.search(r"\W",p) is not None)
def _get_random_amt_password():
p = ""
while not _check_amt_password(p):
p = b"@".join(base64.b64encode(secrets.token_bytes(9)).split(b'/')).decode("utf-8")
return p
def _get_random_hex(count):
return secrets.token_bytes(count).hex();
2024-11-01 12:00:16 -07:00
class Eventer(object):
"""
Eventer object to allow pub/sub interactions with a Session object
"""
2024-11-01 12:00:16 -07:00
def __init__(self):
self._ons = {}
self._onces = {}
def on(self, event, func):
"""
Subscribe to `event`. `func` will be called when that event is emitted.
Args:
event (str): Event name to subscribe to
func (function(data: object)): Function to call when event is emitted. `data` could be of any type. Also used as a key to remove this subscription.
"""
2024-11-01 12:00:16 -07:00
self._ons.setdefault(event, set()).add(func)
def once(self, event, func):
"""
Subscribe to `event` once. `func` will be called when that event is emitted. The binding will then be removed.
Args:
event (str): Event name to subscribe to
func (function(data: object)): Function to call when event is emitted. `data` could be of any type. Also used as a key to remove this subscription.
"""
2024-11-01 12:00:16 -07:00
self._onces.setdefault(event, set()).add(func)
def off(self, event, func):
"""
Unsubscribe from `event`. `func` is the object originally passed during the bind.
Args:
event (str): Event name to unsubscribe from
func (object): Function which was originally passed when subscribing.
"""
2024-11-01 12:00:16 -07:00
try:
self._onces.setdefault(event, set()).remove(func)
except KeyError:
pass
try:
self._ons.setdefault(event, set()).remove(func)
except KeyError:
pass
async def emit(self, event, data):
"""
Emit `event` with `data`. All subscribed functions will be called (order is nonsensical).
Args:
event (str): Event name emit
data (object): Data to pass to all the bound functions
"""
2024-11-01 12:00:16 -07:00
for f in self._onces.get(event, []):
await f(data)
2024-11-01 12:00:16 -07:00
try:
del self._onces[event]
except KeyError:
pass
for f in self._ons.get(event, []):
await f(data)
def compare_dict(dict1, dict2):
try:
if dict1 == dict2:
return True
for key, val in dict1.items():
if key not in dict2:
return False
if type(val) is dict:
if not compare_dict(val, dict2[key]):
return False
elif type(val) is set:
for v in val:
for v2 in dict2[key]:
try:
if compare_dict(v, v2):
break
except:
pass
else:
return False
elif isinstance(val, collections.abc.Iterable):
# We don't want strings to match other iterables, so check that
if isinstance(val, str) or isinstance(dict2[key], str):
if not isinstance(val, type(dict2[key])):
return False
try:
if (len(val) != len(dict2[key])):
return False
for i, v in enumerate(val):
if not compare_dict(v, dict2[key][i]):
return False
except Exception as e:
return False
elif (dict2[key] != val):
return False
return True
except Exception:
return False
def _check_socket(f):
async def _check_errs(self):
if not self.alive and self._main_loop_error is not None:
raise self._main_loop_error
elif not self.alive and self.initialized.is_set():
raise exceptions.SocketError("Socket Closed")
@functools.wraps(f)
async def wrapper(self, *args, **kwargs):
try:
await asyncio.wait_for(self.initialized.wait(), 10)
await _check_errs(self)
await asyncio.wait_for(self._socket_open.wait(), 10)
finally:
await _check_errs(self)
return await f(self, *args, **kwargs)
return wrapper
def _process_websocket_exception(exc):
tmp = websockets.asyncio.client.process_exception(exc)
# SSLVerification error is a subclass of OSError, but doesn't make sense to retry, so we need to handle it separately.
if isinstance(exc, (ssl.SSLCertVerificationError, TimeoutError)):
return exc
if isinstance(exc, python_socks._errors.ProxyError):
return None
return tmp
class proxy_connect(websockets.asyncio.client.connect):
def __init__(self,*args, proxy_url=None, **kwargs):
self.proxy = None
if proxy_url is not None:
self.proxy = Proxy.from_url(proxy_url)
super().__init__(*args, **kwargs)
async def create_connection(self, *args, **kwargs):
if self.proxy is not None:
parsed = urllib.parse.urlparse(self.uri)
self.connection_kwargs["sock"] = await self.proxy.connect(dest_host=parsed.hostname, dest_port=parsed.port)
return await super().create_connection(*args, **kwargs)