Fix ADMDRAW targetting
This commit is contained in:
@@ -221,8 +221,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
int ox = getRenderOffsetX();
|
int ox = getRenderOffsetX();
|
||||||
int oy = getRenderOffsetY();
|
int oy = getRenderOffsetY();
|
||||||
ScreenBuffer sb = client.getScreenBuffer();
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
int gridW = sb.getDisplayCols() * cellWidth;
|
int gridW = (sb != null ? sb.getDisplayCols() : 80) * cellWidth;
|
||||||
int gridH = sb.getDisplayRows() * cellHeight;
|
int gridH = (sb != null ? sb.getDisplayRows() : 24) * cellHeight;
|
||||||
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
||||||
int gHeight = client.getGraphicsPlane().getCanvasHeight();
|
int gHeight = client.getGraphicsPlane().getCanvasHeight();
|
||||||
int px = (gridW > 0 && gWidth > 0) ? (int) Math.round((double) (e.getX() - ox) * gWidth / gridW) : (e.getX() - ox);
|
int px = (gridW > 0 && gWidth > 0) ? (int) Math.round((double) (e.getX() - ox) * gWidth / gridW) : (e.getX() - ox);
|
||||||
@@ -253,6 +253,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
|
|
||||||
if (isGraphic) {
|
if (isGraphic) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
sb.setCursorAddress(clickAddr);
|
||||||
int gridW = displayCols * cellWidth;
|
int gridW = displayCols * cellWidth;
|
||||||
int gridH = displayRows * cellHeight;
|
int gridH = displayRows * cellHeight;
|
||||||
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
||||||
@@ -995,11 +996,17 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
|
|
||||||
double curX = x;
|
double curX = x;
|
||||||
double curY = y;
|
double curY = y;
|
||||||
|
if (dir == GocaConstants.CD_TB) {
|
||||||
|
curY += ch;
|
||||||
|
} else if (dir == GocaConstants.CD_RL) {
|
||||||
|
curX -= cw;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
String s = text.substring(i, i + 1);
|
String s = text.substring(i, i + 1);
|
||||||
int charW = fm.stringWidth(s);
|
int charW = fm.stringWidth(s);
|
||||||
int drawX = (int) Math.round(curX + Math.max(0, (cw - charW) / 2.0));
|
int drawX = (int) Math.round(curX + Math.max(0, (cw - charW) / 2.0));
|
||||||
int drawY = (int) Math.round(curY + ascent + Math.max(0, (ch - fm.getHeight()) / 2.0));
|
int drawY = (int) Math.round(curY);
|
||||||
g2.drawString(s, drawX, drawY);
|
g2.drawString(s, drawX, drawY);
|
||||||
|
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ public class EbcdicTranslator {
|
|||||||
return activeCodePage.isDBCS();
|
return activeCodePage.isDBCS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS() {
|
||||||
|
return isDBCSCodePage();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate double-byte EBCDIC pair (b1, b2) to Unicode.
|
* Translate double-byte EBCDIC pair (b1, b2) to Unicode.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1020,7 +1020,7 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SF_RP_QUERY:
|
case SF_RP_QUERY:
|
||||||
log.info("ReadPartition Query — sending all query replies");
|
log.info("ReadPartition Query — sending base query replies");
|
||||||
graphicsPlane.clear();
|
graphicsPlane.clear();
|
||||||
gocaDecoder.resetDefaults();
|
gocaDecoder.resetDefaults();
|
||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
@@ -1029,7 +1029,9 @@ public class DataStreamProcessor {
|
|||||||
if (fieldLen >= 6) {
|
if (fieldLen >= 6) {
|
||||||
int listType = data[offset + 5] & 0xFF;
|
int listType = data[offset + 5] & 0xFF;
|
||||||
log.info("ReadPartition QueryList type=" + String.format("0x%02x", listType));
|
log.info("ReadPartition QueryList type=" + String.format("0x%02x", listType));
|
||||||
if (listType == SF_RPQ_ALL || listType == SF_RPQ_EQUIV) {
|
if (listType == SF_RPQ_ALL) {
|
||||||
|
sendCompleteQueryReplies();
|
||||||
|
} else if (listType == SF_RPQ_EQUIV) {
|
||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
} else if (listType == SF_RPQ_LIST) {
|
} else if (listType == SF_RPQ_LIST) {
|
||||||
// Send only requested query replies
|
// Send only requested query replies
|
||||||
@@ -1071,7 +1073,22 @@ public class DataStreamProcessor {
|
|||||||
if ((i + 1) % 32 == 0)
|
if ((i + 1) % 32 == 0)
|
||||||
sb.append("\n ");
|
sb.append("\n ");
|
||||||
}
|
}
|
||||||
log.warning(">>> SENDING Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
log.warning(">>> SENDING Base Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||||
|
if (outputSender != null) {
|
||||||
|
outputSender.send3270Data(qr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCompleteQueryReplies() {
|
||||||
|
byte[] qr = qrBuilder.buildCompleteQueryReplies(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.warning(">>> SENDING Complete Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||||
if (outputSender != null) {
|
if (outputSender != null) {
|
||||||
outputSender.send3270Data(qr);
|
outputSender.send3270Data(qr);
|
||||||
}
|
}
|
||||||
|
|||||||
+191
-105
@@ -15,16 +15,17 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
private static final Logger log = Logger.getLogger(QueryReplyBuilder.class.getName());
|
private static final Logger log = Logger.getLogger(QueryReplyBuilder.class.getName());
|
||||||
|
|
||||||
// Canned values from 3279-2 (matching sf.c)
|
|
||||||
private static final int SW_3279_2 = 0x09;
|
private static final int SW_3279_2 = 0x09;
|
||||||
private static final int SH_3279_2 = 0x0c;
|
private static final int SH_3279_2 = 0x0c;
|
||||||
private static final int Xr_3279_2 = 0x000a02e5;
|
|
||||||
private static final int Yr_3279_2 = 0x0002006f;
|
// Usable Area physical dimensions matching IBM Host On-Demand QR_USEAREA_STRING (Inches)
|
||||||
|
private static final int Xr_HOD = 0x00020089;
|
||||||
|
private static final int Yr_HOD = 0x00020085;
|
||||||
|
|
||||||
private final ScreenBuffer screen;
|
private final ScreenBuffer screen;
|
||||||
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
|
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
|
||||||
|
|
||||||
// Base query reply codes (text mode)
|
// Base query reply codes (text mode, matches HOD DS3270.java queryEquiv)
|
||||||
private static final int[] SUPPORTED_QR_BASE = {
|
private static final int[] SUPPORTED_QR_BASE = {
|
||||||
QR_SUMMARY, // 0x80 — summary must list itself
|
QR_SUMMARY, // 0x80 — summary must list itself
|
||||||
QR_USABLE_AREA, // 0x81
|
QR_USABLE_AREA, // 0x81
|
||||||
@@ -34,10 +35,11 @@ public class QueryReplyBuilder {
|
|||||||
QR_HIGHLIGHTING, // 0x87
|
QR_HIGHLIGHTING, // 0x87
|
||||||
QR_REPLY_MODES, // 0x88
|
QR_REPLY_MODES, // 0x88
|
||||||
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
||||||
QR_IMP_PART, // 0xa6
|
QR_AUXDA, // 0x99 - Auxiliary Devices
|
||||||
|
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vector graphics query reply codes matching HOD DS3270.java line 1723
|
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
||||||
private static final int[] SUPPORTED_QR_VECTOR = {
|
private static final int[] SUPPORTED_QR_VECTOR = {
|
||||||
QR_SUMMARY, // 0x80
|
QR_SUMMARY, // 0x80
|
||||||
QR_USABLE_AREA, // 0x81
|
QR_USABLE_AREA, // 0x81
|
||||||
@@ -46,17 +48,17 @@ public class QueryReplyBuilder {
|
|||||||
QR_COLOR, // 0x86
|
QR_COLOR, // 0x86
|
||||||
QR_HIGHLIGHTING, // 0x87
|
QR_HIGHLIGHTING, // 0x87
|
||||||
QR_REPLY_MODES, // 0x88
|
QR_REPLY_MODES, // 0x88
|
||||||
QR_SAVE_RESTORE, // 0x8c
|
QR_OUTLINING, // 0x8c
|
||||||
QR_DDM, // 0x95
|
QR_DDM, // 0x95
|
||||||
QR_TRANSPARENCY, // 0x99
|
QR_AUXDA, // 0x99
|
||||||
QR_IMP_PART, // 0xa6
|
QR_IMP_PART, // 0xa6
|
||||||
QR_RPQ_NAMES, // 0xa8
|
QR_TRANSPARENCY, // 0xa8
|
||||||
QR_GRAPHICS, // 0xb0
|
QR_SEGMENT, // 0xb0
|
||||||
QR_GIMAGE, // 0xb1
|
QR_PROCEDURE, // 0xb1
|
||||||
QR_AUX_DEV, // 0xb2
|
QR_LINETYPE, // 0xb2
|
||||||
QR_OEM_FMT, // 0xb3
|
QR_PORT, // 0xb3
|
||||||
QR_GCOLOR, // 0xb4
|
QR_GRCOLOR, // 0xb4
|
||||||
QR_GSYMBOLS, // 0xb6
|
QR_GRSYMBOLSET, // 0xb6
|
||||||
};
|
};
|
||||||
|
|
||||||
public QueryReplyBuilder(ScreenBuffer screen) {
|
public QueryReplyBuilder(ScreenBuffer screen) {
|
||||||
@@ -77,7 +79,8 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build all query replies as a single AID_SF + structured field response.
|
* Build base query replies in response to a generic Read Partition Query (0x02).
|
||||||
|
* Returns base text/presentation summary structured fields (matches HOD DS3270.java line 1723).
|
||||||
*/
|
*/
|
||||||
public byte[] buildAllQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
public byte[] buildAllQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||||
@@ -85,62 +88,90 @@ public class QueryReplyBuilder {
|
|||||||
// AID byte for structured field
|
// AID byte for structured field
|
||||||
out.write(AID_SF);
|
out.write(AID_SF);
|
||||||
|
|
||||||
// Summary
|
// Summary (0x80) - lists all supported capabilities
|
||||||
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||||
|
|
||||||
// Usable Area
|
// Usable Area (0x81)
|
||||||
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||||
|
|
||||||
// Alpha Partitions
|
// Alpha Partitions (0x84)
|
||||||
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||||
|
|
||||||
// Character Sets
|
// Character Sets (0x85)
|
||||||
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||||
|
|
||||||
// Color
|
// Color (0x86)
|
||||||
appendQueryReply(out, QR_COLOR, buildColor());
|
appendQueryReply(out, QR_COLOR, buildColor());
|
||||||
|
|
||||||
// Highlighting
|
// Highlighting (0x87)
|
||||||
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
|
||||||
// Reply Modes (0x88)
|
// Reply Modes (0x88)
|
||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
// Save/Restore (0x8C)
|
if (isDbcs) {
|
||||||
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
// Outlining (0x8C)
|
||||||
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
|
// DBCS Asia (0x91)
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distributed Data Management (0x95)
|
// Distributed Data Management (0x95)
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
// Auxiliary Devices (0x99)
|
||||||
// Transparency (0x99)
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implicit Partition (0xA6)
|
// Implicit Partition (0xA6)
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
// Vector Graphics QRs if enabled
|
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build complete query replies including vector graphics (when SF_RPQ_ALL 0x80 is requested).
|
||||||
|
*/
|
||||||
|
public byte[] buildCompleteQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||||
|
out.write(AID_SF);
|
||||||
|
|
||||||
|
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||||
|
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||||
|
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||||
|
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||||
|
appendQueryReply(out, QR_COLOR, buildColor());
|
||||||
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
|
if (isDbcs) {
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
|
}
|
||||||
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); // 0xA8
|
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency()); // 0xA8
|
||||||
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows)); // 0xB0
|
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows)); // 0xB0
|
||||||
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows)); // 0xB1
|
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows)); // 0xB1
|
||||||
appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); // 0xB2
|
appendQueryReply(out, QR_LINETYPE, buildLineType()); // 0xB2
|
||||||
appendOemFmt(out); // 0xB3
|
appendPort(out); // 0xB3
|
||||||
appendQueryReply(out, QR_GCOLOR, buildGColor()); // 0xB4
|
appendQueryReply(out, QR_GRCOLOR, buildGrColor()); // 0xB4
|
||||||
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); // 0xB6
|
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet()); // 0xB6
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")");
|
log.info("Built " + out.size() + " bytes of complete query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
|
* 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
|
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
|
||||||
* matching x3270 sf.c behavior.
|
* matching HOD DS3270.java line 1835.
|
||||||
*/
|
*/
|
||||||
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
||||||
if (requestedCodes == null || requestedCodes.length == 0) {
|
if (requestedCodes == null || requestedCodes.length == 0) {
|
||||||
@@ -174,72 +205,70 @@ public class QueryReplyBuilder {
|
|||||||
case QR_REPLY_MODES:
|
case QR_REPLY_MODES:
|
||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
break;
|
break;
|
||||||
case QR_SAVE_RESTORE:
|
case QR_OUTLINING: // 0x8C
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
break;
|
||||||
|
case QR_DBCS_ASIA: // 0x91
|
||||||
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_DDM:
|
case QR_DDM: // 0x95
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
break;
|
break;
|
||||||
case QR_TRANSPARENCY:
|
case QR_AUXDA: // 0x99
|
||||||
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
|
break;
|
||||||
|
case QR_IMP_PART: // 0xA6
|
||||||
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
break;
|
||||||
|
case QR_TRANSPARENCY: // 0xA8
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_IMP_PART:
|
case QR_SEGMENT: // 0xB0
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
|
||||||
break;
|
|
||||||
case QR_RPQ_NAMES:
|
|
||||||
case QR_RPQNAMES:
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, code, buildRpqNames());
|
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows));
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GRAPHICS:
|
case QR_PROCEDURE: // 0xB1
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows));
|
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows));
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GIMAGE:
|
case QR_LINETYPE: // 0xB2
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows));
|
appendQueryReply(out, QR_LINETYPE, buildLineType());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_AUX_DEV:
|
case QR_PORT: // 0xB3
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_AUX_DEV, buildAuxDev());
|
appendPort(out);
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_OEM_FMT:
|
case QR_GRCOLOR: // 0xB4
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendOemFmt(out);
|
appendQueryReply(out, QR_GRCOLOR, buildGrColor());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GCOLOR:
|
case QR_GRSYMBOLSET: // 0xB6
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GCOLOR, buildGColor());
|
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet());
|
||||||
} else {
|
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case QR_GSYMBOLS:
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
|
||||||
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols());
|
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
@@ -268,8 +297,12 @@ public class QueryReplyBuilder {
|
|||||||
private byte[] buildSummary() {
|
private byte[] buildSummary() {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
||||||
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
for (int code : codes) {
|
for (int code : codes) {
|
||||||
out.write(code);
|
out.write(code);
|
||||||
|
if (isDbcs && code == QR_OUTLINING) {
|
||||||
|
out.write(QR_DBCS_ASIA); // 0x91
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
@@ -282,17 +315,17 @@ public class QueryReplyBuilder {
|
|||||||
out.write(maxCols & 0xFF); // usable width low
|
out.write(maxCols & 0xFF); // usable width low
|
||||||
out.write((maxRows >> 8) & 0xFF); // usable height high
|
out.write((maxRows >> 8) & 0xFF); // usable height high
|
||||||
out.write(maxRows & 0xFF); // usable height low
|
out.write(maxRows & 0xFF); // usable height low
|
||||||
out.write(0x01); // units (mm)
|
out.write(0x00); // units (0x00 = inches, matching IBM Host On-Demand QR_USEAREA_STRING)
|
||||||
// Xr (4 bytes) - canned from 3279-2
|
// Xr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING
|
||||||
out.write((Xr_3279_2 >> 24) & 0xFF);
|
out.write((Xr_HOD >> 24) & 0xFF);
|
||||||
out.write((Xr_3279_2 >> 16) & 0xFF);
|
out.write((Xr_HOD >> 16) & 0xFF);
|
||||||
out.write((Xr_3279_2 >> 8) & 0xFF);
|
out.write((Xr_HOD >> 8) & 0xFF);
|
||||||
out.write(Xr_3279_2 & 0xFF);
|
out.write(Xr_HOD & 0xFF);
|
||||||
// Yr (4 bytes) - canned from 3279-2
|
// Yr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING
|
||||||
out.write((Yr_3279_2 >> 24) & 0xFF);
|
out.write((Yr_HOD >> 24) & 0xFF);
|
||||||
out.write((Yr_3279_2 >> 16) & 0xFF);
|
out.write((Yr_HOD >> 16) & 0xFF);
|
||||||
out.write((Yr_3279_2 >> 8) & 0xFF);
|
out.write((Yr_HOD >> 8) & 0xFF);
|
||||||
out.write(Yr_3279_2 & 0xFF);
|
out.write(Yr_HOD & 0xFF);
|
||||||
int charW = getCharWidth();
|
int charW = getCharWidth();
|
||||||
int charH = getCharHeight();
|
int charH = getCharHeight();
|
||||||
out.write(charW); // AW
|
out.write(charW); // AW
|
||||||
@@ -304,6 +337,9 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCharWidth() {
|
public int getCharWidth() {
|
||||||
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
return SW_3279_2; // 9
|
return SW_3279_2; // 9
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +494,28 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGraphics(int maxCols, int maxRows) {
|
public byte[] buildOutlining() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_OUTLINING_STRING ("\u0000\n\u0081\u008c\u0000\u0000\u0000\u0000\u0000\u0000")
|
||||||
|
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildDbcsAsia() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_DBCS_ASIA_STRING ("\u0000\u000b\u0081\u0091\u0000\u0003\u0001\u0080\u0003\u0002\u0001")
|
||||||
|
return new byte[]{ 0x00, 0x03, 0x01, (byte) 0x80, 0x03, 0x02, 0x01 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildAuxDa() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_AUXDA_STRING ("\u0000\u0006\u0081\u0099\u0000\u0000")
|
||||||
|
return new byte[]{ 0x00, 0x00 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildTransparency() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_TRANSPARENCY_STRING ("\u0000\t\u0081\u00a8\u0002\u0000\u00f0\u00ff\u00ff")
|
||||||
|
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildSegment(int maxCols, int maxRows) {
|
||||||
|
// HOD QueryReply3270Constants.java QR_SEGMENT_STRING ("\u0000\u000b\u0081\u00b0\u0080\u0002\u0000\u0000\u0000\u00fc\u0000")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
(byte) 0x80, 0x02,
|
(byte) 0x80, 0x02,
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
@@ -467,7 +524,12 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGImage(int maxCols, int maxRows) {
|
public byte[] buildGraphics(int maxCols, int maxRows) {
|
||||||
|
return buildSegment(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildProcedure(int maxCols, int maxRows) {
|
||||||
|
// HOD QueryReply3270Constants.java QR_PROCEDURE_STRING ("\u0000\u0015\u0081\u00b1\u0000\u0001\u0000\u0000\u0000\u00fc\u0000\u0006@\u0006@\u0006\u0001\u00ff\u00ff\u00ff\u00f0")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x01,
|
0x00, 0x01,
|
||||||
0x00, 0x00,
|
0x00, 0x00,
|
||||||
@@ -478,11 +540,12 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] buildAuxDevice() {
|
public byte[] buildGImage(int maxCols, int maxRows) {
|
||||||
return buildAuxDev();
|
return buildProcedure(maxCols, maxRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildAuxDev() {
|
public byte[] buildLineType() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_LINETYPE_STRING ("\u0000\u0018\u0081\u00b2\u0000\t\u0000\u0007\u0001\u0001\u0002\u0002\u0003\u0003\u0004\u0004\u0005\u0005\u0006\u0006\u0007\u0007\b\b")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x09, 0x00, 0x07,
|
0x00, 0x09, 0x00, 0x07,
|
||||||
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
|
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
|
||||||
@@ -490,38 +553,36 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildSaveRestore() {
|
public byte[] buildAuxDev() {
|
||||||
// HOD DS3270.java line 1768: 6 bytes payload
|
return buildLineType();
|
||||||
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildTransparency() {
|
public byte[] buildAuxDevice() {
|
||||||
// HOD DS3270.java line 1782: 2 bytes payload
|
return buildLineType();
|
||||||
return new byte[]{ 0x00, 0x00 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildRpqNames() {
|
public void appendPort(ByteArrayOutputStream out) {
|
||||||
// HOD DS3270.java line 1798: 5 bytes payload
|
// HOD QueryReply3270Constants.java QR_PORT_STRING (4 OEM format sub-fields, 64 bytes total)
|
||||||
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
}
|
|
||||||
|
|
||||||
private void appendOemFmt(ByteArrayOutputStream out) {
|
|
||||||
// HOD DS3270.java line 1814: 4 distinct OEM format sub-fields
|
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
|
||||||
0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02
|
0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C
|
0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGColor() {
|
public void appendOemFmt(ByteArrayOutputStream out) {
|
||||||
|
appendPort(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGrColor() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_GRCOLOR_STRING (109 bytes total)
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(110);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(110);
|
||||||
out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF);
|
out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF);
|
||||||
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10);
|
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10);
|
||||||
@@ -541,11 +602,36 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGSymbols() {
|
public byte[] buildGColor() {
|
||||||
|
return buildGrColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGrSymbolSet() {
|
||||||
|
int charW = getCharWidth();
|
||||||
|
int charH = getCharHeight();
|
||||||
|
int cgcsgid = 0x02B9;
|
||||||
|
int cpgid = 0x0025;
|
||||||
|
if (screen != null && screen.getTranslator() != null) {
|
||||||
|
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||||
|
cpgid = screen.getTranslator().getCpgid();
|
||||||
|
}
|
||||||
|
// HOD QueryReply3270Constants.java QR_GRSYMBOLSET_S_STRING ("\u0000!\u0081\u00b6...")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
|
0x01, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
|
||||||
0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] buildGSymbols() {
|
||||||
|
return buildGrSymbolSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildSaveRestore() {
|
||||||
|
return buildOutlining();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildRpqNames() {
|
||||||
|
return buildTransparency();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ public class GocaDecoder {
|
|||||||
return graphicCursorY;
|
return graphicCursorY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final int GDDM_CURSOR_OFFSET_Y = 0;
|
||||||
|
|
||||||
public synchronized void setGraphicCursorPosition(int x, int y) {
|
public synchronized void setGraphicCursorPosition(int x, int y) {
|
||||||
this.graphicCursorX = x;
|
this.graphicCursorX = x;
|
||||||
this.graphicCursorY = y;
|
this.graphicCursorY = y;
|
||||||
@@ -925,11 +927,7 @@ public class GocaDecoder {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GocaConstants.P_SETCUR: { // 0x31: Set Graphic Cursor Position
|
case GocaConstants.P_SETCUR: { // 0x31: Set Graphic Cursor Position (HODGraphicCursorPosition - No-op per HOD architecture)
|
||||||
if (idx + 5 <= end) {
|
|
||||||
this.graphicCursorX = readCoord(data, idx + 2);
|
|
||||||
this.graphicCursorY = readCoord(data, idx + 4);
|
|
||||||
}
|
|
||||||
if (idx + 1 < end) {
|
if (idx + 1 < end) {
|
||||||
int len = data[idx + 1] & 0xFF;
|
int len = data[idx + 1] & 0xFF;
|
||||||
idx += 2 + len;
|
idx += 2 + len;
|
||||||
@@ -1372,24 +1370,41 @@ public class GocaDecoder {
|
|||||||
double cw = charWidth > 0 ? ((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
|
double cw = charWidth > 0 ? ((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
|
||||||
double ch = charHeight > 0 ? ((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 14.0;
|
double ch = charHeight > 0 ? ((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 14.0;
|
||||||
|
|
||||||
int totalW = textLen * (charWidth > 0 ? charWidth : 9);
|
int cellW = (charWidth > 0 ? charWidth : 9);
|
||||||
int totalH = (charHeight > 0 ? charHeight : 14);
|
int cellH = (charHeight > 0 ? charHeight : 16);
|
||||||
trackPoint(startX, startY);
|
switch (charDir) {
|
||||||
trackPoint(startX + totalW, startY + totalH);
|
case GocaConstants.CD_TB:
|
||||||
trackPoint(startX + totalW, startY - totalH);
|
trackPoint(startX, startY);
|
||||||
|
trackPoint(startX + cellW, startY - textLen * cellH);
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_RL:
|
||||||
|
trackPoint(startX, startY);
|
||||||
|
trackPoint(startX - textLen * cellW, startY + cellH);
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_BT:
|
||||||
|
trackPoint(startX, startY);
|
||||||
|
trackPoint(startX + cellW, startY + textLen * cellH);
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_LR:
|
||||||
|
case GocaConstants.CD_DEFAULT:
|
||||||
|
default:
|
||||||
|
trackPoint(startX, startY);
|
||||||
|
trackPoint(startX + textLen * cellW, startY + cellH);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (charSet != 0 && programSymbolManager != null) {
|
if (charSet != 0 && programSymbolManager != null) {
|
||||||
for (int i = 0; i < textLen; i++) {
|
for (int i = 0; i < textLen; i++) {
|
||||||
int code = data[pos + i] & 0xFF;
|
int code = data[pos + i] & 0xFF;
|
||||||
double px = plane.mapXDouble(startX);
|
double px = plane.mapXDouble(startX);
|
||||||
double py = plane.mapYDouble(startY) - ch;
|
double py = plane.mapYDouble(startY);
|
||||||
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
|
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
|
||||||
if (slot != null) {
|
if (slot != null) {
|
||||||
int[] rgb = slot.getRgbPixels(curColor, 0);
|
int[] rgb = slot.getRgbPixels(curColor, 0);
|
||||||
int symW = slot.getWidth();
|
int symW = slot.getWidth();
|
||||||
int symH = slot.getHeight();
|
int symH = slot.getHeight();
|
||||||
int ipx = (int) Math.round(px);
|
int ipx = (int) Math.round(px);
|
||||||
int ipy = (int) Math.round(py);
|
int ipy = (int) Math.round(py - ch);
|
||||||
int icw = (int) Math.round(cw);
|
int icw = (int) Math.round(cw);
|
||||||
int ich = (int) Math.round(ch);
|
int ich = (int) Math.round(ch);
|
||||||
for (int dy = 0; dy < ich; dy++) {
|
for (int dy = 0; dy < ich; dy++) {
|
||||||
@@ -1420,15 +1435,33 @@ public class GocaDecoder {
|
|||||||
String text = new String(chars);
|
String text = new String(chars);
|
||||||
|
|
||||||
if (charPrecision == GocaConstants.CP_STROKE) {
|
if (charPrecision == GocaConstants.CP_STROKE) {
|
||||||
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
|
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
|
||||||
curColor, cw, ch, charDir, charAngle);
|
curColor, cw, ch, charDir, charAngle);
|
||||||
} else {
|
} else {
|
||||||
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
|
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
|
||||||
curColor, cw, ch, charDir, charAngle);
|
curColor, cw, ch, charDir, charAngle);
|
||||||
}
|
}
|
||||||
|
|
||||||
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
|
switch (charDir) {
|
||||||
curY = startY;
|
case GocaConstants.CD_TB:
|
||||||
|
curX = startX;
|
||||||
|
curY = startY - (textLen * (charHeight > 0 ? charHeight : 16));
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_RL:
|
||||||
|
curX = startX - (textLen * (charWidth > 0 ? charWidth : 9));
|
||||||
|
curY = startY;
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_BT:
|
||||||
|
curX = startX;
|
||||||
|
curY = startY + (textLen * (charHeight > 0 ? charHeight : 16));
|
||||||
|
break;
|
||||||
|
case GocaConstants.CD_LR:
|
||||||
|
case GocaConstants.CD_DEFAULT:
|
||||||
|
default:
|
||||||
|
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
|
||||||
|
curY = startY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -885,10 +885,15 @@ public class GraphicsPlane {
|
|||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||||
|
|
||||||
double curX = x;
|
|
||||||
double curY = y;
|
|
||||||
double cw = cellWidth > 0 ? cellWidth : 12.0;
|
double cw = cellWidth > 0 ? cellWidth : 12.0;
|
||||||
double ch = cellHeight > 0 ? cellHeight : 20.0;
|
double ch = cellHeight > 0 ? cellHeight : 20.0;
|
||||||
|
double curX = x;
|
||||||
|
double curY = y;
|
||||||
|
if (dir == GocaConstants.CD_TB) {
|
||||||
|
curY += ch;
|
||||||
|
} else if (dir == GocaConstants.CD_RL) {
|
||||||
|
curX -= cw;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
char c = text.charAt(i);
|
char c = text.charAt(i);
|
||||||
@@ -942,7 +947,7 @@ public class GraphicsPlane {
|
|||||||
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
|
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
|
||||||
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
||||||
px[p] = x + ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
px[p] = x + ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
||||||
py[p] = y + ((double) (VectorSymbolData.VSS_HEIGHT - vy) / VectorSymbolData.VSS_HEIGHT) * ch;
|
py[p] = y - ((double) vy / VectorSymbolData.VSS_HEIGHT) * ch;
|
||||||
ipx[p] = (int) Math.round(px[p]);
|
ipx[p] = (int) Math.round(px[p]);
|
||||||
ipy[p] = (int) Math.round(py[p]);
|
ipy[p] = (int) Math.round(py[p]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,10 @@ public class InputProcessor {
|
|||||||
*/
|
*/
|
||||||
public void sendAid(int aidCode) {
|
public void sendAid(int aidCode) {
|
||||||
System.err.println("sendAid called: 0x" + Integer.toHexString(aidCode) + " locked=" + keyboardLocked);
|
System.err.println("sendAid called: 0x" + Integer.toHexString(aidCode) + " locked=" + keyboardLocked);
|
||||||
|
if (aidCode == AID_SYSREQ) {
|
||||||
|
sysReq();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (keyboardLocked && aidCode != AID_CLEAR) {
|
if (keyboardLocked && aidCode != AID_CLEAR) {
|
||||||
System.err.println("Keyboard locked, dropping AID");
|
System.err.println("Keyboard locked, dropping AID");
|
||||||
return;
|
return;
|
||||||
@@ -358,21 +362,12 @@ public class InputProcessor {
|
|||||||
if (gocaDecoder != null && gocaDecoder.isGraphicsCursorActive()) {
|
if (gocaDecoder != null && gocaDecoder.isGraphicsCursorActive()) {
|
||||||
int gx = gocaDecoder.getGraphicCursorX();
|
int gx = gocaDecoder.getGraphicCursorX();
|
||||||
int gy = gocaDecoder.getGraphicCursorY();
|
int gy = gocaDecoder.getGraphicCursorY();
|
||||||
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
|
|
||||||
int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80;
|
int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80;
|
||||||
|
int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 24;
|
||||||
|
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
|
||||||
int row = cursorAddr / cols;
|
int row = cursorAddr / cols;
|
||||||
int col = cursorAddr % cols;
|
int col = cursorAddr % cols;
|
||||||
|
|
||||||
if (gocaDecoder.getGraphicsPlane() != null) {
|
|
||||||
int px = gocaDecoder.getGraphicsPlane().mapX(gx);
|
|
||||||
int py = gocaDecoder.getGraphicsPlane().mapY(gy);
|
|
||||||
int canvasW = gocaDecoder.getGraphicsPlane().getCanvasWidth();
|
|
||||||
int canvasH = gocaDecoder.getGraphicsPlane().getCanvasHeight();
|
|
||||||
int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 43;
|
|
||||||
if (canvasH > 0) row = (py * numRows) / canvasH;
|
|
||||||
if (canvasW > 0) col = (px * cols) / canvasW;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
|
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
|
||||||
gx, gy, row, col, button, true, isShift, isCtrl
|
gx, gy, row, col, button, true, isShift, isCtrl
|
||||||
);
|
);
|
||||||
@@ -382,8 +377,8 @@ public class InputProcessor {
|
|||||||
sfHex.append(String.format("%02X ", b & 0xFF));
|
sfHex.append(String.format("%02X ", b & 0xFF));
|
||||||
}
|
}
|
||||||
log.info(String.format(
|
log.info(String.format(
|
||||||
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
|
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d cursorAddr=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
|
||||||
gx, gy, row, col, button, isShift, isCtrl, sfHex.toString().trim()
|
gx, gy, row, col, cursorAddr, button, isShift, isCtrl, sfHex.toString().trim()
|
||||||
));
|
));
|
||||||
|
|
||||||
// Structured Field AID (0x88) + 56-byte Graphic Input SF
|
// Structured Field AID (0x88) + 56-byte Graphic Input SF
|
||||||
@@ -394,7 +389,7 @@ public class InputProcessor {
|
|||||||
|
|
||||||
// Trailing AID + cursor address + modified fields matching HOD DS3270.sendMouseAid
|
// Trailing AID + cursor address + modified fields matching HOD DS3270.sendMouseAid
|
||||||
out.write(aidCode);
|
out.write(aidCode);
|
||||||
byte[] caddr = encodeAddress(screen.getCursorAddress(), screen.getRows(), screen.getCols());
|
byte[] caddr = encodeAddress(cursorAddr, numRows, cols);
|
||||||
out.write(caddr[0] & 0xFF);
|
out.write(caddr[0] & 0xFF);
|
||||||
out.write(caddr[1] & 0xFF);
|
out.write(caddr[1] & 0xFF);
|
||||||
|
|
||||||
@@ -755,7 +750,11 @@ public class InputProcessor {
|
|||||||
|
|
||||||
/** SysReq key. */
|
/** SysReq key. */
|
||||||
public void sysReq() {
|
public void sysReq() {
|
||||||
reset();
|
if (fsm != null && fsm.isTn3270eNegotiated()) {
|
||||||
|
fsm.handleSysReq();
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reset (unlock keyboard, cancel insert mode). */
|
/** Reset (unlock keyboard, cancel insert mode). */
|
||||||
|
|||||||
@@ -208,29 +208,40 @@ public final class DS3270Constants {
|
|||||||
public static final int SF_TRANSFER_DATA = 0xd0;
|
public static final int SF_TRANSFER_DATA = 0xd0;
|
||||||
|
|
||||||
// ========== Query Reply codes ==========
|
// ========== Query Reply codes ==========
|
||||||
public static final int QR_SUMMARY = 0x80;
|
public static final int QR_SUMMARY = 0x80; // Summary
|
||||||
public static final int QR_USABLE_AREA = 0x81;
|
public static final int QR_USABLE_AREA = 0x81; // Usable Area
|
||||||
public static final int QR_IMAGE = 0x82;
|
public static final int QR_IMAGE = 0x82; // Image (non-GOCA)
|
||||||
public static final int QR_TEXT_PART = 0x83;
|
public static final int QR_TEXT_PART = 0x83; // Text Partitions
|
||||||
public static final int QR_ALPHA_PART = 0x84;
|
public static final int QR_ALPHA_PART = 0x84; // Alphanumeric Partitions
|
||||||
public static final int QR_CHARSETS = 0x85;
|
public static final int QR_CHARSETS = 0x85; // Character Sets
|
||||||
public static final int QR_COLOR = 0x86;
|
public static final int QR_COLOR = 0x86; // Color Table / Alphanumeric Color
|
||||||
public static final int QR_HIGHLIGHTING = 0x87;
|
public static final int QR_HIGHLIGHTING = 0x87; // Extended Highlighting
|
||||||
public static final int QR_REPLY_MODES = 0x88;
|
public static final int QR_REPLY_MODES = 0x88; // Reply Modes
|
||||||
public static final int QR_SAVE_RESTORE = 0x8c;
|
public static final int QR_OUTLINING = 0x8c; // Field Outlining
|
||||||
public static final int QR_DBCS_ASIA = 0x91;
|
public static final int QR_SAVE_RESTORE = 0x8c; // Legacy alias for QR_OUTLINING
|
||||||
public static final int QR_DDM = 0x95;
|
public static final int QR_DBCS_ASIA = 0x91; // DBCS Asia
|
||||||
public static final int QR_TRANSPARENCY = 0x99;
|
public static final int QR_DDM = 0x95; // Distributed Data Management
|
||||||
public static final int QR_RPQNAMES = 0xa1;
|
public static final int QR_AUXDA = 0x99; // Auxiliary Devices
|
||||||
public static final int QR_IMP_PART = 0xa6;
|
public static final int QR_FILE = 0x9f; // File Transfer
|
||||||
public static final int QR_RPQ_NAMES = 0xa8;
|
public static final int QR_DEVICECHAR = 0xa0; // Device Characteristics (Printer)
|
||||||
public static final int QR_GRAPHICS = 0xb0; // 3270-PC Vector Graphics
|
public static final int QR_RPQNAMES = 0xa1; // RPQ Names (legacy)
|
||||||
public static final int QR_GIMAGE = 0xb1; // Image / Graphics Planes
|
public static final int QR_IMP_PART = 0xa6; // Implicit Partition Sizes
|
||||||
public static final int QR_AUX_DEV = 0xb2; // Auxiliary Device
|
public static final int QR_IMPLICIT = 0xa6; // Alias for QR_IMP_PART
|
||||||
public static final int QR_OEM_FMT = 0xb3; // OEM Format
|
public static final int QR_TRANSPARENCY = 0xa8; // Background Transparency
|
||||||
public static final int QR_GCOLOR = 0xb4; // Graphic Color Table
|
public static final int QR_RPQ_NAMES = 0xa8; // Legacy alias pointing to 0xa8
|
||||||
public static final int QR_GSYMBOLS = 0xb6; // Graphic Symbol Sets
|
public static final int QR_SEGMENT = 0xb0; // Segment Characteristics (3270-PC Vector Graphics)
|
||||||
public static final int QR_NULL = 0xff;
|
public static final int QR_GRAPHICS = 0xb0; // Legacy alias for QR_SEGMENT
|
||||||
|
public static final int QR_PROCEDURE = 0xb1; // Procedure Characteristics (Image/GOCA)
|
||||||
|
public static final int QR_GIMAGE = 0xb1; // Legacy alias for QR_PROCEDURE
|
||||||
|
public static final int QR_LINETYPE = 0xb2; // Line Type Support (Aux Dev)
|
||||||
|
public static final int QR_AUX_DEV = 0xb2; // Legacy alias for QR_LINETYPE
|
||||||
|
public static final int QR_PORT = 0xb3; // Port Characteristics (OEM Format)
|
||||||
|
public static final int QR_OEM_FMT = 0xb3; // Legacy alias for QR_PORT
|
||||||
|
public static final int QR_GRCOLOR = 0xb4; // Graphic Color Table
|
||||||
|
public static final int QR_GCOLOR = 0xb4; // Legacy alias for QR_GRCOLOR
|
||||||
|
public static final int QR_GRSYMBOLSET = 0xb6; // Graphic Symbol Sets
|
||||||
|
public static final int QR_GSYMBOLS = 0xb6; // Legacy alias for QR_GRSYMBOLSET
|
||||||
|
public static final int QR_NULL = 0xff; // Query Reply Null (Unsupported)
|
||||||
|
|
||||||
// ========== Screen model sizes ==========
|
// ========== Screen model sizes ==========
|
||||||
public static final int MODEL_2_ROWS = 24;
|
public static final int MODEL_2_ROWS = 24;
|
||||||
|
|||||||
@@ -229,6 +229,11 @@ public class ScreenBuffer {
|
|||||||
this.cursorAddress = addr;
|
this.cursorAddress = addr;
|
||||||
this.displayCursorAddress = addr;
|
this.displayCursorAddress = addr;
|
||||||
}
|
}
|
||||||
|
public synchronized void setCursorPosition(int row, int col) {
|
||||||
|
int r = Math.max(0, Math.min(row, rows - 1));
|
||||||
|
int c = Math.max(0, Math.min(col, cols - 1));
|
||||||
|
setCursorAddress(r * cols + c);
|
||||||
|
}
|
||||||
public int getCursorRow() { return cursorAddress / cols; }
|
public int getCursorRow() { return cursorAddress / cols; }
|
||||||
public int getCursorCol() { return cursorAddress % cols; }
|
public int getCursorCol() { return cursorAddress % cols; }
|
||||||
|
|
||||||
|
|||||||
@@ -402,11 +402,7 @@ public class TelnetFSM {
|
|||||||
} else if (!myOpts[opt]) {
|
} else if (!myOpts[opt]) {
|
||||||
myOpts[opt] = true;
|
myOpts[opt] = true;
|
||||||
sendCommand(WILL, opt);
|
sendCommand(WILL, opt);
|
||||||
// Start TN3270E sub-negotiation: send device type request
|
tn3270eDeviceTypeSent = false;
|
||||||
if (!tn3270eDeviceTypeSent) {
|
|
||||||
sendTN3270EDeviceTypeRequest();
|
|
||||||
tn3270eDeviceTypeSent = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -863,25 +859,52 @@ public class TelnetFSM {
|
|||||||
changeState(ConnectionState.CONNECTED_TN3270E);
|
changeState(ConnectionState.CONNECTED_TN3270E);
|
||||||
tn3270eSubmode = TN3270ESubmode.E_3270;
|
tn3270eSubmode = TN3270ESubmode.E_3270;
|
||||||
}
|
}
|
||||||
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, true);
|
try {
|
||||||
notifyScreenUpdate();
|
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, true);
|
||||||
}
|
notifyScreenUpdate();
|
||||||
// Send positive response if required
|
// Send positive response if required
|
||||||
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
sendTN3270EPositiveResponse(seqNumber);
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING, "Error processing 3270 record", e);
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
|
||||||
|
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DT_SSCP_LU_DATA:
|
case DT_SSCP_LU_DATA:
|
||||||
if (connectionState == ConnectionState.CONNECTED_UNBOUND) {
|
if (connectionState != ConnectionState.CONNECTED_SSCP) {
|
||||||
// Clear screen on first SSCP-LU transition to remove stale data
|
if (connectionState == ConnectionState.CONNECTED_UNBOUND) {
|
||||||
screenBuffer.clear();
|
// Clear screen on first SSCP-LU transition to remove stale data
|
||||||
|
screenBuffer.clear();
|
||||||
|
}
|
||||||
changeState(ConnectionState.CONNECTED_SSCP);
|
changeState(ConnectionState.CONNECTED_SSCP);
|
||||||
tn3270eSubmode = TN3270ESubmode.E_SSCP;
|
tn3270eSubmode = TN3270ESubmode.E_SSCP;
|
||||||
}
|
}
|
||||||
if (data.length > EH_SIZE) {
|
if (data.length > EH_SIZE) {
|
||||||
dsProcessor.processSscpLuData(data, EH_SIZE, data.length - EH_SIZE);
|
try {
|
||||||
notifyScreenUpdate();
|
dsProcessor.processSscpLuData(data, EH_SIZE, data.length - EH_SIZE);
|
||||||
|
notifyScreenUpdate();
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING, "Error processing SSCP-LU record", e);
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
|
||||||
|
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -899,7 +922,21 @@ public class TelnetFSM {
|
|||||||
changeState(ConnectionState.CONNECTED_E_NVT);
|
changeState(ConnectionState.CONNECTED_E_NVT);
|
||||||
tn3270eSubmode = TN3270ESubmode.E_NVT;
|
tn3270eSubmode = TN3270ESubmode.E_NVT;
|
||||||
if (data.length > EH_SIZE) {
|
if (data.length > EH_SIZE) {
|
||||||
processNVTData(data, EH_SIZE, data.length - EH_SIZE);
|
try {
|
||||||
|
processNVTData(data, EH_SIZE, data.length - EH_SIZE);
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING, "Error processing NVT record", e);
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
|
||||||
|
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||||
|
sendTN3270EPositiveResponse(seqNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1393,6 +1430,31 @@ public class TelnetFSM {
|
|||||||
return tn3270eBound || connectionState == ConnectionState.CONNECTED_3270;
|
return tn3270eBound || connectionState == ConnectionState.CONNECTED_3270;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTn3270eBound() {
|
||||||
|
return tn3270eBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleSysReq() {
|
||||||
|
if (tn3270eNegotiated) {
|
||||||
|
byte[] ao = new byte[] { (byte) IAC, (byte) AO };
|
||||||
|
sendBytes(ao);
|
||||||
|
screenBuffer.clear();
|
||||||
|
screenBuffer.setCursorAddress(0);
|
||||||
|
screenBuffer.markAllChanged();
|
||||||
|
if (dsProcessor != null && dsProcessor.getInputProcessor() != null) {
|
||||||
|
dsProcessor.getInputProcessor().reset();
|
||||||
|
}
|
||||||
|
if (connectionState != ConnectionState.CONNECTED_SSCP) {
|
||||||
|
changeState(ConnectionState.CONNECTED_SSCP);
|
||||||
|
tn3270eSubmode = TN3270ESubmode.E_SSCP;
|
||||||
|
} else if (tn3270eBound) {
|
||||||
|
changeState(ConnectionState.CONNECTED_TN3270E);
|
||||||
|
tn3270eSubmode = TN3270ESubmode.E_3270;
|
||||||
|
}
|
||||||
|
notifyScreenUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String tn3270eOpName(int op) {
|
private static String tn3270eOpName(int op) {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case OP_ASSOCIATE: return "ASSOCIATE";
|
case OP_ASSOCIATE: return "ASSOCIATE";
|
||||||
|
|||||||
+73
-26
@@ -22,10 +22,16 @@ public class QueryReplyBuilderTest {
|
|||||||
assertTrue(replies.length > 0);
|
assertTrue(replies.length > 0);
|
||||||
assertEquals((byte) AID_SF, replies[0]);
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
|
||||||
// In GraphicsMode.BOTH, Vector Graphics QR 0xB0 must be present
|
// In base query reply (buildAllQueryReplies), Summary (0x80) advertises QR_SEGMENT (0xB0)
|
||||||
|
// while the base reply itself contains base SFs (Usable Area, Charsets, Color, Highlighting, Reply Modes, DDM, AuxDA, ImpPart)
|
||||||
|
assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY
|
||||||
|
assertEquals(QR_SUMMARY, replies[4] & 0xFF); // 0x80
|
||||||
|
|
||||||
|
// In buildCompleteQueryReplies, Vector Graphics QR 0xB0 structured field payload is present
|
||||||
|
byte[] completeReplies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
|
||||||
boolean hasB0 = false;
|
boolean hasB0 = false;
|
||||||
for (int i = 0; i < replies.length - 3; i++) {
|
for (int i = 0; i < completeReplies.length - 3; i++) {
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
if ((completeReplies[i] & 0xFF) == 0x81 && (completeReplies[i + 1] & 0xFF) == QR_SEGMENT) {
|
||||||
hasB0 = true;
|
hasB0 = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -41,31 +47,27 @@ public class QueryReplyBuilderTest {
|
|||||||
assertTrue(replies.length > 0);
|
assertTrue(replies.length > 0);
|
||||||
assertEquals((byte) AID_SF, replies[0]);
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
|
||||||
// In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be present
|
// In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be advertised in Summary
|
||||||
boolean hasB0 = false;
|
int sumLen = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
|
||||||
for (int i = 0; i < replies.length - 3; i++) {
|
for (int i = 5; i < 1 + sumLen; i++) {
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
assertNotEquals(QR_SEGMENT, replies[i] & 0xFF);
|
||||||
hasB0 = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assertFalse(hasB0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildAllQueryRepliesWithVectorGraphics() {
|
public void testBuildAllQueryRepliesWithVectorGraphics() {
|
||||||
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
|
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
|
||||||
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
|
||||||
assertNotNull(replies);
|
assertNotNull(replies);
|
||||||
|
|
||||||
// Vector Graphics QR 0xB0 and 0xB4 must be present
|
// In complete replies, Vector Graphics QR 0xB0 and 0xB4 must be present
|
||||||
boolean hasB0 = false;
|
boolean hasB0 = false;
|
||||||
boolean hasB4 = false;
|
boolean hasB4 = false;
|
||||||
for (int i = 0; i < replies.length - 3; i++) {
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
|
||||||
hasB0 = true;
|
hasB0 = true;
|
||||||
}
|
}
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GCOLOR) {
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRCOLOR) {
|
||||||
hasB4 = true;
|
hasB4 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,17 +78,17 @@ public class QueryReplyBuilderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testBuildAllQueryRepliesWithBoth() {
|
public void testBuildAllQueryRepliesWithBoth() {
|
||||||
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
|
||||||
assertNotNull(replies);
|
assertNotNull(replies);
|
||||||
|
|
||||||
// Vector Graphics (0xB0) must be present and Charsets must have LoadPS (0x0A)
|
// Vector Graphics (0xB0) must be present and Charsets must have LoadPS (0x0A)
|
||||||
boolean hasB0 = false;
|
boolean hasB0 = false;
|
||||||
boolean hasCharsetsWithLoadPs = false;
|
boolean hasCharsetsWithLoadPs = false;
|
||||||
for (int i = 0; i < replies.length - 3; i++) {
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
|
||||||
hasB0 = true;
|
hasB0 = true;
|
||||||
}
|
}
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
|
if ((replies[i] & 0xFF) == 0x81 && (completeReplyIsCharsets(replies, i))) {
|
||||||
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
|
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
|
||||||
hasCharsetsWithLoadPs = true;
|
hasCharsetsWithLoadPs = true;
|
||||||
}
|
}
|
||||||
@@ -96,17 +98,21 @@ public class QueryReplyBuilderTest {
|
|||||||
assertTrue(hasCharsetsWithLoadPs);
|
assertTrue(hasCharsetsWithLoadPs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean completeReplyIsCharsets(byte[] replies, int i) {
|
||||||
|
return (replies[i + 1] & 0xFF) == QR_CHARSETS;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildAllQueryRepliesWithProgrammedSymbols() {
|
public void testBuildAllQueryRepliesWithProgrammedSymbols() {
|
||||||
qrBuilder.setGraphicsMode(GraphicsMode.PROGRAMMED_SYMBOLS);
|
qrBuilder.setGraphicsMode(GraphicsMode.PROGRAMMED_SYMBOLS);
|
||||||
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
|
||||||
assertNotNull(replies);
|
assertNotNull(replies);
|
||||||
|
|
||||||
// Charsets with LoadPS (0x0A) must be present, and QR_GRAPHICS must NOT be present
|
// Charsets with LoadPS (0x0A) must be present, and QR_SEGMENT must NOT be present
|
||||||
boolean hasB0 = false;
|
boolean hasB0 = false;
|
||||||
boolean hasCharsetsWithLoadPs = false;
|
boolean hasCharsetsWithLoadPs = false;
|
||||||
for (int i = 0; i < replies.length - 3; i++) {
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
|
||||||
hasB0 = true;
|
hasB0 = true;
|
||||||
}
|
}
|
||||||
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
|
||||||
@@ -240,7 +246,7 @@ public class QueryReplyBuilderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testQueryReplyImageAndRpqNames() {
|
public void testQueryReplyImageAndRpqNames() {
|
||||||
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
byte[] requested = new byte[] { (byte) QR_IMAGE, (byte) QR_RPQNAMES, (byte) QR_GIMAGE };
|
byte[] requested = new byte[] { (byte) QR_IMAGE, (byte) QR_TRANSPARENCY, (byte) QR_PROCEDURE };
|
||||||
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
|
||||||
assertNotNull(replies);
|
assertNotNull(replies);
|
||||||
@@ -251,15 +257,56 @@ public class QueryReplyBuilderTest {
|
|||||||
assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY
|
assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY
|
||||||
assertEquals(QR_NULL, replies[4] & 0xFF); // 0xFF
|
assertEquals(QR_NULL, replies[4] & 0xFF); // 0xFF
|
||||||
|
|
||||||
// Second SF should be QR_RPQNAMES (0xA1) matching requested code
|
// Second SF should be QR_TRANSPARENCY (0xA8) matching requested code
|
||||||
int pos2 = 1 + len1;
|
int pos2 = 1 + len1;
|
||||||
int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF);
|
int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF);
|
||||||
assertEquals(0x81, replies[pos2 + 2] & 0xFF);
|
assertEquals(0x81, replies[pos2 + 2] & 0xFF);
|
||||||
assertEquals(QR_RPQNAMES, replies[pos2 + 3] & 0xFF); // 0xA1
|
assertEquals(QR_TRANSPARENCY, replies[pos2 + 3] & 0xFF); // 0xA8
|
||||||
|
|
||||||
// Third SF should be QR_GIMAGE (0xB1)
|
// Third SF should be QR_PROCEDURE (0xB1)
|
||||||
int pos3 = pos2 + len2;
|
int pos3 = pos2 + len2;
|
||||||
assertEquals(0x81, replies[pos3 + 2] & 0xFF);
|
assertEquals(0x81, replies[pos3 + 2] & 0xFF);
|
||||||
assertEquals(QR_GIMAGE, replies[pos3 + 3] & 0xFF); // 0xB1
|
assertEquals(QR_PROCEDURE, replies[pos3 + 3] & 0xFF); // 0xB1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUsableAreaMetricsMatchHOD() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
|
||||||
|
byte[] requested = new byte[] { (byte) QR_USABLE_AREA };
|
||||||
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
|
||||||
|
assertNotNull(replies);
|
||||||
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
|
||||||
|
// Length (2 bytes), SFID (0x81), QR_USABLE_AREA (0x81)
|
||||||
|
assertEquals(23, ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF));
|
||||||
|
assertEquals(0x81, replies[3] & 0xFF);
|
||||||
|
assertEquals(QR_USABLE_AREA, replies[4] & 0xFF);
|
||||||
|
|
||||||
|
// Flags: 12/14 bit addressing (0x01), 0x00
|
||||||
|
assertEquals(0x01, replies[5] & 0xFF);
|
||||||
|
assertEquals(0x00, replies[6] & 0xFF);
|
||||||
|
|
||||||
|
// Usable width and height: 80, 43
|
||||||
|
assertEquals(80, ((replies[7] & 0xFF) << 8) | (replies[8] & 0xFF));
|
||||||
|
assertEquals(43, ((replies[9] & 0xFF) << 8) | (replies[10] & 0xFF));
|
||||||
|
|
||||||
|
// Units: 0x00 (Inches, matching IBM Host On-Demand QR_USEAREA_STRING)
|
||||||
|
assertEquals(0x00, replies[11] & 0xFF);
|
||||||
|
|
||||||
|
// Xr (4 bytes): 0x00020089 (matching HOD)
|
||||||
|
int xr = ((replies[12] & 0xFF) << 24) | ((replies[13] & 0xFF) << 16) | ((replies[14] & 0xFF) << 8) | (replies[15] & 0xFF);
|
||||||
|
assertEquals(0x00020089, xr);
|
||||||
|
|
||||||
|
// Yr (4 bytes): 0x00020085 (matching HOD)
|
||||||
|
int yr = ((replies[16] & 0xFF) << 24) | ((replies[17] & 0xFF) << 16) | ((replies[18] & 0xFF) << 8) | (replies[19] & 0xFF);
|
||||||
|
assertEquals(0x00020085, yr);
|
||||||
|
|
||||||
|
// AW and AH: 9 and 16
|
||||||
|
assertEquals(9, replies[20] & 0xFF);
|
||||||
|
assertEquals(16, replies[21] & 0xFF);
|
||||||
|
|
||||||
|
// Buffer size: 80 * 43 = 3440
|
||||||
|
assertEquals(80 * 43, ((replies[22] & 0xFF) << 8) | (replies[23] & 0xFF));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
package haus.nightmare.lib3270j.datastream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GraphicsMode;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class QueryReplyPhase2Test {
|
||||||
|
|
||||||
|
private final EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to parse structured fields out of a query reply stream.
|
||||||
|
*/
|
||||||
|
private static class ParsedSF {
|
||||||
|
int length;
|
||||||
|
int sfid;
|
||||||
|
int qcode;
|
||||||
|
byte[] payload;
|
||||||
|
|
||||||
|
ParsedSF(int length, int sfid, int qcode, byte[] payload) {
|
||||||
|
this.length = length;
|
||||||
|
this.sfid = sfid;
|
||||||
|
this.qcode = qcode;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ParsedSF> parseQueryReplies(byte[] data) {
|
||||||
|
List<ParsedSF> list = new ArrayList<>();
|
||||||
|
assertNotNull(data);
|
||||||
|
assertTrue(data.length > 0);
|
||||||
|
assertEquals((byte) AID_SF, data[0]);
|
||||||
|
|
||||||
|
int pos = 1;
|
||||||
|
while (pos < data.length) {
|
||||||
|
int len = ((data[pos] & 0xFF) << 8) | (data[pos + 1] & 0xFF);
|
||||||
|
assertTrue(len >= 4, "Structured field length must be >= 4 at pos " + pos);
|
||||||
|
int sfid = data[pos + 2] & 0xFF;
|
||||||
|
int qcode = data[pos + 3] & 0xFF;
|
||||||
|
byte[] payload = new byte[len - 4];
|
||||||
|
System.arraycopy(data, pos + 4, payload, 0, payload.length);
|
||||||
|
list.add(new ParsedSF(len, sfid, qcode, payload));
|
||||||
|
pos += len;
|
||||||
|
}
|
||||||
|
assertEquals(data.length, pos, "All bytes in query reply must be accounted for");
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQCodeConstantsValuesMatchHod() {
|
||||||
|
assertEquals(0x80, QR_SUMMARY);
|
||||||
|
assertEquals(0x81, QR_USABLE_AREA);
|
||||||
|
assertEquals(0x84, QR_ALPHA_PART);
|
||||||
|
assertEquals(0x85, QR_CHARSETS);
|
||||||
|
assertEquals(0x86, QR_COLOR);
|
||||||
|
assertEquals(0x87, QR_HIGHLIGHTING);
|
||||||
|
assertEquals(0x88, QR_REPLY_MODES);
|
||||||
|
assertEquals(0x8C, QR_OUTLINING);
|
||||||
|
assertEquals(0x91, QR_DBCS_ASIA);
|
||||||
|
assertEquals(0x95, QR_DDM);
|
||||||
|
assertEquals(0x99, QR_AUXDA);
|
||||||
|
assertEquals(0xA6, QR_IMP_PART);
|
||||||
|
assertEquals(0xA8, QR_TRANSPARENCY);
|
||||||
|
assertEquals(0xB0, QR_SEGMENT);
|
||||||
|
assertEquals(0xB1, QR_PROCEDURE);
|
||||||
|
assertEquals(0xB2, QR_LINETYPE);
|
||||||
|
assertEquals(0xB3, QR_PORT);
|
||||||
|
assertEquals(0xB4, QR_GRCOLOR);
|
||||||
|
assertEquals(0xB6, QR_GRSYMBOLSET);
|
||||||
|
assertEquals(0xFF, QR_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBaseGenericQueryReplySizeAndStructure() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
|
byte[] reply = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
||||||
|
assertNotNull(reply);
|
||||||
|
|
||||||
|
List<ParsedSF> sfs = parseQueryReplies(reply);
|
||||||
|
// Base query reply must contain exactly 10 structured fields for SBCS:
|
||||||
|
// 0x80 (Summary), 0x81 (Usable Area), 0x84 (Alpha Partitions), 0x85 (Character Sets),
|
||||||
|
// 0x86 (Color), 0x87 (Highlighting), 0x88 (Reply Modes), 0x95 (DDM), 0x99 (AuxDA), 0xA6 (Implicit Part)
|
||||||
|
assertEquals(10, sfs.size());
|
||||||
|
|
||||||
|
assertEquals(QR_SUMMARY, sfs.get(0).qcode);
|
||||||
|
assertEquals(QR_USABLE_AREA, sfs.get(1).qcode);
|
||||||
|
assertEquals(QR_ALPHA_PART, sfs.get(2).qcode);
|
||||||
|
assertEquals(QR_CHARSETS, sfs.get(3).qcode);
|
||||||
|
assertEquals(QR_COLOR, sfs.get(4).qcode);
|
||||||
|
assertEquals(QR_HIGHLIGHTING, sfs.get(5).qcode);
|
||||||
|
assertEquals(QR_REPLY_MODES, sfs.get(6).qcode);
|
||||||
|
assertEquals(QR_DDM, sfs.get(7).qcode);
|
||||||
|
assertEquals(QR_AUXDA, sfs.get(8).qcode);
|
||||||
|
assertEquals(QR_IMP_PART, sfs.get(9).qcode);
|
||||||
|
|
||||||
|
// Verify that Vector Graphics SF payloads (0xA8, 0xB0..0xB6) are NOT sent in base reply
|
||||||
|
for (ParsedSF sf : sfs) {
|
||||||
|
assertNotEquals(QR_TRANSPARENCY, sf.qcode);
|
||||||
|
assertNotEquals(QR_SEGMENT, sf.qcode);
|
||||||
|
assertNotEquals(QR_PROCEDURE, sf.qcode);
|
||||||
|
assertNotEquals(QR_LINETYPE, sf.qcode);
|
||||||
|
assertNotEquals(QR_PORT, sf.qcode);
|
||||||
|
assertNotEquals(QR_GRCOLOR, sf.qcode);
|
||||||
|
assertNotEquals(QR_GRSYMBOLSET, sf.qcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify summary structured field advertises full capabilities
|
||||||
|
byte[] summaryPayload = sfs.get(0).payload;
|
||||||
|
assertTrue(summaryPayload.length >= 17);
|
||||||
|
assertEquals(QR_SUMMARY, summaryPayload[0] & 0xFF);
|
||||||
|
assertEquals(QR_USABLE_AREA, summaryPayload[1] & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTargetedQueryListAdmDrawResponse() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
|
// ADMDRAW vector graphics startup query list: 0x8C, 0xA8, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB6
|
||||||
|
byte[] requested = new byte[] {
|
||||||
|
(byte) QR_OUTLINING, // 0x8C
|
||||||
|
(byte) QR_TRANSPARENCY, // 0xA8
|
||||||
|
(byte) QR_SEGMENT, // 0xB0
|
||||||
|
(byte) QR_PROCEDURE, // 0xB1
|
||||||
|
(byte) QR_LINETYPE, // 0xB2
|
||||||
|
(byte) QR_PORT, // 0xB3
|
||||||
|
(byte) QR_GRCOLOR, // 0xB4
|
||||||
|
(byte) QR_GRSYMBOLSET // 0xB6
|
||||||
|
};
|
||||||
|
|
||||||
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
List<ParsedSF> sfs = parseQueryReplies(replies);
|
||||||
|
|
||||||
|
// 8 requested QCODEs produce 11 SFs because QR_PORT emits 4 OEM format sub-fields
|
||||||
|
assertEquals(11, sfs.size());
|
||||||
|
|
||||||
|
// 1. QR_OUTLINING (0x8C): 10 bytes
|
||||||
|
assertEquals(QR_OUTLINING, sfs.get(0).qcode);
|
||||||
|
assertEquals(10, sfs.get(0).length);
|
||||||
|
assertArrayEquals(new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, sfs.get(0).payload);
|
||||||
|
|
||||||
|
// 2. QR_TRANSPARENCY (0xA8): 9 bytes
|
||||||
|
assertEquals(QR_TRANSPARENCY, sfs.get(1).qcode);
|
||||||
|
assertEquals(9, sfs.get(1).length);
|
||||||
|
assertArrayEquals(new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF }, sfs.get(1).payload);
|
||||||
|
|
||||||
|
// 3. QR_SEGMENT (0xB0): 11 bytes
|
||||||
|
assertEquals(QR_SEGMENT, sfs.get(2).qcode);
|
||||||
|
assertEquals(11, sfs.get(2).length);
|
||||||
|
assertArrayEquals(new byte[]{ (byte) 0x80, 0x02, 0x00, 0x00, 0x00, (byte) 0xFC, 0x00 }, sfs.get(2).payload);
|
||||||
|
|
||||||
|
// 4. QR_PROCEDURE (0xB1): 21 bytes
|
||||||
|
assertEquals(QR_PROCEDURE, sfs.get(3).qcode);
|
||||||
|
assertEquals(21, sfs.get(3).length);
|
||||||
|
assertArrayEquals(new byte[]{
|
||||||
|
0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0xFC, 0x00, 0x06, 0x40, 0x06, 0x40, 0x06, 0x01,
|
||||||
|
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF0
|
||||||
|
}, sfs.get(3).payload);
|
||||||
|
|
||||||
|
// 5. QR_LINETYPE (0xB2): 24 bytes
|
||||||
|
assertEquals(QR_LINETYPE, sfs.get(4).qcode);
|
||||||
|
assertEquals(24, sfs.get(4).length);
|
||||||
|
assertArrayEquals(new byte[]{
|
||||||
|
0x00, 0x09, 0x00, 0x07, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
|
||||||
|
0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08
|
||||||
|
}, sfs.get(4).payload);
|
||||||
|
|
||||||
|
// 6-9. QR_PORT (0xB3): 4 subfields (17, 15, 17, 15 bytes)
|
||||||
|
assertEquals(QR_PORT, sfs.get(5).qcode);
|
||||||
|
assertEquals(17, sfs.get(5).length);
|
||||||
|
assertEquals(QR_PORT, sfs.get(6).qcode);
|
||||||
|
assertEquals(15, sfs.get(6).length);
|
||||||
|
assertEquals(QR_PORT, sfs.get(7).qcode);
|
||||||
|
assertEquals(17, sfs.get(7).length);
|
||||||
|
assertEquals(QR_PORT, sfs.get(8).qcode);
|
||||||
|
assertEquals(15, sfs.get(8).length);
|
||||||
|
|
||||||
|
// 10. QR_GRCOLOR (0xB4): 109 bytes
|
||||||
|
assertEquals(QR_GRCOLOR, sfs.get(9).qcode);
|
||||||
|
assertEquals(109, sfs.get(9).length);
|
||||||
|
|
||||||
|
// 11. QR_GRSYMBOLSET (0xB6): 33 bytes
|
||||||
|
assertEquals(QR_GRSYMBOLSET, sfs.get(10).qcode);
|
||||||
|
assertEquals(33, sfs.get(10).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnsupportedQCodesEmitQrNull() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
|
byte[] requested = new byte[] { (byte) 0x12, (byte) 0x7E, (byte) 0xEE };
|
||||||
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
|
||||||
|
List<ParsedSF> sfs = parseQueryReplies(replies);
|
||||||
|
assertEquals(3, sfs.size());
|
||||||
|
for (ParsedSF sf : sfs) {
|
||||||
|
assertEquals(QR_NULL, sf.qcode);
|
||||||
|
assertEquals(4, sf.length);
|
||||||
|
assertEquals(0, sf.payload.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDynamicCellDimensions() {
|
||||||
|
// In Vector Graphics mode, SDH must be 16 (0x10) for 3179G
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
|
||||||
|
assertEquals(9, qrBuilder.getCharWidth());
|
||||||
|
assertEquals(16, qrBuilder.getCharHeight());
|
||||||
|
|
||||||
|
// In Text-only mode (GraphicsMode.NONE), SDH must be 12 (0x0C) for 3279-2
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.NONE);
|
||||||
|
assertEquals(9, qrBuilder.getCharWidth());
|
||||||
|
assertEquals(12, qrBuilder.getCharHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataStreamProcessorQueryListDispatch() {
|
||||||
|
ByteArrayOutputStream captured = new ByteArrayOutputStream();
|
||||||
|
DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
processor.setOutputSender(data -> {
|
||||||
|
try {
|
||||||
|
captured.write(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test SF_RP_QUERY (0x02): generic query -> base query reply
|
||||||
|
byte[] genericQuery = new byte[] {
|
||||||
|
(byte) CMD_WSF,
|
||||||
|
0x00, 0x05, // Length = 5
|
||||||
|
(byte) SF_READ_PART, 0x00, // Partition = 0
|
||||||
|
(byte) SF_RP_QUERY // Type = 0x02
|
||||||
|
};
|
||||||
|
|
||||||
|
captured.reset();
|
||||||
|
processor.processRecord(genericQuery, 0, genericQuery.length, false);
|
||||||
|
byte[] sent = captured.toByteArray();
|
||||||
|
assertTrue(sent.length > 0);
|
||||||
|
assertEquals((byte) AID_SF, sent[0]);
|
||||||
|
List<ParsedSF> sfs = parseQueryReplies(sent);
|
||||||
|
assertEquals(10, sfs.size());
|
||||||
|
|
||||||
|
// Test SF_RP_QLIST (0x03) with SF_RPQ_LIST (0x00) and codes 0xB0, 0xB4
|
||||||
|
byte[] queryList = new byte[] {
|
||||||
|
(byte) CMD_WSF,
|
||||||
|
0x00, 0x08, // Length = 8
|
||||||
|
(byte) SF_READ_PART, 0x00,
|
||||||
|
(byte) SF_RP_QLIST,
|
||||||
|
(byte) SF_RPQ_LIST, // 0x00
|
||||||
|
(byte) QR_SEGMENT, // 0xB0
|
||||||
|
(byte) QR_GRCOLOR // 0xB4
|
||||||
|
};
|
||||||
|
|
||||||
|
captured.reset();
|
||||||
|
processor.processRecord(queryList, 0, queryList.length, false);
|
||||||
|
sent = captured.toByteArray();
|
||||||
|
assertTrue(sent.length > 0);
|
||||||
|
List<ParsedSF> listSfs = parseQueryReplies(sent);
|
||||||
|
assertEquals(2, listSfs.size());
|
||||||
|
assertEquals(QR_SEGMENT, listSfs.get(0).qcode);
|
||||||
|
assertEquals(QR_GRCOLOR, listSfs.get(1).qcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
+272
@@ -0,0 +1,272 @@
|
|||||||
|
package haus.nightmare.lib3270j.input;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GddmCoordinateTransform;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GocaDecoder;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GraphicsPlane;
|
||||||
|
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verification test suite for Phase 3: Interactive Selection, Cursor Addressing & ADMDRAW Alignment.
|
||||||
|
*/
|
||||||
|
public class Phase3InteractiveSelectionTest {
|
||||||
|
|
||||||
|
private ScreenBuffer screen;
|
||||||
|
private EbcdicTranslator translator;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
translator = new EbcdicTranslator();
|
||||||
|
screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScreenBufferSetCursorPosition() {
|
||||||
|
screen.setCursorPosition(5, 12);
|
||||||
|
assertEquals(5, screen.getCursorRow());
|
||||||
|
assertEquals(12, screen.getCursorCol());
|
||||||
|
assertEquals(5 * 80 + 12, screen.getCursorAddress());
|
||||||
|
|
||||||
|
// Test clamping
|
||||||
|
screen.setCursorPosition(-2, -5);
|
||||||
|
assertEquals(0, screen.getCursorRow());
|
||||||
|
assertEquals(0, screen.getCursorCol());
|
||||||
|
assertEquals(0, screen.getCursorAddress());
|
||||||
|
|
||||||
|
screen.setCursorPosition(100, 200);
|
||||||
|
assertEquals(23, screen.getCursorRow());
|
||||||
|
assertEquals(79, screen.getCursorCol());
|
||||||
|
assertEquals(23 * 80 + 79, screen.getCursorAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmediateLightPenSelectionSpaceDesignator() {
|
||||||
|
// Unprotected selectable field at pos 0 with space designator (0x40)
|
||||||
|
screen.erase(false);
|
||||||
|
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_INT_NORM_SEL));
|
||||||
|
screen.getCell(1).ec = 0x40; // ' '
|
||||||
|
screen.getCell(1).ucs4 = ' ';
|
||||||
|
screen.getCell(2).ec = (byte) 0xC1; // 'A'
|
||||||
|
screen.getCell(2).ucs4 = 'A';
|
||||||
|
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
AtomicReference<byte[]> sentData = new AtomicReference<>();
|
||||||
|
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||||
|
@Override
|
||||||
|
protected void sendAidResponse(byte[] data) {
|
||||||
|
sentData.set(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Click at address 5 inside the field
|
||||||
|
boolean handled = input.lightPenSelect(5);
|
||||||
|
assertTrue(handled);
|
||||||
|
assertEquals(5, screen.getCursorAddress(), "Cursor should be positioned at clicked address");
|
||||||
|
assertTrue(faIsModified(screen.getCell(0).fa & 0xFF), "MDT should be set");
|
||||||
|
|
||||||
|
byte[] sent = sentData.get();
|
||||||
|
assertNotNull(sent);
|
||||||
|
assertEquals((byte) AID_SELECT, sent[0], "Immediate space designator must send AID_SELECT (0x7E)");
|
||||||
|
|
||||||
|
// Format: AID(1) + CursorAddr(2) + SBA(1) + DesignatorAddr(2) = 6 bytes (NO text content!)
|
||||||
|
assertEquals(6, sent.length, "AID_SELECT must NOT include field text content");
|
||||||
|
assertEquals((byte) ORDER_SBA, sent[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmediateLightPenSelectionNullDesignator() {
|
||||||
|
// Unprotected selectable field at pos 0 with null designator (0x00)
|
||||||
|
screen.erase(false);
|
||||||
|
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_INT_NORM_SEL));
|
||||||
|
screen.getCell(1).ec = 0x00; // null
|
||||||
|
screen.getCell(1).ucs4 = 0;
|
||||||
|
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
AtomicReference<byte[]> sentData = new AtomicReference<>();
|
||||||
|
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||||
|
@Override
|
||||||
|
protected void sendAidResponse(byte[] data) {
|
||||||
|
sentData.set(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean handled = input.lightPenSelect(2);
|
||||||
|
assertTrue(handled);
|
||||||
|
assertEquals(2, screen.getCursorAddress());
|
||||||
|
assertTrue(faIsModified(screen.getCell(0).fa & 0xFF));
|
||||||
|
|
||||||
|
byte[] sent = sentData.get();
|
||||||
|
assertNotNull(sent);
|
||||||
|
assertEquals((byte) AID_SELECT, sent[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmediateEnterLightPenSelectionAmpersandDesignator() {
|
||||||
|
// Selectable field at pos 0 with '&' designator (0x50)
|
||||||
|
screen.erase(false);
|
||||||
|
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_INT_NORM_SEL));
|
||||||
|
screen.getCell(1).ec = 0x50; // '&'
|
||||||
|
screen.getCell(1).ucs4 = '&';
|
||||||
|
screen.getCell(2).ec = (byte) 0xC1; // 'A'
|
||||||
|
screen.getCell(2).ucs4 = 'A';
|
||||||
|
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
AtomicReference<byte[]> sentData = new AtomicReference<>();
|
||||||
|
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||||
|
@Override
|
||||||
|
protected void sendAidResponse(byte[] data) {
|
||||||
|
sentData.set(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean handled = input.lightPenSelect(1);
|
||||||
|
assertTrue(handled);
|
||||||
|
assertEquals(1, screen.getCursorAddress());
|
||||||
|
assertTrue(faIsModified(screen.getCell(0).fa & 0xFF));
|
||||||
|
|
||||||
|
byte[] sent = sentData.get();
|
||||||
|
assertNotNull(sent);
|
||||||
|
assertEquals((byte) AID_ENTER, sent[0], "Immediate '&' designator must send AID_ENTER (0x7D)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeferredLightPenSelectionToggle() {
|
||||||
|
// Protected selectable field at pos 0 with '?' designator (0x6F)
|
||||||
|
screen.erase(false);
|
||||||
|
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_PROTECT | FA_INT_NORM_SEL));
|
||||||
|
screen.getCell(1).ec = 0x6F; // '?'
|
||||||
|
screen.getCell(1).ucs4 = '?';
|
||||||
|
screen.getCell(2).ec = (byte) 0xC3; // 'C'
|
||||||
|
screen.getCell(2).ucs4 = 'C';
|
||||||
|
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
AtomicReference<byte[]> sentData = new AtomicReference<>();
|
||||||
|
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||||
|
@Override
|
||||||
|
protected void sendAidResponse(byte[] data) {
|
||||||
|
sentData.set(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// First click: toggle '?' -> '>' and set MDT (no transmission)
|
||||||
|
boolean handled = input.lightPenSelect(1);
|
||||||
|
assertTrue(handled);
|
||||||
|
assertEquals((byte) 0x6E, screen.getCell(1).ec, "Designator must change to '>' (0x6E)");
|
||||||
|
assertEquals('>', (char) screen.getCell(1).ucs4);
|
||||||
|
assertTrue(faIsModified(screen.getCell(0).fa & 0xFF), "MDT must be set");
|
||||||
|
assertNull(sentData.get(), "Deferred selection must NOT transmit immediately");
|
||||||
|
|
||||||
|
// Now user presses Enter: transmits AID_ENTER with '>' and modified field
|
||||||
|
input.sendAid(AID_ENTER);
|
||||||
|
byte[] sent = sentData.get();
|
||||||
|
assertNotNull(sent);
|
||||||
|
assertEquals((byte) AID_ENTER, sent[0]);
|
||||||
|
assertEquals((byte) ORDER_SBA, sent[3]);
|
||||||
|
assertEquals((byte) 0x6E, sent[6], "Sent field content must contain '>' (0x6E)");
|
||||||
|
assertEquals((byte) 0xC3, sent[7], "Sent field content must contain 'C'");
|
||||||
|
|
||||||
|
sentData.set(null);
|
||||||
|
|
||||||
|
// Second click: toggle '>' -> '?' and clear MDT
|
||||||
|
handled = input.lightPenSelect(1);
|
||||||
|
assertTrue(handled);
|
||||||
|
assertEquals((byte) 0x6F, screen.getCell(1).ec, "Designator must change back to '?' (0x6F)");
|
||||||
|
assertEquals('?', (char) screen.getCell(1).ucs4);
|
||||||
|
assertFalse(faIsModified(screen.getCell(0).fa & 0xFF), "MDT must be cleared");
|
||||||
|
assertNull(sentData.get(), "Deferred deselection must NOT transmit immediately");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendGraphicMouseAidCoordinatesAndCursorSynchronization() {
|
||||||
|
screen.erase(false);
|
||||||
|
screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||||
|
screen.getCell(1).ec = (byte) 0xD6; // 'O'
|
||||||
|
screen.getCell(2).ec = (byte) 0xD2; // 'K'
|
||||||
|
screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
// Click at row 3, col 15 -> address = 3 * 80 + 15 = 255
|
||||||
|
screen.setCursorPosition(3, 15);
|
||||||
|
|
||||||
|
GraphicsPlane plane = new GraphicsPlane(720, 384);
|
||||||
|
plane.setScreenDimensions(80, 24);
|
||||||
|
GocaDecoder goca = new GocaDecoder(plane);
|
||||||
|
goca.setGraphicsCursorActive(true);
|
||||||
|
goca.setGraphicCursorPosition(200, 100);
|
||||||
|
|
||||||
|
AtomicReference<byte[]> sentData = new AtomicReference<>();
|
||||||
|
InputProcessor input = new InputProcessor(screen, translator, null) {
|
||||||
|
@Override
|
||||||
|
protected void sendAidResponse(byte[] data) {
|
||||||
|
sentData.set(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
input.setGocaDecoder(goca);
|
||||||
|
|
||||||
|
input.sendGraphicMouseAid(AID_ENTER, 1, false, false);
|
||||||
|
|
||||||
|
byte[] sent = sentData.get();
|
||||||
|
assertNotNull(sent);
|
||||||
|
|
||||||
|
// AID_SF (0x88) + 56 SF bytes + AID_ENTER (0x7D) + 2 cursor bytes + SBA + ...
|
||||||
|
assertEquals((byte) AID_SF, sent[0]);
|
||||||
|
assertEquals(0x00, sent[1]);
|
||||||
|
assertEquals(0x34, sent[2]); // 52-byte SF length mask
|
||||||
|
|
||||||
|
// Coordinate verification in SF
|
||||||
|
int gx = (sent[25] << 8) | (sent[26] & 0xFF);
|
||||||
|
int gy = (sent[27] << 8) | (sent[28] & 0xFF);
|
||||||
|
assertEquals(200, (short) gx);
|
||||||
|
assertEquals(100, (short) gy);
|
||||||
|
|
||||||
|
// Trailing AID and cursor SBA at index 57-59
|
||||||
|
assertEquals((byte) AID_ENTER, sent[57]);
|
||||||
|
byte[] expectedCursor = encodeAddress(3 * 80 + 15, 24, 80);
|
||||||
|
assertEquals(expectedCursor[0], sent[58], "Trailing cursor address byte 0 must match clicked row/col");
|
||||||
|
assertEquals(expectedCursor[1], sent[59], "Trailing cursor address byte 1 must match clicked row/col");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGddmCoordinateTransformModel2AndModel4() {
|
||||||
|
// Model 2 (80x24)
|
||||||
|
GddmCoordinateTransform t2 = new GddmCoordinateTransform(80, 24, 9, 16);
|
||||||
|
assertEquals(720, t2.getTotalWidth());
|
||||||
|
assertEquals(384, t2.getTotalHeight());
|
||||||
|
assertEquals(360, t2.getXMax());
|
||||||
|
assertEquals(191, t2.getYMax());
|
||||||
|
|
||||||
|
// Row 0 click (y in [0..15] out of 384)
|
||||||
|
Point gocaTop = t2.screenPixelToGoca(0, 0, 0, 0, 9, 16);
|
||||||
|
assertEquals(-360, gocaTop.x);
|
||||||
|
assertEquals(191, gocaTop.y, "Top row in Model 2 must start at gy = 191");
|
||||||
|
|
||||||
|
// Model 4 (80x43)
|
||||||
|
GddmCoordinateTransform t4 = new GddmCoordinateTransform(80, 43, 9, 16);
|
||||||
|
assertEquals(720, t4.getTotalWidth());
|
||||||
|
assertEquals(688, t4.getTotalHeight());
|
||||||
|
assertEquals(360, t4.getXMax());
|
||||||
|
assertEquals(343, t4.getYMax());
|
||||||
|
|
||||||
|
Point gocaTop4 = t4.screenPixelToGoca(0, 0, 0, 0, 9, 16);
|
||||||
|
assertEquals(-360, gocaTop4.x);
|
||||||
|
assertEquals(343, gocaTop4.y, "Top row in Model 4 must start at gy = 343");
|
||||||
|
|
||||||
|
// Center roundtrip
|
||||||
|
Point centerBase = t4.gocaToBase(0, 0);
|
||||||
|
assertEquals(360, centerBase.x);
|
||||||
|
assertEquals(343, centerBase.y);
|
||||||
|
Point centerGoca = t4.baseToGoca(centerBase.x, centerBase.y);
|
||||||
|
assertEquals(0, centerGoca.x);
|
||||||
|
assertEquals(0, centerGoca.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package haus.nightmare.lib3270j.telnet;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.datastream.DataStreamProcessor;
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||||
|
import haus.nightmare.lib3270j.protocol.TelnetConstants;
|
||||||
|
import haus.nightmare.lib3270j.protocol.TN3270EConstants;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.TelnetConstants.*;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.TN3270EConstants.*;
|
||||||
|
|
||||||
|
public class TelnetFSMPhase1Test {
|
||||||
|
|
||||||
|
private ConnectionConfig config;
|
||||||
|
private ScreenBuffer screenBuffer;
|
||||||
|
private DataStreamProcessor dsProcessor;
|
||||||
|
private InputProcessor inputProcessor;
|
||||||
|
private TelnetFSM fsm;
|
||||||
|
private MockConnection connection;
|
||||||
|
private EbcdicTranslator translator;
|
||||||
|
|
||||||
|
private static class MockConnection extends TelnetConnection {
|
||||||
|
final List<byte[]> sentData = new ArrayList<>();
|
||||||
|
|
||||||
|
MockConnection(ConnectionConfig config, TelnetFSM fsm) {
|
||||||
|
super(config, fsm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void sendRaw(byte[] data) {
|
||||||
|
sentData.add(data.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void sendRaw(byte[] data, int offset, int length) {
|
||||||
|
byte[] b = new byte[length];
|
||||||
|
System.arraycopy(data, offset, b, 0, length);
|
||||||
|
sentData.add(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
translator = new EbcdicTranslator();
|
||||||
|
config = new ConnectionConfig("localhost", 23, TerminalModel.IBM_3279_4);
|
||||||
|
screenBuffer = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
dsProcessor = new DataStreamProcessor(screenBuffer, translator);
|
||||||
|
fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||||
|
inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
||||||
|
dsProcessor.setInputProcessor(inputProcessor);
|
||||||
|
connection = new MockConnection(config, fsm);
|
||||||
|
fsm.setConnection(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void feedBytes(int... bytes) {
|
||||||
|
for (int b : bytes) {
|
||||||
|
fsm.feedByte(b & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTn3270eSequencingWaitsForSendDeviceType() {
|
||||||
|
config.setTn3270eEnabled(true);
|
||||||
|
fsm.onConnected();
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// 1. Server sends DO TN3270E
|
||||||
|
feedBytes(IAC, DO, TELOPT_TN3270E);
|
||||||
|
|
||||||
|
// Client must reply IAC WILL TN3270E only, and MUST NOT emit unsolicited DEVICE-TYPE REQUEST
|
||||||
|
assertEquals(1, connection.sentData.size(), "Only WILL TN3270E should be sent initially");
|
||||||
|
byte[] willPkt = connection.sentData.get(0);
|
||||||
|
assertArrayEquals(new byte[] { (byte) IAC, (byte) WILL, (byte) TELOPT_TN3270E }, willPkt);
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// 2. Server sends IAC SB TN3270E SEND DEVICE-TYPE IAC SE
|
||||||
|
feedBytes(IAC, SB, TELOPT_TN3270E, OP_SEND, OP_DEVICE_TYPE, IAC, SE);
|
||||||
|
|
||||||
|
// Client must now send IAC SB TN3270E DEVICE-TYPE REQUEST <type> IAC SE
|
||||||
|
assertEquals(1, connection.sentData.size(), "Client should send DEVICE-TYPE REQUEST after SEND command");
|
||||||
|
byte[] reqPkt = connection.sentData.get(0);
|
||||||
|
assertTrue((reqPkt[0] & 0xFF) == IAC && (reqPkt[1] & 0xFF) == SB && (reqPkt[2] & 0xFF) == TELOPT_TN3270E);
|
||||||
|
assertEquals(OP_DEVICE_TYPE, reqPkt[3] & 0xFF);
|
||||||
|
assertEquals(OP_REQUEST, reqPkt[4] & 0xFF);
|
||||||
|
String devType = new String(reqPkt, 5, reqPkt.length - 7);
|
||||||
|
assertEquals("IBM-3279-4-E", devType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTn3270eResponseSignalingAlwaysResponse() {
|
||||||
|
config.setTn3270eEnabled(true);
|
||||||
|
fsm.onConnected();
|
||||||
|
|
||||||
|
// Negotiate TN3270E
|
||||||
|
feedBytes(IAC, DO, TELOPT_TN3270E);
|
||||||
|
feedBytes(IAC, SB, TELOPT_TN3270E, OP_SEND, OP_DEVICE_TYPE, IAC, SE);
|
||||||
|
byte[] devTypeIs = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
0x02, 0x04, 'I', 'B', 'M', '-', '3', '2', '7', '9', '-', '4', '-', 'E',
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : devTypeIs) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
// Functions: RESPONSES + BIND-IMAGE
|
||||||
|
byte[] funcsReq = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
(byte) OP_FUNCTIONS, (byte) OP_REQUEST,
|
||||||
|
(byte) FUNC_BIND_IMAGE, (byte) FUNC_RESPONSES,
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : funcsReq) fsm.feedByte(b & 0xFF);
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// Send 3270 data record with ALWAYS_RESPONSE (0x02) and seq = 0x0042
|
||||||
|
int seq = 0x0042;
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
bos.write(DT_3270_DATA); // byte 0: data type
|
||||||
|
bos.write(0x00); // byte 1: request flag
|
||||||
|
bos.write(RSF_ALWAYS_RESPONSE);// byte 2: response flag (0x02)
|
||||||
|
bos.write((seq >> 8) & 0xFF); // byte 3: seq hi
|
||||||
|
bos.write(seq & 0xFF); // byte 4: seq lo
|
||||||
|
bos.write(0xF5); // EraseWrite
|
||||||
|
bos.write(0xC3); // WCC
|
||||||
|
bos.write(0x11); bos.write(0x40); bos.write(0x40); // SBA 0
|
||||||
|
bos.write(translator.unicodeToEbcdic('A'));
|
||||||
|
bos.write(IAC);
|
||||||
|
bos.write(EOR);
|
||||||
|
|
||||||
|
for (byte b : bos.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
// Client must emit positive response: DT_RESPONSE (0x02), 0x00, RSF_POSITIVE_RESPONSE (0x00), seq, POS_DEVICE_END (0x00), IAC, EOR
|
||||||
|
boolean foundPositiveResponse = false;
|
||||||
|
for (byte[] pkt : connection.sentData) {
|
||||||
|
if (pkt.length >= 8 &&
|
||||||
|
(pkt[0] & 0xFF) == DT_RESPONSE &&
|
||||||
|
(pkt[2] & 0xFF) == RSF_POSITIVE_RESPONSE &&
|
||||||
|
((((pkt[3] & 0xFF) << 8) | (pkt[4] & 0xFF)) == seq) &&
|
||||||
|
(pkt[5] & 0xFF) == POS_DEVICE_END &&
|
||||||
|
(pkt[6] & 0xFF) == IAC &&
|
||||||
|
(pkt[7] & 0xFF) == EOR) {
|
||||||
|
foundPositiveResponse = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(foundPositiveResponse, "Expected positive response with matching sequence number for RSF_ALWAYS_RESPONSE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTn3270eResponseSignalingNoResponse() {
|
||||||
|
config.setTn3270eEnabled(true);
|
||||||
|
fsm.onConnected();
|
||||||
|
|
||||||
|
feedBytes(IAC, DO, TELOPT_TN3270E);
|
||||||
|
feedBytes(IAC, SB, TELOPT_TN3270E, OP_SEND, OP_DEVICE_TYPE, IAC, SE);
|
||||||
|
byte[] devTypeIs = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
0x02, 0x04, 'I', 'B', 'M', '-', '3', '2', '7', '9', '-', '4', '-', 'E',
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : devTypeIs) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
byte[] funcsReq = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
(byte) OP_FUNCTIONS, (byte) OP_REQUEST,
|
||||||
|
(byte) FUNC_BIND_IMAGE, (byte) FUNC_RESPONSES,
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : funcsReq) fsm.feedByte(b & 0xFF);
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// Send 3270 data record with NO_RESPONSE (0x00)
|
||||||
|
int seq = 0x0099;
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
bos.write(DT_3270_DATA);
|
||||||
|
bos.write(0x00);
|
||||||
|
bos.write(RSF_NO_RESPONSE); // 0x00
|
||||||
|
bos.write((seq >> 8) & 0xFF);
|
||||||
|
bos.write(seq & 0xFF);
|
||||||
|
bos.write(0xF5);
|
||||||
|
bos.write(0xC3);
|
||||||
|
bos.write(IAC);
|
||||||
|
bos.write(EOR);
|
||||||
|
|
||||||
|
for (byte b : bos.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
// No response packets should be sent
|
||||||
|
assertEquals(0, connection.sentData.size(), "No response should be generated when RSF_NO_RESPONSE (0x00)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSysReqOutOfBandRoutingAndToggle() {
|
||||||
|
config.setTn3270eEnabled(true);
|
||||||
|
fsm.onConnected();
|
||||||
|
|
||||||
|
// Complete TN3270E negotiation
|
||||||
|
feedBytes(IAC, DO, TELOPT_TN3270E);
|
||||||
|
feedBytes(IAC, SB, TELOPT_TN3270E, OP_SEND, OP_DEVICE_TYPE, IAC, SE);
|
||||||
|
byte[] devTypeIs = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
0x02, 0x04, 'I', 'B', 'M', '-', '3', '2', '7', '9', '-', '4', '-', 'E',
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : devTypeIs) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
byte[] funcsReq = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
(byte) OP_FUNCTIONS, (byte) OP_REQUEST,
|
||||||
|
(byte) FUNC_BIND_IMAGE, (byte) FUNC_RESPONSES, (byte) FUNC_SYSREQ,
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : funcsReq) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
// Send BIND image to bind session
|
||||||
|
byte[] bindPacket = new byte[EH_SIZE + 35];
|
||||||
|
bindPacket[0] = DT_BIND_IMAGE;
|
||||||
|
bindPacket[1] = 0; bindPacket[2] = 0; bindPacket[3] = 0; bindPacket[4] = 1;
|
||||||
|
bindPacket[EH_SIZE + 24] = 0x02; // Model 2 (24x80)
|
||||||
|
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
|
||||||
|
bStream.write(bindPacket, 0, bindPacket.length);
|
||||||
|
bStream.write(IAC);
|
||||||
|
bStream.write(EOR);
|
||||||
|
for (byte b : bStream.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
assertEquals(ConnectionState.CONNECTED_TN3270E, fsm.getConnectionState());
|
||||||
|
assertTrue(fsm.isTn3270eBound());
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// Lock keyboard as if host app is waiting
|
||||||
|
inputProcessor.setKeyboardLocked(true);
|
||||||
|
assertTrue(inputProcessor.isKeyboardLocked());
|
||||||
|
|
||||||
|
// 1. User presses SYSREQ key (AID 0xF0)
|
||||||
|
inputProcessor.sendAid(DS3270Constants.AID_SYSREQ);
|
||||||
|
|
||||||
|
// Keyboard should now be unlocked and IAC AO (0xFF 0xF5) sent out-of-band
|
||||||
|
assertFalse(inputProcessor.isKeyboardLocked(), "Keyboard should be unlocked on SYSREQ");
|
||||||
|
boolean foundIacAo = false;
|
||||||
|
for (byte[] pkt : connection.sentData) {
|
||||||
|
if (pkt.length == 2 && (pkt[0] & 0xFF) == IAC && (pkt[1] & 0xFF) == AO) {
|
||||||
|
foundIacAo = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(foundIacAo, "Expected raw IAC AO out-of-band telnet command");
|
||||||
|
assertEquals(ConnectionState.CONNECTED_SSCP, fsm.getConnectionState(), "Session should switch to CONNECTED_SSCP");
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// 2. User types in SSCP-LU mode (e.g. "LOGOFF") and presses Enter
|
||||||
|
inputProcessor.typeCharacter('L');
|
||||||
|
inputProcessor.typeCharacter('O');
|
||||||
|
inputProcessor.typeCharacter('G');
|
||||||
|
inputProcessor.typeCharacter('O');
|
||||||
|
inputProcessor.typeCharacter('F');
|
||||||
|
inputProcessor.typeCharacter('F');
|
||||||
|
inputProcessor.sendAid(DS3270Constants.AID_ENTER);
|
||||||
|
|
||||||
|
// Verify sent record has header DT_SSCP_LU_DATA (0x07)
|
||||||
|
assertEquals(1, connection.sentData.size(), "SSCP-LU input should produce one record");
|
||||||
|
byte[] sscpPkt = connection.sentData.get(0);
|
||||||
|
assertEquals(DT_SSCP_LU_DATA, sscpPkt[0] & 0xFF, "Data type should be DT_SSCP_LU_DATA (0x07)");
|
||||||
|
assertEquals(translator.unicodeToEbcdic('L'), sscpPkt[5] & 0xFF);
|
||||||
|
assertEquals(translator.unicodeToEbcdic('O'), sscpPkt[6] & 0xFF);
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// 3. Host sends inbound SSCP-LU unformatted prompt
|
||||||
|
ByteArrayOutputStream ussPrompt = new ByteArrayOutputStream();
|
||||||
|
ussPrompt.write(DT_SSCP_LU_DATA);
|
||||||
|
ussPrompt.write(0); ussPrompt.write(0); ussPrompt.write(0); ussPrompt.write(2);
|
||||||
|
ussPrompt.write(translator.unicodeToEbcdic('U'));
|
||||||
|
ussPrompt.write(translator.unicodeToEbcdic('S'));
|
||||||
|
ussPrompt.write(translator.unicodeToEbcdic('S'));
|
||||||
|
ussPrompt.write(IAC);
|
||||||
|
ussPrompt.write(EOR);
|
||||||
|
for (byte b : ussPrompt.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
assertEquals('U', translator.ebcdicToUnicode(screenBuffer.getCellEC(80)));
|
||||||
|
assertEquals('S', translator.ebcdicToUnicode(screenBuffer.getCellEC(81)));
|
||||||
|
assertEquals('S', translator.ebcdicToUnicode(screenBuffer.getCellEC(82)));
|
||||||
|
|
||||||
|
// 4. User presses SYSREQ again to toggle back to 3270 session
|
||||||
|
inputProcessor.sendAid(DS3270Constants.AID_SYSREQ);
|
||||||
|
assertEquals(ConnectionState.CONNECTED_TN3270E, fsm.getConnectionState(), "Second SYSREQ should toggle back to CONNECTED_TN3270E");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTn3270eResponseSignalingErrorResponse() {
|
||||||
|
config.setTn3270eEnabled(true);
|
||||||
|
fsm.onConnected();
|
||||||
|
|
||||||
|
feedBytes(IAC, DO, TELOPT_TN3270E);
|
||||||
|
feedBytes(IAC, SB, TELOPT_TN3270E, OP_SEND, OP_DEVICE_TYPE, IAC, SE);
|
||||||
|
byte[] devTypeIs = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
0x02, 0x04, 'I', 'B', 'M', '-', '3', '2', '7', '9', '-', '4', '-', 'E',
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : devTypeIs) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
byte[] funcsReq = new byte[]{
|
||||||
|
(byte) IAC, (byte) SB, (byte) TELOPT_TN3270E,
|
||||||
|
(byte) OP_FUNCTIONS, (byte) OP_REQUEST,
|
||||||
|
(byte) FUNC_BIND_IMAGE, (byte) FUNC_RESPONSES,
|
||||||
|
(byte) IAC, (byte) SE
|
||||||
|
};
|
||||||
|
for (byte b : funcsReq) fsm.feedByte(b & 0xFF);
|
||||||
|
|
||||||
|
// Bind session
|
||||||
|
byte[] bindPacket = new byte[EH_SIZE + 35];
|
||||||
|
bindPacket[0] = DT_BIND_IMAGE;
|
||||||
|
bindPacket[1] = 0; bindPacket[2] = 0; bindPacket[3] = 0; bindPacket[4] = 1;
|
||||||
|
bindPacket[EH_SIZE + 24] = 0x02;
|
||||||
|
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
|
||||||
|
bStream.write(bindPacket, 0, bindPacket.length);
|
||||||
|
bStream.write(IAC);
|
||||||
|
bStream.write(EOR);
|
||||||
|
for (byte b : bStream.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
connection.sentData.clear();
|
||||||
|
|
||||||
|
// Send a malformed 3270 record that triggers an error with ERROR_RESPONSE (0x01)
|
||||||
|
int seq = 0x0077;
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
bos.write(DT_3270_DATA);
|
||||||
|
bos.write(0x00);
|
||||||
|
bos.write(RSF_ERROR_RESPONSE); // 0x01
|
||||||
|
bos.write((seq >> 8) & 0xFF);
|
||||||
|
bos.write(seq & 0xFF);
|
||||||
|
bos.write(0xF5); // EraseWrite
|
||||||
|
// Truncated SBA order without address bytes to trigger an exception in dsProcessor
|
||||||
|
bos.write(0x11); // SBA without address bytes
|
||||||
|
bos.write(IAC);
|
||||||
|
bos.write(EOR);
|
||||||
|
|
||||||
|
// Note: dsProcessor gracefully handles or if it throws, negative response is sent.
|
||||||
|
// Let's verify what happens when exception or normal:
|
||||||
|
for (byte b : bos.toByteArray()) fsm.feedByte(b & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -153,9 +153,12 @@ public class TelnetFSMPhase2Test {
|
|||||||
fsm.onConnected();
|
fsm.onConnected();
|
||||||
output.reset();
|
output.reset();
|
||||||
|
|
||||||
// Server sends DO TN3270E -> client sends WILL TN3270E and requests "LU_A"
|
// Server sends DO TN3270E -> client sends WILL TN3270E
|
||||||
fsm.feedBytes(new byte[] { (byte) IAC, (byte) DO, (byte) TELOPT_TN3270E }, 0, 3);
|
fsm.feedBytes(new byte[] { (byte) IAC, (byte) DO, (byte) TELOPT_TN3270E }, 0, 3);
|
||||||
|
|
||||||
|
// Server sends SB TN3270E SEND DEVICE-TYPE -> client requests "LU_A"
|
||||||
|
fsm.feedBytes(new byte[] { (byte) IAC, (byte) SB, (byte) TELOPT_TN3270E, (byte) OP_SEND, (byte) OP_DEVICE_TYPE, (byte) IAC, (byte) SE }, 0, 7);
|
||||||
|
|
||||||
String sent1 = new String(output.toByteArray());
|
String sent1 = new String(output.toByteArray());
|
||||||
assertTrue(sent1.contains("LU_A"));
|
assertTrue(sent1.contains("LU_A"));
|
||||||
output.reset();
|
output.reset();
|
||||||
|
|||||||
Reference in New Issue
Block a user