/*
   * Simple check to make sure everything came across as expected.
   */
  private static void checkTransfer(ByteBuffer a, ByteBuffer b) throws Exception {
    a.flip();
    b.flip();

    if (!a.equals(b)) {
      throw new Exception("Data didn't transfer cleanly");
    } else {
      log("\tData transferred cleanly");
    }

    a.position(a.limit());
    b.position(b.limit());
    a.limit(a.capacity());
    b.limit(b.capacity());
  }
  /*
   * Constructor for a secure ChannelIO variant.
   */
  protected ChannelIOSecure(SocketChannel sc, boolean blocking, SSLContext sslc)
      throws IOException {
    super(sc, blocking);

    /*
     * We're a server, so no need to use host/port variant.
     *
     * The first call for a server is a NEED_UNWRAP.
     */
    sslEngine = sslc.createSSLEngine();
    sslEngine.setUseClientMode(false);
    initialHSStatus = HandshakeStatus.NEED_UNWRAP;
    initialHSComplete = false;

    // Create a buffer using the normal expected packet size we'll
    // be getting.  This may change, depending on the peer's
    // SSL implementation.
    netBBSize = sslEngine.getSession().getPacketBufferSize();
    inNetBB = ByteBuffer.allocate(netBBSize);
    outNetBB = ByteBuffer.allocate(netBBSize);
    outNetBB.position(0);
    outNetBB.limit(0);
  }
  /*
   * Perform a FileChannel.TransferTo on the socket channel.
   * <P>
   * We have to copy the data into an intermediary app ByteBuffer
   * first, then send it through the SSLEngine.
   * <P>
   * We return the number of bytes actually read out of the
   * filechannel.  However, the data may actually be stuck
   * in the fileChannelBB or the outNetBB.  The caller
   * is responsible for making sure to call dataFlush()
   * before shutting down.
   */
  long transferTo(FileChannel fc, long pos, long len) throws IOException {

    if (!initialHSComplete) {
      throw new IllegalStateException();
    }

    if (fileChannelBB == null) {
      fileChannelBB = ByteBuffer.allocate(appBBSize);
      fileChannelBB.limit(0);
    }

    fileChannelBB.compact();
    int fileRead = fc.read(fileChannelBB);
    fileChannelBB.flip();

    /*
     * We ignore the return value here, we return the
     * number of bytes actually consumed from the the file.
     * We'll flush the output buffer before we start shutting down.
     */
    doWrite(fileChannelBB);

    return fileRead;
  }
  /*
   * Run the test.
   *
   * Sit in a tight loop, both engines calling wrap/unwrap regardless
   * of whether data is available or not.  We do this until both engines
   * report back they are closed.
   *
   * The main loop handles all of the I/O phases of the SSLEngine's
   * lifetime:
   *
   *     initial handshaking
   *     application data transfer
   *     engine closing
   *
   * One could easily separate these phases into separate
   * sections of code.
   */
  private SSLSession runTest() throws Exception {
    boolean dataDone = false;

    createSSLEngines();
    createBuffers();

    SSLEngineResult clientResult; // results from client's last operation
    SSLEngineResult serverResult; // results from server's last operation

    /*
     * Examining the SSLEngineResults could be much more involved,
     * and may alter the overall flow of the application.
     *
     * For example, if we received a BUFFER_OVERFLOW when trying
     * to write to the output pipe, we could reallocate a larger
     * pipe, but instead we wait for the peer to drain it.
     */
    while (!isEngineClosed(clientEngine) || !isEngineClosed(serverEngine)) {

      log("================");

      clientResult = clientEngine.wrap(clientOut, cTOs);
      log("client wrap: ", clientResult);
      runDelegatedTasks(clientResult, clientEngine);

      serverResult = serverEngine.wrap(serverOut, sTOc);
      log("server wrap: ", serverResult);
      runDelegatedTasks(serverResult, serverEngine);

      cTOs.flip();
      sTOc.flip();

      log("----");

      clientResult = clientEngine.unwrap(sTOc, clientIn);
      log("client unwrap: ", clientResult);
      runDelegatedTasks(clientResult, clientEngine);

      serverResult = serverEngine.unwrap(cTOs, serverIn);
      log("server unwrap: ", serverResult);
      runDelegatedTasks(serverResult, serverEngine);

      cTOs.compact();
      sTOc.compact();

      /*
       * After we've transfered all application data between the client
       * and server, we close the clientEngine's outbound stream.
       * This generates a close_notify handshake message, which the
       * server engine receives and responds by closing itself.
       */
      if (!dataDone
          && (clientOut.limit() == serverIn.position())
          && (serverOut.limit() == clientIn.position())) {

        /*
         * A sanity check to ensure we got what was sent.
         */
        checkTransfer(serverOut, clientIn);
        checkTransfer(clientOut, serverIn);

        log("\tClosing clientEngine's *OUTBOUND*...");
        clientEngine.closeOutbound();
        dataDone = true;
      }
    }

    return clientEngine.getSession();
  }