factoriolab: init at 3.8.1 (#358014)
[NixPkgs.git] / lib / network / internal.nix
blob3e05be90c5475a78d9d3678b423588eff142ae99
2   lib ? import ../.,
3 }:
4 let
5   inherit (builtins)
6     map
7     match
8     genList
9     length
10     concatMap
11     head
12     toString
13     ;
15   inherit (lib) lists strings trivial;
17   inherit (lib.lists) last;
19   /*
20     IPv6 addresses are 128-bit identifiers. The preferred form is 'x:x:x:x:x:x:x:x',
21     where the 'x's are one to four hexadecimal digits of the eight 16-bit pieces of
22     the address. See RFC 4291.
23   */
24   ipv6Bits = 128;
25   ipv6Pieces = 8; # 'x:x:x:x:x:x:x:x'
26   ipv6PieceBits = 16; # One piece in range from 0 to 0xffff.
27   ipv6PieceMaxValue = 65535; # 2^16 - 1
29 let
30   /**
31     Expand an IPv6 address by removing the "::" compression and padding them
32     with the necessary number of zeros. Converts an address from the string to
33     the list of strings which then can be parsed using `_parseExpanded`.
34     Throws an error when the address is malformed.
36     # Type: String -> [ String ]
38     # Example:
40     ```nix
41     expandIpv6 "2001:DB8::ffff"
42     => ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
43     ```
44   */
45   expandIpv6 =
46     addr:
47     if match "^[0-9A-Fa-f:]+$" addr == null then
48       throw "${addr} contains malformed characters for IPv6 address"
49     else
50       let
51         pieces = strings.splitString ":" addr;
52         piecesNoEmpty = lists.remove "" pieces;
53         piecesNoEmptyLen = length piecesNoEmpty;
54         zeros = genList (_: "0") (ipv6Pieces - piecesNoEmptyLen);
55         hasPrefix = strings.hasPrefix "::" addr;
56         hasSuffix = strings.hasSuffix "::" addr;
57         hasInfix = strings.hasInfix "::" addr;
58       in
59       if addr == "::" then
60         zeros
61       else if
62         let
63           emptyCount = length pieces - piecesNoEmptyLen;
64           emptyExpected =
65             # splitString produces two empty pieces when "::" in the beginning
66             # or in the end, and only one when in the middle of an address.
67             if hasPrefix || hasSuffix then
68               2
69             else if hasInfix then
70               1
71             else
72               0;
73         in
74         emptyCount != emptyExpected
75         || (hasInfix && piecesNoEmptyLen >= ipv6Pieces) # "::" compresses at least one group of zeros.
76         || (!hasInfix && piecesNoEmptyLen != ipv6Pieces)
77       then
78         throw "${addr} is not a valid IPv6 address"
79       # Create a list of 8 elements, filling some of them with zeros depending
80       # on where the "::" was found.
81       else if hasPrefix then
82         zeros ++ piecesNoEmpty
83       else if hasSuffix then
84         piecesNoEmpty ++ zeros
85       else if hasInfix then
86         concatMap (piece: if piece == "" then zeros else [ piece ]) pieces
87       else
88         pieces;
90   /**
91     Parses an expanded IPv6 address (see `expandIpv6`), converting each part
92     from a string to an u16 integer. Returns an internal representation of IPv6
93     address (list of integers) that can be easily processed by other helper
94     functions.
95     Throws an error some element is not an u16 integer.
97     # Type: [ String ] -> IPv6
99     # Example:
101     ```nix
102     parseExpandedIpv6 ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
103     => [8193 3512 0 0 0 0 0 65535]
104     ```
105   */
106   parseExpandedIpv6 =
107     addr:
108     assert lib.assertMsg (
109       length addr == ipv6Pieces
110     ) "parseExpandedIpv6: expected list of integers with ${ipv6Pieces} elements";
111     let
112       u16FromHexStr =
113         hex:
114         let
115           parsed = trivial.fromHexString hex;
116         in
117         if 0 <= parsed && parsed <= ipv6PieceMaxValue then
118           parsed
119         else
120           throw "0x${hex} is not a valid u16 integer";
121     in
122     map (piece: u16FromHexStr piece) addr;
125   /**
126     Parses an IPv6 address from a string to the internal representation (list
127     of integers).
129     # Type: String -> IPv6
131     # Example:
133     ```nix
134     parseIpv6FromString "2001:DB8::ffff"
135     => [8193 3512 0 0 0 0 0 65535]
136     ```
137   */
138   parseIpv6FromString = addr: parseExpandedIpv6 (expandIpv6 addr);
141   /*
142     Internally, an IPv6 address is stored as a list of 16-bit integers with 8
143     elements. Wherever you see `IPv6` in internal functions docs, it means that
144     it is a list of integers produced by one of the internal parsers, such as
145     `parseIpv6FromString`
146   */
147   _ipv6 = {
148     /**
149       Converts an internal representation of an IPv6 address (i.e, a list
150       of integers) to a string. The returned string is not a canonical
151       representation as defined in RFC 5952, i.e zeros are not compressed.
153       # Type: IPv6 -> String
155       # Example:
157       ```nix
158       parseIpv6FromString [8193 3512 0 0 0 0 0 65535]
159       => "2001:db8:0:0:0:0:0:ffff"
160       ```
161     */
162     toStringFromExpandedIp =
163       pieces: strings.concatMapStringsSep ":" (piece: strings.toLower (trivial.toHexString piece)) pieces;
165     /**
166       Extract an address and subnet prefix length from a string. The subnet
167       prefix length is optional and defaults to 128. The resulting address and
168       prefix length are validated and converted to an internal representation
169       that can be used by other functions.
171       # Type: String -> [ {address :: IPv6, prefixLength :: Int} ]
173       # Example:
175       ```nix
176       split "2001:DB8::ffff/32"
177       => {
178         address = [8193 3512 0 0 0 0 0 65535];
179         prefixLength = 32;
180       }
181       ```
182     */
183     split =
184       addr:
185       let
186         splitted = strings.splitString "/" addr;
187         splittedLength = length splitted;
188       in
189       if splittedLength == 1 then # [ ip ]
190         {
191           address = parseIpv6FromString addr;
192           prefixLength = ipv6Bits;
193         }
194       else if splittedLength == 2 then # [ ip subnet ]
195         {
196           address = parseIpv6FromString (head splitted);
197           prefixLength =
198             let
199               n = strings.toInt (last splitted);
200             in
201             if 1 <= n && n <= ipv6Bits then
202               n
203             else
204               throw "${addr} IPv6 subnet should be in range [1;${toString ipv6Bits}], got ${toString n}";
205         }
206       else
207         throw "${addr} is not a valid IPv6 address in CIDR notation";
208   };