This commit is contained in:
@@ -121,6 +121,21 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
||||
if (state == FTState.RUNNING || state == FTState.AWAIT_ACK) {
|
||||
log.info("User cancelled transfer");
|
||||
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) {
|
||||
log.info("Forcing cancel from state " + state);
|
||||
completeTransfer("Transfer cancelled.");
|
||||
|
||||
@@ -106,6 +106,7 @@ public class FTDft {
|
||||
}
|
||||
dftSaveBuf = null;
|
||||
dftSaveBufLen = 0;
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,9 +174,9 @@ public class FTDft {
|
||||
nameBuf = extractName(data, sfOffset + 3 + 31, 7);
|
||||
}
|
||||
|
||||
if (OPEN_MSG.equalsIgnoreCase(nameBuf)) {
|
||||
if (isMessageStream(nameBuf)) {
|
||||
messageFlag = true;
|
||||
log.info("DFT: Open request for message stream");
|
||||
log.info("DFT: Open request for message stream (" + nameBuf + ")");
|
||||
} else {
|
||||
messageFlag = false;
|
||||
listener.onDftRunning();
|
||||
@@ -188,15 +189,36 @@ public class FTDft {
|
||||
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) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder asciiSb = new StringBuilder();
|
||||
StringBuilder ebcdicSb = new StringBuilder();
|
||||
for (int i = 0; i < maxLen && (start + i) < data.length; i++) {
|
||||
int b = data[start + i] & 0xFF;
|
||||
if (b == 0) break;
|
||||
char ch = translator.ebcdicToUnicode(b);
|
||||
sb.append(ch);
|
||||
asciiSb.append((char) b);
|
||||
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() {
|
||||
@@ -227,6 +249,7 @@ public class FTDft {
|
||||
int pos = offset + 2;
|
||||
int end = offset + length;
|
||||
|
||||
boolean dataFound = false;
|
||||
// Look for TR_BEGIN_DATA marker
|
||||
while (pos < end) {
|
||||
int headerCode = data[pos] & 0xFF;
|
||||
@@ -234,10 +257,11 @@ public class FTDft {
|
||||
if (headerCode == TR_BEGIN_DATA) {
|
||||
if (pos + 3 > end) break;
|
||||
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;
|
||||
|
||||
if (actualDataLen > 0 && pos + actualDataLen <= end) {
|
||||
dataFound = true;
|
||||
if (messageFlag) {
|
||||
// Handle message payload from host
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
int b = data[offset + i] & 0xFF;
|
||||
if (b == 0 || b == '$') break;
|
||||
char ch = translator.ebcdicToUnicode(b);
|
||||
sb.append(ch);
|
||||
asciiSb.append((char) b);
|
||||
ebcdicSb.append(translator.ebcdicToUnicode(b));
|
||||
}
|
||||
String msg = sb.toString().trim();
|
||||
log.info("DFT message: " + msg);
|
||||
String asciiMsg = asciiSb.toString().trim();
|
||||
String ebcdicMsg = ebcdicSb.toString().trim();
|
||||
|
||||
String msgLower = msg.toLowerCase();
|
||||
if (msg.startsWith(END_TRANSFER) || msgLower.contains("complete") || msgLower.contains("transferred") || msgLower.contains("success")) {
|
||||
// Determine if message is ASCII or EBCDIC
|
||||
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);
|
||||
} 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);
|
||||
} else {
|
||||
// Informational message (default success)
|
||||
@@ -495,7 +540,7 @@ public class FTDft {
|
||||
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
|
||||
if (!messageFlag) {
|
||||
if (listener.getCurrentState() != FTState.NONE) {
|
||||
log.info("DFT: File transfer completed on close request (" + bytesTransferred + " bytes)");
|
||||
listener.onTransferComplete(null);
|
||||
}
|
||||
@@ -504,13 +549,20 @@ public class FTDft {
|
||||
// ========== Data Acknowledgement ==========
|
||||
|
||||
private void dftDataAck() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(12);
|
||||
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((TR_NORMAL_REPLY >> 8) & 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -780,8 +780,10 @@ public class InputProcessor {
|
||||
* Used by DFT mode file transfer.
|
||||
*/
|
||||
public void sendStructuredFieldData(byte[] data) {
|
||||
if (fsm != null) {
|
||||
fsm.send3270Data(data);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a reference to the TelnetFSM for direct SF operations. */
|
||||
public TelnetFSM getTelnetFSM() {
|
||||
|
||||
@@ -43,6 +43,7 @@ fi
|
||||
BUILD_DIR="$SCRIPT_DIR/build"
|
||||
TEST_BUILD_DIR="$BUILD_DIR/test-classes"
|
||||
REPORTS_DIR="$BUILD_DIR/reports/tests"
|
||||
rm -rf "$TEST_BUILD_DIR"
|
||||
mkdir -p "$TEST_BUILD_DIR" "$REPORTS_DIR"
|
||||
|
||||
# 3. Ensure JUnit standalone runner is present
|
||||
|
||||
Reference in New Issue
Block a user