╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │1920 if (pending_exec.*) |old| allocator.free(old); │ │1921 pending_exec.* = cmd; │ │1922 try replyChannelRequest(conn, ch, want_reply, true);│ │1913 - return .exec; │ │1923 + return .{ .exec = cmd }; │ │1924 } │ │1925 │ │1926 // unknown request kind — fail it │ ╰──────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │1985 if (r.buffered().len != 0) return error.InvalidChannelMessage; │ │1986 } │ │1987 │ │1978 -/// Send a CHANNEL_WINDOW_ADJUST if our receive window has dipped below half│ │1979 -/// of `budget`, the credit we're willing to have outstanding. Used both for│ │1988 +/// send a CHANNEL_WINDOW_ADJUST if our receive window has dipped below half│ │1989 +/// of `budget`, the credit we're willing to have outstanding. used both for│ │1990 /// the pre-session discard path and by SessionCtx after it consumes a │ │1991 /// CHANNEL_DATA payload. │ │1992 fn maybeRefillRecvWindow(conn: *Conn, ch: *Channel, budget: u32) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────╮ │src/test/ssh.zig│ ╰────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 95 try proto.writePlainPacket(io, &writer, &.{proto.SSH_MSG_IGNORE}); │ │ 96 │ │ 97 var reader = std.Io.Reader.fixed(writer.buffered()); │ │ 98 - var client_seq: u64 = 0; │ │ 99 - const transport = proto.KexTransport{ .plain = &client_seq }; │ │ 98 + var seqs: proto.PlainSeqs = .{}; │ │ 99 + const transport = proto.KexTransport{ .plain = &seqs }; │ │100 try std.testing.expectError(error.StrictKexViolation, transport.readPacket(allocator, &reader, true));│ │101 - try std.testing.expectEqual(@as(u64, 1), client_seq); │ │101 + try std.testing.expectEqual(@as(u64, 1), seqs.read); │ │102 } │ │103 │ │104 test "ignored packets do not count as connection activity" { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │112 try sender.writePacket(io, &encoded_writer, &.{proto.SSH_MSG_IGNORE});│ │113 │ │114 var idle = proto.IdleState{}; │ │115 - var receiver = proto.Cipher.init(&key_material, 0); │ │116 - receiver.idle = &idle; │ │115 + const receiver = proto.Cipher.init(&key_material, 0); │ │116 var reader = std.Io.Reader.fixed(encoded_writer.buffered()); │ │117 var output_buf: [1]u8 = undefined; │ │118 var output_writer = std.Io.Writer.fixed(&output_buf); │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │124 .cs_cipher = receiver, │ │125 .sc_cipher = sender, │ │126 .rekey = undefined, │ │127 + .idle = &idle, │ │128 }; │ │129 │ │130 try std.testing.expectError(error.EndOfStream, proto.readSessionPacket(&conn));│ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │168 defer c2s.close(io); // unblocks the server even on early test failure│ │169 │ │170 // STEP 1: VERSION EXCHANGE (RFC 4253 §4.2) │ │171 - // Each side writes one "SSH-protoversion-softwareversion\r\n" line. │ │172 - // The exact strings get hashed into the exchange hash later, so they │ │171 + // each side writes one "SSH-protoversion-softwareversion\r\n" line. │ │172 + // the exact strings get hashed into the exchange hash later, so they │ │173 // bind the session's identity. │ │174 try cw.writeAll(client_version ++ "\r\n"); │ │175 try cw.flush(); │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │179 const v_c = client_version; │ │180 │ │181 // STEP 2: ALGORITHM NEGOTIATION (RFC 4253 §7.1) │ │182 - // Both sides send SSH_MSG_KEXINIT listing supported algorithms, in │ │182 + // both sides send SSH_MSG_KEXINIT listing supported algorithms, in │ │183 // preference order, for: key exchange, host-key signature, c->s and │ │184 // s->c ciphers, c->s and s->c MACs (none with chacha20-poly1305 since │ │185 - // it's an AEAD), c->s and s->c compression, plus languages. The first │ │185 + // it's an AEAD), c->s and s->c compression, plus languages. the first │ │186 // algorithm in each slot that the *client* prefers and the server also │ │187 - // offers is selected. Both raw KEXINITs are hashed into the exchange │ │187 + // offers is selected. both raw KEXINITs are hashed into the exchange │ │188 // hash so tampering is caught at signature verification time. │ │189 const client_kex_init = try proto.buildKexInit(io, allocator, &.{"curve25519-sha256"}, &proto.our_host_key_algos, &proto.our_ciphers);│ │190 defer allocator.free(client_kex_init); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │195 try std.testing.expectEqual(@as(u8, proto.SSH_MSG_KEXINIT), server_kex_init[0]);│ │196 │ │197 // STEP 3: ECDH KEY EXCHANGE (RFC 8731) │ │198 - // Client generates a fresh X25519 keypair, sends its public ephemeral │ │199 - // q_c in SSH_MSG_KEX_ECDH_INIT. Server: │ │198 + // client generates a fresh X25519 keypair, sends its public ephemeral │ │199 + // q_c in SSH_MSG_KEX_ECDH_INIT. server: │ │200 // 1. generates its own ephemeral │ │201 // 2. computes K = X25519(server_priv, q_c) — shared secret │ │202 // 3. computes H = SHA256(V_C || V_S || I_C || I_S || K_S || q_c || q_s || K) │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │204 // 4. signs H with its long-term host key │ │205 // 5. replies KEX_ECDH_REPLY with K_S (host key blob), q_s, signature│ │206 // │ │207 - // Client verifies the signature against K_S — MITM defense. │ │207 + // client verifies the signature against K_S — MITM defense. │ │208 const client_eph = try newClientEphemeral(); │ │209 { │ │210 var pkt: std.ArrayList(u8) = .empty; │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │242 // K = X25519(client_priv, q_s) — same scalarmult on the other side. │ │243 const k = try X25519.scalarmult(client_eph.secret_key, server_eph_pub); │ │244 │ │245 - // Recompute H independently. If the server lied about any input the │ │245 + // recompute H independently. if the server lied about any input the │ │246 // signature won't verify against the trusted host key. │ │247 const h = try proto.computeExchangeHash(allocator, v_c, v_s, client_kex_init, server_kex_init, k_s, &client_eph.public_key, &server_eph_pub, &k, false);│ │248 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯