diff --git a/duniterpy/__init__.py b/duniterpy/__init__.py index 91b5f3ceb47f78bc6ab0e1fe21e50e42536b225e..c2952a61267c1d241325bcea04039666862b1c55 100644 --- a/duniterpy/__init__.py +++ b/duniterpy/__init__.py @@ -16,7 +16,6 @@ # Caner Candan <caner@candan.fr>, http://caner.candan.fr # -MANAGED_API=["BASIC_MERKLED_API", "BMAS", "WS2P"] __author__ = 'Caner Candan & inso & vit' __version__ = '0.42.0' diff --git a/duniterpy/documents/constants.py b/duniterpy/documents/constants.py index 994009013d489b308cf15748c1efbe082904f76e..86c8127316c9f2fea27e0e4499dd120c27499bf6 100644 --- a/duniterpy/documents/constants.py +++ b/duniterpy/documents/constants.py @@ -11,5 +11,7 @@ block_uid_regex = "{block_id_regex}-{block_hash_regex}".format(block_id_regex=bl conditions_regex = "(&&|\|\|| |[()]|(SIG\({pubkey_regex}\)|(XHX\({hash_regex}\))))*"\ .format(pubkey_regex=pubkey_regex, hash_regex=hash_regex) ipv4_regex = '(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])' -ipv6_regex = '(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)' +ipv6_regex = '(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)(?:%.+)?' ws2pid_regex = "[0-9a-f]{8}" +host_regex = "[a-z0-9-_.]*(?:.[a-zA-Z])?" +path_regex = "[/\w \.-]*/?" diff --git a/duniterpy/documents/peer.py b/duniterpy/documents/peer.py index 0793e7fb8c2a4ffa4d49c22c82279c06cc5147b0..2b548c6fc621277bf1a682fc607507f96c364625 100644 --- a/duniterpy/documents/peer.py +++ b/duniterpy/documents/peer.py @@ -3,8 +3,7 @@ import re from ..api.bma import ConnectionHandler from .document import Document, MalformedDocumentError from . import BlockUID -from .. import MANAGED_API -from .constants import block_hash_regex, pubkey_regex, ipv4_regex, ipv6_regex, ws2pid_regex +from .constants import block_hash_regex, pubkey_regex, ipv4_regex, ipv6_regex, ws2pid_regex, host_regex, path_regex class Peer(Document): @@ -99,14 +98,9 @@ def endpoint(value): elif isinstance(value, WS2PEndpoint): return value elif isinstance(value, str): - for api in MANAGED_API: - if value.startswith(api): - if api == "BASIC_MERKLED_API": - return BMAEndpoint.from_inline(value) - if api == "BMAS": - return SecuredBMAEndpoint.from_inline(value) - if api == "WS2P": - return WS2PEndpoint.from_inline(value) + for api, cls in MANAGED_API.items(): + if value.startswith(api + " "): + return cls.from_inline(value) return UnknownEndpoint.from_inline(value) else: raise TypeError("Cannot convert {0} to endpoint".format(value)) @@ -141,7 +135,7 @@ class UnknownEndpoint(Endpoint): properties = inline.split()[1:] return cls(api, properties) except IndexError: - return None + raise MalformedDocumentError(inline) def inline(self): doc = self.api @@ -163,9 +157,10 @@ class UnknownEndpoint(Endpoint): class BMAEndpoint(Endpoint): - API = "BMA" - re_inline = re.compile('^BASIC_MERKLED_API(?: ([a-z0-9-_.]*(?:.[a-zA-Z])))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))?(?: ([0-9]+))$'.format(ipv4_regex=ipv4_regex, - ipv6_regex=ipv6_regex)) + API = "BASIC_MERKLED_API" + re_inline = re.compile('^BASIC_MERKLED_API(?: ({host_regex}))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))?(?: ([0-9]+))$'.format(host_regex=host_regex, + ipv4_regex=ipv4_regex, + ipv6_regex=ipv6_regex)) def __init__(self, server, ipv4, ipv6, port): self.server = server @@ -178,7 +173,7 @@ class BMAEndpoint(Endpoint): m = BMAEndpoint.re_inline.match(inline) str_re = BMAEndpoint.re_inline.pattern if m is None: - raise MalformedDocumentError("BMAEndpoint") + raise MalformedDocumentError(BMAEndpoint.API) server = m.group(1) ipv4 = m.group(2) ipv6 = m.group(3) @@ -186,7 +181,7 @@ class BMAEndpoint(Endpoint): return cls(server, ipv4, ipv6, port) def inline(self): - return "BASIC_MERKLED_API{DNS}{IPv4}{IPv6}{PORT}" \ + return BMAEndpoint.API + "{DNS}{IPv4}{IPv6}{PORT}" \ .format(DNS=(" {0}".format(self.server) if self.server else ""), IPv4=(" {0}".format(self.ipv4) if self.ipv4 else ""), IPv6=(" {0}".format(self.ipv6) if self.ipv6 else ""), @@ -221,8 +216,10 @@ class BMAEndpoint(Endpoint): class SecuredBMAEndpoint(BMAEndpoint): API = "BMAS" - re_inline = re.compile('^BMAS(?: ([a-z0-9-_.]*(?:.[a-zA-Z])))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))? ([0-9]+)(?: ([/\w \.-]*)/?)?$'.format(ipv4_regex=ipv4_regex, - ipv6_regex=ipv6_regex)) + re_inline = re.compile('^BMAS(?: ({host_regex}))?(?: ({ipv4_regex}))?(?: ({ipv6_regex}))? ([0-9]+)(?: ({path_regex}))?$'.format(host_regex=host_regex, + ipv4_regex=ipv4_regex, + ipv6_regex=ipv6_regex, + path_regex=path_regex)) def __init__(self, server, ipv4, ipv6, port, path): super().__init__(server, ipv4, ipv6, port) @@ -232,7 +229,7 @@ class SecuredBMAEndpoint(BMAEndpoint): def from_inline(cls, inline): m = SecuredBMAEndpoint.re_inline.match(inline) if m is None: - raise MalformedDocumentError("BMAS") + raise MalformedDocumentError(SecuredBMAEndpoint.API) server = m.group(1) ipv4 = m.group(2) ipv6 = m.group(3) @@ -244,7 +241,7 @@ class SecuredBMAEndpoint(BMAEndpoint): def inline(self): inlined = [str(info) for info in (self.server, self.ipv4, self.ipv6, self.port, self.path) if info] - return "BMAS " + " ".join(inlined) + return SecuredBMAEndpoint.API + " " + " ".join(inlined) def conn_handler(self, session=None, proxy=None): """ @@ -263,28 +260,34 @@ class SecuredBMAEndpoint(BMAEndpoint): class WS2PEndpoint(Endpoint): API = "WS2P" - re_inline = re.compile('^WS2P ({ws2pid_regex}) ((?:[a-z0-9-_.]*(?:.[a-zA-Z]))|(?:{ipv4_regex})) ([0-9]+)?$'.format(ws2pid_regex=ws2pid_regex, + re_inline = re.compile('^WS2P ({ws2pid_regex}) ((?:{host_regex})|(?:{ipv4_regex})) ([0-9]+)?(?: ({path_regex}))?$'.format(ws2pid_regex=ws2pid_regex, + host_regex=host_regex, ipv4_regex=ipv4_regex, - ipv6_regex=ipv6_regex)) + ipv6_regex=ipv6_regex, + path_regex=path_regex)) - def __init__(self, ws2pid, server, port): + def __init__(self, ws2pid, server, port, path): self.ws2pid = ws2pid self.server = server self.port = port + self.path = path @classmethod def from_inline(cls, inline): m = WS2PEndpoint.re_inline.match(inline) if m is None: - raise MalformedDocumentError("WS2P") + raise MalformedDocumentError(WS2PEndpoint.API) ws2pid = m.group(1) server = m.group(2) port = int(m.group(3)) - return cls(ws2pid, server, port) + path = m.group(4) + if not path: + path = "" + return cls(ws2pid, server, port, path) def inline(self): - inlined = [str(info) for info in (self.ws2pid, self.server,self.port)] - return "WS2P " + " ".join(inlined) + inlined = [str(info) for info in (self.ws2pid, self.server, self.port, self.path) if info] + return WS2PEndpoint.API + " " + " ".join(inlined) def conn_handler(self, session=None, proxy=None): """ @@ -293,7 +296,7 @@ class WS2PEndpoint(Endpoint): :param aiohttp.ClientSession session: AIOHTTP client session instance :rtype: ConnectionHandler """ - yield ConnectionHandler("https", "wss", self.server, self.port, "", proxy, session) + yield ConnectionHandler("https", "wss", self.server, self.port, self.path, proxy, session) def __str__(self): return self.inline() @@ -301,9 +304,109 @@ class WS2PEndpoint(Endpoint): def __eq__(self, other): if isinstance(other, WS2PEndpoint): return self.server == other.server and self.ws2pid == other.ws2pid \ - and self.port == other.port + and self.port == other.port and self.path == other.path + else: + return False + + def __hash__(self): + return hash((self.ws2pid, self.server, self.port, self.path)) + + +class ESUserEndpoint(Endpoint): + API = "ES_USER_API" + re_inline = re.compile('^ES_USER_API ((?:{host_regex})|(?:{ipv4_regex})) ([0-9]+)$'.format(ws2pid_regex=ws2pid_regex, + host_regex=host_regex, + ipv4_regex=ipv4_regex)) + + def __init__(self, server, port): + self.server = server + self.port = port + + @classmethod + def from_inline(cls, inline): + m = ESUserEndpoint.re_inline.match(inline) + if m is None: + raise MalformedDocumentError(ESUserEndpoint.API) + server = m.group(1) + port = int(m.group(2)) + return cls(server, port) + + def inline(self): + inlined = [str(info) for info in (self.server, self.port) if info] + return ESUserEndpoint.API + " " + " ".join(inlined) + + def conn_handler(self, session=None, proxy=None): + """ + Return connection handler instance for the endpoint + + :param aiohttp.ClientSession session: AIOHTTP client session instance + :rtype: ConnectionHandler + """ + yield ConnectionHandler("https", "wss", self.server, self.port, "", proxy, session) + + def __str__(self): + return self.inline() + + def __eq__(self, other): + if isinstance(other, ESUserEndpoint): + return self.server == other.server and self.port == other.port else: return False def __hash__(self): - return hash((self.ws2pid, self.server, self.port)) \ No newline at end of file + return hash((self.server, self.port)) + + +class ESSubscribtionEndpoint(Endpoint): + API = "ES_SUBSCRIPTION_API" + re_inline = re.compile('^ES_SUBSCRIPTION_API ((?:{host_regex})|(?:{ipv4_regex})) ([0-9]+)$'.format(ws2pid_regex=ws2pid_regex, + host_regex=host_regex, + ipv4_regex=ipv4_regex)) + + def __init__(self, server, port): + self.server = server + self.port = port + + @classmethod + def from_inline(cls, inline): + m = ESSubscribtionEndpoint.re_inline.match(inline) + if m is None: + raise MalformedDocumentError(ESSubscribtionEndpoint.API) + server = m.group(1) + port = int(m.group(2)) + return cls(server, port) + + def inline(self): + inlined = [str(info) for info in (self.server, self.port) if info] + return ESSubscribtionEndpoint.API + " " + " ".join(inlined) + + def conn_handler(self, session=None, proxy=None): + """ + Return connection handler instance for the endpoint + + :param aiohttp.ClientSession session: AIOHTTP client session instance + :rtype: ConnectionHandler + """ + yield ConnectionHandler("https", "wss", self.server, self.port, "", proxy, session) + + def __str__(self): + return self.inline() + + def __eq__(self, other): + if isinstance(other, ESSubscribtionEndpoint): + return self.server == other.server and self.port == other.port + else: + return False + + def __hash__(self): + return hash((ESSubscribtionEndpoint.API, self.server, self.port)) + + + +MANAGED_API={ + BMAEndpoint.API: BMAEndpoint, + SecuredBMAEndpoint.API: SecuredBMAEndpoint, + WS2PEndpoint.API: WS2PEndpoint, + ESUserEndpoint.API: ESUserEndpoint, + ESSubscribtionEndpoint.API: ESSubscribtionEndpoint +}