From 975a3ea3b79b0a52fa10bf52c6985b367ce87d46 Mon Sep 17 00:00:00 2001 From: Marsel Mavletkulov Date: Wed, 15 Apr 2026 21:18:07 -0400 Subject: [PATCH] Update to Zig 0.16 --- .github/workflows/ci.yml | 13 +++ README.md | 4 +- benchmarks/inspect.zig | 35 ++++---- benchmarks/lookup.zig | 37 ++++---- benchmarks/lookup_cache.zig | 35 ++++---- benchmarks/mycity.zig | 31 +++---- benchmarks/scan.zig | 29 +++--- benchmarks/scan_cache.zig | 29 +++--- build.zig | 5 +- examples/inspect.zig | 20 ++--- examples/lookup.zig | 11 ++- examples/scan.zig | 9 +- src/any.zig | 2 +- src/maxminddb.zig | 119 ++++++++++++++++--------- src/mmap.zig | 173 ------------------------------------ src/net.zig | 53 +++++------ src/reader.zig | 72 ++++++++++----- 17 files changed, 292 insertions(+), 385 deletions(-) delete mode 100644 src/mmap.zig diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bb5aa5..96d69cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,23 @@ jobs: - os: windows-latest runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash steps: - uses: actions/checkout@v6 + # The zig 0.16 aarch64-windows binary and LLVM libs are broken, + # see https://codeberg.org/ziglang/zig/issues/31865. + # As a workaround x86_64 Zig is used via Windows ARM emulation. - uses: mlugg/setup-zig@v2 + if: matrix.os != 'windows-11-arm' + - name: Setup Zig + if: matrix.os == 'windows-11-arm' + run: | + curl -fsSLo zig.zip https://ziglang.org/download/0.16.0/zig-x86_64-windows-0.16.0.zip + unzip -q zig.zip -d zig-sdk + echo "$PWD/zig-sdk/zig-x86_64-windows-0.16.0" >> $GITHUB_PATH - name: Fetch test-data run: git submodule update --init - name: Run tests diff --git a/README.md b/README.md index 3ea56bd..4ffdc64 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ It returns `Result` or null when the IP is not found or the record is empty. Each result owns an arena so you should call `result.deinit()` to free it. ```zig -var db = try maxminddb.Reader.mmap(allocator, db_path, .{}); +var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{}); defer db.close(); if (try db.lookup(maxminddb.geolite2.City, allocator, ip, .{})) |result| { @@ -134,7 +134,7 @@ and uses ~320KB at depth 16, or 12 (~20KB) for constrained devices. It's not worth creating an index for short-lived readers. ```zig -var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = 16 }); +var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{ .ipv4_index_first_n_bits = 16 }); defer db.close(); ``` diff --git a/benchmarks/inspect.zig b/benchmarks/inspect.zig index 44a20b8..cb31dfe 100644 --- a/benchmarks/inspect.zig +++ b/benchmarks/inspect.zig @@ -6,19 +6,19 @@ const default_db_path: []const u8 = "GeoLite2-City.mmdb"; const default_num_lookups: u64 = 1_000_000; const max_mmdb_fields = 32; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var num_lookups = default_num_lookups; var fields: ?[]const []const u8 = null; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10); - if (args.len > 3) { - const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ','); + if (args.next()) |arg| num_lookups = try std.fmt.parseUnsigned(u64, arg, 10); + if (args.next()) |arg| { + const f = try maxminddb.Fields(max_mmdb_fields).parse(arg, ','); fields = f.only(); } @@ -27,10 +27,11 @@ pub fn main() !void { std.debug.print(" Lookups: {d}\n", .{num_lookups}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = 16 }); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{ .ipv4_index_first_n_bits = 16 }); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -42,14 +43,14 @@ pub fn main() !void { const arena_allocator = arena.allocator(); std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var not_found_count: u64 = 0; var lookup_errors: u64 = 0; var ip_bytes: [4]u8 = undefined; for (0..num_lookups) |_| { - std.crypto.random.bytes(&ip_bytes); - const ip = std.net.Address.initIp4(ip_bytes, 0); + io.random(&ip_bytes); + const ip: std.Io.net.IpAddress = .{ .ip4 = .{ .bytes = ip_bytes, .port = 0 } }; const result = db.lookup( maxminddb.any.Value, @@ -69,7 +70,7 @@ pub fn main() !void { _ = arena.reset(.retain_capacity); } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); const lookups_per_second = if (elapsed_s > 0) diff --git a/benchmarks/lookup.zig b/benchmarks/lookup.zig index 2011fee..6f9d465 100644 --- a/benchmarks/lookup.zig +++ b/benchmarks/lookup.zig @@ -6,33 +6,34 @@ const default_db_path: []const u8 = "GeoLite2-City.mmdb"; const default_num_lookups: u64 = 1_000_000; const max_mmdb_fields = 32; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var num_lookups = default_num_lookups; var fields: ?[]const []const u8 = null; var index_bits: u8 = 16; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10); - if (args.len > 3) { - const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ','); + if (args.next()) |arg| num_lookups = try std.fmt.parseUnsigned(u64, arg, 10); + if (args.next()) |arg| { + const f = try maxminddb.Fields(max_mmdb_fields).parse(arg, ','); fields = f.only(); } - if (args.len > 4) index_bits = try std.fmt.parseUnsigned(u8, args[4], 10); + if (args.next()) |arg| index_bits = try std.fmt.parseUnsigned(u8, arg, 10); std.debug.print("Benchmarking with:\n", .{}); std.debug.print(" Database: {s}\n", .{db_path}); std.debug.print(" Lookups: {d}\n", .{num_lookups}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = index_bits }); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{ .ipv4_index_first_n_bits = index_bits }); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -44,14 +45,14 @@ pub fn main() !void { const arena_allocator = arena.allocator(); std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var not_found_count: u64 = 0; var lookup_errors: u64 = 0; var ip_bytes: [4]u8 = undefined; for (0..num_lookups) |_| { - std.crypto.random.bytes(&ip_bytes); - const ip = std.net.Address.initIp4(ip_bytes, 0); + io.random(&ip_bytes); + const ip: std.Io.net.IpAddress = .{ .ip4 = .{ .bytes = ip_bytes, .port = 0 } }; const result = db.lookup( maxminddb.geolite2.City, @@ -71,7 +72,7 @@ pub fn main() !void { _ = arena.reset(.retain_capacity); } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); const lookups_per_second = if (elapsed_s > 0) diff --git a/benchmarks/lookup_cache.zig b/benchmarks/lookup_cache.zig index b73cbdc..e68b740 100644 --- a/benchmarks/lookup_cache.zig +++ b/benchmarks/lookup_cache.zig @@ -5,19 +5,19 @@ const default_db_path: []const u8 = "GeoLite2-City.mmdb"; const default_num_lookups: u64 = 1_000_000; const max_mmdb_fields = 32; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var num_lookups = default_num_lookups; var fields: ?[]const []const u8 = null; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10); - if (args.len > 3) { - const f = try maxminddb.Fields(max_mmdb_fields).parse(args[3], ','); + if (args.next()) |arg| num_lookups = try std.fmt.parseUnsigned(u64, arg, 10); + if (args.next()) |arg| { + const f = try maxminddb.Fields(max_mmdb_fields).parse(arg, ','); fields = f.only(); } @@ -26,10 +26,11 @@ pub fn main() !void { std.debug.print(" Lookups: {d}\n", .{num_lookups}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = 16 }); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{ .ipv4_index_first_n_bits = 16 }); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -40,14 +41,14 @@ pub fn main() !void { defer cache.deinit(); std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var not_found_count: u64 = 0; var lookup_errors: u64 = 0; var ip_bytes: [4]u8 = undefined; for (0..num_lookups) |_| { - std.crypto.random.bytes(&ip_bytes); - const ip = std.net.Address.initIp4(ip_bytes, 0); + io.random(&ip_bytes); + const ip: std.Io.net.IpAddress = .{ .ip4 = .{ .bytes = ip_bytes, .port = 0 } }; const entry = db.find(ip, .{}) catch |err| { std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err }); @@ -65,7 +66,7 @@ pub fn main() !void { }; } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); const lookups_per_second = if (elapsed_s > 0) diff --git a/benchmarks/mycity.zig b/benchmarks/mycity.zig index e9de225..37fd24f 100644 --- a/benchmarks/mycity.zig +++ b/benchmarks/mycity.zig @@ -12,26 +12,27 @@ const MyCity = struct { } = .{}, }; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var num_lookups = default_num_lookups; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) num_lookups = try std.fmt.parseUnsigned(u64, args[2], 10); + if (args.next()) |arg| num_lookups = try std.fmt.parseUnsigned(u64, arg, 10); std.debug.print("Benchmarking with:\n", .{}); std.debug.print(" Database: {s}\n", .{db_path}); std.debug.print(" Lookups: {d}\n", .{num_lookups}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{ .ipv4_index_first_n_bits = 16 }); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{ .ipv4_index_first_n_bits = 16 }); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -43,14 +44,14 @@ pub fn main() !void { const arena_allocator = arena.allocator(); std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var not_found_count: u64 = 0; var lookup_errors: u64 = 0; var ip_bytes: [4]u8 = undefined; for (0..num_lookups) |_| { - std.crypto.random.bytes(&ip_bytes); - const ip = std.net.Address.initIp4(ip_bytes, 0); + io.random(&ip_bytes); + const ip: std.Io.net.IpAddress = .{ .ip4 = .{ .bytes = ip_bytes, .port = 0 } }; const result = db.lookup( MyCity, @@ -70,7 +71,7 @@ pub fn main() !void { _ = arena.reset(.retain_capacity); } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); const lookups_per_second = if (elapsed_s > 0) diff --git a/benchmarks/scan.zig b/benchmarks/scan.zig index 948cc7e..9656802 100644 --- a/benchmarks/scan.zig +++ b/benchmarks/scan.zig @@ -4,17 +4,17 @@ const maxminddb = @import("maxminddb"); const default_db_path: []const u8 = "GeoLite2-City.mmdb"; const max_mmdb_fields = 32; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var fields: ?[]const []const u8 = null; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) { - const f = try maxminddb.Fields(max_mmdb_fields).parse(args[2], ','); + if (args.next()) |arg| { + const f = try maxminddb.Fields(max_mmdb_fields).parse(arg, ','); fields = f.only(); } @@ -22,10 +22,11 @@ pub fn main() !void { std.debug.print(" Database: {s}\n", .{db_path}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{}); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{}); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -38,7 +39,7 @@ pub fn main() !void { maxminddb.Network.all_ipv6; std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var it = try db.scan(maxminddb.any.Value, allocator, network, .{ .only = fields }); @@ -48,7 +49,7 @@ pub fn main() !void { item.deinit(); } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); diff --git a/benchmarks/scan_cache.zig b/benchmarks/scan_cache.zig index cb7090f..f6fa307 100644 --- a/benchmarks/scan_cache.zig +++ b/benchmarks/scan_cache.zig @@ -4,17 +4,17 @@ const maxminddb = @import("maxminddb"); const default_db_path: []const u8 = "GeoLite2-City.mmdb"; const max_mmdb_fields = 32; -pub fn main() !void { - const allocator = std.heap.smp_allocator; +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - - var db_path: []const u8 = default_db_path; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse default_db_path; var fields: ?[]const []const u8 = null; - if (args.len > 1) db_path = args[1]; - if (args.len > 2) { - const f = try maxminddb.Fields(max_mmdb_fields).parse(args[2], ','); + if (args.next()) |arg| { + const f = try maxminddb.Fields(max_mmdb_fields).parse(arg, ','); fields = f.only(); } @@ -22,10 +22,11 @@ pub fn main() !void { std.debug.print(" Database: {s}\n", .{db_path}); std.debug.print("Opening database...\n", .{}); - var open_timer = try std.time.Timer.start(); - var db = try maxminddb.Reader.mmap(allocator, db_path, .{}); + const open_start = std.Io.Clock.Timestamp.now(io, .awake); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{}); defer db.close(); - const open_time_ms = @as(f64, @floatFromInt(open_timer.read())) / + const open_elapsed_ns: i64 = @intCast(open_start.untilNow(io).raw.nanoseconds); + const open_time_ms = @as(f64, @floatFromInt(open_elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_ms)); std.debug.print("Database opened successfully in {d} ms. Type: {s}\n", .{ open_time_ms, @@ -41,7 +42,7 @@ pub fn main() !void { defer cache.deinit(); std.debug.print("Starting benchmark...\n", .{}); - var timer = try std.time.Timer.start(); + const timer_start = std.Io.Clock.Timestamp.now(io, .awake); var it = try db.entries(network, .{}); @@ -51,7 +52,7 @@ pub fn main() !void { n += 1; } - const elapsed_ns = timer.read(); + const elapsed_ns: i64 = @intCast(timer_start.untilNow(io).raw.nanoseconds); const elapsed_s = @as(f64, @floatFromInt(elapsed_ns)) / @as(f64, @floatFromInt(std.time.ns_per_s)); diff --git a/build.zig b/build.zig index c386532..da44084 100644 --- a/build.zig +++ b/build.zig @@ -48,10 +48,11 @@ pub fn build(b: *std.Build) void { }), }); exe.root_module.addImport("maxminddb", maxminddb_module); - b.installArtifact(exe); + + const install = b.addInstallArtifact(exe, .{}); const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); + run_cmd.step.dependOn(&install.step); if (b.args) |args| { run_cmd.addArgs(args); } diff --git a/examples/inspect.zig b/examples/inspect.zig index 824f7f3..fd2b3b0 100644 --- a/examples/inspect.zig +++ b/examples/inspect.zig @@ -1,23 +1,23 @@ const std = @import("std"); const maxminddb = @import("maxminddb"); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - const allocator = gpa.allocator(); - defer _ = gpa.detectLeaks(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - const db_path = if (args.len > 1) args[1] else "test-data/test-data/GeoIP2-City-Test.mmdb"; - const ip = if (args.len > 2) args[2] else "89.160.20.128"; + var args = try std.process.Args.Iterator.initAllocator(init.minimal.args, allocator); + defer args.deinit(); + _ = args.skip(); + const db_path = args.next() orelse "test-data/test-data/GeoIP2-City-Test.mmdb"; + const ip = args.next() orelse "89.160.20.128"; - var db = try maxminddb.Reader.mmap(allocator, db_path, .{}); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{}); defer db.close(); const result = try db.lookup( maxminddb.any.Value, allocator, - try std.net.Address.parseIp(ip, 0), + try std.Io.net.IpAddress.parse(ip, 0), .{}, ) orelse { std.debug.print("{s}: not found\n", .{ip}); diff --git a/examples/lookup.zig b/examples/lookup.zig index 7e87073..832e18b 100644 --- a/examples/lookup.zig +++ b/examples/lookup.zig @@ -3,17 +3,16 @@ const maxminddb = @import("maxminddb"); const db_path = "test-data/test-data/GeoIP2-City-Test.mmdb"; -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - const allocator = gpa.allocator(); - defer _ = gpa.detectLeaks(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - var db = try maxminddb.Reader.open(allocator, db_path, .{}); + var db = try maxminddb.Reader.open(allocator, io, db_path, .{}); defer db.close(); // Note, for better performance use arena allocator and reset it after calling lookup(). // You won't need to call city.deinit() in that case. - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const city = try db.lookup(maxminddb.geoip2.City, allocator, ip, .{}) orelse return; defer city.deinit(); diff --git a/examples/scan.zig b/examples/scan.zig index 5098761..bba18ee 100644 --- a/examples/scan.zig +++ b/examples/scan.zig @@ -3,12 +3,11 @@ const maxminddb = @import("maxminddb"); const db_path = "test-data/test-data/GeoLite2-City-Test.mmdb"; -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - const allocator = gpa.allocator(); - defer _ = gpa.detectLeaks(); +pub fn main(init: std.process.Init) !void { + const allocator = init.gpa; + const io = init.io; - var db = try maxminddb.Reader.mmap(allocator, db_path, .{}); + var db = try maxminddb.Reader.mmap(allocator, io, db_path, .{}); defer db.close(); const network = if (db.metadata.ip_version == 4) diff --git a/src/any.zig b/src/any.zig index 2673e5c..d98dddd 100644 --- a/src/any.zig +++ b/src/any.zig @@ -88,7 +88,7 @@ pub const Value = union(enum) { fn expectJSON(expected: []const u8, v: Value) !void { var out: [4096]u8 = undefined; - var w = std.io.Writer.fixed(&out); + var w = std.Io.Writer.fixed(&out); try v.format(&w); try std.testing.expectEqualStrings(expected, out[0..w.end]); } diff --git a/src/maxminddb.zig b/src/maxminddb.zig index da28814..04051a0 100644 --- a/src/maxminddb.zig +++ b/src/maxminddb.zig @@ -120,6 +120,7 @@ fn expectEqualMaps( } const allocator = std.testing.allocator; +const io = std.testing.io; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualStrings = std.testing.expectEqualStrings; @@ -129,6 +130,7 @@ const expectError = std.testing.expectError; test "Metadata.decodeAs any.Value" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -139,8 +141,8 @@ test "Metadata.decodeAs any.Value" { const meta = try Metadata.decodeAs(any.Value, arena.allocator(), db.src); try expectEqualStrings("GeoLite2-City", meta.get("database_type").?.string); - try expectEqual(@as(u16, 6), meta.get("ip_version").?.uint16); - try expectEqual(@as(u16, 2), meta.get("binary_format_major_version").?.uint16); + try expectEqual(6, meta.get("ip_version").?.uint16); + try expectEqual(2, meta.get("binary_format_major_version").?.uint16); } test "reject invalid metadata" { @@ -150,12 +152,13 @@ test "reject invalid metadata" { test "Reader.open" { var db = try Reader.open( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); defer db.close(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geolite2.City, allocator, ip, .{})).?; defer got.deinit(); @@ -165,6 +168,7 @@ test "Reader.open" { test "reject index bits > 24" { try expectError(error.InvalidPrefixLen, Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{ .ipv4_index_first_n_bits = 25 }, )); @@ -173,13 +177,14 @@ test "reject index bits > 24" { test "reject invalid prefix length" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); defer db.close(); try expectError(error.InvalidPrefixLen, db.entries(.{ - .ip = try std.net.Address.parseIp("0.0.0.0", 0), + .ip = try std.Io.net.IpAddress.parse("0.0.0.0", 0), .prefix_len = 33, }, .{})); } @@ -187,13 +192,14 @@ test "reject invalid prefix length" { test "reject invalid node count" { try expectError( error.CorruptedTree, - Reader.mmap(allocator, "test-data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb", .{}), + Reader.mmap(allocator, io, "test-data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb", .{}), ); } test "reject IPv6 on IPv4-only database" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/MaxMind-DB-test-ipv4-32.mmdb", .{}, ); @@ -203,7 +209,7 @@ test "reject IPv6 on IPv4-only database" { const it = db.scan(any.Value, allocator, network, .{}); try expectError(error.IPv6AddressInIPv4Database, it); - const ip = try std.net.Address.parseIp("2001:db8::1", 0); + const ip = try std.Io.net.IpAddress.parse("2001:db8::1", 0); const result = db.lookup(any.Value, allocator, ip, .{}); try expectError(error.IPv6AddressInIPv4Database, result); } @@ -241,6 +247,7 @@ test DatabaseType { test "GeoLite2 Country" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-Country-Test.mmdb", .{}, ); @@ -248,7 +255,7 @@ test "GeoLite2 Country" { try expectEqual(DatabaseType.geolite_country, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geolite2.Country, allocator, ip, .{})).?; defer got.deinit(); @@ -281,7 +288,7 @@ test "GeoLite2 Country" { try expectEqualDeep(geolite2.Country.RepresentedCountry{}, got.value.represented_country); // Verify network masking for an IPv6 lookup. - const ipv6 = try std.net.Address.parseIp("2001:218:ffff:ffff:ffff:ffff:ffff:ffff", 0); + const ipv6 = try std.Io.net.IpAddress.parse("2001:218:ffff:ffff:ffff:ffff:ffff:ffff", 0); const got_v6 = (try db.lookup(geolite2.Country, allocator, ipv6, .{})).?; defer got_v6.deinit(); @@ -295,6 +302,7 @@ test "GeoLite2 Country" { test "GeoLite2 City" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -302,7 +310,7 @@ test "GeoLite2 City" { try expectEqual(DatabaseType.geolite_city, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geolite2.City, allocator, ip, .{})).?; defer got.deinit(); @@ -367,6 +375,7 @@ test "GeoLite2 City" { test "GeoLite2 ASN" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-ASN-Test.mmdb", .{}, ); @@ -374,7 +383,7 @@ test "GeoLite2 ASN" { try expectEqual(DatabaseType.geolite_asn, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geolite2.ASN, allocator, ip, .{})).?; defer got.deinit(); @@ -392,6 +401,7 @@ test "GeoLite2 ASN" { test "GeoIP2 Country" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Country-Test.mmdb", .{}, ); @@ -399,7 +409,7 @@ test "GeoIP2 Country" { try expectEqual(DatabaseType.geoip_country, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geoip2.Country, allocator, ip, .{})).?; defer got.deinit(); @@ -438,7 +448,7 @@ test "GeoIP2 Country" { got.value.traits, ); - const ip2 = try std.net.Address.parseIp("214.1.1.0", 0); + const ip2 = try std.Io.net.IpAddress.parse("214.1.1.0", 0); const got2 = (try db.lookup(geoip2.Country, allocator, ip2, .{})).?; defer got2.deinit(); @@ -448,12 +458,13 @@ test "GeoIP2 Country" { test "GeoIP2 Country RepresentedCountry" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Country-Test.mmdb", .{}, ); defer db.close(); - const ip = try std.net.Address.parseIp("202.196.224.0", 0); + const ip = try std.Io.net.IpAddress.parse("202.196.224.0", 0); const got = (try db.lookup(geoip2.Country, allocator, ip, .{})).?; defer got.deinit(); @@ -474,6 +485,7 @@ test "GeoIP2 Country RepresentedCountry" { test "GeoIP2 City" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-City-Test.mmdb", .{}, ); @@ -481,7 +493,7 @@ test "GeoIP2 City" { try expectEqual(DatabaseType.geoip_city, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(geoip2.City, allocator, ip, .{})).?; defer got.deinit(); @@ -549,7 +561,7 @@ test "GeoIP2 City" { got.value.traits, ); - const ip2 = try std.net.Address.parseIp("214.1.1.0", 0); + const ip2 = try std.Io.net.IpAddress.parse("214.1.1.0", 0); const got2 = (try db.lookup(geoip2.City, allocator, ip2, .{})).?; defer got2.deinit(); @@ -559,6 +571,7 @@ test "GeoIP2 City" { test "GeoIP2 Enterprise" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Enterprise-Test.mmdb", .{}, ); @@ -566,7 +579,7 @@ test "GeoIP2 Enterprise" { try expectEqual(DatabaseType.geoip_enterprise, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("74.209.24.0", 0); + const ip = try std.Io.net.IpAddress.parse("74.209.24.0", 0); const got = (try db.lookup(geoip2.Enterprise, allocator, ip, .{})).?; defer got.deinit(); @@ -649,7 +662,7 @@ test "GeoIP2 Enterprise" { got.value.traits, ); - const ip2 = try std.net.Address.parseIp("214.1.1.0", 0); + const ip2 = try std.Io.net.IpAddress.parse("214.1.1.0", 0); const got2 = (try db.lookup(geoip2.Enterprise, allocator, ip2, .{})).?; defer got2.deinit(); @@ -659,6 +672,7 @@ test "GeoIP2 Enterprise" { test "GeoIP2 ISP" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-ISP-Test.mmdb", .{}, ); @@ -666,7 +680,7 @@ test "GeoIP2 ISP" { try expectEqual(DatabaseType.geoip_isp, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("149.101.100.0", 0); + const ip = try std.Io.net.IpAddress.parse("149.101.100.0", 0); const got = (try db.lookup(geoip2.ISP, allocator, ip, .{})).?; defer got.deinit(); @@ -684,6 +698,7 @@ test "GeoIP2 ISP" { test "GeoIP2 Connection-Type" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Connection-Type-Test.mmdb", .{}, ); @@ -691,7 +706,7 @@ test "GeoIP2 Connection-Type" { try expectEqual(DatabaseType.geoip_connection_type, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("96.1.20.112", 0); + const ip = try std.Io.net.IpAddress.parse("96.1.20.112", 0); const got = (try db.lookup(geoip2.ConnectionType, allocator, ip, .{})).?; defer got.deinit(); @@ -704,6 +719,7 @@ test "GeoIP2 Connection-Type" { test "GeoIP2 Anonymous-IP" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb", .{}, ); @@ -711,7 +727,7 @@ test "GeoIP2 Anonymous-IP" { try expectEqual(DatabaseType.geoip_anonymous_ip, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("81.2.69.0", 0); + const ip = try std.Io.net.IpAddress.parse("81.2.69.0", 0); const got = (try db.lookup(geoip2.AnonymousIP, allocator, ip, .{})).?; defer got.deinit(); @@ -729,6 +745,7 @@ test "GeoIP2 Anonymous-IP" { test "GeoIP Anonymous-Plus" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP-Anonymous-Plus-Test.mmdb", .{}, ); @@ -736,7 +753,7 @@ test "GeoIP Anonymous-Plus" { try expectEqual(DatabaseType.geoip_anonymous_plus, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("1.2.0.1", 0); + const ip = try std.Io.net.IpAddress.parse("1.2.0.1", 0); const got = (try db.lookup(geoip2.AnonymousPlus, allocator, ip, .{})).?; defer got.deinit(); @@ -753,6 +770,7 @@ test "GeoIP Anonymous-Plus" { test "GeoIP2 DensityIncome" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-DensityIncome-Test.mmdb", .{}, ); @@ -760,7 +778,7 @@ test "GeoIP2 DensityIncome" { try expectEqual(DatabaseType.geoip_densityincome, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("5.83.124.123", 0); + const ip = try std.Io.net.IpAddress.parse("5.83.124.123", 0); const got = (try db.lookup(geoip2.DensityIncome, allocator, ip, .{})).?; defer got.deinit(); @@ -774,6 +792,7 @@ test "GeoIP2 DensityIncome" { test "GeoIP2 Domain" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Domain-Test.mmdb", .{}, ); @@ -781,7 +800,7 @@ test "GeoIP2 Domain" { try expectEqual(DatabaseType.geoip_domain, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("66.92.80.123", 0); + const ip = try std.Io.net.IpAddress.parse("66.92.80.123", 0); const got = (try db.lookup(geoip2.Domain, allocator, ip, .{})).?; defer got.deinit(); @@ -794,6 +813,7 @@ test "GeoIP2 Domain" { test "GeoIP2 IP-Risk" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-IP-Risk-Test.mmdb", .{}, ); @@ -801,7 +821,7 @@ test "GeoIP2 IP-Risk" { try expectEqual(DatabaseType.geoip_ip_risk, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("6.1.2.1", 0); + const ip = try std.Io.net.IpAddress.parse("6.1.2.1", 0); const got = (try db.lookup(geoip2.IPRisk, allocator, ip, .{})).?; defer got.deinit(); @@ -815,7 +835,7 @@ test "GeoIP2 IP-Risk" { }; try expectEqualDeep(want, got.value); - const ip2 = try std.net.Address.parseIp("214.2.3.5", 0); + const ip2 = try std.Io.net.IpAddress.parse("214.2.3.5", 0); const got2 = (try db.lookup(geoip2.IPRisk, allocator, ip2, .{})).?; defer got2.deinit(); @@ -832,6 +852,7 @@ test "GeoIP2 IP-Risk" { test "GeoIP2 Static-IP-Score" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Static-IP-Score-Test.mmdb", .{}, ); @@ -839,7 +860,7 @@ test "GeoIP2 Static-IP-Score" { try expectEqual(DatabaseType.geoip_static_ip_score, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("1.2.3.4", 0); + const ip = try std.Io.net.IpAddress.parse("1.2.3.4", 0); const got = (try db.lookup(geoip2.StaticIPScore, allocator, ip, .{})).?; defer got.deinit(); @@ -852,6 +873,7 @@ test "GeoIP2 Static-IP-Score" { test "GeoIP2 User-Count" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-User-Count-Test.mmdb", .{}, ); @@ -859,7 +881,7 @@ test "GeoIP2 User-Count" { try expectEqual(DatabaseType.geoip_user_count, DatabaseType.new(db.metadata.database_type)); - const ip = try std.net.Address.parseIp("1.2.3.4", 0); + const ip = try std.Io.net.IpAddress.parse("1.2.3.4", 0); const got = (try db.lookup(geoip2.UserCount, allocator, ip, .{})).?; defer got.deinit(); @@ -873,12 +895,13 @@ test "GeoIP2 User-Count" { test "lookup with field name filtering" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); defer db.close(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup( geolite2.City, @@ -903,6 +926,7 @@ test "lookup with field name filtering" { test "lookup with custom record" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -917,7 +941,7 @@ test "lookup with custom record" { } = .{}, }; - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(MyCity, allocator, ip, .{})).?; defer got.deinit(); @@ -928,12 +952,13 @@ test "lookup with custom record" { test "lookup with any.Value" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); defer db.close(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup(any.Value, allocator, ip, .{})).?; defer got.deinit(); @@ -951,12 +976,13 @@ test "lookup with any.Value" { test "lookup with any.Value and field name filtering" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); defer db.close(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const got = (try db.lookup( any.Value, allocator, @@ -980,6 +1006,7 @@ test "lookup with any.Value and field name filtering" { test "IPv4 index matches non-indexed find" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -987,6 +1014,7 @@ test "IPv4 index matches non-indexed find" { var db_idx = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{ .ipv4_index_first_n_bits = 16 }, ); @@ -1000,7 +1028,7 @@ test "IPv4 index matches non-indexed find" { "0.0.0.0", "255.255.255.255", }) |ip_str| { - const ip = try std.net.Address.parseIp(ip_str, 0); + const ip = try std.Io.net.IpAddress.parse(ip_str, 0); const entry1 = try db.find(ip, .{}); const entry2 = try db_idx.find(ip, .{}); @@ -1017,6 +1045,7 @@ test "IPv4 index matches non-indexed find" { test "scan returns all networks" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -1035,6 +1064,7 @@ test "scan returns all networks" { test "scan yields record when query prefix is narrower than record network" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-ASN-Test.mmdb", .{}, ); @@ -1051,7 +1081,7 @@ test "scan yields record when query prefix is narrower than record network" { try expectEqual(17, item.network.prefix_len); var out: [256]u8 = undefined; - var w = std.io.Writer.fixed(&out); + var w = std.Io.Writer.fixed(&out); try item.network.format(&w); try expectEqualStrings("89.160.0.0/17", out[0..w.end]); @@ -1064,6 +1094,7 @@ test "scan yields record when query prefix is narrower than record network" { test "scan yields record when start node is a data pointer" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb", .{}, ); @@ -1085,13 +1116,14 @@ test "scan yields record when start node is a data pointer" { test "find skips empty records by default" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb", .{}, ); defer db.close(); // 1.0.0.1 is in the db but its record is empty. - const ip = try std.net.Address.parseIp("1.0.0.1", 0); + const ip = try std.Io.net.IpAddress.parse("1.0.0.1", 0); // Empty records are skipped by default. try expect(try db.find(ip, .{}) == null); @@ -1102,6 +1134,7 @@ test "find skips empty records by default" { test "scan skips empty records" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoIP2-Anonymous-IP-Test.mmdb", .{}, ); @@ -1137,6 +1170,7 @@ test "scan skips empty records" { test "cache hit returns same value" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -1145,7 +1179,7 @@ test "cache hit returns same value" { var cache = try Cache(geolite2.City).init(allocator, .{ .size = 4 }); defer cache.deinit(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const entry = (try db.find(ip, .{})).?; // Cache miss, decodes. @@ -1163,6 +1197,7 @@ test "cache hit returns same value" { test "cache eviction" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -1172,12 +1207,12 @@ test "cache eviction" { var cache = try Cache(geolite2.City).init(allocator, .{ .size = 1 }); defer cache.deinit(); - const ip1 = try std.net.Address.parseIp("89.160.20.128", 0); + const ip1 = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const entry1 = (try db.find(ip1, .{})).?; _ = try cache.decode(&db, entry1, .{}); try expect(cache.get(entry1.pointer) != null); - const ip2 = try std.net.Address.parseIp("2001:218::", 0); + const ip2 = try std.Io.net.IpAddress.parse("2001:218::", 0); const entry2 = (try db.find(ip2, .{})).?; _ = try cache.decode(&db, entry2, .{}); @@ -1189,6 +1224,7 @@ test "cache eviction" { test "cache ring buffer wrap-around" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -1197,9 +1233,9 @@ test "cache ring buffer wrap-around" { var cache = try Cache(geolite2.City).init(allocator, .{ .size = 2 }); defer cache.deinit(); - const ip1 = try std.net.Address.parseIp("89.160.20.128", 0); - const ip2 = try std.net.Address.parseIp("2001:218::", 0); - const ip3 = try std.net.Address.parseIp("216.160.83.56", 0); + const ip1 = try std.Io.net.IpAddress.parse("89.160.20.128", 0); + const ip2 = try std.Io.net.IpAddress.parse("2001:218::", 0); + const ip3 = try std.Io.net.IpAddress.parse("216.160.83.56", 0); const entry1 = (try db.find(ip1, .{})).?; const entry2 = (try db.find(ip2, .{})).?; @@ -1218,6 +1254,7 @@ test "cache ring buffer wrap-around" { test "cache decode with field filtering" { var db = try Reader.mmap( allocator, + io, "test-data/test-data/GeoLite2-City-Test.mmdb", .{}, ); @@ -1226,7 +1263,7 @@ test "cache decode with field filtering" { var cache = try Cache(geolite2.City).init(allocator, .{ .size = 4 }); defer cache.deinit(); - const ip = try std.net.Address.parseIp("89.160.20.128", 0); + const ip = try std.Io.net.IpAddress.parse("89.160.20.128", 0); const entry = (try db.find(ip, .{})).?; const v = try cache.decode(&db, entry, .{ .only = &.{"city"} }); diff --git a/src/mmap.zig b/src/mmap.zig deleted file mode 100644 index 27a6388..0000000 --- a/src/mmap.zig +++ /dev/null @@ -1,173 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const windows = std.os.windows; - -// Maps a file into memory. -// The file handle is closed after mapping. -// The caller must call unmap() to release the mapped memory. -pub fn map(path: []const u8) ![]const u8 { - return switch (builtin.os.tag) { - .windows => mapWindows(path), - else => posixMap(path), - }; -} - -pub fn unmap(src: []const u8) void { - switch (builtin.os.tag) { - .windows => unmapWindows(src), - else => posixUnmap(src), - } -} - -// The file is closed after mmap (POSIX guarantees the mapping survives), -// see https://man7.org/linux/man-pages/man2/mmap.2.html. -fn posixMap(path: []const u8) ![]const u8 { - var f = try std.fs.cwd().openFile(path, .{}); - defer f.close(); - - const stat = try f.stat(); - if (stat.kind != .file) { - return error.NotFile; - } - - const file_size = stat.size; - if (file_size == 0) { - return error.FileEmpty; - } - - const page_size = std.heap.pageSize(); - const aligned_file_size = std.mem.alignForward(usize, file_size, page_size); - const src = try std.posix.mmap( - null, - aligned_file_size, - std.posix.PROT.READ, - .{ .TYPE = .SHARED }, - f.handle, - 0, - ); - - return src[0..file_size]; -} - -fn posixUnmap(src: []const u8) void { - const page_size = std.heap.pageSize(); - const aligned_src_len = std.mem.alignForward(usize, src.len, page_size); - std.posix.munmap(@alignCast(src.ptr[0..aligned_src_len])); -} - -// Maps a file into memory using the Win32 API. -// The file and mapping handles are closed after mapping. -// The mapped view remains valid until unmapped, see -// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile. -fn mapWindows(path: []const u8) ![]const u8 { - // Null-terminate the path for CreateFileA (that's what std.posix.toPosixPath() does). - var buf: [std.fs.max_path_bytes]u8 = undefined; - if (path.len >= buf.len) { - return error.NameTooLong; - } - - @memcpy(buf[0..path.len], path); - buf[path.len] = 0; - - // Open the file for reading. - const handle = CreateFileA( - buf[0..path.len :0], - windows.GENERIC_READ, - windows.FILE_SHARE_READ, - null, - windows.OPEN_EXISTING, - windows.FILE_ATTRIBUTE_NORMAL, - null, - ); - if (handle == windows.INVALID_HANDLE_VALUE) { - return switch (windows.kernel32.GetLastError()) { - .FILE_NOT_FOUND, .PATH_NOT_FOUND => error.FileNotFound, - .ACCESS_DENIED => error.AccessDenied, - .SHARING_VIOLATION => error.SharingViolation, - else => error.Unexpected, - }; - } - defer windows.CloseHandle(handle); - - var file_size: i64 = undefined; - if (windows.kernel32.GetFileSizeEx(handle, &file_size) == 0) { - return error.Unexpected; - } - if (file_size == 0) { - return error.FileEmpty; - } - - // Create a read-only file mapping object backed by the file. - // Passing 0 for size means "map the entire file". - const mapping = CreateFileMappingW( - handle, - null, - windows.PAGE_READONLY, - 0, - 0, - null, - ) orelse return error.Unexpected; - defer windows.CloseHandle(mapping); - - // Map the file into the process address space. - // The view stays valid after both handles are closed. - const ptr: [*]const u8 = @ptrCast( - MapViewOfFile( - mapping, - FILE_MAP_READ, - 0, - 0, - 0, - ) orelse return error.Unexpected, - ); - - return ptr[0..@intCast(file_size)]; -} - -fn unmapWindows(src: []const u8) void { - _ = UnmapViewOfFile(src.ptr); -} - -const FILE_MAP_READ: u32 = 4; - -// Zig's standard library doesn't provide bindings for the following Win32 functions. - -// Creates or opens a file or I/O device, see -// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea. -extern "kernel32" fn CreateFileA( - lpFileName: [*:0]const u8, - dwDesiredAccess: u32, - dwShareMode: u32, - lpSecurityAttributes: ?*anyopaque, - dwCreationDisposition: u32, - dwFlagsAndAttributes: u32, - hTemplateFile: ?windows.HANDLE, -) callconv(.winapi) windows.HANDLE; - -// Creates or opens a named or unnamed file mapping object for a specified file, see -// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw. -extern "kernel32" fn CreateFileMappingW( - hFile: windows.HANDLE, - lpFileMappingAttributes: ?*anyopaque, - flProtect: u32, - dwMaximumSizeHigh: u32, - dwMaximumSizeLow: u32, - lpName: ?[*:0]const u16, -) callconv(.winapi) ?windows.HANDLE; - -// Maps a view of a file mapping into the address space of a calling process, see -// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile. -extern "kernel32" fn MapViewOfFile( - hFileMappingObject: windows.HANDLE, - dwDesiredAccess: u32, - dwFileOffsetHigh: u32, - dwFileOffsetLow: u32, - dwNumberOfBytesToMap: usize, -) callconv(.winapi) ?*anyopaque; - -// Unmaps a mapped view of a file from the calling process's address space, see -// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile. -extern "kernel32" fn UnmapViewOfFile( - lpBaseAddress: *const anyopaque, -) callconv(.winapi) windows.BOOL; diff --git a/src/net.zig b/src/net.zig index 888c909..1115920 100644 --- a/src/net.zig +++ b/src/net.zig @@ -2,20 +2,20 @@ const std = @import("std"); // Represents an IP network. pub const Network = struct { - ip: std.net.Address, + ip: std.Io.net.IpAddress, prefix_len: usize = 0, pub const all_ipv4 = Network{ - .ip = std.net.Address.parseIp("0.0.0.0", 0) catch unreachable, + .ip = std.Io.net.IpAddress.parse("0.0.0.0", 0) catch unreachable, }; pub const all_ipv6 = Network{ - .ip = std.net.Address.parseIp("::", 0) catch unreachable, + .ip = std.Io.net.IpAddress.parse("::", 0) catch unreachable, }; // Parses an IP address or CIDR string like "1.0.0.0/24". pub fn parse(s: []const u8) !Network { - if (std.mem.indexOfScalar(u8, s, '/')) |sep| { - const ip = try std.net.Address.parseIp(s[0..sep], 0); + if (std.mem.findScalar(u8, s, '/')) |sep| { + const ip = try std.Io.net.IpAddress.parse(s[0..sep], 0); const prefix_len = try std.fmt.parseInt(usize, s[sep + 1 ..], 10); return .{ .ip = ip, @@ -23,21 +23,20 @@ pub const Network = struct { }; } - const ip = try std.net.Address.parseIp(s, 0); + const ip = try std.Io.net.IpAddress.parse(s, 0); return .{ .ip = ip, - .prefix_len = switch (ip.any.family) { - std.posix.AF.INET => 32, - std.posix.AF.INET6 => 128, - else => unreachable, + .prefix_len = switch (ip) { + .ip4 => 32, + .ip6 => 128, }, }; } pub fn format(self: Network, writer: anytype) !void { - switch (self.ip.any.family) { - std.posix.AF.INET => { - const b: *const [4]u8 = @ptrCast(&self.ip.in.sa.addr); + switch (self.ip) { + .ip4 => |v| { + const b = v.bytes; try writer.print( "{}.{}.{}.{}/{}", .{ @@ -49,8 +48,8 @@ pub const Network = struct { }, ); }, - std.posix.AF.INET6 => { - const b = self.ip.in6.sa.addr; + .ip6 => |v| { + const b = v.bytes; try writer.print( "{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}:{x:0>4}/{}", .{ @@ -66,7 +65,6 @@ pub const Network = struct { }, ); }, - else => unreachable, } } }; @@ -89,7 +87,7 @@ test "Network.format" { var buf: [128]u8 = undefined; for (tests) |tc| { const addr = Network{ - .ip = try std.net.Address.parseIp(tc.addr, 0), + .ip = try std.Io.net.IpAddress.parse(tc.addr, 0), .prefix_len = 64, }; const got = try std.fmt.bufPrint(&buf, "{f}", .{addr}); @@ -120,15 +118,10 @@ pub const IP = union(enum) { v4: [4]u8, v6: [16]u8, - pub fn init(addr: std.net.Address) IP { - return switch (addr.any.family) { - std.posix.AF.INET => .{ - .v4 = std.mem.asBytes(&addr.in.sa.addr).*, - }, - std.posix.AF.INET6 => .{ - .v6 = addr.in6.sa.addr, - }, - else => unreachable, + pub fn init(addr: std.Io.net.IpAddress) IP { + return switch (addr) { + .ip4 => |v| .{ .v4 = v.bytes }, + .ip6 => |v| .{ .v6 = v.bytes }, }; } @@ -184,20 +177,20 @@ pub const IP = union(enum) { pub fn network(self: IP, prefix_len: usize) Network { return switch (self) { .v4 => |b| .{ - .ip = std.net.Address.initIp4(b, 0), + .ip = .{ .ip4 = .{ .bytes = b, .port = 0 } }, .prefix_len = prefix_len, }, .v6 => |b| { // IPv4 in IPv6 form. if (std.mem.allEqual(u8, b[0..12], 0) and prefix_len >= 96) { return .{ - .ip = std.net.Address.initIp4(b[12..16].*, 0), + .ip = .{ .ip4 = .{ .bytes = b[12..16].*, .port = 0 } }, .prefix_len = prefix_len - 96, }; } return .{ - .ip = std.net.Address.initIp6(b, 0, 0, 0), + .ip = .{ .ip6 = .{ .bytes = b, .port = 0 } }, .prefix_len = prefix_len, }; }, @@ -268,7 +261,7 @@ test "IP.mask" { var buf: [64]u8 = undefined; for (tests) |tc| { - const ip = IP.init(try std.net.Address.parseIp(tc.addr, 0)); + const ip = IP.init(try std.Io.net.IpAddress.parse(tc.addr, 0)); const masked = ip.mask(tc.prefix_len).network(tc.prefix_len); const got = try std.fmt.bufPrint(&buf, "{f}", .{masked}); diff --git a/src/reader.zig b/src/reader.zig index 8fa7964..76c7c14 100644 --- a/src/reader.zig +++ b/src/reader.zig @@ -2,7 +2,6 @@ const std = @import("std"); const decoder = @import("decoder.zig"); const collection = @import("collection.zig"); -const memorymap = @import("mmap.zig"); const net = @import("net.zig"); pub const ReadError = error{ @@ -53,7 +52,7 @@ pub const Metadata = struct { } fn findMetadataStart(src: []const u8) !usize { - var metadata_start = std.mem.lastIndexOf(u8, src, start_marker) orelse { + var metadata_start = std.mem.findLast(u8, src, start_marker) orelse { return ReadError.MetadataStartNotFound; }; metadata_start += start_marker.len; @@ -88,7 +87,8 @@ pub const Reader = struct { // This lets us return the correct prefix length // without re-traversing the tree for terminal nodes in the index. ipv4_index_prefix_len: ?[]u8, - is_mapped: bool, + memory_map: ?std.Io.File.MemoryMap, + io: std.Io, arena: *std.heap.ArenaAllocator, pub const Options = struct { @@ -154,7 +154,12 @@ pub const Reader = struct { }; } - fn init(arena: *std.heap.ArenaAllocator, src: []const u8, options: Options) !Reader { + fn init( + arena: *std.heap.ArenaAllocator, + io: std.Io, + src: []const u8, + options: Options, + ) !Reader { const metadata = try Metadata.decode(arena.allocator(), src); switch (metadata.record_size) { @@ -180,7 +185,8 @@ pub const Reader = struct { .ipv4_index_first_n_bits = options.ipv4_index_first_n_bits, .ipv4_index = null, .ipv4_index_prefix_len = null, - .is_mapped = false, + .memory_map = null, + .io = io, .arena = arena, }; @@ -194,10 +200,12 @@ pub const Reader = struct { } /// Loads a MaxMind DB file into memory. - pub fn open(allocator: std.mem.Allocator, path: []const u8, options: Options) !Reader { - var f = try std.fs.cwd().openFile(path, .{}); - defer f.close(); - + pub fn open( + allocator: std.mem.Allocator, + io: std.Io, + path: []const u8, + options: Options, + ) !Reader { const arena = try allocator.create(std.heap.ArenaAllocator); errdefer { arena.deinit(); @@ -205,15 +213,39 @@ pub const Reader = struct { } arena.* = std.heap.ArenaAllocator.init(allocator); - const src = try f.readToEndAlloc(arena.allocator(), max_db_size); + const src = try std.Io.Dir.cwd().readFileAlloc( + io, + path, + arena.allocator(), + .limited(max_db_size), + ); - return try init(arena, src, options); + return try init(arena, io, src, options); } /// Maps a MaxMind DB file into memory. - pub fn mmap(allocator: std.mem.Allocator, path: []const u8, options: Options) !Reader { - const src = try memorymap.map(path); - errdefer memorymap.unmap(src); + pub fn mmap( + allocator: std.mem.Allocator, + io: std.Io, + path: []const u8, + options: Options, + ) !Reader { + var f = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer f.close(io); + + const file_size: usize = @intCast(try f.length(io)); + if (file_size == 0) { + return error.FileEmpty; + } + + var mm = try f.createMemoryMap( + io, + .{ + .len = file_size, + .protection = .{ .read = true }, + }, + ); + errdefer mm.destroy(io); const arena = try allocator.create(std.heap.ArenaAllocator); errdefer { @@ -222,8 +254,8 @@ pub const Reader = struct { } arena.* = std.heap.ArenaAllocator.init(allocator); - var r = try init(arena, src, options); - r.is_mapped = true; + var r = try init(arena, io, mm.memory, options); + r.memory_map = mm; return r; } @@ -236,8 +268,8 @@ pub const Reader = struct { self.arena.deinit(); allocator.destroy(self.arena); - if (self.is_mapped) { - memorymap.unmap(self.src); + if (self.memory_map) |*mm| { + mm.destroy(self.io); } } @@ -249,7 +281,7 @@ pub const Reader = struct { self: *Reader, T: type, allocator: std.mem.Allocator, - address: std.net.Address, + address: std.Io.net.IpAddress, options: QueryOptions, ) !?Result(T) { const entry = try self.find( @@ -264,7 +296,7 @@ pub const Reader = struct { /// Returns null if the IP address is not found or the record is empty. /// Empty records are skipped by default. /// Use include_empty_values = true to return them. - pub fn find(self: *Reader, address: std.net.Address, options: EntryOptions) !?Entry { + pub fn find(self: *Reader, address: std.Io.net.IpAddress, options: EntryOptions) !?Entry { const ip = net.IP.init(address); if (ip.bitCount() == 128 and self.metadata.ip_version == 4) { return ReadError.IPv6AddressInIPv4Database;