Files
j3270/lib3270j/src/test/java/haus/nightmare/lib3270j/datastream/QueryReplyBuilderTest.java
T
rudi 3d3a939ece
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m21s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m29s
Codepage, ECL, and some UI updates
2026-09-22 13:38:12 +00:00

339 lines
14 KiB
Java

package haus.nightmare.lib3270j.datastream;
import org.junit.jupiter.api.Test;
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 QueryReplyBuilderTest {
private final EbcdicTranslator translator = new EbcdicTranslator();
private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen);
@Test
public void testBuildAllQueryRepliesDefaultBoth() {
assertEquals(GraphicsMode.BOTH, qrBuilder.getGraphicsMode());
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
assertTrue(replies.length > 0);
assertEquals((byte) AID_SF, replies[0]);
// 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;
for (int i = 0; i < completeReplies.length - 3; i++) {
if ((completeReplies[i] & 0xFF) == 0x81 && (completeReplies[i + 1] & 0xFF) == QR_SEGMENT) {
hasB0 = true;
break;
}
}
assertTrue(hasB0);
}
@Test
public void testBuildAllQueryRepliesExplicitNone() {
qrBuilder.setGraphicsMode(GraphicsMode.NONE);
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
assertTrue(replies.length > 0);
assertEquals((byte) AID_SF, replies[0]);
// In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be advertised in Summary
int sumLen = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
boolean hasB0 = false;
boolean hasA8 = false;
for (int i = 5; i < 1 + sumLen; i++) {
if ((replies[i] & 0xFF) == QR_SEGMENT) hasB0 = true;
if ((replies[i] & 0xFF) == QR_TRANSPARENCY) hasA8 = true;
}
assertFalse(hasB0);
assertTrue(hasA8, "QR_TRANSPARENCY (0xA8) should be advertised even in GraphicsMode.NONE");
// And QR_TRANSPARENCY structured field payload must be present in replies
boolean hasA8Payload = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_TRANSPARENCY) {
hasA8Payload = true;
break;
}
}
assertTrue(hasA8Payload, "QR_TRANSPARENCY structured field should be included in buildAllQueryReplies");
}
@Test
public void testBuildAllQueryRepliesWithVectorGraphics() {
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
// In complete replies, Vector Graphics QR 0xB0 and 0xB4 must be present
boolean hasB0 = false;
boolean hasB4 = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
hasB0 = true;
}
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRCOLOR) {
hasB4 = true;
}
}
assertTrue(hasB0);
assertTrue(hasB4);
}
@Test
public void testBuildAllQueryRepliesWithBoth() {
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
// Vector Graphics (0xB0) must be present and Charsets must be present (0x85)
boolean hasB0 = false;
boolean hasCharsets = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
hasB0 = true;
}
if ((replies[i] & 0xFF) == 0x81 && (completeReplyIsCharsets(replies, i))) {
hasCharsets = true;
}
}
assertTrue(hasB0);
assertTrue(hasCharsets);
}
private boolean completeReplyIsCharsets(byte[] replies, int i) {
return (replies[i + 1] & 0xFF) == QR_CHARSETS;
}
@Test
public void testBuildAllQueryRepliesWithProgrammedSymbols() {
qrBuilder.setGraphicsMode(GraphicsMode.PROGRAMMED_SYMBOLS);
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
// Charsets with LoadPS (0x0A) must be present, and QR_SEGMENT must NOT be present
boolean hasB0 = false;
boolean hasCharsetsWithLoadPs = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
hasB0 = true;
}
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
hasCharsetsWithLoadPs = true;
}
}
}
assertFalse(hasB0);
assertTrue(hasCharsetsWithLoadPs);
}
@Test
public void testBuildFilteredQueryRepliesWithSupportedCodes() {
byte[] requested = new byte[] { (byte) QR_DDM, (byte) QR_USABLE_AREA };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
// First SF should be DDM: length (2 bytes), SFID_QREPLY (0x81), QR_DDM (0x95)
int len1 = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
assertEquals(0x81, replies[3] & 0xFF);
assertEquals(QR_DDM, replies[4] & 0xFF);
// Second SF should be Usable Area: 23 bytes (4 bytes header + 19 bytes payload)
int pos2 = 1 + len1;
int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF);
assertEquals(23, len2);
assertEquals(0x81, replies[pos2 + 2] & 0xFF);
assertEquals(QR_USABLE_AREA, replies[pos2 + 3] & 0xFF);
// Offset 0x15 (21 in SF payload = pos2 + 21): Buffer size high
int bufSize = ((replies[pos2 + 21] & 0xFF) << 8) | (replies[pos2 + 22] & 0xFF);
assertEquals(80 * 43, bufSize);
}
@Test
public void testBuildFilteredQueryRepliesWithUnsupportedCode() {
byte[] requested = new byte[] { (byte) 0x77 }; // Unsupported code
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
// Should emit QR_NULL (0xFF)
assertEquals(0x81, replies[3] & 0xFF);
assertEquals(QR_NULL, replies[4] & 0xFF);
}
@Test
public void testImplicitPartitionModel4() {
byte[] requested = new byte[] { (byte) QR_IMP_PART };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
int len = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY
assertEquals(QR_IMP_PART, replies[4] & 0xFF); // 0xA6
// Payload starts at index 5: 22 bytes total
// Default size = 80 x 24
int defCols = ((replies[10] & 0xFF) << 8) | (replies[11] & 0xFF);
int defRows = ((replies[12] & 0xFF) << 8) | (replies[13] & 0xFF);
assertEquals(80, defCols);
assertEquals(24, defRows);
// Alt size = 80 x 43
int altCols = ((replies[14] & 0xFF) << 8) | (replies[15] & 0xFF);
int altRows = ((replies[16] & 0xFF) << 8) | (replies[17] & 0xFF);
assertEquals(80, altCols);
assertEquals(43, altRows);
}
@Test
public void testProgrammedSymbolsDescriptorsSingleAndTriplePlane() {
qrBuilder.setGraphicsMode(GraphicsMode.PROGRAMMED_SYMBOLS);
byte[] requested = new byte[] { (byte) QR_CHARSETS };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
int offset = -1;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
offset = i + 2;
break;
}
}
assertTrue(offset >= 0, "QR_CHARSETS must be present");
// QR_CHARSETS payload structure:
// Flags (2 bytes), SDW (1 byte), SDH (1 byte), Form (1 byte), DevType (2 bytes), Res (1 byte), DL (1 byte)
// DL is at offset + 8, and is 7 bytes per descriptor.
int dl = replies[offset + 8] & 0xFF;
assertEquals(7, dl);
int descOffset = offset + 9;
// Descriptor 1: SET 0 (Base) -> flags 0x10
assertEquals(0x00, replies[descOffset] & 0xFF);
assertEquals(0x10, replies[descOffset + 1] & 0xFF);
// Descriptor 2: SET 1 (APL) -> flags 0x00
assertEquals(0x01, replies[descOffset + 7] & 0xFF);
assertEquals(0x00, replies[descOffset + 7 + 1] & 0xFF);
// Descriptor 3: PSA (Single plane) -> flags 0x80 (Loadable, single-plane)
assertEquals(0x02, replies[descOffset + 14] & 0xFF);
assertEquals(0x80, replies[descOffset + 14 + 1] & 0xFF);
// Descriptor 4: PSB (Single plane) -> flags 0x80 (Loadable, single-plane)
assertEquals(0x03, replies[descOffset + 21] & 0xFF);
assertEquals(0x80, replies[descOffset + 21 + 1] & 0xFF);
// Descriptor 5: PSC (Triple plane) -> flags 0xC0 (Loadable | Triple-plane)
assertEquals(0x04, replies[descOffset + 28] & 0xFF);
assertEquals(0xC0, replies[descOffset + 28 + 1] & 0xFF);
// Descriptor 6: PSD (Triple plane) -> flags 0xC0 (Loadable | Triple-plane)
assertEquals(0x05, replies[descOffset + 35] & 0xFF);
assertEquals(0xC0, replies[descOffset + 35 + 1] & 0xFF);
// Descriptor 7: PSE (Triple plane) -> flags 0xC0 (Loadable | Triple-plane)
assertEquals(0x06, replies[descOffset + 42] & 0xFF);
assertEquals(0xC0, replies[descOffset + 42 + 1] & 0xFF);
// Descriptor 8: PSF (Triple plane) -> flags 0xC0 (Loadable | Triple-plane)
assertEquals(0x07, replies[descOffset + 49] & 0xFF);
assertEquals(0xC0, replies[descOffset + 49 + 1] & 0xFF);
}
@Test
public void testQueryReplyImageAndRpqNames() {
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
byte[] requested = new byte[] { (byte) QR_IMAGE, (byte) QR_TRANSPARENCY, (byte) QR_PROCEDURE };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
// First SF should be QR_NULL (0xFF) because 0x82 is 3270 Image SF (not supported on 3179G)
int len1 = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY
assertEquals(QR_NULL, replies[4] & 0xFF); // 0xFF
// Second SF should be QR_TRANSPARENCY (0xA8) matching requested code
int pos2 = 1 + len1;
int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF);
assertEquals(0x81, replies[pos2 + 2] & 0xFF);
assertEquals(QR_TRANSPARENCY, replies[pos2 + 3] & 0xFF); // 0xA8
// Third SF should be QR_PROCEDURE (0xB1)
int pos3 = pos2 + len2;
assertEquals(0x81, replies[pos3 + 2] & 0xFF);
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 | Graphics (0x03), 0x00
assertEquals(0x03, 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 DS3270.java)
assertEquals(0x00, replies[11] & 0xFF);
// Xr (4 bytes): 0x00010060 (96 dpi matching HOD)
int xr = ((replies[12] & 0xFF) << 24) | ((replies[13] & 0xFF) << 16) | ((replies[14] & 0xFF) << 8) | (replies[15] & 0xFF);
assertEquals(0x00010060, xr);
// Yr (4 bytes): 0x00010060 (96 dpi matching HOD)
int yr = ((replies[16] & 0xFF) << 24) | ((replies[17] & 0xFF) << 16) | ((replies[18] & 0xFF) << 8) | (replies[19] & 0xFF);
assertEquals(0x00010060, 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));
}
@Test
public void testGrSymbolSetMatchesHOD() {
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
byte[] requested = new byte[] { (byte) QR_GRSYMBOLSET };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
assertEquals(33, ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF));
assertEquals(0x81, replies[3] & 0xFF);
assertEquals(QR_GRSYMBOLSET, replies[4] & 0xFF);
}
}