📚
zigでipv4アドレスをパースする
はじめに
先日ネットで以下の記事をみつけた。
面白そうだったので学習もかねてzigで実装してみた。
目標
次のような関数を実装する
fn validIpv4(text: []const u8) bool {}
実装する
まずはテストの作成。
テストケースはChatGPTに作成してもらって、心配な部分は自分で書いた。
test "validIpv4" {
const TestCase = struct {
text: []const u8,
expected: bool,
};
const inputs = [_]TestCase{
.{ .text = "0.0.0.0", .expected = true },
.{ .text = "192.168.0.1", .expected = true },
.{ .text = "10.0.0.1", .expected = true },
.{ .text = "255.255.255.255", .expected = true },
.{ .text = "127.0.0.1", .expected = true },
.{ .text = "172.16.0.1", .expected = true },
.{ .text = "100.100.100.100", .expected = true },
.{ .text = "8.8.8.8", .expected = true },
.{ .text = "192.0.2.1", .expected = true },
.{ .text = "198.51.100.1", .expected = true },
.{ .text = "203.0.113.1", .expected = true },
.{ .text = "169.254.0.1", .expected = true },
.{ .text = "1.2.3.4", .expected = true },
.{ .text = "192.168.1.10", .expected = true },
.{ .text = "10.10.10.10", .expected = true },
.{ .text = "0.0.0.1", .expected = true },
.{ .text = "192.0.0.1", .expected = true },
.{ .text = "224.0.0.1", .expected = true },
.{ .text = "240.0.0.1", .expected = true },
.{ .text = "198.18.0.1", .expected = true },
.{ .text = "256.0.0.0", .expected = false },
.{ .text = "300.300.300.300", .expected = false },
.{ .text = "192.168.1.256", .expected = false },
.{ .text = "10.0.0.1.1", .expected = false },
.{ .text = "192.168.1.", .expected = false },
.{ .text = "1.2.3", .expected = false },
.{ .text = "1.2.3.4.", .expected = false },
.{ .text = "1.2.3..", .expected = false },
.{ .text = "1.2.3.4.5", .expected = false },
.{ .text = "19216801", .expected = false },
.{ .text = "200.10.20.30.40", .expected = false },
.{ .text = "1000.1000.1000.1000", .expected = false },
.{ .text = "10.00.0.1", .expected = false },
.{ .text = "255.256.255.255", .expected = false },
.{ .text = "255.255.255.256", .expected = false },
.{ .text = "192.168.0.01", .expected = false },
.{ .text = "001.2.3.4", .expected = false },
.{ .text = "00001.2.3.4", .expected = false },
.{ .text = "12..34.56", .expected = false },
.{ .text = "abc.def.ghi.jkl", .expected = false },
};
for (inputs) |input| {
try tt.expect(validIpv4(input.text) == input.expected);
}
}
続いて目標ととなる関数を定義する。
fn validIpv4(text: []const u8) bool {
return true;
}
テストを実行してみる。
$ zig test ip-parser.zig
当然落ちる。
Test [1/1] test.validIpv4... FAIL (TestUnexpectedResult)
/usr/lib/zig/std/testing.zig:515:14: 0x224b5f in expect (test)
if (!ok) return error.TestUnexpectedResult;
^
/home/fujis/workspace/zig-snippets/ip-parser.zig:109:9: 0x224ce4 in test.validIpv4 (test)
try tt.expect(validIpv4(input.text) == input.expected);
^
0 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
さて、いよいよ中身を実装していく。
fn countLen(it: anytype) u8 {
var count: u8 = 0;
while (it.next()) |_| {
count += 1;
}
it.reset();
return count;
}
fn validIpv4(text: []const u8) bool {
// 数字と.以外の文字が含まれている場合は不正
for (text) |c| {
if ((c < '0' or c > '9') and c != '.') {
return false;
}
}
// 4つのパートに分割する。
// ex) 192.168.1.1 -> ["192", "168", "1", "1"]
var it = std.mem.split(u8, text, ".");
// 4つのパートに分割できない場合は不正
if (countLen(&it) != 4) {
return false;
}
while (it.next()) |part| {
// 12..0のように連続するドットがある場合は不正
if (part.len == 0) {
return false;
}
// 0で始まる数字は不正
if (part[0] == '0' and part.len > 1) {
return false;
}
var value: u32 = 0;
for (part) |c| {
value = value * 10 + (c - '0');
}
// 255より大きい数字が含まれている場合は不正
if (value > 255) {
return false;
}
}
return true;
}
テストを実行する。
$ zig test ip-parser.zig
All 1 tests passed.
完成しました!🎉🎉🎉
コード全文
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const tt = std.testing;
fn countLen(it: anytype) u8 {
var count: u8 = 0;
while (it.next()) |_| {
count += 1;
}
it.reset();
return count;
}
fn validIpv4(text: []const u8) bool {
// 数字と.以外の文字が含まれている場合は不正
for (text) |c| {
if ((c < '0' or c > '9') and c != '.') {
return false;
}
}
// 4つのパートに分割する。
// ex) 192.168.1.1 -> ["192", "168", "1", "1"]
var it = std.mem.split(u8, text, ".");
// 4つのパートに分割できない場合は不正
if (countLen(&it) != 4) {
return false;
}
while (it.next()) |part| {
// 12..0のように連続するドットがある場合は不正
if (part.len == 0) {
return false;
}
// 0で始まる数字は不正
if (part[0] == '0' and part.len > 1) {
return false;
}
var value: u32 = 0;
for (part) |c| {
value = value * 10 + (c - '0');
}
// 255より大きい数字が含まれている場合は不正
if (value > 255) {
return false;
}
}
return true;
}
test "validIpv4" {
const TestCase = struct {
text: []const u8,
expected: bool,
};
const inputs = [_]TestCase{
.{ .text = "0.0.0.0", .expected = true },
.{ .text = "192.168.0.1", .expected = true },
.{ .text = "10.0.0.1", .expected = true },
.{ .text = "255.255.255.255", .expected = true },
.{ .text = "127.0.0.1", .expected = true },
.{ .text = "172.16.0.1", .expected = true },
.{ .text = "100.100.100.100", .expected = true },
.{ .text = "8.8.8.8", .expected = true },
.{ .text = "192.0.2.1", .expected = true },
.{ .text = "198.51.100.1", .expected = true },
.{ .text = "203.0.113.1", .expected = true },
.{ .text = "169.254.0.1", .expected = true },
.{ .text = "1.2.3.4", .expected = true },
.{ .text = "192.168.1.10", .expected = true },
.{ .text = "10.10.10.10", .expected = true },
.{ .text = "0.0.0.1", .expected = true },
.{ .text = "192.0.0.1", .expected = true },
.{ .text = "224.0.0.1", .expected = true },
.{ .text = "240.0.0.1", .expected = true },
.{ .text = "198.18.0.1", .expected = true },
.{ .text = "256.0.0.0", .expected = false },
.{ .text = "300.300.300.300", .expected = false },
.{ .text = "192.168.1.256", .expected = false },
.{ .text = "10.0.0.1.1", .expected = false },
.{ .text = "192.168.1.", .expected = false },
.{ .text = "1.2.3", .expected = false },
.{ .text = "1.2.3.4.", .expected = false },
.{ .text = "1.2.3..", .expected = false },
.{ .text = "1.2.3.4.5", .expected = false },
.{ .text = "19216801", .expected = false },
.{ .text = "200.10.20.30.40", .expected = false },
.{ .text = "1000.1000.1000.1000", .expected = false },
.{ .text = "10.00.0.1", .expected = false },
.{ .text = "255.256.255.255", .expected = false },
.{ .text = "255.255.255.256", .expected = false },
.{ .text = "192.168.0.01", .expected = false },
.{ .text = "001.2.3.4", .expected = false },
.{ .text = "00001.2.3.4", .expected = false },
.{ .text = "12..34.56", .expected = false },
.{ .text = "abc.def.ghi.jkl", .expected = false },
};
for (inputs) |input| {
try tt.expect(validIpv4(input.text) == input.expected);
}
}
Discussion