From 84e2179e54758b6398f5ab16f51ee1f32f925c8c Mon Sep 17 00:00:00 2001 From: sumanjeet0012 Date: Mon, 13 Jul 2026 23:02:07 +0530 Subject: [PATCH] Fix decapsulate implementation to handle repeated protocols and substrings Resolves #109 --- multiaddr/multiaddr.py | 32 ++++++++++++++++++++++++++------ newsfragments/109.bugfix | 1 + tests/test_multiaddr.py | 15 +++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 newsfragments/109.bugfix diff --git a/multiaddr/multiaddr.py b/multiaddr/multiaddr.py index 45efc6c..d6bfa58 100644 --- a/multiaddr/multiaddr.py +++ b/multiaddr/multiaddr.py @@ -240,12 +240,32 @@ def decapsulate(self, addr: Union["Multiaddr", str]) -> "Multiaddr": For example: /ip4/1.2.3.4/tcp/80 decapsulate /ip4/1.2.3.4 = /tcp/80 """ - addr_str = str(addr) - s = str(self) - i = s.rindex(addr_str) - if i < 0: - raise ValueError(f"Address {s} does not contain subaddress: {addr_str}") - return Multiaddr(s[:i]) + other = Multiaddr(addr) if not isinstance(addr, Multiaddr) else addr + other_components = list(bytes_iter(other.to_bytes())) + self_components = list(bytes_iter(self._bytes)) + + last_match_end = -1 + for i in range(len(self_components)): + match = True + for j, (_, proto, _, value) in enumerate(other_components): + if i + j >= len(self_components): + match = False + break + _, s_proto, _, s_value = self_components[i + j] + if s_proto != proto or s_value != value: + match = False + break + + if match: + last_match_end = self_components[i][0] # byte offset + + if last_match_end < 0: + raise ValueError(f"Address {self} does not contain subaddress: {addr}") + + if last_match_end == 0: + return Multiaddr("") + + return Multiaddr(self._bytes[:last_match_end]) def decapsulate_code(self, code: int) -> "Multiaddr": """ diff --git a/newsfragments/109.bugfix b/newsfragments/109.bugfix new file mode 100644 index 0000000..08e7895 --- /dev/null +++ b/newsfragments/109.bugfix @@ -0,0 +1 @@ +Fixed an issue where decapsulate produced incorrect results for repeated protocols and substring collisions. diff --git a/tests/test_multiaddr.py b/tests/test_multiaddr.py index da402e5..8dc8cf9 100644 --- a/tests/test_multiaddr.py +++ b/tests/test_multiaddr.py @@ -613,6 +613,21 @@ def test_decapsulate(): u = Multiaddr("/udp/1234") assert a.decapsulate(u) == Multiaddr("/ip4/127.0.0.1") + # Issue #109 Case 1 — Repeated protocol + ma1 = Multiaddr("/ip4/1.2.3.4/tcp/80/ip4/5.6.7.8/tcp/443") + assert ma1.decapsulate("/ip4/5.6.7.8") == Multiaddr("/ip4/1.2.3.4/tcp/80") + + # Issue #109 Case 2 — Substring collision + ma2 = Multiaddr("/dns4/example.com/tcp/80") + import pytest + + with pytest.raises(ValueError, match="does not contain subaddress"): + ma2.decapsulate("/tcp/8") + + # Issue #109 Case 3 — Value contains protocol name + ma3 = Multiaddr("/dns4/tcp.example.com/tcp/80") + assert ma3.decapsulate("/tcp/80") == Multiaddr("/dns4/tcp.example.com") + def test__repr(): a = Multiaddr("/ip4/127.0.0.1/udp/1234")