Connection hardening and FileTransfer UI changes
This commit is contained in:
@@ -14,7 +14,7 @@ public class FTConfigTest {
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
|
||||
assertEquals("IND$FILE GET MY.DATASET ASCII CRLF", config.buildCommand());
|
||||
assertEquals("TSO IND$FILE GET MY.DATASET ASCII CRLF", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -25,7 +25,7 @@ public class FTConfigTest {
|
||||
config.setHostFilename("'USER.BIN.DATA'");
|
||||
config.setTransferMode(FTConfig.TransferMode.BINARY);
|
||||
|
||||
assertEquals("IND$FILE GET 'USER.BIN.DATA'", config.buildCommand());
|
||||
assertEquals("TSO IND$FILE GET 'USER.BIN.DATA'", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -40,7 +40,7 @@ public class FTConfigTest {
|
||||
config.setLrecl("80");
|
||||
config.setBlksize("3120");
|
||||
|
||||
assertEquals("IND$FILE PUT MY.DATASET ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120)", config.buildCommand());
|
||||
assertEquals("TSO IND$FILE PUT MY.DATASET ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120)", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,7 +143,7 @@ public class FTConfigTest {
|
||||
config.setUnits(FTConfig.AllocationUnit.TRACKS);
|
||||
|
||||
String cmd = config.buildCommand();
|
||||
assertEquals("IND$FILE PUT 'USER.PDS.DATA' ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120) SPACE(15,5,10) TRACKS", cmd);
|
||||
assertEquals("TSO IND$FILE PUT 'USER.PDS.DATA' ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120) SPACE(15,5,10) TRACKS", cmd);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -157,7 +157,7 @@ public class FTConfigTest {
|
||||
assertEquals(10, config.getSecondarySpace());
|
||||
assertEquals(15, config.getDirectoryBlocks());
|
||||
assertEquals(FTConfig.AllocationUnit.TRACKS, config.getUnits());
|
||||
assertEquals("IND$FILE PUT SYS1.PARMLIB ASCII CRLF SPACE(20,10,15) TRACKS", config.buildCommand());
|
||||
assertEquals("TSO IND$FILE PUT SYS1.PARMLIB ASCII CRLF SPACE(20,10,15) TRACKS", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -171,7 +171,7 @@ public class FTConfigTest {
|
||||
assertEquals(5, config.getSecondarySpace());
|
||||
assertEquals(20, config.getDirectoryBlocks());
|
||||
assertEquals(FTConfig.AllocationUnit.CYLINDERS, config.getUnits());
|
||||
assertEquals("IND$FILE PUT SYS1.PROCLIB ASCII CRLF SPACE(10,5,20) CYLINDERS", config.buildCommand());
|
||||
assertEquals("TSO IND$FILE PUT SYS1.PROCLIB ASCII CRLF SPACE(10,5,20) CYLINDERS", config.buildCommand());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -443,7 +443,7 @@ public class FTProtocolHardeningPhase16Test {
|
||||
assertEquals("APVUFILE", optionsCfg.getProgramName());
|
||||
|
||||
String cmd = optionsCfg.buildCommand();
|
||||
assertTrue(cmd.startsWith("APVUFILE PUT 'TEST.FILE'"));
|
||||
assertTrue(cmd.startsWith("TSO APVUFILE PUT 'TEST.FILE'"));
|
||||
assertTrue(cmd.contains("NEW"));
|
||||
assertTrue(cmd.contains("NOSO"));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ public class DirectoryParserTest {
|
||||
assertEquals(80, e0.getLrecl());
|
||||
assertEquals(3120, e0.getBlksize());
|
||||
assertEquals("PS", e0.getDsorg());
|
||||
assertEquals(15, e0.getTracksAllocated());
|
||||
assertEquals(1, e0.getExtents());
|
||||
|
||||
TSODirectoryEntry e1 = entries.get(1);
|
||||
assertEquals("TSOUSER.OTHER.DATA", e1.getName());
|
||||
@@ -151,4 +153,183 @@ public class DirectoryParserTest {
|
||||
assertEquals("USER.TEST.CNTL", entries.get(0).getName());
|
||||
assertEquals("USER.TEST.DATA", entries.get(1).getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCmsMoreRunningAndReadyHandling() {
|
||||
String cmsOutput =
|
||||
"PROFILE EXEC A1 V 80 25 1 2026-05-10 14:22:01 MORE...\n" +
|
||||
"MORE...\n" +
|
||||
"RUNNING\n" +
|
||||
"HOLDING\n" +
|
||||
"TEST DATA A1 F 80 100 2 2026-08-01 09:15:00\n" +
|
||||
"OTHER FILE A1 V 80 50 1 2026-08-02 10:00:00 RUNNING\n" +
|
||||
"Ready(00000); T=0.01/0.01 12:00:00\n";
|
||||
|
||||
List<CMSDirectoryEntry> entries = CMSDirectoryParser.parse(cmsOutput);
|
||||
assertEquals(3, entries.size(), "Should parse 3 CMS entries and strip/ignore MORE..., RUNNING, HOLDING, Ready(00000)");
|
||||
assertEquals("PROFILE", entries.get(0).getFilename());
|
||||
assertEquals("TEST", entries.get(1).getFilename());
|
||||
assertEquals("OTHER", entries.get(2).getFilename());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoListcatWithClusterAndGdg() {
|
||||
String listcatOutput =
|
||||
"IDCAMS SYSTEM SERVICES TIME: 12:00:00\n" +
|
||||
"NONVSAM ------- TSOUSER.NORMAL.DATA\n" +
|
||||
" VOLUMES\n" +
|
||||
" VOLSER------------TSO001\n" +
|
||||
"CLUSTER ------- TSOUSER.VSAM.KSDS\n" +
|
||||
" IN-CAT --- CATALOG.USER\n" +
|
||||
"GDG ----------- TSOUSER.GDG.BASE\n" +
|
||||
"ALIAS --------- TSOUSER.MY.ALIAS\n";
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(listcatOutput);
|
||||
assertEquals(4, entries.size(), "Should parse NONVSAM, CLUSTER, GDG, and ALIAS");
|
||||
assertEquals("TSOUSER.NORMAL.DATA", entries.get(0).getName());
|
||||
assertEquals("TSO001", entries.get(0).getVolume());
|
||||
assertEquals("TSOUSER.VSAM.KSDS", entries.get(1).getName());
|
||||
assertEquals("VSAM", entries.get(1).getDsorg());
|
||||
assertEquals("TSOUSER.GDG.BASE", entries.get(2).getName());
|
||||
assertEquals("TSOUSER.MY.ALIAS", entries.get(3).getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoListcatAllocDetails() {
|
||||
String allocOutput =
|
||||
"IDCAMS SYSTEM SERVICES TIME: 12:00:00\n" +
|
||||
"NONVSAM ------- TSOUSER.PROJECT.DATA\n" +
|
||||
" IN-CAT --- CATALOG.USER\n" +
|
||||
" VOLUMES\n" +
|
||||
" VOLSER------------TSOP01 DEVTYPE------X'3010200F'\n" +
|
||||
" ALLOCATION\n" +
|
||||
" SPACE-TYPE---------TRACK SPACE-PRI----------------15 SPACE-SEC----------------5\n" +
|
||||
" EXTENTS-----------------1\n" +
|
||||
"CLUSTER ------- TSOUSER.VSAM.TEST\n" +
|
||||
" IN-CAT --- CATALOG.USER\n" +
|
||||
" DATA ------- TSOUSER.VSAM.TEST.DATA\n" +
|
||||
" VOLUMES\n" +
|
||||
" VOLSER------------TSOP02\n" +
|
||||
" ALLOCATION\n" +
|
||||
" SPACE-TYPE------CYLINDER SPACE-PRI----------------2\n" +
|
||||
" EXTENTS-----------------2\n" +
|
||||
" ATTRIBUTES\n" +
|
||||
" AVGLRECL--------------80 MAXLRECL----------------80 CISIZE----------------4096\n";
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(allocOutput);
|
||||
assertEquals(2, entries.size());
|
||||
|
||||
TSODirectoryEntry nonvsam = entries.get(0);
|
||||
assertEquals("TSOUSER.PROJECT.DATA", nonvsam.getName());
|
||||
assertEquals("TSOP01", nonvsam.getVolume());
|
||||
assertEquals(15, nonvsam.getTracksAllocated());
|
||||
assertEquals(1, nonvsam.getExtents());
|
||||
|
||||
TSODirectoryEntry vsam = entries.get(1);
|
||||
assertEquals("TSOUSER.VSAM.TEST", vsam.getName());
|
||||
assertEquals("VSAM", vsam.getDsorg());
|
||||
assertEquals("TSOP02", vsam.getVolume());
|
||||
assertEquals(30, vsam.getTracksAllocated()); // 2 cylinders * 15 tracks
|
||||
assertEquals(2, vsam.getExtents());
|
||||
assertEquals(80, vsam.getLrecl());
|
||||
assertEquals(4096, vsam.getBlksize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoListdsParsing() {
|
||||
String listdsOutput =
|
||||
"TSOUSER.MY.SOURCE\n" +
|
||||
"--RECFM-LRECL-BLKSIZE-DSORG\n" +
|
||||
" FB 80 3120 PO\n" +
|
||||
"--VOLUMES--\n" +
|
||||
" SRC001\n";
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(listdsOutput);
|
||||
assertEquals(1, entries.size());
|
||||
TSODirectoryEntry e = entries.get(0);
|
||||
assertEquals("TSOUSER.MY.SOURCE", e.getName());
|
||||
assertEquals("FB", e.getRecfm());
|
||||
assertEquals(80, e.getLrecl());
|
||||
assertEquals(3120, e.getBlksize());
|
||||
assertEquals("PO", e.getDsorg());
|
||||
assertEquals("SRC001", e.getVolume());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoListdsWithStatusAllocationParsing() {
|
||||
String listdsStatusOutput =
|
||||
"SYS1.ACMDLIB\n" +
|
||||
"--RECFM-LRECL-BLKSIZE-DSORG\n" +
|
||||
" U 0 32760 PO\n" +
|
||||
"--VOLUMES--\n" +
|
||||
" TK5DLB\n" +
|
||||
"--ALLOCATION---------------\n" +
|
||||
" SPACE-TYPE TRACK\n" +
|
||||
" SPACE-PRI 30\n" +
|
||||
" SPACE-SEC 5\n" +
|
||||
"--TRACKS-ALLOCATED-EXTENTS-\n" +
|
||||
" 30 1\n";
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(listdsStatusOutput);
|
||||
assertEquals(1, entries.size());
|
||||
TSODirectoryEntry e = entries.get(0);
|
||||
assertEquals("SYS1.ACMDLIB", e.getName());
|
||||
assertEquals("U", e.getRecfm());
|
||||
assertEquals(0, e.getLrecl());
|
||||
assertEquals(32760, e.getBlksize());
|
||||
assertEquals("PO", e.getDsorg());
|
||||
assertEquals("TK5DLB", e.getVolume());
|
||||
assertEquals(30, e.getTracksAllocated());
|
||||
assertEquals(1, e.getExtents());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoTabularDslispParsing() {
|
||||
String tabular = " USER.SAMPLE.COBOL MNT01 15 50 1 3390 PO FB 80 3120\n";
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(tabular);
|
||||
assertEquals(1, entries.size());
|
||||
TSODirectoryEntry e = entries.get(0);
|
||||
assertEquals("USER.SAMPLE.COBOL", e.getName());
|
||||
assertEquals("MNT01", e.getVolume());
|
||||
assertEquals(15, e.getTracksAllocated());
|
||||
assertEquals(50, e.getPercentUsed());
|
||||
assertEquals(1, e.getExtents());
|
||||
assertEquals("3390", e.getDevice());
|
||||
assertEquals("PO", e.getDsorg());
|
||||
assertEquals("FB", e.getRecfm());
|
||||
assertEquals(80, e.getLrecl());
|
||||
assertEquals(3120, e.getBlksize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTsoListcatWithSysresAsterisksVolume() {
|
||||
String listcatOutput =
|
||||
"IDCAMS SYSTEM SERVICES TIME: 02:32:00\n" +
|
||||
"NONVSAM ------- SYS1.ACMDLIB\n" +
|
||||
" IN-CAT --- SYS1.MCAT.TK5\n" +
|
||||
" HISTORY\n" +
|
||||
" OWNER-IDENT-------(NULL) CREATION----------23.146\n" +
|
||||
" RELEASE----------------2 EXPIRATION--------00.000\n" +
|
||||
" VOLUMES\n" +
|
||||
" VOLSER------------****** DEVTYPE------X'3010200F' FSEQN------------------0\n" +
|
||||
" ASSOCIATIONS--------(NULL)\n" +
|
||||
"NONVSAM ------- SYS1.SYSTEM.CMDSBSYS.CSCDATA\n" +
|
||||
" IN-CAT --- SYS1.MCAT.TK5\n" +
|
||||
" HISTORY\n" +
|
||||
" OWNER-IDENT-------(NULL) CREATION----------23.146\n" +
|
||||
" VOLUMES\n" +
|
||||
" VOLSER------------TK5002 DEVTYPE------X'3010200F' FSEQN------------------0\n" +
|
||||
" ASSOCIATIONS--------(NULL)\n";
|
||||
|
||||
List<TSODirectoryEntry> entries = TSODirectoryParser.parse(listcatOutput);
|
||||
assertEquals(2, entries.size());
|
||||
|
||||
TSODirectoryEntry sysresEntry = entries.get(0);
|
||||
assertEquals("SYS1.ACMDLIB", sysresEntry.getName());
|
||||
assertEquals("******", sysresEntry.getVolume(), "SYSRES volume with asterisks should be preserved");
|
||||
|
||||
TSODirectoryEntry explicitEntry = entries.get(1);
|
||||
assertEquals("SYS1.SYSTEM.CMDSBSYS.CSCDATA", explicitEntry.getName());
|
||||
assertEquals("TK5002", explicitEntry.getVolume());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public class Mock3270Server implements AutoCloseable {
|
||||
private int sequenceNumber = 0;
|
||||
|
||||
private final List<byte[]> receivedRecords = new CopyOnWriteArrayList<>();
|
||||
private final List<int[]> receivedCommands = new CopyOnWriteArrayList<>();
|
||||
private final AtomicInteger receivedNops = new AtomicInteger(0);
|
||||
private final AtomicInteger receivedTimingMarks = new AtomicInteger(0);
|
||||
|
||||
@@ -126,6 +127,10 @@ public class Mock3270Server implements AutoCloseable {
|
||||
return Collections.unmodifiableList(receivedRecords);
|
||||
}
|
||||
|
||||
public List<int[]> getReceivedCommands() {
|
||||
return Collections.unmodifiableList(receivedCommands);
|
||||
}
|
||||
|
||||
public byte[] getLastReceivedRecord() {
|
||||
if (receivedRecords.isEmpty()) return null;
|
||||
return receivedRecords.get(receivedRecords.size() - 1);
|
||||
@@ -285,6 +290,7 @@ public class Mock3270Server implements AutoCloseable {
|
||||
|
||||
private void handleOptionCommand(int cmd, int opt) throws IOException {
|
||||
log.fine("Mock3270Server: RCVD IAC " + cmd + " opt=" + opt);
|
||||
receivedCommands.add(new int[] { cmd, opt });
|
||||
if (cmd == WILL && opt == TELOPT_TN3270E) {
|
||||
// Client accepted TN3270E -> Send SB TN3270E SEND DEVICE-TYPE IAC SE
|
||||
sendDeviceTypeSend();
|
||||
|
||||
+42
@@ -290,4 +290,46 @@ public class Mock3270ServerIntegrationTest {
|
||||
|
||||
assertTrue(matched, "Fragmented stream must be reassembled and displayed correctly");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGracefulDisconnectSendsUnbindAndDeNegotiation() throws Exception {
|
||||
ConnectionConfig config = new ConnectionConfig("127.0.0.1", server.getPort(), TerminalModel.IBM_3279_2);
|
||||
client = new Telnet3270Client(config);
|
||||
assertTrue(client.connect(8000));
|
||||
assertTrue(server.waitForHandshake(5, TimeUnit.SECONDS));
|
||||
waitForState(client, ConnectionState.CONNECTED_TN3270E, 5000);
|
||||
assertTrue(client.getTelnetFSM().isTn3270eBound());
|
||||
|
||||
// Perform graceful disconnect
|
||||
client.disconnect();
|
||||
|
||||
// Verify client transitioned to NOT_CONNECTED
|
||||
assertEquals(ConnectionState.NOT_CONNECTED, client.getConnectionState());
|
||||
assertFalse(client.isConnected());
|
||||
|
||||
// Wait briefly for server to process queued records
|
||||
Thread.sleep(150);
|
||||
|
||||
// Verify server received TN3270E UNBIND
|
||||
boolean receivedUnbind = false;
|
||||
for (byte[] rec : server.getReceivedRecords()) {
|
||||
if (rec != null && rec.length > 5 && (rec[0] & 0xFF) == DT_UNBIND) {
|
||||
int reason = rec[5] & 0xFF;
|
||||
assertEquals(UNBIND_NORMAL, reason, "UNBIND reason should be UNBIND_NORMAL (0x01)");
|
||||
receivedUnbind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(receivedUnbind, "Server should have received TN3270E UNBIND record");
|
||||
|
||||
// Verify server received Telnet option de-negotiation (WONT TN3270E or DONT TN3270E)
|
||||
boolean receivedWontOrDontTn3270e = false;
|
||||
for (int[] cmd : server.getReceivedCommands()) {
|
||||
if ((cmd[0] == WONT || cmd[0] == DONT) && cmd[1] == TELOPT_TN3270E) {
|
||||
receivedWontOrDontTn3270e = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(receivedWontOrDontTn3270e, "Server should have received Telnet de-negotiation for TN3270E");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user