Skip to content

Commit 45f4220

Browse files
committed
Move information about expected binary size of each multiaddr protocol into the codec registry
Avoids the current duplication of information and reduces the number of possible error cases.
1 parent 9aef48b commit 45f4220

File tree

12 files changed

+130
-94
lines changed

12 files changed

+130
-94
lines changed

multiaddr/codec.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1+
# -*- encoding: utf-8 -*-
12
from __future__ import absolute_import
23
import importlib
34

5+
import six
46
import varint
57

68
from .protocols import protocol_with_code
79
from .protocols import protocol_with_name
810
from .protocols import read_varint_code
911

1012

13+
# These are special sizes
14+
LENGTH_PREFIXED_VAR_SIZE = -1
15+
16+
17+
class NoneCodec:
18+
SIZE = 0
19+
IS_PATH = False
20+
21+
1122
CODEC_CACHE = {}
1223
def find_codec_by_name(name):
13-
if not name:
14-
raise ValueError("unknown protocol codec")
24+
if name is None: # Special “do nothing – expect nothing” pseudo-codec
25+
return NoneCodec
1526
codec = CODEC_CACHE.get(name)
1627
if not codec:
1728
codec = CODEC_CACHE[name] = importlib.import_module(".codecs.{0}".format(name), __package__)
@@ -34,16 +45,17 @@ def string_to_bytes(string):
3445
element = sp.pop(0)
3546
proto = protocol_with_name(element)
3647
bs.append(varint.encode(proto.code))
37-
if proto.size == 0:
48+
try:
49+
codec = find_codec_by_name(proto.codec)
50+
except ImportError as exc:
51+
six.raise_from(ValueError("failed to parse %s addr: unknown" % proto.name), exc)
52+
if codec.SIZE == 0:
3853
continue
3954
if len(sp) < 1:
4055
raise ValueError(
4156
"protocol requires address, none given: %s" % proto.name)
42-
if proto.path:
57+
if codec.IS_PATH:
4358
sp = ["/" + "/".join(sp)]
44-
if not proto.codec:
45-
raise ValueError("failed to parse %s addr: unknown" % proto.name)
46-
codec = find_codec_by_name(proto.codec)
4759
bs.append(codec.to_bytes(proto, sp.pop(0)))
4860
return b''.join(bs)
4961

@@ -56,21 +68,24 @@ def bytes_to_string(buf):
5668
buf = buf[num_bytes_read:]
5769
proto = protocol_with_code(code)
5870
maddr_component += proto.name
59-
size = size_for_addr(proto, buf)
60-
if size > 0:
71+
try:
6172
codec = find_codec_by_name(proto.codec)
73+
except ImportError as exc:
74+
six.raise_from(ValueError("failed to parse %s addr: unknown" % proto.name), exc)
75+
size = size_for_addr(codec, buf)
76+
if size > 0:
6277
addr = codec.to_string(proto, buf[:size])
63-
if not (proto.path and addr[0] == '/'):
78+
if not (codec.IS_PATH and addr[0] == '/'):
6479
maddr_component += '/'
6580
maddr_component += addr
6681
st.append(maddr_component)
6782
buf = buf[size:]
6883
return '/'.join(st)
6984

7085

71-
def size_for_addr(proto, buf):
72-
if proto.size >= 0:
73-
return proto.size // 8
86+
def size_for_addr(codec, buf):
87+
if codec.SIZE >= 0:
88+
return codec.SIZE // 8
7489
else:
7590
size, num_bytes_read = read_varint_code(buf)
7691
return size + num_bytes_read
@@ -81,7 +96,11 @@ def bytes_split(buf):
8196
while buf:
8297
code, num_bytes_read = read_varint_code(buf)
8398
proto = protocol_with_code(code)
84-
size = size_for_addr(proto, buf[num_bytes_read:])
99+
try:
100+
codec = find_codec_by_name(proto.codec)
101+
except ImportError as exc:
102+
six.raise_from(ValueError("failed to parse %s addr: unknown" % proto.name), exc)
103+
size = size_for_addr(codec, buf[num_bytes_read:])
85104
length = size + num_bytes_read
86105
ret.append(buf[:length])
87106
buf = buf[length:]

multiaddr/codecs/fspath.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import six
55
import varint
66

7+
from ..codec import LENGTH_PREFIXED_VAR_SIZE
78
from ..protocols import read_varint_code
89

910

11+
SIZE = LENGTH_PREFIXED_VAR_SIZE
12+
IS_PATH = True
13+
14+
1015
if hasattr(os, "fsencode") and hasattr(os, "fsdecode"):
1116
fsencode = os.fsencode
1217
fsdecode = os.fsdecode

multiaddr/codecs/idna.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import idna
44
import varint
55

6+
from ..codec import LENGTH_PREFIXED_VAR_SIZE
67
from ..protocols import read_varint_code
78

89

10+
SIZE = LENGTH_PREFIXED_VAR_SIZE
11+
IS_PATH = False
12+
13+
914
def to_bytes(proto, string):
1015
bytes = idna.encode(string, uts46=True)
1116
size = varint.encode(len(bytes))

multiaddr/codecs/ip4.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from ..util import packed_net_bytes_to_int
77

88

9+
SIZE = 32
10+
IS_PATH = False
11+
12+
913
def to_bytes(proto, string):
1014
try:
1115
return netaddr.IPAddress(string, version=4).packed

multiaddr/codecs/ip6.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from ..util import packed_net_bytes_to_int
77

88

9+
SIZE = 128
10+
IS_PATH = False
11+
12+
913
def to_bytes(proto, string):
1014
try:
1115
return netaddr.IPAddress(string, version=6).packed

multiaddr/codecs/onion.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import six
66

77

8+
SIZE = 96
9+
IS_PATH = False
10+
11+
812
def to_bytes(proto, string):
913
addr = string.split(":")
1014
if len(addr) != 2:

multiaddr/codecs/p2p.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
import six
55
import varint
66

7+
from ..codec import LENGTH_PREFIXED_VAR_SIZE
78
from ..protocols import read_varint_code
89

910

11+
SIZE = LENGTH_PREFIXED_VAR_SIZE
12+
IS_PATH = False
13+
14+
1015
def to_bytes(proto, string):
1116
# the address is a base58-encoded string
1217
try:

multiaddr/codecs/uint16be.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import six
55

66

7+
SIZE = 16
8+
IS_PATH = False
9+
10+
711
def to_bytes(proto, string):
812
try:
913
return struct.pack('>H', int(string, 10))

multiaddr/multiaddr.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import six
55

6+
from .codec import find_codec_by_name
67
from .codec import size_for_addr
78
from .codec import string_to_bytes
89
from .codec import bytes_to_string
@@ -93,9 +94,13 @@ def protocols(self):
9394
while buf:
9495
code, num_bytes_read = read_varint_code(buf)
9596
proto = protocol_with_code(code)
97+
try:
98+
codec = find_codec_by_name(proto.codec)
99+
except ImportError as exc:
100+
six.raise_from(ValueError("failed to parse %s addr: unknown" % proto.name), exc)
96101
protos.append(proto)
97102
buf = buf[num_bytes_read:]
98-
size = size_for_addr(proto, buf)
103+
size = size_for_addr(codec, buf)
99104
buf = buf[size:]
100105
return protos
101106

multiaddr/protocols.py

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -63,62 +63,64 @@
6363
]
6464

6565

66-
# These are special sizes
67-
LENGTH_PREFIXED_VAR_SIZE = -1
68-
66+
find_codec_by_name = None
6967

7068
class Protocol(object):
7169
__slots__ = [
7270
"code", # int
73-
"size", # int (-1 indicates a length-prefixed variable size)
7471
"name", # string
75-
"vcode", # bytes
7672
"codec", # string
77-
"path", # bool (True indicates a path protocol (eg unix, http))
7873
]
7974

80-
def __init__(self, code, size, name, codec=None, path=False):
75+
def __init__(self, code, name, codec):
8176
if not isinstance(code, six.integer_types):
8277
raise ValueError("code must be an integer")
83-
if not isinstance(size, six.integer_types):
84-
raise ValueError("size must be an integer")
8578
if not isinstance(name, six.string_types):
8679
raise ValueError("name must be a string")
87-
if not isinstance(codec, six.string_types) and codec is not None:
80+
if not isinstance(codec, six.string_types) and not codec is None:
8881
raise ValueError("codec must be a string or None")
89-
if not isinstance(path, bool):
90-
raise ValueError("path must be a boolean")
9182

9283
if code not in _CODES and code != 0:
9384
raise ValueError("Invalid code '%d'" % code)
9485

95-
if size < -1 or size > 128:
96-
raise ValueError("Invalid size")
9786
self.code = code
98-
self.size = size
9987
self.name = name
100-
self.vcode = varint.encode(code)
10188
self.codec = codec
102-
self.path = path
89+
90+
@property
91+
def size(self):
92+
global find_codec_by_name
93+
if find_codec_by_name is None:
94+
from .codec import find_codec_by_name
95+
96+
return find_codec_by_name(self.codec).SIZE
97+
98+
@property
99+
def path(self):
100+
global find_codec_by_name
101+
if find_codec_by_name is None:
102+
from .codec import find_codec_by_name
103+
104+
return find_codec_by_name(self.codec).IS_PATH
105+
106+
@property
107+
def vcode(self):
108+
return varint.encode(self.code)
103109

104110
def __eq__(self, other):
105111
return all((self.code == other.code,
106-
self.size == other.size,
107112
self.name == other.name,
108-
self.vcode == other.vcode,
109113
self.codec == other.codec,
110114
self.path == other.path))
111115

112116
def __ne__(self, other):
113117
return not self == other
114118

115119
def __repr__(self):
116-
return "Protocol(code={code}, name='{name}', size={size}, codec={codec!r}, path={path})".format(
120+
return "Protocol(code={code!r}, name={name!r}, codec={codec!r})".format(
117121
code=self.code,
118-
size=self.size,
119122
name=self.name,
120123
codec=self.codec,
121-
path=self.path,
122124
)
123125

124126

@@ -147,31 +149,31 @@ def read_varint_code(buf):
147149

148150
# Protocols is the list of multiaddr protocols supported by this module.
149151
PROTOCOLS = [
150-
Protocol(P_IP4, 32, 'ip4', 'ip4'),
151-
Protocol(P_TCP, 16, 'tcp', 'uint16be'),
152-
Protocol(P_UDP, 16, 'udp', 'uint16be'),
153-
Protocol(P_DCCP, 16, 'dccp', 'uint16be'),
154-
Protocol(P_IP6, 128, 'ip6', 'ip6'),
155-
Protocol(P_IP6ZONE, LENGTH_PREFIXED_VAR_SIZE, 'ip6zone'),
156-
Protocol(P_DNS, LENGTH_PREFIXED_VAR_SIZE, 'dns', 'idna'),
157-
Protocol(P_DNS4, LENGTH_PREFIXED_VAR_SIZE, 'dns4', 'idna'),
158-
Protocol(P_DNS6, LENGTH_PREFIXED_VAR_SIZE, 'dns6', 'idna'),
159-
Protocol(P_DNSADDR, LENGTH_PREFIXED_VAR_SIZE, 'dnsaddr', 'idna'),
160-
Protocol(P_SCTP, 16, 'sctp', 'uint16be'),
161-
Protocol(P_UDT, 0, 'udt'),
162-
Protocol(P_UTP, 0, 'utp'),
163-
Protocol(P_P2P, LENGTH_PREFIXED_VAR_SIZE, 'p2p', 'p2p'),
164-
Protocol(P_ONION, 96, 'onion', 'onion'),
165-
Protocol(P_QUIC, 0, 'quic'),
166-
Protocol(P_HTTP, 0, 'http'),
167-
Protocol(P_HTTPS, 0, 'https'),
168-
Protocol(P_WS, 0, 'ws'),
169-
Protocol(P_WSS, 0, 'wss'),
170-
Protocol(P_P2P_WEBSOCKET_STAR, 0, 'p2p-websocket-star'),
171-
Protocol(P_P2P_WEBRTC_STAR, 0, 'p2p-webrtc-star'),
172-
Protocol(P_P2P_WEBRTC_DIRECT, 0, 'p2p-webrtc-direct'),
173-
Protocol(P_P2P_CIRCUIT, 0, 'p2p-circuit'),
174-
Protocol(P_UNIX, LENGTH_PREFIXED_VAR_SIZE, 'unix', 'fspath', path=True),
152+
Protocol(P_IP4, 'ip4', 'ip4'),
153+
Protocol(P_TCP, 'tcp', 'uint16be'),
154+
Protocol(P_UDP, 'udp', 'uint16be'),
155+
Protocol(P_DCCP, 'dccp', 'uint16be'),
156+
Protocol(P_IP6, 'ip6', 'ip6'),
157+
Protocol(P_IP6ZONE, 'ip6zone', '?'),
158+
Protocol(P_DNS, 'dns', 'idna'),
159+
Protocol(P_DNS4, 'dns4', 'idna'),
160+
Protocol(P_DNS6, 'dns6', 'idna'),
161+
Protocol(P_DNSADDR, 'dnsaddr', 'idna'),
162+
Protocol(P_SCTP, 'sctp', 'uint16be'),
163+
Protocol(P_UDT, 'udt', None),
164+
Protocol(P_UTP, 'utp', None),
165+
Protocol(P_P2P, 'p2p', 'p2p'),
166+
Protocol(P_ONION, 'onion', 'onion'),
167+
Protocol(P_QUIC, 'quic', None),
168+
Protocol(P_HTTP, 'http', None),
169+
Protocol(P_HTTPS, 'https', None),
170+
Protocol(P_WS, 'ws', None),
171+
Protocol(P_WSS, 'wss', None),
172+
Protocol(P_P2P_WEBSOCKET_STAR, 'p2p-websocket-star', None),
173+
Protocol(P_P2P_WEBRTC_STAR, 'p2p-webrtc-star', None),
174+
Protocol(P_P2P_WEBRTC_DIRECT, 'p2p-webrtc-direct', None),
175+
Protocol(P_P2P_CIRCUIT, 'p2p-circuit', None),
176+
Protocol(P_UNIX, 'unix', 'fspath'),
175177
]
176178

177179
_names_to_protocols = dict((proto.name, proto) for proto in PROTOCOLS)

0 commit comments

Comments
 (0)