From b94d93dd820f2f47a1437180253ce0b616a911cd Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Wed, 26 Aug 2026 19:19:51 -0400 Subject: [PATCH] fix: DSR replies to bare CSI n (missing default status-report parameter) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DSR (CSI Ps n) declared no defaultParameters, so a bare CSI n (Ps omitted -> args[0]==0) matched neither the args[0]==5 (status) nor args[0]==6 (CPR) branch and produced no reply. A guest sending a bare status query then hangs waiting for \033[0n that never comes (§36 m4). Add defaultParameters returning {5}. CSIManager substitutes a 0/omitted slot with the handler's declared default BEFORE execute runs (it iterates defaults.length, so the default fires even at argCount==0), so bare CSI n now resolves to Ps=5 -> \033[0n. This matches ECMA-48 / common terminal behavior (Ps=0 treated as Ps=5 for status). CSI 5 n is unchanged (args[0] ==5, no substitution); CSI 6 n (CPR) is unchanged; CSI ? n (DECDSR) path is untouched. Tests: dsrBareCsiNRepliesStatus (bare CSI n -> \033[0n) and dsrExplicit- FiveStillRepliesStatus (explicit 5 unchanged), reading the reply via terminal.io.getInput(). Revert-and-fail proven: removing defaultParameters fails the bare-CSI-n test (no reply -> assertNotNull fails). 102/0. QA: clean build (compileJava + compileTestJava ran, not cached), full suite green; PMD main 0 (preserved zero-violation baseline), Checkstyle 0 in touched files (the 1 main warning is pre-existing in DeltaFrameCodec), SpotBugs delta 0. Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review. --- .../common/vm/terminal/escapes/csi/DSR.java | 8 ++++++ .../vm/terminal/TerminalBufferTest.java | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/li/cil/oc2/common/vm/terminal/escapes/csi/DSR.java b/src/main/java/li/cil/oc2/common/vm/terminal/escapes/csi/DSR.java index 3ef6a558..706f3458 100644 --- a/src/main/java/li/cil/oc2/common/vm/terminal/escapes/csi/DSR.java +++ b/src/main/java/li/cil/oc2/common/vm/terminal/escapes/csi/DSR.java @@ -7,6 +7,14 @@ public DSR(final Terminal terminal) { super(terminal); } + @Override + public int[] defaultParameters(CSIState state) { + // Ps omitted (or 0) defaults to 5 — device status report. CSIManager substitutes + // this before execute, so a bare CSI n replies \033[0n instead of hanging the guest + // waiting for a status response that never comes (§36 m4). + return new int[] {5}; + } + @Override public void execute(int[] args, int argCount, CSIState state) { if (args[0] == 5) { diff --git a/src/test/java/li/cil/oc2/common/vm/terminal/TerminalBufferTest.java b/src/test/java/li/cil/oc2/common/vm/terminal/TerminalBufferTest.java index 4bd87707..6e392814 100644 --- a/src/test/java/li/cil/oc2/common/vm/terminal/TerminalBufferTest.java +++ b/src/test/java/li/cil/oc2/common/vm/terminal/TerminalBufferTest.java @@ -130,6 +130,32 @@ void vpaMovesRowOnly() { assertEquals(6, terminal.y); } + @Test + void dsrBareCsiNRepliesStatus() { + // §36 m4: DSR (CSI Ps n) with no Ps defaults to Ps=5 (device status). CSIManager + // substitutes the handler's defaultParameters before execute, so a bare CSI n + // replies \033[0n instead of hanging a guest waiting for a status response. + write(terminal, CSI + "n"); + final ByteBuffer reply = terminal.io.getInput(); + assertNotNull(reply, "bare CSI n must produce a status-report reply"); + final byte[] bytes = new byte[reply.remaining()]; + reply.get(bytes); + assertEquals("\033[0n", new String(bytes, StandardCharsets.US_ASCII), + "bare CSI n replies operating-status (\\033[0n)"); + } + + @Test + void dsrExplicitFiveStillRepliesStatus() { + // Explicit Ps=5 is unchanged by the defaultParameters fix — still a status report. + write(terminal, CSI + "5n"); + final ByteBuffer reply = terminal.io.getInput(); + assertNotNull(reply, "CSI 5n must produce a status-report reply"); + final byte[] bytes = new byte[reply.remaining()]; + reply.get(bytes); + assertEquals("\033[0n", new String(bytes, StandardCharsets.US_ASCII), + "CSI 5n replies operating-status (\\033[0n)"); + } + @Test void cudMovesCursorDownAndClampsSaturatedCount() { // parseArgument saturates at Integer.MAX_VALUE; terminal.y + args[0] must not overflow to a