/** @author [email protected] */
public abstract class ConnectionOrientedMessageChannel extends MessageChannel
    implements SIPMessageListener, Runnable, RawMessageChannel {

  private static StackLogger logger =
      CommonLogger.getLogger(ConnectionOrientedMessageChannel.class);
  protected SIPTransactionStack sipStack;

  protected Socket mySock;

  protected PipelinedMsgParser myParser;

  protected String key;

  protected InputStream myClientInputStream; // just to pass to thread.

  // Set here on initialization to avoid thread leak. See issue 266
  protected boolean isRunning = true;

  protected boolean isCached;

  protected Thread mythread;

  protected String myAddress;

  protected int myPort;

  protected InetAddress peerAddress;

  // This is the port and adress that we will find in the headers of the messages from the peer
  protected int peerPortAdvertisedInHeaders = -1;
  protected String peerAddressAdvertisedInHeaders;

  protected int peerPort;

  protected String peerProtocol;

  private volatile long lastKeepAliveReceivedTime;

  private SIPStackTimerTask pingKeepAliveTimeoutTask;
  private Semaphore keepAliveSemaphore;

  private long keepAliveTimeout;

  public ConnectionOrientedMessageChannel(SIPTransactionStack sipStack) {
    this.sipStack = sipStack;
    this.keepAliveTimeout = sipStack.getReliableConnectionKeepAliveTimeout();
    if (keepAliveTimeout > 0) {
      keepAliveSemaphore = new Semaphore(1);
    }
  }

  /** Returns "true" as this is a reliable transport. */
  public boolean isReliable() {
    return true;
  }

  /** Close the message channel. */
  public void close() {
    close(true, true);
  }

  protected abstract void close(boolean removeSocket, boolean stopKeepAliveTask);

  /**
   * Get my SIP Stack.
   *
   * @return The SIP Stack for this message channel.
   */
  public SIPTransactionStack getSIPStack() {
    return sipStack;
  }

  /**
   * get the address of the client that sent the data to us.
   *
   * @return Address of the client that sent us data that resulted in this channel being created.
   */
  public String getPeerAddress() {
    if (peerAddress != null) {
      return peerAddress.getHostAddress();
    } else return getHost();
  }

  protected InetAddress getPeerInetAddress() {
    return peerAddress;
  }

  public String getPeerProtocol() {
    return this.peerProtocol;
  }

  /**
   * Return a formatted message to the client. We try to re-connect with the peer on the other end
   * if possible.
   *
   * @param sipMessage Message to send.
   * @throws IOException If there is an error sending the message
   */
  public void sendMessage(final SIPMessage sipMessage) throws IOException {

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG) && !sipMessage.isNullRequest()) {
      logger.logDebug(
          "sendMessage:: "
              + sipMessage.getFirstLine()
              + " cseq method = "
              + sipMessage.getCSeq().getMethod());
    }

    for (MessageProcessor messageProcessor : getSIPStack().getMessageProcessors()) {
      if (messageProcessor.getIpAddress().getHostAddress().equals(this.getPeerAddress())
          && messageProcessor.getPort() == this.getPeerPort()
          && messageProcessor.getTransport().equalsIgnoreCase(this.getPeerProtocol())) {
        Runnable processMessageTask =
            new Runnable() {

              public void run() {
                try {
                  processMessage((SIPMessage) sipMessage.clone());
                } catch (Exception ex) {
                  if (logger.isLoggingEnabled(ServerLogger.TRACE_ERROR)) {
                    logger.logError("Error self routing message cause by: ", ex);
                  }
                }
              }
            };
        getSIPStack().getSelfRoutingThreadpoolExecutor().execute(processMessageTask);

        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Self routing message");
        return;
      }
    }

    byte[] msg = sipMessage.encodeAsBytes(this.getTransport());

    long time = System.currentTimeMillis();

    // need to store the peerPortAdvertisedInHeaders in case the response has an rport (ephemeral)
    // that failed to retry on the regular via port
    // for responses, no need to store anything for subsequent requests.
    if (peerPortAdvertisedInHeaders <= 0) {
      if (sipMessage instanceof SIPResponse) {
        SIPResponse sipResponse = (SIPResponse) sipMessage;
        Via via = sipResponse.getTopmostVia();
        if (via.getRPort() > 0) {
          if (via.getPort() <= 0) {
            // if port is 0 we assume the default port for TCP
            this.peerPortAdvertisedInHeaders = 5060;
          } else {
            this.peerPortAdvertisedInHeaders = via.getPort();
          }
          if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                "1.Storing peerPortAdvertisedInHeaders = "
                    + peerPortAdvertisedInHeaders
                    + " for via port = "
                    + via.getPort()
                    + " via rport = "
                    + via.getRPort()
                    + " and peer port = "
                    + peerPort
                    + " for this channel "
                    + this
                    + " key "
                    + key);
          }
        }
      }
    }

    // JvB: also retry for responses, if the connection is gone we should
    // try to reconnect
    this.sendMessage(msg, sipMessage instanceof SIPRequest);

    // message was sent without any exception so let's set set port and
    // address before we feed it to the logger
    sipMessage.setRemoteAddress(this.peerAddress);
    sipMessage.setRemotePort(this.peerPort);
    sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress());
    sipMessage.setLocalPort(this.getPort());

    if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES))
      logMessage(sipMessage, peerAddress, peerPort, time);
  }

  protected abstract void sendMessage(byte[] msg, boolean b) throws IOException;

  public void processMessage(SIPMessage sipMessage, InetAddress address) {
    this.peerAddress = address;
    try {
      processMessage(sipMessage);
    } catch (Exception e) {
      if (logger.isLoggingEnabled(ServerLog.TRACE_ERROR)) {
        logger.logError("ERROR processing self routing", e);
      }
    }
  }

  /**
   * Gets invoked by the parser as a callback on successful message parsing (i.e. no parser errors).
   *
   * @param sipMessage Message to process (this calls the application for processing the message).
   *     <p>Jvb: note that this code is identical to TCPMessageChannel, refactor some day
   */
  public void processMessage(SIPMessage sipMessage) throws Exception {
    try {
      if (sipMessage.getFrom() == null
          || sipMessage.getTo() == null
          || sipMessage.getCallId() == null
          || sipMessage.getCSeq() == null
          || sipMessage.getViaHeaders() == null) {

        if (logger.isLoggingEnabled()) {
          String badmsg = sipMessage.encode();
          logger.logError("bad message " + badmsg);
          logger.logError(">>> Dropped Bad Msg");
        }
        return;
      }

      sipMessage.setRemoteAddress(this.peerAddress);
      sipMessage.setRemotePort(this.getPeerPort());
      sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress());
      sipMessage.setLocalPort(this.getPort());
      // Issue 3: https://telestax.atlassian.net/browse/JSIP-3
      sipMessage.setPeerPacketSourceAddress(this.peerAddress);
      sipMessage.setPeerPacketSourcePort(this.peerPort);

      ViaList viaList = sipMessage.getViaHeaders();
      // For a request
      // first via header tells where the message is coming from.
      // For response, this has already been recorded in the outgoing
      // message.
      if (sipMessage instanceof SIPRequest) {
        Via v = (Via) viaList.getFirst();
        // the peer address and tag it appropriately.
        Hop hop = sipStack.addressResolver.resolveAddress(v.getHop());
        this.peerProtocol = v.getTransport();
        // if(peerPortAdvertisedInHeaders <= 0) {
        int hopPort = v.getPort();
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug(
              "hop port = "
                  + hopPort
                  + " for request "
                  + sipMessage
                  + " for this channel "
                  + this
                  + " key "
                  + key);
        }
        if (hopPort <= 0) {
          // if port is 0 we assume the default port for TCP
          this.peerPortAdvertisedInHeaders = 5060;
        } else {
          this.peerPortAdvertisedInHeaders = hopPort;
        }
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug(
              "3.Storing peerPortAdvertisedInHeaders = "
                  + peerPortAdvertisedInHeaders
                  + " for this channel "
                  + this
                  + " key "
                  + key);
        }
        // }
        // may be needed to reconnect, when diff than peer address
        if (peerAddressAdvertisedInHeaders == null) {
          peerAddressAdvertisedInHeaders = hop.getHost();
          if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
            logger.logDebug(
                "3.Storing peerAddressAdvertisedInHeaders = "
                    + peerAddressAdvertisedInHeaders
                    + " for this channel "
                    + this
                    + " key "
                    + key);
          }
        }

        try {
          if (mySock != null) { // selfrouting makes socket = null
            // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=297
            this.peerAddress = mySock.getInetAddress();
          }
          // Check to see if the received parameter matches
          // the peer address and tag it appropriately.

          // JvB: dont do this. It is both costly and incorrect
          // Must set received also when it is a FQDN, regardless
          // whether
          // it resolves to the correct IP address
          // InetAddress sentByAddress =
          // InetAddress.getByName(hop.getHost());
          // JvB: if sender added 'rport', must always set received
          boolean hasRPort = v.hasParameter(Via.RPORT);
          if (!hasRPort && v.getPort() != peerPort) {
            // https://github.com/RestComm/jain-sip/issues/79
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
              logger.logDebug(
                  "setting rport since viaPort "
                      + v.getPort()
                      + " different than peerPacketSourcePort "
                      + peerPort
                      + " so that the response can be routed back");
            }
            hasRPort = true;
          }
          if (hasRPort || !hop.getHost().equals(this.peerAddress.getHostAddress())) {
            v.setParameter(Via.RECEIVED, this.peerAddress.getHostAddress());
          }
          // @@@ hagai
          // JvB: technically, may only do this when Via already
          // contains
          // rport
          v.setParameter(Via.RPORT, Integer.toString(this.peerPort));
        } catch (java.text.ParseException ex) {
          InternalErrorHandler.handleException(ex);
        }
        // Use this for outgoing messages as well.
        if (!this.isCached && mySock != null) { // self routing makes
          // mySock=null
          // https://jain-sip.dev.java.net/issues/show_bug.cgi?id=297
          this.isCached = true;
          int remotePort = ((java.net.InetSocketAddress) mySock.getRemoteSocketAddress()).getPort();
          String key = IOHandler.makeKey(mySock.getInetAddress(), remotePort);
          if (this.messageProcessor instanceof NioTcpMessageProcessor) {
            // https://java.net/jira/browse/JSIP-475 don't use iohandler in case of NIO
            // communications of the socket will leak in the iohandler sockettable
            ((NioTcpMessageProcessor) this.messageProcessor)
                .nioHandler.putSocket(key, mySock.getChannel());
          } else {
            sipStack.ioHandler.putSocket(key, mySock);
          }
          // since it can close the socket it needs to be after the mySock usage otherwise
          // it the socket will be disconnected and NPE will be thrown in some edge cases
          ((ConnectionOrientedMessageProcessor) this.messageProcessor).cacheMessageChannel(this);
        }
      }

      // Foreach part of the request header, fetch it and process it

      long receptionTime = System.currentTimeMillis();
      //

      if (sipMessage instanceof SIPRequest) {
        // This is a request - process the request.
        SIPRequest sipRequest = (SIPRequest) sipMessage;
        // Create a new sever side request processor for this
        // message and let it handle the rest.

        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug("----Processing Message---");
        }
        if (logger.isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) {

          sipStack.serverLogger.logMessage(
              sipMessage,
              this.getPeerHostPort().toString(),
              this.messageProcessor.getIpAddress().getHostAddress()
                  + ":"
                  + this.messageProcessor.getPort(),
              false,
              receptionTime);
        }
        // Check for reasonable size - reject message
        // if it is too long.
        if (sipStack.getMaxMessageSize() > 0
            && sipRequest.getSize()
                    + (sipRequest.getContentLength() == null
                        ? 0
                        : sipRequest.getContentLength().getContentLength())
                > sipStack.getMaxMessageSize()) {
          SIPResponse sipResponse = sipRequest.createResponse(SIPResponse.MESSAGE_TOO_LARGE);
          byte[] resp = sipResponse.encodeAsBytes(this.getTransport());
          this.sendMessage(resp, false);
          throw new Exception("Message size exceeded");
        }

        String sipVersion = ((SIPRequest) sipMessage).getRequestLine().getSipVersion();
        if (!sipVersion.equals("SIP/2.0")) {
          SIPResponse versionNotSupported =
              ((SIPRequest) sipMessage)
                  .createResponse(Response.VERSION_NOT_SUPPORTED, "Bad SIP version " + sipVersion);
          this.sendMessage(versionNotSupported.encodeAsBytes(this.getTransport()), false);
          throw new Exception("Bad version ");
        }

        String method = ((SIPRequest) sipMessage).getMethod();
        String cseqMethod = ((SIPRequest) sipMessage).getCSeqHeader().getMethod();

        if (!method.equalsIgnoreCase(cseqMethod)) {
          SIPResponse sipResponse = sipRequest.createResponse(SIPResponse.BAD_REQUEST);
          byte[] resp = sipResponse.encodeAsBytes(this.getTransport());
          this.sendMessage(resp, false);
          throw new Exception("Bad CSeq method" + sipMessage + " method " + method);
        }

        // Stack could not create a new server request interface.
        // maybe not enough resources.
        ServerRequestInterface sipServerRequest = sipStack.newSIPServerRequest(sipRequest, this);

        if (sipServerRequest != null) {
          try {
            sipServerRequest.processRequest(sipRequest, this);
          } finally {
            if (sipServerRequest instanceof SIPTransaction) {
              SIPServerTransaction sipServerTx = (SIPServerTransaction) sipServerRequest;
              if (!sipServerTx.passToListener()) ((SIPTransaction) sipServerRequest).releaseSem();
            }
          }
        } else {
          if (sipStack.sipMessageValve
              == null) { // Allow message valves to nullify messages without error
            SIPResponse response = sipRequest.createResponse(Response.SERVICE_UNAVAILABLE);

            RetryAfter retryAfter = new RetryAfter();

            // Be a good citizen and send a decent response code back.
            try {
              retryAfter.setRetryAfter((int) (10 * (Math.random())));
              response.setHeader(retryAfter);
              this.sendMessage(response);
            } catch (Exception e) {
              // IGNore
            }
            if (logger.isLoggingEnabled())
              logger.logWarning("Dropping message -- could not acquire semaphore");
          }
        }
      } else {
        SIPResponse sipResponse = (SIPResponse) sipMessage;
        // JvB: dont do this
        // if (sipResponse.getStatusCode() == 100)
        // sipResponse.getTo().removeParameter("tag");
        try {
          sipResponse.checkHeaders();
        } catch (ParseException ex) {
          if (logger.isLoggingEnabled())
            logger.logError("Dropping Badly formatted response message >>> " + sipResponse);
          return;
        }
        // This is a response message - process it.
        // Check the size of the response.
        // If it is too large dump it silently.
        if (sipStack.getMaxMessageSize() > 0
            && sipResponse.getSize()
                    + (sipResponse.getContentLength() == null
                        ? 0
                        : sipResponse.getContentLength().getContentLength())
                > sipStack.getMaxMessageSize()) {
          if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
            logger.logDebug("Message size exceeded");
          return;
        }

        ServerResponseInterface sipServerResponse =
            sipStack.newSIPServerResponse(sipResponse, this);
        if (sipServerResponse != null) {
          try {
            if (sipServerResponse instanceof SIPClientTransaction
                && !((SIPClientTransaction) sipServerResponse).checkFromTag(sipResponse)) {
              if (logger.isLoggingEnabled())
                logger.logError("Dropping response message with invalid tag >>> " + sipResponse);
              return;
            }

            sipServerResponse.processResponse(sipResponse, this);
          } finally {
            if (sipServerResponse instanceof SIPTransaction
                && !((SIPTransaction) sipServerResponse).passToListener()) {
              // Note that the semaphore is released in event
              // scanner if the
              // request is actually processed by the Listener.
              ((SIPTransaction) sipServerResponse).releaseSem();
            }
          }
        } else {
          logger.logWarning(
              "Application is blocked -- could not acquire semaphore -- dropping response");
        }
      }
    } finally {
    }
  }

  /**
   * This gets invoked when thread.start is called from the constructor. Implements a message loop -
   * reading the tcp connection and processing messages until we are done or the other end has
   * closed.
   */
  public void run() {
    Pipeline hispipe = null;
    // Create a pipeline to connect to our message parser.
    hispipe =
        new Pipeline(
            myClientInputStream, sipStack.readTimeout, ((SIPTransactionStack) sipStack).getTimer());
    // Create a pipelined message parser to read and parse
    // messages that we write out to him.
    myParser = new PipelinedMsgParser(sipStack, this, hispipe, this.sipStack.getMaxMessageSize());
    // Start running the parser thread.
    myParser.processInput();
    // bug fix by Emmanuel Proulx
    int bufferSize = 4096;
    ((ConnectionOrientedMessageProcessor) this.messageProcessor).useCount++;
    this.isRunning = true;
    try {
      while (true) {
        try {
          byte[] msg = new byte[bufferSize];
          int nbytes = myClientInputStream.read(msg, 0, bufferSize);
          // no more bytes to read...
          if (nbytes == -1) {
            hispipe.write("\r\n\r\n".getBytes("UTF-8"));
            try {
              if (sipStack.maxConnections != -1) {
                synchronized (messageProcessor) {
                  ((ConnectionOrientedMessageProcessor) this.messageProcessor).nConnections--;
                  messageProcessor.notify();
                }
              }
              hispipe.close();
              close();
            } catch (IOException ioex) {
            }
            return;
          }

          hispipe.write(msg, 0, nbytes);

        } catch (IOException ex) {
          // Terminate the message.
          try {
            hispipe.write("\r\n\r\n".getBytes("UTF-8"));
          } catch (Exception e) {
            // InternalErrorHandler.handleException(e);
          }

          try {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
              logger.logDebug("IOException closing sock " + ex);
            try {
              if (sipStack.maxConnections != -1) {
                synchronized (messageProcessor) {
                  ((ConnectionOrientedMessageProcessor) this.messageProcessor).nConnections--;
                  messageProcessor.notify();
                }
              }
              close();
              hispipe.close();
            } catch (IOException ioex) {
            }
          } catch (Exception ex1) {
            // Do nothing.
          }
          return;
        } catch (Exception ex) {
          InternalErrorHandler.handleException(ex, logger);
        }
      }
    } finally {
      this.isRunning = false;
      ((ConnectionOrientedMessageProcessor) this.messageProcessor).remove(this);
      ((ConnectionOrientedMessageProcessor) this.messageProcessor).useCount--;
      // parser could be null if the socket was closed by the remote end already
      if (myParser != null) {
        myParser.close();
      }
    }
  }

  protected void uncache() {
    if (isCached && !isRunning) {
      ((ConnectionOrientedMessageProcessor) this.messageProcessor).remove(this);
    }
  }

  /**
   * Get an identifying key. This key is used to cache the connection and re-use it if necessary.
   */
  public String getKey() {
    if (this.key != null) {
      return this.key;
    } else {
      this.key = MessageChannel.getKey(this.peerAddress, this.peerPort, getTransport());
      return this.key;
    }
  }

  /**
   * Get the host to assign to outgoing messages.
   *
   * @return the host to assign to the via header.
   */
  public String getViaHost() {
    return myAddress;
  }

  /**
   * Get the port for outgoing messages sent from the channel.
   *
   * @return the port to assign to the via header.
   */
  public int getViaPort() {
    return myPort;
  }

  /**
   * Get the port of the peer to whom we are sending messages.
   *
   * @return the peer port.
   */
  public int getPeerPort() {
    return peerPort;
  }

  public int getPeerPacketSourcePort() {
    return this.peerPort;
  }

  public InetAddress getPeerPacketSourceAddress() {
    return this.peerAddress;
  }

  /*
   * (non-Javadoc)
   * @see gov.nist.javax.sip.parser.SIPMessageListener#sendSingleCLRF()
   */
  public void sendSingleCLRF() throws Exception {
    lastKeepAliveReceivedTime = System.currentTimeMillis();

    if (mySock != null && !mySock.isClosed()) {
      sendMessage("\r\n".getBytes("UTF-8"), false);
    }

    synchronized (this) {
      if (isRunning) {
        if (keepAliveTimeout > 0) {
          rescheduleKeepAliveTimeout(keepAliveTimeout);
        }
      }
    }
  }

  public void cancelPingKeepAliveTimeoutTaskIfStarted() {
    if (pingKeepAliveTimeoutTask != null && pingKeepAliveTimeoutTask.getSipTimerTask() != null) {
      try {
        keepAliveSemaphore.acquire();
      } catch (InterruptedException e) {
        logger.logError("Couldn't acquire keepAliveSemaphore");
        return;
      }
      try {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug(
              "~~~ cancelPingKeepAliveTimeoutTaskIfStarted for MessageChannel(key="
                  + key
                  + "), clientAddress="
                  + peerAddress
                  + ", clientPort="
                  + peerPort
                  + ", timeout="
                  + keepAliveTimeout
                  + ")");
        }
        sipStack.getTimer().cancel(pingKeepAliveTimeoutTask);
      } finally {
        keepAliveSemaphore.release();
      }
    }
  }

  public void setKeepAliveTimeout(long keepAliveTimeout) {
    if (keepAliveTimeout < 0) {
      cancelPingKeepAliveTimeoutTaskIfStarted();
    }
    if (keepAliveTimeout == 0) {
      keepAliveTimeout = messageProcessor.getSIPStack().getReliableConnectionKeepAliveTimeout();
    }

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug(
          "~~~ setKeepAliveTimeout for MessageChannel(key="
              + key
              + "), clientAddress="
              + peerAddress
              + ", clientPort="
              + peerPort
              + ", timeout="
              + keepAliveTimeout
              + ")");
    }

    this.keepAliveTimeout = keepAliveTimeout;
    if (keepAliveSemaphore == null) {
      keepAliveSemaphore = new Semaphore(1);
    }

    boolean isKeepAliveTimeoutTaskScheduled = pingKeepAliveTimeoutTask != null;
    if (isKeepAliveTimeoutTaskScheduled && keepAliveTimeout > 0) {
      rescheduleKeepAliveTimeout(keepAliveTimeout);
    }
  }

  public long getKeepAliveTimeout() {
    return keepAliveTimeout;
  }

  public void rescheduleKeepAliveTimeout(long newKeepAliveTimeout) {
    //        long now = System.currentTimeMillis();
    //        long lastKeepAliveReceivedTimeOrNow = lastKeepAliveReceivedTime == 0 ? now :
    // lastKeepAliveReceivedTime;
    //
    //        long newScheduledTime =  lastKeepAliveReceivedTimeOrNow + newKeepAliveTimeout;

    StringBuilder methodLog = new StringBuilder();

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      methodLog.append(
          "~~~ rescheduleKeepAliveTimeout for MessageChannel(key="
              + key
              + "), clientAddress="
              + peerAddress
              + ", clientPort="
              + peerPort
              + ", timeout="
              + keepAliveTimeout
              + "): newKeepAliveTimeout=");
      if (newKeepAliveTimeout == Long.MAX_VALUE) {
        methodLog.append("Long.MAX_VALUE");
      } else {
        methodLog.append(newKeepAliveTimeout);
      }
      //            methodLog.append(", lastKeepAliveReceivedTimeOrNow=");
      //            methodLog.append(lastKeepAliveReceivedTimeOrNow);
      //            methodLog.append(", newScheduledTime=");
      //            methodLog.append(newScheduledTime);
    }

    //        long delay = newScheduledTime > now ? newScheduledTime - now : 1;
    try {
      keepAliveSemaphore.acquire();
    } catch (InterruptedException e) {
      logger.logWarning("Couldn't acquire keepAliveSemaphore");
      return;
    }
    try {
      if (pingKeepAliveTimeoutTask == null) {
        pingKeepAliveTimeoutTask = new KeepAliveTimeoutTimerTask();
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          methodLog.append(", scheduling pingKeepAliveTimeoutTask to execute after ");
          methodLog.append(keepAliveTimeout / 1000);
          methodLog.append(" seconds");
          logger.logDebug(methodLog.toString());
        }
        sipStack.getTimer().schedule(pingKeepAliveTimeoutTask, keepAliveTimeout);
      } else {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug(
              "~~~ cancelPingKeepAliveTimeout for MessageChannel(key="
                  + key
                  + "), clientAddress="
                  + peerAddress
                  + ", clientPort="
                  + peerPort
                  + ", timeout="
                  + keepAliveTimeout
                  + ")");
        }
        sipStack.getTimer().cancel(pingKeepAliveTimeoutTask);
        pingKeepAliveTimeoutTask = new KeepAliveTimeoutTimerTask();
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          methodLog.append(", scheduling pingKeepAliveTimeoutTask to execute after ");
          methodLog.append(keepAliveTimeout / 1000);
          methodLog.append(" seconds");
          logger.logDebug(methodLog.toString());
        }
        sipStack.getTimer().schedule(pingKeepAliveTimeoutTask, keepAliveTimeout);
      }
    } finally {
      keepAliveSemaphore.release();
    }
  }

  class KeepAliveTimeoutTimerTask extends SIPStackTimerTask {

    public void runTask() {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug(
            "~~~ Starting processing of KeepAliveTimeoutEvent( "
                + peerAddress.getHostAddress()
                + ","
                + peerPort
                + ")...");
      }
      close(true, true);
      if (sipStack instanceof SipStackImpl) {
        for (Iterator<SipProviderImpl> it = ((SipStackImpl) sipStack).getSipProviders();
            it.hasNext(); ) {
          SipProviderImpl nextProvider = (SipProviderImpl) it.next();
          SipListener sipListener = nextProvider.getSipListener();
          ListeningPoint[] listeningPoints = nextProvider.getListeningPoints();
          for (ListeningPoint listeningPoint : listeningPoints) {
            if (sipListener != null
                && sipListener instanceof SipListenerExt
                // making sure that we don't notify each listening point but only the one on which
                // the timeout happened
                && listeningPoint.getIPAddress().equalsIgnoreCase(myAddress)
                && listeningPoint.getPort() == myPort
                && listeningPoint.getTransport().equalsIgnoreCase(getTransport())) {
              ((SipListenerExt) sipListener)
                  .processIOException(
                      new IOExceptionEventExt(
                          nextProvider,
                          Reason.KeepAliveTimeout,
                          myAddress,
                          myPort,
                          peerAddress.getHostAddress(),
                          peerPort,
                          getTransport()));
            }
          }
        }
      } else {
        SipListener sipListener = sipStack.getSipListener();
        if (sipListener instanceof SipListenerExt) {
          ((SipListenerExt) sipListener)
              .processIOException(
                  new IOExceptionEventExt(
                      this,
                      Reason.KeepAliveTimeout,
                      myAddress,
                      myPort,
                      peerAddress.getHostAddress(),
                      peerPort,
                      getTransport()));
        }
      }
    }
  }
}
/**
 * This is a stack abstraction for TCP connections. This abstracts a stream of parsed messages. The
 * SIP sipStack starts this from the main SIPStack class for each connection that it accepts. It
 * starts a message parser in its own thread and talks to the message parser via a pipe. The message
 * parser calls back via the parseError or processMessage functions that are defined as part of the
 * SIPMessageListener interface.
 *
 * @see gov.nist.javax.sip.parser.PipelinedMsgParser
 * @author M. Ranganathan <br>
 * @version 1.2 $Revision: 1.83 $ $Date: 2010-12-02 22:44:53 $
 */
public class TCPMessageChannel extends ConnectionOrientedMessageChannel {
  private static StackLogger logger = CommonLogger.getLogger(TCPMessageChannel.class);

  protected OutputStream myClientOutputStream;

  protected TCPMessageChannel(SIPTransactionStack sipStack) {
    super(sipStack);
  }

  /**
   * Constructor - gets called from the SIPStack class with a socket on accepting a new client. All
   * the processing of the message is done here with the sipStack being freed up to handle new
   * connections. The sock input is the socket that is returned from the accept. Global data that is
   * shared by all threads is accessible in the Server structure.
   *
   * @param sock Socket from which to read and write messages. The socket is already connected (was
   *     created as a result of an accept).
   * @param sipStack Ptr to SIP Stack
   */
  protected TCPMessageChannel(
      Socket sock,
      SIPTransactionStack sipStack,
      TCPMessageProcessor msgProcessor,
      String threadName)
      throws IOException {

    super(sipStack);
    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("creating new TCPMessageChannel ");
      logger.logStackTrace();
    }
    mySock = sock;
    peerAddress = mySock.getInetAddress();
    myAddress = msgProcessor.getIpAddress().getHostAddress();
    myClientInputStream = mySock.getInputStream();
    myClientOutputStream = mySock.getOutputStream();
    mythread = new Thread(this);
    mythread.setDaemon(true);
    mythread.setName(threadName);
    this.peerPort = mySock.getPort();
    this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP");

    this.myPort = msgProcessor.getPort();
    // Bug report by Vishwashanti Raj Kadiayl
    super.messageProcessor = msgProcessor;
    // Can drop this after response is sent potentially.
    mythread.start();
  }

  /**
   * Constructor - connects to the given inet address. Acknowledgement -- Lamine Brahimi (IBM
   * Zurich) sent in a bug fix for this method. A thread was being uncessarily created.
   *
   * @param inetAddr inet address to connect to.
   * @param sipStack is the sip sipStack from which we are created.
   * @throws IOException if we cannot connect.
   */
  protected TCPMessageChannel(
      InetAddress inetAddr,
      int port,
      SIPTransactionStack sipStack,
      TCPMessageProcessor messageProcessor)
      throws IOException {

    super(sipStack);
    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("creating new TCPMessageChannel ");
      logger.logStackTrace();
    }
    this.peerAddress = inetAddr;
    this.peerPort = port;
    this.myPort = messageProcessor.getPort();
    this.peerProtocol = "TCP";
    this.myAddress = messageProcessor.getIpAddress().getHostAddress();
    // Bug report by Vishwashanti Raj Kadiayl
    this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP");
    super.messageProcessor = messageProcessor;
  }

  /** Close the message channel. */
  public void close(boolean removeSocket, boolean stopKeepAliveTask) {
    isRunning = false;
    // we need to close everything because the socket may be closed by the other end
    // like in LB scenarios sending OPTIONS and killing the socket after it gets the response
    if (mySock != null) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Closing socket " + key);
      try {
        mySock.close();
        mySock = null;
      } catch (IOException ex) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
          logger.logDebug("Error closing socket " + ex);
      }
    }
    if (myParser != null) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
        logger.logDebug("Closing my parser " + myParser);
      myParser.close();
    }
    // no need to close myClientInputStream since myParser.close() above will do it
    if (myClientOutputStream != null) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
        logger.logDebug("Closing client output stream " + myClientOutputStream);
      try {
        myClientOutputStream.close();
      } catch (IOException ex) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
          logger.logDebug("Error closing client output stream" + ex);
      }
    }
    if (removeSocket) {
      // remove the "tcp:" part of the key to cleanup the ioHandler hashmap
      String ioHandlerKey = key.substring(4);
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
        logger.logDebug("Closing TCP socket " + ioHandlerKey);
      // Issue 358 : remove socket and semaphore on close to avoid leaking
      sipStack.ioHandler.removeSocket(ioHandlerKey);
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("Closing message Channel (key = " + key + ")" + this);
      }

      cancelPingKeepAliveTimeoutTaskIfStarted();

    } else {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        String ioHandlerKey = key.substring(4);
        logger.logDebug(
            "not removing socket key from the cached map since it has already been updated by the iohandler.sendBytes "
                + ioHandlerKey);
      }
    }
  }

  /**
   * get the transport string.
   *
   * @return "tcp" in this case.
   */
  public String getTransport() {
    return "TCP";
  }

  /**
   * Send message to whoever is connected to us. Uses the topmost via address to send to.
   *
   * @param msg is the message to send.
   * @param isClient
   */
  protected synchronized void sendMessage(byte[] msg, boolean isClient) throws IOException {

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("sendMessage isClient  = " + isClient);
    }

    Socket sock = null;
    IOException problem = null;
    try {
      sock =
          this.sipStack.ioHandler.sendBytes(
              this.messageProcessor.getIpAddress(),
              this.peerAddress,
              this.peerPort,
              this.peerProtocol,
              msg,
              isClient,
              this);
    } catch (IOException any) {
      problem = any;
      logger.logWarning(
          "Failed to connect "
              + this.peerAddress
              + ":"
              + this.peerPort
              + " but trying the advertised port="
              + this.peerPortAdvertisedInHeaders
              + " if it's different than the port we just failed on");
    }
    if (sock
        == null) { // http://java.net/jira/browse/JSIP-362 If we couldn't connect to the host, try
                   // the advertised host and port as failsafe
      if (peerAddressAdvertisedInHeaders != null && peerPortAdvertisedInHeaders > 0) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "Couldn't connect to peerAddress = "
                  + peerAddress
                  + " peerPort = "
                  + peerPort
                  + " key = "
                  + key
                  + " retrying on peerPortAdvertisedInHeaders "
                  + peerPortAdvertisedInHeaders);
        }
        InetAddress address = InetAddress.getByName(peerAddressAdvertisedInHeaders);
        sock =
            this.sipStack.ioHandler.sendBytes(
                this.messageProcessor.getIpAddress(),
                address,
                this.peerPortAdvertisedInHeaders,
                this.peerProtocol,
                msg,
                isClient,
                this);
        this.peerPort = this.peerPortAdvertisedInHeaders;
        this.peerAddress = address;
        this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP");
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "retry suceeded to peerAddress = "
                  + peerAddress
                  + " peerPortAdvertisedInHeaders = "
                  + peerPortAdvertisedInHeaders
                  + " key = "
                  + key);
        }
      } else {
        throw problem; // throw the original excpetion we had from the first attempt
      }
    }

    // Created a new socket so close the old one and stick the new
    // one in its place but dont do this if it is a datagram socket.
    // (could have replied via udp but received via tcp!).
    // if (mySock == null && s != null) {
    // this.uncache();
    // } else
    if (sock != mySock && sock != null) {
      if (mySock != null) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning("Old socket different than new socket on channel " + key);
          logger.logStackTrace();
          logger.logWarning("Old socket local ip address " + mySock.getLocalSocketAddress());
          logger.logWarning("Old socket remote ip address " + mySock.getRemoteSocketAddress());
          logger.logWarning("New socket local ip address " + sock.getLocalSocketAddress());
          logger.logWarning("New socket remote ip address " + sock.getRemoteSocketAddress());
        }
        close(false, false);
      }
      if (problem == null) {
        if (mySock != null) {
          if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
            logger.logWarning(
                "There was no exception for the retry mechanism so creating a new thread based on the new socket for incoming "
                    + key);
          }
        }
        mySock = sock;
        this.myClientInputStream = mySock.getInputStream();
        this.myClientOutputStream = mySock.getOutputStream();
        Thread thread = new Thread(this);
        thread.setDaemon(true);
        thread.setName("TCPMessageChannelThread");
        thread.start();
      } else {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "There was an exception for the retry mechanism so not creating a new thread based on the new socket for incoming "
                  + key);
        }
        mySock = sock;
      }
    }
  }

  /**
   * Send a message to a specified address.
   *
   * @param message Pre-formatted message to send.
   * @param receiverAddress Address to send it to.
   * @param receiverPort Receiver port.
   * @throws IOException If there is a problem connecting or sending.
   */
  public synchronized void sendMessage(
      byte message[], InetAddress receiverAddress, int receiverPort, boolean retry)
      throws IOException {
    if (message == null || receiverAddress == null)
      throw new IllegalArgumentException("Null argument");

    if (peerPortAdvertisedInHeaders <= 0) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug(
            "receiver port = " + receiverPort + " for this channel " + this + " key " + key);
      }
      if (receiverPort <= 0) {
        // if port is 0 we assume the default port for TCP
        this.peerPortAdvertisedInHeaders = 5060;
      } else {
        this.peerPortAdvertisedInHeaders = receiverPort;
      }
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug(
            "2.Storing peerPortAdvertisedInHeaders = "
                + peerPortAdvertisedInHeaders
                + " for this channel "
                + this
                + " key "
                + key);
      }
    }

    Socket sock = null;
    IOException problem = null;
    try {
      sock =
          this.sipStack.ioHandler.sendBytes(
              this.messageProcessor.getIpAddress(),
              receiverAddress,
              receiverPort,
              "TCP",
              message,
              retry,
              this);
    } catch (IOException any) {
      problem = any;
      logger.logWarning(
          "Failed to connect "
              + this.peerAddress
              + ":"
              + receiverPort
              + " but trying the advertised port="
              + this.peerPortAdvertisedInHeaders
              + " if it's different than the port we just failed on");
      logger.logError("Error is ", any);
    }
    if (sock
        == null) { // http://java.net/jira/browse/JSIP-362 If we couldn't connect to the host, try
                   // the advertised host:port as failsafe
      if (peerAddressAdvertisedInHeaders != null && peerPortAdvertisedInHeaders > 0) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "Couldn't connect to receiverAddress = "
                  + receiverAddress
                  + " receiverPort = "
                  + receiverPort
                  + " key = "
                  + key
                  + " retrying on peerPortAdvertisedInHeaders "
                  + peerPortAdvertisedInHeaders);
        }
        InetAddress address = InetAddress.getByName(peerAddressAdvertisedInHeaders);
        sock =
            this.sipStack.ioHandler.sendBytes(
                this.messageProcessor.getIpAddress(),
                address,
                this.peerPortAdvertisedInHeaders,
                "TCP",
                message,
                retry,
                this);
        this.peerPort = this.peerPortAdvertisedInHeaders;
        this.peerAddress = address;
        this.key = MessageChannel.getKey(peerAddress, peerPort, "TCP");
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "retry suceeded to peerAddress = "
                  + peerAddress
                  + " peerPort = "
                  + peerPort
                  + " key = "
                  + key);
        }
      } else {
        throw problem; // throw the original excpetion we had from the first attempt
      }
    }

    if (sock != mySock && sock != null) {
      if (mySock != null) {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning("Old socket different than new socket on channel " + key);
          logger.logStackTrace();
          logger.logWarning("Old socket local ip address " + mySock.getLocalSocketAddress());
          logger.logWarning("Old socket remote ip address " + mySock.getRemoteSocketAddress());
          logger.logWarning("New socket local ip address " + sock.getLocalSocketAddress());
          logger.logWarning("New socket remote ip address " + sock.getRemoteSocketAddress());
        }
        close(false, false);
      }
      if (problem == null) {
        if (mySock != null) {
          if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
            logger.logWarning(
                "There was no exception for the retry mechanism so creating a new thread based on the new socket for incoming "
                    + key);
          }
        }
        mySock = sock;
        this.myClientInputStream = mySock.getInputStream();
        this.myClientOutputStream = mySock.getOutputStream();
        // start a new reader on this end of the pipe.
        Thread mythread = new Thread(this);
        mythread.setDaemon(true);
        mythread.setName("TCPMessageChannelThread");
        mythread.start();
      } else {
        if (logger.isLoggingEnabled(LogWriter.TRACE_WARN)) {
          logger.logWarning(
              "There was an exception for the retry mechanism so not creating a new thread based on the new socket for incoming "
                  + key);
        }
        mySock = sock;
      }
    }
  }

  /**
   * Exception processor for exceptions detected from the parser. (This is invoked by the parser
   * when an error is detected).
   *
   * @param sipMessage -- the message that incurred the error.
   * @param ex -- parse exception detected by the parser.
   * @param header -- header that caused the error.
   * @throws ParseException Thrown if we want to reject the message.
   */
  public void handleException(
      ParseException ex, SIPMessage sipMessage, Class hdrClass, String header, String message)
      throws ParseException {
    if (logger.isLoggingEnabled()) logger.logException(ex);
    // Log the bad message for later reference.
    if ((hdrClass != null)
        && (hdrClass.equals(From.class)
            || hdrClass.equals(To.class)
            || hdrClass.equals(CSeq.class)
            || hdrClass.equals(Via.class)
            || hdrClass.equals(CallID.class)
            || hdrClass.equals(ContentLength.class)
            || hdrClass.equals(RequestLine.class)
            || hdrClass.equals(StatusLine.class))) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("Encountered Bad Message \n" + sipMessage.toString());
      }

      // JvB: send a 400 response for requests (except ACK)
      // Currently only UDP, @todo also other transports
      String msgString = sipMessage.toString();
      if (!msgString.startsWith("SIP/") && !msgString.startsWith("ACK ")) {
        if (mySock != null) {
          if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) {
            logger.logError("Malformed mandatory headers: closing socket! :" + mySock.toString());
          }

          try {
            mySock.close();

          } catch (IOException ie) {
            if (logger.isLoggingEnabled(LogWriter.TRACE_ERROR)) {
              logger.logError(
                  "Exception while closing socket! :" + mySock.toString() + ":" + ie.toString());
            }
          }
        }
      }

      throw ex;
    } else {
      sipMessage.addUnparsed(header);
    }
  }

  /**
   * Equals predicate.
   *
   * @param other is the other object to compare ourselves to for equals
   */
  public boolean equals(Object other) {

    if (!this.getClass().equals(other.getClass())) return false;
    else {
      TCPMessageChannel that = (TCPMessageChannel) other;
      if (this.mySock != that.mySock) return false;
      else return true;
    }
  }

  /** TCP Is not a secure protocol. */
  public boolean isSecure() {
    return false;
  }
}
public class NioTlsMessageProcessor extends NioTcpMessageProcessor {

  private static StackLogger logger = CommonLogger.getLogger(NioTlsMessageProcessor.class);

  // Create a trust manager that does not validate certificate chains
  TrustManager[] trustAllCerts =
      new TrustManager[] {
        new X509TrustManager() {
          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
          }

          public void checkClientTrusted(X509Certificate[] certs, String authType) {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
              logger.logDebug(
                  "checkClientTrusted : Not validating certs " + certs + " authType " + authType);
            }
          }

          public void checkServerTrusted(X509Certificate[] certs, String authType) {
            if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
              logger.logDebug(
                  "checkServerTrusted : Not validating certs " + certs + " authType " + authType);
            }
          }
        }
      };

  SSLContext sslServerCtx;
  SSLContext sslClientCtx;

  public NioTlsMessageProcessor(InetAddress ipAddress, SIPTransactionStack sipStack, int port) {
    super(ipAddress, sipStack, port);
    transport = "TLS";
    try {
      init();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public NioTcpMessageChannel createMessageChannel(
      NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel client) throws IOException {
    return NioTlsMessageChannel.create(NioTlsMessageProcessor.this, client);
  }

  @Override
  public MessageChannel createMessageChannel(HostPort targetHostPort) throws IOException {
    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("NioTlsMessageProcessor::createMessageChannel: " + targetHostPort);
    }
    NioTlsMessageChannel retval = null;
    try {
      String key = MessageChannel.getKey(targetHostPort, "TLS");

      if (messageChannels.get(key) != null) {
        retval = (NioTlsMessageChannel) this.messageChannels.get(key);
        return retval;
      } else {
        retval =
            new NioTlsMessageChannel(
                targetHostPort.getInetAddress(), targetHostPort.getPort(), sipStack, this);

        //	retval.getSocketChannel().register(selector, SelectionKey.OP_READ);
        synchronized (messageChannels) {
          this.messageChannels.put(key, retval);
        }
        retval.isCached = true;
        if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
          logger.logDebug("key " + key);
          logger.logDebug("Creating " + retval);
        }
        selector.wakeup();
        return retval;
      }
    } finally {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("MessageChannel::createMessageChannel - exit " + retval);
      }
    }
  }

  @Override
  public MessageChannel createMessageChannel(InetAddress targetHost, int port) throws IOException {
    String key = MessageChannel.getKey(targetHost, port, "TLS");
    if (messageChannels.get(key) != null) {
      return this.messageChannels.get(key);
    } else {
      NioTlsMessageChannel retval = new NioTlsMessageChannel(targetHost, port, sipStack, this);

      selector.wakeup();
      //           retval.getSocketChannel().register(selector, SelectionKey.OP_READ);
      this.messageChannels.put(key, retval);
      retval.isCached = true;
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("key " + key);
        logger.logDebug("Creating " + retval);
      }
      return retval;
    }
  }

  public void init() throws Exception, CertificateException, FileNotFoundException, IOException {
    if (sipStack.securityManagerProvider.getKeyManagers(false) == null
        || sipStack.securityManagerProvider.getTrustManagers(false) == null
        || sipStack.securityManagerProvider.getTrustManagers(true) == null) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("TLS initialization failed due to NULL security config");
      }
      return; // The settings
    }

    sslServerCtx = SSLContext.getInstance("TLS");
    sslClientCtx = SSLContext.getInstance("TLS");

    if (sipStack.getClientAuth() == ClientAuthType.DisabledAll) {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug(
            "ClientAuth " + sipStack.getClientAuth() + " bypassing all cert validations");
      }
      sslServerCtx.init(
          sipStack.securityManagerProvider.getKeyManagers(false), trustAllCerts, null);
      sslClientCtx.init(sipStack.securityManagerProvider.getKeyManagers(true), trustAllCerts, null);
    } else {
      if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
        logger.logDebug("ClientAuth " + sipStack.getClientAuth());
      }
      sslServerCtx.init(
          sipStack.securityManagerProvider.getKeyManagers(false),
          sipStack.securityManagerProvider.getTrustManagers(false),
          null);
      sslClientCtx.init(
          sipStack.securityManagerProvider.getKeyManagers(true),
          sipStack.securityManagerProvider.getTrustManagers(true),
          null);
    }
  }
}
public class NioTlsWebSocketMessageChannel extends NioWebSocketMessageChannel
    implements NioTlsChannelInterface {

  private static StackLogger logger = CommonLogger.getLogger(NioTlsWebSocketMessageChannel.class);

  SSLStateMachine sslStateMachine;

  private int appBufferMax;
  private int netBufferMax;

  public static NioTlsWebSocketMessageChannel create(
      NioTlsWebSocketMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel)
      throws IOException {
    NioTlsWebSocketMessageChannel retval =
        (NioTlsWebSocketMessageChannel) channelMap.get(socketChannel);
    if (retval == null) {
      retval = new NioTlsWebSocketMessageChannel(nioTcpMessageProcessor, socketChannel);
      channelMap.put(socketChannel, retval);
    }
    return retval;
  }

  protected NioTlsWebSocketMessageChannel(
      NioTcpMessageProcessor nioTcpMessageProcessor, SocketChannel socketChannel)
      throws IOException {
    super(nioTcpMessageProcessor, socketChannel);

    messageProcessor = nioTcpMessageProcessor;
    myClientInputStream = socketChannel.socket().getInputStream();
    try {
      this.init(false);
      createBuffers();
    } catch (Exception e) {
      throw new IOException("Can't do TLS init", e);
    }
  }

  public void init(boolean clientMode)
      throws Exception, CertificateException, FileNotFoundException, IOException {
    SSLContext ctx = ((NioTlsWebSocketMessageProcessor) messageProcessor).sslServerCtx;
    sslStateMachine = new SSLStateMachine(ctx.createSSLEngine(), this);

    sslStateMachine.sslEngine.setUseClientMode(false);
    String auth =
        ((SipStackImpl) super.sipStack)
            .getConfigurationProperties()
            .getProperty("gov.nist.javax.sip.TLS_CLIENT_AUTH_TYPE");

    sslStateMachine.sslEngine.setNeedClientAuth(false);
    sslStateMachine.sslEngine.setWantClientAuth(false);

    String clientProtocols =
        ((SipStackImpl) super.sipStack)
            .getConfigurationProperties()
            .getProperty("gov.nist.javax.sip.TLS_CLIENT_PROTOCOLS");
    if (clientProtocols != null) {
      sslStateMachine.sslEngine.setEnabledProtocols(clientProtocols.split(","));
    }
  }

  public ByteBuffer prepareEncryptedDataBuffer() {
    return ByteBufferFactory.getInstance().allocateDirect(netBufferMax);
  }

  public ByteBuffer prepareAppDataBuffer() {
    return ByteBufferFactory.getInstance().allocateDirect(appBufferMax);
  }

  public static class SSLReconnectedException extends IOException {
    private static final long serialVersionUID = 1L;
  }

  @Override
  protected void sendMessage(final byte[] msg, final boolean isClient) throws IOException {
    checkSocketState();

    ByteBuffer b = ByteBuffer.wrap(msg);
    try {
      sslStateMachine.wrap(
          b,
          ByteBufferFactory.getInstance().allocateDirect(netBufferMax),
          new MessageSendCallback() {

            @Override
            public void doSend(byte[] bytes) throws IOException {

              NioTlsWebSocketMessageChannel.super.sendMessage(bytes, isClient);
            }
          });
    } catch (Exception e) {
      throw new IOException("Can't send message", e);
    }
  }

  public void sendEncryptedData(byte[] msg) throws IOException {
    // bypass the encryption for already encrypted data or TLS metadata
    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug(
          "sendEncryptedData "
              + " this = "
              + this
              + " peerPort = "
              + peerPort
              + " addr = "
              + peerAddress);
    }
    lastActivityTimeStamp = System.currentTimeMillis();

    NIOHandler nioHandler = ((NioTcpMessageProcessor) messageProcessor).nioHandler;
    if (this.socketChannel != null
        && this.socketChannel.isConnected()
        && this.socketChannel.isOpen()) {
      nioHandler.putSocket(NIOHandler.makeKey(this.peerAddress, this.peerPort), this.socketChannel);
    }
    super.sendNonWebSocketMessage(msg, false);
    // super.sendMessage(msg, this.peerAddress, this.peerPort, true);
  }

  @Override
  public void sendMessage(
      final byte message[],
      final InetAddress receiverAddress,
      final int receiverPort,
      final boolean retry)
      throws IOException {
    checkSocketState();

    ByteBuffer b = ByteBuffer.wrap(message);
    try {
      sslStateMachine.wrap(
          b,
          ByteBufferFactory.getInstance().allocateDirect(netBufferMax),
          new MessageSendCallback() {

            @Override
            public void doSend(byte[] bytes) throws IOException {
              NioTlsWebSocketMessageChannel.super.sendTCPMessage(
                  bytes, receiverAddress, receiverPort, retry);
            }
          });
    } catch (IOException e) {
      throw e;
    }
  }

  public void sendHttpMessage(
      final byte message[],
      final InetAddress receiverAddress,
      final int receiverPort,
      final boolean retry)
      throws IOException {
    checkSocketState();

    ByteBuffer b = ByteBuffer.wrap(message);
    try {
      sslStateMachine.wrap(
          b,
          ByteBufferFactory.getInstance().allocateDirect(netBufferMax),
          new MessageSendCallback() {

            @Override
            public void doSend(byte[] bytes) throws IOException {
              NioTlsWebSocketMessageChannel.super.sendMessage(
                  bytes, receiverAddress, receiverPort, retry);
            }
          });
    } catch (IOException e) {
      throw e;
    }
  }

  private void createBuffers() {

    SSLSession session = sslStateMachine.sslEngine.getSession();
    appBufferMax = session.getApplicationBufferSize();
    netBufferMax = session.getPacketBufferSize();

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("appBufferMax=" + appBufferMax + " netBufferMax=" + netBufferMax);
    }
  }

  public NioTlsWebSocketMessageChannel(
      InetAddress inetAddress,
      int port,
      SIPTransactionStack sipStack,
      NioTcpMessageProcessor nioTcpMessageProcessor)
      throws IOException {
    super(inetAddress, port, sipStack, nioTcpMessageProcessor);
    try {
      init(true);
      createBuffers();
    } catch (Exception e) {
      throw new IOException("Can't init the TLS channel", e);
    }
  }

  @Override
  protected void addBytes(byte[] bytes) throws Exception {
    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("Adding WSS bytes for decryption " + bytes.length);
    }
    if (bytes.length <= 0) return;
    ByteBuffer buffer = ByteBuffer.wrap(bytes);
    sslStateMachine.unwrap(buffer);
  }

  @Override
  protected void sendNonWebSocketMessage(byte[] msg, boolean isClient) throws IOException {

    if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
      logger.logDebug("sendMessage isClient  = " + isClient + " this = " + this);
    }
    lastActivityTimeStamp = System.currentTimeMillis();

    NIOHandler nioHandler = ((NioTcpMessageProcessor) messageProcessor).nioHandler;
    if (this.socketChannel != null
        && this.socketChannel.isConnected()
        && this.socketChannel.isOpen()) {
      nioHandler.putSocket(NIOHandler.makeKey(this.peerAddress, this.peerPort), this.socketChannel);
    }
    sendMessage(msg, this.peerAddress, this.peerPort, isClient);
  }

  @Override
  public String getTransport() {
    return "WSS";
  }

  @Override
  public void onNewSocket(byte[] message) {
    super.onNewSocket(message);
    try {
      if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG)) {
        String last = null;
        if (message != null) {
          last = new String(message, "UTF-8");
        }
        logger.logDebug("New socket for " + this + " last message = " + last);
      }
      init(true);
      createBuffers();
      sendMessage(message, false);
    } catch (Exception e) {
      logger.logError("Cant reinit", e);
    }
  }

  private void checkSocketState() throws IOException {
    if (socketChannel != null && (!socketChannel.isConnected() || !socketChannel.isOpen())) {
      if (logger.isLoggingEnabled(LogLevels.TRACE_DEBUG))
        logger.logDebug("Need to reset SSL engine for socket " + socketChannel);
      try {
        init(sslStateMachine.sslEngine.getUseClientMode());
      } catch (Exception ex) {
        logger.logError("Cannot reset SSL engine", ex);
        throw new IOException(ex);
      }
    }
  }

  @Override
  public boolean isSecure() {
    return true;
  }

  @Override
  public void addPlaintextBytes(byte[] bytes) throws Exception {
    super.addBytes(bytes);
  }
}
/**
 * Implementation of the SipCache interface, backed by an Infinispan Cache
 *
 * <p>The configuration of Infinispan Cache can be set through the following Restcomm SIP Stack
 * property : <b>org.mobicents.ha.javax.sip.INFINISPAN_CACHE_CONFIG_PATH</b>
 *
 * <p>If there is an already existing Infinispan CacheManager instance to be used, then it can be
 * plugged in by specifying its JNDI name through the following property:
 * <b>org.mobicents.ha.javax.sip.INFINISPAN_CACHEMANAGER_JNDI_NAME</b>
 *
 * <p>If neither the Infinispan cache configuration path property, nor the CacheManager JNDI name
 * are specified, then a default Infinispan config will be used, which can be found at:
 * <b>META-INF/cache-configuration.xml</b>
 *
 * @author <A HREF="mailto:[email protected]">Gergely Posfai</A>
 * @author <A HREF="mailto:[email protected]">Andras Kokuti</A>
 */
public class InfinispanCache implements SipCache {

  public static final String INFINISPAN_CACHE_CONFIG_PATH =
      "org.mobicents.ha.javax.sip.INFINISPAN_CACHE_CONFIG_PATH";
  public static final String DEFAULT_FILE_CONFIG_PATH = "META-INF/cache-configuration.xml";
  public static final String INFINISPAN_CACHEMANAGER_JNDI_NAME =
      "org.mobicents.ha.javax.sip.INFINISPAN_CACHEMANAGER_JNDI_NAME";
  private static StackLogger clusteredlogger = CommonLogger.getLogger(InfinispanCache.class);

  private ScheduledThreadPoolExecutor executor = null;
  private Properties configProperties = null;
  private ClusteredSipStack stack;
  private Cache<String, Object> dialogs;
  private Cache<String, Object> appDataMap;
  private Cache<String, Object> serverTransactions;
  private Cache<String, Object> serverTransactionsApp;
  private Cache<String, Object> clientTransactions;
  private Cache<String, Object> clientTransactionsApp;

  private CacheContainer cm;

  private SIPDialogCacheData dialogCacheData;
  private SIPServerTransactionCacheData serverTXCacheData;
  private SIPClientTransactionCacheData clientTXCacheData;

  public SIPDialog getDialog(String dialogId) throws SipCacheException {
    if (dialogId == null) throw new SipCacheException("No dialogId");

    if (dialogCacheData != null) return dialogCacheData.getDialog(dialogId);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public void putDialog(SIPDialog dialog) throws SipCacheException {
    if (dialog == null) throw new SipCacheException("SipDialog is null");

    if (dialogCacheData != null) dialogCacheData.putDialog(dialog);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public void updateDialog(SIPDialog dialog) throws SipCacheException {
    if (dialog == null) throw new SipCacheException("SipDialog is null");

    if (dialogCacheData != null) dialogCacheData.updateDialog(dialog);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public void removeDialog(String dialogId) throws SipCacheException {
    if (dialogId == null) throw new SipCacheException("No dialogId");

    if (dialogCacheData != null) {
      dialogCacheData.removeDialog(dialogId);
    } else {
      throw new SipCacheException("No SIPClientTransactionCache");
    }
  }

  public void evictDialog(String dialogId) {
    if (dialogCacheData != null) dialogCacheData.evictDialog(dialogId);
  }

  public SIPClientTransaction getClientTransaction(String txId) throws SipCacheException {
    if (clientTXCacheData != null) return clientTXCacheData.getClientTransaction(txId);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public void putClientTransaction(SIPClientTransaction clientTransaction)
      throws SipCacheException {
    if (clientTXCacheData != null) clientTXCacheData.putClientTransaction(clientTransaction);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public void removeClientTransaction(String txId) throws SipCacheException {
    if (clientTXCacheData != null) clientTXCacheData.removeClientTransaction(txId);
    else throw new SipCacheException("No SIPClientTransactionCache");
  }

  public SIPServerTransaction getServerTransaction(String txId) throws SipCacheException {
    if (serverTXCacheData != null) return serverTXCacheData.getServerTransaction(txId);
    else throw new SipCacheException("No SIPServerTransactionCache");
  }

  public void putServerTransaction(SIPServerTransaction serverTransaction)
      throws SipCacheException {
    if (serverTXCacheData != null) serverTXCacheData.putServerTransaction(serverTransaction);
    else throw new SipCacheException("No SIPServerTransactionCache");
  }

  public void removeServerTransaction(String txId) throws SipCacheException {
    if (serverTXCacheData != null) serverTXCacheData.removeServerTransaction(txId);
    else throw new SipCacheException("No SIPServerTransactionCache");
  }

  public void init() throws SipCacheException {

    executor = new ScheduledThreadPoolExecutor(1);

    final String configurationPath =
        configProperties.getProperty(INFINISPAN_CACHE_CONFIG_PATH, DEFAULT_FILE_CONFIG_PATH);

    if (configProperties.containsKey(INFINISPAN_CACHEMANAGER_JNDI_NAME)) {
      if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) {
        clusteredlogger.logInfo(
            INFINISPAN_CACHEMANAGER_JNDI_NAME
                + " specified, trying to load Inifinispan CacheManager from JNDI "
                + configProperties.getProperty(INFINISPAN_CACHEMANAGER_JNDI_NAME));
      }
      executor.scheduleAtFixedRate(
          new Runnable() {

            static final int MAX_ATTEMPTS = 30;
            int attempts = 0;

            public void run() {
              attempts++;
              // Init Infinispan CacheManager
              if (configProperties.containsKey(INFINISPAN_CACHEMANAGER_JNDI_NAME)) {
                try {
                  InitialContext context = new InitialContext();
                  String cacheManagerJndiName =
                      configProperties.getProperty(INFINISPAN_CACHEMANAGER_JNDI_NAME);
                  cm = (CacheContainer) context.lookup(cacheManagerJndiName);
                  if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) {
                    clusteredlogger.logInfo(
                        "Found Inifinispan CacheManager: cacheManagerJndiName \""
                            + cacheManagerJndiName
                            + "\" "
                            + cm
                            + " after attempts "
                            + attempts);
                  }
                  executor.remove(this);
                  executor.shutdown();
                } catch (NamingException e) {
                  // Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or
                  // lookup failed
                  if (attempts > MAX_ATTEMPTS) {
                    clusteredlogger.logError(
                        "Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or lookup failed after attempts "
                            + attempts
                            + " stopping there",
                        e);
                    executor.remove(this);
                    executor.shutdown();
                  } else {
                    if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) {
                      clusteredlogger.logInfo(
                          "Inifinispan CacheManager JNDI lookup failed: could not get InitialContext or lookup failed after attempts "
                              + attempts
                              + ", retrying every second");
                    }
                  }
                  return;
                }
              }

              setupCacheStructures();

              if (dialogCacheData != null) {
                dialogCacheData.setDialogs(dialogs);
                dialogCacheData.setAppDataMap(appDataMap);
              }
              if (serverTXCacheData != null) {
                serverTXCacheData.setServerTransactions(serverTransactions);
                serverTXCacheData.setServerTransactionsApp(serverTransactionsApp);
              }
              if (clientTXCacheData != null) {
                clientTXCacheData.setClientTransactions(clientTransactions);
                clientTXCacheData.setClientTransactionsApp(clientTransactionsApp);
              }
            }
          },
          0,
          1,
          TimeUnit.SECONDS);
    } else {
      if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) {
        clusteredlogger.logInfo(
            INFINISPAN_CACHEMANAGER_JNDI_NAME
                + " not specified, trying to load Inifinispan CacheManager from configuration file "
                + configurationPath);
      }
      try {
        if (cm == null) {
          cm = CacheManagerHolder.getManager(configurationPath);
          if (clusteredlogger.isLoggingEnabled(LogLevels.TRACE_INFO)) {
            clusteredlogger.logInfo(
                "Found Inifinispan CacheManager: configuration file from path \""
                    + configurationPath
                    + "\" "
                    + cm);
          }
        }
        setupCacheStructures();
      } catch (IOException e) {
        clusteredlogger.logError(
            "Failed to init Inifinispan CacheManager: could not read configuration file from path \""
                + configurationPath
                + "\"",
            e);
      }

      if (dialogCacheData != null) {
        dialogCacheData.setDialogs(dialogs);
        dialogCacheData.setAppDataMap(appDataMap);
      }
      if (serverTXCacheData != null) {
        serverTXCacheData.setServerTransactions(serverTransactions);
        serverTXCacheData.setServerTransactionsApp(serverTransactionsApp);
      }
      if (clientTXCacheData != null) {
        clientTXCacheData.setClientTransactions(clientTransactions);
        clientTXCacheData.setClientTransactionsApp(clientTransactionsApp);
      }
    }
  }

  private void setupCacheStructures() {
    InfinispanCacheListener dialogsCacheListener = new InfinispanCacheListener(stack);

    dialogs = cm.getCache("cache.dialogs");
    appDataMap = cm.getCache("cache.appdata");
    serverTransactions = cm.getCache("cache.serverTX");
    serverTransactionsApp = cm.getCache("cache.serverTXApp");
    clientTransactions = cm.getCache("cache.clientTX");
    clientTransactionsApp = cm.getCache("cache.clientTXApp");

    dialogs.addListener(dialogsCacheListener);
  }

  public void start() throws SipCacheException {
    dialogCacheData = new SIPDialogCacheData(stack, dialogs, appDataMap);
    serverTXCacheData =
        new SIPServerTransactionCacheData(stack, serverTransactions, serverTransactionsApp);
    clientTXCacheData =
        new SIPClientTransactionCacheData(stack, clientTransactions, clientTransactionsApp);
  }

  public void stop() throws SipCacheException {
    clientTXCacheData = null;
    serverTXCacheData = null;
    dialogCacheData = null;
  }

  public void setConfigurationProperties(Properties configurationProperties) {
    this.configProperties = configurationProperties;
  }

  public boolean inLocalMode() {
    return false;
  }

  public void setClusteredSipStack(ClusteredSipStack clusteredStack) {
    stack = clusteredStack;
  }

  public CacheContainer getCacheManager() {
    return cm;
  }
}