From 25a01845829e13eade22e44307d5e3d166514083 Mon Sep 17 00:00:00 2001 From: anshalshukla Date: Tue, 3 Feb 2026 00:31:43 +0530 Subject: [PATCH 1/2] feat: upgrade to zig 0.15.2 --- .github/workflows/ci.yml | 4 +- README.md | 3 +- build.zig | 8 +- build.zig.zon | 18 +- src/enr.zig | 47 ++++-- src/root.zig | 345 ++++++++++++++++++++++++++------------- 6 files changed, 288 insertions(+), 137 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b28831e..09c9dc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: env: - ZIG_VERSION: 0.14.1 + ZIG_VERSION: 0.15.2 jobs: build: @@ -20,7 +20,7 @@ jobs: - name: Set up Zig uses: mlugg/setup-zig@v2.0.5 with: - version: 0.14.1 + version: 0.15.2 - name: Formatting run: zig fmt --check --color on . diff --git a/README.md b/README.md index 224e6d8..a977ea1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # enr + Ethereum Node Records (ENR) utility in Zig. ## Prerequisites -- Zig 0.14.1 +- Zig 0.15.2 ## Building diff --git a/build.zig b/build.zig index 314d313..ce62996 100644 --- a/build.zig +++ b/build.zig @@ -14,7 +14,12 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); - const peer_id_dep = multiformats_dep.builder.dependency("peer_id", .{ + const multiaddr_dep = b.dependency("multiaddr", .{ + .target = target, + .optimize = optimize, + }); + + const peer_id_dep = b.dependency("peer_id", .{ .target = target, .optimize = optimize, }); @@ -28,6 +33,7 @@ pub fn build(b: *std.Build) !void { lib_mod.addImport("secp256k1", secp256k1_dep.module("secp256k1")); lib_mod.addImport("peer-id", peer_id_dep.module("peer-id")); lib_mod.addImport("multiformats", multiformats_dep.module("multiformats-zig")); + lib_mod.addImport("multiaddr", multiaddr_dep.module("multiaddr")); lib_mod.linkLibrary(secp256k1_dep.artifact("libsecp")); const lib = b.addLibrary(.{ diff --git a/build.zig.zon b/build.zig.zon index 039ba29..bb5b66e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,16 +5,24 @@ .fingerprint = 0x499ae5d2db8fed70, - .minimum_zig_version = "0.14.1", + .minimum_zig_version = "0.15.2", .dependencies = .{ .secp256k1 = .{ - .url = "git+https://github.com/zig-bitcoin/libsecp256k1-zig#b96922b375a601f3303bf43be1dceb3d101df6c6", - .hash = "secp256k1-0.0.0-AAAAAPPgAAADPOkS7brnO1B26LjbhZsxwz6Kx8a6jCwB", + .url = "git+https://github.com/blockblaz/libsecp256k1-zig#518a1370b66c6b575b4831c4a1a182f5b30b3d75", + .hash = "secp256k1-0.0.3-vtxi58DhAABoPpDgG3wagaDXiHHIDezLOPE0BI4V0bDS", }, .zmultiformats = .{ - .url = "git+https://github.com/zen-eth/multiformats-zig#92c90226ee8b14052718df724a1bc753996a6e07", - .hash = "zmultiformats-0.1.0-U2uyh94UBQCwBOXy5xZ9SZQJkQXZSiYCudWPWxNqjuE0", + .url = "git+https://github.com/zen-eth/multiformats-zig#ddf4f0af34ce8f87c2dc7df73a261738c3868093", + .hash = "zmultiformats-0.1.0-yjvchohoBADALnCylBLeQF7Vn0SKULmQ_sFOrhzgmvQO", + }, + .multiaddr = .{ + .url = "git+https://github.com/zen-eth/multiaddr-zig#c5e16a3a506cca8d37b732ea343818033782e874", + .hash = "multiaddr-0.1.0-Cjayds-5AAArNw0ghMgquaRygVf7-kEWQJlQGjVpZzZ0", + }, + .peer_id = .{ + .url = "git+https://github.com/blockblaz/peer-id#41f336b92315dcbc852345a2468487c697cfdcbe", + .hash = "peer_id-0.0.0-vHIk8ZdfAADctTzIgznksWwEzygVynEeSKDim42Pz-QL", }, }, .paths = .{ diff --git a/src/enr.zig b/src/enr.zig index 7db3aa2..7d65c55 100644 --- a/src/enr.zig +++ b/src/enr.zig @@ -3,8 +3,9 @@ const std = @import("std"); const rlp = @import("rlp.zig"); -const multiaddr = @import("multiformats").multiaddr; -const Multiaddr = multiaddr.Multiaddr; +const multiaddr_mod = @import("multiaddr"); +const multiaddr = multiaddr_mod.multiaddr; +const Multiaddr = multiaddr_mod.Multiaddr; const peer_id = @import("peer-id"); const PeerId = peer_id.PeerId; @@ -16,6 +17,8 @@ const secp256k1 = @import("secp256k1.zig"); const digest_size = secp256k1.digest_size; pub const max_enr_size = 300; +pub const enr_txt_prefix_len = 4; // "enr:" +pub const max_enr_txt_size = enr_txt_prefix_len + std.base64.url_safe_no_pad.Encoder.calcSize(max_enr_size); pub const signature_size = 64; // non-kv bytes // list_len_len, list_len, sig_len_len, sig_len, sig, seq, id_len, id_byte @@ -46,9 +49,22 @@ pub const KVs = struct { } pub fn put(self: *KVs, key: []const u8, value: []const u8) !void { - const owned_key = try self.allocator.dupe(u8, key); const owned_value = try self.allocator.dupe(u8, value); - try self.map.put(owned_key, owned_value); + errdefer self.allocator.free(owned_value); + + const gop = try self.map.getOrPut(key); + if (gop.found_existing) { + self.allocator.free(gop.value_ptr.*); + gop.value_ptr.* = owned_value; + } else { + const owned_key = self.allocator.dupe(u8, key) catch |err| { + // Remove the uninitialized slot that getOrPut created. + self.map.removeByPtr(gop.key_ptr); + return err; + }; + gop.key_ptr.* = owned_key; + gop.value_ptr.* = owned_value; + } } pub fn get(self: *const KVs, key: []const u8) ?[]const u8 { @@ -61,15 +77,16 @@ pub const KVs = struct { pub const Iterator = struct { keys: std.ArrayList([]const u8), + allocator: std.mem.Allocator, map: *const std.StringHashMap([]const u8), index: usize, pub fn init(kvs: *const KVs) Iterator { - var keys = std.ArrayList([]const u8).init(kvs.allocator); + var keys: std.ArrayList([]const u8) = .empty; var map_it = kvs.map.iterator(); while (map_it.next()) |entry| { - keys.append(entry.key_ptr.*) catch unreachable; + keys.append(kvs.allocator, entry.key_ptr.*) catch unreachable; } std.sort.heap([]const u8, keys.items, {}, struct { @@ -81,6 +98,7 @@ pub const KVs = struct { return Iterator{ .keys = keys, + .allocator = kvs.allocator, .map = &kvs.map, .index = 0, }; @@ -97,7 +115,7 @@ pub const KVs = struct { } pub fn deinit(self: *Iterator) void { - self.keys.deinit(); + self.keys.deinit(self.allocator); } }; @@ -326,6 +344,8 @@ pub const ENR = struct { const seq = std.mem.readVarInt(u64, seq_bytes, .big); var kvs = KVs.init(); + errdefer kvs.deinit(); + while (!list_reader.finished()) { const key = list_reader.read(.{ .short_string, .long_string }) catch unreachable; const value = list_reader.read(.{ .single_byte, .short_string, .long_string }) catch unreachable; @@ -362,13 +382,15 @@ pub const ENR = struct { } pub fn decodeTxtInto(enr: *ENR, source: []const u8) !void { - if (!std.mem.eql(u8, source[0..4], "enr:")) { + if (source.len < 4 or !std.mem.eql(u8, source[0..4], "enr:")) { return Error.BadPrefix; } - var buffer: [max_enr_size]u8 = undefined; const decoder = std.base64.url_safe_no_pad.Decoder; const size = try decoder.calcSizeForSlice(source[4..]); + if (size > max_enr_size) return Error.TooLong; + + var buffer: [max_enr_size]u8 = undefined; try decoder.decode(buffer[0..size], source[4..]); try decodeInto(enr, buffer[0..size]); @@ -810,12 +832,13 @@ fn kvsLen(kvs: *const KVs) usize { } pub fn decodeTxtIntoRlp(dest: []u8, source: []const u8) ![]u8 { - if (!std.mem.eql(u8, source[0..4], "enr:")) { + if (source.len < 4 or !std.mem.eql(u8, source[0..4], "enr:")) { return Error.BadPrefix; } const decoder = std.base64.url_safe_no_pad.Decoder; const size = try decoder.calcSizeForSlice(source[4..]); + if (size > dest.len) return Error.TooLong; try decoder.decode(dest[0..size], source[4..]); return dest[0..size]; @@ -981,7 +1004,7 @@ pub const EncodedENR = struct { } pub fn decodeTxtInto(source: []const u8) !Self { - if (!std.mem.eql(u8, source[0..4], "enr:")) { + if (source.len < 4 or !std.mem.eql(u8, source[0..4], "enr:")) { return Error.BadPrefix; } @@ -1297,7 +1320,7 @@ test "ENR test vector" { defer std.testing.allocator.free(ma); } - var enr3_encoded_buf: [max_enr_size]u8 = undefined; + var enr3_encoded_buf: [max_enr_txt_size]u8 = undefined; const enr3_encoded = try signable_enr3.encodeToTxt(&enr3_encoded_buf); var enr4: ENR = undefined; try ENR.decodeTxtInto(&enr4, enr3_encoded); diff --git a/src/root.zig b/src/root.zig index addd0b4..0e03614 100644 --- a/src/root.zig +++ b/src/root.zig @@ -31,20 +31,24 @@ pub const deinitGlobalSecp256k1Ctx = secp256k1.deinitSecp256k1Context; /// Returns a pointer to the global context for secp256k1 operations. pub const getGlobalSecp256k1Ctx = secp256k1.getSecp256k1Context; -const enr_prefix = "enr:"; +const max_enr_txt_size = enrlib.max_enr_txt_size; /// Loads ENR from the given file path pub fn loadENRFromDisk(enr: *ENR, file_path: []const u8) !void { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); - try readENR(file.reader(), enr); + var buf: [max_enr_txt_size]u8 = undefined; + var r = file.readerStreaming(&buf); + try readENR(&r.interface, enr); } /// Loads EncodedENR from the given file path pub fn loadEncodedENRFromDisk(encoded_enr: *EncodedENR, file_path: []const u8) !void { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); - try readEncodedENR(file.reader(), encoded_enr); + var buf: [max_enr_txt_size]u8 = undefined; + var r = file.readerStreaming(&buf); + try readEncodedENR(&r.interface, encoded_enr); } /// Saves an ENR to disk with given file path @@ -56,7 +60,10 @@ pub fn saveENRToDisk(file_path: []const u8, enr: *const ENR) !void { const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); - try writeENR(file.writer(), enr); + var buf: [max_enr_txt_size]u8 = undefined; + var w = file.writer(&buf); + try writeENR(&w.interface, enr); + try w.interface.flush(); } /// Saves a SignableENR to disk with given file path @@ -68,21 +75,30 @@ pub fn saveSignableENRToDisk(file_path: []const u8, signable_enr: *const Signabl const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); - try writeSignableENR(file.writer(), signable_enr); + var buf: [max_enr_txt_size]u8 = undefined; + var w = file.writer(&buf); + try writeSignableENR(&w.interface, signable_enr); + try w.interface.flush(); } /// Loads multiple ENRs from a single file -pub fn loadMultipleENRsFromDisk(enr_list: *std.ArrayList(ENR), file_path: []const u8, delimiter: u8) !void { +pub fn loadMultipleENRsFromDisk(enr_list: *std.ArrayList(ENR), allocator: std.mem.Allocator, file_path: []const u8, delimiter: u8) !void { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); - try readMultipleENRs(file.reader(), enr_list, delimiter); + // +1 to fit a max-size ENR token plus the delimiter byte in the streaming buffer. + var read_buf: [max_enr_txt_size + 1]u8 = undefined; + var r = file.readerStreaming(&read_buf); + try readMultipleENRs(&r.interface, enr_list, allocator, delimiter); } /// Loads multiple EncodedENRs from a single file -pub fn loadMultipleEncodedENRsFromDisk(enr_list: *std.ArrayList(EncodedENR), file_path: []const u8, delimiter: u8) !void { +pub fn loadMultipleEncodedENRsFromDisk(enr_list: *std.ArrayList(EncodedENR), allocator: std.mem.Allocator, file_path: []const u8, delimiter: u8) !void { const file = try std.fs.cwd().openFile(file_path, .{}); defer file.close(); - try readMultipleEncodedENRs(file.reader(), enr_list, delimiter); + // +1 to fit a max-size ENR token plus the delimiter byte in the streaming buffer. + var read_buf: [max_enr_txt_size + 1]u8 = undefined; + var r = file.readerStreaming(&read_buf); + try readMultipleEncodedENRs(&r.interface, enr_list, allocator, delimiter); } /// Saves multiple ENRs to a single file @@ -94,7 +110,10 @@ pub fn saveMultipleENRsToDisk(file_path: []const u8, enrs: []const ENR, delimite const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); - try writeMultipleENRs(file.writer(), enrs, delimiter); + var buf: [max_enr_txt_size]u8 = undefined; + var w = file.writer(&buf); + try writeMultipleENRs(&w.interface, enrs, delimiter); + try w.interface.flush(); } /// Saves multiple SignableENRs to a single file @@ -106,26 +125,29 @@ pub fn saveMultipleSignableENRsToDisk(file_path: []const u8, signable_enrs: []co const file = try std.fs.cwd().createFile(file_path, .{}); defer file.close(); - try writeMultipleSignableENRs(file.writer(), signable_enrs, delimiter); + var buf: [max_enr_txt_size]u8 = undefined; + var w = file.writer(&buf); + try writeMultipleSignableENRs(&w.interface, signable_enrs, delimiter); + try w.interface.flush(); } /// Generic function to write ENR to any writer pub fn writeENR(writer: anytype, enr: *const ENR) !void { - var txt_buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; + var txt_buffer: [max_enr_txt_size]u8 = undefined; const out = try enr.encodeToTxt(&txt_buffer); try writer.writeAll(out); } /// Generic function to write SignableENR to any writer pub fn writeSignableENR(writer: anytype, signable_enr: *const SignableENR) !void { - var txt_buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; + var txt_buffer: [max_enr_txt_size]u8 = undefined; const out = try signable_enr.encodeToTxt(&txt_buffer); try writer.writeAll(out); } /// Generic function to write multiple ENRs to any writer pub fn writeMultipleENRs(writer: anytype, enrs: []const ENR, delimiter: u8) !void { - var txt_buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; + var txt_buffer: [max_enr_txt_size]u8 = undefined; for (enrs, 0..) |*enr, i| { const out = try enr.encodeToTxt(&txt_buffer); try writer.writeAll(out); @@ -138,7 +160,7 @@ pub fn writeMultipleENRs(writer: anytype, enrs: []const ENR, delimiter: u8) !voi /// Generic function to write multiple SignableENRs to any writer pub fn writeMultipleSignableENRs(writer: anytype, signable_enrs: []const SignableENR, delimiter: u8) !void { - var txt_buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; + var txt_buffer: [max_enr_txt_size]u8 = undefined; for (signable_enrs, 0..) |*enr, i| { const out = try enr.encodeToTxt(&txt_buffer); try writer.writeAll(out); @@ -150,32 +172,49 @@ pub fn writeMultipleSignableENRs(writer: anytype, signable_enrs: []const Signabl } /// Generic function to read ENR from any reader -pub fn readENR(reader: anytype, enr: *ENR) !void { - var buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; - const bytes_read = try reader.readAll(&buffer); +pub fn readENR(reader: *std.Io.Reader, enr: *ENR) !void { + var buffer: [max_enr_txt_size]u8 = undefined; + const bytes_read = try readTxtIntoBuffer(reader, &buffer); const content = std.mem.trim(u8, buffer[0..bytes_read], " \t\r\n"); try ENR.decodeTxtInto(enr, content); } /// Generic function to read EncodedENR from any reader -pub fn readEncodedENR(reader: anytype, encoded_enr: *EncodedENR) !void { - var buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; - const bytes_read = try reader.readAll(&buffer); +pub fn readEncodedENR(reader: *std.Io.Reader, encoded_enr: *EncodedENR) !void { + var buffer: [max_enr_txt_size]u8 = undefined; + const bytes_read = try readTxtIntoBuffer(reader, &buffer); const content = std.mem.trim(u8, buffer[0..bytes_read], " \t\r\n"); encoded_enr.* = try EncodedENR.decodeTxtInto(content); } -/// Generic function to read multiple ENRs from any reader -pub fn readMultipleENRs(reader: anytype, enr_list: *std.ArrayList(ENR), delimiter: u8) !void { - var buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; +fn readTxtIntoBuffer(reader: *std.Io.Reader, buffer: []u8) !usize { + const bytes_read = reader.readSliceShort(buffer) catch return error.ReadFailed; + if (bytes_read == buffer.len) { + // Buffer is full. Drain any remaining bytes — trailing whitespace is + // allowed (common when files end with a newline), but any non-whitespace + // byte means the content genuinely exceeds the buffer. + var extra: [1]u8 = undefined; + while (true) { + const n = reader.readSliceShort(&extra) catch return error.ReadFailed; + if (n == 0) break; + switch (extra[0]) { + ' ', '\t', '\r', '\n' => {}, + else => return error.StreamTooLong, + } + } + } + return bytes_read; +} +/// Generic function to read multiple ENRs from any reader +pub fn readMultipleENRs(reader: *std.Io.Reader, enr_list: *std.ArrayList(ENR), allocator: std.mem.Allocator, delimiter: u8) !void { while (true) { - const line = reader.readUntilDelimiterOrEof(&buffer, delimiter) catch |err| switch (err) { + const line = reader.takeDelimiter(delimiter) catch |err| switch (err) { error.StreamTooLong => { - try reader.skipUntilDelimiterOrEof(delimiter); + _ = reader.discardDelimiterInclusive(delimiter) catch 0; continue; }, - else => return err, + error.ReadFailed => return error.ReadFailed, }; if (line == null) break; @@ -185,21 +224,19 @@ pub fn readMultipleENRs(reader: anytype, enr_list: *std.ArrayList(ENR), delimite var enr: ENR = undefined; try ENR.decodeTxtInto(&enr, trimmed); - try enr_list.append(enr); + try enr_list.append(allocator, enr); } } /// Generic function to read multiple EncodedENRs from any reader -pub fn readMultipleEncodedENRs(reader: anytype, enr_list: *std.ArrayList(EncodedENR), delimiter: u8) !void { - var buffer: [enrlib.max_enr_size + enr_prefix.len]u8 = undefined; - +pub fn readMultipleEncodedENRs(reader: *std.Io.Reader, enr_list: *std.ArrayList(EncodedENR), allocator: std.mem.Allocator, delimiter: u8) !void { while (true) { - const line = reader.readUntilDelimiterOrEof(&buffer, delimiter) catch |err| switch (err) { + const line = reader.takeDelimiter(delimiter) catch |err| switch (err) { error.StreamTooLong => { - try reader.skipUntilDelimiterOrEof(delimiter); + _ = reader.discardDelimiterInclusive(delimiter) catch 0; continue; }, - else => return err, + error.ReadFailed => return error.ReadFailed, }; if (line == null) break; @@ -208,7 +245,7 @@ pub fn readMultipleEncodedENRs(reader: anytype, enr_list: *std.ArrayList(Encoded if (trimmed.len == 0) continue; const encoded_enr = try EncodedENR.decodeTxtInto(trimmed); - try enr_list.append(encoded_enr); + try enr_list.append(allocator, encoded_enr); } } @@ -289,8 +326,8 @@ fn readTempMultipleEnrs(allocator: std.mem.Allocator, tmp_dir: *std.testing.TmpD const full_path = try std.fs.path.join(allocator, &.{ tmp_dir_path, file_name }); defer allocator.free(full_path); - var enr_list = std.ArrayList(ENR).init(allocator); - try loadMultipleENRsFromDisk(&enr_list, full_path, delimiter); + var enr_list: std.ArrayList(ENR) = .empty; + try loadMultipleENRsFromDisk(&enr_list, allocator, full_path, delimiter); return enr_list; } @@ -316,8 +353,8 @@ fn readTempMultipleEncodedEnrs(allocator: std.mem.Allocator, tmp_dir: *std.testi const full_path = try std.fs.path.join(allocator, &.{ tmp_dir_path, file_name }); defer allocator.free(full_path); - var enr_list = std.ArrayList(EncodedENR).init(allocator); - try loadMultipleEncodedENRsFromDisk(&enr_list, full_path, delimiter); + var enr_list: std.ArrayList(EncodedENR) = .empty; + try loadMultipleEncodedENRsFromDisk(&enr_list, allocator, full_path, delimiter); return enr_list; } @@ -350,8 +387,8 @@ fn writeTempMultipleSignableEnrs(tmp_dir: *std.testing.TmpDir, file_name: []cons /// Helper to compare two ENRs by encoding fn expectEqualEnrs(original: *ENR, loaded: *ENR) !void { - var original_encoded: [1024]u8 = undefined; - var loaded_encoded: [1024]u8 = undefined; + var original_encoded: [enrlib.max_enr_size]u8 = undefined; + var loaded_encoded: [enrlib.max_enr_size]u8 = undefined; try original.encodeInto(&original_encoded); try loaded.encodeInto(&loaded_encoded); @@ -367,7 +404,10 @@ fn expectEqualEnrs(original: *ENR, loaded: *ENR) !void { fn createTestFileWithContent(tmp_dir: *std.testing.TmpDir, file_name: []const u8, content: []const u8) !void { const file = try tmp_dir.dir.createFile(file_name, .{}); defer file.close(); - try file.writeAll(content); + var buf: [max_enr_txt_size]u8 = undefined; + var w = file.writer(&buf); + try w.interface.writeAll(content); + try w.interface.flush(); } test "single ENR file operations" { @@ -401,13 +441,13 @@ test "multiple ENRs with newline delimiter" { defer tmp_dir.cleanup(); const allocator = testing.allocator; - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); for (test_enrs[0..5]) |enr_txt| { var enr: ENR = undefined; try ENR.decodeTxtInto(&enr, enr_txt); - try enr_list.append(enr); + try enr_list.append(allocator, enr); } const test_file = "test_multiple_enrs_newline.txt"; @@ -415,7 +455,7 @@ test "multiple ENRs with newline delimiter" { try writeTempMultipleEnrs(&tmp_dir, test_file, enr_list.items, '\n'); var loaded_enr_list = try readTempMultipleEnrs(allocator, &tmp_dir, test_file, '\n'); - defer loaded_enr_list.deinit(); + defer loaded_enr_list.deinit(allocator); try testing.expectEqual(enr_list.items.len, loaded_enr_list.items.len); @@ -447,7 +487,9 @@ test "error handling - invalid ENR format" { const file_size = try read_file.getEndPos(); var enr_txt: [enrlib.max_enr_size]u8 = undefined; - _ = try read_file.readAll(enr_txt[0..file_size]); + var read_buf: [4096]u8 = undefined; + var rdr = read_file.reader(&read_buf); + rdr.interface.readSliceAll(enr_txt[0..file_size]) catch return error.ReadFailed; const result = ENR.decodeTxtInto(&enr, enr_txt[0..file_size]); try testing.expectError(error.BadPrefix, result); @@ -480,7 +522,7 @@ test "empty and whitespace handling in multiple ENRs" { try createTestFileWithContent(&tmp_dir, test_file, content); var enr_list = try readTempMultipleEnrs(allocator, &tmp_dir, test_file, '\n'); - defer enr_list.deinit(); + defer enr_list.deinit(allocator); try testing.expectEqual(@as(usize, 3), enr_list.items.len); } @@ -490,13 +532,13 @@ test "custom delimiter" { defer tmp_dir.cleanup(); const allocator = testing.allocator; - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); for (test_enrs[12..14]) |enr_txt| { var enr: ENR = undefined; try ENR.decodeTxtInto(&enr, enr_txt); - try enr_list.append(enr); + try enr_list.append(allocator, enr); } const test_file = "test_custom_delimiter.txt"; @@ -504,7 +546,7 @@ test "custom delimiter" { try writeTempMultipleEnrs(&tmp_dir, test_file, enr_list.items, '|'); var loaded_enr_list = try readTempMultipleEnrs(allocator, &tmp_dir, test_file, '|'); - defer loaded_enr_list.deinit(); + defer loaded_enr_list.deinit(allocator); try testing.expectEqual(enr_list.items.len, loaded_enr_list.items.len); } @@ -545,10 +587,10 @@ test "multiple EncodedENRs file operations" { try createTestFileWithContent(&tmp_dir, test_file, content); var loaded_encoded_enr_list = try readTempMultipleEncodedEnrs(allocator, &tmp_dir, test_file, '\n'); - defer loaded_encoded_enr_list.deinit(); + defer loaded_encoded_enr_list.deinit(allocator); var loaded_enr_list = try readTempMultipleEnrs(allocator, &tmp_dir, test_file, '\n'); - defer loaded_enr_list.deinit(); + defer loaded_enr_list.deinit(allocator); try testing.expectEqual(@as(usize, 5), loaded_encoded_enr_list.items.len); try testing.expectEqual(loaded_enr_list.items.len, loaded_encoded_enr_list.items.len); @@ -614,12 +656,12 @@ test "multiple SignableENRs file operations" { defer tmp_dir.cleanup(); const allocator = testing.allocator; - var signable_enr_list = std.ArrayList(SignableENR).init(allocator); + var signable_enr_list: std.ArrayList(SignableENR) = .empty; defer { for (signable_enr_list.items) |*item| { item.deinit(); } - signable_enr_list.deinit(); + signable_enr_list.deinit(allocator); } for (0..3) |i| { @@ -631,7 +673,7 @@ test "multiple SignableENRs file operations" { std.mem.writeInt(u16, &udp_bytes, @intCast(30303 + i), .big); try signable_enr.set("udp", &udp_bytes); - try signable_enr_list.append(signable_enr); + try signable_enr_list.append(allocator, signable_enr); } const test_file = "test_multiple_signable_enrs.txt"; @@ -639,7 +681,7 @@ test "multiple SignableENRs file operations" { try writeTempMultipleSignableEnrs(&tmp_dir, test_file, signable_enr_list.items, '\n'); var loaded_enr_list = try readTempMultipleEnrs(allocator, &tmp_dir, test_file, '\n'); - defer loaded_enr_list.deinit(); + defer loaded_enr_list.deinit(allocator); try testing.expectEqual(signable_enr_list.items.len, loaded_enr_list.items.len); @@ -670,10 +712,10 @@ test "writeENR to different writers" { try testing.expectEqualStrings(test_enrs[0], written_data); const allocator = testing.allocator; - var array_buffer = std.ArrayList(u8).init(allocator); - defer array_buffer.deinit(); + var array_buffer: std.ArrayList(u8) = .empty; + defer array_buffer.deinit(allocator); - try writeENR(array_buffer.writer(), &enr); + try writeENR(array_buffer.writer(allocator), &enr); try testing.expectEqualStrings(test_enrs[0], array_buffer.items); } @@ -702,23 +744,23 @@ test "writeSignableENR to different writers" { try testing.expectEqualStrings("192.168.1.100", (try parsed_enr.getIPStr(&ip_buffer)).?); const allocator = testing.allocator; - var array_buffer = std.ArrayList(u8).init(allocator); - defer array_buffer.deinit(); + var array_buffer: std.ArrayList(u8) = .empty; + defer array_buffer.deinit(allocator); - try writeSignableENR(array_buffer.writer(), &signable_enr); + try writeSignableENR(array_buffer.writer(allocator), &signable_enr); try testing.expectEqualStrings(written_data, array_buffer.items); } test "writeMultipleENRs to different writers" { const allocator = testing.allocator; - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); for (test_enrs[0..3]) |enr_txt| { var enr: ENR = undefined; try ENR.decodeTxtInto(&enr, enr_txt); - try enr_list.append(enr); + try enr_list.append(allocator, enr); } // Test with newline delimiter @@ -751,10 +793,10 @@ test "writeMultipleENRs to different writers" { // Test writing to ArrayList { - var array_buffer = std.ArrayList(u8).init(allocator); - defer array_buffer.deinit(); + var array_buffer: std.ArrayList(u8) = .empty; + defer array_buffer.deinit(allocator); - try writeMultipleENRs(array_buffer.writer(), enr_list.items, ','); + try writeMultipleENRs(array_buffer.writer(allocator), enr_list.items, ','); const expected = try std.mem.join(allocator, ",", test_enrs[0..3]); defer allocator.free(expected); @@ -765,12 +807,12 @@ test "writeMultipleENRs to different writers" { test "writeMultipleSignableENRs to different writers" { const allocator = testing.allocator; - var signable_enr_list = std.ArrayList(SignableENR).init(allocator); + var signable_enr_list: std.ArrayList(SignableENR) = .empty; defer { for (signable_enr_list.items) |*item| { item.deinit(); } - signable_enr_list.deinit(); + signable_enr_list.deinit(allocator); } for (0..2) |i| { @@ -782,7 +824,7 @@ test "writeMultipleSignableENRs to different writers" { std.mem.writeInt(u16, &udp_bytes, @intCast(5000 + i), .big); try signable_enr.set("udp", &udp_bytes); - try signable_enr_list.append(signable_enr); + try signable_enr_list.append(allocator, signable_enr); } var buffer: [4096]u8 = undefined; @@ -807,10 +849,10 @@ test "writeMultipleSignableENRs to different writers" { } try testing.expectEqual(@as(usize, 2), count); - var array_buffer = std.ArrayList(u8).init(allocator); - defer array_buffer.deinit(); + var array_buffer: std.ArrayList(u8) = .empty; + defer array_buffer.deinit(allocator); - try writeMultipleSignableENRs(array_buffer.writer(), signable_enr_list.items, '|'); + try writeMultipleSignableENRs(array_buffer.writer(allocator), signable_enr_list.items, '|'); try testing.expect(std.mem.indexOf(u8, array_buffer.items, "|") != null); } @@ -883,11 +925,11 @@ test "readENR from different readers" { var enr: ENR = undefined; try ENR.decodeTxtInto(&enr, test_enrs[0]); - // Test reading from fixed buffer stream + // Test reading from fixed reader { - var fbs = std.io.fixedBufferStream(test_enrs[0]); + var reader: std.Io.Reader = .fixed(test_enrs[0]); var read_enr: ENR = undefined; - try readENR(fbs.reader(), &read_enr); + try readENR(&reader, &read_enr); try expectEqualEnrs(&enr, &read_enr); } @@ -903,17 +945,19 @@ test "readENR from different readers" { defer file.close(); var read_enr: ENR = undefined; - try readENR(file.reader(), &read_enr); + var read_buf: [max_enr_txt_size]u8 = undefined; + var r = file.readerStreaming(&read_buf); + try readENR(&r.interface, &read_enr); try expectEqualEnrs(&enr, &read_enr); } } test "readEncodedENR from different readers" { - // Test reading from fixed buffer stream + // Test reading from fixed reader { - var fbs = std.io.fixedBufferStream(test_enrs[0]); + var reader: std.Io.Reader = .fixed(test_enrs[0]); var read_encoded_enr: EncodedENR = undefined; - try readEncodedENR(fbs.reader(), &read_encoded_enr); + try readEncodedENR(&reader, &read_encoded_enr); try testing.expectEqual(@as(u64, 3), read_encoded_enr.seq()); var ip_buffer: [16]u8 = undefined; @@ -923,14 +967,14 @@ test "readEncodedENR from different readers" { // Test reading from ArrayList buffer { const allocator = testing.allocator; - var array_buffer = std.ArrayList(u8).init(allocator); - defer array_buffer.deinit(); + var array_buffer: std.ArrayList(u8) = .empty; + defer array_buffer.deinit(allocator); - try array_buffer.appendSlice(test_enrs[0]); + try array_buffer.appendSlice(allocator, test_enrs[0]); - var fbs = std.io.fixedBufferStream(array_buffer.items); + var reader: std.Io.Reader = .fixed(array_buffer.items); var read_encoded_enr: EncodedENR = undefined; - try readEncodedENR(fbs.reader(), &read_encoded_enr); + try readEncodedENR(&reader, &read_encoded_enr); try testing.expectEqual(@as(u64, 3), read_encoded_enr.seq()); } @@ -942,13 +986,13 @@ test "readMultipleENRs from different readers" { const content = try std.mem.join(allocator, "\n", test_enrs[0..3]); defer allocator.free(content); - // Test reading from fixed buffer stream + // Test reading from fixed buffer { - var fbs = std.io.fixedBufferStream(content); - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var reader: std.Io.Reader = .fixed(content); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); - try readMultipleENRs(fbs.reader(), &enr_list, '\n'); + try readMultipleENRs(&reader, &enr_list, allocator, '\n'); try testing.expectEqual(@as(usize, 3), enr_list.items.len); @@ -968,10 +1012,12 @@ test "readMultipleENRs from different readers" { const file = try tmp_dir.dir.openFile(test_file, .{}); defer file.close(); - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var read_buf: [max_enr_txt_size]u8 = undefined; + var r = file.readerStreaming(&read_buf); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); - try readMultipleENRs(file.reader(), &enr_list, '\n'); + try readMultipleENRs(&r.interface, &enr_list, allocator, '\n'); try testing.expectEqual(@as(usize, 3), enr_list.items.len); } } @@ -982,11 +1028,11 @@ test "readMultipleEncodedENRs from different readers" { const content = try std.mem.join(allocator, "|", test_enrs[0..2]); defer allocator.free(content); - var fbs = std.io.fixedBufferStream(content); - var encoded_enr_list = std.ArrayList(EncodedENR).init(allocator); - defer encoded_enr_list.deinit(); + var reader: std.Io.Reader = .fixed(content); + var encoded_enr_list: std.ArrayList(EncodedENR) = .empty; + defer encoded_enr_list.deinit(allocator); - try readMultipleEncodedENRs(fbs.reader(), &encoded_enr_list, '|'); + try readMultipleEncodedENRs(&reader, &encoded_enr_list, allocator, '|'); try testing.expectEqual(@as(usize, 2), encoded_enr_list.items.len); @@ -1001,10 +1047,10 @@ test "read functions with whitespace handling" { // Test single ENR with surrounding whitespace { const content_with_whitespace = " \t " ++ test_enrs[0] ++ " \n "; - var fbs = std.io.fixedBufferStream(content_with_whitespace); + var reader: std.Io.Reader = .fixed(content_with_whitespace); var read_enr: ENR = undefined; - try readENR(fbs.reader(), &read_enr); + try readENR(&reader, &read_enr); try testing.expectEqual(@as(u64, 3), read_enr.seq); } @@ -1012,12 +1058,12 @@ test "read functions with whitespace handling" { // Test multiple ENRs with empty lines and whitespace { const content = test_enrs[0] ++ "\n\n \t \n" ++ test_enrs[1] ++ "\n \n"; - var fbs = std.io.fixedBufferStream(content); + var reader: std.Io.Reader = .fixed(content); - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); - try readMultipleENRs(fbs.reader(), &enr_list, '\n'); + try readMultipleENRs(&reader, &enr_list, allocator, '\n'); try testing.expectEqual(@as(usize, 2), enr_list.items.len); } } @@ -1028,34 +1074,101 @@ test "read functions error handling" { // Test reading invalid ENR format { const invalid_content = "invalid-enr-format"; - var fbs = std.io.fixedBufferStream(invalid_content); + var reader: std.Io.Reader = .fixed(invalid_content); var enr: ENR = undefined; - const result = readENR(fbs.reader(), &enr); + const result = readENR(&reader, &enr); try testing.expectError(error.BadPrefix, result); } // Test reading from empty reader { - const empty_content = ""; - var fbs = std.io.fixedBufferStream(empty_content); + var reader: std.Io.Reader = .fixed(""); + var enr_list: std.ArrayList(ENR) = .empty; + defer enr_list.deinit(allocator); - var enr_list = std.ArrayList(ENR).init(allocator); - defer enr_list.deinit(); - - try readMultipleENRs(fbs.reader(), &enr_list, '\n'); + try readMultipleENRs(&reader, &enr_list, allocator, '\n'); try testing.expectEqual(@as(usize, 0), enr_list.items.len); } } test "standard input simulation" { const stdin_content = test_enrs[0]; - var fbs = std.io.fixedBufferStream(stdin_content); + var reader: std.Io.Reader = .fixed(stdin_content); var enr: ENR = undefined; - try readENR(fbs.reader(), &enr); + try readENR(&reader, &enr); try testing.expectEqual(@as(u64, 3), enr.seq); var ip_buffer: [16]u8 = undefined; try testing.expectEqualStrings("4.157.240.54", (try enr.getIPStr(&ip_buffer)).?); } + +test "max ENR text size boundary - text encoding exceeds old buffer size" { + // Create a SignableENR and pad it with data so the binary encoding approaches + // max_enr_size (300). The base64 text form will then exceed 304 bytes (the old + // undersized buffer of max_enr_size + len("enr:")) but must fit within + // max_enr_txt_size. + const key_pair = KeyPair.generate(); + var signable_enr = SignableENR.create(key_pair); + defer signable_enr.deinit(); + + try signable_enr.set("ip", &[_]u8{ 10, 0, 0, 1 }); + var udp_bytes: [2]u8 = undefined; + std.mem.writeInt(u16, &udp_bytes, 9000, .big); + try signable_enr.set("udp", &udp_bytes); + + // Add padding data to push binary size close to max_enr_size. + // Use a conservative initial size, then verify the result. + // Start by adding a large pad value, then check encodedLen. + var pad_value: [enrlib.max_enr_size]u8 = undefined; + @memset(&pad_value, 0xAB); + + // Binary search for the right padding size that keeps us just under max_enr_size + var lo: usize = 100; + var hi: usize = 200; + while (lo < hi) { + const mid = (lo + hi + 1) / 2; + try signable_enr.set("pad", pad_value[0..mid]); + if (signable_enr.encodedLen() <= enrlib.max_enr_size) { + lo = mid; + } else { + hi = mid - 1; + } + } + try signable_enr.set("pad", pad_value[0..lo]); + + const binary_len = signable_enr.encodedLen(); + + // Binary must not exceed the limit + try testing.expect(binary_len <= enrlib.max_enr_size); + // Binary must be large enough that base64 text exceeds the old 304-byte buffer + const old_buffer_size = enrlib.max_enr_size + enrlib.enr_txt_prefix_len; // 304 + const txt_len = signable_enr.encodedTxtLen(); + try testing.expect(txt_len > old_buffer_size); + // Text must fit within the correctly computed max_enr_txt_size + try testing.expect(txt_len <= enrlib.max_enr_txt_size); + + // Round-trip: encode to text, then decode back + var txt_buf: [enrlib.max_enr_txt_size]u8 = undefined; + const txt = try signable_enr.encodeToTxt(&txt_buf); + try testing.expectEqual(txt_len, txt.len); + + // Decode the text back into an ENR and verify fields + var decoded_enr: ENR = undefined; + try ENR.decodeTxtInto(&decoded_enr, txt); + try testing.expectEqual(@as(u16, 9000), (try decoded_enr.getUDP()).?); + var decoded_ip_buf: [16]u8 = undefined; + try testing.expectEqualStrings("10.0.0.1", (try decoded_enr.getIPStr(&decoded_ip_buf)).?); + + // Also verify file round-trip with the near-max-size ENR + var tmp_dir = testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + try writeTempSignableEnr(&tmp_dir, "max_boundary.txt", &signable_enr); + + var loaded_enr: ENR = undefined; + try readTempEnr(&tmp_dir, "max_boundary.txt", &loaded_enr); + try testing.expectEqual(@as(u16, 9000), (try loaded_enr.getUDP()).?); + var loaded_ip_buf: [16]u8 = undefined; + try testing.expectEqualStrings("10.0.0.1", (try loaded_enr.getIPStr(&loaded_ip_buf)).?); +} From 16b719df8b7c691f8fdc8961fa41a2c2604cb668 Mon Sep 17 00:00:00 2001 From: anshalshukla Date: Fri, 6 Feb 2026 16:39:49 +0530 Subject: [PATCH 2/2] fix: make max_enr_txt_size public --- src/root.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root.zig b/src/root.zig index 0e03614..b913ddb 100644 --- a/src/root.zig +++ b/src/root.zig @@ -31,7 +31,7 @@ pub const deinitGlobalSecp256k1Ctx = secp256k1.deinitSecp256k1Context; /// Returns a pointer to the global context for secp256k1 operations. pub const getGlobalSecp256k1Ctx = secp256k1.getSecp256k1Context; -const max_enr_txt_size = enrlib.max_enr_txt_size; +pub const max_enr_txt_size = enrlib.max_enr_txt_size; /// Loads ENR from the given file path pub fn loadENRFromDisk(enr: *ENR, file_path: []const u8) !void {