import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class Connection {

    private static final Logger LOG = Logger.getLogger(Connection.class.getName());

    public final static String[] TLS_PROTOCOLS = {"TLSv1.3"};
    public final static String[] TLS_CIPHER_SUITES = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};

    public static void main(final String[] args) throws Exception {
        // to keep this dummy code simple
        System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");

        // a trust manager to trust in all servers and reject all clients
        TrustManager[] trusts = new TrustManager[]{
                new X509TrustManager() {

                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        throw new CertificateException("Not trusted.");
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                        return; // trusted
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        };

        // start a SSL socket to show the issue
        {
            // a dummy SSL certificate
            String cert = "MIACAQMwgAYJKoZIhvcNAQcBoIAkgASCA+gwgDCABgkqhkiG9w0BBwGggASCAxQwggMQMIIDDAYLKoZIhvcNAQwKAQKgggKz"
                    + "MIICrzApBgoqhkiG9w0BDAEDMBsEFOkb3cZjQYPLL8m+Js1LLgyA2vtDAgMAyAAEggKAA/GVS98QC7vDu8LQUe1SYOXNx/Ie26V0"
                    + "taV/CbImGvz8Hrhc7J6Nf0D3GB8WTkI60SRZ7UGnc+7oO1v+3yqzMDRSo0dtRKgqYuISN4sL2q2yyFKkGNVBDyBHN9KQ3K+iprwL"
                    + "/ShuqvxMfK9aVlOJiAmigFr7oysx71u5NkMxCZFJyk9kJZgjvASKO2eddWuIA5B7UDKxvu18abDqBFBZnUBj3Wpmgxz4/j5tCMEa"
                    + "4Wp7dhDBxcN69Mztc9cSsS9SGHt8BAjBmSdummcVS09vx3LEQCdWOT7M7I5bchlsL9fSgyJMiJ9TVZen3exIXc78dprHA3fIF3ze"
                    + "jfge6hzPq5Mua618l4hK4P8HO1oxQ+n81tcc6XWekK5PLp13PMMwrDlNxnlxV+hU9/uhwGE8izGdmfZUeI7n70UsZXnn0DVQHMgt"
                    + "+CZlUEuyYHu2pubLvNRnVURW3Glo8XbD9a7jPW7mhVlj4ZqShxXDgq57GGBgr6Dn12XpXAjrHRYQQUbOCftmn9BgI0JdhuVFErF9"
                    + "u9q0U52FWKYI4DMFVgWaxYa4hr7grck5ipS5Q62D8G3iWLgJFXe2yo9/XXusAkAYzi2IvmCNaHyxmfV3UR9MBTSlblNvpAvdhSAM"
                    + "Xg2znCelZKDaCJ4mQ0v6OBAJ/2ntIyqkwH3BH1Pf78hnJ+vFsUggcby6WPxgb+IpEtI2NyIAX+x6F5/4INeNszxVksBzXLIWI0D9"
                    + "wwSdUKRB33o1HytYvRXk6GBzEymGUg5DIM5OYtJQa8bEvYI4Zw7qaLvNMDxB46UGXoINhQiGOwgLVmZYojirMOQAIIowj0ZAgifj"
                    + "yFSFniSoVy7/wHVuHxjB8zFGMB8GCSqGSIb3DQEJFDESHhAAaQBkAGUAbgB0AGkAdAB5MCMGCSqGSIb3DQEJFTEWBBT76rmH2Qg7"
                    + "P9M2MAzr8XKx9922ggAAAAAwgAYJKoZIhvcNAQcGoIAwgAIBADCABgkqhkiG9w0BBwEwKQYKKoZIhvcNAQwBBjAbBBSb6VFGFYLQ"
                    + "que5J6P4rnITdJkUUwIDAMgAgIICgLeHrleGCloetqri6X72rHaB3aM4ZyuWAIVXr+RZTPTZZPigpvocv4r0PPCwCpMVoUWijmga"
                    + "C1IoascVCmU3qGKt1ORRtaTuMdZskh/84t4lhbyMFUlgsxBPiqNnPPdqoAfUY0wtsk7go4kDBIICH+Y8E4i3vS5ea2Ah1XVnTcp1"
                    + "o68Rj1baX8/WbiiEYajDVMcQ4Y+8W/XdHGCha1+X4jSL52V8uXRX86R8KfTb3SJVDV+NUnC0AJ7YBs7PfDxBSBszyO51ZaF8rQfn"
                    + "2J44aDoQkrU5ALw/4H4sTpnvj86VivMCOYW0F8hjHMkLk1YZsroZQzjmA9HEylc0zb6+Xnv83PHv7y4B+AKSS0kfSiIulGkJZO2A"
                    + "MsJt6cFPW8faHpzMXobu8QLyGbYBNeddJ+lPfuxy2PZrsvewA6Ush8I5wo4sPj/QqQ/fchCmURx7pcyyWRSPQoeuAjVCXW8oTyLQ"
                    + "2dhpYNKVrNl+5mF1eT51H1ULy6oUqfYkcqxbrkNK777AeJTp/JwGRVRr4f4THnBLS3FP4d0ab0unXI0HUygvFNaBAW0v//zHtCF9"
                    + "79b8hOx2vrbnUca6SzJApgVy3w68l/QaW/gFA41YTM0OIhud9GbtBns3U7Qkel0v3v25oDCkHTYzjJ6y7eORXNud7kTCqlICKZYL"
                    + "DTstIqAe+wWsy2Ae80HteWWuzArStsA0lUC1e8hkRhZd8GkOEjXW4ujDPQS0tfVpSASBIM5coao2+Vyk9AJ2KUM5iomKrdAcQdAw"
                    + "QkvvqKs51k4IrlJ3fCj3m7r8VWmELK9rcbWikSDYWPly5QLa+gR5ZXzIQtxbOzRhBSxB64RuTXURZK6jVLFr3YXUAAAAAAAAAAAA"
                    + "AAAAAAAAADA+MCEwCQYFKw4DAhoFAAQUnYwoKFFWZmoSCoSDJRRhGo5jeDUEFAgLJTnMBHs+vhoFesnZBojOCjELAgMBkAAAAA==";
            char[] password = "dummy".toCharArray();

            {
                KeyStore keyStore = KeyStore.getInstance("PKCS12");

                keyStore.load(new ByteArrayInputStream(Base64.getDecoder().decode(cert)), password);

                KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

                factory.init(keyStore, password);

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(factory.getKeyManagers(), trusts, null);

                SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
                SSLServerSocket socket;

                socket = (SSLServerSocket) socketFactory.createServerSocket(4567);
                socket.setNeedClientAuth(true);
                socket.setEnabledProtocols(TLS_PROTOCOLS);
                socket.setEnabledCipherSuites(TLS_CIPHER_SUITES);

                new Thread(() -> {
                    // try to read the incomming connections (this is doomed, we trust no client)
                    try ( var s = socket) {
                        while (true) {
                            Socket cnn = s.accept();

                            try {
                                cnn.getInputStream().read();

                                // should not happen, we do not trust the client
                                System.exit(666);
                            } catch (SSLException e) {
                                cnn.close();
                            }
                        }
                    } catch (IOException ex) {
                        Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }).start();
            }
        }

        // start to send requests to the socket
        {
            SSLContext sslContext = SSLContext.getInstance("TLS");

            sslContext.init(null, trusts, null);

            HttpClient client = HttpClient
                    .newBuilder()
                    .sslContext(sslContext)
                    .connectTimeout(Duration.ofSeconds(2))
                    .build();

            byte[] data = new byte[8 * 1024];

            // the issue does not occur with out a BodyPublisher set
            HttpRequest request = HttpRequest
                    .newBuilder(URI.create("https://localhost:4567"))
                    .method("POST", HttpRequest.BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(data)))
                    .timeout(Duration.ofSeconds(4)).build();

            // start to spin, to provoke the issue
            for (int i = 0; i < 1000; i++) {
                try {
                    // send the request
                    client.send(request, HttpResponse.BodyHandlers.ofByteArray());
                } catch (IOException e) {
                    // we expect a failure here
                    Throwable cause = e.getCause();

                    // get the root cause of the exception
                    if (cause.getCause() != null) {
                        cause = cause.getCause();
                    }

                    // we expect a SSLException, if so continue to spin
                    if (cause instanceof SSLException) {
                        LOG.info(i + ": got the expected " + cause.toString());
                        continue;
                    }

                    // we are sending the same request and the dummy server is stateless
                    // why is there a different exception now?
                    throw new IllegalStateException( i + ": why is there no SSLException as root cause?", e);
                }
            }
        }
    }
} 