docs: @Override
      public int discriminate(VirtualConnection vc, Object discrimData) {
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "discriminate");
          }
  
          // Check for null discrimData
          if (discrimData == null) {
              // Can't handle this condition.
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Received null discrim data.  Returning NO from discriminator.");
              }
              if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                  Tr.exit(tc, "discriminate");
              }
              return Discriminator.NO;
          }
  
          // Get data read in on device side. Note, it will always be an array of one element.
          WsByteBuffer netBuffer = ((WsByteBuffer[]) discrimData)[0];
          if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
              Tr.event(tc, "netBuffer: " + SSLUtils.getBufferTraceInfo(netBuffer));
          }
  
          // Check for empty discrimData
          if (0 == netBuffer.position()) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Received empty discrim data.  Returning MAYBE from discriminator.");
              }
              if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                  Tr.exit(tc, "discriminate");
              }
              // No data to discriminate on.
              return Discriminator.MAYBE;
          }
  
          // Ensure initialize has taken place first.
          if (!this.isInitialized) {
              try {
                  init();
              } catch (Exception e) {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Exception caught while getting SSL connection link: " + e);
                  }
                  FFDCFilter.processException(e, getClass().getName(), "148", this, new Object[] { vc });
                  if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                      Tr.exit(tc, "discriminate");
                  }
                  return Discriminator.NO;
              }
          }
  
          // Initial position of networkBuffer.
          int initialNetBufPosition = netBuffer.position();
          // Initial limit of networkBuffer.
          int initialNetBufLimit = netBuffer.limit();
          // Unencrypted buffer from the ssl engine output to be handed up to the application.
          WsByteBuffer decryptedNetBuffer = null;
          // State map from the VC.
          Map<Object, Object> stateMap = vc.getStateMap();
          // The SSL engine.
          SSLEngine sslEngine = null;
          // The SSL context for this connection.
          SSLContext vcSSLContext = null;
          // Result output from the SSL engine.
          SSLEngineResult sslResult = null;
          // Result from discrimination.
          int result = Discriminator.YES;
          // State saved from potentially former call to discriminator for this connection.
          SSLDiscriminatorState discState = null;
  
          try {
              // Prepare the networkBuffer for the call to unwrap. Align data between pos and lim.
              netBuffer.flip();
              // Determine if this connection has already been through discrimination once.
              discState = (SSLDiscriminatorState) stateMap.get(SSL_DISCRIMINATOR_STATE);
              if (discState == null) {
                  // Create the sslContext based on the virtual connection.
                  vcSSLContext = getSSLContextForInboundLink(null, vc);
                  // This is the first call to discriminate. Build a new SSL engine for this connection.
                  sslEngine = SSLUtils.getSSLEngine(vcSSLContext, FlowType.INBOUND,
                                                    (SSLLinkConfig) vc.getStateMap().get(SSLConnectionLink.LINKCONFIG),
                                                    (SSLConnectionLink) getConnectionLink(vc));
                  // Line up all the buffers needed for a call to unwrap.
                  decryptedNetBuffer = SSLUtils.allocateByteBuffer(sslEngine.getSession().getApplicationBufferSize(),
                                                                   getConfig().getDecryptBuffersDirect());
              } else {
                  // This is NOT the first call to discriminate. Extract the ssl engine and context.
                  sslEngine = discState.getEngine();
                  vcSSLContext = discState.getSSLContext();
                  // Extract output buffer used during discrimination.
                  decryptedNetBuffer = discState.getDecryptedNetBuffer();
              }
  
              // Note: Can't call handshake code because we can't write to the client. Unwrap discrim data.
              if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                  Tr.event(tc, "before unwrap: \r\n\tnetBuf: " + SSLUtils.getBufferTraceInfo(netBuffer)
                               + "\r\n\tdecNetBuf: " + SSLUtils.getBufferTraceInfo(decryptedNetBuffer));
              }
  
              // Note, net and decNet buffers will always be one buffer for discrimination.
              // Protect JSSE from potential SSL packet sizes that are too big.
              int savedLimit = SSLUtils.adjustBufferForJSSE(netBuffer,
                                                            sslEngine.getSession().getPacketBufferSize());
  
              // Have the SSL engine inspect the first packet.
              sslResult = sslEngine.unwrap(netBuffer.getWrappedByteBuffer(),
                                           decryptedNetBuffer.getWrappedByteBuffer());
              // we should be in a handshake stage so I don't expect bytes to be
              // produced, but if there are, then the buffer needs to flip
              if (0 < sslResult.bytesProduced()) {
                  decryptedNetBuffer.flip();
              }
  
              // If adjustments were made for the JSSE, restore them.
              if (-1 != savedLimit) {
                  netBuffer.limit(savedLimit);
              }
  
              if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                  Tr.event(tc, "after unwrap: \r\n\tnetBuf: " + SSLUtils.getBufferTraceInfo(netBuffer)
                               + "\r\n\tdecNetBuf: " + SSLUtils.getBufferTraceInfo(decryptedNetBuffer)
                               + "\r\n\tstatus=" + sslResult.getStatus()
                               + " HSstatus=" + sslResult.getHandshakeStatus()
                               + " consumed=" + sslResult.bytesConsumed()
                               + " produced=" + sslResult.bytesProduced());
              }
  
              if (sslResult.getStatus() == Status.BUFFER_UNDERFLOW) {
                  // More data needed to make decision.
                  result = Discriminator.MAYBE;
              } else {
                  result = Discriminator.YES;
                  if (netBuffer.remaining() == 0) {
                      netBuffer.clear();
                  }
              }
          } catch (Throwable t) {
              // No FFDC needed. Some exceptions expected and handled elsewhere.
              // Input was not SSL.
              result = Discriminator.NO;
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Caught Exception during discriminate: " + t);
              }
          }
  
          // Take appropriate action based on result to be returned to caller.
          switch (result) {
              case Discriminator.YES: {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Discriminator returning YES");
                  }
                  // Save discriminator state for the ready method.
                  // Note, if this isn't the first call to discriminator for this connection, the SSL engine and
                  // decrypted network buffer will already be there. For speed, use single method and all for
                  // redundant setting of engine and buffer.
                  // Save the position and limit of networkBuffer since we must return with an unmodified buffer.
                  // Note, the networkBuffer is reset at the bottom of this method.
                  if (discState == null) {
                      discState = new SSLDiscriminatorState();
                  }
                  discState.updateState(vcSSLContext, sslEngine, sslResult,
                                        decryptedNetBuffer, netBuffer.position(), netBuffer.limit());
                  stateMap.put(SSL_DISCRIMINATOR_STATE, discState);
                  break;
              }
              case Discriminator.NO: {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Discriminator returning NO");
                  }
                  // If this wasn't the first time discriminate was called, clean up the state map.
                  if (discState != null) {
                      // Remove reference to discriminator state.
                      stateMap.remove(SSL_DISCRIMINATOR_STATE);
                  }
                  if (null != sslEngine) {
                      // PK13349 - close the discrimination engine
                      closeEngine(sslEngine);
                  }
                  if (null != decryptedNetBuffer) {
                      // Release the unwrap output buffer back to the pool.
                      if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                          Tr.event(tc, "Releasing decryptedNetworkBuffer");
                      }
                      decryptedNetBuffer.release();
                      decryptedNetBuffer = null;
                  }
                  break;
              }
              default: { // Discriminator.MAYBE
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Discriminator returning MAYBE");
                  }
                  // If this is the first time discrimination has run, save the SSL Engine and output buffer in the VC
                  // Otherwise, the references are already there.
                  if (discState == null) {
                      // Note no need to update position and limit since they weren't modified by unwrap.
                      discState = new SSLDiscriminatorState();
                      discState.updateState(vcSSLContext, sslEngine, sslResult,
                                            decryptedNetBuffer, netBuffer.position(), netBuffer.limit());
                      stateMap.put(SSL_DISCRIMINATOR_STATE, discState);
                  }
                  break;
              }
          }
          // Reset the position and limit of networkBuffer, required by discrimination process.
          netBuffer.limit(initialNetBufLimit);
          netBuffer.position(initialNetBufPosition);
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "discriminate");
          }
          return result;
      }