╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │142 } │ │143 │ │144 fn runTui(handler: *const SessionHandler, sess: *ssh.SessionCtx, pty: ssh.PtySize) !void {│ │145 - const allocator = sess.allocator; │ │146 - const io = sess.io; │ │145 + const allocator = sess.conn.allocator; │ │146 + const io = sess.conn.io; │ │147 │ │148 // some clients (notably scripted ssh with no local controlling tty) │ │149 // allocate a pty without ever sending a non-zero size. fall back to a │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │252 }; │ │253 │ │254 fn runGitSession(handler: *const SessionHandler, sess: *ssh.SessionCtx, command: []const u8) !void { │ │255 - const allocator = sess.allocator; │ │256 - const io = sess.io; │ │255 + const allocator = sess.conn.allocator; │ │256 + const io = sess.conn.io; │ │257 │ │258 const parsed = parseGitCommand(allocator, command) catch return writeError(sess, "unsupported command (expected git-upload-pack or git-receive-pack)");│ │259 defer parsed.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │163 │ │164 /// next thing that arrived from the client. │ │165 pub const Event = union(enum) { │ │166 - data: []u8, // CHANNEL_DATA payload — caller owns and must free via sess.allocator │ │166 + data: []u8, // CHANNEL_DATA payload — caller owns and must free via sess.conn.allocator│ │167 resize: PtySize, // window-change request │ │168 close, // peer sent EOF or CHANNEL_CLOSE │ │169 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │170 │ │171 -/// session bridge handed to the consumer's handleSession callback. exposes a│ │172 -/// pumped event API plus a byte-write API; the byte stream goes out as │ │173 -/// CHANNEL_DATA packets respecting the channel's flow-control window. │ │174 -pub const SessionCtx = struct { │ │171 +/// per-connection transport state: the byte stream, both ciphers, and what a│ │172 +/// rekey needs. built once after the initial KEX and threaded through the │ │173 +/// auth and channel layers. │ │174 +pub const Conn = struct { │ │175 io: std.Io, │ │176 allocator: std.mem.Allocator, │ │177 reader: *std.Io.Reader, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │178 writer: *std.Io.Writer, │ │179 - cs_cipher: *Cipher, │ │180 - sc_cipher: *Cipher, │ │181 - rekey: *const RekeyState, │ │179 + cs_cipher: Cipher, │ │180 + sc_cipher: Cipher, │ │181 + rekey: RekeyState, │ │182 +}; │ │183 + │ │184 +/// session bridge handed to the consumer's handleSession callback. exposes a│ │185 +/// pumped event API plus a byte-write API; the byte stream goes out as │ │186 +/// CHANNEL_DATA packets respecting the channel's flow-control window. │ │187 +pub const SessionCtx = struct { │ │188 + conn: *Conn, │ │189 channel: *Channel, │ │190 /// SHA256 fingerprint of the pubkey this session authenticated with, │ │191 /// formatted as "SHA256:<base64-no-padding>" — same value the openssh │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │206 write_scratch: std.ArrayList(u8) = .empty, │ │207 │ │208 pub fn deinit(self: *SessionCtx) void { │ │202 - self.incoming_buffer.deinit(self.allocator); │ │203 - self.write_scratch.deinit(self.allocator); │ │209 + self.incoming_buffer.deinit(self.conn.allocator); │ │210 + self.write_scratch.deinit(self.conn.allocator); │ │211 } │ │212 │ │213 fn incomingBytes(self: *const SessionCtx) []const u8 {│ ╰───────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │235 if (payload.len > max_incoming_buffered - self.incoming_buffer.items.len) {│ │236 return error.IncomingBufferExceeded; │ │237 } │ │231 - try self.incoming_buffer.appendSlice(self.allocator, payload); │ │238 + try self.incoming_buffer.appendSlice(self.conn.allocator, payload); │ │239 } │ │240 │ │241 /// interactive sessions idle legitimately; tell the host's idle │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │242 /// watchdog to stand down for the rest of this connection. │ │243 pub fn exemptFromIdleTimeout(self: *SessionCtx) void { │ │237 - if (self.cs_cipher.idle) |idle| idle.exempt.store(true, .monotonic); │ │244 + if (self.conn.cs_cipher.idle) |idle| idle.exempt.store(true, .monotonic);│ │245 } │ │246 │ │247 /// pump SSH packets until something interesting (data / resize / close) │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │256 } │ │257 if (self.incomingBytes().len > 0) { │ │258 // hand the buffered bytes to the caller (caller owns) │ │252 - const payload = try self.allocator.dupe(u8, self.incomingBytes()); │ │259 + const payload = try self.conn.allocator.dupe(u8, self.incomingBytes());│ │260 self.consumeIncoming(payload.len); │ │261 return .{ .data = payload }; │ │262 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │287 /// effects — incoming CHANNEL_DATA goes into incoming_buffer for a future │ │288 /// SessionReader). │ │289 fn writeChannel(self: *SessionCtx, bytes: []const u8, extended: bool) !void {│ │290 + const conn = self.conn; │ │291 var rest = bytes; │ │292 while (rest.len > 0) { │ │293 while (self.channel.remote_window == 0) { │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯