moonip

    IP addresses and networks for MoonBit: IPv4, IPv6, prefixes and how to classify them. Parsing and arithmetic only, no sockets.

    ip
    ipv4
    ipv6
    ip-address
    ip-addresses
    address
    cidr
    subnet
    subnetting
    supernet
    prefix
    netmask
    hostmask
    broadcast
    network
    netip
    ipaddress
    inet-pton
    inet-ntop
    parsing
    parser
    formatting
    classification
    private-address
    loopback
    link-local
    multicast
    zone
    scoped-address
    reverse-dns
    ptr
    teredo
    6to4
    rfc791
    rfc1918
    rfc3021
    rfc3596
    rfc3849
    rfc3879
    rfc3927
    rfc4007
    rfc4193
    rfc4291
    rfc4632
    rfc5952
    rfc6164
    rfc6890
    rfc8200
    rfc9637
    iana
    moonbit
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    yesterday
    Downloads
    20

    No dependencies

    #moonip

    IP addresses and networks for MoonBit. IPv4, IPv6, classless prefixes and the special-purpose ranges: parsing, formatting, block arithmetic and classification. No sockets, no I/O, no dependencies.

    moon add moonbitstack/moonip

    fn main raise {
    let block = @moonip.Prefix::parse("192.0.2.0/24")
    println(block.netmask()) // 255.255.255.0
    println(block.contains(@moonip.Addr::parse("192.0.2.7"))) // true
    println(block.subnets(bits=26).collect().length()) // 4
    }

    #Three values

    TypeWhat it isMade by
    AddrOne IPv4 or IPv6 address, plus the zone a scoped IPv6 address may carryAddr::parse, Addr::decode, Addr::v4, Addr::v6
    PrefixAn address and a mask lengthPrefix::parse, Prefix::new
    AddrPortAn address and a portAddrPort::parse, AddrPort::new

    All three are immutable values that compare, hash and order, so they can key a map or sort a list. Everything else in the package is a function over one of them.

    #Addresses

    Addr::parse reads the text forms of RFC 4291; to_string writes the canonical form of RFC 5952.

    let a = @moonip.Addr::parse("2001:0DB8:0000:0000:0000:0000:0000:0001")
    assert_eq(a.to_string(), "2001:db8::1")
    assert_eq(a.exploded(), "2001:0db8:0000:0000:0000:0000:0000:0001")
    assert_eq(a.encode().length(), 16)
    assert_eq(@moonip.Addr::parse("::ffff:192.0.2.1").unmap().to_string(), "192.0.2.1")

    next and prev step through a family and stop at its ends. reverse_pointer gives the name in the reverse DNS tree. sixtofour and teredo pull the IPv4 addresses back out of the two transition forms.

    #Blocks

    let block = @moonip.Prefix::parse("192.0.2.0/24")
    assert_eq(block.masked().to_string(), "192.0.2.0/24")
    assert_eq(block.netmask().to_string(), "255.255.255.0")
    assert_eq(block.hostmask().to_string(), "0.0.0.255")
    assert_eq(block.broadcast().map(@moonip.Addr::to_string), Some("192.0.2.255"))
    assert_eq(@moonip.Prefix::parse("2001:db8::/32").broadcast(), None) // IPv6 has none

    A prefix keeps the address it was written with, so the same value can stand for an interface on that block. Prefix::parse refuses host bits by default and clears them with strict=false; Prefix::new keeps whatever it is given, and masked() is how you ask for the block itself.

    CallAnswer
    contains, overlapswhether an address or another block meets this one
    is_subnet_of, is_supernet_ofwhich of the two holds the other
    hoststhe addresses an interface can be given, lazily
    subnets, supernetsplit down, or the block that holds this one
    excludethe parts of a block outside another one
    summarize, collapsethe fewest blocks holding a range or a set
    num_addresses, is_single_iphow many addresses there are, or whether there is one

    let parts = @moonip.summarize(
    @moonip.Addr::parse("192.0.2.0"),
    @moonip.Addr::parse("192.0.2.130"),
    )
    assert_eq(parts.map(@moonip.Prefix::to_string), [
    "192.0.2.0/25", "192.0.2.128/31", "192.0.2.130/32",
    ])

    #Classification

    Eight predicates answer one row each of the two IANA special-purpose address registries:

    is_unspecified · is_loopback · is_multicast · is_link_local · is_site_local · is_reserved · is_private · is_global

    Two more read the scope of a multicast address, which RFC 4291 puts in the second octet: is_interface_local_multicast is ff01::/16 and is_link_local_multicast is 224.0.0.0/24 and ff02::/16.

    is_private means "a registry marks this as not globally reachable": the RFC 1918 blocks, unique local addresses, loopback, link local, documentation, benchmarking and the reserved space. Shared address space, 100.64.0.0/10, is the one block that is neither private nor reachable, and it answers false to both. An IPv4-mapped IPv6 address is judged by the IPv4 address inside it.

    #Refusals

    Every call that can fail raises Refused, and the variant says what kind of mistake it was:

    VariantMeaning
    Malformed(at~)the text stopped making sense at character at, counted from 0
    OutOfRange(limit~, got~)a number did not fit: a mask length or a port
    Length(want~, got~)a byte string was the wrong size
    HostBitshost bits set below the mask while strict forbade it
    FamilyIPv4 against IPv6
    Reverseda range that ends before it starts

    #Choices

    Parsing is strict on purpose. These are refused rather than guessed at:

    • a leading zero in an IPv4 field, which some resolvers would read as octal;
    • a dotted netmask, 192.0.2.0/255.255.255.0, in place of a mask length;
    • a zone in a prefix, because a block spans links rather than sitting on one;
    • a second % in a zone, or a zone on an IPv4 address;
    • an unbracketed IPv6 address where a port is expected.

    #Documents

    • Home
    • Guide — ten steps, each with the output it gives
    • API reference — generated from the doc comments
    • Explorer — the library compiled to JavaScript, running in the page
    • Showcase — a datagram reassembled from its fragments, and the classification walk drawn as hops
    • Skill — for an agent that has to use the package

    Apache-2.0.

    Refused

    pub(all) suberror Refused {
    Malformed(at~ : Int)
    OutOfRange(limit~ : Int, got~ : Int)
    Length(want~ : Int, got~ : Int)
    HostBits
    Family
    Reversed
    }

    Why a text form, a byte string or an argument pair was refused.

    Every call that can refuse reports one of these rather than a boolean, so the caller can tell a typo from a number that was merely too large.
    impl Show for Refused

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    Addr

    pub struct Addr {
    hi : UInt64
    lo : UInt64
    v4 : Bool
    zone : String?
    }

    An IPv4 or IPv6 address, and the zone an IPv6 scoped address may carry.

    The value never changes once made, compares by its bits, and can be a key in a map. IPv4 keeps its 32 bits in the low word with v4 set, so 1.2.3.4 and ::1.2.3.4 are two different addresses that never compare equal.
    impl Compare for Addr
    impl Eq for Addr
    impl Hash for Addr
    impl Show for Addr

    Addr::bits

    fn Addr::bits(self : Addr) -> Int

    How many bits the address has: 32 for IPv4, 128 for IPv6.

    Addr::compare

    fn Addr::compare(self : Addr, other : Addr) -> Int

    Addr::decode

    fn Addr::decode(bytes : BytesView) -> Addr raise Refused

    Read the address back from four (IPv4) or sixteen (IPv6) bytes.

    Addr::encode

    fn Addr::encode(self : Addr) -> Bytes

    The four or sixteen bytes of the address, big-endian, the way a packet or a certificate option carries them. A zone is not part of the address and is not written.

    Addr::equal

    fn Addr::equal(self : Addr, other : Addr) -> Bool

    Addr::exploded

    fn Addr::exploded(self : Addr) -> String

    Every group written out in full, four hexadecimal digits each, with nothing compressed. Handy for anything that compares text rather than reads it: two addresses are equal exactly when their exploded forms are.

    test {
    assert_eq(
    @moonip.Addr::parse("2001:db8::1").exploded(),
    "2001:0db8:0000:0000:0000:0000:0000:0001",
    )
    }

    Addr::hash

    fn Addr::hash(self : Addr) -> Int

    Addr::hash_combine

    fn Addr::hash_combine(self : Addr, hasher : Hasher) -> Unit

    Addr::is_global

    fn Addr::is_global(self : Addr) -> Bool

    Whether the address is globally reachable: the opposite of is_private, except for shared address space 100.64.0.0/10, which is neither private nor reachable and so answers false to both.

    test {
    assert_true(@moonip.Addr::parse("8.8.8.8").is_global())
    assert_true(@moonip.Addr::parse("2001:4860::1").is_global())
    assert_false(@moonip.Addr::parse("100.64.0.1").is_global())
    assert_false(@moonip.Addr::parse("100.64.0.1").is_private())
    }

    Addr::is_interface_local_multicast

    fn Addr::is_interface_local_multicast(self : Addr) -> Bool

    Whether this is an interface-local multicast address, ff01::/16 (RFC 4291 section 2.7). The scope says the packet never leaves the interface it was sent on, which is why IPv4, whose multicast has no such scope, is always false.

    test {
    assert_true(@moonip.Addr::parse("ff01::1").is_interface_local_multicast())
    assert_false(@moonip.Addr::parse("ff02::1").is_interface_local_multicast())
    }
    fn Addr::is_link_local(self : Addr) -> Bool

    Whether this is a link-local address: 169.254.0.0/16 for IPv4 (RFC 3927), fe80::/10 for IPv6.
    fn Addr::is_link_local_multicast(self : Addr) -> Bool

    Whether this is a link-local multicast address: 224.0.0.0/24 for IPv4, ff02::/16 for IPv6. is_link_local is the unicast counterpart.

    test {
    assert_true(@moonip.Addr::parse("224.0.0.251").is_link_local_multicast())
    assert_true(@moonip.Addr::parse("ff02::fb").is_link_local_multicast())
    assert_false(@moonip.Addr::parse("239.1.1.1").is_link_local_multicast())
    }

    Addr::is_loopback

    fn Addr::is_loopback(self : Addr) -> Bool

    Whether this is a loopback address: 127.0.0.0/8 for IPv4, ::1 for IPv6.

    Addr::is_multicast

    fn Addr::is_multicast(self : Addr) -> Bool

    Whether this is a multicast address: 224.0.0.0/4 for IPv4, ff00::/8 for IPv6.

    Addr::is_private

    fn Addr::is_private(self : Addr) -> Bool

    Whether the address sits in a block the IANA registries mark as not globally reachable: the private-use ranges, loopback, link local, documentation, benchmarking and the rest, with the IPv6 exceptions the registry itself names.

    test {
    assert_true(@moonip.Addr::parse("10.1.2.3").is_private())
    assert_false(@moonip.Addr::parse("8.8.8.8").is_private())
    assert_true(@moonip.Addr::parse("fd00::1").is_private())
    }

    Addr::is_reserved

    fn Addr::is_reserved(self : Addr) -> Bool

    Whether this address is reserved for a purpose other than unicast: 240.0.0.0/4 for IPv4, and for IPv6 every block outside 2000::/3 and fe80::/10 (RFC 4291 section 2.4).

    Addr::is_site_local

    fn Addr::is_site_local(self : Addr) -> Bool

    Whether this is a deprecated site-local address, fec0::/10 (RFC 3879). IPv4 has no such thing, so it is always false there.

    Addr::is_unspecified

    fn Addr::is_unspecified(self : Addr) -> Bool

    Whether this is the unspecified address, 0.0.0.0 or ::. An IPv4-mapped address is judged by the IPv4 address inside it, like every other question here.

    Addr::is_v4

    fn Addr::is_v4(self : Addr) -> Bool

    Whether this is an IPv4 address.

    Addr::is_v4_mapped

    fn Addr::is_v4_mapped(self : Addr) -> Bool

    Whether this is an IPv4-mapped IPv6 address, that is, one in ::ffff:0:0/96.

    Addr::is_v6

    fn Addr::is_v6(self : Addr) -> Bool

    Whether this is an IPv6 address. An IPv4-mapped address such as ::ffff:192.0.2.1 is IPv6 here; unmap is how it becomes IPv4.

    Addr::mapped

    fn Addr::mapped(self : Addr) -> Addr

    The IPv4-mapped IPv6 form of an IPv4 address; a real IPv6 address comes back unchanged.

    Addr::next

    fn Addr::next(self : Addr) -> Addr?

    The next address, or None at the top of the family.

    Addr::not_equal

    fn Addr::not_equal(x : Addr, y : Addr) -> Bool

    Addr::op_ge

    fn Addr::op_ge(x : Addr, y : Addr) -> Bool

    Addr::op_gt

    fn Addr::op_gt(x : Addr, y : Addr) -> Bool

    Addr::op_le

    fn Addr::op_le(x : Addr, y : Addr) -> Bool

    Addr::op_lt

    fn Addr::op_lt(x : Addr, y : Addr) -> Bool

    Addr::output

    fn Addr::output(self : Addr, logger : &Logger) -> Unit

    Addr::parse

    fn Addr::parse(text : StringView) -> Addr raise Refused

    Read an address out of its text form: four dotted decimal fields, or the colon form of RFC 4291 with an optional %zone.

    Addr::prev

    fn Addr::prev(self : Addr) -> Addr?

    The address before this one, or None at the bottom of the family.

    Addr::reverse_pointer

    fn Addr::reverse_pointer(self : Addr) -> String

    The name this address has in the reverse DNS tree: 4.3.2.1.in-addr.arpa for IPv4, and the thirty-two nibbles of the address in reverse order under ip6.arpa for IPv6 (RFC 3596).

    test {
    assert_eq(
    @moonip.Addr::parse("192.0.2.1").reverse_pointer(),
    "1.2.0.192.in-addr.arpa",
    )
    assert_eq(
    @moonip.Addr::parse("::1").reverse_pointer(),
    "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa",
    )
    }

    Addr::sixtofour

    fn Addr::sixtofour(self : Addr) -> Addr?

    The IPv4 address carried by a 6to4 address, 2002::/16 (RFC 3056).

    Addr::teredo

    fn Addr::teredo(self : Addr) -> (Addr, Addr)?

    The server and client addresses carried by a Teredo address, 2001::/32 (RFC 4380). The client is stored inverted (RFC 5991), so the last 32 bits are read as a complement.

    Addr::to_repr

    Addr::to_string

    fn Addr::to_string(self : Addr) -> String

    Addr::unmap

    fn Addr::unmap(self : Addr) -> Addr

    The IPv4 address inside an IPv4-mapped IPv6 one; anything else comes back unchanged. The zone goes with the IPv6 form and is dropped.

    Addr::v4

    fn Addr::v4(a : Byte, b : Byte, c : Byte, d : Byte) -> Addr

    An IPv4 address from its four octets.

    Addr::v6

    fn Addr::v6(hi : UInt64, lo : UInt64) -> Addr

    An IPv6 address from its two halves: hi the top 64 bits, lo the bottom 64.

    Addr::with_zone

    fn Addr::with_zone(self : Addr, zone : String?) -> Addr

    The same address on another zone. A zone only means something to a scoped IPv6 address, so on IPv4 this returns the address unchanged.

    Addr::zone

    fn Addr::zone(self : Addr) -> String?

    The zone (RFC 4007), when the address came with one.

    AddrPort

    pub struct AddrPort {
    addr : Addr
    port : Int
    }

    An address and a port: one end of a TCP or UDP conversation.

    test {
    let end = @moonip.AddrPort::parse("[::1]:443")
    assert_eq(end.to_string(), "[::1]:443")
    assert_eq(end.port(), 443)
    }
    impl Compare for AddrPort
    impl Eq for AddrPort
    impl Hash for AddrPort
    impl Show for AddrPort

    AddrPort::addr

    fn AddrPort::addr(self : AddrPort) -> Addr

    The address half.

    AddrPort::compare

    fn AddrPort::compare(self : AddrPort, other : AddrPort) -> Int

    AddrPort::equal

    fn AddrPort::equal(self : AddrPort, other : AddrPort) -> Bool

    AddrPort::hash

    fn AddrPort::hash(self : AddrPort) -> Int

    AddrPort::hash_combine

    fn AddrPort::hash_combine(self : AddrPort, hasher : Hasher) -> Unit

    AddrPort::new

    fn AddrPort::new(addr : Addr, port : Int) -> AddrPort raise Refused

    An address and a port, the port being 0 to 65535.

    AddrPort::not_equal

    fn AddrPort::not_equal(x : AddrPort, y : AddrPort) -> Bool

    AddrPort::op_ge

    fn AddrPort::op_ge(x : AddrPort, y : AddrPort) -> Bool

    AddrPort::op_gt

    fn AddrPort::op_gt(x : AddrPort, y : AddrPort) -> Bool

    AddrPort::op_le

    fn AddrPort::op_le(x : AddrPort, y : AddrPort) -> Bool

    AddrPort::op_lt

    fn AddrPort::op_lt(x : AddrPort, y : AddrPort) -> Bool

    AddrPort::output

    fn AddrPort::output(self : AddrPort, logger : &Logger) -> Unit

    AddrPort::parse

    fn AddrPort::parse(text : StringView) -> AddrPort raise Refused

    Read 1.2.3.4:80 or [::1]:80.

    The brackets around an IPv6 address are what tells the last colon from the ones in the address, so an IPv6 address without them is refused rather than guessed at.

    AddrPort::port

    fn AddrPort::port(self : AddrPort) -> Int

    The port half, 0 to 65535.

    AddrPort::to_repr

    AddrPort::to_string

    fn AddrPort::to_string(self : AddrPort) -> String

    AddrPort::with_port

    fn AddrPort::with_port(self : AddrPort, port : Int) -> AddrPort raise Refused

    The same address on another port, for a proxy that rewrites one half of an end.

    test {
    let end = @moonip.AddrPort::parse("192.0.2.1:8080")
    assert_eq(end.with_port(443).to_string(), "192.0.2.1:443")
    }

    Prefix

    pub struct Prefix {
    net : Addr
    plen : Int
    }

    A network: an address and the number of leading bits that all addresses in it share, written 192.0.2.0/24.

    The address is kept as it was given, so 192.0.2.5/24 stands for "this host on that link" the way an interface does; every question about the block itself works on masked, which is the same prefix with the host bits cleared.
    impl Compare for Prefix
    impl Eq for Prefix
    impl Hash for Prefix
    impl Show for Prefix

    Prefix::addr

    fn Prefix::addr(self : Prefix) -> Addr

    The address as written, host bits and all.

    Prefix::bits

    fn Prefix::bits(self : Prefix) -> Int

    The length of the mask in bits: 0 to 32 for IPv4, 0 to 128 for IPv6.

    Prefix::broadcast

    fn Prefix::broadcast(self : Prefix) -> Addr?

    The last address of the block, or None for IPv6, which has no broadcast address: the top of an IPv6 block is an ordinary unicast address.

    Prefix::compare

    fn Prefix::compare(self : Prefix, other : Prefix) -> Int

    Prefix::contains

    fn Prefix::contains(self : Prefix, addr : Addr) -> Bool

    Whether the address falls inside the block. An IPv4-mapped IPv6 address is matched against IPv6 blocks only; unmap first if you mean the IPv4 one.

    Prefix::equal

    fn Prefix::equal(self : Prefix, other : Prefix) -> Bool

    Prefix::exclude

    fn Prefix::exclude(self : Prefix, other : Prefix) -> Array[Prefix] raise Refused

    The parts of this block that the other one does not cover, largest first.

    The other block only has to share the family; a block that merely touches this one at the edge leaves it whole, one that covers it leaves nothing, and one that sits over part of it leaves the surround. So the answer is always the exact difference, whatever the caller passes.

    test {
    let parts = @moonip.Prefix::parse("192.0.2.0/28").exclude(
    @moonip.Prefix::parse("192.0.2.1/32"),
    )
    assert_eq(parts.length(), 4)
    assert_eq(parts[0].to_string(), "192.0.2.8/29")
    }

    Prefix::hash

    fn Prefix::hash(self : Prefix) -> Int

    Prefix::hash_combine

    fn Prefix::hash_combine(self : Prefix, hasher : Hasher) -> Unit

    Prefix::hostmask

    fn Prefix::hostmask(self : Prefix) -> Addr

    The complement of the mask, 0.0.0.255 or ::ffff:ffff:ffff:ffff.

    Prefix::hosts

    fn Prefix::hosts(self : Prefix) -> Iter[Addr]

    The addresses that can be given to an interface in the block.

    For IPv4 that is every address except the network and broadcast ones, with a /31 (RFC 3021) and a /32 kept whole because both ends are usable there. For IPv6 it is every address except the subnet-router anycast address at the bottom, with a /127 and a /128 kept whole.

    The iterator is lazy; a /0 has a great many addresses and no one wants them in an array.

    test {
    let usable = @moonip.Prefix::parse("192.0.2.0/30").hosts().collect()
    assert_eq(usable.length(), 2)
    assert_eq(usable[0].to_string(), "192.0.2.1")
    }

    Prefix::is_single_ip

    fn Prefix::is_single_ip(self : Prefix) -> Bool

    Whether the block is a single address, that is, a /32 or a /128.

    Prefix::is_subnet_of

    fn Prefix::is_subnet_of(self : Prefix, other : Prefix) -> Bool

    Whether this block sits inside the other one, equal blocks included.

    Prefix::is_supernet_of

    fn Prefix::is_supernet_of(self : Prefix, other : Prefix) -> Bool

    Whether this block holds the other one, equal blocks included. The same test as is_subnet_of with the two sides the other way round, for callers who read better that way.

    test {
    let wide = @moonip.Prefix::parse("10.0.0.0/8")
    assert_true(wide.is_supernet_of(@moonip.Prefix::parse("10.1.0.0/16")))
    assert_false(wide.is_supernet_of(@moonip.Prefix::parse("11.0.0.0/8")))
    }

    Prefix::masked

    fn Prefix::masked(self : Prefix) -> Prefix

    The block itself: the address with every bit below the mask cleared.

    Prefix::netmask

    fn Prefix::netmask(self : Prefix) -> Addr

    The mask as an address, 255.255.255.0 or ffff:ffff:ffff:ffff::.

    Prefix::new

    fn Prefix::new(addr : Addr, bits : Int) -> Prefix raise Refused

    A prefix from an address and a length. The address is kept whole; use masked to turn it into the network it names.

    Prefix::not_equal

    fn Prefix::not_equal(x : Prefix, y : Prefix) -> Bool

    Prefix::num_addresses

    fn Prefix::num_addresses(self : Prefix) -> UInt64?

    How many addresses the block holds, or None when that is more than a UInt64 can count: an IPv6 block shorter than /64. An IPv4 block always answers, 0.0.0.0/0 with 4294967296.

    test {
    assert_eq(@moonip.Prefix::parse("192.0.2.0/24").num_addresses(), Some(256UL))
    assert_eq(@moonip.Prefix::parse("2001:db8::/32").num_addresses(), None)
    }

    Prefix::op_ge

    fn Prefix::op_ge(x : Prefix, y : Prefix) -> Bool

    Prefix::op_gt

    fn Prefix::op_gt(x : Prefix, y : Prefix) -> Bool

    Prefix::op_le

    fn Prefix::op_le(x : Prefix, y : Prefix) -> Bool

    Prefix::op_lt

    fn Prefix::op_lt(x : Prefix, y : Prefix) -> Bool

    Prefix::output

    fn Prefix::output(self : Prefix, logger : &Logger) -> Unit

    Prefix::overlaps

    fn Prefix::overlaps(self : Prefix, other : Prefix) -> Bool

    Whether two blocks share at least one address. Blocks of different families never do.

    Prefix::parse

    fn Prefix::parse(text : StringView, strict? : Bool) -> Prefix raise Refused

    Read a prefix from address/length.

    With strict on, bits set below the mask are refused; with it off they are cleared, which is how a block is usually written by hand.

    test {
    let subnet = @moonip.Prefix::parse("192.0.2.0/24")
    assert_eq(subnet.to_string(), "192.0.2.0/24")
    assert_eq(
    @moonip.Prefix::parse("192.0.2.5/24", strict=false).to_string(),
    "192.0.2.0/24",
    )
    }

    Prefix::subnets

    fn Prefix::subnets(self : Prefix, bits~ : Int) -> Iter[Prefix] raise Refused

    The blocks of length bits that together make up this one, in ascending order.

    test {
    let halves = @moonip.Prefix::parse("10.0.0.0/8").subnets(bits=9).collect()
    assert_eq(halves.length(), 2)
    assert_eq(halves[1].to_string(), "10.128.0.0/9")
    }

    Prefix::supernet

    fn Prefix::supernet(self : Prefix, bits~ : Int) -> Prefix raise Refused

    The block of length bits that contains this one.

    test {
    assert_eq(
    @moonip.Prefix::parse("192.0.2.0/24").supernet(bits=16).to_string(),
    "192.0.0.0/16",
    )
    }

    Prefix::to_repr

    Prefix::to_string

    fn Prefix::to_string(self : Prefix) -> String

    collapse

    fn collapse(blocks : ArrayView[Prefix]) -> Array[Prefix]

    The fewest blocks that hold exactly the same addresses as the ones given.

    Blocks of different families are collapsed separately and the IPv4 ones come first, so the answer is always exact even though a mix of families has no single ordering.

    test {
    let halves = [
    @moonip.Prefix::parse("192.0.2.0/25"),
    @moonip.Prefix::parse("192.0.2.128/25"),
    ]
    let whole = @moonip.collapse(halves)
    assert_eq(whole.length(), 1)
    assert_eq(whole[0].to_string(), "192.0.2.0/24")
    }

    loopback4

    let loopback4 : Addr

    127.0.0.1, the near end of the loopback range.

    loopback6

    let loopback6 : Addr

    ::1, the only loopback address IPv6 has.

    summarize

    fn summarize(first : Addr, last : Addr) -> Array[Prefix] raise Refused

    The fewest blocks that hold exactly the addresses from first to last.

    test {
    let blocks = @moonip.summarize(
    @moonip.Addr::parse("192.0.2.0"),
    @moonip.Addr::parse("192.0.2.130"),
    )
    assert_eq(blocks.length(), 3)
    assert_eq(blocks[0].to_string(), "192.0.2.0/25")
    }

    unspecified4

    let unspecified4 : Addr

    0.0.0.0, the address that means "this host, no address yet".

    unspecified6

    let unspecified6 : Addr

    ::, the same idea in IPv6.