339 lines
14 KiB
Java
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);
|
|
}
|
|
}
|