-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ACP(core::net): add Ipv[4|6]Address::from_octets
and Ipv6Address::from_segments
#447
Comments
Ipv[4|6]Address::from_octets
and Ipv6Address::from_segmentsIpv[4|6]Address::from_octets
and Ipv6Address::from_segments
If the goal is just to not write the length of the array, something like this works fine: use std::net::Ipv4Addr;
fn demo(x: &[u8]) -> Ipv4Addr {
let bytes = *x.first_chunk().unwrap();
bytes.into()
} Or if you want to check the slice size, something like use std::net::Ipv4Addr;
fn demo2(x: &[u8]) -> Option<(Ipv4Addr, &[u8])> {
let (bytes, rest) = x.split_first_chunk()?;
Some(((*bytes).into(), rest))
} Basically, use a different way to get the array rather than |
I think the problem of handling a slice of bytes could be addressed better by |
Most of the fields in IP packets have a fixed position and length. In smoltcp, the positions are represented as &data[field::SRC_ADDR] In smoltcp, we can then easily convert this subslice into an This is implemented as follows in smoltcp: /// Construct an IPv4 address from a sequence of octets, in big-endian.
///
/// # Panics
/// The function panics if `data` is not four octets long.
pub fn from_bytes(data: &[u8]) -> Address {
let mut bytes = [0; ADDR_SIZE];
bytes.copy_from_slice(data);
Address(bytes)
} I find this method of creating an IP address in smoltcp very straightforward, which contrasts with the more complex approach currently required by the types in the core library.
This would also be better to address the problem. I modified the ACP to also mention this. |
@scottmcm your suggested snippets behave a bit differently, they'll silently ignore slices longer than 4 bytes, while we'd like to panic in that case. There's another argument in favor of adding these methods: consistency with other conversions, like for u32. Currently the
but the
Why is it like that? Why is one direction only available with a method, why is the other direction only available with a
(and same for ipv6 octets and segments) |
So I'm not at all opposed to the clearly-named methods for this that take arrays by value. That seems entirely reasonable, and array-of-bytes and IPv4 aren't exactly value-preserving in the https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from sense, so using What I mean is that the slice part of this feels like it wants something different. You're only really having that problem because the If you could do TL/DR: let's add a way that means you don't need to
There's of course various other phrasings of the same thing that do that, like use std::net::Ipv4Addr;
fn demo(x: &[u8]) -> Ipv4Addr {
let Some((bytes, [])) = x.split_first_chunk() else { panic!() };
From::from(*bytes)
} |
that would be awesome, but such a thing doesn't exist today in Rust and adding it is a much larger undertaking than adding a few methods to |
you could use something like: use std::ops::Index;
pub struct MyField;
impl Index<MyField> for [u8] {
type Output = [u8; 4];
fn index(&self, _i: MyField) -> &Self::Output {
(&self[8..12]).try_into().unwrap()
}
} |
Well I bring it up because of other posts here mentioning Adding pub const fn from_octets(octets: [u8; 4]) -> Ipv4Addr;
pub const fn from_segments(segments: [u16; 8]) -> Ipv6Addr; seems entirely fine to me, I agree. I'm just much less convinced that anything dealing in unknown-length slices is solving the right problem, because that problem should be solved at the slicing level instead IMHO, rather than at the every-type-constructible-from-an-array level. |
@programmerjake including generalizing it to something like struct Field<const OFFSET: usize, const LEN: usize>;
const SRC_ADDR: Field<12, 4> = Field;
const DST_ADDR: Field<16, 4> = Field;
impl<T, const OFFSET: usize, const LEN: usize> Index<Field<OFFSET, LEN>> for [T] {
type Output = [T; LEN];
fn index(&self, Field: Field<OFFSET, LEN>) -> &Self::Output {
(&self[OFFSET..][..LEN]).try_into().unwrap()
}
}
fn packet_src_dst_addrs(packet: &[u8]) -> (Ipv4Addr, Ipv4Addr) {
(packet[SRC_ADDR].into(), packet[DST_ADDR].into())
} |
tbh this makes me think that Rust needs some length-based range types, kinda like verilog's |
@programmerjake I only wrote it that way because I can't do type Output = [T; END - BEGIN]; in Rust today :P (Given the choice, I'd absolutely use a half-open range here too.) |
I guess they still wouldn't do what we want, which is: |
The way this is usually done in Java and Node is to use a buffer object with a cursor and then read things field by field into local variables and then process those variables further.
It could look like this fn parse_header(buf: &[u8]) -> Option<Header> {
let buf = Buffer::new(buf, Endianness::Big);
let header = Header::default();
header.version = buf.read_u8()?;
header.flags = buf.read_u32()?;
header.src_ip6 = buf::read_chunk::<16>()?.into();
header.dst_ip6 = buf::read_chunk::<16>()?.into();
Some(header)
} this could even be added to |
We discussed this in today's @rust-lang/libs-api meeting. We were in favor of adding the proposed Some of us were in favor of adding the direct |
i'm not convinced about |
New methods can be added |
They should be separate PRs regardless -- the new methods can be added in nightly as soon as a reviewer |
ping! so what's the next steps here? someone from T-libs accepts this ACP? |
The next step is to submit a PR to add |
Proposal: Add
Ipv[4|6]Address::from_octets
andIpv6Address::from_segments
Problem statement
It is common to convert
&[u8]
to anIpv[4|6]Addr
. This can currently be achieved using:impl From<[u8; 16]> for Ipv6Addr
impl From<[u16; 8]> for Ipv6Addr
impl From<[u8; 4]> for Ipv4Addr
which requires us to write:
or
This is not convenient as the type needs to be implicitly written.
Motivating examples or use cases
Packets are usually just
&[u8]
, which a TCP/IP stack needs to parse. Indexing the fields return either au8
or a&[u8]
when the field is one octet or multiple octets, respectively. Creating an IP address therefore requires us to parse a&[u8]
into anIpv4Addr
orIpv6Addr
.To achieve this in C#, the
public IPAddress (byte[] address)
can be used. When theaddress
contains 4 elements, an IPv4 address is constructed. For other cases, an IPv6 address is constructed.In go, an address can also be constructed by a byte array, without specifying the length of the array.
Python also allows creating an address by passing a
bytes
object.smoltcp is a TCP/IP stack, which contains custom
Ipv[4|6]Address
types, instead of usingcore::net::Ipv[4|6]Addr
. The network stack parses IPv6 address like this:This ACP seeks to add functions which allow creating addresses from a
&[u8]
, where the compiler can infer the type when callingtry_into()
.Solution sketch
By adding the following functions:
the compiler can infer the type for
try_into()
, which was previously not possible with theFrom
implementationscan now be written as
These functions are consistent with the
Ipv4Addr::octets(&self) -> [u8; 4]
,Ipv6Addr::octets(&self) -> [u8; 16]
andIpv6Addr::segments(&self) -> [u16; 8]
functions.Another way of easily constructing an address from a
&[u8]
, would be by implementing:This would make it possible to construct an address as:
Alternatives
Instead of using
try_into
to convert&[u8]
to[u8; 4]
or[u8; 16]
,from_octets
could just take a&[u8]
, and panic when the length is not 4 or 16 forIpv4Addr
andIpv6Addr
respectively.Links and related work
rust-lang/rust#130629 already implements this ACP.
C# documentation: https://learn.microsoft.com/en-us/dotnet/api/system.net.ipaddress.-ctor?view=net-8.0#system-net-ipaddress-ctor(system-byte())
go documentation: https://pkg.go.dev/net#IP
Python documentation: https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Address
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution:
The text was updated successfully, but these errors were encountered: