Skip to content

Commit f3c2930

Browse files
Update protocols.py
1 parent b8ca134 commit f3c2930

File tree

1 file changed

+171
-76
lines changed

1 file changed

+171
-76
lines changed

multiaddr/protocols.py

Lines changed: 171 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# -*- coding: utf-8 -*-
2-
import six
31
import varint
42

53
from . import exceptions
64
from .codecs import codec_by_name
75

6+
__all__ = ("Protocol", "PROTOCOLS", "REGISTRY")
7+
88

99
# source of protocols https://github.com/multiformats/multicodec/blob/master/table.csv#L382
1010
# replicating table here to:
@@ -23,6 +23,7 @@
2323
P_P2P = 0x01A5
2424
P_HTTP = 0x01E0
2525
P_HTTPS = 0x01BB
26+
P_TLS = 0x01C0
2627
P_QUIC = 0x01CC
2728
P_QUIC1 = 0x01CD
2829
P_WS = 0x01DD
@@ -39,50 +40,20 @@
3940
P_P2P_WEBRTC_DIRECT = 0x0114
4041
P_UNIX = 0x0190
4142

42-
_CODES = [
43-
P_IP4,
44-
P_IP6,
45-
P_IP6ZONE,
46-
P_TCP,
47-
P_UDP,
48-
P_DCCP,
49-
P_SCTP,
50-
P_UDT,
51-
P_UTP,
52-
P_P2P,
53-
P_HTTP,
54-
P_HTTPS,
55-
P_QUIC,
56-
P_QUIC1,
57-
P_WS,
58-
P_WSS,
59-
P_ONION,
60-
P_ONION3,
61-
P_P2P_CIRCUIT,
62-
P_DNS,
63-
P_DNS4,
64-
P_DNS6,
65-
P_DNSADDR,
66-
P_P2P_WEBSOCKET_STAR,
67-
P_P2P_WEBRTC_STAR,
68-
P_P2P_WEBRTC_DIRECT,
69-
P_UNIX,
70-
]
71-
7243

73-
class Protocol(object):
44+
class Protocol:
7445
__slots__ = [
7546
"code", # int
7647
"name", # string
7748
"codec", # string
7849
]
7950

8051
def __init__(self, code, name, codec):
81-
if not isinstance(code, six.integer_types):
52+
if not isinstance(code, int):
8253
raise TypeError("code must be an integer")
83-
if not isinstance(name, six.string_types):
54+
if not isinstance(name, str):
8455
raise TypeError("name must be a string")
85-
if not isinstance(codec, six.string_types) and codec is not None:
56+
if not isinstance(codec, str) and codec is not None:
8657
raise TypeError("codec must be a string or None")
8758

8859
self.code = code
@@ -121,28 +92,29 @@ def __repr__(self):
12192
)
12293

12394

124-
# Protocols is the list of multiaddr protocols supported by this module.
95+
# List of multiaddr protocols supported by this module by default
12596
PROTOCOLS = [
12697
Protocol(P_IP4, 'ip4', 'ip4'),
12798
Protocol(P_TCP, 'tcp', 'uint16be'),
12899
Protocol(P_UDP, 'udp', 'uint16be'),
129100
Protocol(P_DCCP, 'dccp', 'uint16be'),
130101
Protocol(P_IP6, 'ip6', 'ip6'),
131102
Protocol(P_IP6ZONE, 'ip6zone', 'utf8'),
132-
Protocol(P_DNS, 'dns', 'idna'),
133-
Protocol(P_DNS4, 'dns4', 'idna'),
134-
Protocol(P_DNS6, 'dns6', 'idna'),
135-
Protocol(P_DNSADDR, 'dnsaddr', 'idna'),
103+
Protocol(P_DNS, 'dns', 'domain'),
104+
Protocol(P_DNS4, 'dns4', 'domain'),
105+
Protocol(P_DNS6, 'dns6', 'domain'),
106+
Protocol(P_DNSADDR, 'dnsaddr', 'domain'),
136107
Protocol(P_SCTP, 'sctp', 'uint16be'),
137108
Protocol(P_UDT, 'udt', None),
138109
Protocol(P_UTP, 'utp', None),
139-
Protocol(P_P2P, 'p2p', 'p2p'),
110+
Protocol(P_P2P, 'p2p', 'cid'),
140111
Protocol(P_ONION, 'onion', 'onion'),
141112
Protocol(P_ONION3, 'onion3', 'onion3'),
142113
Protocol(P_QUIC, 'quic', None),
143114
Protocol(P_QUIC1, 'quic-v1', None),
144115
Protocol(P_HTTP, 'http', None),
145116
Protocol(P_HTTPS, 'https', None),
117+
Protocol(P_TLS, 'tls', None),
146118
Protocol(P_WS, 'ws', None),
147119
Protocol(P_WSS, 'wss', None),
148120
Protocol(P_P2P_WEBSOCKET_STAR, 'p2p-websocket-star', None),
@@ -152,57 +124,180 @@ def __repr__(self):
152124
Protocol(P_UNIX, 'unix', 'fspath'),
153125
]
154126

155-
_names_to_protocols = dict((proto.name, proto) for proto in PROTOCOLS)
156-
_codes_to_protocols = dict((proto.code, proto) for proto in PROTOCOLS)
157127

128+
class ProtocolRegistry:
129+
"""A collection of individual Multiaddr protocols indexed for fast lookup"""
130+
__slots__ = ("_codes_to_protocols", "_locked", "_names_to_protocols")
131+
132+
def __init__(self, protocols=()):
133+
self._locked = False
134+
self._codes_to_protocols = {proto.code: proto for proto in protocols}
135+
self._names_to_protocols = {proto.name: proto for proto in protocols}
136+
137+
def add(self, proto):
138+
"""Add the given protocol description to this registry
139+
140+
Raises
141+
------
142+
~multiaddr.exceptions.ProtocolRegistryLocked
143+
Protocol registry is locked and does not accept any new entries.
144+
145+
You can use `.copy(unlock=True)` to copy an existing locked registry
146+
and unlock it.
147+
~multiaddr.exceptions.ProtocolExistsError
148+
A protocol with the given name or code already exists.
149+
"""
150+
if self._locked:
151+
raise exceptions.ProtocolRegistryLocked()
152+
153+
if proto.name in self._names_to_protocols:
154+
raise exceptions.ProtocolExistsError(proto, "name")
155+
156+
if proto.code in self._codes_to_protocols:
157+
raise exceptions.ProtocolExistsError(proto, "code")
158+
159+
self._names_to_protocols[proto.name] = proto
160+
self._codes_to_protocols[proto.code] = proto
161+
return proto
162+
163+
def add_alias_name(self, proto, alias_name):
164+
"""Add an alternate name for an existing protocol description to the registry
165+
166+
Raises
167+
------
168+
~multiaddr.exceptions.ProtocolRegistryLocked
169+
Protocol registry is locked and does not accept any new entries.
170+
171+
You can use `.copy(unlock=True)` to copy an existing locked registry
172+
and unlock it.
173+
~multiaddr.exceptions.ProtocolExistsError
174+
A protocol with the given name already exists.
175+
~multiaddr.exceptions.ProtocolNotFoundError
176+
No protocol matching *proto* could be found.
177+
"""
178+
if self._locked:
179+
raise exceptions.ProtocolRegistryLocked()
180+
181+
proto = self.find(proto)
182+
assert self._names_to_protocols.get(proto.name) is proto, \
183+
"Protocol to alias must have already been added to the registry"
184+
185+
if alias_name in self._names_to_protocols:
186+
raise exceptions.ProtocolExistsError(self._names_to_protocols[alias_name], "name")
187+
188+
self._names_to_protocols[alias_name] = proto
189+
190+
def add_alias_code(self, proto, alias_code):
191+
"""Add an alternate code for an existing protocol description to the registry
158192
159-
def add_protocol(proto):
160-
if proto.name in _names_to_protocols:
161-
raise exceptions.ProtocolExistsError(proto, "name")
193+
Raises
194+
------
195+
~multiaddr.exceptions.ProtocolRegistryLocked
196+
Protocol registry is locked and does not accept any new entries.
162197
163-
if proto.code in _codes_to_protocols:
164-
raise exceptions.ProtocolExistsError(proto, "code")
198+
You can use `.copy(unlock=True)` to copy an existing locked registry
199+
and unlock it.
200+
~multiaddr.exceptions.ProtocolExistsError
201+
A protocol with the given code already exists.
202+
~multiaddr.exceptions.ProtocolNotFoundError
203+
No protocol matching *proto* could be found.
204+
"""
205+
if self._locked:
206+
raise exceptions.ProtocolRegistryLocked()
165207

166-
PROTOCOLS.append(proto)
167-
_names_to_protocols[proto.name] = proto
168-
_codes_to_protocols[proto.code] = proto
169-
return None
208+
proto = self.find(proto)
209+
assert self._codes_to_protocols.get(proto.code) is proto, \
210+
"Protocol to alias must have already been added to the registry"
211+
212+
if alias_code in self._codes_to_protocols:
213+
raise exceptions.ProtocolExistsError(self._codes_to_protocols[alias_code], "name")
214+
215+
self._codes_to_protocols[alias_code] = proto
216+
217+
def lock(self):
218+
"""Lock this registry instance to deny any further changes"""
219+
self._locked = True
220+
221+
@property
222+
def locked(self):
223+
return self._locked
224+
225+
def copy(self, *, unlock=False):
226+
"""Create a copy of this protocol registry
227+
228+
Arguments
229+
---------
230+
unlock
231+
Create the copied registry unlocked even if the current one is locked?
232+
"""
233+
registry = ProtocolRegistry()
234+
registry._locked = self._locked and not unlock
235+
registry._codes_to_protocols = self._codes_to_protocols.copy()
236+
registry._names_to_protocols = self._names_to_protocols.copy()
237+
return registry
238+
239+
__copy__ = copy
240+
241+
def find_by_name(self, name):
242+
"""Look up a protocol by its human-readable name
243+
244+
Raises
245+
------
246+
~multiaddr.exceptions.ProtocolNotFoundError
247+
"""
248+
if name not in self._names_to_protocols:
249+
raise exceptions.ProtocolNotFoundError(name, "name")
250+
return self._names_to_protocols[name]
251+
252+
def find_by_code(self, code):
253+
"""Look up a protocol by its binary representation code
254+
255+
Raises
256+
------
257+
~multiaddr.exceptions.ProtocolNotFoundError
258+
"""
259+
if code not in self._codes_to_protocols:
260+
raise exceptions.ProtocolNotFoundError(code, "code")
261+
return self._codes_to_protocols[code]
262+
263+
def find(self, proto):
264+
"""Look up a protocol by its name or code, return existing protocol objects unchanged
265+
266+
Raises
267+
------
268+
~multiaddr.exceptions.ProtocolNotFoundError
269+
"""
270+
if isinstance(proto, Protocol):
271+
return proto
272+
elif isinstance(proto, str):
273+
return self.find_by_name(proto)
274+
elif isinstance(proto, int):
275+
return self.find_by_code(proto)
276+
else:
277+
raise TypeError("Protocol object, name or code expected, got {0!r}".format(proto))
278+
279+
280+
REGISTRY = ProtocolRegistry(PROTOCOLS)
281+
REGISTRY.add_alias_name("p2p", "ipfs")
282+
REGISTRY.lock()
170283

171284

172285
def protocol_with_name(name):
173-
name = str(name) # PY2: Convert Unicode strings to native/binary representation
174-
if name not in _names_to_protocols:
175-
raise exceptions.ProtocolNotFoundError(name, "name")
176-
return _names_to_protocols[name]
286+
return REGISTRY.find_by_name(name)
177287

178288

179289
def protocol_with_code(code):
180-
if code not in _codes_to_protocols:
181-
raise exceptions.ProtocolNotFoundError(code, "code")
182-
return _codes_to_protocols[code]
290+
return REGISTRY.find_by_code(code)
183291

184292

185293
def protocol_with_any(proto):
186-
if isinstance(proto, Protocol):
187-
return proto
188-
elif isinstance(proto, int):
189-
return protocol_with_code(proto)
190-
elif isinstance(proto, six.string_types):
191-
return protocol_with_name(proto)
192-
else:
193-
raise TypeError("Protocol object, name or code expected, got {0!r}".format(proto))
294+
return REGISTRY.find(proto)
194295

195296

196297
def protocols_with_string(string):
197298
"""Return a list of protocols matching given string."""
198-
# Normalize string
199-
while "//" in string:
200-
string = string.replace("//", "/")
201-
string = string.strip("/")
202-
if not string:
203-
return []
204-
205299
ret = []
206300
for name in string.split("/"):
207-
ret.append(protocol_with_name(name))
301+
if len(name) > 0:
302+
ret.append(protocol_with_name(name))
208303
return ret

0 commit comments

Comments
 (0)