Skip to content

Commit 491d4fb

Browse files
committed
Record associated codec along with each protocol type instead of maintaining a separate table inside codec.py for this
1 parent 67db97e commit 491d4fb

File tree

5 files changed

+115
-128
lines changed

5 files changed

+115
-128
lines changed

multiaddr/codec.py

Lines changed: 13 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
from __future__ import absolute_import
2+
import importlib
23

34
import varint
45

5-
from .protocols import P_DNS
6-
from .protocols import P_DNS4
7-
from .protocols import P_DNS6
8-
from .protocols import P_DCCP
9-
from .protocols import P_IP4
10-
from .protocols import P_IP6
11-
from .protocols import P_P2P
12-
from .protocols import P_ONION
136
from .protocols import protocol_with_code
147
from .protocols import protocol_with_name
15-
from .protocols import P_SCTP
16-
from .protocols import P_TCP
17-
from .protocols import P_UDP
18-
from .protocols import P_UNIX
198
from .protocols import read_varint_code
209

2110

11+
def find_codec_by_name(name):
12+
if not name:
13+
raise ValueError("unknown protocol codec")
14+
return importlib.import_module(".codecs.{0}".format(name), __package__)
15+
16+
2217
def string_to_bytes(string):
2318
if not string:
2419
return b''
@@ -42,7 +37,10 @@ def string_to_bytes(string):
4237
"protocol requires address, none given: %s" % proto.name)
4338
if proto.path:
4439
sp = ["/" + "/".join(sp)]
45-
bs.append(address_string_to_bytes(proto, sp.pop(0)))
40+
if not proto.codec:
41+
raise ValueError("failed to parse %s addr: unknown" % proto.name)
42+
codec = find_codec_by_name(proto.codec)
43+
bs.append(codec.to_bytes(proto, sp.pop(0)))
4644
return b''.join(bs)
4745

4846

@@ -56,7 +54,8 @@ def bytes_to_string(buf):
5654
maddr_component += proto.name
5755
size = size_for_addr(proto, buf)
5856
if size > 0:
59-
addr = address_bytes_to_string(proto, buf[:size])
57+
codec = find_codec_by_name(proto.codec)
58+
addr = codec.to_string(proto, buf[:size])
6059
if not (proto.path and addr[0] == '/'):
6160
maddr_component += '/'
6261
maddr_component += addr
@@ -65,58 +64,6 @@ def bytes_to_string(buf):
6564
return '/'.join(st)
6665

6766

68-
def address_string_to_bytes(proto, addr_string):
69-
if proto.code == P_IP4: # ipv4
70-
from .codecs import ip4
71-
return ip4.to_bytes(proto, addr_string)
72-
elif proto.code == P_IP6: # ipv6
73-
from .codecs import ip6
74-
return ip6.to_bytes(proto, addr_string)
75-
# tcp udp dccp sctp
76-
elif proto.code in [P_TCP, P_UDP, P_DCCP, P_SCTP]:
77-
from .codecs import uint16be
78-
return uint16be.to_bytes(proto, addr_string)
79-
elif proto.code == P_ONION:
80-
from .codecs import onion
81-
return onion.to_bytes(proto, addr_string)
82-
elif proto.code == P_P2P: # ipfs
83-
from .codecs import multihash
84-
return multihash.to_bytes(proto, addr_string)
85-
elif proto.code == P_UNIX:
86-
from .codecs import fspath
87-
return fspath.to_bytes(proto, addr_string)
88-
elif proto.code in (P_DNS, P_DNS4, P_DNS6):
89-
from .codecs import idna
90-
return idna.to_bytes(proto, addr_string)
91-
else:
92-
raise ValueError("failed to parse %s addr: unknown" % proto.name)
93-
94-
95-
def address_bytes_to_string(proto, buf):
96-
if proto.code == P_IP4:
97-
from .codecs import ip4
98-
return ip4.to_string(proto, buf)
99-
elif proto.code == P_IP6:
100-
from .codecs import ip6
101-
return ip6.to_string(proto, buf)
102-
elif proto.code in [P_TCP, P_UDP, P_DCCP, P_SCTP]:
103-
from .codecs import uint16be
104-
return uint16be.to_string(proto, buf)
105-
elif proto.code == P_ONION:
106-
from .codecs import onion
107-
return onion.to_string(proto, buf)
108-
elif proto.code == P_P2P:
109-
from .codecs import multihash
110-
return multihash.to_string(proto, buf)
111-
elif proto.code == P_UNIX:
112-
from .codecs import fspath
113-
return fspath.to_string(proto, buf)
114-
elif proto.code in (P_DNS, P_DNS4, P_DNS6):
115-
from .codecs import idna
116-
return idna.to_string(proto, buf)
117-
raise ValueError("unknown protocol")
118-
119-
12067
def size_for_addr(proto, buf):
12168
if proto.size >= 0:
12269
return proto.size // 8

multiaddr/protocols.py

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,19 @@ class Protocol(object):
7373
"size", # int (-1 indicates a length-prefixed variable size)
7474
"name", # string
7575
"vcode", # bytes
76+
"codec", # string
7677
"path", # bool (True indicates a path protocol (eg unix, http))
7778
]
7879

79-
def __init__(self, code, size, name, vcode, path=False):
80+
def __init__(self, code, size, name, codec=None, path=False):
8081
if not isinstance(code, six.integer_types):
8182
raise ValueError("code must be an integer")
8283
if not isinstance(size, six.integer_types):
8384
raise ValueError("size must be an integer")
8485
if not isinstance(name, six.string_types):
8586
raise ValueError("name must be a string")
86-
if not isinstance(vcode, six.binary_type):
87-
raise ValueError("vcode must be binary")
87+
if not isinstance(codec, six.string_types) and codec is not None:
88+
raise ValueError("codec must be a string or None")
8889
if not isinstance(path, bool):
8990
raise ValueError("path must be a boolean")
9091

@@ -96,24 +97,27 @@ def __init__(self, code, size, name, vcode, path=False):
9697
self.code = code
9798
self.size = size
9899
self.name = name
99-
self.vcode = vcode
100+
self.vcode = varint.encode(code)
101+
self.codec = codec
100102
self.path = path
101103

102104
def __eq__(self, other):
103105
return all((self.code == other.code,
104106
self.size == other.size,
105107
self.name == other.name,
106108
self.vcode == other.vcode,
109+
self.codec == other.codec,
107110
self.path == other.path))
108111

109112
def __ne__(self, other):
110113
return not self == other
111114

112115
def __repr__(self):
113-
return "Protocol(code={code}, name='{name}', size={size}, path={path})".format(
116+
return "Protocol(code={code}, name='{name}', size={size}, codec={codec!r}, path={path})".format(
114117
code=self.code,
115118
size=self.size,
116119
name=self.name,
120+
codec=self.codec,
117121
path=self.path,
118122
)
119123

@@ -143,31 +147,31 @@ def read_varint_code(buf):
143147

144148
# Protocols is the list of multiaddr protocols supported by this module.
145149
PROTOCOLS = [
146-
Protocol(P_IP4, 32, 'ip4', varint.encode(P_IP4)),
147-
Protocol(P_TCP, 16, 'tcp', varint.encode(P_TCP)),
148-
Protocol(P_UDP, 16, 'udp', varint.encode(P_UDP)),
149-
Protocol(P_DCCP, 16, 'dccp', varint.encode(P_DCCP)),
150-
Protocol(P_IP6, 128, 'ip6', varint.encode(P_IP6)),
151-
Protocol(P_IP6ZONE, LENGTH_PREFIXED_VAR_SIZE, 'ip6zone', varint.encode(P_IP6ZONE)),
152-
Protocol(P_DNS, LENGTH_PREFIXED_VAR_SIZE, 'dns', varint.encode(P_DNS)),
153-
Protocol(P_DNS4, LENGTH_PREFIXED_VAR_SIZE, 'dns4', varint.encode(P_DNS4)),
154-
Protocol(P_DNS6, LENGTH_PREFIXED_VAR_SIZE, 'dns6', varint.encode(P_DNS6)),
155-
Protocol(P_DNSADDR, LENGTH_PREFIXED_VAR_SIZE, 'dnsaddr', varint.encode(P_DNSADDR)),
156-
Protocol(P_SCTP, 16, 'sctp', varint.encode(P_SCTP)),
157-
Protocol(P_UDT, 0, 'udt', varint.encode(P_UDT)),
158-
Protocol(P_UTP, 0, 'utp', varint.encode(P_UTP)),
159-
Protocol(P_P2P, LENGTH_PREFIXED_VAR_SIZE, 'p2p', varint.encode(P_P2P)),
160-
Protocol(P_ONION, 96, 'onion', varint.encode(P_ONION)),
161-
Protocol(P_QUIC, 0, 'quic', varint.encode(P_QUIC)),
162-
Protocol(P_HTTP, 0, 'http', varint.encode(P_HTTP)),
163-
Protocol(P_HTTPS, 0, 'https', varint.encode(P_HTTPS)),
164-
Protocol(P_WS, 0, 'ws', varint.encode(P_WS)),
165-
Protocol(P_WSS, 0, 'wss', varint.encode(P_WSS)),
166-
Protocol(P_P2P_WEBSOCKET_STAR, 0, 'p2p-websocket-star', varint.encode(P_P2P_WEBSOCKET_STAR)),
167-
Protocol(P_P2P_WEBRTC_STAR, 0, 'p2p-webrtc-star', varint.encode(P_P2P_WEBRTC_STAR)),
168-
Protocol(P_P2P_WEBRTC_DIRECT, 0, 'p2p-webrtc-direct', varint.encode(P_P2P_WEBRTC_DIRECT)),
169-
Protocol(P_P2P_CIRCUIT, 0, 'p2p-circuit', varint.encode(P_P2P_CIRCUIT)),
170-
Protocol(P_UNIX, LENGTH_PREFIXED_VAR_SIZE, 'unix', varint.encode(P_UNIX), path=True),
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', 'multihash'),
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),
171175
]
172176

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

tests/test_codec.py

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# -*- encoding: utf-8 -*-
22
import pytest
33

4-
from multiaddr.codec import address_bytes_to_string
5-
from multiaddr.codec import address_string_to_bytes
4+
from multiaddr.codec import find_codec_by_name
65
from multiaddr.codec import bytes_split
76
from multiaddr.codec import bytes_to_string
87
from multiaddr.codec import size_for_addr
98
from multiaddr.codec import string_to_bytes
109

10+
import multiaddr.protocols
11+
from multiaddr.protocols import _codes_to_protocols
1112
from multiaddr.protocols import _names_to_protocols
13+
from multiaddr.protocols import protocol_with_code
1214
from multiaddr.protocols import Protocol
1315

1416
# These test values were generated by running them
@@ -67,14 +69,14 @@ def test_bytes_split(buf, expected):
6769

6870

6971
@pytest.mark.parametrize("proto, buf, expected", ADDR_BYTES_MAP_STR_TEST_DATA)
70-
def test_address_bytes_to_string(proto, buf, expected):
71-
assert address_bytes_to_string(proto, buf) == expected
72+
def test_codec_to_string(proto, buf, expected):
73+
assert find_codec_by_name(proto.codec).to_string(proto, buf) == expected
7274

7375

7476
@pytest.mark.parametrize("proto, expected, string",
7577
ADDR_BYTES_MAP_STR_TEST_DATA)
76-
def test_address_string_to_bytes(proto, string, expected):
77-
assert address_string_to_bytes(proto, string) == expected
78+
def test_codec_to_bytes(proto, string, expected):
79+
assert find_codec_by_name(proto.codec).to_bytes(proto, string) == expected
7880

7981

8082
@pytest.mark.parametrize("string, buf", BYTES_MAP_STR_TEST_DATA)
@@ -87,24 +89,49 @@ def test_bytes_to_string(string, buf):
8789
assert bytes_to_string(buf) == string
8890

8991

90-
@pytest.mark.parametrize("string", [
91-
'test',
92-
'/ip4/'
93-
])
94-
def test_string_to_bytes_value_error(string):
95-
with pytest.raises(ValueError):
96-
string_to_bytes(string)
97-
98-
9992
class DummyProtocol(Protocol):
100-
def __init__(self, code, size, name, vcode, path=False):
93+
def __init__(self, code, size, name, vcode, codec=None, path=False):
10194
self.code = code
10295
self.size = size
10396
self.name = name
10497
self.vcode = vcode
98+
self.codec = codec
10599
self.path = path
106100

107101

102+
class UnparsableProtocol(DummyProtocol):
103+
def __init__(self):
104+
super(UnparsableProtocol, self).__init__(333, 16, "unparsable", b"\xcd\x02")
105+
106+
107+
@pytest.fixture
108+
def protocol_extension(monkeypatch):
109+
# “Add” additional non-parsable protocol to protocols from code list
110+
names_to_protocols = _names_to_protocols.copy()
111+
codes_to_protocols = _codes_to_protocols.copy()
112+
names_to_protocols["unparsable"] = codes_to_protocols[333] = UnparsableProtocol()
113+
monkeypatch.setattr(multiaddr.protocols, "_names_to_protocols", names_to_protocols)
114+
monkeypatch.setattr(multiaddr.protocols, "_codes_to_protocols", codes_to_protocols)
115+
116+
117+
@pytest.mark.parametrize("string", [
118+
'test',
119+
'/ip4/',
120+
'/unparsable/5'
121+
])
122+
def test_string_to_bytes_value_error(protocol_extension, string):
123+
with pytest.raises(ValueError):
124+
string_to_bytes(string)
125+
126+
127+
@pytest.mark.parametrize("bytes", [
128+
b'\xcd\x02\x0c\x0d'
129+
])
130+
def test_bytes_to_string_value_error(protocol_extension, bytes):
131+
with pytest.raises(ValueError):
132+
bytes_to_string(bytes)
133+
134+
108135
@pytest.mark.parametrize("proto, address", [
109136
(DummyProtocol(234, 32, 'test', b'123'), '1.2.3.4'),
110137
(_names_to_protocols['ip4'], '1124.2.3'),
@@ -118,16 +145,16 @@ def __init__(self, code, size, name, vcode, path=False):
118145
(_names_to_protocols['onion'], 'timaq4ygg2iegci7:71234'),
119146
(_names_to_protocols['p2p'], '15230d52ebb89d85b02a284948203a'),
120147
])
121-
def test_address_string_to_bytes_value_error(proto, address):
148+
def test_codec_to_bytes_value_error(proto, address):
122149
with pytest.raises(ValueError):
123-
address_string_to_bytes(proto, address)
150+
find_codec_by_name(proto.codec).to_bytes(proto, address)
124151

125152

126153
@pytest.mark.parametrize("proto, buf", [
127154
(DummyProtocol(234, 32, 'test', b'123'), b'\x0a\x0b\x0c\x0d'),
128155
(_names_to_protocols['p2p'], b'\x15\x23\x0d\x52\xeb\xb8\x9d\x85\xb0\x2a\x28\x49\x48\x20\x3a'),
129156
(_names_to_protocols['tcp'], b'\xff\xff\xff\xff')
130157
])
131-
def test_address_bytes_to_string_value_error(proto, buf):
158+
def test_codec_to_string_value_error(proto, buf):
132159
with pytest.raises(ValueError):
133-
address_bytes_to_string(proto, buf)
160+
find_codec_by_name(proto.codec).to_string(proto, buf)

tests/test_multiaddr.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"/ip4/127.0.0.1/tcp/9090/http/p2p-webcrt-direct",
5252
"/dns",
5353
"/dns4",
54-
"/dns6"])
54+
"/dns6",
55+
"/cancer"])
5556
def test_invalid(addr_str):
5657
with pytest.raises(ValueError):
5758
Multiaddr(addr_str)

0 commit comments

Comments
 (0)