╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │199 } │ │200 │ │201 fn writePacket(self: *Conn, payload: []const u8) !void { │ │202 + try self.writePacketVec(&.{payload}); │ │203 + } │ │204 + │ │205 + fn writePacketVec(self: *Conn, parts: []const []const u8) !void { │ │206 self.setWaiting(true); │ │207 defer self.setWaiting(false); │ │204 - try self.sc_cipher.writePacket(self.io, self.writer, payload); │ │208 + try self.sc_cipher.writePacketVec(self.io, self.writer, parts);│ │209 // a peer that keeps reading is alive │ │210 recordActivity(self); │ │211 } │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │261 eof_reported: bool = false, │ │262 // resize seen by the background pump, returned by the next nextEvent call│ │263 pending_resize: ?PtySize = null, │ │260 - // reused per-chunk message buffer for writeChannel │ │261 - write_scratch: std.ArrayList(u8) = .empty, │ │264 │ │265 pub fn deinit(self: *SessionCtx) void { │ │266 self.incoming_buffer.deinit(self.conn.allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │265 - self.write_scratch.deinit(self.conn.allocator); │ │267 } │ │268 │ │269 fn incomingBytes(self: *const SessionCtx) []const u8 {│ ╰───────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │345 /// SessionReader). │ │346 fn writeChannel(self: *SessionCtx, bytes: []const u8, extended: bool) !void { │ │347 const conn = self.conn; │ │348 + │ │349 + // byte type, uint32 recipient_channel, [if extended: uint32 type_code], │ │350 + // uint32 data length │ │351 + var header: [13]u8 = undefined; │ │352 + header[0] = if (extended) SSH_MSG_CHANNEL_EXTENDED_DATA else SSH_MSG_CHANNEL_DATA;│ │353 + std.mem.writeInt(u32, header[1..5], self.channel.remote_id, .big); │ │354 + if (extended) std.mem.writeInt(u32, header[5..9], SSH_EXTENDED_DATA_STDERR, .big);│ │355 + const len_start: usize = if (extended) 9 else 5; │ │356 + │ │357 var rest = bytes; │ │358 while (rest.len > 0) { │ │359 if (self.closed or self.remote_closed) return error.RemoteClosed; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │365 const cap = @min(rest.len, self.channel.max_packet, self.channel.remote_window, max_packet_size); │ │366 const chunk_len: u32 = @intCast(cap); │ │367 │ │358 - self.write_scratch.clearRetainingCapacity(); │ │359 - try self.write_scratch.append(conn.allocator, if (extended) SSH_MSG_CHANNEL_EXTENDED_DATA else SSH_MSG_CHANNEL_DATA);│ │360 - try writeU32(&self.write_scratch, conn.allocator, self.channel.remote_id); │ │361 - if (extended) try writeU32(&self.write_scratch, conn.allocator, SSH_EXTENDED_DATA_STDERR); │ │362 - try writeStringField(&self.write_scratch, conn.allocator, rest[0..chunk_len]); │ │363 - try conn.writePacket(self.write_scratch.items); │ │368 + std.mem.writeInt(u32, header[len_start..][0..4], chunk_len, .big); │ │369 + try conn.writePacketVec(&.{ header[0 .. len_start + 4], rest[0..chunk_len] }); │ │370 │ │371 self.channel.remote_window -= chunk_len; │ │372 rest = rest[chunk_len..]; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1459 io: std.Io, │ │1460 writer: *std.Io.Writer, │ │1461 payload: []const u8, │ │1462 + ) !void { │ │1463 + try self.writePacketVec(io, writer, &.{payload}); │ │1464 + } │ │1465 + │ │1466 + /// the payload is the concatenation of `parts` │ │1467 + pub fn writePacketVec( │ │1468 + self: *Cipher, │ │1469 + io: std.Io, │ │1470 + writer: *std.Io.Writer, │ │1471 + parts: []const []const u8, │ │1472 ) !void { │ │1473 + var payload_len: usize = 0; │ │1474 + for (parts) |part| payload_len += part.len; │ │1475 + │ │1476 const block: usize = 8; │ │1458 - const initial_pad = block - ((1 + payload.len) % block); │ │1477 + const initial_pad = block - ((1 + payload_len) % block); │ │1478 const padding_len: u8 = @intCast(if (initial_pad < 4) initial_pad + block else initial_pad);│ │1460 - const body_len: u32 = @intCast(1 + payload.len + padding_len); │ │1479 + const body_len = 1 + payload_len + padding_len; │ │1480 + if (body_len > max_packet_len) return error.PacketTooLarge; │ │1481 │ │1482 const nonce = makeNonce(self.seq); │ │1483 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │1464 - // plaintext body = padding_length || payload || random padding. │ │1465 - // stack-allocated to avoid a heap alloc on every packet. │ │1466 - var body_buf: [max_packet_len]u8 = undefined; │ │1467 - if (body_len > body_buf.len) return error.PacketTooLarge; │ │1468 - const body = body_buf[0..body_len]; │ │1484 + // encrypted length || body || mac, contiguous so the packet goes out │ │1485 + // in one write. stack-allocated to avoid a heap alloc on every packet.│ │1486 + var packet_buf: [packet_buffer_size]u8 = undefined; │ │1487 + const packet = packet_buf[0 .. 4 + body_len + Poly1305.mac_length]; │ │1488 + const enc_length = packet[0..4]; │ │1489 + const body = packet[4..][0..body_len]; │ │1490 + │ │1491 + // plaintext body = padding_length || payload || random padding │ │1492 body[0] = padding_len; │ │1470 - @memcpy(body[1 .. 1 + payload.len], payload); │ │1471 - io.random(body[1 + payload.len ..]); │ │1493 + var end: usize = 1; │ │1494 + for (parts) |part| { │ │1495 + @memcpy(body[end..][0..part.len], part); │ │1496 + end += part.len; │ │1497 + } │ │1498 + io.random(body[end..]); │ │1499 │ │1500 // encrypt body in place with counter=1 │ │1501 ChaCha20.xor(body, body, 1, self.main_key, nonce); │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │1502 │ │1503 // encrypt length field with header key, counter=0 │ │1477 - var enc_length: [4]u8 = undefined; │ │1478 - var length_plain: [4]u8 = undefined; │ │1479 - std.mem.writeInt(u32, &length_plain, body_len, .big); │ │1480 - ChaCha20.xor(&enc_length, &length_plain, 0, self.header_key, nonce);│ │1504 + std.mem.writeInt(u32, enc_length, @intCast(body_len), .big); │ │1505 + ChaCha20.xor(enc_length, enc_length, 0, self.header_key, nonce); │ │1506 │ │1507 // poly1305 key = first 32 bytes of chacha20(K_2, nonce, counter=0) │ │1508 var poly_key: [Poly1305.key_length]u8 = undefined; │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │1510 │ │1511 // mac over encrypted_length || encrypted_body │ │1512 var poly = Poly1305.init(&poly_key); │ │1488 - poly.update(&enc_length); │ │1489 - poly.update(body); │ │1490 - var tag: [Poly1305.mac_length]u8 = undefined; │ │1491 - poly.final(&tag); │ │1513 + poly.update(packet[0 .. 4 + body_len]); │ │1514 + poly.final(packet[4 + body_len ..][0..Poly1305.mac_length]);│ │1515 │ │1493 - try writer.writeAll(&enc_length); │ │1494 - try writer.writeAll(body); │ │1495 - try writer.writeAll(&tag); │ │1516 + try writer.writeAll(packet); │ │1517 try writer.flush(); │ │1518 │ │1519 self.seq += 1; │ ╰──────────────────────────────────────────────────────────────────────────╯