Skip to content

Commit d6d2f2c

Browse files
committed
Fix decapsulate implementation to handle repeated protocols and substrings
Resolves #109
1 parent 0b6493a commit d6d2f2c

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

multiaddr/multiaddr.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,32 @@ def decapsulate(self, addr: Union["Multiaddr", str]) -> "Multiaddr":
240240
For example:
241241
/ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = /tcp/80
242242
"""
243-
addr_str = str(addr)
244-
s = str(self)
245-
i = s.rindex(addr_str)
246-
if i < 0:
247-
raise ValueError(f"Address {s} does not contain subaddress: {addr_str}")
248-
return Multiaddr(s[:i])
243+
other = Multiaddr(addr) if not isinstance(addr, Multiaddr) else addr
244+
other_components = list(bytes_iter(other.to_bytes()))
245+
self_components = list(bytes_iter(self._bytes))
246+
247+
last_match_end = -1
248+
for i in range(len(self_components)):
249+
match = True
250+
for j, (offset, proto, codec, value) in enumerate(other_components):
251+
if i + j >= len(self_components):
252+
match = False
253+
break
254+
s_offset, s_proto, s_codec, s_value = self_components[i + j]
255+
if s_proto != proto or s_value != value:
256+
match = False
257+
break
258+
259+
if match:
260+
last_match_end = self_components[i][0] # byte offset
261+
262+
if last_match_end < 0:
263+
raise ValueError(f"Address {self} does not contain subaddress: {addr}")
264+
265+
if last_match_end == 0:
266+
return Multiaddr("")
267+
268+
return Multiaddr(self._bytes[:last_match_end])
249269

250270
def decapsulate_code(self, code: int) -> "Multiaddr":
251271
"""

newsfragments/109.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed an issue where decapsulate produced incorrect results for repeated protocols and substring collisions.

tests/test_multiaddr.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,21 @@ def test_decapsulate():
613613
u = Multiaddr("/udp/1234")
614614
assert a.decapsulate(u) == Multiaddr("/ip4/127.0.0.1")
615615

616+
# Issue #109 Case 1 — Repeated protocol
617+
ma1 = Multiaddr("/ip4/1.2.3.4/tcp/80/ip4/5.6.7.8/tcp/443")
618+
assert ma1.decapsulate("/ip4/5.6.7.8") == Multiaddr("/ip4/1.2.3.4/tcp/80")
619+
620+
# Issue #109 Case 2 — Substring collision
621+
ma2 = Multiaddr("/dns4/example.com/tcp/80")
622+
import pytest
623+
624+
with pytest.raises(ValueError, match="does not contain subaddress"):
625+
ma2.decapsulate("/tcp/8")
626+
627+
# Issue #109 Case 3 — Value contains protocol name
628+
ma3 = Multiaddr("/dns4/tcp.example.com/tcp/80")
629+
assert ma3.decapsulate("/tcp/80") == Multiaddr("/dns4/tcp.example.com")
630+
616631

617632
def test__repr():
618633
a = Multiaddr("/ip4/127.0.0.1/udp/1234")

0 commit comments

Comments
 (0)