1
- # -*- coding: utf-8 -*-
2
- import six
3
1
import varint
4
2
5
3
from . import exceptions
6
4
from .codecs import codec_by_name
7
5
6
+ __all__ = ("Protocol" , "PROTOCOLS" , "REGISTRY" )
7
+
8
8
9
9
# source of protocols https://github.com/multiformats/multicodec/blob/master/table.csv#L382
10
10
# replicating table here to:
23
23
P_P2P = 0x01A5
24
24
P_HTTP = 0x01E0
25
25
P_HTTPS = 0x01BB
26
+ P_TLS = 0x01C0
26
27
P_QUIC = 0x01CC
27
28
P_QUIC1 = 0x01CD
28
29
P_WS = 0x01DD
39
40
P_P2P_WEBRTC_DIRECT = 0x0114
40
41
P_UNIX = 0x0190
41
42
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
-
72
43
73
- class Protocol ( object ) :
44
+ class Protocol :
74
45
__slots__ = [
75
46
"code" , # int
76
47
"name" , # string
77
48
"codec" , # string
78
49
]
79
50
80
51
def __init__ (self , code , name , codec ):
81
- if not isinstance (code , six . integer_types ):
52
+ if not isinstance (code , int ):
82
53
raise TypeError ("code must be an integer" )
83
- if not isinstance (name , six . string_types ):
54
+ if not isinstance (name , str ):
84
55
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 :
86
57
raise TypeError ("codec must be a string or None" )
87
58
88
59
self .code = code
@@ -121,28 +92,29 @@ def __repr__(self):
121
92
)
122
93
123
94
124
- # Protocols is the list of multiaddr protocols supported by this module.
95
+ # List of multiaddr protocols supported by this module by default
125
96
PROTOCOLS = [
126
97
Protocol (P_IP4 , 'ip4' , 'ip4' ),
127
98
Protocol (P_TCP , 'tcp' , 'uint16be' ),
128
99
Protocol (P_UDP , 'udp' , 'uint16be' ),
129
100
Protocol (P_DCCP , 'dccp' , 'uint16be' ),
130
101
Protocol (P_IP6 , 'ip6' , 'ip6' ),
131
102
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 ' ),
136
107
Protocol (P_SCTP , 'sctp' , 'uint16be' ),
137
108
Protocol (P_UDT , 'udt' , None ),
138
109
Protocol (P_UTP , 'utp' , None ),
139
- Protocol (P_P2P , 'p2p' , 'p2p ' ),
110
+ Protocol (P_P2P , 'p2p' , 'cid ' ),
140
111
Protocol (P_ONION , 'onion' , 'onion' ),
141
112
Protocol (P_ONION3 , 'onion3' , 'onion3' ),
142
113
Protocol (P_QUIC , 'quic' , None ),
143
114
Protocol (P_QUIC1 , 'quic-v1' , None ),
144
115
Protocol (P_HTTP , 'http' , None ),
145
116
Protocol (P_HTTPS , 'https' , None ),
117
+ Protocol (P_TLS , 'tls' , None ),
146
118
Protocol (P_WS , 'ws' , None ),
147
119
Protocol (P_WSS , 'wss' , None ),
148
120
Protocol (P_P2P_WEBSOCKET_STAR , 'p2p-websocket-star' , None ),
@@ -152,57 +124,180 @@ def __repr__(self):
152
124
Protocol (P_UNIX , 'unix' , 'fspath' ),
153
125
]
154
126
155
- _names_to_protocols = dict ((proto .name , proto ) for proto in PROTOCOLS )
156
- _codes_to_protocols = dict ((proto .code , proto ) for proto in PROTOCOLS )
157
127
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
158
192
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.
162
197
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 ()
165
207
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 ()
170
283
171
284
172
285
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 )
177
287
178
288
179
289
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 )
183
291
184
292
185
293
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 )
194
295
195
296
196
297
def protocols_with_string (string ):
197
298
"""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
-
205
299
ret = []
206
300
for name in string .split ("/" ):
207
- ret .append (protocol_with_name (name ))
301
+ if len (name ) > 0 :
302
+ ret .append (protocol_with_name (name ))
208
303
return ret
0 commit comments