IND$FILE receiving
This commit is contained in:
@@ -115,6 +115,9 @@ public class Telnet3270Client {
|
||||
/** Get the input processor for keyboard operations. */
|
||||
public InputProcessor getInputProcessor() { return inputProcessor; }
|
||||
|
||||
/** Get the data stream processor. */
|
||||
public DataStreamProcessor getDataStreamProcessor() { return dsProcessor; }
|
||||
|
||||
/** Get the connection config. */
|
||||
public ConnectionConfig getConfig() { return config; }
|
||||
|
||||
@@ -130,6 +133,11 @@ public class Telnet3270Client {
|
||||
}
|
||||
}
|
||||
|
||||
/** Emulate input of a string, pressing Enter on newlines. */
|
||||
public void emulateInput(String s) {
|
||||
inputProcessor.emulateInput(s);
|
||||
}
|
||||
|
||||
/** Send Enter key. */
|
||||
public void sendEnter() {
|
||||
inputProcessor.sendAid(org.lib3270j.protocol.DS3270Constants.AID_ENTER);
|
||||
|
||||
@@ -30,6 +30,9 @@ public class DataStreamProcessor {
|
||||
|
||||
// Callback for sending data back to host
|
||||
private OutputSender outputSender;
|
||||
|
||||
// File Transfer DFT handler
|
||||
private org.lib3270j.ft.FTDft ftDft;
|
||||
|
||||
/** Functional interface for sending output back through the telnet stack. */
|
||||
@FunctionalInterface
|
||||
@@ -48,6 +51,10 @@ public class DataStreamProcessor {
|
||||
public void setOutputSender(OutputSender sender) {
|
||||
this.outputSender = sender;
|
||||
}
|
||||
|
||||
public void setFTDft(org.lib3270j.ft.FTDft ftDft) {
|
||||
this.ftDft = ftDft;
|
||||
}
|
||||
|
||||
public void addScreenUpdateListener(ScreenUpdateListener l) {
|
||||
screenListeners.add(l);
|
||||
@@ -698,6 +705,13 @@ public class DataStreamProcessor {
|
||||
processRecord(data, pos + 4, fieldLen - 4, false);
|
||||
}
|
||||
break;
|
||||
case SF_TRANSFER_DATA:
|
||||
if (ftDft != null) {
|
||||
ftDft.processStructuredField(data, pos, fieldLen);
|
||||
} else {
|
||||
log.fine("SF_TRANSFER_DATA received but ftDft is null");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.fine("Unknown SF id: " + String.format("0x%02x", sfId));
|
||||
break;
|
||||
|
||||
@@ -31,6 +31,7 @@ public class QueryReplyBuilder {
|
||||
QR_COLOR, // 0x86
|
||||
QR_HIGHLIGHTING, // 0x87
|
||||
QR_REPLY_MODES, // 0x88
|
||||
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
||||
QR_IMP_PART, // 0xa6
|
||||
};
|
||||
|
||||
@@ -68,6 +69,10 @@ public class QueryReplyBuilder {
|
||||
// Reply Modes
|
||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||
|
||||
// Distributed Data Management (DFT File Transfer)
|
||||
// Hardcoded limit of 4096 is standard; the client can configure it during transfer.
|
||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||
|
||||
// Implicit Partition
|
||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||
|
||||
@@ -189,6 +194,19 @@ public class QueryReplyBuilder {
|
||||
return new byte[] { SF_SRM_FIELD, SF_SRM_XFIELD, SF_SRM_CHAR };
|
||||
}
|
||||
|
||||
private byte[] buildDdm(int bufferSize) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(8);
|
||||
out.write(0x00); // reserved
|
||||
out.write(0x00); // reserved
|
||||
out.write((bufferSize >> 8) & 0xFF); // inbound length limit (INLIM)
|
||||
out.write(bufferSize & 0xFF);
|
||||
out.write((bufferSize >> 8) & 0xFF); // outbound length limit (OUTLIM)
|
||||
out.write(bufferSize & 0xFF);
|
||||
out.write(0x01); // NSS = 01
|
||||
out.write(0x01); // DDMSS = 01
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] buildImplicitPartition(int maxCols, int maxRows) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(22);
|
||||
// Implicit partition sizes, 2 self-defining parameters
|
||||
|
||||
@@ -319,4 +319,113 @@ public class InputProcessor {
|
||||
keyboardLocked = false;
|
||||
insertMode = false;
|
||||
}
|
||||
|
||||
// ========== File Transfer Support ==========
|
||||
|
||||
/**
|
||||
* Send an AID code, bypassing the keyboard lock.
|
||||
* Used internally by the file transfer protocol to send Enter/PF keys
|
||||
* during a transfer when the keyboard is normally locked.
|
||||
*/
|
||||
public void sendAidForFT(int aidCode) {
|
||||
boolean wasLocked = keyboardLocked;
|
||||
keyboardLocked = false;
|
||||
sendAid(aidCode);
|
||||
keyboardLocked = wasLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a string into the current cursor position and press Enter.
|
||||
* Used by IND$FILE to inject the transfer command.
|
||||
* Equivalent to emulate_input() in x3270.
|
||||
*/
|
||||
public void emulateInput(String text) {
|
||||
// Type each character
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
if (ch == '\n') {
|
||||
// Newline means press Enter
|
||||
sendAid(AID_ENTER);
|
||||
return;
|
||||
}
|
||||
typeCharacter(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase the current field and return its capacity.
|
||||
* Used by IND$FILE to prepare the field for the transfer command.
|
||||
* Equivalent to kybd_prime() in x3270.
|
||||
*
|
||||
* Returns:
|
||||
* - The number of characters that can fit in the field, or
|
||||
* - -1 if the keyboard is locked
|
||||
* - -2 if not in 3270 mode
|
||||
* - -3 if no input field found
|
||||
*/
|
||||
public int kybdPrime() {
|
||||
if (keyboardLocked) return -1;
|
||||
|
||||
if (!screen.isFormatted()) {
|
||||
// Unformatted screen — use entire buffer
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
screen.setCursorAddress(0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
screen.getCell(i).ec = 0;
|
||||
screen.getCell(i).ucs4 = 0;
|
||||
}
|
||||
screen.markAllChanged();
|
||||
return size;
|
||||
}
|
||||
|
||||
// Find the field at cursor and erase it
|
||||
int curAddr = screen.getCursorAddress();
|
||||
byte faVal = screen.getFieldAttributeAt(curAddr);
|
||||
if (faIsProtected(faVal & 0xFF)) {
|
||||
// Try to find first unprotected field
|
||||
curAddr = screen.findNextUnprotected(curAddr);
|
||||
faVal = screen.getFieldAttributeAt(curAddr);
|
||||
if (faIsProtected(faVal & 0xFF)) {
|
||||
return -3; // No unprotected field
|
||||
}
|
||||
}
|
||||
|
||||
// Move to start of field
|
||||
int faAddr = screen.findFieldAttribute(curAddr);
|
||||
int fieldStart = screen.incrementAddress(faAddr);
|
||||
screen.setCursorAddress(fieldStart);
|
||||
|
||||
// Count field length and erase
|
||||
int fieldLen = 0;
|
||||
int pos = fieldStart;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
while (!screen.getCell(pos).isFieldAttribute()) {
|
||||
screen.getCell(pos).ec = 0;
|
||||
screen.getCell(pos).ucs4 = 0;
|
||||
fieldLen++;
|
||||
pos = screen.incrementAddress(pos);
|
||||
if (pos == fieldStart) break;
|
||||
}
|
||||
|
||||
// Set MDT
|
||||
if (faAddr >= 0) {
|
||||
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||
}
|
||||
screen.markAllChanged();
|
||||
return fieldLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a structured field response directly.
|
||||
* Used by DFT mode file transfer.
|
||||
*/
|
||||
public void sendStructuredFieldData(byte[] data) {
|
||||
fsm.send3270Data(data);
|
||||
}
|
||||
|
||||
/** Get a reference to the TelnetFSM for direct SF operations. */
|
||||
public TelnetFSM getTelnetFSM() {
|
||||
return fsm;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -326,4 +326,59 @@ public class ScreenBuffer {
|
||||
public ExtendedAttribute getDefaultFieldAttribute() {
|
||||
return defaultFA;
|
||||
}
|
||||
|
||||
// ========== Low-level cell access for file transfer (CUT mode) ==========
|
||||
|
||||
/**
|
||||
* Write an EBCDIC byte to a cell at the given buffer address.
|
||||
* Used by CUT mode file transfer to place data into the screen buffer.
|
||||
*/
|
||||
public void setCell(int baddr, int ebcdicByte) {
|
||||
if (baddr < 0 || baddr >= maxRows * maxCols) return;
|
||||
buffer[baddr].ec = (byte) (ebcdicByte & 0xFF);
|
||||
buffer[baddr].ucs4 = 0; // will be set on display refresh
|
||||
screenChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an EBCDIC byte with extended attribute flags (cs=0).
|
||||
* Equivalent to ctlr_add() in x3270.
|
||||
*/
|
||||
public void setCellWithCS(int baddr, int ebcdicByte, int cs) {
|
||||
if (baddr < 0 || baddr >= maxRows * maxCols) return;
|
||||
buffer[baddr].ec = (byte) (ebcdicByte & 0xFF);
|
||||
buffer[baddr].cs = (byte) (cs & 0xFF);
|
||||
buffer[baddr].ucs4 = 0;
|
||||
screenChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the field attribute byte at a given buffer address.
|
||||
* Used by CUT mode to hide data fields during transfer.
|
||||
*/
|
||||
public void setCellFA(int baddr, byte fa) {
|
||||
if (baddr < 0 || baddr >= maxRows * maxCols) return;
|
||||
buffer[baddr].fa = fa;
|
||||
buffer[baddr].ic = 1; // mark as field attribute
|
||||
formatted = true;
|
||||
screenChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get just the EBCDIC code byte from a cell.
|
||||
* Convenience method for file transfer data extraction.
|
||||
*/
|
||||
public int getCellEC(int baddr) {
|
||||
if (baddr < 0 || baddr >= maxRows * maxCols) return 0;
|
||||
return buffer[baddr].ec & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field attribute byte at a cell (the fa byte, not the ic field).
|
||||
* For CUT mode checking of field attribute modifications.
|
||||
*/
|
||||
public byte getCellFAByte(int baddr) {
|
||||
if (baddr < 0 || baddr >= maxRows * maxCols) return 0;
|
||||
return buffer[baddr].fa;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user