╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │661 // Writer interface represents fan-out / repeated patterns). │ │662 var extra: usize = 0; │ │686 - if (data.len > 1) { │ │687 - for (data[0 .. data.len - 1]) |chunk| { │ │688 - if (chunk.len == 0) continue; │ │689 - self.sess.writeBytes(chunk) catch |err| return self.sess.failWrite(err); │ │690 - extra += chunk.len; │ │691 - } │ │663 + for (data[0 .. data.len - 1]) |chunk| { │ │664 + self.sess.writeBytes(chunk) catch |err| return self.sess.failWrite(err); │ │665 + extra += chunk.len; │ │666 } │ │693 - if (data.len > 0) { │ │694 - const pattern = data[data.len - 1]; │ │695 - if (pattern.len > 0) { │ │696 - var i: usize = 0; │ │697 - while (i < splat) : (i += 1) { │ │698 - self.sess.writeBytes(pattern) catch |err| return self.sess.failWrite(err);│ │699 - extra += pattern.len; │ │700 - } │ │701 - } │ │667 + const pattern = data[data.len - 1]; │ │668 + for (0..splat) |_| { │ │669 + self.sess.writeBytes(pattern) catch |err| return self.sess.failWrite(err); │ │670 + extra += pattern.len; │ │671 } │ │672 │ │673 // consume tells the Writer how many bytes (buffered + data) were │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │688 pub fn init(sess: *SessionCtx, buffer: []u8) SessionReader {│ │689 return .{ │ │690 .interface = .{ │ │722 - .vtable = &.{ │ │723 - .readVec = readVec, │ │724 - .stream = stream, │ │725 - }, │ │691 + .vtable = &.{ .stream = stream }, │ │692 .buffer = buffer, │ │693 .seek = 0, │ │694 .end = 0, │ ╰─────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │715 │ │716 return written; │ │717 } │ │752 - │ │753 - fn readVec(r: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize { │ │754 - const self: *SessionReader = @fieldParentPtr("interface", r); │ │755 - │ │756 - // wait until some input is buffered (or EOF) │ │757 - while (self.sess.incomingBytes().len == 0) { │ │758 - if (self.sess.incoming_eof) return error.EndOfStream; │ │759 - self.sess.processOneBackgroundPacket() catch |err| return self.sess.failRead(err); │ │760 - } │ │761 - │ │762 - const src = self.sess.incomingBytes(); │ │763 - │ │764 - // vtable.readVec contract: if data[0] is empty, write into r.buffer │ │765 - // instead and return 0. without this branch the caller spins, │ │766 - // calling readVec repeatedly with the same empty buffer. │ │767 - if (data.len == 0 or data[0].len == 0) { │ │768 - const dest = r.buffer[r.end..]; │ │769 - if (dest.len == 0) return 0; │ │770 - const copy = @min(dest.len, src.len); │ │771 - @memcpy(dest[0..copy], src[0..copy]); │ │772 - r.end += copy; │ │773 - self.sess.consumeIncoming(copy); │ │774 - self.sess.maybeRefillRecvWindowForBufferedInput() catch |err| return self.sess.failRead(err);│ │775 - return 0; │ │776 - } │ │777 - │ │778 - const dest = data[0]; │ │779 - const copy = @min(dest.len, src.len); │ │780 - @memcpy(dest[0..copy], src[0..copy]); │ │781 - self.sess.consumeIncoming(copy); │ │782 - self.sess.maybeRefillRecvWindowForBufferedInput() catch |err| return self.sess.failRead(err); │ │783 - return copy; │ │784 - } │ │718 }; │ │719 │ │720 // --------------------------------------------------------------------------- │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │725 /// pub fn handleSession(self, sess: *SessionCtx, request: Request) anyerror!void│ │726 /// invoked once the channel is open and a shell/exec request has arrived. │ │727 /// │ │795 -/// `reader` and `writer` are the bidirectional byte stream. `idle`, if │ │796 -/// given, is kept current for the host's watchdog. │ │728 +/// `reader` and `writer` are the bidirectional byte stream. `idle` is kept │ │729 +/// current for the host's watchdog. │ │730 pub fn handleConnection( │ │731 io: std.Io, │ │732 allocator: std.mem.Allocator, │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │733 reader: *std.Io.Reader, │ │734 writer: *std.Io.Writer, │ │735 host_key: *const HostKey, │ │803 - idle: ?*IdleState, │ │736 + idle: *IdleState, │ │737 handler: anytype, │ │738 ) !void { │ │739 const client_version = try exchangeVersions(allocator, reader, writer);│ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │740 defer allocator.free(client_version); │ │741 │ │809 - var kex = try runKex(io, allocator, reader, writer, host_key, client_version); │ │810 - defer std.crypto.secureZero(u8, std.mem.asBytes(&kex)); │ │811 - │ │812 - // post-KEX everything is encrypted with chacha20-poly1305@openssh.com │ │813 - var conn = Conn{ │ │814 - .io = io, │ │815 - .allocator = allocator, │ │816 - .reader = reader, │ │817 - .writer = writer, │ │818 - .cs_cipher = Cipher.init(&kex.cs_key, kex.next_cs_seq), │ │819 - .sc_cipher = Cipher.init(&kex.sc_key, kex.next_sc_seq), │ │820 - .rekey = .{ │ │821 - .host_key = host_key, │ │822 - .client_version = client_version, │ │823 - .session_id = kex.session_id, │ │824 - .strict = kex.strict, │ │825 - }, │ │826 - .idle = idle, │ │827 - }; │ │742 + var conn = try runKex(io, allocator, reader, writer, host_key, client_version, idle);│ │743 defer std.crypto.secureZero(u8, std.mem.asBytes(&conn.cs_cipher)); │ │744 defer std.crypto.secureZero(u8, std.mem.asBytes(&conn.sc_cipher)); │ │745 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │831 - const fingerprint = runAuth(&conn) catch |err| { │ │832 - disconnectOnError(&conn, err); │ │833 - return err; │ │834 - }; │ │746 + errdefer |err| disconnectOnError(&conn, err); │ │747 │ │836 - runChannelLayer(&conn, &fingerprint, handler) catch |err| { │ │837 - disconnectOnError(&conn, err); │ │838 - return err; │ │839 - }; │ │748 + const fingerprint = try runAuth(&conn); │ │749 + try runChannelLayer(&conn, &fingerprint, handler); │ │750 } │ │751 │ │752 /// tell the peer why we're hanging up (RFC 4253 §11.1), best effort. protocol│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────╮ │826 const padding_len = try reader.takeByte(); │ │827 if (padding_len < 4 or @as(u32, padding_len) + 1 > packet_len) return error.InvalidPadding;│ │828 const payload_len = packet_len - 1 - @as(u32, padding_len); │ │829 + // a payload starts with its message type │ │830 + if (payload_len == 0) return error.EmptyPacket; │ │831 │ │832 const payload = try reader.readAlloc(allocator, payload_len); │ │833 errdefer allocator.free(payload); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │863 }, │ │864 .encrypted => |ciphers| try ciphers.cs.readPacket(allocator, reader),│ │865 }; │ │954 - if (packet.len >= 1 and isIgnorableMsg(packet[0])) { │ │866 + if (isIgnorableMsg(packet[0])) { │ │867 allocator.free(packet); │ │868 if (strict) return error.StrictKexViolation; │ │869 continue; │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │888 pub fn readSessionPacket(conn: *Conn) ![]u8 { │ │889 while (true) { │ │890 const packet = try conn.readPacket(); │ │979 - if (packet.len >= 1 and isIgnorableMsg(packet[0])) {│ │891 + if (isIgnorableMsg(packet[0])) { │ │892 conn.allocator.free(packet); │ │893 continue; │ │894 } │ ╰─────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯