8210695: Create test to cover JDK-8205330 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection
authorxyin
Mon, 17 Sep 2018 13:49:55 +0800
changeset 51760 caac55d48dc3
parent 51759 ac6e9a2ebc04
child 51761 5f868838bc5f
8210695: Create test to cover JDK-8205330 InitialDirContext ctor sometimes throws NPE if the server has sent a disconnection Reviewed-by: vtewari, dfuchs, chegar
test/jdk/com/sun/jndi/ldap/DisconnectNPETest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/DisconnectNPETest.java	Mon Sep 17 13:49:55 2018 +0800
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Hashtable;
+
+/*
+ * @test
+ * @bug 8205330
+ * @summary Test that If a connection has already been established and then
+ *          the LDAP directory server sends an (unsolicited)
+ *          "Notice of Disconnection", make sure client handle it correctly,
+ *          no NPE been thrown.
+ * @run main/othervm DisconnectNPETest
+ */
+
+public class DisconnectNPETest {
+    // Normally the NPE bug should be hit less than 100 times run, but just in
+    // case, we set repeat count to 1000 here.
+    private static final int REPEAT_COUNT = 1000;
+
+    public static void main(String[] args) throws IOException {
+        new DisconnectNPETest().run();
+    }
+
+    private ServerSocket serverSocket;
+    private Hashtable<Object, Object> env;
+    private TestLDAPServer server;
+
+    private void initRes() throws IOException {
+        serverSocket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
+        server = new TestLDAPServer();
+        server.start();
+    }
+
+    private void initTest() {
+        env = new Hashtable<>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY,
+                "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, String.format("ldap://%s:%d/",
+                InetAddress.getLoopbackAddress().getHostName(),
+                serverSocket.getLocalPort()));
+        env.put(Context.SECURITY_AUTHENTICATION, "simple");
+        env.put(Context.SECURITY_PRINCIPAL,
+                "cn=8205330,ou=Client6,ou=Vendor1,o=IMC,c=US");
+        env.put(Context.SECURITY_CREDENTIALS, "secret123");
+    }
+
+    private void run() throws IOException {
+        initRes();
+        initTest();
+        int count = 0;
+        try {
+            while (count < REPEAT_COUNT) {
+                count++;
+                InitialDirContext context = null;
+                try {
+                    context = new InitialDirContext(env);
+                } catch (NamingException ne) {
+                    System.out.println("(" + count + "/" + REPEAT_COUNT
+                            + ") It's ok to get NamingException: " + ne);
+                    // for debug
+                    ne.printStackTrace(System.out);
+                } finally {
+                    cleanupContext(context);
+                }
+            }
+        } finally {
+            System.out.println("Test count: " + count + "/" + REPEAT_COUNT);
+            cleanupTest();
+        }
+    }
+
+    private void cleanupTest() {
+        if (server != null) {
+            server.stopServer();
+        }
+        cleanupClosableRes(serverSocket);
+    }
+
+    private void cleanupContext(DirContext context) {
+        if (context != null) {
+            try {
+                context.close();
+            } catch (NamingException e) {
+                // ignore
+            }
+        }
+    }
+
+    private static void cleanupClosableRes(Closeable res) {
+        if (res != null) {
+            try {
+                res.close();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    class TestLDAPServer extends Thread {
+        private volatile boolean isRunning;
+
+        TestLDAPServer() {
+            isRunning = true;
+        }
+
+        private void stopServer() {
+            isRunning = false;
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (isRunning) {
+                    Socket clientSocket = serverSocket.accept();
+                    Thread handler = new Thread(
+                            new LDAPServerHandler(clientSocket));
+                    handler.start();
+                }
+            } catch (IOException e) {
+                if (isRunning) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    static class LDAPServerHandler implements Runnable {
+        // "Notice of Disconnection" message
+        private static final byte[] DISCONNECT_MSG = { 0x30, 0x4C, 0x02, 0x01,
+                0x00, 0x78, 0x47, 0x0A, 0x01, 0x34, 0x04, 0x00, 0x04, 0x28,
+                0x55, 0x4E, 0x41, 0x56, 0x41, 0x49, 0x4C, 0x41, 0x42, 0x4C,
+                0x45, 0x3A, 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72,
+                0x76, 0x65, 0x72, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x64,
+                0x69, 0x73, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x21,
+                (byte) 0x8A, 0x16, 0x31, 0x2E, 0x33, 0x2E, 0x36, 0x2E, 0x31,
+                0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x31, 0x34, 0x36, 0x36, 0x2E,
+                0x32, 0x30, 0x30, 0x33, 0x36 };
+        private static final byte[] BIND_RESPONSE = { 0x30, 0x0C, 0x02, 0x01,
+                0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 };
+        private final Socket clientSocket;
+
+        private LDAPServerHandler(final Socket clientSocket) {
+            this.clientSocket = clientSocket;
+        }
+
+        @Override
+        public void run() {
+            try (clientSocket;
+                    OutputStream out = clientSocket.getOutputStream();
+                    InputStream in = clientSocket.getInputStream()) {
+                if (in.read() > 0) {
+                    in.skip(in.available());
+                    out.write(BIND_RESPONSE);
+                    out.write(DISCONNECT_MSG);
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}