Fix gradle and add workflows
Build and Test j3270 / Build JAR & Run Tests (push) Failing after 2m24s
Build and Test j3270 / Build JAR & Run Tests (push) Failing after 2m24s
This commit is contained in:
@@ -196,6 +196,18 @@ public class Telnet3270Client {
|
||||
public void tab() { inputProcessor.tab(); }
|
||||
/** Back-tab to previous unprotected field. */
|
||||
public void backTab() { inputProcessor.backTab(); }
|
||||
/** Move cursor to next line. */
|
||||
public void newline() { inputProcessor.newline(); }
|
||||
/** Erase all unprotected fields. */
|
||||
public void eraseInput() { inputProcessor.eraseInput(); }
|
||||
/** Insert Duplicate order. */
|
||||
public void dup() { inputProcessor.dup(); }
|
||||
/** Insert Field Mark order. */
|
||||
public void fieldMark() { inputProcessor.fieldMark(); }
|
||||
/** Attention key. */
|
||||
public void attn() { inputProcessor.attn(); }
|
||||
/** SysReq key. */
|
||||
public void sysReq() { inputProcessor.sysReq(); }
|
||||
/** Reset (unlock keyboard). */
|
||||
public void reset() { inputProcessor.reset(); }
|
||||
}
|
||||
|
||||
@@ -816,8 +816,18 @@ public class DataStreamProcessor {
|
||||
}
|
||||
|
||||
private void sendRequestedQueryReplies(byte[] codes) {
|
||||
// For now, send all — refinement can come later
|
||||
sendAllQueryReplies();
|
||||
byte[] qr = qrBuilder.buildQueryReplies(codes, screen.getMaxCols(), screen.getMaxRows(),
|
||||
screen.getMaxCols() * screen.getMaxRows());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < qr.length; i++) {
|
||||
sb.append(String.format("%02x ", qr[i] & 0xFF));
|
||||
if ((i + 1) % 32 == 0)
|
||||
sb.append("\n ");
|
||||
}
|
||||
log.info("Requested query reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||
if (outputSender != null) {
|
||||
outputSender.send3270Data(qr);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Output helpers ==========
|
||||
|
||||
@@ -70,13 +70,69 @@ public class QueryReplyBuilder {
|
||||
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));
|
||||
|
||||
log.info("Built " + out.size() + " bytes of query replies");
|
||||
log.info("Built " + out.size() + " bytes of all query replies");
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
|
||||
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
|
||||
* matching x3270 sf.c behavior.
|
||||
*/
|
||||
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
||||
if (requestedCodes == null || requestedCodes.length == 0) {
|
||||
return buildAllQueryReplies(maxCols, maxRows, bufferSize);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||
out.write(AID_SF);
|
||||
|
||||
for (byte codeByte : requestedCodes) {
|
||||
int code = codeByte & 0xFF;
|
||||
switch (code) {
|
||||
case QR_SUMMARY:
|
||||
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||
break;
|
||||
case QR_USABLE_AREA:
|
||||
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||
break;
|
||||
case QR_ALPHA_PART:
|
||||
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||
break;
|
||||
case QR_CHARSETS:
|
||||
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||
break;
|
||||
case QR_COLOR:
|
||||
appendQueryReply(out, QR_COLOR, buildColor());
|
||||
break;
|
||||
case QR_HIGHLIGHTING:
|
||||
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||
break;
|
||||
case QR_REPLY_MODES:
|
||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||
break;
|
||||
case QR_DDM:
|
||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||
break;
|
||||
case QR_IMP_PART:
|
||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||
break;
|
||||
case QR_RPQNAMES:
|
||||
appendQueryReply(out, QR_RPQNAMES, new byte[0]);
|
||||
break;
|
||||
default:
|
||||
// Unsupported query reply code — emit QR_NULL
|
||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Built " + out.size() + " bytes of requested query replies for " + requestedCodes.length + " codes");
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -225,84 +225,96 @@ public class FTConfig {
|
||||
// IND$FILE GET/PUT hostfile
|
||||
cmd.append("IND$FILE ");
|
||||
cmd.append(isReceive() ? "GET " : "PUT ");
|
||||
cmd.append(hostFilename);
|
||||
cmd.append(hostFilename != null ? hostFilename.trim() : "");
|
||||
|
||||
// CMS/CICS options use ( prefix
|
||||
if (hostType != HostType.TSO) {
|
||||
cmd.append(" (");
|
||||
} else {
|
||||
cmd.append(" ");
|
||||
}
|
||||
// Collect options into a separate buffer
|
||||
StringBuilder opts = new StringBuilder();
|
||||
|
||||
// Mode
|
||||
if (isAscii()) {
|
||||
cmd.append("ASCII");
|
||||
opts.append("ASCII");
|
||||
} else if (hostType == HostType.CICS) {
|
||||
cmd.append("BINARY");
|
||||
opts.append("BINARY");
|
||||
}
|
||||
|
||||
// CR/LF handling
|
||||
if (isAscii() && isCrFlag()) {
|
||||
cmd.append(" CRLF");
|
||||
} else if (hostType == HostType.CICS) {
|
||||
cmd.append(" NOCRLF");
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("CRLF");
|
||||
} else if (hostType == HostType.CICS && !isAscii()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("NOCRLF");
|
||||
}
|
||||
|
||||
// Append (send only)
|
||||
if (isAppend() && isSend()) {
|
||||
cmd.append(" APPEND");
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("APPEND");
|
||||
}
|
||||
|
||||
// TSO-specific send options
|
||||
// Host-specific send options
|
||||
if (isSend()) {
|
||||
if (hostType == HostType.TSO) {
|
||||
if (recfm != RecordFormat.DEFAULT) {
|
||||
cmd.append(" RECFM(");
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("RECFM(");
|
||||
switch (recfm) {
|
||||
case FIXED: cmd.append("F"); break;
|
||||
case VARIABLE: cmd.append("V"); break;
|
||||
case UNDEFINED: cmd.append("U"); break;
|
||||
case FIXED: opts.append("F"); break;
|
||||
case VARIABLE: opts.append("V"); break;
|
||||
case UNDEFINED: opts.append("U"); break;
|
||||
default: break;
|
||||
}
|
||||
cmd.append(")");
|
||||
opts.append(")");
|
||||
if (lrecl > 0) {
|
||||
cmd.append(" LRECL(").append(lrecl).append(")");
|
||||
opts.append(" LRECL(").append(lrecl).append(")");
|
||||
}
|
||||
if (blksize > 0) {
|
||||
cmd.append(" BLKSIZE(").append(blksize).append(")");
|
||||
opts.append(" BLKSIZE(").append(blksize).append(")");
|
||||
}
|
||||
}
|
||||
if (units != AllocationUnit.DEFAULT) {
|
||||
cmd.append(" SPACE(").append(primarySpace);
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("SPACE(").append(primarySpace);
|
||||
if (secondarySpace > 0) {
|
||||
cmd.append(",").append(secondarySpace);
|
||||
opts.append(",").append(secondarySpace);
|
||||
}
|
||||
cmd.append(")");
|
||||
opts.append(")");
|
||||
switch (units) {
|
||||
case TRACKS: cmd.append(" TRACKS"); break;
|
||||
case CYLINDERS: cmd.append(" CYLINDERS"); break;
|
||||
case AVBLOCK: cmd.append(" AVBLOCK(").append(avblock).append(")"); break;
|
||||
case TRACKS: opts.append(" TRACKS"); break;
|
||||
case CYLINDERS: opts.append(" CYLINDERS"); break;
|
||||
case AVBLOCK: opts.append(" AVBLOCK(").append(avblock).append(")"); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
} else if (hostType == HostType.CMS) {
|
||||
if (recfm != RecordFormat.DEFAULT) {
|
||||
cmd.append(" RECFM ");
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append("RECFM ");
|
||||
switch (recfm) {
|
||||
case FIXED: cmd.append("F"); break;
|
||||
case VARIABLE: cmd.append("V"); break;
|
||||
case FIXED: opts.append("F"); break;
|
||||
case VARIABLE: opts.append("V"); break;
|
||||
default: break;
|
||||
}
|
||||
if (lrecl > 0) {
|
||||
cmd.append(" LRECL ").append(lrecl);
|
||||
opts.append(" LRECL ").append(lrecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional options
|
||||
if (otherOptions != null) {
|
||||
cmd.append(" ").append(otherOptions);
|
||||
if (otherOptions != null && !otherOptions.trim().isEmpty()) {
|
||||
if (opts.length() > 0) opts.append(" ");
|
||||
opts.append(otherOptions.trim());
|
||||
}
|
||||
|
||||
// Apply options with host-specific prefix only if options are present
|
||||
if (opts.length() > 0) {
|
||||
if (hostType != HostType.TSO) {
|
||||
cmd.append(" (").append(opts.toString());
|
||||
} else {
|
||||
cmd.append(" ").append(opts.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.toString().trim();
|
||||
|
||||
@@ -21,7 +21,7 @@ public class FTCut {
|
||||
|
||||
/** Callback for transfer events */
|
||||
public interface FTCutListener {
|
||||
void onTransferRunning();
|
||||
void onCutRunning();
|
||||
void onTransferComplete(String errorMessage);
|
||||
void onTransferAborted(String errorMessage);
|
||||
void onBytesTransferred(long bytes);
|
||||
@@ -221,7 +221,7 @@ public class FTCut {
|
||||
xlateBufIx = 0;
|
||||
cutEof = false;
|
||||
cutAck();
|
||||
listener.onTransferRunning();
|
||||
listener.onCutRunning();
|
||||
break;
|
||||
|
||||
case SC_XFER_COMPLETE:
|
||||
|
||||
@@ -23,7 +23,7 @@ public class FTDft {
|
||||
|
||||
/** Callback for transfer events (shared interface with CUT) */
|
||||
public interface FTDftListener {
|
||||
void onTransferRunning();
|
||||
void onDftRunning();
|
||||
void onTransferComplete(String errorMessage);
|
||||
void onTransferAborted(String errorMessage);
|
||||
void onBytesTransferred(long bytes);
|
||||
@@ -178,7 +178,7 @@ public class FTDft {
|
||||
log.info("DFT: Open request for message stream");
|
||||
} else {
|
||||
messageFlag = false;
|
||||
listener.onTransferRunning();
|
||||
listener.onDftRunning();
|
||||
}
|
||||
|
||||
dftEof = false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.lib3270j.screen.ScreenBuffer;
|
||||
import org.lib3270j.screen.ExtendedAttribute;
|
||||
import org.lib3270j.charset.EbcdicTranslator;
|
||||
import org.lib3270j.telnet.TelnetFSM;
|
||||
import org.lib3270j.protocol.TelnetConstants;
|
||||
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -362,6 +363,82 @@ public class InputProcessor {
|
||||
deleteChar();
|
||||
}
|
||||
|
||||
/** Erase All Unprotected fields (Erase Input key in 3270). */
|
||||
public void eraseInput() {
|
||||
if (keyboardLocked) return;
|
||||
screen.eraseAllUnprotected();
|
||||
screen.markAllChanged();
|
||||
}
|
||||
|
||||
/** Move cursor to first unprotected field on next line (Newline key in 3270). */
|
||||
public void newline() {
|
||||
if (keyboardLocked) return;
|
||||
int cur = screen.getCursorAddress();
|
||||
int cols = screen.getCols();
|
||||
int rows = screen.getRows();
|
||||
int curRow = cur / cols;
|
||||
int nextRow = (curRow + 1) % rows;
|
||||
int target = nextRow * cols;
|
||||
if (screen.isFormatted()) {
|
||||
target = screen.findNextUnprotected(target == 0 ? (rows * cols - 1) : (target - 1));
|
||||
}
|
||||
screen.setCursorAddress(target);
|
||||
screen.markAllChanged();
|
||||
}
|
||||
|
||||
/** Insert Duplicate (DUP) code and advance to next field. */
|
||||
public void dup() {
|
||||
if (keyboardLocked) return;
|
||||
int baddr = screen.getCursorAddress();
|
||||
byte faVal = screen.getFieldAttributeAt(baddr);
|
||||
if (faIsProtected(faVal & 0xFF)) return;
|
||||
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
ea.ec = (byte) FCORDER_DUP;
|
||||
ea.ucs4 = '*';
|
||||
int faAddr = screen.findFieldAttribute(baddr);
|
||||
if (faAddr >= 0) {
|
||||
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||
}
|
||||
tab();
|
||||
}
|
||||
|
||||
/** Insert Field Mark (FM) code. */
|
||||
public void fieldMark() {
|
||||
if (keyboardLocked) return;
|
||||
int baddr = screen.getCursorAddress();
|
||||
byte faVal = screen.getFieldAttributeAt(baddr);
|
||||
if (faIsProtected(faVal & 0xFF)) return;
|
||||
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
ea.ec = (byte) FCORDER_FM;
|
||||
ea.ucs4 = ';';
|
||||
int faAddr = screen.findFieldAttribute(baddr);
|
||||
if (faAddr >= 0) {
|
||||
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||
}
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
baddr = (baddr + 1) % size;
|
||||
while (screen.getCell(baddr).isFieldAttribute()) {
|
||||
baddr = (baddr + 1) % size;
|
||||
}
|
||||
screen.setCursorAddress(baddr);
|
||||
screen.markAllChanged();
|
||||
}
|
||||
|
||||
/** Attention key (sends Telnet IP). */
|
||||
public void attn() {
|
||||
if (fsm != null && fsm.getConnectionState() != null && fsm.getConnectionState().isConnected()) {
|
||||
byte[] ip = new byte[] { (byte) TelnetConstants.IAC, (byte) TelnetConstants.IP };
|
||||
fsm.sendRecord(ip);
|
||||
}
|
||||
}
|
||||
|
||||
/** SysReq key. */
|
||||
public void sysReq() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/** Reset (unlock keyboard, cancel insert mode). */
|
||||
public void reset() {
|
||||
setKeyboardLocked(false);
|
||||
|
||||
@@ -305,6 +305,7 @@ public class ScreenBuffer {
|
||||
|
||||
private char getAplGraphic(int ec) {
|
||||
switch (ec) {
|
||||
// Box-drawing line and corner characters (standard IBM 3270 GE / APL)
|
||||
case 0xA2: return '\u2500'; // Horizontal Line 's' -> '─'
|
||||
case 0x85: return '\u2502'; // Vertical Line 'e' -> '│'
|
||||
case 0xC5: return '\u250C'; // Top Left 'E' -> '┌'
|
||||
@@ -316,6 +317,24 @@ public class ScreenBuffer {
|
||||
case 0xC7: return '\u252C'; // T-Junction Top 'G' -> '┬'
|
||||
case 0xD7: return '\u2534'; // T-Junction Bottom 'P' -> '┴'
|
||||
case 0xCB: return '\u253C'; // Cross -> '┼'
|
||||
|
||||
// Special math and APL symbols (matching x3270 cg.c / apl.c)
|
||||
case 0x8C: return '\u2264'; // Less-than or equal '≤'
|
||||
case 0xAE: return '\u2265'; // Greater-than or equal '≥'
|
||||
case 0xBE: return '\u2260'; // Not equal '≠'
|
||||
case 0xAD: return '['; // Left bracket
|
||||
case 0xBD: return ']'; // Right bracket
|
||||
case 0x8D: return '{'; // Left brace
|
||||
case 0x9D: return '}'; // Right brace
|
||||
case 0xB0: return '\u00B0'; // Degree '°'
|
||||
case 0xB1: return '\u00B1'; // Plus-minus '±'
|
||||
case 0xB2: return '\u00B2'; // Superscript 2 '²'
|
||||
case 0xB3: return '\u00B3'; // Superscript 3 '³'
|
||||
case 0xAF: return '\u00AF'; // Overbar '¯'
|
||||
case 0xBA: return '\u03A9'; // Omega 'Ω'
|
||||
case 0xBF: return '\u00B5'; // Micro 'µ'
|
||||
case 0x5F: return '\u00AC'; // Not sign '¬'
|
||||
|
||||
default: return translator.ebcdicToUnicode(ec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,13 +37,26 @@ public class TelnetConnection {
|
||||
* Connect to the host. Blocks until connection is established or fails.
|
||||
*/
|
||||
public void connect() throws IOException {
|
||||
log.info("Connecting to " + config.getHost() + ":" + config.getPort());
|
||||
socket = new Socket();
|
||||
socket.setKeepAlive(true);
|
||||
socket.setOOBInline(true);
|
||||
socket.setTcpNoDelay(true);
|
||||
socket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
||||
config.getConnectTimeoutMs());
|
||||
if (config.isUseTls()) {
|
||||
log.info("Connecting with TLS to " + config.getHost() + ":" + config.getPort());
|
||||
javax.net.ssl.SSLSocketFactory ssf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
||||
javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssf.createSocket();
|
||||
sslSocket.setKeepAlive(true);
|
||||
sslSocket.setOOBInline(true);
|
||||
sslSocket.setTcpNoDelay(true);
|
||||
sslSocket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
||||
config.getConnectTimeoutMs());
|
||||
sslSocket.startHandshake();
|
||||
socket = sslSocket;
|
||||
} else {
|
||||
log.info("Connecting to " + config.getHost() + ":" + config.getPort());
|
||||
socket = new Socket();
|
||||
socket.setKeepAlive(true);
|
||||
socket.setOOBInline(true);
|
||||
socket.setTcpNoDelay(true);
|
||||
socket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
||||
config.getConnectTimeoutMs());
|
||||
}
|
||||
|
||||
inputStream = new BufferedInputStream(socket.getInputStream(), READ_BUFFER_SIZE);
|
||||
outputStream = new BufferedOutputStream(socket.getOutputStream());
|
||||
|
||||
Reference in New Issue
Block a user