Fix IND$FILE
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 45s

This commit is contained in:
2026-08-26 00:04:30 +00:00
parent bcfd4ba2e0
commit ebc0b5ae6f
4 changed files with 91 additions and 21 deletions
@@ -121,6 +121,21 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener {
if (state == FTState.RUNNING || state == FTState.AWAIT_ACK) { if (state == FTState.RUNNING || state == FTState.AWAIT_ACK) {
log.info("User cancelled transfer"); log.info("User cancelled transfer");
setState(FTState.ABORT_WAIT); // Signal handlers to abort at next chance setState(FTState.ABORT_WAIT); // Signal handlers to abort at next chance
// Start a safety timeout in case the host does not send further packets to trigger abort
cancelTimeout();
timeoutTimer = new Timer("FTCancelTimeout", true);
timeoutTimer.schedule(new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> {
if (state != FTState.NONE) {
log.warning("Transfer cancel timeout — force resetting state");
completeTransfer("Transfer cancelled by user.");
callback.onTransferAborted("Cancelled by user.");
}
});
}
}, 3000);
} else if (state != FTState.NONE) { } else if (state != FTState.NONE) {
log.info("Forcing cancel from state " + state); log.info("Forcing cancel from state " + state);
completeTransfer("Transfer cancelled."); completeTransfer("Transfer cancelled.");
@@ -106,6 +106,7 @@ public class FTDft {
} }
dftSaveBuf = null; dftSaveBuf = null;
dftSaveBufLen = 0; dftSaveBufLen = 0;
resetState();
} }
/** /**
@@ -173,9 +174,9 @@ public class FTDft {
nameBuf = extractName(data, sfOffset + 3 + 31, 7); nameBuf = extractName(data, sfOffset + 3 + 31, 7);
} }
if (OPEN_MSG.equalsIgnoreCase(nameBuf)) { if (isMessageStream(nameBuf)) {
messageFlag = true; messageFlag = true;
log.info("DFT: Open request for message stream"); log.info("DFT: Open request for message stream (" + nameBuf + ")");
} else { } else {
messageFlag = false; messageFlag = false;
listener.onDftRunning(); listener.onDftRunning();
@@ -188,15 +189,36 @@ public class FTDft {
dftOpenAck(); dftOpenAck();
} }
private boolean isMessageStream(String name) {
if (name == null) return false;
String u = name.toUpperCase();
return u.contains(OPEN_MSG) || u.contains("MSG");
}
private boolean isDataStream(String name) {
if (name == null) return false;
String u = name.toUpperCase();
return u.contains("FT:DATA") || u.contains("DATA");
}
private String extractName(byte[] data, int start, int maxLen) { private String extractName(byte[] data, int start, int maxLen) {
StringBuilder sb = new StringBuilder(); StringBuilder asciiSb = new StringBuilder();
StringBuilder ebcdicSb = new StringBuilder();
for (int i = 0; i < maxLen && (start + i) < data.length; i++) { for (int i = 0; i < maxLen && (start + i) < data.length; i++) {
int b = data[start + i] & 0xFF; int b = data[start + i] & 0xFF;
if (b == 0) break; if (b == 0) break;
char ch = translator.ebcdicToUnicode(b); asciiSb.append((char) b);
sb.append(ch); ebcdicSb.append(translator.ebcdicToUnicode(b));
} }
return sb.toString().trim(); String ascii = asciiSb.toString().trim();
String ebcdic = ebcdicSb.toString().trim();
if (isMessageStream(ascii) || isDataStream(ascii)) {
return ascii;
}
if (isMessageStream(ebcdic) || isDataStream(ebcdic)) {
return ebcdic;
}
return ascii;
} }
private void dftOpenAck() { private void dftOpenAck() {
@@ -227,6 +249,7 @@ public class FTDft {
int pos = offset + 2; int pos = offset + 2;
int end = offset + length; int end = offset + length;
boolean dataFound = false;
// Look for TR_BEGIN_DATA marker // Look for TR_BEGIN_DATA marker
while (pos < end) { while (pos < end) {
int headerCode = data[pos] & 0xFF; int headerCode = data[pos] & 0xFF;
@@ -234,10 +257,11 @@ public class FTDft {
if (headerCode == TR_BEGIN_DATA) { if (headerCode == TR_BEGIN_DATA) {
if (pos + 3 > end) break; if (pos + 3 > end) break;
int dataLen = ((data[pos + 1] & 0xFF) << 8) | (data[pos + 2] & 0xFF); int dataLen = ((data[pos + 1] & 0xFF) << 8) | (data[pos + 2] & 0xFF);
int actualDataLen = dataLen - 3; int actualDataLen = (dataLen > 5) ? Math.min(dataLen - 5, end - (pos + 3)) : (end - (pos + 3));
pos += 3; pos += 3;
if (actualDataLen > 0 && pos + actualDataLen <= end) { if (actualDataLen > 0 && pos + actualDataLen <= end) {
dataFound = true;
if (messageFlag) { if (messageFlag) {
// Handle message payload from host // Handle message payload from host
dftDataAck(); dftDataAck();
@@ -269,25 +293,46 @@ public class FTDft {
} }
} }
// Send acknowledgement for file data // Send acknowledgement only if file data was actually received and processed
if (dataFound) {
dftDataAck(); dftDataAck();
} }
}
private void handleHostMessage(byte[] data, int offset, int length) { private void handleHostMessage(byte[] data, int offset, int length) {
StringBuilder sb = new StringBuilder(); StringBuilder asciiSb = new StringBuilder();
StringBuilder ebcdicSb = new StringBuilder();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int b = data[offset + i] & 0xFF; int b = data[offset + i] & 0xFF;
if (b == 0 || b == '$') break; if (b == 0 || b == '$') break;
char ch = translator.ebcdicToUnicode(b); asciiSb.append((char) b);
sb.append(ch); ebcdicSb.append(translator.ebcdicToUnicode(b));
} }
String msg = sb.toString().trim(); String asciiMsg = asciiSb.toString().trim();
log.info("DFT message: " + msg); String ebcdicMsg = ebcdicSb.toString().trim();
String msgLower = msg.toLowerCase(); // Determine if message is ASCII or EBCDIC
if (msg.startsWith(END_TRANSFER) || msgLower.contains("complete") || msgLower.contains("transferred") || msgLower.contains("success")) { String msg = asciiMsg;
if (asciiMsg.toUpperCase().startsWith("TRANS") || asciiMsg.toLowerCase().contains("file") || asciiMsg.toLowerCase().contains("error")) {
msg = asciiMsg;
} else if (ebcdicMsg.toUpperCase().startsWith("TRANS") || ebcdicMsg.toLowerCase().contains("file") || ebcdicMsg.toLowerCase().contains("error")) {
msg = ebcdicMsg;
} else {
int asciiPrintable = 0;
for (char c : asciiMsg.toCharArray()) {
if (c >= 32 && c <= 126) asciiPrintable++;
}
if (asciiPrintable < asciiMsg.length() / 2 && !ebcdicMsg.isEmpty()) {
msg = ebcdicMsg;
}
}
log.info("DFT host message: " + msg);
String msgUpper = msg.toUpperCase();
if (msgUpper.startsWith(END_TRANSFER) || msgUpper.contains("COMPLETE") || msgUpper.contains("TRANSFERRED") || msgUpper.contains("SUCCESS")) {
listener.onTransferComplete(null); listener.onTransferComplete(null);
} else if (listener.getCurrentState() == FTState.ABORT_SENT || msgLower.contains("error") || msgLower.contains("failed") || msgLower.contains("abort")) { } else if (msgUpper.startsWith("TRANS") || msgUpper.contains("ERROR") || msgUpper.contains("FAILED") || msgUpper.contains("ABORT") || msgUpper.contains("NOT FOUND") || listener.getCurrentState() == FTState.ABORT_SENT) {
listener.onTransferAborted(msg.isEmpty() ? "Transfer aborted" : msg); listener.onTransferAborted(msg.isEmpty() ? "Transfer aborted" : msg);
} else { } else {
// Informational message (default success) // Informational message (default success)
@@ -495,7 +540,7 @@ public class FTDft {
input.sendStructuredFieldData(out.toByteArray()); input.sendStructuredFieldData(out.toByteArray());
if (!messageFlag) { if (listener.getCurrentState() != FTState.NONE) {
log.info("DFT: File transfer completed on close request (" + bytesTransferred + " bytes)"); log.info("DFT: File transfer completed on close request (" + bytesTransferred + " bytes)");
listener.onTransferComplete(null); listener.onTransferComplete(null);
} }
@@ -504,13 +549,20 @@ public class FTDft {
// ========== Data Acknowledgement ========== // ========== Data Acknowledgement ==========
private void dftDataAck() { private void dftDataAck() {
ByteArrayOutputStream out = new ByteArrayOutputStream(6); ByteArrayOutputStream out = new ByteArrayOutputStream(12);
out.write(AID_SF); out.write(AID_SF);
out.write(0); out.write(5); out.write(0); out.write(11); // SF length = 11 (0x000B)
out.write(SF_TRANSFER_DATA); out.write(SF_TRANSFER_DATA);
out.write((TR_NORMAL_REPLY >> 8) & 0xFF); out.write((TR_NORMAL_REPLY >> 8) & 0xFF);
out.write(TR_NORMAL_REPLY & 0xFF); out.write(TR_NORMAL_REPLY & 0xFF);
out.write((TR_RECNUM_HDR >> 8) & 0xFF);
out.write(TR_RECNUM_HDR & 0xFF);
out.write((int) ((recnum >> 24) & 0xFF));
out.write((int) ((recnum >> 16) & 0xFF));
out.write((int) ((recnum >> 8) & 0xFF));
out.write((int) (recnum & 0xFF));
recnum++;
input.sendStructuredFieldData(out.toByteArray()); input.sendStructuredFieldData(out.toByteArray());
} }
@@ -780,8 +780,10 @@ public class InputProcessor {
* Used by DFT mode file transfer. * Used by DFT mode file transfer.
*/ */
public void sendStructuredFieldData(byte[] data) { public void sendStructuredFieldData(byte[] data) {
if (fsm != null) {
fsm.send3270Data(data); fsm.send3270Data(data);
} }
}
/** Get a reference to the TelnetFSM for direct SF operations. */ /** Get a reference to the TelnetFSM for direct SF operations. */
public TelnetFSM getTelnetFSM() { public TelnetFSM getTelnetFSM() {
+1
View File
@@ -43,6 +43,7 @@ fi
BUILD_DIR="$SCRIPT_DIR/build" BUILD_DIR="$SCRIPT_DIR/build"
TEST_BUILD_DIR="$BUILD_DIR/test-classes" TEST_BUILD_DIR="$BUILD_DIR/test-classes"
REPORTS_DIR="$BUILD_DIR/reports/tests" REPORTS_DIR="$BUILD_DIR/reports/tests"
rm -rf "$TEST_BUILD_DIR"
mkdir -p "$TEST_BUILD_DIR" "$REPORTS_DIR" mkdir -p "$TEST_BUILD_DIR" "$REPORTS_DIR"
# 3. Ensure JUnit standalone runner is present # 3. Ensure JUnit standalone runner is present