IND$FILE receiving

This commit is contained in:
2026-04-21 15:34:11 -04:00
parent ac88b527fa
commit bda8ea9530
15 changed files with 280 additions and 1 deletions
@@ -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;
}
}