Phase 2
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
package haus.nightmare.lib3270j.datastream;
|
||||
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class DataStreamProcessorPhase2Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private DataStreamProcessor processor;
|
||||
private ByteArrayOutputStream output;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
haus.nightmare.lib3270j.charset.EbcdicTranslator translator = new haus.nightmare.lib3270j.charset.EbcdicTranslator();
|
||||
screen = new ScreenBuffer(haus.nightmare.lib3270j.TerminalModel.IBM_3279_2, translator);
|
||||
processor = new DataStreamProcessor(screen, translator);
|
||||
output = new ByteArrayOutputStream();
|
||||
processor.setOutputSender(bytes -> output.write(bytes, 0, bytes.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleFFAndCountFF() {
|
||||
byte[] raw = new byte[] { 0x01, (byte) 0xFF, 0x02, (byte) 0xFF, (byte) 0xFF, 0x03 };
|
||||
assertEquals(3, DataStreamProcessor.countFF(raw, raw.length));
|
||||
|
||||
byte[] doubled = DataStreamProcessor.doubleFF(raw, raw.length);
|
||||
assertEquals(9, doubled.length);
|
||||
assertEquals(0x01, doubled[0]);
|
||||
assertEquals((byte) 0xFF, doubled[1]);
|
||||
assertEquals((byte) 0xFF, doubled[2]);
|
||||
assertEquals(0x02, doubled[3]);
|
||||
assertEquals((byte) 0xFF, doubled[4]);
|
||||
assertEquals((byte) 0xFF, doubled[5]);
|
||||
assertEquals((byte) 0xFF, doubled[6]);
|
||||
assertEquals((byte) 0xFF, doubled[7]);
|
||||
assertEquals(0x03, doubled[8]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFastX2BinAndCmd2Ebc() {
|
||||
// Test x2bin 6-bit decode
|
||||
assertEquals(0, DataStreamProcessor.x2bin(0x40)); // space = 0
|
||||
assertEquals(1, DataStreamProcessor.x2bin(0xC1)); // 'A' = 1
|
||||
assertEquals(2, DataStreamProcessor.x2bin(0xC2)); // 'B' = 2
|
||||
|
||||
// Test cmd2ebc
|
||||
assertEquals(SNA_CMD_W, DataStreamProcessor.cmd2ebc(CMD_W));
|
||||
assertEquals(SNA_CMD_EW, DataStreamProcessor.cmd2ebc(CMD_EW));
|
||||
assertEquals(SNA_CMD_WSF, DataStreamProcessor.cmd2ebc(CMD_WSF));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedAttributePlanes() {
|
||||
// SFE with FA + Outlining + Validation + FG Color
|
||||
byte[] sfeRecord = new byte[] {
|
||||
(byte) CMD_EW, 0x00, // EW with null WCC
|
||||
(byte) ORDER_SFE, 0x04, // 4 pairs
|
||||
(byte) XA_3270, (byte) FA_PRINTABLE,
|
||||
(byte) XA_OUTLINING, (byte) 0x0F, // Full box outline
|
||||
(byte) XA_VALIDATION, (byte) 0x01,
|
||||
(byte) XA_FOREGROUND, (byte) 0xF2 // Red
|
||||
};
|
||||
|
||||
processor.processRecord(sfeRecord, 0, sfeRecord.length, true);
|
||||
|
||||
ExtendedAttribute cell = screen.getCell(0);
|
||||
assertTrue(cell.isFieldAttribute());
|
||||
assertEquals((byte) 0x0F, cell.ol);
|
||||
assertEquals((byte) 0x01, cell.vl);
|
||||
assertEquals((byte) 0xF2, cell.fg);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBufferInExtendedFieldMode() {
|
||||
// Set up field with FG color
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.getCell(0).fg = (byte) 0xF4; // Green
|
||||
screen.setReplyMode((byte) SF_SRM_XFIELD);
|
||||
|
||||
// Execute Read Buffer command (0xF2)
|
||||
byte[] rbRecord = new byte[] { (byte) CMD_RB };
|
||||
processor.processRecord(rbRecord, 0, rbRecord.length, false);
|
||||
|
||||
byte[] sent = output.toByteArray();
|
||||
assertTrue(sent.length > 5);
|
||||
assertEquals(AID_NO, sent[0] & 0xFF);
|
||||
|
||||
// At position 0, response should contain ORDER_SFE (0x29) instead of ORDER_SF (0x1D)
|
||||
assertEquals(ORDER_SFE, sent[3] & 0xFF);
|
||||
assertEquals(2, sent[4] & 0xFF); // 2 pairs (3270 FA and FG)
|
||||
assertEquals(XA_3270, sent[5] & 0xFF);
|
||||
assertEquals(FA_PRINTABLE, sent[6] & 0xFF);
|
||||
assertEquals(XA_FOREGROUND, sent[7] & 0xFF);
|
||||
assertEquals(0xF4, sent[8] & 0xFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStructuredFieldPartitions() {
|
||||
// WSF Create Partition (pid = 1, 32 rows, 80 cols)
|
||||
byte[] createPart = new byte[] {
|
||||
(byte) CMD_WSF,
|
||||
0x00, 0x08, // Length = 8
|
||||
(byte) SF_CREATE_PART, 0x01, // PID = 1
|
||||
0x00, 80, // Cols = 80
|
||||
0x00, 32 // Rows = 32
|
||||
};
|
||||
|
||||
processor.processRecord(createPart, 0, createPart.length, false);
|
||||
assertEquals(1, screen.getActivePartition());
|
||||
assertTrue(screen.isExplicitPartitionActive());
|
||||
|
||||
// WSF Activate Partition 0
|
||||
byte[] activatePart = new byte[] {
|
||||
(byte) CMD_WSF,
|
||||
0x00, 0x04,
|
||||
(byte) SF_ACTIVATE_PART, 0x00
|
||||
};
|
||||
processor.processRecord(activatePart, 0, activatePart.length, false);
|
||||
assertEquals(0, screen.getActivePartition());
|
||||
assertFalse(screen.isExplicitPartitionActive());
|
||||
|
||||
// WSF Destroy Partition 1
|
||||
byte[] destroyPart = new byte[] {
|
||||
(byte) CMD_WSF,
|
||||
0x00, 0x04,
|
||||
(byte) SF_DESTROY_PART, 0x01
|
||||
};
|
||||
processor.processRecord(destroyPart, 0, destroyPart.length, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package haus.nightmare.lib3270j.nvt;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class NvtProcessorTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private NvtProcessor processor;
|
||||
private ByteArrayOutputStream output;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(haus.nightmare.lib3270j.TerminalModel.IBM_3279_2, translator);
|
||||
processor = new NvtProcessor(screen, translator);
|
||||
output = new ByteArrayOutputStream();
|
||||
processor.setOutputSender(bytes -> output.write(bytes, 0, bytes.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlainTextProcessing() {
|
||||
byte[] text = "HELLO 3270".getBytes();
|
||||
processor.processNVTData(text, 0, text.length);
|
||||
|
||||
assertEquals(10, screen.getCursorAddress());
|
||||
assertEquals('H', screen.getCell(0).ucs4);
|
||||
assertEquals('E', screen.getCell(1).ucs4);
|
||||
assertEquals('L', screen.getCell(2).ucs4);
|
||||
assertEquals('L', screen.getCell(3).ucs4);
|
||||
assertEquals('O', screen.getCell(4).ucs4);
|
||||
assertEquals(' ', screen.getCell(5).ucs4);
|
||||
assertEquals('3', screen.getCell(6).ucs4);
|
||||
assertEquals('2', screen.getCell(7).ucs4);
|
||||
assertEquals('7', screen.getCell(8).ucs4);
|
||||
assertEquals('0', screen.getCell(9).ucs4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCursorMovementAnsiSequences() {
|
||||
// CUP: ESC [ 5 ; 10 H (Row 5, Col 10 => 1-indexed, so row 4, col 9)
|
||||
byte[] cup = "\u001B[5;10H".getBytes();
|
||||
processor.processNVTData(cup, 0, cup.length);
|
||||
|
||||
assertEquals(4 * 80 + 9, screen.getCursorAddress());
|
||||
|
||||
// CUU: ESC [ 2 A (Up 2 rows => row 2, col 9)
|
||||
byte[] cuu = "\u001B[2A".getBytes();
|
||||
processor.processNVTData(cuu, 0, cuu.length);
|
||||
assertEquals(2 * 80 + 9, screen.getCursorAddress());
|
||||
|
||||
// CUD: ESC [ 3 B (Down 3 rows => row 5, col 9)
|
||||
byte[] cud = "\u001B[3B".getBytes();
|
||||
processor.processNVTData(cud, 0, cud.length);
|
||||
assertEquals(5 * 80 + 9, screen.getCursorAddress());
|
||||
|
||||
// CUB: ESC [ 4 D (Left 4 cols => row 5, col 5)
|
||||
byte[] cub = "\u001B[4D".getBytes();
|
||||
processor.processNVTData(cub, 0, cub.length);
|
||||
assertEquals(5 * 80 + 5, screen.getCursorAddress());
|
||||
|
||||
// CUF: ESC [ 10 C (Right 10 cols => row 5, col 15)
|
||||
byte[] cuf = "\u001B[10C".getBytes();
|
||||
processor.processNVTData(cuf, 0, cuf.length);
|
||||
assertEquals(5 * 80 + 15, screen.getCursorAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSgrColorAndAttributes() {
|
||||
// ESC [ 1 ; 4 ; 31 ; 42 m (Bold, Underline, Red FG, Green BG)
|
||||
byte[] sgr = "\u001B[1;4;31;42mX".getBytes();
|
||||
processor.processNVTData(sgr, 0, sgr.length);
|
||||
|
||||
ExtendedAttribute cell = screen.getCell(0);
|
||||
assertEquals('X', cell.ucs4);
|
||||
assertEquals((byte) haus.nightmare.lib3270j.protocol.DS3270Constants.HOST_COLOR_RED, cell.fg);
|
||||
assertEquals((byte) haus.nightmare.lib3270j.protocol.DS3270Constants.HOST_COLOR_GREEN, cell.bg);
|
||||
assertTrue((cell.gr & 0x08) != 0); // GR_INTENSIFY
|
||||
assertTrue((cell.gr & 0x04) != 0); // GR_UNDERLINE
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEraseInDisplay() {
|
||||
byte[] initial = "ABCDEFGHIJ".getBytes();
|
||||
processor.processNVTData(initial, 0, initial.length);
|
||||
assertEquals('A', screen.getCell(0).ucs4);
|
||||
|
||||
// ESC [ 2 J (Clear entire display)
|
||||
byte[] ed2 = "\u001B[2J".getBytes();
|
||||
processor.processNVTData(ed2, 0, ed2.length);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertEquals(0, screen.getCell(i).ucs4);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveAndRestoreCursor() {
|
||||
byte[] move = "\u001B[10;20H".getBytes();
|
||||
processor.processNVTData(move, 0, move.length);
|
||||
int savedAddr = screen.getCursorAddress();
|
||||
|
||||
// DECSC: ESC 7 (save cursor)
|
||||
byte[] save = "\u001B7".getBytes();
|
||||
processor.processNVTData(save, 0, save.length);
|
||||
|
||||
// Move elsewhere
|
||||
byte[] move2 = "\u001B[1;1H".getBytes();
|
||||
processor.processNVTData(move2, 0, move2.length);
|
||||
assertEquals(0, screen.getCursorAddress());
|
||||
|
||||
// DECRC: ESC 8 (restore cursor)
|
||||
byte[] restore = "\u001B8".getBytes();
|
||||
processor.processNVTData(restore, 0, restore.length);
|
||||
assertEquals(savedAddr, screen.getCursorAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendNvtMethods() throws IOException {
|
||||
processor.sendNVTString("LOGIN\n");
|
||||
byte[] sent = output.toByteArray();
|
||||
assertTrue(sent.length >= 6);
|
||||
assertEquals('L', (char) sent[0]);
|
||||
assertEquals('O', (char) sent[1]);
|
||||
assertEquals('G', (char) sent[2]);
|
||||
assertEquals('I', (char) sent[3]);
|
||||
assertEquals('N', (char) sent[4]);
|
||||
// Normalized newline to CRLF
|
||||
assertEquals('\r', (char) sent[5]);
|
||||
assertEquals('\n', (char) sent[6]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package haus.nightmare.lib3270j.telnet;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.datastream.DataStreamProcessor;
|
||||
import haus.nightmare.lib3270j.protocol.TN3270EConstants;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.TN3270EConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.TelnetConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TelnetFSMPhase2Test {
|
||||
|
||||
private ConnectionConfig config;
|
||||
private ScreenBuffer screenBuffer;
|
||||
private DataStreamProcessor dsProcessor;
|
||||
private TelnetFSM fsm;
|
||||
private ByteArrayOutputStream output;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
config = new ConnectionConfig("test.host", 23, TerminalModel.IBM_3279_4);
|
||||
screenBuffer = new ScreenBuffer(TerminalModel.IBM_3279_4, new haus.nightmare.lib3270j.charset.EbcdicTranslator());
|
||||
dsProcessor = new DataStreamProcessor(screenBuffer, new haus.nightmare.lib3270j.charset.EbcdicTranslator());
|
||||
fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||
output = new ByteArrayOutputStream();
|
||||
|
||||
// Create a mock TelnetConnection
|
||||
TelnetConnection connection = new TelnetConnection(config, fsm) {
|
||||
@Override
|
||||
public synchronized void sendRaw(byte[] data, int offset, int length) {
|
||||
output.write(data, offset, length);
|
||||
}
|
||||
};
|
||||
fsm.setConnection(connection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnaBindImageParsingSSIZE03() {
|
||||
fsm.onConnected();
|
||||
|
||||
// Craft SNA BIND RU: 5-byte TN3270E header (DT_BIND_IMAGE) + 30-byte BIND image
|
||||
byte[] bindData = new byte[EH_SIZE + 30];
|
||||
bindData[0] = (byte) DT_BIND_IMAGE;
|
||||
bindData[1] = 0;
|
||||
bindData[2] = (byte) RSF_ALWAYS_RESPONSE;
|
||||
bindData[3] = 0;
|
||||
bindData[4] = 1; // Seq = 1
|
||||
|
||||
// SSIZE at offset 24 = 0x03 (default 24x80, alt = model max 43x80)
|
||||
bindData[EH_SIZE + 20] = 24; // RD
|
||||
bindData[EH_SIZE + 21] = 80; // CD
|
||||
bindData[EH_SIZE + 24] = 0x03; // SSIZE
|
||||
|
||||
fsm.process_bind(bindData, RSF_ALWAYS_RESPONSE, 1);
|
||||
|
||||
assertEquals(ConnectionState.CONNECTED_TN3270E, fsm.getConnectionState());
|
||||
assertTrue(fsm.checkSessionBound());
|
||||
assertEquals(43, screenBuffer.getAltRows());
|
||||
assertEquals(80, screenBuffer.getAltCols());
|
||||
|
||||
// Verify positive response was sent
|
||||
byte[] sent = output.toByteArray();
|
||||
assertTrue(sent.length >= EH_SIZE + 1);
|
||||
assertEquals((byte) DT_RESPONSE, sent[0]);
|
||||
assertEquals((byte) RSF_POSITIVE_RESPONSE, sent[2]);
|
||||
assertEquals((byte) POS_DEVICE_END, sent[5]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnaBindImageParsingSSIZE7F() {
|
||||
fsm.onConnected();
|
||||
|
||||
byte[] bindData = new byte[EH_SIZE + 30];
|
||||
bindData[0] = (byte) DT_BIND_IMAGE;
|
||||
bindData[1] = 0;
|
||||
bindData[2] = (byte) RSF_NO_RESPONSE;
|
||||
bindData[3] = 0;
|
||||
bindData[4] = 2;
|
||||
|
||||
// SSIZE at offset 24 = 0x7F (separate default and alternate: 32x80 alt)
|
||||
bindData[EH_SIZE + 20] = 24; // RD
|
||||
bindData[EH_SIZE + 21] = 80; // CD
|
||||
bindData[EH_SIZE + 22] = 32; // RA
|
||||
bindData[EH_SIZE + 23] = 80; // CA
|
||||
bindData[EH_SIZE + 24] = 0x7F; // SSIZE
|
||||
|
||||
fsm.process_bind(bindData, RSF_NO_RESPONSE, 2);
|
||||
|
||||
assertEquals(32, screenBuffer.getAltRows());
|
||||
assertEquals(80, screenBuffer.getAltCols());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnaUnbindTransitionsToUnboundAndClearsScreen() {
|
||||
fsm.onConnected();
|
||||
screenBuffer.getCell(0).ucs4 = 'X';
|
||||
|
||||
fsm.process_unbind(UNBIND_NORMAL, RSF_ALWAYS_RESPONSE, 10);
|
||||
|
||||
assertEquals(ConnectionState.CONNECTED_UNBOUND, fsm.getConnectionState());
|
||||
assertFalse(fsm.checkSessionBound());
|
||||
assertEquals(0, screenBuffer.getCell(0).ucs4); // Cleared
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnaDfcClearAndSignal() {
|
||||
fsm.onConnected();
|
||||
screenBuffer.getCell(5).ucs4 = 'Z';
|
||||
|
||||
fsm.process_DFC(DFC_CLEAR, RSF_NO_RESPONSE, 5);
|
||||
assertEquals(0, screenBuffer.getCell(5).ucs4); // Cleared
|
||||
|
||||
boolean[] alarmHeard = new boolean[1];
|
||||
fsm.addScreenUpdateListener(new haus.nightmare.lib3270j.listener.ScreenUpdateListener() {
|
||||
@Override public void onScreenUpdated() {}
|
||||
@Override public void onScreenSizeChanged(int rows, int cols) {}
|
||||
@Override public void onSoundAlarm() { alarmHeard[0] = true; }
|
||||
});
|
||||
|
||||
fsm.process_DFC(DFC_SIGNAL, RSF_NO_RESPONSE, 6);
|
||||
assertTrue(alarmHeard[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeviceNamePoolManagement() {
|
||||
fsm.addDeviceName("POOL1", "LU001", -1);
|
||||
fsm.addDeviceName("POOL1", "LU002", -1);
|
||||
fsm.addDeviceName("POOL1", "LU000", 0);
|
||||
|
||||
assertEquals("LU000", fsm.getDeviceName("POOL1", 0));
|
||||
assertEquals("LU001", fsm.getDeviceName("POOL1", 1));
|
||||
assertEquals("LU002", fsm.getDeviceName("POOL1", 2));
|
||||
|
||||
fsm.removeDeviceName("POOL1", "LU001", -1);
|
||||
assertEquals("LU002", fsm.getDeviceName("POOL1", 1));
|
||||
|
||||
List<String> poolList = fsm.getDevicePoolList("POOL1");
|
||||
assertEquals(2, poolList.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLuPoolCyclingOnReject() {
|
||||
config.setLuNames(Arrays.asList("LU_A", "LU_B", "LU_C"));
|
||||
fsm.onConnected();
|
||||
output.reset();
|
||||
|
||||
// Server sends DO TN3270E -> client sends WILL TN3270E and requests "LU_A"
|
||||
fsm.feedBytes(new byte[] { (byte) IAC, (byte) DO, (byte) TELOPT_TN3270E }, 0, 3);
|
||||
|
||||
String sent1 = new String(output.toByteArray());
|
||||
assertTrue(sent1.contains("LU_A"));
|
||||
output.reset();
|
||||
|
||||
// Server rejects device type with REASON_DEVICE_IN_USE
|
||||
fsm.feedBytes(new byte[] { (byte) IAC, (byte) SB, (byte) TELOPT_TN3270E, (byte) OP_DEVICE_TYPE, (byte) OP_REJECT, (byte) OP_REASON, (byte) REASON_DEVICE_IN_USE, (byte) IAC, (byte) SE }, 0, 9);
|
||||
|
||||
// Client should have automatically retried with next LU ("LU_B")
|
||||
String sent2 = new String(output.toByteArray());
|
||||
assertTrue(sent2.contains("LU_B"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user