╭ changed ───────╮ │src/test/ssh.zig│ ╰────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │ 94 } │ │ 95 } │ │ 96 │ │ 97 -test "Escape timeout preserves partial SSH packets and fragmented arrow keys" { │ │ 97 +test "Escape timeout preserves partial packets, arrow keys, and buffered input" {│ │ 98 const io = std.testing.io; │ │ 99 var pipe_buffer: [256]u8 = undefined; │ │100 var pipe = Pipe.init(&pipe_buffer); │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │109 var terminal_writer = std.Io.Writer.fixed(&terminal_output); │ │110 var terminal = try StreamTerminal.init(std.testing.allocator, &terminal_writer, .{ .width = 80, .height = 24 });│ │111 defer terminal.deinit(); │ │112 - try terminal.writeBytes("\x1b"); │ │113 - │ │112 var encoded: [128]u8 = undefined; │ │113 var encoded_writer = std.Io.Writer.fixed(&encoded); │ │114 var sender = proto.Cipher.init(&TestSession.key, 0); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │117 - try sender.writePacket(io, &encoded_writer, &.{ proto.SSH_MSG_CHANNEL_DATA, 0, 0, 0, 0, 0, 0, 0, 1, 'x' }); │ │118 - // let the read consume only part of the encrypted length field │ │119 - try pipe.putAll(io, encoded_writer.buffered()[0..2]); │ │120 - try std.testing.expectEqual(null, try server_impl.nextTuiEvent(&ctx.sess, &terminal)); │ │121 - try std.testing.expectEqual(.escape, std.meta.activeTag(terminal.popKey().?)); │ │122 - try std.testing.expectEqual(null, terminal.popKey()); │ │123 - // rendering is allowed before the rest of the packet arrives │ │124 - try ctx.sess.writeBytes("render"); │ │125 - try pipe.putAll(io, encoded_writer.buffered()[2..]); │ │126 - const event = (try server_impl.nextTuiEvent(&ctx.sess, &terminal)).?; │ │127 - defer std.testing.allocator.free(event.data); │ │128 - try std.testing.expectEqualStrings("x", event.data); │ │129 - try std.testing.expectEqual(1, ctx.conn.cs_cipher.seq); │ │115 + // time out inside the length field and just before the tag is complete │ │116 + for ([_]bool{ false, true }) |near_tag| { │ │117 + encoded_writer = .fixed(&encoded); │ │118 + try sender.writePacket(io, &encoded_writer, &.{ proto.SSH_MSG_CHANNEL_DATA, 0, 0, 0, 0, 0, 0, 0, 1, 'x' });│ │119 + const split = if (near_tag) encoded_writer.buffered().len - 1 else 2; │ │120 + try terminal.writeBytes("\x1b"); │ │121 + try pipe.putAll(io, encoded_writer.buffered()[0..split]); │ │122 + try std.testing.expectEqual(null, try server_impl.nextTuiEvent(&ctx.sess, &terminal)); │ │123 + try std.testing.expectEqual(.escape, std.meta.activeTag(terminal.popKey().?)); │ │124 + try std.testing.expectEqual(null, terminal.popKey()); │ │125 + // rendering is allowed before the rest of the packet arrives │ │126 + try ctx.sess.writeBytes("render"); │ │127 + try pipe.putAll(io, encoded_writer.buffered()[split..]); │ │128 + const event = (try server_impl.nextTuiEvent(&ctx.sess, &terminal)).?; │ │129 + defer std.testing.allocator.free(event.data); │ │130 + try std.testing.expectEqualStrings("x", event.data); │ │131 + try std.testing.expectEqual(sender.seq, ctx.conn.cs_cipher.seq); │ │132 + } │ │133 │ │134 encoded_writer = .fixed(&encoded); │ │135 try sender.writePacket(io, &encoded_writer, &.{ proto.SSH_MSG_CHANNEL_DATA, 0, 0, 0, 0, 0, 0, 0, 1, '[' }); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │143 } │ │144 try std.testing.expectEqual(.arrow_up, std.meta.activeTag(terminal.popKey().?)); │ │145 try std.testing.expectEqual(null, terminal.popKey()); │ │146 + │ │147 + // a complete buffered packet needs neither a clock nor concurrent tasks │ │148 + encoded_writer = .fixed(&encoded); │ │149 + try sender.writePacket(io, &encoded_writer, &.{ proto.SSH_MSG_CHANNEL_EOF, 0, 0, 0, 0 });│ │150 + ctx.reader = .fixed(encoded_writer.buffered()); │ │151 + ctx.conn.reader = &ctx.reader; │ │152 + ctx.conn.io = .failing; │ │153 + ctx.conn.read_timeout = .{ .deadline = .{ .raw = .zero, .clock = .awake } }; │ │154 + try std.testing.expectEqual(.eof, std.meta.activeTag(try ctx.sess.nextEvent())); │ │155 } │ │156 │ │157 test "fingerprint format matches openssh layout" { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯