Skip to content

Commit ed0b90e

Browse files
mhchiarobzajac
authored andcommitted
Add support for UNIX protocol (#32)
* Add codec for protocol `unix` And add the attribute `path` in `Protocol`, to identify if a protocol is a path protocol. * Fix `bytes_to_string` and `addr_bytes_to_string` - Fix `addr_bytes_to_string` to remove the size prefix correctly - Fix `bytes_to_string` to handle the protocol Path correctly * Fix `value_for_protocol` to support UNIX And add the tests for UNIX protocol * flake8 * Fix bug in value_for_protocol When there is only one layer of path protocols like UNIX, `Multiaddr("/unix/studio").value_for_protocol(P_UNIX)` gives "studio", which is wrong. * Add test for invalid path of protocol
1 parent 2cda03a commit ed0b90e

File tree

6 files changed

+73
-23
lines changed

6 files changed

+73
-23
lines changed

multiaddr/codec.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import base58
22
import base64
33
import binascii
4-
import six
54

65
from netaddr import IPAddress
76

@@ -16,6 +15,7 @@
1615
from .protocols import P_SCTP
1716
from .protocols import P_TCP
1817
from .protocols import P_UDP
18+
from .protocols import P_UNIX
1919
from .protocols import read_varint_code
2020

2121

@@ -40,6 +40,8 @@ def string_to_bytes(string):
4040
if len(sp) < 1:
4141
raise ValueError(
4242
"protocol requires address, none given: %s" % proto.name)
43+
if proto.path:
44+
sp = ["/" + "/".join(sp)]
4345
bs.append(address_string_to_bytes(proto, sp.pop(0)))
4446
return b''.join(bs)
4547

@@ -48,14 +50,18 @@ def bytes_to_string(buf):
4850
st = [''] # start with empty string so we get a leading slash on join()
4951
buf = binascii.unhexlify(buf)
5052
while buf:
53+
maddr_component = ""
5154
code, num_bytes_read = read_varint_code(buf)
5255
buf = buf[num_bytes_read:]
5356
proto = protocol_with_code(code)
54-
st.append(proto.name)
57+
maddr_component += proto.name
5558
size = size_for_addr(proto, buf)
5659
if size > 0:
5760
addr = address_bytes_to_string(proto, binascii.hexlify(buf[:size]))
58-
st.append(addr)
61+
if not (proto.path and addr[0] == '/'):
62+
maddr_component += '/'
63+
maddr_component += addr
64+
st.append(maddr_component)
5965
buf = buf[size:]
6066
return '/'.join(st)
6167

@@ -149,6 +155,10 @@ def address_string_to_bytes(proto, addr_string):
149155
# TODO - port go-multihash so we can do this correctly
150156
raise ValueError("invalid P2P multihash: %s" % mm)
151157
return b''.join([size, mm])
158+
elif proto.code == P_UNIX:
159+
addr_string_bytes = addr_string.encode("ascii")
160+
size = code_to_varint(len(addr_string_bytes))
161+
return b''.join([size, binascii.hexlify(addr_string_bytes)])
152162
else:
153163
raise ValueError("failed to parse %s addr: unknown" % proto.name)
154164

@@ -179,6 +189,10 @@ def address_bytes_to_string(proto, buf):
179189
if len(buf) != size:
180190
raise ValueError("inconsistent lengths")
181191
return base58.b58encode(buf).decode()
192+
elif proto.code == P_UNIX:
193+
buf = binascii.unhexlify(buf)
194+
size, num_bytes_read = read_varint_code(buf)
195+
return buf[num_bytes_read:].decode('ascii')
182196
raise ValueError("unknown protocol")
183197

184198

multiaddr/multiaddr.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from .codec import size_for_addr
66
from .codec import string_to_bytes
77
from .codec import bytes_to_string
8-
from .codec import protocol_with_name
98
from .protocols import protocol_with_code
109
from .protocols import read_varint_code
1110

@@ -117,13 +116,16 @@ def value_for_protocol(self, code):
117116
"""Return the value (if any) following the specified protocol."""
118117
from .util import split
119118

120-
if isinstance(code, str):
121-
protocol = protocol_with_name(code)
122-
code = protocol.code
119+
if not isinstance(code, int):
120+
raise ValueError("code type should be `int`, code={}".format(code))
123121

124122
for sub_addr in split(self):
125-
if sub_addr.protocols()[0].code == code:
123+
protocol = sub_addr.protocols()[0]
124+
if protocol.code == code:
125+
# e.g. if `sub_addr=/unix/123`, then `addr_parts=['', 'unix', '123']`
126126
addr_parts = str(sub_addr).split("/")
127+
if protocol.path:
128+
return "/" + "/".join(addr_parts[2:])
127129
if len(addr_parts) > 3:
128130
raise ValueError("Unknown Protocol format")
129131
elif len(addr_parts) == 3:

multiaddr/protocols.py

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

78-
def __init__(self, code, size, name, vcode):
79+
def __init__(self, code, size, name, vcode, path=False):
7980
if not isinstance(code, six.integer_types):
8081
raise ValueError("code must be an integer")
8182
if not isinstance(size, six.integer_types):
@@ -84,6 +85,8 @@ def __init__(self, code, size, name, vcode):
8485
raise ValueError("name must be a string")
8586
if not isinstance(vcode, six.binary_type):
8687
raise ValueError("vcode must be binary")
88+
if not isinstance(path, bool):
89+
raise ValueError("path must be a boolean")
8790

8891
if code not in _CODES and code != 0:
8992
raise ValueError("Invalid code '%d'" % code)
@@ -94,21 +97,25 @@ def __init__(self, code, size, name, vcode):
9497
self.size = size
9598
self.name = name
9699
self.vcode = vcode
100+
self.path = path
97101

98102
def __eq__(self, other):
99103
return all((self.code == other.code,
100104
self.size == other.size,
101105
self.name == other.name,
102-
self.vcode == other.vcode))
106+
self.vcode == other.vcode,
107+
self.path == other.path))
103108

104109
def __ne__(self, other):
105110
return not self == other
106111

107112
def __repr__(self):
108-
return "Protocol(code={code}, name='{name}', size={size})".format(
113+
return "Protocol(code={code}, name='{name}', size={size}, path={path})".format(
109114
code=self.code,
110115
size=self.size,
111-
name=self.name)
116+
name=self.name,
117+
path=self.path,
118+
)
112119

113120

114121
def code_to_varint(num):
@@ -169,7 +176,7 @@ def read_varint_code(buf):
169176
Protocol(P_P2P_WEBRTC_STAR, 0, 'p2p-webrtc-star', code_to_varint(P_P2P_WEBRTC_STAR)),
170177
Protocol(P_P2P_WEBRTC_DIRECT, 0, 'p2p-webrtc-direct', code_to_varint(P_P2P_WEBRTC_DIRECT)),
171178
Protocol(P_P2P_CIRCUIT, 0, 'p2p-circuit', code_to_varint(P_P2P_CIRCUIT)),
172-
Protocol(P_UNIX, LENGTH_PREFIXED_VAR_SIZE, 'unix', code_to_varint(P_UNIX)),
179+
Protocol(P_UNIX, LENGTH_PREFIXED_VAR_SIZE, 'unix', code_to_varint(P_UNIX), path=True),
173180
]
174181

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

tests/test_codec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_size_for_addr(proto, buf, expected):
5151
b'\x06\x10\xe1']),
5252
])
5353
def test_bytes_split(buf, expected):
54-
assert bytes_split(buf) == expected
54+
assert bytes_split(buf) == expected
5555

5656

5757
@pytest.mark.parametrize("proto, buf, expected", ADDR_BYTES_MAP_STR_TEST_DATA)
@@ -85,11 +85,12 @@ def test_string_to_bytes_value_error(string):
8585

8686

8787
class DummyProtocol(Protocol):
88-
def __init__(self, code, size, name, vcode):
88+
def __init__(self, code, size, name, vcode, path=False):
8989
self.code = code
9090
self.size = size
9191
self.name = name
9292
self.vcode = vcode
93+
self.path = path
9394

9495

9596
@pytest.mark.parametrize("proto, address", [

tests/test_multiaddr.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from multiaddr.protocols import P_UTP
1313
from multiaddr.protocols import P_TCP
1414
from multiaddr.protocols import P_UDP
15+
from multiaddr.protocols import P_UNIX
1516
from multiaddr.util import split
1617
from multiaddr.util import join
1718

@@ -41,7 +42,10 @@
4142
"/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa",
4243
"/ip4/127.0.0.1/tcp",
4344
"/ip4/127.0.0.1/p2p",
44-
"/ip4/127.0.0.1/p2p/tcp"])
45+
"/ip4/127.0.0.1/p2p/tcp",
46+
"/unix",
47+
"/ip4/1.2.3.4/tcp/80/unix",
48+
"/ip4/127.0.0.1/tcp/9090/http/p2p-webcrt-direct"])
4549
def test_invalid(addr_str):
4650
with pytest.raises(ValueError):
4751
Multiaddr(addr_str)
@@ -71,11 +75,12 @@ def test_invalid(addr_str):
7175
"/tcp/1234/https",
7276
"/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
7377
"/ip4/127.0.0.1/udp/1234",
74-
"/ip4/127.0.0.1/udp/0",
75-
"/ip4/127.0.0.1/tcp/1234",
76-
"/ip4/127.0.0.1/tcp/1234/",
77-
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC",
78-
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234"]) # nopep8
78+
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",
79+
"/unix/a/b/c/d/e",
80+
"/unix/stdio",
81+
"/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f",
82+
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio",
83+
"/ip4/127.0.0.1/tcp/9090/http/p2p-webrtc-direct"]) # nopep8
7984
def test_valid(addr_str):
8085
ma = Multiaddr(addr_str)
8186
assert str(ma) == addr_str.rstrip("/")
@@ -207,6 +212,13 @@ def test_get_value():
207212
assert_value_for_proto(a, P_UDP, "12345")
208213
assert_value_for_proto(a, P_UTP, "")
209214

215+
a = Multiaddr("/ip4/0.0.0.0/unix/a/b/c/d") # ending in a path one.
216+
assert_value_for_proto(a, P_IP4, "0.0.0.0")
217+
assert_value_for_proto(a, P_UNIX, "/a/b/c/d")
218+
219+
a = Multiaddr("/unix/studio")
220+
assert_value_for_proto(a, P_UNIX, "/studio") # only a path.
221+
210222

211223
def test_bad_initialization_no_params():
212224
with pytest.raises(TypeError):
@@ -238,6 +250,12 @@ def test_get_value_too_many_fields_protocol(monkeypatch):
238250
a.value_for_protocol(P_UDP)
239251

240252

253+
def test_value_for_protocol_argument_wrong_type():
254+
a = Multiaddr("/ip4/127.0.0.1/udp/1234")
255+
with pytest.raises(ValueError):
256+
a.value_for_protocol('str123')
257+
258+
241259
def test_multi_addr_str_corruption():
242260
a = Multiaddr("/ip4/127.0.0.1/udp/1234")
243261
a._bytes = b"047047047"

tests/test_protocols.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def valid_params():
2121
return {'code': protocols.P_IP4,
2222
'size': 32,
2323
'name': 'ipb4',
24-
'vcode': protocols.code_to_varint(protocols.P_IP4)}
24+
'vcode': protocols.code_to_varint(protocols.P_IP4),
25+
'path': False}
2526

2627

2728
def test_valid(valid_params):
@@ -59,6 +60,13 @@ def test_invalid_vcode(valid_params, invalid_vcode):
5960
protocols.Protocol(**valid_params)
6061

6162

63+
@pytest.mark.parametrize("invalid_path", [123, '123', 0.123])
64+
def test_invalid_path(valid_params, invalid_path):
65+
valid_params['path'] = invalid_path
66+
with pytest.raises(ValueError):
67+
protocols.Protocol(**valid_params)
68+
69+
6270
@pytest.mark.parametrize("name", ["foo-str", u"foo-u"])
6371
def test_valid_names(valid_params, name):
6472
valid_params['name'] = name
@@ -152,7 +160,7 @@ def test_add_protocol_twice(patch_protocols, valid_params):
152160

153161
def test_protocol_repr():
154162
proto = protocols.protocol_with_name('ip4')
155-
assert "Protocol(code=4, name='ip4', size=32)" == repr(proto)
163+
assert "Protocol(code=4, name='ip4', size=32, path=False)" == repr(proto)
156164

157165

158166
@pytest.mark.parametrize("buf", [

0 commit comments

Comments
 (0)