This commit is contained in:
@@ -42,6 +42,7 @@ public class FTDft {
|
||||
private boolean dftEof = false;
|
||||
private boolean messageFlag = false;
|
||||
private long bytesTransferred = 0;
|
||||
private int pendingByte = -1;
|
||||
|
||||
// Savebuf for Read Modified retransmit
|
||||
private byte[] dftSaveBuf = null;
|
||||
@@ -92,6 +93,7 @@ public class FTDft {
|
||||
dftSaveBuf = null;
|
||||
dftSaveBufLen = 0;
|
||||
lastCr = false;
|
||||
pendingByte = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,7 +405,7 @@ public class FTDft {
|
||||
|
||||
try {
|
||||
while (!dftEof && totalRead < numbytes) {
|
||||
if (config.isAscii() && (config.isRemapFlag() || config.isCrFlag())) {
|
||||
if (config.isAscii()) {
|
||||
int b = dftAsciiRead(config);
|
||||
if (b == -1) {
|
||||
dftEof = true;
|
||||
@@ -490,6 +492,12 @@ public class FTDft {
|
||||
* Matching x3270 dft_ascii_read logic.
|
||||
*/
|
||||
private int dftAsciiRead(FTConfig config) throws IOException {
|
||||
if (pendingByte != -1) {
|
||||
int b = pendingByte;
|
||||
pendingByte = -1;
|
||||
return b;
|
||||
}
|
||||
|
||||
if (inputStream == null) return -1;
|
||||
|
||||
int c = inputStream.read();
|
||||
@@ -497,15 +505,18 @@ public class FTDft {
|
||||
|
||||
if (config.isCrFlag() && !lastCr && c == '\n') {
|
||||
lastCr = false;
|
||||
// Expand \n to \r\n: return \r byte now
|
||||
int rEbc = translator.unicodeToEbcdic('\r');
|
||||
if (rEbc < 0) rEbc = 0x0D;
|
||||
return config.isRemapFlag() ? FT2ASC[rEbc & 0xFF] : rEbc;
|
||||
// Expand \n to \r\n: buffer \n in pendingByte, return \r now
|
||||
pendingByte = convertAsciiByte('\n', config);
|
||||
return convertAsciiByte('\r', config);
|
||||
}
|
||||
lastCr = (c == '\r');
|
||||
|
||||
return convertAsciiByte((char) c, config);
|
||||
}
|
||||
|
||||
private int convertAsciiByte(char c, FTConfig config) {
|
||||
if (!config.isRemapFlag()) {
|
||||
int ebc = translator.unicodeToEbcdic((char) c);
|
||||
int ebc = translator.unicodeToEbcdic(c);
|
||||
return ebc >= 0 ? ebc : 0x40;
|
||||
}
|
||||
|
||||
@@ -519,7 +530,7 @@ public class FTDft {
|
||||
} else if (c == 0x9F) {
|
||||
ebc = 0xFF;
|
||||
} else {
|
||||
ebc = translator.unicodeToEbcdic((char) c);
|
||||
ebc = translator.unicodeToEbcdic(c);
|
||||
}
|
||||
if (ebc < 0) ebc = 0x40;
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package haus.nightmare.lib3270j.ft;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.ft.FTConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class FTCutTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor inputProcessor;
|
||||
private TestFTCutListener listener;
|
||||
private FTCut ftCut;
|
||||
|
||||
static class TestFTCutListener implements FTCut.FTCutListener {
|
||||
FTConfig config;
|
||||
FTState state = FTState.RUNNING;
|
||||
long bytesTransferred = 0;
|
||||
boolean completed = false;
|
||||
String errorMessage = null;
|
||||
|
||||
@Override
|
||||
public void onCutRunning() {
|
||||
state = FTState.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransferComplete(String error) {
|
||||
state = FTState.NONE;
|
||||
completed = true;
|
||||
errorMessage = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransferAborted(String error) {
|
||||
state = FTState.NONE;
|
||||
completed = false;
|
||||
errorMessage = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBytesTransferred(long bytes) {
|
||||
this.bytesTransferred = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FTState getCurrentState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(FTState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FTConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getLocalFile() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||
inputProcessor = new InputProcessor(screen, translator, null);
|
||||
listener = new TestFTCutListener();
|
||||
ftCut = new FTCut(screen, inputProcessor, translator, listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCutUploadAsciiExpandsUnixNewlines() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("GENPASS REXX A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRemapFlag(true);
|
||||
listener.config = config;
|
||||
|
||||
String originalText = "WORDS.1 = 'aah'\n";
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(originalText.getBytes(StandardCharsets.UTF_8));
|
||||
ftCut.initTransfer(in, null);
|
||||
|
||||
// Setup CUT screen frame for DATA_REQUEST
|
||||
screen.setCellFA(O_SF, (byte) (FA_PROTECT | FA_NUMERIC));
|
||||
screen.setCell(O_FRAME_TYPE, FT_DATA_REQUEST);
|
||||
screen.setCell(O_DR_FRAME_SEQ, FTConstants.to6(0, translator));
|
||||
|
||||
ftCut.processScreenUpdate();
|
||||
|
||||
// Read uploaded data from screen
|
||||
int len = (FTConstants.from6(screen.getCellEC(O_UP_LEN), translator) << 6) |
|
||||
FTConstants.from6(screen.getCellEC(O_UP_LEN + 1), translator);
|
||||
assertTrue(len > 0);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,85 @@
|
||||
package haus.nightmare.lib3270j.ft;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import static haus.nightmare.lib3270j.ft.FTConstants.*;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class FTDftTest {
|
||||
|
||||
private EbcdicTranslator translator;
|
||||
private ScreenBuffer screen;
|
||||
private InputProcessor input;
|
||||
private TestListener listener;
|
||||
private FTDft dft;
|
||||
private FTConfig config;
|
||||
private TestInputProcessor inputProcessor;
|
||||
private TestFTDftListener listener;
|
||||
private FTDft ftDft;
|
||||
|
||||
private static class TestListener implements FTDft.FTDftListener {
|
||||
static class TestInputProcessor extends InputProcessor {
|
||||
final List<byte[]> sentStructuredFields = new ArrayList<>();
|
||||
|
||||
public TestInputProcessor(ScreenBuffer screen, EbcdicTranslator translator) {
|
||||
super(screen, translator, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendStructuredFieldData(byte[] data) {
|
||||
sentStructuredFields.add(data.clone());
|
||||
}
|
||||
}
|
||||
|
||||
static class TestFTDftListener implements FTDft.FTDftListener {
|
||||
FTConfig config;
|
||||
FTState state = FTState.RUNNING;
|
||||
long bytesTransferred = 0;
|
||||
boolean dftRunningCalled = false;
|
||||
boolean completeCalled = false;
|
||||
boolean abortCalled = false;
|
||||
String completeMsg = null;
|
||||
String abortMsg = null;
|
||||
long bytesTransferred = 0;
|
||||
FTConstants.FTState state = FTConstants.FTState.RUNNING;
|
||||
FTConfig config;
|
||||
|
||||
@Override
|
||||
public void onDftRunning() {
|
||||
dftRunningCalled = true;
|
||||
state = FTState.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransferComplete(String errorMessage) {
|
||||
public void onTransferComplete(String error) {
|
||||
state = FTState.NONE;
|
||||
completeCalled = true;
|
||||
completeMsg = errorMessage;
|
||||
completeMsg = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransferAborted(String errorMessage) {
|
||||
public void onTransferAborted(String error) {
|
||||
state = FTState.NONE;
|
||||
abortCalled = true;
|
||||
abortMsg = errorMessage;
|
||||
abortMsg = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBytesTransferred(long bytes) {
|
||||
bytesTransferred = bytes;
|
||||
this.bytesTransferred = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FTConstants.FTState getCurrentState() {
|
||||
public FTState getCurrentState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(FTConstants.FTState state) {
|
||||
public void setState(FTState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@@ -80,21 +97,35 @@ public class FTDftTest {
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
listener = new TestListener();
|
||||
config = new FTConfig();
|
||||
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||
inputProcessor = new TestInputProcessor(screen, translator);
|
||||
listener = new TestFTDftListener();
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||
config.setTransferMode(FTConfig.TransferMode.BINARY);
|
||||
listener.config = config;
|
||||
ftDft = new FTDft(inputProcessor, translator, listener);
|
||||
}
|
||||
|
||||
dft = new FTDft(input, translator, listener);
|
||||
private byte[] extractDataFromGetReply(byte[] sf) {
|
||||
// SF format: AID_SF(1) + Len(2) + SF_TRANSFER_DATA(1) + TR_GET_REPLY(2) + TR_RECNUM_HDR(2) + Recnum(4) + TR_NOT_COMPRESSED(2) + TR_BEGIN_DATA(1) + DataLen(2) + Data(...)
|
||||
// Find TR_BEGIN_DATA (0x61)
|
||||
for (int i = 0; i < sf.length - 3; i++) {
|
||||
if ((sf[i] & 0xFF) == TR_BEGIN_DATA) {
|
||||
int dataLen = ((sf[i + 1] & 0xFF) << 8) | (sf[i + 2] & 0xFF);
|
||||
int actualLen = dataLen - 5;
|
||||
byte[] data = new byte[actualLen];
|
||||
System.arraycopy(sf, i + 3, data, 0, actualLen);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenMessageStreamAsciiAndHandleTrans14Error() {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
dft.initTransfer(null, outStream);
|
||||
ftDft.initTransfer(null, outStream);
|
||||
|
||||
// 1. Simulate TR_OPEN_REQ with ASCII "FT:MSG" matching j3270.log:
|
||||
// SF length = 0x0023, SF type = 0xD0, Request code = 0x0012, stream name "FT:MSG " at offset 28
|
||||
@@ -105,7 +136,7 @@ public class FTDftTest {
|
||||
0x46, 0x54, 0x3A, 0x4D, 0x53, 0x47, 0x20 // "FT:MSG "
|
||||
};
|
||||
|
||||
dft.processStructuredField(openReq, 0, openReq.length);
|
||||
ftDft.processStructuredField(openReq, 0, openReq.length);
|
||||
|
||||
// Should NOT trigger onDftRunning because it's a message stream
|
||||
assertFalse(listener.dftRunningCalled);
|
||||
@@ -132,7 +163,7 @@ public class FTDftTest {
|
||||
insertData[0] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
insertData[1] = (byte) (sfLen & 0xFF);
|
||||
|
||||
dft.processStructuredField(insertData, 0, insertData.length);
|
||||
ftDft.processStructuredField(insertData, 0, insertData.length);
|
||||
|
||||
// Verify that onTransferAborted was called with the error message
|
||||
assertTrue(listener.abortCalled, "onTransferAborted should be called for TRANS14 error");
|
||||
@@ -147,7 +178,7 @@ public class FTDftTest {
|
||||
@Test
|
||||
public void testOpenDataStreamAndWriteData() {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
dft.initTransfer(null, outStream);
|
||||
ftDft.initTransfer(null, outStream);
|
||||
|
||||
// 1. Simulate TR_OPEN_REQ with ASCII "FT:DATA"
|
||||
byte[] openReq = new byte[] {
|
||||
@@ -157,7 +188,7 @@ public class FTDftTest {
|
||||
0x46, 0x54, 0x3A, 0x44, 0x41, 0x54, 0x41 // "FT:DATA"
|
||||
};
|
||||
|
||||
dft.processStructuredField(openReq, 0, openReq.length);
|
||||
ftDft.processStructuredField(openReq, 0, openReq.length);
|
||||
assertTrue(listener.dftRunningCalled, "onDftRunning should be called for file data stream");
|
||||
|
||||
// 2. Simulate TR_DATA_INSERT with binary file payload
|
||||
@@ -181,10 +212,258 @@ public class FTDftTest {
|
||||
insertData[0] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
insertData[1] = (byte) (sfLen & 0xFF);
|
||||
|
||||
dft.processStructuredField(insertData, 0, insertData.length);
|
||||
ftDft.processStructuredField(insertData, 0, insertData.length);
|
||||
|
||||
// Data must be written to file stream
|
||||
assertArrayEquals(fileData, outStream.toByteArray());
|
||||
assertEquals(8, listener.bytesTransferred);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDftUploadAsciiExpandsUnixNewlines() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("GENPASS REXX A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRemapFlag(true);
|
||||
listener.config = config;
|
||||
|
||||
String originalText = "WORDS.1 = 'aah'\nWORDS.2 = 'aba'\nWORDS.3 = 'abo'\n";
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(originalText.getBytes(StandardCharsets.UTF_8));
|
||||
ftDft.initTransfer(in, null);
|
||||
|
||||
// Host sends TR_GET_REQ
|
||||
byte[] getReq = new byte[] {
|
||||
(byte) 0x00, (byte) 0x05, (byte) SF_TRANSFER_DATA,
|
||||
(byte) (TR_GET_REQ >> 8), (byte) (TR_GET_REQ & 0xFF)
|
||||
};
|
||||
ftDft.processStructuredField(getReq, 0, getReq.length);
|
||||
|
||||
assertEquals(1, inputProcessor.sentStructuredFields.size());
|
||||
byte[] reply = inputProcessor.sentStructuredFields.get(0);
|
||||
byte[] payload = extractDataFromGetReply(reply);
|
||||
|
||||
// Verify payload has CRLF (\r\n) after each line
|
||||
// Translate payload back:
|
||||
// Host pseudo-ASCII -> EBCDIC -> Unicode
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : payload) {
|
||||
int ub = b & 0xFF;
|
||||
if (ub == 0x0D) {
|
||||
sb.append("\r");
|
||||
} else if (ub == 0x0A) {
|
||||
sb.append("\n");
|
||||
} else {
|
||||
int ebc = ASC2FT[ub];
|
||||
sb.append(translator.ebcdicToUnicode(ebc));
|
||||
}
|
||||
}
|
||||
|
||||
String expected = "WORDS.1 = 'aah'\r\nWORDS.2 = 'aba'\r\nWORDS.3 = 'abo'\r\n";
|
||||
assertEquals(expected, sb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDftUploadAsciiPreservesWindowsNewlines() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("TEST FILE A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRemapFlag(true);
|
||||
listener.config = config;
|
||||
|
||||
String originalText = "LINE1\r\nLINE2\r\n";
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(originalText.getBytes(StandardCharsets.UTF_8));
|
||||
ftDft.initTransfer(in, null);
|
||||
|
||||
byte[] getReq = new byte[] {
|
||||
(byte) 0x00, (byte) 0x05, (byte) SF_TRANSFER_DATA,
|
||||
(byte) (TR_GET_REQ >> 8), (byte) (TR_GET_REQ & 0xFF)
|
||||
};
|
||||
ftDft.processStructuredField(getReq, 0, getReq.length);
|
||||
|
||||
assertEquals(1, inputProcessor.sentStructuredFields.size());
|
||||
byte[] payload = extractDataFromGetReply(inputProcessor.sentStructuredFields.get(0));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : payload) {
|
||||
int ub = b & 0xFF;
|
||||
if (ub == 0x0D) {
|
||||
sb.append("\r");
|
||||
} else if (ub == 0x0A) {
|
||||
sb.append("\n");
|
||||
} else {
|
||||
int ebc = ASC2FT[ub];
|
||||
sb.append(translator.ebcdicToUnicode(ebc));
|
||||
}
|
||||
}
|
||||
|
||||
String expected = "LINE1\r\nLINE2\r\n";
|
||||
assertEquals(expected, sb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDftUploadAsciiKeepCr() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("TEST FILE A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.KEEP);
|
||||
config.setRemapFlag(true);
|
||||
listener.config = config;
|
||||
|
||||
String originalText = "LINE1\nLINE2\n";
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(originalText.getBytes(StandardCharsets.UTF_8));
|
||||
ftDft.initTransfer(in, null);
|
||||
|
||||
byte[] getReq = new byte[] {
|
||||
(byte) 0x00, (byte) 0x05, (byte) SF_TRANSFER_DATA,
|
||||
(byte) (TR_GET_REQ >> 8), (byte) (TR_GET_REQ & 0xFF)
|
||||
};
|
||||
ftDft.processStructuredField(getReq, 0, getReq.length);
|
||||
|
||||
assertEquals(1, inputProcessor.sentStructuredFields.size());
|
||||
byte[] payload = extractDataFromGetReply(inputProcessor.sentStructuredFields.get(0));
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : payload) {
|
||||
int ub = b & 0xFF;
|
||||
if (ub == 0x0D) {
|
||||
sb.append("\r");
|
||||
} else if (ub == 0x0A) {
|
||||
sb.append("\n");
|
||||
} else {
|
||||
int ebc = ASC2FT[ub];
|
||||
sb.append(translator.ebcdicToUnicode(ebc));
|
||||
}
|
||||
}
|
||||
|
||||
String expected = "LINE1\nLINE2\n";
|
||||
assertEquals(expected, sb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDftDownloadAsciiStripsCR() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("TEST FILE A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRemapFlag(true);
|
||||
listener.config = config;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ftDft.initTransfer(null, out);
|
||||
|
||||
// Prepare host insert data with CRLF
|
||||
String hostData = "RECORD1\r\nRECORD2\r\n";
|
||||
ByteArrayOutputStream hostStream = new ByteArrayOutputStream();
|
||||
for (char c : hostData.toCharArray()) {
|
||||
if (c == '\r' || c == '\n') {
|
||||
hostStream.write(c);
|
||||
} else {
|
||||
int ebc = translator.unicodeToEbcdic(c);
|
||||
hostStream.write(FT2ASC[ebc & 0xFF]);
|
||||
}
|
||||
}
|
||||
byte[] hostBytes = hostStream.toByteArray();
|
||||
|
||||
// Build SF_TRANSFER_DATA with TR_DATA_INSERT
|
||||
ByteArrayOutputStream sf = new ByteArrayOutputStream();
|
||||
sf.write(0); sf.write(0); // length placeholder
|
||||
sf.write(SF_TRANSFER_DATA);
|
||||
sf.write((TR_DATA_INSERT >> 8) & 0xFF);
|
||||
sf.write(TR_DATA_INSERT & 0xFF);
|
||||
sf.write(TR_BEGIN_DATA);
|
||||
int dataLen = hostBytes.length + 5;
|
||||
sf.write((dataLen >> 8) & 0xFF);
|
||||
sf.write(dataLen & 0xFF);
|
||||
sf.write(hostBytes, 0, hostBytes.length);
|
||||
|
||||
byte[] sfBytes = sf.toByteArray();
|
||||
int totalLen = sfBytes.length;
|
||||
sfBytes[0] = (byte) ((totalLen >> 8) & 0xFF);
|
||||
sfBytes[1] = (byte) (totalLen & 0xFF);
|
||||
|
||||
ftDft.processStructuredField(sfBytes, 0, totalLen);
|
||||
|
||||
String downloaded = new String(out.toByteArray(), StandardCharsets.UTF_8);
|
||||
assertEquals("RECORD1\nRECORD2\n", downloaded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDftUploadLargeFileMultiBufferSpan() {
|
||||
FTConfig config = new FTConfig();
|
||||
config.setDirection(FTConfig.Direction.SEND);
|
||||
config.setHostType(FTConfig.HostType.CMS);
|
||||
config.setHostFilename("GENPASS REXX A");
|
||||
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||
config.setRemapFlag(true);
|
||||
config.setDftBufferSize(512); // Use small buffer size to test multi-buffer splitting
|
||||
listener.config = config;
|
||||
|
||||
// Build 500 lines resembling GENPASS.rexx
|
||||
StringBuilder originalSb = new StringBuilder();
|
||||
originalSb.append("/* REXX */\n");
|
||||
for (int i = 1; i <= 500; i++) {
|
||||
originalSb.append("WORDS.").append(i).append(" = 'word_").append(i).append("'\n");
|
||||
}
|
||||
String originalText = originalSb.toString();
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(originalText.getBytes(StandardCharsets.UTF_8));
|
||||
ftDft.initTransfer(in, null);
|
||||
|
||||
byte[] getReq = new byte[] {
|
||||
(byte) 0x00, (byte) 0x05, (byte) SF_TRANSFER_DATA,
|
||||
(byte) (TR_GET_REQ >> 8), (byte) (TR_GET_REQ & 0xFF)
|
||||
};
|
||||
|
||||
// Repeatedly process getReq until EOF
|
||||
while (listener.state != FTState.NONE && inputProcessor.sentStructuredFields.size() < 100) {
|
||||
int beforeCount = inputProcessor.sentStructuredFields.size();
|
||||
ftDft.processStructuredField(getReq, 0, getReq.length);
|
||||
if (inputProcessor.sentStructuredFields.size() == beforeCount) break;
|
||||
|
||||
byte[] lastSent = inputProcessor.sentStructuredFields.get(inputProcessor.sentStructuredFields.size() - 1);
|
||||
// Check if EOF reply (0x4608 / TR_ERROR_REPLY)
|
||||
if (lastSent.length >= 6 && (lastSent[4] & 0xFF) == 0x46 && (lastSent[5] & 0xFF) == 0x08) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct full stream from all data buffers
|
||||
ByteArrayOutputStream fullStream = new ByteArrayOutputStream();
|
||||
for (byte[] sf : inputProcessor.sentStructuredFields) {
|
||||
byte[] payload = extractDataFromGetReply(sf);
|
||||
fullStream.write(payload, 0, payload.length);
|
||||
}
|
||||
|
||||
byte[] allBytes = fullStream.toByteArray();
|
||||
StringBuilder decodedSb = new StringBuilder();
|
||||
for (byte b : allBytes) {
|
||||
int ub = b & 0xFF;
|
||||
if (ub == 0x0D) {
|
||||
decodedSb.append("\r");
|
||||
} else if (ub == 0x0A) {
|
||||
decodedSb.append("\n");
|
||||
} else {
|
||||
int ebc = ASC2FT[ub];
|
||||
decodedSb.append(translator.ebcdicToUnicode(ebc));
|
||||
}
|
||||
}
|
||||
|
||||
String expected = originalText.replace("\n", "\r\n");
|
||||
assertEquals(expected, decodedSb.toString());
|
||||
|
||||
// Verify specific line structure
|
||||
String decodedStr = decodedSb.toString();
|
||||
assertTrue(decodedStr.contains("WORDS.160 = 'word_160'\r\nWORDS.161 = 'word_161'\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user