╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ ssh.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
689 // tests don't drive any code path that calls stream, but the vtable 690 // requires a non-default impl. read into a small staging buffer and 691 // forward, so the implementation is correct if it ever fires. 692 const self: *PipeReader = @fieldParentPtr("interface", r); 693 var stage: [256]u8 = undefined; 694 const cap = limit.minInt(stage.len); 695 if (cap == 0) return 0; 696 const n = self.pipe.get(self.io, stage[0..cap], 1) catch |err| switch (err) { 697 error.Closed => return error.EndOfStream, 698 error.Canceled => return error.ReadFailed, 699 }; 700 return try w.write(stage[0..n]); 701 } 702 703 fn readVec(r: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize { 704 const self: *PipeReader = @fieldParentPtr("interface", r); 705 // vtable contract: if data[0] is empty, fill r.buffer[r.end..] instead. 706 const dest = if (data.len == 0 or data[0].len == 0) r.buffer[r.end..] else data[0]; 707 if (dest.len == 0) return 0; 708 const n = self.pipe.get(self.io, dest, 1) catch |err| switch (err) { 709 error.Closed => return error.EndOfStream, 710 error.Canceled => return error.ReadFailed, 711 }; 712 if (data.len == 0 or data[0].len == 0) { 713 r.end += n; 714 return 0; 715 } 716 return n; 717 } 718 }; 719 720 const PipeWriter = struct { 721 interface: std.Io.Writer, 722 io: std.Io, 723 pipe: *Pipe, 724 725 fn init(io: std.Io, pipe: *Pipe, buf: []u8) PipeWriter { 726 return .{ 727 .interface = .{ 728 .vtable = &.{ .drain = drain }, 729 .buffer = buf, 730 }, 731 .io = io, 732 .pipe = pipe, 733 }; 734 } 735 736 fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { 737 const self: *PipeWriter = @fieldParentPtr("interface", w); 738 const buffered = w.buffered(); 739 if (buffered.len > 0) self.pipe.putAll(self.io, buffered) catch return error.WriteFailed; 740 741 var extra: usize = 0; 742 if (data.len > 1) { 743 for (data[0 .. data.len - 1]) |chunk| { 744 if (chunk.len == 0) continue; 745 self.pipe.putAll(self.io, chunk) catch return error.WriteFailed; 746 extra += chunk.len; 747 } 748 } 749 if (data.len > 0) { 750 const pattern = data[data.len - 1]; 751 if (pattern.len > 0) { 752 var i: usize = 0; 753 while (i < splat) : (i += 1) { 754 self.pipe.putAll(self.io, pattern) catch return error.WriteFailed; 755 extra += pattern.len; 756 } 757 } 758 } 759 return w.consume(buffered.len + extra); 760 } 761 }; 762 763 // runs proto.handleConnection in a concurrent task (spawned via std.Io.concurrent). 764 // also acts as the session handler — captures the authenticated key's 765 // fingerprint and returns immediately, leaving runChannelLayer's wrap-up to 766 // send exit-status / EOF / CLOSE. records any error so the test can assert 767 // on it after awaiting the future. 768 const ServerTask = struct { 769 io: std.Io, 770 allocator: std.mem.Allocator, 771 host_key: *const proto.HostKey, 772 reader: *std.Io.Reader, 773 writer: *std.Io.Writer, 774 outgoing: *Pipe, // closed on exit so the client's reads return EOF 775 captured_fp: [proto.fingerprint_len]u8 = undefined, 776 captured_exec_buf: [256]u8 = undefined, 777 captured_exec_len: usize = 0, 778 result: ?anyerror = null, 779 780 pub fn handleSession(self: *ServerTask, sess: *proto.SessionCtx, request: proto.Request) !void { 781 @memcpy(&self.captured_fp, &sess.fingerprint); 782 switch (request) { 783 .exec => |cmd| { 784 // copy out of the protocol's stack-scoped buffer so the 785 // test can assert on it after the handler returns. 786 @memcpy(self.captured_exec_buf[0..cmd.len], cmd); 787 self.captured_exec_len = cmd.len; 788 }, 789 .shell => {}, 790 } 791 } 792 793 fn run(self: *ServerTask) void { 794 defer self.outgoing.close(self.io); 795 proto.handleConnection( 796 self.io, 797 self.allocator, 798 self.reader, 799 self.writer, 800 self.host_key, 801 self, 802 ) catch |e| { 803 self.result = e; 804 }; 805 } 806 }; 807 808 // deterministic keys so failures don't depend on RNG state. 809 810 fn newHostKey() !proto.HostKey { 811 const seed = [_]u8{0xAA} ** Ed25519.KeyPair.seed_length; 812 return .{ .keypair = try Ed25519.KeyPair.generateDeterministic(seed) }; 813 } 814 815 fn newUserKeypair() !Ed25519.KeyPair { 816 const seed = [_]u8{0xBB} ** Ed25519.KeyPair.seed_length; 817 return try Ed25519.KeyPair.generateDeterministic(seed); 818 } 819 820 fn newClientEphemeral() !X25519.KeyPair { 821 const seed = [_]u8{0xCC} ** X25519.seed_length; 822 return try X25519.KeyPair.generateDeterministic(seed); 823 } 824