From 11c1c2fb422a890495916dd4b05fbdac74461af8 Mon Sep 17 00:00:00 2001 From: Sahana Bogar Date: Thu, 2 Jul 2026 14:49:15 +0530 Subject: [PATCH 1/2] decode scoped IPv6 addresses encoded as [address, null, zone id] --- docs/versionhistory.rst | 5 ++++ rust/decoder.rs | 66 ++++++++++++++++++++++++----------------- tests/test_decoder.py | 10 +++++++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index e8ecd151..44e24df9 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -7,6 +7,11 @@ This library adheres to `Semantic Versioning 2.0 `_. **UNRELEASED** +- Fixed the decoder rejecting scoped IPv6 addresses (tag 54) with a ``CBORDecodeError`` reading + ``invalid types in input array``; the encoder emits them as ``[address, null, zone id]`` but the + decoder only handled the network and interface array forms, so a scoped :class:`~ipaddress.IPv6Address` + could not be decoded back + (`#324 `_; PR by @sahvx655-wq) - Fixed the decoder registering 6-byte strings in the string reference namespace at indices 65536–4294967295 where the encoder does not, desynchronising the namespace and resolving later string references to the wrong value diff --git a/rust/decoder.rs b/rust/decoder.rs index 0cf75987..0d2b26c4 100644 --- a/rust/decoder.rs +++ b/rust/decoder.rs @@ -1208,35 +1208,12 @@ impl CBORDecoder { { // The decoded value was a 2-item (or 3 with zone ID) array. // Check the types of the elements: - // (int, bytes) -> network - // (bytes, int) -> interface + // (bytes, null) -> scoped address + // (int, bytes) -> network + // (bytes, int) -> interface let first_item = tuple.get_item(0)?; let second_item = tuple.get_item(1)?; let zone_id = tuple.get_item(2).ok(); - let (class, addr_bytes, prefix) = if let Ok(prefix) = first_item.cast::() - && let Ok(address) = second_item.cast::() - { - let mut address_vec: Vec = address.extract()?; - if address_vec.len() > 16 { - return Err(CBORDecodeError::new_err(format!( - "address byte string for IPv6 network is too long ({} bytes)", - address_vec.len() - ))); - } - address_vec.resize(16, 0); - Ok(( - IPV6NETWORK_TYPE.get(py)?, - PyBytes::new(py, address_vec.as_slice()), - prefix, - )) - } else if let Ok(address) = first_item.cast_into::() - && let Ok(prefix) = second_item.cast::() - { - Ok((IPV6INTERFACE_TYPE.get(py)?, address, prefix)) - } else { - Err(CBORDecodeError::new_err("invalid types in input array")) - }?; - let addr_obj = ipv6addr_class.call1((addr_bytes,))?; // Format the zone ID suffix if a zone ID was included // (bytes or integer as the last item of a 3-tuple) @@ -1255,8 +1232,41 @@ impl CBORDecoder { String::default() }; - let formatted_addr = format!("{addr_obj}{zone_id_suffix}/{prefix}"); - class.call1((formatted_addr,))? + if second_item.is_none() + && let Ok(address) = first_item.cast::() + { + // A scoped address is encoded as [address, null, zone id]; with no prefix + // length it decodes to a bare IPv6 address rather than a network or interface. + let addr_obj = ipv6addr_class.call1((address,))?; + ipv6addr_class.call1((format!("{addr_obj}{zone_id_suffix}"),))? + } else { + let (class, addr_bytes, prefix) = if let Ok(prefix) = first_item.cast::() + && let Ok(address) = second_item.cast::() + { + let mut address_vec: Vec = address.extract()?; + if address_vec.len() > 16 { + return Err(CBORDecodeError::new_err(format!( + "address byte string for IPv6 network is too long ({} bytes)", + address_vec.len() + ))); + } + address_vec.resize(16, 0); + Ok(( + IPV6NETWORK_TYPE.get(py)?, + PyBytes::new(py, address_vec.as_slice()), + prefix, + )) + } else if let Ok(address) = first_item.cast_into::() + && let Ok(prefix) = second_item.cast::() + { + Ok((IPV6INTERFACE_TYPE.get(py)?, address, prefix)) + } else { + Err(CBORDecodeError::new_err("invalid types in input array")) + }?; + let addr_obj = ipv6addr_class.call1((addr_bytes,))?; + let formatted_addr = format!("{addr_obj}{zone_id_suffix}/{prefix}"); + class.call1((formatted_addr,))? + } } else { return Err(CBORDecodeError::new_err( "input value must be a bytestring or an array of 2 elements", diff --git a/tests/test_decoder.py b/tests/test_decoder.py index c4e3328f..bda4e0dd 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -873,6 +873,16 @@ def test_uuid_invalid_type() -> None: IPv6Interface("fe80::202:2ff:ffff:fe03:303%2/64"), id="ipv6if_num_zoneid", ), + pytest.param( + "d8368350fe8000000000020202fffffffe030303f64465746830", + IPv6Address("fe80::202:2ff:ffff:fe03:303%eth0"), + id="ipv6addr_str_zoneid", + ), + pytest.param( + "d8368350fe8000000000020202fffffffe030303f64132", + IPv6Address("fe80::202:2ff:ffff:fe03:303%2"), + id="ipv6addr_num_zoneid", + ), ], ) def test_ipaddress(payload: bytes, expected: Any) -> None: From 1b3d70711f5e7dc471ae36c9d9375616db8c82dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Jul 2026 13:09:18 +0300 Subject: [PATCH 2/2] Relocated the changelog entry --- docs/versionhistory.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 44e24df9..f0fafacd 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -7,11 +7,6 @@ This library adheres to `Semantic Versioning 2.0 `_. **UNRELEASED** -- Fixed the decoder rejecting scoped IPv6 addresses (tag 54) with a ``CBORDecodeError`` reading - ``invalid types in input array``; the encoder emits them as ``[address, null, zone id]`` but the - decoder only handled the network and interface array forms, so a scoped :class:`~ipaddress.IPv6Address` - could not be decoded back - (`#324 `_; PR by @sahvx655-wq) - Fixed the decoder registering 6-byte strings in the string reference namespace at indices 65536–4294967295 where the encoder does not, desynchronising the namespace and resolving later string references to the wrong value @@ -30,6 +25,11 @@ This library adheres to `Semantic Versioning 2.0 `_. deciding whether to add them to the string reference namespace, desynchronising it from the decoder (which counts bytes) and corrupting later string references for non-ASCII strings (`#314 `_; PR by @sahvx655-wq) +- Fixed the decoder rejecting scoped IPv6 addresses (tag 54) with a ``CBORDecodeError`` reading + ``invalid types in input array``; the encoder emits them as ``[address, null, zone id]`` but the + decoder only handled the network and interface array forms, so a scoped + :class:`~ipaddress.IPv6Address` could not be decoded back + (`#324 `_; PR by @sahvx655-wq) **6.1.2** (2026-06-02)