【C++】IPアドレスの計算をやりたくないのでツール作った

## 注意事項

このアプリケーションは素人が作成したものです。
動作等に一切の責任を持つことができません。ごめんなさい
具体的には引数prefixに24より大きい値を渡すと計算量がとんでもないことになります。
また、エラー処理等一切実装してません。

## はじめに

研修中ネットワークエンジニアのこじろーです。
ネットワークエンジニアというものはIPアドレスから避けて通ることができない職業だと思っています。
自分はまだ研修中で資格の勉強中ですがそれでも嫌という程IPアドレスの計算をします。
別にIPアドレスから逃げたいわけではありませんが計算からは逃げ出したいので
最近趣味でやっていたC++でIP計算機てきななにかを作成することにしました。

## ホストアドレスからネットワークアドレスを求める

まず最初にbitsetをIPアドレスのビット列を作成し、
ループでビット列の先頭から確認して左に１シフトしていきます。
このとき２の７乗から計算して変数tempに加算し、
8ビット分が終了したら指数をリセットして３回繰り返します。

<details><summary>失敗例</summary><div>

```cpp:main.cpp

#include <iostream>
#include <bitset>
#include <cmath>

using namespace std;

int main()
{
 string address = "";
 int count = 0;
 int temp = 0;

 bitset<32> bit("11000000101010000000000001100100");
 bitset<32> mask("11111111111111111111111100000000");

 bit &= mask;

 for (int i = 0; i < 32; i++)
 {
 if (bit[0])
 {
 temp += pow(2, count);
 }
 count++;
 if ((i + 1) % 8 == 0)
 {
 count = 0;
 address.append(to_string(temp) + ".");
 temp = 0;
 }
 bit >>= 1;
 }

 cout << address.substr(0, address.length() - 1) << endl;

 return 0;
}
```

```plaintext:実行結果
0.0.168.192
```

ビット列の見る方向が逆でした＾＾
</div></details>

<details><summary>ソースコードと実行結果</summary><div>

```cpp:main.cpp
#include <iostream>
#include <bitset>
#include <cmath>

using namespace std;

int main()
{
 string address = "";
 int count = 0;
 int temp = 0;
 int n = 7;
 bitset<32> bit("11000000101010000000000001100100");
 bitset<32> mask("11111111111111111111111100000000");

 bit &= mask;

 for (int i = 0; i < 32; i++)
 {
 if (bit[31])
 {
 temp += pow(2, n);
 }
 n--;
 count++;
 if ((i + 1) % 8 == 0)
 {
 count = 0;
 address.append(to_string(temp) + ".");
 temp = 0;
 n = 7;
 }
 bit <<= 1;
 }

 cout << address.substr(0, address.length() - 1) << endl;

 return 0;
}
```

```plaintext:実行結果
192.168.0.0
```

</div></details>

ネットワークアドレスを求めることができました。
これでbitsetからIPアドレスを文字列にすることができたので少し応用して
IPアドレスの範囲を求めます。

## アドレス範囲を列挙する

IPアドレスの計算において範囲とは
ネットワークアドレスとブロードキャストアドレスの間という意味になり
また、言い換えるとホスト部が全部０と全部１になります。

これをコードにすると

```cpp
bitset<32> bit("11000000101010000000000001100100");
bitset<32> mask("11111111111111111111111100000000");

// ネットワークアドレス
bit & mask;
// ブロードキャストアドレス
bit | ~mask;
```

になるのでこの範囲で１ビットずつ加算すればアドレス範囲を求めることができます。

恥ずかしながらビットで加算をするので全加算器のやり方を使いますが
頭から完全に抜け落ちていたので少し苦労しました。反省

<details><summary>ソースコードと実行結果</summary><div>

```cpp:main.cpp
#include <iostream>
#include <bitset>
#include <cmath>

using namespace std;

bitset<32> full_adder(bitset<32>, bitset<32>);

int main()
{
 static const bitset<32> OFFSET(1);
 string address = "";
 int count = 0;
 int temp = 0;
 int n = 7;
 bitset<32> bit("11000000101010000000000001100100");
 bitset<32> mask("11111111111111111111111100000000");

 bitset<32> addr_network = bit & mask;
 bitset<32> addr_broadcast = addr_network | ~mask;
 bitset<32> addr_current = addr_network | OFFSET;
 bitset<32> addr_temp;

 while (addr_current != addr_broadcast)
 {
 addr_temp = addr_current;
 for (int i = 0; i < 32; i++)
 {
 if (addr_current[31])
 {
 temp += pow(2, n);
 }
 n--;
 count++;
 if ((i + 1) % 8 == 0)
 {
 count = 0;
 address.append(to_string(temp) + ".");
 temp = 0;
 n = 7;
 }
 addr_current <<= 1;
 }
 cout << address.substr(0, address.length() - 1) << endl;
 address = "";
 addr_current = full_adder(addr_temp, OFFSET);
 }

 return 0;
}

bitset<32> full_adder(bitset<32> a, bitset<32> b)
{
 while (b != 0)
 {
 bitset<32> c = (a & b) << 1;
 a ^= b;
 b = c;
 }
 return a;
}
```

```plaintext:実行結果
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
192.168.0.5
～～一部省略～～
192.168.0.254
```

これで使用可能ネットワークアドレスの範囲を求めることができました。

</div></details>

これでアドレス範囲を求めることができました。

ここまで全部ソースコードの中にIPアドレスとかサブネットマスクなどをべた書きしていましたが
コマンドライン引数をとってIPアドレスの範囲を表示してみます。

## 外部入力に対応する

ここでは大きく追加した処理としてaddr_parse()とget_prefix()があります。

addr_parse()はconst char*型とbitset<32>型でoverloadしていて

文字列表現を渡すとビット列にして返す
ビット列にを渡すと文字列表現を返す

get_prefix()はprefixの文字列表現（例：'24'）を渡すとbitsetで返す

という処理にしています。

<details><summary>ソースコードと実行結果</summary><div>

```cpp:main.cpp
#include <iostream>
#include <bitset>
#include <cmath>

using namespace std;

bitset<32> full_adder(bitset<32>, bitset<32>);
bitset<32> addr_parse(const char *);
string addr_parse(bitset<32>);
bitset<32> prefix_get(const char *);

int main(int argc, char *argv[])
{
 static const bitset<32> OFFSET(1);

 bitset<32> bit = addr_parse(argv[1]);
 bitset<32> mask = prefix_get(argv[2]);
 bitset<32> addr_network = bit & mask;
 bitset<32> addr_broadcast = addr_network | ~mask;
 bitset<32> addr_current = addr_network | OFFSET;
 bitset<32> addr_temp;

 while (addr_current != addr_broadcast)
 {
 cout << addr_parse(addr_current) << endl;
 addr_current = full_adder(addr_current, OFFSET);
 }

 return 0;
}

bitset<32> full_adder(bitset<32> a, bitset<32> b)
{
 while (b != 0)
 {
 bitset<32> c = (a & b) << 1;
 a ^= b;
 b = c;
 }
 return a;
}

string addr_parse(bitset<32> address)
{
 int count = 0;
 int temp = 0;
 int n = 7;
 string result = "";
 for (int i = 0; i < 32; i++)
 {
 if (address[31])
 {
 temp += pow(2, n);
 }
 n--;
 count++;
 if ((i + 1) % 8 == 0)
 {
 count = 0;
 result.append(to_string(temp) + ".");
 temp = 0;
 n = 7;
 }
 address <<= 1;
 }

 return result.substr(0, result.length() - 1);
}

bitset<32> addr_parse(const char *address)
{
 char octet[4][4];
 int index = 0;
 int index_octet = 0;
 int index_str = 0;

 while (address[index] != '\0')
 {
 if (address[index] == '.')
 {
 octet[index_octet][index_str] = '\0';
 index_octet++;
 index_str = 0;
 }
 else
 {
 octet[index_octet][index_str] = address[index];
 index_str++;
 }
 index++;
 }

 bitset<32> bit;
 int count = 0;
 const unsigned int octet_array[] = {atoi(octet[0]), atoi(octet[1]), atoi(octet[2]), atoi(octet[3])};
 for (int octet : octet_array)
 {
 for (int index = 0; index < 8; index++)
 {
 if (octet % 2 != 0)
 {
 bit.set(index);
 }
 octet /= 2;
 }
 if (count < 3) {
 bit <<= 8;
 count++;
 }
 }

 return bit;
}

bitset<32> prefix_get(const char *prefix_str)
{
 int prefix = atoi(prefix_str);
 bitset<32> subnet_mask;
 for (int count = 1; count < prefix; count++)
 {
 subnet_mask.set(31);
 subnet_mask >>= 1;
 }
 subnet_mask.set(31);

 return subnet_mask;
}
```

```plaintext:実行結果
$./a.out 192.168.0.252 30
192.168.0.253
192.168.0.254
```

</div></details>

ここまででかなり時間とソースコードの行数を消費しましたが
最後にncursesを使ってUIを作ります。

## それっぽいインターフェースをncursesで作る

prefixを24にするとアドレスの範囲は254になります。
つまりなにが言いたいかというと

アドレスの範囲 = 出力行数

ということになり非常に見にくいので
ncursesを使って上下にスクロールできるようにしました。

ncursesで表示するにあたって通常のウィンドウではなくパッドを使用しました。
パッドはサイズ制限のないウィンドウで実際のターミナルの画面サイズよりも
大きいウィンドウを作成することができます。

実際の処理では

1. アドレス範囲の行数を持つパッドを作成
2. そのパッドに全部出力
3. 矢印キーを押下するたびに表示範囲を移動する

という処理にしてスクロールを表現しています。

<details><summary>ソースコードと実行結果</summary><div>

```cpp:main.cpp
#include <iostream>
#include <bitset>
#include <cmath>
#include <ncurses.h>
#include <unistd.h>

using namespace std;

bitset<32> full_adder(bitset<32>, bitset<32>);
bitset<32> addr_parse(const char *);
string addr_parse(bitset<32>);
bitset<32> prefix_get(int prefix);
int get_addr_size(int);

int main(int argc, char *argv[])
{
 static const char *ADDRESS = argv[1];
 static const int PREFIX = atoi(argv[2]);
 static const bitset<32> OFFSET(1);
 static const int USEABLE_ADDR_SIZE = get_addr_size(PREFIX);

 bitset<32> bit = addr_parse(ADDRESS);
 bitset<32> mask = prefix_get(PREFIX);
 bitset<32> addr_network = bit & mask;
 bitset<32> addr_broadcast = addr_network | ~mask;
 bitset<32> addr_current = addr_network | OFFSET;

 WINDOW *pad;
 int line = 0;
 int h = 0;
 int w = 0;
 initscr();
 getmaxyx(stdscr, h, w);
 pad = newpad(USEABLE_ADDR_SIZE, w);

 /**
 * 入力オプション群
 */
 noecho();
 curs_set(0);
 nonl();
 intrflush(stdscr, FALSE);
 intrflush(pad, TRUE);
 keypad(pad, TRUE);

 while (addr_current != addr_broadcast)
 {
 string result = addr_parse(addr_current);
 result = result.substr(0, result.length() - 1);
 mvwprintw(pad, line, 0, "%s", result.data());
 addr_current = full_adder(addr_current, OFFSET);
 line++;
 }
 prefresh(pad, 0, 0, 0, 0, h - 1, w - 1);

 int y = 0;

 while (true)
 {
 int ch = wgetch(pad);

 if (ch == 'q')
 {
 delwin(pad);
 endwin();
 break;
 }
 if (ch == KEY_DOWN && y + h < USEABLE_ADDR_SIZE)
 {
 y++;
 prefresh(pad, y, 0, 0, 0, h - 1, w - 1);
 }
 else if (ch == KEY_UP && y > 0)
 {
 y--;
 prefresh(pad, y, 0, 0, 0, h - 1, w - 1);
 } else {
 beep();
 }
 }

 return 0;
}

bitset<32> full_adder(bitset<32> a, bitset<32> b)
{
 while (b != 0)
 {
 bitset<32> c = (a & b) << 1;
 a ^= b;
 b = c;
 }
 return a;
}

string addr_parse(bitset<32> address)
{
 int count = 0;
 int temp = 0;
 int n = 7;
 string result = "";
 for (int i = 0; i < 32; i++)
 {
 if (address[31])
 {
 temp += pow(2, n);
 }
 n--;
 count++;
 if ((i + 1) % 8 == 0)
 {
 count = 0;
 result.append(to_string(temp) + ".");
 temp = 0;
 n = 7;
 }
 address <<= 1;
 }

 // return result.substr(0, result.length() - 1).data();
 return result;
}

bitset<32> addr_parse(const char *address)
{
 char octet[4][4];
 int index = 0;
 int index_octet = 0;
 int index_str = 0;

 while (address[index] != '\0')
 {
 if (address[index] == '.')
 {
 octet[index_octet][index_str] = '\0';
 index_octet++;
 index_str = 0;
 }
 else
 {
 octet[index_octet][index_str] = address[index];
 index_str++;
 }
 index++;
 }

 bitset<32> bit;
 int count = 0;
 const unsigned int octet_array[] = {atoi(octet[0]), atoi(octet[1]), atoi(octet[2]), atoi(octet[3])};
 for (int octet : octet_array)
 {
 for (int index = 0; index < 8; index++)
 {
 if (octet % 2 != 0)
 {
 bit.set(index);
 }
 octet /= 2;
 }
 if (count < 3) {
 bit <<= 8;
 count++;
 }
 }

 return bit;
}

bitset<32> prefix_get(int prefix)
{
 bitset<32> subnet_mask;
 for (int count = 1; count < prefix; count++)
 {
 subnet_mask.set(31);
 subnet_mask >>= 1;
 }
 subnet_mask.set(31);

 return subnet_mask;
}

int get_addr_size(int prefix)
{
 static const int MAX_PREFIX = 32;

 return pow(2, MAX_PREFIX - prefix) - 2;
}
```

```plaintext:実行結果
スクロールしているところを想像してください
```

</div></details>

## まとめ

やっぱりプログラムを書くのはとても楽しい。
ただ思ったより３倍ぐらいはソースコードが長くなってしまった。
ncursesの情報がほぼ英語なので辛かった。
正直なところC++といってもほぼCみたいな書き方をしているので次は
クラスなどを使ってみたい。あとは他の言語やTUIライブラリなど
例えばgoの[tui-go](https://github.com/marcusolsson/tui-go)やrustの[tui-rs](https://github.com/fdehau/tui-rs)とかも面白そう。

## 参考文献
https://www.kaoriya.net/blog/2013/02/04/
[C++ std::string 文字列の分割（split）｜区切り文字／文字列に対応](https://marycore.jp/prog/cpp/std-string-split/)
[文字列で学ぶC++入門](https://qiita.com/7shi/items/cac7b3e9b90bf91b00cc)
[bitset - cpprefjp C++日本語リファレンス](https://cpprefjp.github.io/reference/bitset/bitset.html)
[basic_string::append - cpprefjp C++日本語リファレンス](https://cpprefjp.github.io/reference/string/basic_string/append.html)
[第２６章　プロトタイプ](http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01026.html)
[Qiita Markdown で折りたたみを表現する方法](https://qiita.com/matagawa/items/31e26e9cd53c3e61ae07)
[Ncurses Programming Guide](http://www.cs.ukzn.ac.za/~hughm/os/notes/ncurses.html#input)
[NCURSES Programming HOWTO](http://www.on-sky.net/~hs/misc/?NCURSES+Programming+HOWTO#l1)
[［Ｃ言語］ポインタとしての文字列](https://qiita.com/HAMADA_Hiroshi/items/ed9305e377dc7e10fbe5)
[char型をint型に変換する方法と注意【数値化 キャスト 文字列変換】](https://marycore.jp/prog/c-lang/convert-or-cast-char-to-
int/)
[ubuntu に ncurses を入れてみる(2)](https://yaneno-suzume.at.webry.info/201701/article_2.html)
[ncursesでブロック崩し](https://qiita.com/kaityo256/items/80863d466e0d69808eb9)
[【C++】string型をcharに変換/コピーする方法【値 配列 ポインタ string to char】](https://marycore.jp/prog/cpp/convert-
string-to-char/)
[ncurses](http://www.kis-lab.com/serikashiki/man/ncurses.html)