summaryrefslogtreecommitdiffstats
path: root/lib/krebs/genipv6.nix
blob: bf3ebab3839cd97e8b467f0d5fa8b2ef0ce3c972 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
lib:
with lib;
let {
  body = netname: subnetname: suffixSpec: rec {
    address = let
      suffix' = prependZeros suffixLength suffix;
    in
      normalize-ip6-addr
        (checkAddress addressLength (joinAddress subnetPrefix suffix'));
    addressCIDR = "${address}/${toString addressLength}";
    addressLength = 128;

    inherit netname;
    netCIDR = "${netAddress}/${toString netPrefixLength}";
    netAddress = appendZeros netPrefixLength netPrefix;
    netHash = toString {
      retiolum = 0;
      wirelum = 1;
    }.${netname};
    netPrefix = "42:${netHash}";
    netPrefixLength = {
      retiolum = 32;
      wirelum = 32;
    }.${netname};

    inherit subnetname;
    subnetCIDR = "${subnetAddress}/${toString subnetPrefixLength}";
    subnetAddress = appendZeros subnetPrefixLength subnetPrefix;
    subnetHash = hash 4 subnetname;
    subnetPrefix = joinAddress netPrefix subnetHash;
    subnetPrefixLength = netPrefixLength + 16;

    suffix = getAttr (typeOf suffixSpec) {
      set =
        concatStringsSep
          ":"
          (stringToGroupsOf 4 (hash (suffixLength / 4) suffixSpec.hostName));
      string = suffixSpec;
    };
    suffixLength = addressLength - subnetPrefixLength;
  };

  appendZeros = n: s: let
    n' = n / 16;
    zeroCount = n' - length parsedaddr;
    parsedaddr = parseAddress s;
  in
    formatAddress (parsedaddr ++ map (const "0") (range 1 zeroCount));

  prependZeros = n: s: let
    n' = n / 16;
    zeroCount = n' - length parsedaddr;
    parsedaddr = parseAddress s;
  in
    formatAddress (map (const "0") (range 1 zeroCount) ++ parsedaddr);

  # Split string into list of chunks where each chunk is at most n chars long.
  # The leftmost chunk might shorter.
  # Example: stringToGroupsOf "123456" -> ["12" "3456"]
  stringToGroupsOf = n: s: let
    acc =
      foldl'
        (acc: c: if stringLength acc.chunk < n then {
          chunk = acc.chunk + c;
          chunks = acc.chunks;
        } else {
          chunk = c;
          chunks = acc.chunks ++ [acc.chunk];
        })
        {
          chunk = "";
          chunks = [];
        }
        (stringToCharacters s);
  in
    filter (x: x != []) ([acc.chunk] ++ acc.chunks);

  hash = n: s: substring 0 n (hashString "sha256" s);

  dropLast = n: xs: reverseList (drop n (reverseList xs));
  takeLast = n: xs: reverseList (take n (reverseList xs));

  hasEmptyPrefix = xs: take 2 xs == ["" ""];
  hasEmptySuffix = xs: takeLast 2 xs == ["" ""];
  hasEmptyInfix = xs: any (x: x == "") (trimEmpty 2 xs);

  hasEmptyGroup = xs:
    any (p: p xs) [hasEmptyPrefix hasEmptyInfix hasEmptySuffix];

  ltrimEmpty = n: xs: if hasEmptyPrefix xs then drop n xs else xs;
  rtrimEmpty = n: xs: if hasEmptySuffix xs then dropLast n xs else xs;
  trimEmpty = n: xs: rtrimEmpty n (ltrimEmpty n xs);

  parseAddress = splitString ":";
  formatAddress = concatStringsSep ":";

  check = s: c: if !c then throw "${s}" else true;

  checkAddress = maxaddrlen: addr: let
    parsedaddr = parseAddress addr;
    normalizedaddr = trimEmpty 1 parsedaddr;
  in
    assert (check "address malformed; lone leading colon: ${addr}" (
      head parsedaddr == "" -> tail (take 2 parsedaddr) == ""
    ));
    assert (check "address malformed; lone trailing colon ${addr}" (
      last parsedaddr == "" -> head (takeLast 2 parsedaddr) == ""
    ));
    assert (check "address malformed; too many successive colons: ${addr}" (
      length (filter (x: x == "") normalizedaddr) > 1 -> addr == [""]
    ));
    assert (check "address malformed: ${addr}" (
      all (test "[0-9a-f]{0,4}") parsedaddr
    ));
    assert (check "address is too long: ${addr}" (
      length normalizedaddr * 16 <= maxaddrlen
    ));
    addr;

  joinAddress = prefix: suffix: let
    parsedPrefix = parseAddress prefix;
    parsedSuffix = parseAddress suffix;
    normalizePrefix = rtrimEmpty 2 parsedPrefix;
    normalizeSuffix = ltrimEmpty 2 parsedSuffix;
    delimiter =
      optional (length (normalizePrefix ++ normalizeSuffix) < 8 &&
                (hasEmptySuffix parsedPrefix || hasEmptyPrefix parsedSuffix))
               "";
  in
    formatAddress (normalizePrefix ++ delimiter ++ normalizeSuffix);
}