TLS and Graphics, what more?
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 43s
Release j3270 / Build & Publish Release (push) Successful in 1m7s

This commit is contained in:
2026-08-21 02:51:24 -04:00
parent f0fc1b6e70
commit d7d2f8bad5
49 changed files with 3611 additions and 102 deletions
@@ -3,6 +3,7 @@ package org.lib3270j.datastream;
import org.junit.jupiter.api.Test;
import org.lib3270j.TerminalModel;
import org.lib3270j.charset.EbcdicTranslator;
import org.lib3270j.graphics.GraphicsMode;
import org.lib3270j.screen.ScreenBuffer;
import static org.junit.jupiter.api.Assertions.*;
import static org.lib3270j.protocol.DS3270Constants.*;
@@ -14,11 +15,43 @@ public class QueryReplyBuilderTest {
private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen);
@Test
public void testBuildAllQueryReplies() {
public void testBuildAllQueryRepliesDefaultNone() {
assertEquals(GraphicsMode.NONE, qrBuilder.getGraphicsMode());
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 present
boolean hasB0 = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
hasB0 = true;
break;
}
}
assertFalse(hasB0);
}
@Test
public void testBuildAllQueryRepliesWithVectorGraphics() {
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
assertNotNull(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_GRAPHICS) {
hasB0 = true;
}
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GCOLOR) {
hasB4 = true;
}
}
assertTrue(hasB0);
assertTrue(hasB4);
}
@Test
@@ -52,4 +85,29 @@ public class QueryReplyBuilderTest {
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);
}
}
@@ -0,0 +1,237 @@
package org.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import static org.junit.jupiter.api.Assertions.*;
public class GocaDecoderTest {
@Test
public void testBasicLineAndColorOrders() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
assertFalse(plane.hasContent());
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Color to Red (GOCA Color 2) - 2 bytes: order, color
out.write(GocaConstants.G_GSCOL);
out.write(0x02); // Red
// Line from (1000, 1000) to (2000, 2000) - long order: order, len, x1, y1, x2, y2
out.write(GocaConstants.G_GLINE);
out.write(0x08); // 8 bytes: (x1, y1, x2, y2)
out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF);
out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF);
out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF);
out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testAreaAndPatternFill() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Pattern to Solid - 2 bytes: order, pattern
out.write(GocaConstants.G_GSPT);
out.write(GocaConstants.PT_SOLID);
// Begin Area - 2 bytes: order, flags
out.write(GocaConstants.G_GBAR);
out.write(0x00);
// Polyline forming a triangle: (500, 500) -> (1500, 500) -> (1000, 1500) -> (500, 500)
out.write(GocaConstants.G_GLINE);
out.write(0x10); // 4 points * 4 bytes = 16 bytes
out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF);
out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF);
out.write((1500 >> 8) & 0xFF); out.write(1500 & 0xFF);
out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF);
out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF);
out.write((1500 >> 8) & 0xFF); out.write(1500 & 0xFF);
out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF);
out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF);
// End Area - 1 byte
out.write(GocaConstants.G_GEAR);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testMarkerAndVectorText() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Set Marker Type to Circle - 2 bytes: order, type
out.write(GocaConstants.G_GSMT);
out.write(GocaConstants.MK_CIRCLE);
// Draw Marker at (2000, 2000)
out.write(GocaConstants.G_GMRK);
out.write(0x04);
out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF);
out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF);
// Draw Vector Stroked Character String: "IBM" (EBCDIC: 0xC9, 0xC2, 0xD4)
out.write(GocaConstants.G_GCHST);
out.write(0x07); // 4 bytes pos + 3 bytes chars
out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF);
out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF);
out.write(0xC9); out.write(0xC2); out.write(0xD4);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testProcedureOrdersAndErase() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// Draw something first
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GLINE);
out.write(0x08);
out.write(0x00); out.write(0x00);
out.write(0x00); out.write(0x00);
out.write(0x00); out.write(0x64);
out.write(0x00); out.write(0x64);
byte[] drawStream = out.toByteArray();
decoder.decodeStream(drawStream, 0, drawStream.length);
assertTrue(plane.hasContent());
// Process procedure order 0x0A (Erase presentation space)
byte[] procOrders = new byte[] { (byte) GocaConstants.P_ERASE };
decoder.processProcedureOrders(procOrders, 0, procOrders.length);
assertFalse(plane.hasContent());
}
@Test
public void testSegmentAndRelativeLineOrders() {
GraphicsPlane plane = new GraphicsPlane(720, 688);
plane.setScreenDimensions(80, 43);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 0x70: Begin Segment (14 bytes total)
out.write(GocaConstants.G_BEGSEGM);
out.write(0x0C);
out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x01); // Seg ID 1
out.write(0x74); out.write(0x70);
out.write(0x00); out.write(0x1E); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00);
// 0x3E: End Prologue
out.write(GocaConstants.G_ENDPROLOGUE);
out.write(0x00);
// 0x0A: Color (7 = White)
out.write(GocaConstants.G_GSCOL);
out.write(0x07);
// 0xE1: Relative Line (Absolute Start (300, 200), deltas: (+10, -5), (-10, +5))
out.write(GocaConstants.G_GRLINE);
out.write(0x08); // 4 bytes start coord + 4 bytes (2 deltas) = 8 bytes
out.write(0x01); out.write(0x2C); // Start X = 300
out.write(0x00); out.write(0xC8); // Start Y = 200
out.write(0x0A); out.write(0xFB); // dx = +10, dy = -5
out.write(0xF6); out.write(0x05); // dx = -10, dy = +5
// 0x71: End Segment
out.write(GocaConstants.G_ENDSEGM);
out.write(0x00);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent());
}
@Test
public void testPartialOrderBufferingAcrossStreams() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
// First packet sends G_GLINE order code and half of its coordinates
ByteArrayOutputStream chunk1 = new ByteArrayOutputStream();
chunk1.write(GocaConstants.G_GLINE);
chunk1.write(0x08); // 8 bytes: 2 points
chunk1.write(0x00); chunk1.write(0x64); // x1 = 100
chunk1.write(0x00); chunk1.write(0x64); // y1 = 100
byte[] b1 = chunk1.toByteArray();
decoder.decodeStream(b1, 0, b1.length);
// Order is not complete yet, so no line drawn yet
assertFalse(plane.hasContent());
// Second packet sends the rest of the order: x2 = 200, y2 = 200
ByteArrayOutputStream chunk2 = new ByteArrayOutputStream();
chunk2.write(0x00); chunk2.write(0xC8); // x2 = 200
chunk2.write(0x00); chunk2.write(0xC8); // y2 = 200
byte[] b2 = chunk2.toByteArray();
decoder.decodeStream(b2, 0, b2.length);
// Now the combined order executes!
assertTrue(plane.hasContent());
}
@Test
public void testSetCurrentPositionAndRelativeLine() {
GraphicsPlane plane = new GraphicsPlane(400, 300);
GocaDecoder decoder = new GocaDecoder(plane);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 0x21: Set Current Position to (50, 50)
out.write(GocaConstants.G_GSCP);
out.write(0x04); // len = 4
out.write(0x00); out.write(0x32); // x = 50
out.write(0x00); out.write(0x32); // y = 50
// 0xA1: Relative Line from Current Position: (+10, +20), (-5, -10)
out.write(GocaConstants.G_GCRLIN);
out.write(0x04); // len = 4 (2 steps)
out.write(0x0A); out.write(0x14); // dx = +10, dy = +20
out.write(0xFB); out.write(0xF6); // dx = -5, dy = -10
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent());
assertEquals(55, decoder.getCurX());
assertEquals(60, decoder.getCurY());
}
@Test
public void test3179GCoordinateMapping() {
GraphicsPlane plane = new GraphicsPlane(720, 688);
plane.setScreenDimensions(80, 43);
// Screen center (0, 0) should map to canvas center (360, 343)
assertEquals(360, plane.mapX(0));
assertEquals(343, plane.mapY(0));
// Left edge (-360) should map to 0
assertEquals(0, plane.mapX(-360));
// Right edge (+359) should map to 719
assertEquals(719, plane.mapX(359));
// Top edge (+343) should map to 0
assertEquals(0, plane.mapY(343));
// Bottom edge (-344) should map to 687
assertEquals(687, plane.mapY(-344));
}
}
@@ -0,0 +1,57 @@
package org.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import org.lib3270j.ConnectionConfig;
import static org.junit.jupiter.api.Assertions.*;
public class GraphicsModeTest {
@Test
public void testDefaultsAndParsing() {
assertEquals(GraphicsMode.NONE, GraphicsMode.fromString(null));
assertEquals(GraphicsMode.NONE, GraphicsMode.fromString(""));
assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("NONE"));
assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("off"));
assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("false"));
assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("ps"));
assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("programmed_symbols"));
assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("apl"));
assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("vector"));
assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("goca"));
assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("vector_graphics"));
assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("both"));
assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("all"));
assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("true"));
assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("on"));
}
@Test
public void testFlags() {
assertFalse(GraphicsMode.NONE.isProgrammedSymbolsEnabled());
assertFalse(GraphicsMode.NONE.isVectorGraphicsEnabled());
assertTrue(GraphicsMode.PROGRAMMED_SYMBOLS.isProgrammedSymbolsEnabled());
assertFalse(GraphicsMode.PROGRAMMED_SYMBOLS.isVectorGraphicsEnabled());
assertFalse(GraphicsMode.VECTOR_GRAPHICS.isProgrammedSymbolsEnabled());
assertTrue(GraphicsMode.VECTOR_GRAPHICS.isVectorGraphicsEnabled());
assertTrue(GraphicsMode.BOTH.isProgrammedSymbolsEnabled());
assertTrue(GraphicsMode.BOTH.isVectorGraphicsEnabled());
}
@Test
public void testConnectionConfigDefault() {
ConnectionConfig config = new ConnectionConfig();
assertEquals(GraphicsMode.NONE, config.getGraphicsMode());
config.setGraphicsMode(GraphicsMode.BOTH);
assertEquals(GraphicsMode.BOTH, config.getGraphicsMode());
config.setGraphicsMode(null);
assertEquals(GraphicsMode.NONE, config.getGraphicsMode());
}
}
@@ -0,0 +1,144 @@
package org.lib3270j.graphics;
import org.junit.jupiter.api.Test;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import static org.junit.jupiter.api.Assertions.*;
public class ProgramSymbolManagerTest {
@Test
public void testSetAllocationAndLookup() {
ProgramSymbolManager manager = new ProgramSymbolManager();
// Loadable set can be queried after loadps or clearAll
assertNull(manager.getSymbolSet(0x40));
assertNull(manager.getSymbol(0x40, 0x41));
manager.clearAll();
assertNull(manager.getSymbolSet(0x40));
}
@Test
public void testSinglePlaneLoadPsFormat1() {
ProgramSymbolManager manager = new ProgramSymbolManager();
// Build Format 1 Load PS payload for LCID 0x40, code point 0x41 ('A')
// Format 1 header:
// byte 0: flags (0x01: format 1, single plane)
// byte 1: LCID (0x40)
// byte 2: Start code point (0x41)
// byte 3: RWS (0x02: loadable slot 2)
// followed by 18 bytes per symbol for 9x16 cell:
// Byte 0-1: Col 0 for rows 0..15 (0x80 = bit for row 0 col 0)
// Bytes 2-17: Cols 1..8 for rows 0..15 (0xFF = bits for row 0 cols 1..8)
byte[] payload = new byte[4 + 18];
payload[0] = 0x01; // Format 1
payload[1] = 0x40; // LCID 0x40
payload[2] = 0x41; // Code point 0x41
payload[3] = 0x02; // RWS 2
payload[4] = (byte) 0x80; // Row 0 Col 0 bit
payload[5] = (byte) 0x00; // Rows 8..15 Col 0
for (int r = 0; r < 16; r++) {
payload[6 + r] = (byte) 0xFF; // Cols 1..8 on all rows
}
manager.loadps(payload);
ProgramSymbolSet set = manager.getSymbolSet(0x40);
assertNotNull(set);
assertEquals(0x40, set.getLcid());
assertFalse(set.isTriplePlane());
ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x40, 0x41);
assertNotNull(slot);
assertEquals(9, slot.getWidth());
assertEquals(16, slot.getHeight());
assertFalse(slot.isTriplePlane());
// Verify pixel data (Row 0 Col 0 is 1)
byte[] pixels = slot.getPixelData();
assertNotNull(pixels);
assertEquals(1, pixels[0]); // Row 0 Col 0 is 1
// Test rendering into BufferedImage
BufferedImage canvas = new BufferedImage(80, 24, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = canvas.createGraphics();
boolean drawn = manager.drawSymbol(g2, 0x40, 0x41, 0, 0, 10, 20, Color.GREEN, Color.BLACK);
g2.dispose();
assertTrue(drawn);
}
@Test
public void testTriplePlaneMultiColorComposite() {
ProgramSymbolManager manager = new ProgramSymbolManager();
// Build Format 1 Load PS payload for LCID 0x42, triple plane
// flags = 0x01 (Format 1)
// RWS = 4 (Triple Plane)
byte[] payload = new byte[4 + 18];
payload[0] = 0x01; // Format 1
payload[1] = 0x42; // LCID 0x42
payload[2] = 0x45; // Code point 0x45
payload[3] = 0x04; // RWS 4 (Triple Plane)
// Set pixel at row 0 col 0
payload[4] = (byte) 0x80; // Row 0 Col 0
payload[5] = (byte) 0x00;
for (int r = 0; r < 16; r++) {
payload[6 + r] = (byte) 0x00;
}
manager.loadps(payload);
ProgramSymbolSet set = manager.getSymbolSet(0x42);
assertNotNull(set);
assertTrue(set.isTriplePlane());
ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x42, 0x45);
assertNotNull(slot);
assertTrue(slot.isTriplePlane());
// When loaded with colorPlane=0 (default), all 3 planes are set (val 7 = white composite)
byte[] pixels = slot.getPixelData();
assertEquals(7, pixels[0] & 0xFF);
// Render image
BufferedImage img = slot.getImage(Color.WHITE, Color.BLACK);
assertNotNull(img);
// Pixel (0,0) should be white composite (Red=255, Green=255, Blue=255)
int rgb = img.getRGB(0, 0);
Color pixelColor = new Color(rgb, true);
assertEquals(255, pixelColor.getRed());
assertEquals(255, pixelColor.getGreen());
assertEquals(255, pixelColor.getBlue());
}
@Test
public void testFormat3Bitstream() {
ProgramSymbolManager manager = new ProgramSymbolManager();
// Format 3 payload
// byte 0: 0x03 (Format 3)
// byte 1: LCID (0x44)
// byte 2: code point (0x43)
// byte 3: RWS (2)
// followed by (9 * 16 + 7) / 8 = 18 bytes
byte[] payload = new byte[4 + 18];
payload[0] = 0x03; // Format 3
payload[1] = 0x44; // LCID 0x44
payload[2] = 0x43; // Code point 0x43
payload[3] = 0x02; // RWS 2
payload[4] = (byte) 0x80; // First pixel is set
manager.loadps(payload);
ProgramSymbolSet set = manager.getSymbolSet(0x44);
assertNotNull(set);
ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x44, 0x43);
assertNotNull(slot);
assertEquals(1, slot.getPixelData()[0]);
}
}
@@ -0,0 +1,108 @@
package org.lib3270j.tls;
import org.junit.jupiter.api.Test;
import org.lib3270j.ConnectionConfig;
import org.lib3270j.TerminalModel;
import javax.net.ssl.SSLContext;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
public class TlsConfigTest {
@Test
public void testConnectionConfigDefaults() {
ConnectionConfig config = new ConnectionConfig("localhost", 23);
assertFalse(config.isUseTls());
assertTrue(config.isTlsVerifyCert());
assertEquals("TLS", config.getSslProtocol());
assertNull(config.getCertificateVerifier());
}
@Test
public void testParseHostStringTlsPrefixes() {
// L: prefix
ConnectionConfig c1 = ConnectionConfig.parseHostString("L:mainframe.example.com", 0, TerminalModel.IBM_3279_4);
assertTrue(c1.isUseTls());
assertEquals("mainframe.example.com", c1.getHost());
assertEquals(992, c1.getPort());
// ssl: prefix with explicit port
ConnectionConfig c2 = ConnectionConfig.parseHostString("ssl:zos.local:2323", 0, TerminalModel.IBM_3279_2);
assertTrue(c2.isUseTls());
assertEquals("zos.local", c2.getHost());
assertEquals(2323, c2.getPort());
// y: prefix
ConnectionConfig c3 = ConnectionConfig.parseHostString("y:vm370.net:992", 23, TerminalModel.IBM_3279_4);
assertTrue(c3.isUseTls());
assertEquals("vm370.net", c3.getHost());
assertEquals(992, c3.getPort());
// Plain host
ConnectionConfig c4 = ConnectionConfig.parseHostString("plain.host.com", 23, TerminalModel.IBM_3279_4);
assertFalse(c4.isUseTls());
assertEquals("plain.host.com", c4.getHost());
assertEquals(23, c4.getPort());
// IPv6 host with brackets
ConnectionConfig c5 = ConnectionConfig.parseHostString("L:[2001:db8::1]:992", 0, TerminalModel.IBM_3279_4);
assertTrue(c5.isUseTls());
assertEquals("2001:db8::1", c5.getHost());
assertEquals(992, c5.getPort());
}
@Test
public void testTlsTrustManagerBypass() {
ConnectionConfig config = new ConnectionConfig("untrusted.host", 992);
config.setUseTls(true);
config.setTlsVerifyCert(false);
TlsTrustManager trustManager = new TlsTrustManager(config);
// With tlsVerifyCert=false, checkServerTrusted must not throw even with null/empty chain
assertDoesNotThrow(() -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA"));
}
@Test
public void testTlsTrustManagerVerifierCallback() {
ConnectionConfig config = new ConnectionConfig("selfsigned.host", 992);
config.setUseTls(true);
config.setTlsVerifyCert(true);
AtomicBoolean verifierInvoked = new AtomicBoolean(false);
config.setCertificateVerifier((chain, authType, exception) -> {
verifierInvoked.set(true);
return true; // Accept certificate
});
TlsTrustManager trustManager = new TlsTrustManager(config);
// Standard verification will fail on empty cert chain, triggering our callback
assertDoesNotThrow(() -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA"));
assertTrue(verifierInvoked.get(), "Expected verifier callback to be invoked");
}
@Test
public void testTlsTrustManagerVerifierRejection() {
ConnectionConfig config = new ConnectionConfig("badcert.host", 992);
config.setUseTls(true);
config.setTlsVerifyCert(true);
config.setCertificateVerifier((chain, authType, exception) -> false); // Reject certificate
TlsTrustManager trustManager = new TlsTrustManager(config);
assertThrows(CertificateException.class, () -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA"));
}
@Test
public void testCreateSSLContext() throws Exception {
ConnectionConfig config = new ConnectionConfig("secure.host", 992);
config.setUseTls(true);
SSLContext context = TlsTrustManager.createSSLContext(config);
assertNotNull(context);
assertNotNull(context.getSocketFactory());
}
}