README.md

# README

![Pfx test](https://github.com/hertogp/pfx/actions/workflows/elixir.yml/badge.svg)

[Online Pfx Documentation](https://hexdocs.pm/pfx).

<!-- @MODULEDOC -->

Functions to make working with prefixes easier, especially IP prefixes (IPv4 and
IPv6).

`Pfx` defines a prefix as a struct with a number of `bits` and a maximum
`maxlen` length.  Hence a `Pfx` struct represents some domain-specific value,
like an IPv4/6 address or network, a MAC address, a MAC OUI range or something
else entirely.

A `Pfx` struct can be created from:
1. a `t:bitstring/0` and a `t:non_neg_integer/0` for the maximum length,
2. a `t:Pfx.ip_address/0`,
3. a `t:Pfx.ip_prefix/0`, or
4. a `t:binary/0` denoting an IP prefix in CIDR-notation or an EUI-48/64
   address

The first option allows for the creation of any sort of prefix, the second and
third option yield either an IPv4 or IPv6 prefix.  Lastly, strings can be used
to create either IPv4, IPv6, EUI-48 or EUI-64 prefixes.

Several functions, like `Pfx.unique_local?/1` are more IP oriented, and are
included along with the more generic `Pfx` functions (like `Pfx.cut/3`) in
order to have one module to rule them all.

Functions generally accept all representations and, if possible,  yield their result in the same fashion:

    iex> hosts("10.10.10.0/30")
    ["10.10.10.0", "10.10.10.1", "10.10.10.2", "10.10.10.3"]

    iex> hosts({{10, 10, 10, 0}, 30})
    [
      {{10, 10, 10, 0}, 32},
      {{10, 10, 10, 1}, 32},
      {{10, 10, 10, 2}, 32},
      {{10, 10, 10, 3}, 32}
    ]

    iex> hosts(%Pfx{bits: <<10, 10, 10, 0::6>>, maxlen: 32})
    [
      %Pfx{bits: <<10, 10, 10, 0>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 1>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 2>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 3>>, maxlen: 32},
    ]

    # adopt representation of first argument
    iex> band({10, 10, 10, 1}, "255.255.255.0")
    {10, 10, 10, 0}

    iex> multicast?("ff00::1")
    true

    # MAC OUI
    iex> keep("aa:bb:cc:dd:ee:ff", 24)
    "AA-BB-CC-00-00-00/24"

## Validity

The `Pfx.new/2` function will silently clip the provided `bits`-string to
`maxlen`-bits when needed, since a `Pfx` struct named `pfx` is valid, iff:
- `bit_size(pfx.bits)` <= `pfx.maxlen`, and where
- `pfx.maxlen` is a `t:non_neg_integer/0`

Keep that in mind when instantiating directly or updating a `Pfx`, otherwise
functions will choke on it.

Same goes for `t:Pfx.ip_address/0` representations, which must be a valid
`:inet.ip_address()`, representing either an IPv4 or IPv6 address through a
tuple of four `8`-bit wide numbers or eight `16`-bit wide numbers.

If used as the first element in a `t:Pfx.ip_prefix/0` tuple, the second element
is interpreted as the mask, used to clip the bitstring when creating the `Pfx`
struct.  IPv4 masks must be in range `0..32` and IPv6 masks in range `0..128`.
The resulting `Pfx` will have its `maxlen` set to `32` for IPv4 tuples and
`128` for IPv6 tuples.

Last but not least, a binary is interpreted as a string either in CIDR-notation
for some IPv4/IPv6 address or prefix or a representation of an EUI-48 or EUI-64
address or prefix.


## Ancient tradition

`Pfx.new/1` accepts CIDR-strings which are ultimately processed using erlang's
`:inet.parse_address` which, at the time of writing, still honors the ancient
linux tradition of injecting zero's when presented with less than four IPv4
digits in a CIDR string.

    # "d" -> "0.0.0.d"
    iex> new("10") |> format()
    "0.0.0.10"

    iex> new("10/8") |> format()
    "0.0.0.0/8"

    # "d1.d2" -> "d1.0.0.d2"
    iex> new("10.10") |> format()
    "10.0.0.10"

    iex> new("10.10/16") |> format()
    "10.0.0.0/16"

    # "d1.d2.d3" -> "d1.d2.0.d3"
    iex> new("10.10.10") |> format()
    "10.10.0.10"

    iex> new("10.10.10/24") |> format()
    "10.10.0.0/24"

Bottom line: never go short, you may be unpleasantly surprised.

## EUI-64's

Since a string is first parsed as an IP prefix, EUI-64's like
"11:22:33:44:55:66:77:88" will come out as an IPv6 prefix with their `maxlen`
property set to `128`.  So, when in doubt, parse EUI's with `Pfx.from_mac/1`.
That also supports the tuple formats.

    # turns it into IPv6
    iex> new("11:22:33:44:55:66:77:88")
    %Pfx{bits: <<0x11::16, 0x22::16, 0x33::16, 0x44::16, 0x55::16, 0x66::16, 0x77::16, 0x88::16>>, maxlen: 128}

    # from_mac has 'context'
    iex> from_mac("11:22:33:44:55:66:77:88")
    %Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}

    # from {digits}
    iex> from_mac({0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
    %Pfx{bits: <<0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88>>, maxlen: 64}

    # from {{digits}, len}, keeping first 3 bytes
    iex> from_mac({{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, 24})
    %Pfx{bits: <<0x11, 0x22, 0x33>>, maxlen: 64}


## Enumeration

A `t:Pfx.t/0` implements the `Enumerable` protocol:

    iex> for ip <- %Pfx{bits: <<1, 2, 3, 0::6>>, maxlen: 32}, do: ip
    [
      %Pfx{bits: <<1, 2, 3, 0>>, maxlen: 32},
      %Pfx{bits: <<1, 2, 3, 1>>, maxlen: 32},
      %Pfx{bits: <<1, 2, 3, 2>>, maxlen: 32},
      %Pfx{bits: <<1, 2, 3, 3>>, maxlen: 32},
    ]


## String.Chars

`t:Pfx.t/0` implements the `String.Chars` protocol with some defaults for
prefixes that formats prefixes with:
- `maxlen: 32` as an IPv4 CIDR string,
- `maxlen: 48` as a EUI-48 address string and
- `maxlen: 64` as a EUI-64 address string
- `maxlen: 128` as an IPv6 CIDR string

Other `maxlen`'s will simply come out as a series of 8-bit numbers joined by "."
followed by `/num_of_bits`. The latter is omitted if equal to `pfx.bits`
length.

If other formatting is required, use the `Pfx.format/2` function, which takes
some options that help shape the string representation for a `Pfx` struct.

    iex> "#{%Pfx{bits: <<10, 11, 12>>, maxlen: 32}}"
    "10.11.12.0/24"

    iex> "#{new(<<44252::16, 6518::16>>, 128)}"
    "acdc:1976:0:0:0:0:0:0/32"

    iex> "#{%Pfx{bits: <<0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6>>, maxlen: 48}}"
    "A1-B2-C3-D4-E5-F6"

    iex> "#{new(<<1, 2, 3, 4, 5>>, 64)}"
    "01-02-03-04-05-00-00-00/40"

    # an ip4 address formatted as a string of bits
    iex> new(<<1, 2, 3, 4>>, 32) |> format(width: 1, unit: 8)
    "00000001.00000010.00000011.00000100"

    # the enumeration example earlier, could also read:
    iex> for ip <- new("1.2.3.0/30"), do: "#{ip}"
    [
      "1.2.3.0",
      "1.2.3.1",
      "1.2.3.2",
      "1.2.3.3"
    ]


## Limitations

A lot of `Pfx`-functions convert the `Pfx.bits` bitstring to an integer using
`Pfx.cast/1`, before performing some, often `Bitwise`-related, calculation on
them.  Luckily [Elixir](https://elixir-lang.org/docs.html) can handle pretty
large numbers which seem mostly limited by the available system memory.

Other functions, like `Pfx.digits/2` return a tuple with numbers and are so
limited by the maximum number of elements in a tuple (~16M+).

So if you're taking this somewhere far, far away, heed these limitations before
take off.

Also, everything is done in Elixir with no extra, external dependencies.
Usually fast enough, but if you really feel the need for speed, you might want
to look elsewhere.

Anyway, enough downplay, here are some more examples.

## Examples

    # IANA's OUI range 00-00-5e-xx-xx-xx
    iex> new("00-00-5e-00-00-00/24")
    %Pfx{bits: <<0, 0, 94>>, maxlen: 48}

    # IANA's assignment for the VRRP MAC address range 00-00-5e-00-01-{VRID}
    iex> vrrp_mac_range = new("00-00-5e-00-01-00/40")
    %Pfx{bits: <<0, 0, 94, 0, 1>>, maxlen: 48}
    iex>
    iex> vrrp_mac = new("00-00-5e-00-01-0f")
    %Pfx{bits: <<0, 0, 94, 0, 1, 15>>, maxlen: 48}
    iex>
    iex> member?(vrrp_mac, vrrp_mac_range)
    true
    iex> cut(vrrp_mac, 47, -8) |> cast()
    15

    # IPv4 examples
    iex> new("10.10.10.0/24")
    %Pfx{bits: <<10, 10, 10>>, maxlen: 32}

    iex> new({10, 10, 10, 10})
    %Pfx{bits: <<10, 10, 10, 10>>, maxlen: 32}

    iex> new({{10, 10, 10, 10}, 24})
    %Pfx{bits: <<10, 10, 10>>, maxlen: 32}

    iex> new(<<10, 10, 10>>, 32)
    %Pfx{bits: <<10, 10, 10>>, maxlen: 32}

    # IPv6 examples
    iex> new(<<44252::16, 6518::16>>, 128)
    %Pfx{bits: <<0xACDC::16, 0x1976::16>>, maxlen: 128}

    iex> new("acdc:1976::/32")
    %Pfx{bits: <<44252::16, 6518::16>>, maxlen: 128}

    iex> new({{44252, 6518, 0, 0, 0, 0, 0, 0}, 32})
    %Pfx{bits: <<0xACDC::16, 0x1976::16>>, maxlen: 128}

    # EUI-48 examples
    iex> new("aa:bb:cc:dd:ee:ff")
    %Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}

    iex> new("aa-bb-cc-dd-ee-ff")
    %Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}

    iex> new("aabb.ccdd.eeff")
    %Pfx{bits: <<0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 48}

    # EUI-64 examples
    iex> new("11-22-aa-bb-cc-dd-ee-ff")
    %Pfx{bits: <<0x11, 0x22, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 64}

    # for EUI-64's with ':' punctuation, use:
    iex> from_mac("11:22:aa:bb:cc:dd:ee:ff")
    %Pfx{bits: <<0x11, 0x22, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff>>, maxlen: 64}


Functions are sometimes IP specific, like:

    iex> dns_ptr("acdc:1975::b1ba:2021")
    "1.2.0.2.a.b.1.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.5.7.9.1.c.d.c.a.ip6.arpa"

    iex> teredo_decode("2001:0000:4136:e378:8000:63bf:3fff:fdd2")
    %{
      server: "65.54.227.120",
      client: "192.0.2.45",
      port: 40000,
      flags: {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      prefix: "2001:0000:4136:e378:8000:63bf:3fff:fdd2"
    }

But most of the times, functions have generic names, since they apply to all
sorts of prefixes, e.g.

    iex> partition(%Pfx{bits: <<10, 10, 10>>, maxlen: 32}, 26)
    [
      %Pfx{bits: <<10, 10, 10, 0::size(2)>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 1::size(2)>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 2::size(2)>>, maxlen: 32},
      %Pfx{bits: <<10, 10, 10, 3::size(2)>>, maxlen: 32}
    ]

The `Pfx.new/1` and `Pfx.new/2` always return a `t:Pfx.t/0` struct, but most
other functions will return their results in the same representation they were
given.  So the above could also be done as:

    iex> partition("10.10.10.0/24", 26)
    [ "10.10.10.0/26",
      "10.10.10.64/26",
      "10.10.10.128/26",
      "10.10.10.192/26"
    ]

    # or
    iex> partition({{10, 10, 10, 0}, 24}, 26)
    [ {{10, 10, 10, 0}, 26},
      {{10, 10, 10, 64}, 26},
      {{10, 10, 10, 128}, 26},
      {{10, 10, 10, 192}, 26}
    ]


<!-- @MODULEDOC -->

## Installation

[Pfx](https://hexdocs.pm/pfx/Pfx.html) can be installed by adding `pfx` to your
list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:pfx, "~> 0.3.0"}
  ]
end
```