gt:         if (ldr == null) {
context: /*
   * Copyright (c) 1999, 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.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * 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.
   */
  
  package com.sun.jndi.ldap;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InterruptedIOException;
  import java.io.OutputStream;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.net.Socket;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.util.Arrays;
  
  import javax.naming.CommunicationException;
  import javax.naming.InterruptedNamingException;
  import javax.naming.NamingException;
  import javax.naming.ServiceUnavailableException;
  import javax.naming.ldap.Control;
  import javax.net.ssl.SSLParameters;
  import javax.net.ssl.SSLSocket;
  
  import sun.misc.IOUtils;
  
  /**
    * A thread that creates a connection to an LDAP server.
    * After the connection, the thread reads from the connection.
    * A caller can invoke methods on the instance to read LDAP responses
    * and to send LDAP requests.
    * <p>
    * There is a one-to-one correspondence between an LdapClient and
    * a Connection. Access to Connection and its methods is only via
    * LdapClient with two exceptions: SASL authentication and StartTLS.
    * SASL needs to access Connection's socket IO streams (in order to do encryption
    * of the security layer). StartTLS needs to do replace IO streams
    * and close the IO  streams on nonfatal close. The code for SASL
    * authentication can be treated as being the same as from LdapClient
    * because the SASL code is only ever called from LdapClient, from
    * inside LdapClient's synchronized authenticate() method. StartTLS is called
    * directly by the application but should only occur when the underlying
    * connection is quiet.
    * <p>
    * In terms of synchronization, worry about data structures
    * used by the Connection thread because that usage might contend
    * with calls by the main threads (i.e., those that call LdapClient).
    * Main threads need to worry about contention with each other.
    * Fields that Connection thread uses:
    *     inStream - synced access and update; initialized in constructor;
    *           referenced outside class unsync'ed (by LdapSasl) only
    *           when connection is quiet
    *     traceFile, traceTagIn, traceTagOut - no sync; debugging only
    *     parent - no sync; initialized in constructor; no updates
    *     pendingRequests - sync
    *     pauseLock - per-instance lock;
    *     paused - sync via pauseLock (pauseReader())
    * Members used by main threads (LdapClient):
    *     host, port - unsync; read-only access for StartTLS and debug messages
    *     setBound(), setV3() - no sync; called only by LdapClient.authenticate(),
    *             which is a sync method called only when connection is "quiet"
    *     getMsgId() - sync
    *     writeRequest(), removeRequest(),findRequest(), abandonOutstandingReqs() -
    *             access to shared pendingRequests is sync
    *     writeRequest(),  abandonRequest(), ldapUnbind() - access to outStream sync
    *     cleanup() - sync
    *     readReply() - access to sock sync
    *     unpauseReader() - (indirectly via writeRequest) sync on pauseLock
    * Members used by SASL auth (main thread):
    *     inStream, outStream - no sync; used to construct new stream; accessed
    *             only when conn is "quiet" and not shared
    *     replaceStreams() - sync method
    * Members used by StartTLS:
    *     inStream, outStream - no sync; used to record the existing streams;
    *             accessed only when conn is "quiet" and not shared
    *     replaceStreams() - sync method
    * <p>
    * Handles anonymous, simple, and SASL bind for v3; anonymous and simple
    * for v2.
    * %%% made public for access by LdapSasl %%%
    *
    * @author Vincent Ryan
    * @author Rosanna Lee
    * @author Jagane Sundar
    */
  public final class Connection implements Runnable {
  
      private static final boolean debug = false;
      private static final int dump = 0; // > 0 r, > 1 rw
  
  
      final private Thread worker;    // Initialized in constructor
  
      private boolean v3 = true;       // Set in setV3()
  
      final public String host;  // used by LdapClient for generating exception messages
                           // used by StartTlsResponse when creating an SSL socket
      final public int port;     // used by LdapClient for generating exception messages
                           // used by StartTlsResponse when creating an SSL socket
  
      private boolean bound = false;   // Set in setBound()
  
      // All three are initialized in constructor and read-only afterwards
      private OutputStream traceFile = null;
      private String traceTagIn = null;
      private String traceTagOut = null;
  
      // Initialized in constructor; read and used externally (LdapSasl);
      // Updated in replaceStreams() during "quiet", unshared, period
      public InputStream inStream;   // must be public; used by LdapSasl
  
      // Initialized in constructor; read and used externally (LdapSasl);
      // Updated in replaceOutputStream() during "quiet", unshared, period
      public OutputStream outStream; // must be public; used by LdapSasl
  
      // Initialized in constructor; read and used externally (TLS) to
      // get new IO streams; closed during cleanup
      public Socket sock;            // for TLS
  
      // For processing "disconnect" unsolicited notification
      // Initialized in constructor
      final private LdapClient parent;
  
      // Incremented and returned in sync getMsgId()
      private int outMsgId = 0;
  
      //
      // The list of ldapRequests pending on this binding
      //
      // Accessed only within sync methods
      private LdapRequest pendingRequests = null;
  
      volatile IOException closureReason = null;
      volatile boolean useable = true;  // is Connection still useable
  
      int readTimeout;
      int connectTimeout;
      private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED
              = hostnameVerificationDisabledValue();
  
      private static boolean hostnameVerificationDisabledValue() {
          PrivilegedAction<String> act = () -> System.getProperty(
                  "com.sun.jndi.ldap.object.disableEndpointIdentification");
          String prop = AccessController.doPrivileged(act);
          if (prop == null) {
              return false;
          }
          return prop.isEmpty() ? true : Boolean.parseBoolean(prop);
      }
      // true means v3; false means v2
      // Called in LdapClient.authenticate() (which is synchronized)
      // when connection is "quiet" and not shared; no need to synchronize
      void setV3(boolean v) {
          v3 = v;
      }
  
      // A BIND request has been successfully made on this connection
      // When cleaning up, remember to do an UNBIND
      // Called in LdapClient.authenticate() (which is synchronized)
      // when connection is "quiet" and not shared; no need to synchronize
      void setBound() {
          bound = true;
      }
  
      ////////////////////////////////////////////////////////////////////////////
      //
      // Create an LDAP Binding object and bind to a particular server
      //
      ////////////////////////////////////////////////////////////////////////////
  
      Connection(LdapClient parent, String host, int port, String socketFactory,
          int connectTimeout, int readTimeout, OutputStream trace) throws NamingException {
  
          this.host = host;
          this.port = port;
          this.parent = parent;
          this.readTimeout = readTimeout;
          this.connectTimeout = connectTimeout;
  
          if (trace != null) {
              traceFile = trace;
              traceTagIn = "<- " + host + ":" + port + "\n\n";
              traceTagOut = "-> " + host + ":" + port + "\n\n";
          }
  
          //
          // Connect to server
          //
          try {
              sock = createSocket(host, port, socketFactory, connectTimeout);
  
              if (debug) {
                  System.err.println("Connection: opening socket: " + host + "," + port);
              }
  
              inStream = new BufferedInputStream(sock.getInputStream());
              outStream = new BufferedOutputStream(sock.getOutputStream());
  
          } catch (InvocationTargetException e) {
              Throwable realException = e.getTargetException();
              // realException.printStackTrace();
  
              CommunicationException ce =
                  new CommunicationException(host + ":" + port);
              ce.setRootCause(realException);
              throw ce;
          } catch (Exception e) {
              // Class.forName() seems to do more error checking
              // and will throw IllegalArgumentException and such.
              // That's why we need to have a catch all here and
              // ignore generic exceptions.
              // Also catches all IO errors generated by socket creation.
              CommunicationException ce =
                  new CommunicationException(host + ":" + port);
              ce.setRootCause(e);
              throw ce;
          }
  
          worker = Obj.helper.createThread(this);
          worker.setDaemon(true);
          worker.start();
      }
  
      /*
       * Create an InetSocketAddress using the specified hostname and port number.
       */
      private Object createInetSocketAddress(String host, int port)
              throws NoSuchMethodException {
  
          try {
              Class<?> inetSocketAddressClass =
                  Class.forName("java.net.InetSocketAddress");
  
              Constructor<?> inetSocketAddressCons =
                  inetSocketAddressClass.getConstructor(new Class<?>[]{
                  String.class, int.class});
  
              return inetSocketAddressCons.newInstance(new Object[]{
                  host, new Integer(port)});
  
          } catch (ClassNotFoundException |
                   InstantiationException |
                   InvocationTargetException |
                   IllegalAccessException e) {
              throw new NoSuchMethodException();
  
          }
      }
  
      /*
       * Create a Socket object using the specified socket factory and time limit.
       *
       * If a timeout is supplied and unconnected sockets are supported then
       * an unconnected socket is created and the timeout is applied when
       * connecting the socket. If a timeout is supplied but unconnected sockets
       * are not supported then the timeout is ignored and a connected socket
       * is created.
       */
      private Socket createSocket(String host, int port, String socketFactory,
              int connectTimeout) throws Exception {
  
          Socket socket = null;
  
          if (socketFactory != null) {
  
              // create the factory
  
              Class<?> socketFactoryClass = Obj.helper.loadClass(socketFactory);
              Method getDefault =
                  socketFactoryClass.getMethod("getDefault", new Class<?>[]{});
              Object factory = getDefault.invoke(null, new Object[]{});
  
              // create the socket
  
              Method createSocket = null;
  
              if (connectTimeout > 0) {
  
                  try {
                      createSocket = socketFactoryClass.getMethod("createSocket",
                          new Class<?>[]{});
  
                      Method connect = Socket.class.getMethod("connect",
                          new Class<?>[]{Class.forName("java.net.SocketAddress"),
                          int.class});
                      Object endpoint = createInetSocketAddress(host, port);
  
                      // unconnected socket
                      socket =
                          (Socket)createSocket.invoke(factory, new Object[]{});
  
                      if (debug) {
                          System.err.println("Connection: creating socket with " +
                              "a timeout using supplied socket factory");
                      }
  
                      // connected socket
                      connect.invoke(socket, new Object[]{
                          endpoint, new Integer(connectTimeout)});
  
                  } catch (NoSuchMethodException e) {
                      // continue (but ignore connectTimeout)
                  }
              }
  
              if (socket == null) {
                  createSocket = socketFactoryClass.getMethod("createSocket",
                      new Class<?>[]{String.class, int.class});
  
                  if (debug) {
                      System.err.println("Connection: creating socket using " +
                          "supplied socket factory");
                  }
                  // connected socket
                  socket = (Socket) createSocket.invoke(factory,
                      new Object[]{host, new Integer(port)});
              }
          } else {
  
              if (connectTimeout > 0) {
  
                  try {
                      Constructor<Socket> socketCons =
                          Socket.class.getConstructor(new Class<?>[]{});
  
                      Method connect = Socket.class.getMethod("connect",
                          new Class<?>[]{Class.forName("java.net.SocketAddress"),
                          int.class});
                      Object endpoint = createInetSocketAddress(host, port);
  
                      socket = socketCons.newInstance(new Object[]{});
  
                      if (debug) {
                          System.err.println("Connection: creating socket with " +
                              "a timeout");
                      }
                      connect.invoke(socket, new Object[]{
                          endpoint, new Integer(connectTimeout)});
  
                  } catch (NoSuchMethodException e) {
                      // continue (but ignore connectTimeout)
                  }
              }
  
              if (socket == null) {
                  if (debug) {
                      System.err.println("Connection: creating socket");
                  }
                  // connected socket
                  socket = new Socket(host, port);
              }
          }
  
          // For LDAP connect timeouts on LDAP over SSL connections must treat
          // the SSL handshake following socket connection as part of the timeout.
          // So explicitly set a socket read timeout, trigger the SSL handshake,
          // then reset the timeout.
          if (socket instanceof SSLSocket) {
              SSLSocket sslSocket = (SSLSocket) socket;
              if (!IS_HOSTNAME_VERIFICATION_DISABLED) {
                  SSLParameters param = sslSocket.getSSLParameters();
                  param.setEndpointIdentificationAlgorithm("LDAPS");
                  sslSocket.setSSLParameters(param);
              }
              if (connectTimeout > 0) {
                  int socketTimeout = sslSocket.getSoTimeout();
                  sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value
                  sslSocket.startHandshake();
                  sslSocket.setSoTimeout(socketTimeout);
              }
          }
          return socket;
      }
  
      ////////////////////////////////////////////////////////////////////////////
      //
      // Methods to IO to the LDAP server
      //
      ////////////////////////////////////////////////////////////////////////////
  
      synchronized int getMsgId() {
          return ++outMsgId;
      }
  
      LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException {
          return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1);
      }
  
      LdapRequest writeRequest(BerEncoder ber, int msgId,
          boolean pauseAfterReceipt) throws IOException {
          return writeRequest(ber, msgId, pauseAfterReceipt, -1);
      }
  
      LdapRequest writeRequest(BerEncoder ber, int msgId,
          boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException {
  
          LdapRequest req =
              new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity);
          addRequest(req);
  
          if (traceFile != null) {
              Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen());
          }
  
  
          // unpause reader so that it can get response
          // NOTE: Must do this before writing request, otherwise might
          // create a race condition where the writer unblocks its own response
          unpauseReader();
  
          if (debug) {
              System.err.println("Writing request to: " + outStream);
          }
  
          try {
              synchronized (this) {
                  outStream.write(ber.getBuf(), 0, ber.getDataLen());
                  outStream.flush();
              }
          } catch (IOException e) {
              cleanup(null, true);
              throw (closureReason = e); // rethrow
          }
  
          return req;
      }
  
      /**
       * Reads a reply; waits until one is ready.
       */
      BerDecoder readReply(LdapRequest ldr)
              throws IOException, NamingException {
          BerDecoder rber;
  
          // Track down elapsed time to workaround spurious wakeups
          long elapsedMilli = 0;
          long elapsedNano = 0;
  
          while (((rber = ldr.getReplyBer()) == null) &&
                  (readTimeout <= 0 || elapsedMilli < readTimeout))
          {
              try {
                  // If socket closed, don't even try
                  synchronized (this) {
                      if (sock == null) {
                          throw new ServiceUnavailableException(host + ":" + port +
                              "; socket closed");
                      }
                  }
                  synchronized (ldr) {
                      // check if condition has changed since our last check
                      rber = ldr.getReplyBer();
                      if (rber == null) {
                          if (readTimeout > 0) {  // Socket read timeout is specified
                              long beginNano = System.nanoTime();
  
                              // will be woken up before readTimeout if reply is
                              // available
                              ldr.wait(readTimeout - elapsedMilli);
                              elapsedNano += (System.nanoTime() - beginNano);
                              elapsedMilli += elapsedNano / 1000_000;
                              elapsedNano %= 1000_000;
  
                          } else {
                              // no timeout is set so we wait infinitely until
                              // a response is received
                              // https://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP
                              ldr.wait();
                          }
                      } else {
                          break;
                      }
                  }
              } catch (InterruptedException ex) {
                  throw new InterruptedNamingException(
                      "Interrupted during LDAP operation");
              }
          }
  
          if ((rber == null) && (elapsedMilli >= readTimeout)) {
              abandonRequest(ldr, null);
              throw new NamingException("LDAP response read timed out, timeout used:"
                              + readTimeout + "ms." );
  
          }
          return rber;
      }
  
  
      ////////////////////////////////////////////////////////////////////////////
      //
      // Methods to add, find, delete, and abandon requests made to server
      //
      ////////////////////////////////////////////////////////////////////////////
  
      private synchronized void addRequest(LdapRequest ldapRequest) {
  
          LdapRequest ldr = pendingRequests;
  