ネットワーク・レンジからCIDRをもとめる 改訂版

CIDRとネットワーク・レンジを変換(2005年8月12日)」のスクリプトを改訂しました。
https://tociyuki.sakura.ne.jp/archive/cidr.pl Perl
https://tociyuki.sakura.ne.jp/archive/cidr.rb Ruby
使い方は両方ともまったく同じです。

$ perl cidr.pl 210.81.64.0/18
210.81.64.0 - 210.81.127.255
$ perl cidr.pl '219.168.0.0 - 219.215.255.255'
219.168.0.0/13
219.176.0.0/12
219.192.0.0/12
219.208.0.0/13

改訂前の版では、上の例のようなレンジがうまく変換できませんでしたので、再帰呼び出しに書き改めて扱えるようにしました。
計算をしているサブルーチンを抜き出すと、次のようになっています。パラメータ $a が着目しているレンジの始まりの IPv4 アドレスを 32 ビット整数で表したもの、$b レンジの終わり、$len が着目しているネットマスク長を表しています。

sub _cidr {
    my( $self, $a, $b, $len) = @_;
    $len < 32 or die "RuntimeError: len overrun";
    # len長のネットマスクを計算。
    my $mask = ~((1 << (32 - $len)) - 1);
    if ( ($a & $mask) == ($b & $mask) ) {
        # レンジの始めと終わりが同じネットワークである。
        if ( $a == ($a & $mask) && $b == ($b | ~$mask) ) {
            # ネットマスクに一致する。
            (IPv4Addr->new($a)->addr . "/" . $len);
        } else {
            # ネットマスク長が短すぎる。
            $self->_cidr($a, $b, $len + 1);
        }
    } else {
        # 2つのネットワークに分割して処理をやりなおし
        ( $self->_cidr($a, $a | ~$mask, $len),   # ネットワークの最下位ビットが 0
          $self->_cidr($b & $mask, $b, $len) );  # ネットワークの最下位ビットが 1
    }
}

レンジの始まりと終わりがネットワークのそれらと一致するときは、CIDR を求めます。そうでないときに、ネットワーク内の含まれているならネットマスク長を増やして再帰呼び出しをおこない、ネットワークをまたいでいるときは最下桁が 0 のときと、1 のときをそれぞれ再帰呼び出しで処理します。
TODO: Perl 版では 32 ビット整数のマスクをしていないのですが、64 ビット整数を扱うようにコンパイルした Perl 処理系のために、Ruby 版のようにマスクしておいた方が良いのかなぁ……と思うので、近いうちに Perl 版を改訂すると思います。