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) {
|
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
|
||||||
dftDataAck();
|
if (dataFound) {
|
||||||
|
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,7 +780,9 @@ 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) {
|
||||||
fsm.send3270Data(data);
|
if (fsm != null) {
|
||||||
|
fsm.send3270Data(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a reference to the TelnetFSM for direct SF operations. */
|
/** Get a reference to the TelnetFSM for direct SF operations. */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user