Functional Weave
Code in Python

net.ipv4

Parse, format and check dotted-quad IPv4 addresses, refusing leading zeros, octal, hex and short forms.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 39 tests, run in TypeScript, Python and Rust.parseIpv4 17 · formatIpv4 11 · isIpv4 11

What it does

Parse dotted-quad IPv4 text to an unsigned 32-bit number, format a number back to text, and check text without raising.

## Why so strict

The functions

A group: 3 functions that work together, each in its own file, each pinned by its own tests in TypeScript, Python and Rust. A project can install only the ones it calls.

  1. parse_ipv4 (text: string) -> int
  2. format_ipv4 (value: int) -> string
  3. is_ipv4 (text: string) -> bool

Once installed, your code imports each one from the group's module.

parse_ipv4 throws on bad input 17 tests

def parse_ipv4(text: str) -> int
textstringfour decimal octets 0-255 separated by dots; no leading zeros, signs or whitespace
returnsintthe address as an unsigned 32-bit number, 0 to 4294967295

For example

  • parse_ipv4(192.168.1.1) → 3,232,235,777 a private address: 192*2^24 + 168*2^16 + 1*2^8 + 1
  • parse_ipv4(127.0.0.1) → 2,130,706,433 loopback is 127*2^24 + 1
  • parse_ipv4(1.2.3.4) → 16,909,060 each octet in its own byte
from fune.net.ipv4 import parse_ipv4  # net.ipv4@^1
impl/python/parse_ipv4.py · 45 lines · open · raw
from typing import Optional


def parse_ipv4(text: str) -> int:
    """The address as an unsigned 32-bit number.

    Only the strict dotted quad is accepted. The C library's inet_aton also
    reads "010.1.1.1" as octal 8.1.1.1, "0x7f.1" as hex and "127.1" as
    127.0.0.1; a validator that agrees with one reader and a socket library
    that agrees with the other is how an allow-list gets bypassed, so all of
    those are refused.
    """
    value = ipv4_value(text)
    if value is None:
        raise ValueError('"%s" is not a dotted-quad IPv4 address' % (text,))
    return value


# Exported for is_ipv4 and for capabilities that parse IPv4 text without
# wanting an error; not part of the group's contract.


def ipv4_value(text: object) -> Optional[int]:
    """The address as a number, or None when the text is not a strict dotted quad."""
    if not isinstance(text, str):
        return None
    parts = text.split(".")
    if len(parts) != 4:
        return None
    value = 0
    for part in parts:
        if len(part) < 1 or len(part) > 3:
            return None
        # "0" <= ch <= "9" rather than isdigit(), which accepts other scripts' digits.
        for ch in part:
            if not ("0" <= ch <= "9"):
                return None
        # A leading zero is where octal readers and decimal readers part company.
        if len(part) > 1 and part[0] == "0":
            return None
        octet = int(part)
        if octet > 255:
            return None
        value = value * 256 + octet
    return value

format_ipv4 throws on bad input 11 tests

def format_ipv4(value: int) -> str
valueint0 to 4294967295
returnsstringdotted-quad text, the inverse of parseIpv4

For example

  • format_ipv4(3,232,235,777) → 192.168.1.1 3232235777 is 192.168.1.1
  • format_ipv4(2,130,706,433) → 127.0.0.1 2130706433 is loopback
  • format_ipv4(0) → 0.0.0.0 zero
from fune.net.ipv4 import format_ipv4  # net.ipv4@^1
impl/python/format_ipv4.py · 5 lines · open · raw
def format_ipv4(value: int) -> str:
    """Dotted-quad text for an unsigned 32-bit address: the inverse of parse_ipv4."""
    if isinstance(value, bool) or not isinstance(value, int) or value < 0 or value > 4294967295:
        raise ValueError("IPv4 value must be an integer from 0 to 4294967295, received %r" % (value,))
    return "%d.%d.%d.%d" % ((value >> 24) & 255, (value >> 16) & 255, (value >> 8) & 255, value & 255)

is_ipv4 11 tests

def is_ipv4(text: str) -> bool
textstringany text; true exactly when parseIpv4 would accept it
returnsbool

For example

  • is_ipv4(192.168.0.1) → true an ordinary address
  • is_ipv4(0.0.0.0) → true all zeros
  • is_ipv4(255.255.255.255) → true all ones
from fune.net.ipv4 import is_ipv4  # net.ipv4@^1
impl/python/is_ipv4.py · 6 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from .net_ipv4_parse_ipv4 import ipv4_value  ← parseIpv4, another function of this group · built into the same file, even by a slim install


def is_ipv4(text: str) -> bool:
    """True exactly when parse_ipv4 would accept the text; never raises."""
    return ipv4_value(text) is not None

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add net.ipv4

That builds the whole group. To build only what you call, and whatever it uses inside the group:

fune add net.ipv4 --only parseIpv4
Download for Python net.ipv4-1.0.0-python.fune · 11,790 bytes sha256 315b231c88cfdc9d66e9f47f1c02bc9d73614c43bd69122ee28c0409457eddbe

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./net.ipv4-1.0.0-python.fune, or fetch it from a terminal with fune pull net.ipv4@1.0.0:python.

The whole function, every language, is one file too: net.ipv4-1.0.0.fune, 17,239 bytes, sha256 239a64ddd143396d95f8918b6a17f5f3532ede133b4c5a6482d44307f26f5f5d. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before net.ipv4.parseIpv4
# fune: before net.ipv4.formatIpv4
# fune: before net.ipv4.isIpv4

after — your function gets the result and the arguments, and returns the final result.

# fune: after net.ipv4.parseIpv4
# fune: after net.ipv4.formatIpv4
# fune: after net.ipv4.isIpv4

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside a function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show net.ipv4 --steps.

# fune: step net.ipv4.<fn> after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

parseIpv4 17 tests

CaseArgumentsExpected
a private address: 192*2^24 + 168*2^16 + 1*2^8 + 1 192.168.1.1 → 3,232,235,777
loopback is 127*2^24 + 1 127.0.0.1 → 2,130,706,433
each octet in its own byte 1.2.3.4 → 16,909,060
all zeros is 0 (a lone 0 octet is not a leading zero) 0.0.0.0 → 0
all ones is 2^32 - 1 255.255.255.255 → 4,294,967,295
10.0.0.1 10.0.0.1 → 167,772,161
a leading zero is refused, not read as octal (inet_aton would say 1.2.3.4 or 8.x) 010.1.1.1 → error: is not a dotted-quad IPv4 address
an octet above 255 256.1.1.1 → error: is not a dotted-quad IPv4 address
three parts is not shorthand for 1.2.0.3 1.2.3 → error: is not a dotted-quad IPv4 address
five parts 1.2.3.4.5 → error: is not a dotted-quad IPv4 address
Show the other 7 tests
CaseArgumentsExpected
an empty octet 1..3.4 → error: is not a dotted-quad IPv4 address
hex is refused 0x7f.0.0.1 → error: is not a dotted-quad IPv4 address
a sign is refused +1.2.3.4 → error: is not a dotted-quad IPv4 address
leading whitespace is refused 1.2.3.4 → error: is not a dotted-quad IPv4 address
a trailing newline is refused 1.2.3.4 → error: is not a dotted-quad IPv4 address
a non-ASCII digit is refused ١.2.3.4 → error: is not a dotted-quad IPv4 address
empty text → error: is not a dotted-quad IPv4 address

formatIpv4 11 tests

CaseArgumentsExpected
3232235777 is 192.168.1.1 3,232,235,777 → 192.168.1.1
2130706433 is loopback 2,130,706,433 → 127.0.0.1
zero 0 → 0.0.0.0
the largest address 4,294,967,295 → 255.255.255.255
256 carries into the third octet 256 → 0.0.1.0
2^24 is 1.0.0.0 16,777,216 → 1.0.0.0
above 2^31 does not go negative (signed 32-bit shifts would) 3,221,225,985 → 192.0.2.1
16909060 is 1.2.3.4 16,909,060 → 1.2.3.4
negative -1 → error: IPv4 value must be an integer from 0 to 4294967295
2^32 is out of range 4,294,967,296 → error: IPv4 value must be an integer from 0 to 4294967295
Show the other 1 test
CaseArgumentsExpected
a fraction 1.5 → error: IPv4 value must be an integer from 0 to 4294967295

isIpv4 11 tests

CaseArgumentsExpected
an ordinary address 192.168.0.1 → true
all zeros 0.0.0.0 → true
all ones 255.255.255.255 → true
a double zero octet is a leading zero 00.0.0.0 → false
a short form 127.1 → false
out of range 999.999.999.999 → false
a trailing newline 1.2.3.4 → false
non-ASCII digits ١٢.1.1.1 → false
an IPv6 address ::1 → false
a CIDR block is not an address 10.0.0.0/8 → false
Show the other 1 test
CaseArgumentsExpected
empty text → false

More from the author

There is more than one way to read "an IPv4 address". The C library's `inet_aton` (and so `ping`, `curl` and many socket libraries) accepts `127.1` (meaning 127.0.0.1), `0x7f.0.0.1` (hex) and `010.1.1.1` (octal, so 8.1.1.1). Python's `ipaddress` and most validators refuse them. When the validator and the thing that connects disagree, an allow-list can be bypassed (CVE-2021-29921 was exactly this). This capability accepts only the form RFC 791 writes and everyone reads the same way: exactly four decimal octets 0-255, no leading zeros (a lone `0` is fine), no signs, no whitespace, ASCII digits only.

## Notes

- `parseIpv4` and `formatIpv4` are inverses over 0 to 4294967295. - `isIpv4` never raises; it is true exactly when `parseIpv4` would succeed. - The number is an ordinary integer, so it compares and subtracts correctly in every language (no signed 32-bit wrap above 128.0.0.0). - `ipv4Value` (`ipv4_value`) is exported for sibling capabilities that want "number or null"; it is not part of the contract.

Sources: RFC 791 (Internet Protocol), section 2.3 and 3.2; RFC 6943 section 3.1.1 on the ambiguity of non-dotted-decimal forms.

Files

PathBytes
README.md1,303
impl/python/format_ipv4.py425
impl/python/is_ipv4.py194
impl/python/parse_ipv4.py1,540
impl/rust/format_ipv4.rs899
impl/rust/is_ipv4.rs305
impl/rust/parse_ipv4.rs1,701
impl/typescript/format_ipv4.ts563
impl/typescript/is_ipv4.ts212
impl/typescript/parse_ipv4.ts1,384
vectors.json4,414