diff --git a/src/network/protocol/messages/lib.zig b/src/network/protocol/messages/lib.zig index dac5b76..1e1e2bd 100644 --- a/src/network/protocol/messages/lib.zig +++ b/src/network/protocol/messages/lib.zig @@ -3,12 +3,14 @@ pub const VersionMessage = @import("version.zig").VersionMessage; pub const VerackMessage = @import("verack.zig").VerackMessage; pub const MempoolMessage = @import("mempool.zig").MempoolMessage; pub const GetaddrMessage = @import("getaddr.zig").GetaddrMessage; +pub const PingMessage = @import("ping.zig").PingMessage; pub const MessageTypes = enum { Version, Verack, Mempool, Getaddr, + Ping, }; pub const Message = union(MessageTypes) { @@ -16,6 +18,7 @@ pub const Message = union(MessageTypes) { Verack: VerackMessage, Mempool: MempoolMessage, Getaddr: GetaddrMessage, + Ping: PingMessage, pub fn deinit(self: Message, allocator: std.mem.Allocator) void { switch (self) { @@ -23,6 +26,7 @@ pub const Message = union(MessageTypes) { .Verack => {}, .Mempool => {}, .Getaddr => {}, + .Ping => {}, } } pub fn checksum(self: Message) [4]u8 { @@ -31,6 +35,7 @@ pub const Message = union(MessageTypes) { .Verack => |m| m.checksum(), .Mempool => |m| m.checksum(), .Getaddr => |m| m.checksum(), + .Ping => |m| m.checksum(), }; } @@ -40,6 +45,7 @@ pub const Message = union(MessageTypes) { .Verack => |m| m.hintSerializedLen(), .Mempool => |m| m.hintSerializedLen(), .Getaddr => |m| m.hintSerializedLen(), + .Ping => |m| m.hintSerializedLen(), }; } }; diff --git a/src/network/protocol/messages/ping.zig b/src/network/protocol/messages/ping.zig new file mode 100644 index 0000000..7255519 --- /dev/null +++ b/src/network/protocol/messages/ping.zig @@ -0,0 +1,104 @@ +const std = @import("std"); +const protocol = @import("../lib.zig"); +const Sha256 = std.crypto.hash.sha2.Sha256; + +/// PingMessage represents the "Ping" message +/// +/// https://developer.bitcoin.org/reference/p2p_networking.html#ping +pub const PingMessage = struct { + nonce: u64, + + const Self = @This(); + + pub inline fn name() *const [12]u8 { + return protocol.CommandNames.PING ++ [_]u8{0} ** 8; + } + + /// Returns the message checksum + /// + /// Computed as `Sha256(Sha256(self.serialize()))[0..4]` + pub fn checksum(self: *const Self) [4]u8 { + var digest: [32]u8 = undefined; + var hasher = Sha256.init(.{}); + const writer = hasher.writer(); + self.serializeToWriter(writer) catch unreachable; // Sha256.write is infaible + hasher.final(&digest); + + Sha256.hash(&digest, &digest, .{}); + + return digest[0..4].*; + } + + /// Serialize a message as bytes and write them to the buffer. + /// + /// buffer.len must be >= than self.hintSerializedLen() + pub fn serializeToSlice(self: *const Self, buffer: []u8) !void { + var fbs = std.io.fixedBufferStream(buffer); + try self.serializeToWriter(fbs.writer()); + } + + /// Serialize a message as bytes and return them. + pub fn serialize(self: *const Self, allocator: std.mem.Allocator) ![]u8 { + const serialized_len = self.hintSerializedLen(); + + const ret = try allocator.alloc(u8, serialized_len); + errdefer allocator.free(ret); + + try self.serializeToSlice(ret); + + return ret; + } + + /// Serialize the message as bytes and write them to the Writer. + /// + /// `w` should be a valid `Writer`. + pub fn serializeToWriter(self: *const Self, w: anytype) !void { + comptime { + if (!std.meta.hasFn(@TypeOf(w), "writeInt")) @compileError("Expects r to have fn 'writeInt'."); + } + + try w.writeInt(u64, self.nonce, .little); + } + + /// Returns the hint of the serialized length of the message + pub inline fn hintSerializedLen(_: *const Self) usize { + // 8 bytes for nonce + return 8; + } + + pub fn deserializeSlice(allocator: std.mem.Allocator, bytes: []const u8) !Self { + var fbs = std.io.fixedBufferStream(bytes); + return try Self.deserializeReader(allocator, fbs.reader()); + } + + /// Deserialize a Reader bytes as a `VersionMessage` + pub fn deserializeReader(_: std.mem.Allocator, r: anytype) !Self { + comptime { + if (!std.meta.hasFn(@TypeOf(r), "readInt")) @compileError("Expects r to have fn 'readInt'."); + } + + var vm: Self = undefined; + + vm.nonce = try r.readInt(u64, .little); + return vm; + } + + pub inline fn new(nonce: u64) Self { + return .{ + .nonce = nonce, + }; + } +}; + +// TESTS +test "ok_fullflow_ping_message" { + const allocator = std.testing.allocator; + + { + const msg = PingMessage.new(0x1234567890abcdef); + const payload = try msg.serialize(allocator); + defer allocator.free(payload); + const deserialized_msg = try PingMessage.deserializeSlice(allocator, payload); + try std.testing.expectEqual(msg.nonce, deserialized_msg.nonce); + } +} diff --git a/src/network/wire/lib.zig b/src/network/wire/lib.zig index 0c8a0ff..2ce0e96 100644 --- a/src/network/wire/lib.zig +++ b/src/network/wire/lib.zig @@ -84,6 +84,8 @@ pub fn receiveMessage(allocator: std.mem.Allocator, r: anytype) !protocol.messag protocol.messages.Message{ .Mempool = try protocol.messages.MempoolMessage.deserializeReader(allocator, r) } else if (std.mem.eql(u8, &command, protocol.messages.GetaddrMessage.name())) protocol.messages.Message{ .Getaddr = try protocol.messages.GetaddrMessage.deserializeReader(allocator, r) } + else if (std.mem.eql(u8, &command, protocol.messages.PingMessage.name())) + protocol.messages.Message{ .Ping = try protocol.messages.PingMessage.deserializeReader(allocator, r) } else return error.UnknownMessage; errdefer message.deinit(allocator); @@ -137,9 +139,7 @@ test "ok_send_version_message" { switch (received_message) { .Version => |rm| try std.testing.expect(message.eql(&rm)), - .Verack => unreachable, - .Mempool => unreachable, - .Getaddr => unreachable, + else => unreachable, } } @@ -164,9 +164,7 @@ test "ok_send_verack_message" { switch (received_message) { .Verack => {}, - .Version => unreachable, - .Mempool => unreachable, - .Getaddr => unreachable, + else => unreachable, } } @@ -191,9 +189,32 @@ test "ok_send_mempool_message" { switch (received_message) { .Mempool => {}, - .Verack => unreachable, - .Version => unreachable, - .Getaddr => unreachable, + else => unreachable, + } +} + +test "ok_send_ping_message" { + const Config = @import("../../config/config.zig").Config; + const ArrayList = std.ArrayList; + const test_allocator = std.testing.allocator; + const PingMessage = protocol.messages.PingMessage; + + var list: std.ArrayListAligned(u8, null) = ArrayList(u8).init(test_allocator); + defer list.deinit(); + + const message = PingMessage.new(21000000); + + const writer = list.writer(); + try sendMessage(test_allocator, writer, Config.PROTOCOL_VERSION, Config.BitcoinNetworkId.MAINNET, message); + var fbs: std.io.FixedBufferStream([]u8) = std.io.fixedBufferStream(list.items); + const reader = fbs.reader(); + + const received_message = try receiveMessage(test_allocator, reader); + defer received_message.deinit(test_allocator); + + switch (received_message) { + .Ping => |ping_message| try std.testing.expectEqual(message.nonce, ping_message.nonce), + else => unreachable, } } diff --git a/src/node/ibd.zig b/src/node/ibd.zig index f030fc0..4ff5457 100644 --- a/src/node/ibd.zig +++ b/src/node/ibd.zig @@ -11,6 +11,6 @@ pub const IBD = struct { .p2p = p2p, }; } - + pub fn start(_: *IBD) !void {} };