Skip to content

Commit 5d46d92

Browse files
authored
Merge pull request #49 from alexander255/master
Add `multiaddr.Multiaddr.split(self, maxsplit=-1)` and `multiaddr.Multiaddr.join(*addrs)`
2 parents a2cb14c + 37db1de commit 5d46d92

File tree

6 files changed

+77
-57
lines changed

6 files changed

+77
-57
lines changed

multiaddr/multiaddr.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
collections.abc = collections
77

88
import six
9+
import varint
910

1011
from . import exceptions, protocols
1112

@@ -33,7 +34,7 @@ def __getitem__(self, idx):
3334
__hash__ = collections.abc.KeysView._hash
3435

3536
def __iter__(self):
36-
for proto, _, _ in bytes_iter(self._mapping.to_bytes()):
37+
for _, proto, _, _ in bytes_iter(self._mapping.to_bytes()):
3738
yield proto
3839

3940

@@ -52,7 +53,7 @@ def __getitem__(self, idx):
5253
raise IndexError("Protocol item list index out of range")
5354

5455
def __iter__(self):
55-
for proto, codec, part in bytes_iter(self._mapping.to_bytes()):
56+
for _, proto, codec, part in bytes_iter(self._mapping.to_bytes()):
5657
if codec.SIZE != 0:
5758
try:
5859
# If we have an address, return it
@@ -130,6 +131,12 @@ def __init__(self, addr):
130131
else:
131132
raise TypeError("MultiAddr must be bytes, str or another MultiAddr instance")
132133

134+
@classmethod
135+
def join(cls, *addrs):
136+
"""Concatenate the values of the given MultiAddr strings or objects,
137+
encapsulating each successive MultiAddr value with the previous ones."""
138+
return cls(b"".join(map(lambda a: cls(a).to_bytes(), addrs)))
139+
133140
def __eq__(self, other):
134141
"""Checks if two Multiaddr objects are exactly equal."""
135142
return self._bytes == other._bytes
@@ -171,6 +178,29 @@ def protocols(self):
171178
"""Returns a list of Protocols this Multiaddr includes."""
172179
return MultiAddrKeys(self)
173180

181+
def split(self, maxsplit=-1):
182+
"""Returns the list of individual path components this MultiAddr is made
183+
up of."""
184+
final_split_offset = -1
185+
results = []
186+
for idx, (offset, proto, codec, part_value) in enumerate(bytes_iter(self._bytes)):
187+
# Split at most `maxplit` times
188+
if idx == maxsplit:
189+
final_split_offset = offset
190+
break
191+
192+
# Re-assemble binary MultiAddr representation
193+
part_size = varint.encode(len(part_value)) if codec.SIZE < 0 else b""
194+
part = b"".join((proto.vcode, part_size, part_value))
195+
196+
# Add MultiAddr with the given value
197+
results.append(self.__class__(part))
198+
# Add final item with remainder of MultiAddr if there is anything left
199+
if final_split_offset >= 0:
200+
results.append(self.__class__(self._bytes[final_split_offset:]))
201+
202+
return results
203+
174204
keys = protocols
175205

176206
def items(self):
@@ -185,9 +215,7 @@ def encapsulate(self, other):
185215
For example:
186216
/ip4/1.2.3.4 encapsulate /tcp/80 = /ip4/1.2.3.4/tcp/80
187217
"""
188-
mb = self.to_bytes()
189-
ob = Multiaddr(other).to_bytes()
190-
return Multiaddr(b''.join([mb, ob]))
218+
return self.join(self, other)
191219

192220
def decapsulate(self, other):
193221
"""Remove a Multiaddr wrapping.
@@ -209,7 +237,7 @@ def value_for_protocol(self, proto):
209237
210238
Returns
211239
-------
212-
union[object, NoneType]
240+
Union[object, NoneType]
213241
The parsed protocol value for the given protocol code or ``None``
214242
if the given protocol does not require any value
215243

multiaddr/protocols.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
import binascii
32
import six
43
import varint
54

@@ -118,31 +117,6 @@ def __repr__(self):
118117
)
119118

120119

121-
def _uvarint(buf):
122-
"""Reads a varint from a bytes buffer and returns the value and # bytes"""
123-
x = 0
124-
s = 0
125-
for i, b_str in enumerate(buf):
126-
if six.PY3:
127-
b = b_str
128-
else: # pragma: no cover (PY2)
129-
b = int(binascii.b2a_hex(b_str), 16)
130-
if b < 0x80:
131-
if i > 9 or (i == 9 and b > 1):
132-
# Code 34 apparently means `OverflowError`
133-
# (as opposed to other `ArithmeticError` types)
134-
raise OverflowError(34, "UVarInt too large")
135-
return (x | b << s, i + 1)
136-
x |= (b & 0x7f) << s
137-
s += 7
138-
return 0, 0
139-
140-
141-
def read_varint_code(buf):
142-
num, n = _uvarint(buf)
143-
return int(num), n
144-
145-
146120
# Protocols is the list of multiaddr protocols supported by this module.
147121
PROTOCOLS = [
148122
Protocol(P_IP4, 'ip4', 'ip4'),

multiaddr/transforms.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- encoding: utf-8 -*-
2+
import io
23
import six
34
import varint
45

@@ -9,7 +10,6 @@
910

1011
from .protocols import protocol_with_code
1112
from .protocols import protocol_with_name
12-
from .protocols import read_varint_code
1313

1414

1515
def string_to_bytes(string):
@@ -29,7 +29,7 @@ def string_to_bytes(string):
2929

3030
def bytes_to_string(buf):
3131
st = [u''] # start with empty string so we get a leading slash on join()
32-
for proto, codec, part in bytes_iter(buf):
32+
for _, proto, codec, part in bytes_iter(buf):
3333
st.append(proto.name)
3434
if codec.SIZE != 0:
3535
try:
@@ -43,11 +43,11 @@ def bytes_to_string(buf):
4343
return u'/'.join(st)
4444

4545

46-
def size_for_addr(codec, buf):
46+
def size_for_addr(codec, buf_io):
4747
if codec.SIZE >= 0:
48-
return codec.SIZE // 8, 0
48+
return codec.SIZE // 8
4949
else:
50-
return read_varint_code(buf)
50+
return varint.decode_stream(buf_io)
5151

5252

5353
def string_iter(string):
@@ -82,8 +82,10 @@ def string_iter(string):
8282

8383

8484
def bytes_iter(buf):
85-
while buf:
86-
code, num_bytes_read = read_varint_code(buf)
85+
buf_io = io.BytesIO(buf)
86+
while buf_io.tell() < len(buf):
87+
offset = buf_io.tell()
88+
code = varint.decode_stream(buf_io)
8789
proto = None
8890
try:
8991
proto = protocol_with_code(code)
@@ -97,7 +99,5 @@ def bytes_iter(buf):
9799
),
98100
exc,
99101
)
100-
size, num_bytes_read2 = size_for_addr(codec, buf[num_bytes_read:])
101-
length = size + num_bytes_read2 + num_bytes_read
102-
yield proto, codec, buf[(length - size):length]
103-
buf = buf[length:]
102+
size = size_for_addr(codec, buf_io)
103+
yield offset, proto, codec, buf_io.read(size)

tests/test_multiaddr.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,33 @@ def test_invalid_protocols_with_string(proto_string):
159159
protocols_with_string(proto_string)
160160

161161

162+
@pytest.mark.parametrize(
163+
'proto_string,maxsplit,expected',
164+
[("/ip4/1.2.3.4", -1, ("/ip4/1.2.3.4",)),
165+
("/ip4/0.0.0.0", 0, ("/ip4/0.0.0.0",)),
166+
("/ip6/::1", 1, ("/ip6/::1",)),
167+
("/onion/timaq4ygg2iegci7:80/http", 0, ("/onion/timaq4ygg2iegci7:80/http",)),
168+
("/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", 1,
169+
("/ip4/127.0.0.1", "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",)),
170+
("/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", -1,
171+
("/ip4/1.2.3.4", "/tcp/80", "/unix/a/b/c/d/e/f"))])
172+
def test_split(proto_string, maxsplit, expected):
173+
assert tuple(map(str, Multiaddr(proto_string).split(maxsplit))) == expected
174+
175+
176+
@pytest.mark.parametrize(
177+
'proto_parts,expected',
178+
[(("/ip4/1.2.3.4",), "/ip4/1.2.3.4"),
179+
((b"\x04\x00\x00\x00\x00",), "/ip4/0.0.0.0"),
180+
(("/ip6/::1",), "/ip6/::1"),
181+
(("/onion/timaq4ygg2iegci7:80/http",), "/onion/timaq4ygg2iegci7:80/http"),
182+
((b"\x04\x7F\x00\x00\x01", "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234",),
183+
"/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234"),
184+
(("/ip4/1.2.3.4", "/tcp/80", "/unix/a/b/c/d/e/f"), "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f")])
185+
def test_join(proto_parts, expected):
186+
assert str(Multiaddr.join(*proto_parts)) == expected
187+
188+
162189
def test_encapsulate():
163190
m1 = Multiaddr("/ip4/127.0.0.1/udp/1234")
164191
m2 = Multiaddr("/udp/5678")

tests/test_protocols.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,3 @@ def test_add_protocol_twice(patch_protocols, valid_params):
161161
def test_protocol_repr():
162162
proto = protocols.protocol_with_name('ip4')
163163
assert "Protocol(code=4, name='ip4', codec='ip4')" == repr(proto)
164-
165-
166-
@pytest.mark.parametrize("buf", [
167-
b'\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x01',
168-
b'\x90\x91\x92\x93\x94\x95\x96\x97\x98\x02'])
169-
def test_overflowing_varint(buf):
170-
with pytest.raises(OverflowError):
171-
protocols.read_varint_code(buf)
172-
173-
174-
def test_nonterminated_varint():
175-
assert protocols.read_varint_code(b'\x80\x80') == (0, 0)

tests/test_transforms.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- encoding: utf-8 -*-
2+
import io
3+
24
import pytest
35

46
from multiaddr.codecs import codec_by_name
@@ -57,7 +59,8 @@
5759
('p2p', b'\x40\x50\x60\x51', (64, 1)),
5860
])
5961
def test_size_for_addr(codec_name, buf, expected):
60-
assert size_for_addr(codec_by_name(codec_name), buf) == expected
62+
buf_io = io.BytesIO(buf)
63+
assert (size_for_addr(codec_by_name(codec_name), buf_io), buf_io.tell()) == expected
6164

6265

6366
@pytest.mark.parametrize("buf, expected", [
@@ -69,7 +72,7 @@ def test_size_for_addr(codec_name, buf, expected):
6972
(_names_to_protocols["tcp"], b'\x10\xe1')]),
7073
])
7174
def test_bytes_iter(buf, expected):
72-
assert list((proto, val) for proto, _, val in bytes_iter(buf)) == expected
75+
assert list((proto, val) for _, proto, _, val in bytes_iter(buf)) == expected
7376

7477

7578
@pytest.mark.parametrize("proto, buf, expected", ADDR_BYTES_MAP_STR_TEST_DATA)

0 commit comments

Comments
 (0)