Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
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 <https://github.com/agronholm/cbor2/pull/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 <https://github.com/agronholm/cbor2/pull/324>`_; PR by @sahvx655-wq)

**6.1.2** (2026-06-02)

Expand Down
66 changes: 38 additions & 28 deletions rust/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<PyInt>()
&& let Ok(address) = second_item.cast::<PyBytes>()
{
let mut address_vec: Vec<u8> = 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::<PyBytes>()
&& let Ok(prefix) = second_item.cast::<PyInt>()
{
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)
Expand All @@ -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::<PyBytes>()
{
// 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::<PyInt>()
&& let Ok(address) = second_item.cast::<PyBytes>()
{
let mut address_vec: Vec<u8> = 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::<PyBytes>()
&& let Ok(prefix) = second_item.cast::<PyInt>()
{
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",
Expand Down
10 changes: 10 additions & 0 deletions tests/test_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading