╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │184 cs_cipher: Cipher, │ │185 sc_cipher: Cipher, │ │186 rekey: RekeyState, │ │187 + idle: ?*IdleState = null, │ │188 }; │ │189 │ │190 /// session bridge handed to the consumer's handleSession callback. exposes a│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │251 /// interactive sessions idle legitimately; tell the host's idle │ │252 /// watchdog to stand down for the rest of this connection. │ │253 pub fn exemptFromIdleTimeout(self: *SessionCtx) void { │ │253 - if (self.conn.cs_cipher.idle) |idle| idle.exempt.store(true, .monotonic);│ │254 + if (self.conn.idle) |idle| idle.exempt.store(true, .monotonic); │ │255 } │ │256 │ │257 /// pump SSH packets until something interesting (data / resize / close) │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │395 if (self.closed) return; │ │396 self.closed = true; │ │397 const conn = self.conn; │ │397 - if (conn.cs_cipher.idle) |idle| idle.closing.store(true, .release);│ │398 + if (conn.idle) |idle| idle.closing.store(true, .release); │ │399 │ │400 // once the peer has sent CHANNEL_CLOSE the channel is gone on its │ │401 // side, and these would be addressed to an id it no longer knows. │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────╮ │615 .session_id = kex.session_id, │ │616 .strict = kex.strict, │ │617 }, │ │618 + .idle = idle, │ │619 }; │ │618 - conn.cs_cipher.idle = idle; │ │620 │ │621 const fingerprint = runAuth(&conn) catch |err| {│ │622 disconnectOnError(&conn, err); │ ╰─────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │712 return msg_type == SSH_MSG_IGNORE or msg_type == SSH_MSG_UNIMPLEMENTED or msg_type == SSH_MSG_DEBUG;│ │713 } │ │714 │ │715 +/// packets handled during the plaintext phase — each direction's cipher picks │ │716 +/// up its sequence number where that direction left off. │ │717 +pub const PlainSeqs = struct { read: u64 = 0, written: u64 = 0 }; │ │718 + │ │719 // transport for KEX packets: the initial exchange runs in plaintext, a rekey │ │720 // runs under the current session ciphers. │ │721 pub const KexTransport = union(enum) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │717 - plain: *u64, // counts packets read so the caller can seed cipher seqnos │ │722 + plain: *PlainSeqs, │ │723 encrypted: struct { cs: *Cipher, sc: *Cipher }, │ │724 │ │725 // read one packet, skipping ignorable messages — unless strict kex is in│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │727 pub fn readPacket(self: KexTransport, allocator: std.mem.Allocator, reader: *std.Io.Reader, strict: bool) ![]u8 {│ │728 while (true) { │ │729 const packet = switch (self) { │ │725 - .plain => |count| packet: { │ │730 + .plain => |seqs| packet: { │ │731 const packet = try readPlainPacket(allocator, reader); │ │727 - count.* += 1; │ │732 + seqs.read += 1; │ │733 break :packet packet; │ │734 }, │ │735 .encrypted => |ciphers| try ciphers.cs.readPacket(allocator, reader), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │745 │ │746 fn writePacket(self: KexTransport, io: std.Io, writer: *std.Io.Writer, payload: []const u8) !void {│ │747 switch (self) { │ │743 - .plain => try writePlainPacket(io, writer, payload), │ │748 + .plain => |seqs| { │ │749 + try writePlainPacket(io, writer, payload); │ │750 + seqs.written += 1; │ │751 + }, │ │752 .encrypted => |ciphers| try ciphers.sc.writePacket(io, writer, payload), │ │753 } │ │754 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │792 } │ │793 │ │794 fn recordActivity(conn: *Conn) void { │ │787 - if (conn.cs_cipher.idle) |idle| _ = idle.activity.fetchAdd(1, .monotonic);│ │795 + if (conn.idle) |idle| _ = idle.activity.fetchAdd(1, .monotonic); │ │796 } │ │797 │ │798 // ---------------------------------------------------------------------------│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │879 const our_macs = [_][]const u8{}; // none — implicit in the AEAD cipher│ │880 const our_compression = [_][]const u8{"none"}; │ │881 │ │874 -/// Build a KEXINIT payload with the given algorithm name-lists │ │882 +/// build a KEXINIT payload with the given algorithm name-lists │ │883 pub fn buildKexInit( │ │884 io: std.Io, │ │885 allocator: std.mem.Allocator, │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯