// ©AngelaMos | 2026 // packet.zig const std = @import("std"); const builtin = @import("builtin"); pub const EthHdr = extern struct { dst: [6]u8, src: [6]u8, ethertype: u16, }; pub const Ipv4Hdr = extern struct { version_ihl: u8, tos: u8, total_len: u16, id: u16, flags_frag: u16, ttl: u8, protocol: u8, checksum: u16, src: u32, dst: u32, }; pub const TcpHdr = extern struct { src_port: u16, dst_port: u16, seq: u32, ack: u32, data_off_ns: u8, flags: u8, window: u16, checksum: u16, urgent: u16, }; pub const UdpHdr = extern struct { src_port: u16, dst_port: u16, length: u16, checksum: u16, }; pub const Ipv6Hdr = extern struct { version_tc_flow: u32, payload_len: u16, next_header: u8, hop_limit: u8, src: [16]u8, dst: [16]u8, }; comptime { std.debug.assert(@sizeOf(EthHdr) == 14); std.debug.assert(@sizeOf(Ipv4Hdr) == 20); std.debug.assert(@sizeOf(TcpHdr) == 20); std.debug.assert(@sizeOf(UdpHdr) == 8); std.debug.assert(@sizeOf(Ipv6Hdr) == 40); } pub const Addr = union(enum) { v4: u32, v6: [16]u8, pub fn eql(a: Addr, b: Addr) bool { if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; return switch (a) { .v4 => |x| x == b.v4, .v6 => |x| std.mem.eql(u8, &x, &b.v6), }; } pub fn order(a: Addr, b: Addr) std.math.Order { const fam_a: u2 = if (a == .v4) 0 else 1; const fam_b: u2 = if (b == .v4) 0 else 1; if (fam_a != fam_b) return std.math.order(fam_a, fam_b); return switch (a) { .v4 => |x| std.math.order(x, b.v4), .v6 => |x| std.mem.order(u8, &x, &b.v6), }; } }; pub fn checksum(bytes: []const u8) u16 { var sum: u32 = 0; var i: usize = 0; while (i + 1 < bytes.len) : (i += 2) { const word = (@as(u16, bytes[i]) << 8) | @as(u16, bytes[i + 1]); sum += word; } if (i < bytes.len) { sum += @as(u32, bytes[i]) << 8; } while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } return ~@as(u16, @truncate(sum)); } pub fn checksumSimd(bytes: []const u8) u16 { const lanes = comptime (std.simd.suggestVectorLength(u16) orelse 8); const stride = lanes * 2; const native_le = builtin.cpu.arch.endian() == .little; var acc: @Vector(lanes, u32) = @splat(0); var i: usize = 0; while (i + stride <= bytes.len) : (i += stride) { const block: [stride]u8 = bytes[i..][0..stride].*; var words: @Vector(lanes, u16) = @bitCast(block); if (native_le) words = @byteSwap(words); acc += @as(@Vector(lanes, u32), words); } var sum: u32 = @reduce(.Add, acc); while (i + 1 < bytes.len) : (i += 2) { sum += (@as(u32, bytes[i]) << 8) | @as(u32, bytes[i + 1]); } if (i < bytes.len) { sum += @as(u32, bytes[i]) << 8; } while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } return ~@as(u16, @truncate(sum)); } pub fn incrementalUpdate(old_check: u16, old_word: u16, new_word: u16) u16 { var sum: u32 = @as(u32, ~old_check) + @as(u32, ~old_word) + @as(u32, new_word); while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } return ~@as(u16, @truncate(sum)); } pub fn tcpChecksum(src_be: u32, dst_be: u32, segment: []const u8) u16 { var pseudo: [12]u8 = undefined; @memcpy(pseudo[0..4], std.mem.asBytes(&src_be)); @memcpy(pseudo[4..8], std.mem.asBytes(&dst_be)); pseudo[8] = 0; pseudo[9] = 6; std.mem.writeInt(u16, pseudo[10..12], @intCast(segment.len), .big); var sum: u32 = 0; var i: usize = 0; while (i + 1 < pseudo.len) : (i += 2) { sum += (@as(u32, pseudo[i]) << 8) | @as(u32, pseudo[i + 1]); } i = 0; while (i + 1 < segment.len) : (i += 2) { sum += (@as(u32, segment[i]) << 8) | @as(u32, segment[i + 1]); } if (i < segment.len) { sum += @as(u32, segment[i]) << 8; } while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } return ~@as(u16, @truncate(sum)); } pub fn pseudoChecksum6(src: [16]u8, dst: [16]u8, next_header: u8, payload: []const u8) u16 { var sum: u32 = 0; var i: usize = 0; while (i + 1 < src.len) : (i += 2) sum += (@as(u32, src[i]) << 8) | src[i + 1]; i = 0; while (i + 1 < dst.len) : (i += 2) sum += (@as(u32, dst[i]) << 8) | dst[i + 1]; const len: u32 = @intCast(payload.len); sum += (len >> 16) & 0xffff; sum += len & 0xffff; sum += next_header; i = 0; while (i + 1 < payload.len) : (i += 2) sum += (@as(u32, payload[i]) << 8) | payload[i + 1]; if (i < payload.len) sum += @as(u32, payload[i]) << 8; while (sum >> 16 != 0) sum = (sum & 0xffff) + (sum >> 16); return ~@as(u16, @truncate(sum)); } pub fn tcpChecksum6(src: [16]u8, dst: [16]u8, segment: []const u8) u16 { return pseudoChecksum6(src, dst, 6, segment); } pub fn udpChecksum(src_be: u32, dst_be: u32, segment: []const u8) u16 { var pseudo: [12]u8 = undefined; @memcpy(pseudo[0..4], std.mem.asBytes(&src_be)); @memcpy(pseudo[4..8], std.mem.asBytes(&dst_be)); pseudo[8] = 0; pseudo[9] = 17; std.mem.writeInt(u16, pseudo[10..12], @intCast(segment.len), .big); var sum: u32 = 0; var i: usize = 0; while (i + 1 < pseudo.len) : (i += 2) { sum += (@as(u32, pseudo[i]) << 8) | @as(u32, pseudo[i + 1]); } i = 0; while (i + 1 < segment.len) : (i += 2) { sum += (@as(u32, segment[i]) << 8) | @as(u32, segment[i + 1]); } if (i < segment.len) { sum += @as(u32, segment[i]) << 8; } while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } const folded: u16 = ~@as(u16, @truncate(sum)); return if (folded == 0) 0xffff else folded; } pub const TcpFlag = struct { pub const fin: u8 = 0x01; pub const syn: u8 = 0x02; pub const rst: u8 = 0x04; pub const psh: u8 = 0x08; pub const ack: u8 = 0x10; pub const urg: u8 = 0x20; }; pub const ScanType = enum { syn, fin, null_scan, xmas, maimon, ack, window, pub fn probeFlags(self: ScanType) u8 { return switch (self) { .syn => TcpFlag.syn, .fin => TcpFlag.fin, .null_scan => 0, .xmas => TcpFlag.fin | TcpFlag.psh | TcpFlag.urg, .maimon => TcpFlag.fin | TcpFlag.ack, .ack, .window => TcpFlag.ack, }; } pub fn cookieInAck(self: ScanType) bool { return switch (self) { .maimon, .ack, .window => true, else => false, }; } pub fn seqConsumed(self: ScanType) u32 { return switch (self) { .syn, .fin, .xmas => 1, else => 0, }; } pub fn parse(text: []const u8) ?ScanType { const map = .{ .{ "syn", ScanType.syn }, .{ "fin", ScanType.fin }, .{ "null", ScanType.null_scan }, .{ "xmas", ScanType.xmas }, .{ "maimon", ScanType.maimon }, .{ "ack", ScanType.ack }, .{ "window", ScanType.window }, }; inline for (map) |entry| { if (std.mem.eql(u8, text, entry[0])) return entry[1]; } return null; } }; const opt_eol: u8 = 0; const opt_nop: u8 = 1; const opt_mss: u8 = 2; const opt_mss_len: u8 = 4; const opt_wscale: u8 = 3; const opt_wscale_len: u8 = 3; const opt_sack_perm: u8 = 4; const opt_sack_perm_len: u8 = 2; const opt_ts: u8 = 8; const opt_ts_len: u8 = 10; const mss_ethernet_hi: u8 = 0x05; const mss_ethernet_lo: u8 = 0xb4; const wscale_linux: u8 = 7; const wscale_windows: u8 = 8; const wscale_macos: u8 = 6; const window_minimal: u16 = 1024; const window_linux: u16 = 64240; const window_windows: u16 = 64240; const window_macos: u16 = 65535; const syn_opts_masscan = [_]u8{ opt_mss, opt_mss_len, mss_ethernet_hi, mss_ethernet_lo }; const syn_opts_linux = [_]u8{ opt_mss, opt_mss_len, mss_ethernet_hi, mss_ethernet_lo, opt_sack_perm, opt_sack_perm_len, opt_ts, opt_ts_len, 0, 0, 0, 0, 0, 0, 0, 0, opt_nop, opt_wscale, opt_wscale_len, wscale_linux, }; const syn_opts_windows = [_]u8{ opt_mss, opt_mss_len, mss_ethernet_hi, mss_ethernet_lo, opt_nop, opt_wscale, opt_wscale_len, wscale_windows, opt_nop, opt_nop, opt_sack_perm, opt_sack_perm_len, }; const syn_opts_macos = [_]u8{ opt_mss, opt_mss_len, mss_ethernet_hi, mss_ethernet_lo, opt_nop, opt_wscale, opt_wscale_len, wscale_macos, opt_nop, opt_nop, opt_ts, opt_ts_len, 0, 0, 0, 0, 0, 0, 0, 0, opt_sack_perm, opt_sack_perm_len, opt_eol, opt_eol, }; pub const max_syn_options_len: usize = syn_opts_macos.len; pub const OsProfile = enum { none, masscan, linux, windows, macos, pub fn options(self: OsProfile) []const u8 { return switch (self) { .none => &.{}, .masscan => &syn_opts_masscan, .linux => &syn_opts_linux, .windows => &syn_opts_windows, .macos => &syn_opts_macos, }; } pub fn window(self: OsProfile) u16 { return switch (self) { .none, .masscan => window_minimal, .linux => window_linux, .windows => window_windows, .macos => window_macos, }; } pub fn tsValOffset(self: OsProfile) ?usize { return switch (self) { .linux => 8, .macos => 12, else => null, }; } pub fn variesIpId(self: OsProfile) bool { return switch (self) { .windows, .macos => true, else => false, }; } pub fn parse(text: []const u8) ?OsProfile { const map = .{ .{ "none", OsProfile.none }, .{ "masscan", OsProfile.masscan }, .{ "linux", OsProfile.linux }, .{ "windows", OsProfile.windows }, .{ "macos", OsProfile.macos }, }; inline for (map) |entry| { if (std.mem.eql(u8, text, entry[0])) return entry[1]; } return null; } }; pub fn optionKinds(opts: []const u8, out: []u8) usize { var i: usize = 0; var n: usize = 0; while (i < opts.len) { const kind = opts[i]; if (kind == opt_eol) break; if (n < out.len) { out[n] = kind; n += 1; } if (kind == opt_nop) { i += 1; continue; } if (i + 1 >= opts.len) break; const len = opts[i + 1]; if (len < 2) break; i += len; } return n; } comptime { std.debug.assert((@sizeOf(TcpHdr) + syn_opts_masscan.len) % 4 == 0); std.debug.assert((@sizeOf(TcpHdr) + syn_opts_linux.len) % 4 == 0); std.debug.assert((@sizeOf(TcpHdr) + syn_opts_windows.len) % 4 == 0); std.debug.assert((@sizeOf(TcpHdr) + syn_opts_macos.len) % 4 == 0); std.debug.assert(syn_opts_linux.len == 20); std.debug.assert(syn_opts_windows.len == 12); std.debug.assert(syn_opts_macos.len == 24); } test "header sizes are wire-exact" { try std.testing.expectEqual(@as(usize, 14), @sizeOf(EthHdr)); try std.testing.expectEqual(@as(usize, 20), @sizeOf(Ipv4Hdr)); try std.testing.expectEqual(@as(usize, 20), @sizeOf(TcpHdr)); try std.testing.expectEqual(@as(usize, 8), @sizeOf(UdpHdr)); } test "RFC 1071 checksum matches the canonical IPv4 KAT (0xb861)" { const hdr = [_]u8{ 0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7, }; try std.testing.expectEqual(@as(u16, 0xb861), checksum(&hdr)); } test "SIMD checksum matches the canonical IPv4 KAT (0xb861)" { const hdr = [_]u8{ 0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7, }; try std.testing.expectEqual(@as(u16, 0xb861), checksumSimd(&hdr)); } test "SIMD checksum equals scalar checksum for every length 0..256" { var prng = std.Random.DefaultPrng.init(0xC0FFEE_1624_517A); const rand = prng.random(); var buf: [256]u8 = undefined; var len: usize = 0; while (len <= 256) : (len += 1) { rand.bytes(buf[0..len]); try std.testing.expectEqual(checksum(buf[0..len]), checksumSimd(buf[0..len])); } } test "RFC 1624 incremental update matches the RFC section 4 worked example" { try std.testing.expectEqual(@as(u16, 0x0000), incrementalUpdate(0xDD2F, 0x5555, 0x3285)); } test "incremental update equals a full recompute for random single-word edits" { var prng = std.Random.DefaultPrng.init(0x1624_DEAD_BEEF_0001); const rand = prng.random(); var hdr: [20]u8 = undefined; var trial: usize = 0; while (trial < 4096) : (trial += 1) { rand.bytes(&hdr); std.mem.writeInt(u16, hdr[10..12], 0, .big); const old_check = checksum(&hdr); const word_index = rand.uintLessThan(usize, 9) * 2; const off = if (word_index >= 10) word_index + 2 else word_index; const old_word = std.mem.readInt(u16, hdr[off..][0..2], .big); const new_word = rand.int(u16); std.mem.writeInt(u16, hdr[off..][0..2], new_word, .big); const full = checksum(&hdr); try std.testing.expectEqual(full, incrementalUpdate(old_check, old_word, new_word)); } } test "tcpChecksum self-verifies: a segment with its correct checksum folds to 0" { var tcp = TcpHdr{ .src_port = std.mem.nativeToBig(u16, 54321), .dst_port = std.mem.nativeToBig(u16, 80), .seq = std.mem.nativeToBig(u32, 0xdead_beef), .ack = 0, .data_off_ns = 0x50, .flags = 0x02, .window = std.mem.nativeToBig(u16, 1024), .checksum = 0, .urgent = 0, }; const src = std.mem.nativeToBig(u32, 0x7f000001); const dst = std.mem.nativeToBig(u32, 0x7f000001); tcp.checksum = std.mem.nativeToBig(u16, tcpChecksum(src, dst, std.mem.asBytes(&tcp))); try std.testing.expectEqual(@as(u16, 0), tcpChecksum(src, dst, std.mem.asBytes(&tcp))); } test "Ipv6 header is wire-exact 40 bytes" { try std.testing.expectEqual(@as(usize, 40), @sizeOf(Ipv6Hdr)); } test "tcpChecksum6 equals a full RFC 1071 sum over the assembled IPv6 pseudo-header (independent path)" { const src = [16]u8{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; const dst = [16]u8{ 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; const seg = [_]u8{ 0xde, 0xad, 0x00, 0x50, 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0, 0x50, 0x02, 0x04, 0x00, 0, 0, 0, 0 }; var buf: [40 + seg.len]u8 = undefined; @memcpy(buf[0..16], &src); @memcpy(buf[16..32], &dst); std.mem.writeInt(u32, buf[32..36], @intCast(seg.len), .big); buf[36] = 0; buf[37] = 0; buf[38] = 0; buf[39] = 6; @memcpy(buf[40..], &seg); try std.testing.expectEqual(checksum(&buf), tcpChecksum6(src, dst, &seg)); } test "tcpChecksum6 self-verifies: a correctly-summed segment folds back to 0" { const src = [16]u8{ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; const dst = [16]u8{ 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; var tcp = TcpHdr{ .src_port = std.mem.nativeToBig(u16, 54321), .dst_port = std.mem.nativeToBig(u16, 443), .seq = std.mem.nativeToBig(u32, 0x1234_5678), .ack = 0, .data_off_ns = 0x50, .flags = 0x02, .window = std.mem.nativeToBig(u16, 65535), .checksum = 0, .urgent = 0, }; tcp.checksum = std.mem.nativeToBig(u16, tcpChecksum6(src, dst, std.mem.asBytes(&tcp))); try std.testing.expectEqual(@as(u16, 0), tcpChecksum6(src, dst, std.mem.asBytes(&tcp))); } test "udpChecksum self-verifies: a correct datagram re-sums to the 0xFFFF all-ones marker" { const src = std.mem.nativeToBig(u32, 0x7f000001); const dst = std.mem.nativeToBig(u32, 0x08080808); var seg: [12]u8 = undefined; var hdr = UdpHdr{ .src_port = std.mem.nativeToBig(u16, 40000), .dst_port = std.mem.nativeToBig(u16, 53), .length = std.mem.nativeToBig(u16, 12), .checksum = 0, }; @memcpy(seg[0..8], std.mem.asBytes(&hdr)); @memcpy(seg[8..12], "abcd"); const ck = udpChecksum(src, dst, &seg); try std.testing.expect(ck != 0); hdr.checksum = std.mem.nativeToBig(u16, ck); @memcpy(seg[0..8], std.mem.asBytes(&hdr)); try std.testing.expectEqual(@as(u16, 0xffff), udpChecksum(src, dst, &seg)); } test "udpChecksum maps a computed 0x0000 to 0xFFFF (IPv4 UDP quirk)" { try std.testing.expectEqual(@as(u16, 0xffff), udpChecksum(0, 0, &[_]u8{ 0xff, 0xec })); try std.testing.expect(udpChecksum(0, 0, &[_]u8{ 0xff, 0xec }) != 0); } test "the Linux SYN option chain decodes to the authoritative JA4T kind list 2-4-8-1-3" { var kinds: [16]u8 = undefined; const n = optionKinds(OsProfile.linux.options(), &kinds); try std.testing.expectEqualSlices(u8, &.{ 2, 4, 8, 1, 3 }, kinds[0..n]); } test "the Windows SYN option chain omits the timestamp (kinds 2-1-3-1-1-4)" { var kinds: [16]u8 = undefined; const n = optionKinds(OsProfile.windows.options(), &kinds); try std.testing.expectEqualSlices(u8, &.{ 2, 1, 3, 1, 1, 4 }, kinds[0..n]); for (kinds[0..n]) |k| try std.testing.expect(k != 8); } test "the macOS SYN option chain carries the timestamp before SACK (kinds 2-1-3-1-1-8-4)" { var kinds: [16]u8 = undefined; const n = optionKinds(OsProfile.macos.options(), &kinds); try std.testing.expectEqualSlices(u8, &.{ 2, 1, 3, 1, 1, 8, 4 }, kinds[0..n]); } test "the masscan profile sends exactly one MSS option and the fingerprintable 1024 window" { var kinds: [16]u8 = undefined; const n = optionKinds(OsProfile.masscan.options(), &kinds); try std.testing.expectEqualSlices(u8, &.{2}, kinds[0..n]); try std.testing.expectEqual(@as(u16, 1024), OsProfile.masscan.window()); } test "the bare profile sends no options" { try std.testing.expectEqual(@as(usize, 0), OsProfile.none.options().len); } test "every OS profile advertises the ethernet MSS 1460" { for ([_]OsProfile{ .masscan, .linux, .windows, .macos }) |p| { const opts = p.options(); try std.testing.expectEqual(opt_mss, opts[0]); try std.testing.expectEqual(@as(u16, 1460), std.mem.readInt(u16, opts[2..4], .big)); } } test "the timestamp offset points at four zero bytes inside the option chain" { inline for ([_]OsProfile{ .linux, .macos }) |p| { const off = p.tsValOffset().?; const opts = p.options(); try std.testing.expectEqual(opt_ts, opts[off - 2]); try std.testing.expectEqual(opt_ts_len, opts[off - 1]); try std.testing.expectEqual(@as(u32, 0), std.mem.readInt(u32, opts[off..][0..4], .big)); } try std.testing.expect(OsProfile.windows.tsValOffset() == null); } test "scan-type probe flags match the RFC 793 flag combinations" { try std.testing.expectEqual(TcpFlag.syn, ScanType.syn.probeFlags()); try std.testing.expectEqual(TcpFlag.fin, ScanType.fin.probeFlags()); try std.testing.expectEqual(@as(u8, 0), ScanType.null_scan.probeFlags()); try std.testing.expectEqual(TcpFlag.fin | TcpFlag.psh | TcpFlag.urg, ScanType.xmas.probeFlags()); try std.testing.expectEqual(TcpFlag.fin | TcpFlag.ack, ScanType.maimon.probeFlags()); try std.testing.expectEqual(TcpFlag.ack, ScanType.ack.probeFlags()); try std.testing.expectEqual(TcpFlag.ack, ScanType.window.probeFlags()); } test "ack-flag scans carry the cookie in the ack field, seq-scans in the seq field" { for ([_]ScanType{ .maimon, .ack, .window }) |st| try std.testing.expect(st.cookieInAck()); for ([_]ScanType{ .syn, .fin, .null_scan, .xmas }) |st| try std.testing.expect(!st.cookieInAck()); } test "seqConsumed reflects whether the probe advances the sequence space" { for ([_]ScanType{ .syn, .fin, .xmas }) |st| try std.testing.expectEqual(@as(u32, 1), st.seqConsumed()); for ([_]ScanType{ .null_scan, .maimon, .ack, .window }) |st| try std.testing.expectEqual(@as(u32, 0), st.seqConsumed()); } test "scan-type and OS-profile parsers round-trip the CLI spellings" { try std.testing.expectEqual(ScanType.null_scan, ScanType.parse("null").?); try std.testing.expectEqual(ScanType.window, ScanType.parse("window").?); try std.testing.expect(ScanType.parse("bogus") == null); try std.testing.expectEqual(OsProfile.macos, OsProfile.parse("macos").?); try std.testing.expect(OsProfile.parse("bogus") == null); } test "Addr.eql distinguishes families and values" { const a = Addr{ .v4 = 0x0a000001 }; const b = Addr{ .v4 = 0x0a000001 }; const c = Addr{ .v4 = 0x0a000002 }; const v6a = Addr{ .v6 = [_]u8{0} ** 15 ++ [_]u8{1} }; const v6b = Addr{ .v6 = [_]u8{0} ** 15 ++ [_]u8{1} }; const v6c = Addr{ .v6 = [_]u8{0} ** 15 ++ [_]u8{2} }; try std.testing.expect(a.eql(b)); try std.testing.expect(!a.eql(c)); try std.testing.expect(v6a.eql(v6b)); try std.testing.expect(!v6a.eql(v6c)); try std.testing.expect(!a.eql(v6a)); } test "Addr.order sorts v4 before v6, then by value" { const v4lo = Addr{ .v4 = 1 }; const v4hi = Addr{ .v4 = 2 }; const v6lo = Addr{ .v6 = [_]u8{0} ** 16 }; const v6hi = Addr{ .v6 = [_]u8{0} ** 15 ++ [_]u8{1} }; try std.testing.expectEqual(std.math.Order.lt, v4lo.order(v4hi)); try std.testing.expectEqual(std.math.Order.lt, v4hi.order(v6lo)); try std.testing.expectEqual(std.math.Order.lt, v6lo.order(v6hi)); try std.testing.expectEqual(std.math.Order.eq, v6hi.order(v6hi)); }