IP v4 の address 範囲から CIDR を計算する

# Overview

[これ](https://blog.ip2location.com/knowledge-base/how-to-convert-ip-address-range-into-cidr/) の python3 と javascript 版。

javascript でブラウザで動かせるやつが欲しかったのだけど、意外と手こずったので、一度 python で書いてから js で書き直した。

検証は[この辺](https://www.ipaddressguide.com/cidr)で。

# python3 版

[netaddr](https://pypi.org/project/netaddr/) っていうお手本があるので、これを使うと楽。
ちなみに `netaddr.iprange_to_cidrs` っていうそのまんまな実装がすでにあるけど、今回は手書きしたかったので使いません。

```py3:a.py
import netaddr
import math

def iprange2cidr(start, end):
    iMask = lambda s: (2**32 - 2**(32-s))

    while end >= start:
        max_size = 32
        while max_size > 0:
            assert 0 < max_size <= 32
            mask = iMask(max_size - 1)
            if (start & mask) != start:
                break
            max_size -= 1

        x = math.log(end - start + 1) / math.log(2)
        max_diff = math.floor(32 - math.floor(x))

        if max_size < max_diff:
            max_size = max_diff

        yield "{}/{}".format(netaddr.IPAddress(start), max_size)
        start += 2**(32-max_size)


if __name__ == "__main__":
    expect = [
        "192.0.2.46/31",
        "192.0.2.48/28",
        "192.0.2.64/26",
        "192.0.2.128/25",
        "192.0.3.0/24",
        "192.0.4.0/22",
        "192.0.8.0/21",
        "192.0.16.0/20",
        "192.0.32.0/24",
        "192.0.33.0/29",
        "192.0.33.8/30",
        "192.0.33.12/31",
        "192.0.33.14/32",
    ]

    a1 = netaddr.IPAddress('192.0.2.46')
    b1 = netaddr.IPAddress('192.0.33.14')

    actual = list(iprange2cidr(int(a1), int(b1)))
    print(actual)
    assert actual == expect
```

```bash
$ python a.py
['192.0.2.46/31', '192.0.2.48/28', '192.0.2.64/26', '192.0.2.128/25', '192.0.3.0/24', '192.0.4.0/22', '192.0.8.0/21', '192.0.16.0/20', '192.0.32.0/24', '192.0.33.0/29', '192.0.33.8/30', '192.0.33.12/31', '192.0.33.14/32']
```

# javascript 版

unsigned が明示できないのが罠でちょっとハマった。

```javascript:a.js
class IPv4 {
  static OctetsToInt(octets) {
    const ret = (octets[0] << 24) + (octets[1] << 16) + (octets[2] << 8) + octets[3];
    return ret >>> 0; // cast to unsigned
  }

  static IntToOctets(i) {
    return [
      (i >>> 24) & 0xFF,
      (i >>> 16) & 0xFF,
      (i >>> 8)  & 0xFF,
      (i >>> 0)  & 0xFF,
    ];
  }

  static IntToString(i) {
    return this.OctetsToString(this.IntToOctets(i));
  }

  static StringToOctets(s) {
    return s.split(".").map((x) => parseInt(x, 10))
  }

  static OctetsToString(octets) {
    return octets.join(".")
  }

  static IntToBits(i) {
    const mask = [...Array(32).keys()].reverse().map((x) => 1<<x);
    const bits = mask.map((x) => i & x).map((x) => x != 0).map(Number);
    return bits.join("");
  }
}

function iprange2cidr(start, end) {
  const ret = [];
  const iMask = function(s) {
    return (2**32 - 2**(32-s));
  }

  while (end >= start) {
      let maxSize = 32;
      while (maxSize > 0) {
          const mask = iMask(maxSize-1);
          const maskBase = (start & mask)>>>0;  // cast to unsigned
          if (maskBase != start) {
              break;
          }
          maxSize -= 1;
      }

      const x = Math.log(end - start + 1) / Math.log(2);
      const maxDiff = Math.floor(32 - Math.floor(x));

      if (maxSize < maxDiff) {
          maxSize = maxDiff;
      }

      ret.push([IPv4.IntToString(start), maxSize].join("/"));
      start += 2**(32-maxSize);
  }

  return ret;
}

a1 = '192.0.2.46';
b1 = '192.0.33.14';

a2 = IPv4.OctetsToInt(IPv4.StringToOctets(a1));
b2 = IPv4.OctetsToInt(IPv4.StringToOctets(b1));

c = iprange2cidr(a2, b2);
console.log(c);
```

```bash
$ node a.js
[
  '192.0.2.46/31',  '192.0.2.48/28',
  '192.0.2.64/26',  '192.0.2.128/25',
  '192.0.3.0/24',   '192.0.4.0/22',
  '192.0.8.0/21',   '192.0.16.0/20',
  '192.0.32.0/24',  '192.0.33.0/29',
  '192.0.33.8/30',  '192.0.33.12/31',
  '192.0.33.14/32'
]
```

# 用途

CIDR計算がいちいちめんどくさいのでchrome拡張作りたかった時とか。

https://github.com/arc279/chrome-ipv4-cidr-calculator

おわり。