Break cluster lines on a bare carriage return

N1MM splits the telnet stream on CR, not LF, which says some nodes send CR with
no LF after it. Against those the assembler was handing the whole session back
as one unfinished line.

Also adds "user:" to the login prompts, which N1MM matches and we did not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 15:53:23 +00:00
parent 2b8033ccdd
commit 2b5ab54e94
4 changed files with 52 additions and 11 deletions

View File

@@ -31,4 +31,31 @@ public class LineAssemblerTests
Assert.Empty(assembler.Add("login: "));
Assert.Equal("login: ", assembler.Pending);
}
/// N1MM splits on CR alone, so some nodes evidently leave the LF out.
[Fact]
public void ALineEndedWithACarriageReturnAloneIsStillALine()
{
LineAssembler assembler = new();
Assert.Equal(["one", "two"], assembler.Add("one\rtwo\r"));
}
[Fact]
public void CarriageReturnAndLineFeedTogetherAreOneEnding()
{
LineAssembler assembler = new();
Assert.Equal(["one"], assembler.Add("one\r\n"));
Assert.Equal("", assembler.Pending);
}
[Fact]
public void AnEndingSplitAcrossTwoBlocksIsStillOneEnding()
{
LineAssembler assembler = new();
Assert.Equal(["one"], assembler.Add("one\r"));
Assert.Empty(assembler.Add("\n"));
}
}

View File

@@ -38,8 +38,10 @@ public class TelnetStreamTests
Assert.Equal("DX de W3LPL: 14025.0 JA1XYZ\r\n", read);
}
/// A client that leaves the negotiation unanswered gets these bytes mixed
/// into the first line it reads.
/// N1MM never answers the negotiation. It drops bytes 251, 252 and 255 from
/// what it displays but lets 253 and 254 through, so a node that opens with
/// IAC DO TERMINAL-TYPE puts two stray characters in its packet window and
/// leaves the node waiting for an answer that never comes. We answer.
[Fact]
public async Task TheNegotiationIsAnsweredAndKeptOutOfTheText()
{