╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │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) { │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │299 const chunk_len: u32 = @intCast(cap); │ │300 │ │301 self.write_scratch.clearRetainingCapacity(); │ │294 - try self.write_scratch.append(self.allocator, if (extended) SSH_MSG_CHANNEL_EXTENDED_DATA else SSH_MSG_CHANNEL_DATA);│ │295 - try writeU32(&self.write_scratch, self.allocator, self.channel.remote_id); │ │296 - if (extended) try writeU32(&self.write_scratch, self.allocator, SSH_EXTENDED_DATA_STDERR); │ │297 - try writeStringField(&self.write_scratch, self.allocator, rest[0..chunk_len]); │ │298 - try self.sc_cipher.writePacket(self.io, self.writer, self.write_scratch.items); │ │302 + try self.write_scratch.append(conn.allocator, if (extended) SSH_MSG_CHANNEL_EXTENDED_DATA else SSH_MSG_CHANNEL_DATA);│ │303 + try writeU32(&self.write_scratch, conn.allocator, self.channel.remote_id); │ │304 + if (extended) try writeU32(&self.write_scratch, conn.allocator, SSH_EXTENDED_DATA_STDERR); │ │305 + try writeStringField(&self.write_scratch, conn.allocator, rest[0..chunk_len]); │ │306 + try conn.sc_cipher.writePacket(conn.io, conn.writer, self.write_scratch.items); │ │307 │ │308 self.channel.remote_window -= chunk_len; │ │309 rest = rest[chunk_len..]; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │317 /// channel/global requests get a polite failure. driven by nextEvent, │ │318 /// SessionReader, and writeBytes (waiting for send-window). │ │319 fn processOneBackgroundPacket(self: *SessionCtx) !void { │ │312 - const packet = readSessionPacket(self.io, self.allocator, self.reader, self.writer, self.cs_cipher, self.sc_cipher, self.rekey) catch |err| switch (err) {│ │320 + const conn = self.conn; │ │321 + const packet = readSessionPacket(conn) catch |err| switch (err) { │ │322 error.EndOfStream => { │ │323 self.incoming_eof = true; │ │324 return; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯