diff --git a/flake.lock b/flake.lock index cc6aa27..e2f5d8d 100644 --- a/flake.lock +++ b/flake.lock @@ -75,26 +75,13 @@ "type": "github" } }, - "lib-net": { - "flake": false, - "locked": { - "lastModified": 1596309860, - "narHash": "sha256-izAzepR/6cDvnRfaa2ceSolMLMwqzQB5x9q62aR5J2g=", - "type": "tarball", - "url": "https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba/archive/3885f7cd9ed0a746a9d675da6f265d41e9fd6704.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba/archive/3885f7cd9ed0a746a9d675da6f265d41e9fd6704.tar.gz" - } - }, "nixpkgs": { "locked": { - "lastModified": 1737885589, - "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", + "lastModified": 1739214665, + "narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", + "rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a", "type": "github" }, "original": { @@ -142,7 +129,6 @@ "inputs": { "devshell": "devshell", "flake-parts": "flake-parts", - "lib-net": "lib-net", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } diff --git a/flake.nix b/flake.nix index 744f523..aa4acff 100644 --- a/flake.nix +++ b/flake.nix @@ -9,11 +9,6 @@ flake-parts.url = "github:hercules-ci/flake-parts"; - lib-net = { - url = "https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba/archive/3885f7cd9ed0a746a9d675da6f265d41e9fd6704.tar.gz"; - flake = false; - }; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; pre-commit-hooks = { diff --git a/lib/misc.nix b/lib/misc.nix index 70d667b..dea4727 100644 --- a/lib/misc.nix +++ b/lib/misc.nix @@ -1,43 +1,37 @@ -_inputs: _final: prev: let - inherit - (prev.lib) +_inputs: _final: prev: +let + inherit (prev.lib) filter foldl' genAttrs - genList mergeAttrs mkMerge - stringToCharacters substring unique + stringToCharacters ; # Counts how often each element occurrs in xs. # Elements must be strings. - countOccurrences = - foldl' - (acc: x: acc // {${x} = (acc.${x} or 0) + 1;}) - {}; + countOccurrences = foldl' (acc: x: acc // { ${x} = (acc.${x} or 0) + 1; }) { }; # Returns all elements in xs that occur at least twice - duplicates = xs: let - occurrences = countOccurrences xs; - in + duplicates = + xs: + let + occurrences = countOccurrences xs; + in unique (filter (x: occurrences.${x} > 1) xs); # Concatenates all given attrsets as if calling a // b in order. - concatAttrs = foldl' mergeAttrs {}; + concatAttrs = foldl' mergeAttrs { }; # True if the path or string starts with / isAbsolutePath = x: substring 0 1 x == "/"; # Merges all given attributes from the given attrsets using mkMerge. # Useful to merge several top-level configs in a module. - mergeToplevelConfigs = keys: attrs: - genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs)); - - # Calculates base^exp, but careful, this overflows for results > 2^62 - pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp); + mergeToplevelConfigs = keys: attrs: genAttrs keys (attr: mkMerge (map (x: x.${attr} or { }) attrs)); hexLiteralValues = { "0" = 0; @@ -66,19 +60,18 @@ _inputs: _final: prev: let # Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63), # after that the sign bit will overflow. - hexToDec = v: foldl' (acc: x: acc * 16 + hexLiteralValues.${x}) 0 (stringToCharacters v); -in { - lib = - prev.lib - // { - inherit - concatAttrs - countOccurrences - duplicates - hexToDec - isAbsolutePath - mergeToplevelConfigs - pow - ; - }; + hexToDec = + v: foldl' (acc: x: (builtins.bitShiftLeft acc 4) + hexLiteralValues.${x}) 0 (stringToCharacters v); +in +{ + lib = prev.lib // { + inherit + hexToDec + concatAttrs + countOccurrences + duplicates + isAbsolutePath + mergeToplevelConfigs + ; + }; } diff --git a/lib/net.nix b/lib/net.nix index 3713f09..4ce66cc 100644 --- a/lib/net.nix +++ b/lib/net.nix @@ -1,6 +1,6 @@ -inputs: _final: prev: let - inherit - (prev.lib) +inputs: _final: prev: +let + inherit (prev.lib) all any assertMsg @@ -19,27 +19,27 @@ inputs: _final: prev: let substring unique warnIf - ; - - # From misc.nix - inherit - (prev.lib) hexToDec - pow ; # IP address math library # https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba # Plus some extensions by us libNet = - (import "${inputs.lib-net}/net.nix" { + (import ./netu.nix { inherit (inputs.nixpkgs) lib; - }) - .lib - .net; -in { + }).lib; +in +{ lib = recursiveUpdate prev.lib { - net = recursiveUpdate (removeAttrs libNet ["types"]) { + inherit (libNet) + arithmetic + typechecks + bit + implementations + parsers + ; + net = recursiveUpdate (removeAttrs libNet.net [ "types" ]) { cidr = rec { # host :: (ip | mac | integer) -> cidr -> ip # @@ -58,11 +58,13 @@ in { # "192.168.1.0" # > net.cidr.host (-257) "192.168.1.0/24" # - host = i: n: let - cap = libNet.cidr.capacity n; - in + host = + i: n: + let + cap = libNet.net.cidr.capacity n; + in assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}"; - libNet.cidr.host i n; + libNet.net.cidr.host i n; # hostCidr :: (ip | mac | integer) -> cidr -> cidr # # Returns the nth host in the given cidr range (like cidr.host) @@ -72,7 +74,7 @@ in { # # > net.cidr.hostCidr 2 "192.168.1.0/24" # "192.168.1.2/24" - hostCidr = n: x: "${libNet.cidr.host n x}/${toString (libNet.cidr.length x)}"; + hostCidr = n: x: "${libNet.net.cidr.host n x}/${toString (libNet.net.cidr.length x)}"; # ip :: (cidr | ip) -> ip # # Returns just the ip part of the cidr. @@ -93,7 +95,7 @@ in { # # > net.cidr.canonicalize "192.168.1.100/24" # "192.168.1.0/24" - canonicalize = x: libNet.cidr.make (libNet.cidr.length x) (ip x); + canonicalize = x: libNet.net.cidr.make (libNet.net.cidr.length x) (ip x); # mergev4 :: [cidrv4 | ipv4] -> (cidrv4 | null) # # Returns the smallest cidr network that includes all given networks. @@ -103,37 +105,30 @@ in { # # > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"] # "192.168.0.0/21" - mergev4 = addrs_: let - # Append /32 if necessary - addrs = map (x: - if hasInfix "/" x - then x - else "${x}/32") - addrs_; - # The smallest occurring length is the first we need to start checking, since - # any greater cidr length represents a smaller address range which - # wouldn't contain all of the original addresses. - startLength = foldl' min 32 (map libNet.cidr.length addrs); - possibleLengths = reverseList (range 0 startLength); - # The first ip address will be "expanded" in cidr length until it covers all other - # used addresses. - firstIp = ip (head addrs); - # Return the first (i.e. greatest length -> smallest prefix) cidr length - # in the list that covers all used addresses - bestLength = head (filter - # All given addresses must be contained by the generated address. - (len: - all (x: - libNet.cidr.contains - (ip x) - (libNet.cidr.make len firstIp)) - addrs) - possibleLengths); - in + mergev4 = + addrs_: + let + # Append /32 if necessary + addrs = map (x: if hasInfix "/" x then x else "${x}/32") addrs_; + # The smallest occurring length is the first we need to start checking, since + # any greater cidr length represents a smaller address range which + # wouldn't contain all of the original addresses. + startLength = foldl' min 32 (map libNet.net.cidr.length addrs); + possibleLengths = reverseList (range 0 startLength); + # The first ip address will be "expanded" in cidr length until it covers all other + # used addresses. + firstIp = ip (head addrs); + # Return the first (i.e. greatest length -> smallest prefix) cidr length + # in the list that covers all used addresses + bestLength = head ( + filter + # All given addresses must be contained by the generated address. + (len: all (x: libNet.net.cidr.contains (ip x) (libNet.net.cidr.make len firstIp)) addrs) + possibleLengths + ); + in assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses"; - if addrs == [] - then null - else libNet.cidr.make bestLength firstIp; + if addrs == [ ] then null else libNet.net.cidr.make bestLength firstIp; # mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null) # # Returns the smallest cidr network that includes all given networks. @@ -143,128 +138,44 @@ in { # # > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"] # "fd00:c000::/18" - mergev6 = addrs_: let - # Append /128 if necessary - addrs = map (x: - if hasInfix "/" x - then x - else "${x}/128") - addrs_; - # The smallest occurring length is the first we need to start checking, since - # any greater cidr length represents a smaller address range which - # wouldn't contain all of the original addresses. - startLength = foldl' min 128 (map libNet.cidr.length addrs); - possibleLengths = reverseList (range 0 startLength); - # The first ip address will be "expanded" in cidr length until it covers all other - # used addresses. - firstIp = ip (head addrs); - # Return the first (i.e. greatest length -> smallest prefix) cidr length - # in the list that covers all used addresses - bestLength = head (filter - # All given addresses must be contained by the generated address. - (len: - all (x: - libNet.cidr.contains - (ip x) - (libNet.cidr.make len firstIp)) - addrs) - possibleLengths); - in + mergev6 = + addrs_: + let + # Append /128 if necessary + addrs = map (x: if hasInfix "/" x then x else "${x}/128") addrs_; + # The smallest occurring length is the first we need to start checking, since + # any greater cidr length represents a smaller address range which + # wouldn't contain all of the original addresses. + startLength = foldl' min 128 (map libNet.net.cidr.length addrs); + possibleLengths = reverseList (range 0 startLength); + # The first ip address will be "expanded" in cidr length until it covers all other + # used addresses. + firstIp = ip (head addrs); + # Return the first (i.e. greatest length -> smallest prefix) cidr length + # in the list that covers all used addresses + bestLength = head ( + filter + # All given addresses must be contained by the generated address. + (len: all (x: libNet.net.cidr.contains (ip x) (libNet.net.cidr.make len firstIp)) addrs) + possibleLengths + ); + in assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses"; - if addrs == [] - then null - else libNet.cidr.make bestLength firstIp; + if addrs == [ ] then null else libNet.net.cidr.make bestLength firstIp; # merge :: [cidr] -> { cidrv4 = (cidrv4 | null); cidrv6 = (cidrv4 | null); } # # Returns the smallest cidr network that includes all given networks, # but yields two separate result for all given ipv4 and ipv6 addresses. # Equivalent to calling mergev4 and mergev6 on a partition individually. - merge = addrs: let - v4_and_v6 = partition (hasInfix ":") addrs; - in { - cidrv4 = mergev4 v4_and_v6.wrong; - cidrv6 = mergev6 v4_and_v6.right; - }; - # assignIps :: cidr -> [int | ip] -> [string] -> [ip] - # - # Assigns a semi-stable ip address from the given cidr network to each hostname. - # The algorithm is based on hashing (abusing sha256) with linear probing. - # The order of hosts doesn't matter. No ip (or offset) from the reserved list - # will be assigned. The network address and broadcast address will always be reserved - # automatically. - # - # Examples: - # - # > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c"] - # { a = "192.168.100.202"; b = "192.168.100.74"; c = "192.168.100.226"; } - # - # > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c" "a-new-elem"] - # { a = "192.168.100.202"; a-new-elem = "192.168.100.88"; b = "192.168.100.74"; c = "192.168.100.226"; } - # - # > net.cidr.assignIps "192.168.100.1/24" [202 "192.168.100.74"] ["a" "b" "c"] - # { a = "192.168.100.203"; b = "192.168.100.75"; c = "192.168.100.226"; } - assignIps = net: reserved: hosts: let - cidrSize = libNet.cidr.size net; - capacity = libNet.cidr.capacity net; - # The base address of the network. Used to convert ip-based reservations to offsets - baseAddr = host 0 net; - # Reserve some values for the network, host and broadcast address. - # The network and broadcast address should never be used, and we - # want to reserve the host address for the host. We also convert - # any ips to offsets here. - init = unique ( - [0 (capacity - 1)] - ++ flip map reserved (x: - if builtins.typeOf x == "int" - then x - else -(libNet.ip.diff baseAddr x)) - ); - nHosts = builtins.length hosts; - nInit = builtins.length init; - # Pre-sort all hosts, to ensure ordering invariance - sortedHosts = - warnIf - ((nInit + nHosts) > 0.3 * capacity) - "assignIps: hash stability may be degraded since utilization is >30%" - (builtins.sort builtins.lessThan hosts); - # Generates a hash (i.e. offset value) for a given hostname - hashElem = x: - builtins.bitAnd (capacity - 1) - (hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x))); - # Do linear probing. Returns the first unused value at or after the given value. - probe = avoid: value: - if elem value avoid - # TODO lib.mod - # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue - # class of x modulo 2^n, so we can use bitAnd instead. - then probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) - else value; - # Hash a new element and avoid assigning any existing values. - assignOne = { - assigned, - used, - }: x: let - value = probe used (hashElem x); - in { - assigned = - assigned - // { - ${x} = host value net; - }; - used = [value] ++ used; + merge = + addrs: + let + v4_and_v6 = partition (hasInfix ":") addrs; + in + { + cidrv4 = mergev4 v4_and_v6.wrong; + cidrv6 = mergev6 v4_and_v6.right; }; - in - assert assertMsg (cidrSize >= 2 && cidrSize <= 62) - "assignIps: cidrSize=${toString cidrSize} is not in [2, 62]."; - assert assertMsg (nHosts <= capacity - nInit) - "assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; - # Assign an ip in the subnet to each element, in order - (foldl' assignOne { - assigned = {}; - used = init; - } - sortedHosts) - .assigned; }; ip = rec { # Checks whether the given address (with or without cidr notation) is an ipv4 address. @@ -275,11 +186,14 @@ in { mac = { # Adds offset to the given base address and ensures the result is in # a locally administered range by replacing the second nibble with a 2. - addPrivate = base: offset: let - added = libNet.mac.add base offset; - pre = substring 0 1 added; - suf = substring 2 (-1) added; - in "${pre}2${suf}"; + addPrivate = + base: offset: + let + added = libNet.net.mac.add base offset; + pre = substring 0 1 added; + suf = substring 2 (-1) added; + in + "${pre}2${suf}"; # assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac] # # Assigns a semi-stable MAC address starting in [base, base + 2^size) to each hostname. @@ -297,65 +211,66 @@ in { # # > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"] # { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; } - assignMacs = base: size: reserved: hosts: let - capacity = pow 2 size; - baseAsInt = libNet.mac.diff base "00:00:00:00:00:00"; - init = unique ( - flip map reserved (x: - if builtins.typeOf x == "int" - then x - else libNet.mac.diff x base) - ); - nHosts = builtins.length hosts; - nInit = builtins.length init; - # Pre-sort all hosts, to ensure ordering invariance - sortedHosts = - warnIf - ((nInit + nHosts) > 0.3 * capacity) - "assignMacs: hash stability may be degraded since utilization is >30%" - (builtins.sort builtins.lessThan hosts); - # Generates a hash (i.e. offset value) for a given hostname - hashElem = x: - builtins.bitAnd (capacity - 1) - (hexToDec (substring 0 16 (builtins.hashString "sha256" x))); - # Do linear probing. Returns the first unused value at or after the given value. - probe = avoid: value: - if elem value avoid - # TODO lib.mod - # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue - # class of x modulo 2^n, so we can use bitAnd instead. - then probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) - else value; - # Hash a new element and avoid assigning any existing values. - assignOne = { - assigned, - used, - }: x: let - value = probe used (hashElem x); - in { - assigned = - assigned - // { - ${x} = libNet.mac.add value base; + assignMacs = + base: size: reserved: hosts: + let + capacity = builtins.bitShiftLeft 1 size; + baseAsInt = libNet.net.mac.diff base "00:00:00:00:00:00"; + init = unique ( + flip map reserved (x: if builtins.typeOf x == "int" then x else libNet.net.mac.diff x base) + ); + nHosts = builtins.length hosts; + nInit = builtins.length init; + # Pre-sort all hosts, to ensure ordering invariance + sortedHosts = + warnIf ((nInit + nHosts) > 0.3 * capacity) + "assignMacs: hash stability may be degraded since utilization is >30%" + (builtins.sort builtins.lessThan hosts); + # Generates a hash (i.e. offset value) for a given hostname + hashElem = + x: builtins.bitAnd (capacity - 1) (hexToDec (substring 0 16 (builtins.hashString "sha256" x))); + # Do linear probing. Returns the first unused value at or after the given value. + probe = + avoid: value: + if + elem value avoid + # TODO lib.mod + # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue + # class of x modulo 2^n, so we can use bitAnd instead. + then + probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) + else + value; + # Hash a new element and avoid assigning any existing values. + assignOne = + { + assigned, + used, + }: + x: + let + value = probe used (hashElem x); + in + { + assigned = assigned // { + ${x} = libNet.net.mac.add value base; + }; + used = [ value ] ++ used; }; - used = [value] ++ used; - }; - in - assert assertMsg (size >= 2 && size <= 62) - "assignMacs: size=${toString size} is not in [2, 62]."; - assert assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0) - "assignMacs: the size=${toString size} least significant bits of the base mac address must be 0."; + in + assert assertMsg (size >= 2 && size <= 62) "assignMacs: size=${toString size} is not in [2, 62]."; + assert assertMsg ( + builtins.bitAnd (capacity - 1) baseAsInt == 0 + ) "assignMacs: the size=${toString size} least significant bits of the base mac address must be 0."; assert assertMsg (nHosts <= capacity - nInit) - "assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; + "assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; # Assign an ip in the subnet to each element, in order - (foldl' assignOne { - assigned = {}; - used = init; - } - sortedHosts) - .assigned; + (foldl' assignOne { + assigned = { }; + used = init; + } sortedHosts).assigned; }; }; - types.net = libNet.types; + types.net = libNet.net.types; }; } diff --git a/lib/netu.nix b/lib/netu.nix index a6df65e..4436796 100644 --- a/lib/netu.nix +++ b/lib/netu.nix @@ -2,13 +2,10 @@ lib ? null, ... }: - let - net = { ip = { - # add :: (ip | mac | integer) -> ip -> ip # # Examples: @@ -96,7 +93,6 @@ let }; mac = { - # add :: (ip | mac | integer) -> mac -> mac # # Examples: @@ -334,7 +330,6 @@ let cidr' = typechecks.cidr function "cidr" cidr; in builders.cidr (implementations.cidr.subnet length' netnum' cidr'); - }; } // ( @@ -344,7 +339,6 @@ let { types = let - mkParsedOptionType = { name, @@ -385,10 +379,8 @@ let // { description = type.description + " in ${builtins.concatStringsSep " or " cidrs}"; }; - in rec { - ip = mkParsedOptionType { name = "ip"; description = "IPv4 or IPv6 address"; @@ -449,7 +441,6 @@ let parser = parsers.mac; builder = builders.mac; }; - }; } ); @@ -460,20 +451,23 @@ let bit = let - shift = - n: x: - if n < 0 then - x * math.pow 2 (-n) + left = + a: b: + if a >= 64 then + 0 + else if a < 0 then + right (-a) b else - let - safeDiv = n: d: if d == 0 then 0 else n / d; - d = math.pow 2 n; - in - if x < 0 then not (safeDiv (not x) d) else safeDiv x d; + builtins.bitShiftLeft b a; - left = n: shift (-n); - - right = shift; + right = + a: b: + if a >= 64 then + 0 + else if a < 0 then + left (-a) b + else + builtins.bitShiftRight b a; and = builtins.bitAnd; @@ -505,20 +499,10 @@ let clamp = a: b: c: max a (min b c); - - pow = - x: n: - if n == 0 then - 1 - else if bit.and n 1 != 0 then - x * pow (x * x) ((n - 1) / 2) - else - pow (x * x) (n / 2); }; parsers = let - # fmap :: (a -> b) -> parser a -> parser b fmap = f: ma: bind ma (a: pure (f a)); @@ -766,7 +750,6 @@ let afterDoubleColon = alt ipv4' ( liftA2 list.cons hextet (alt (then_ colon afterDoubleColon) (pure [ ])) ); - in bind (alt (then_ (string "::") (doubleColon 0)) (part 0)) fromHextets; @@ -791,7 +774,6 @@ let }; in liftA6 fromOctets octet octet' octet' octet' octet' octet'; - in { ipv4 = run ipv4; @@ -806,7 +788,6 @@ let builders = let - ipv4 = address: let @@ -832,7 +813,6 @@ let ipv6 = address: let - digits = "0123456789abcdef"; toHexString = @@ -843,7 +823,6 @@ let prefix = if rest == 0 then "" else toHexString rest; in "${prefix}${builtins.substring current 1 digits}"; - in if (with address.ipv6; a == 0 && b == 0 && c == 0 && d > 65535) then "::${ipv4 { ipv4 = address.ipv6.d; }}" @@ -851,7 +830,6 @@ let "::ffff:${ipv4 { ipv4 = address.ipv6.d; }}" else let - a = bit.right 16 address.ipv6.a; b = bit.mask 16 address.ipv6.a; c = bit.right 16 address.ipv6.b; @@ -961,7 +939,6 @@ let f = bit.mask 8 (bit.right 0 address.mac); in "${octet a}:${octet b}:${octet c}:${octet d}:${octet e}:${octet f}"; - in { inherit @@ -1096,7 +1073,7 @@ let diff = a: b: let - toIPv6 = coerce ({ ipv6.a = 0; }); + toIPv6 = coerce { ipv6.a = 0; }; result = (subtract b (toIPv6 a)).ipv6; max32 = bit.left 32 1 - 1; in @@ -1352,7 +1329,6 @@ let typechecks = let - fail = description: function: argument: builtins.throw "${function}: ${argument} parameter must be ${description}"; @@ -1369,7 +1345,6 @@ let result = parser input; in if builtins.isNull result then error else result; - in { int = @@ -1385,11 +1360,16 @@ let else meta parsers.numeric "an integer or IPv4, IPv6 or MAC address" function argument input; }; - in - { lib = { - inherit net; + inherit + arithmetic + net + typechecks + parsers + implementations + bit + ; }; }