/**
   * Generic function to send either a ping or a pong.
   *
   * @param data Optional message.
   * @param opcode The byte to include as the opcode.
   * @throws IOException If an error occurs writing to the client
   */
  private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        doFlush(false);

        upgradeOutbound.write(0x80 | opcode);
        if (data == null) {
          upgradeOutbound.write(0);
        } else {
          upgradeOutbound.write(data.limit() - data.position());
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }

        upgradeOutbound.flush();
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
  /**
   * Send a close message to the client
   *
   * @param status Must be a valid status code or zero to send no code
   * @param data Optional message. If message is defined, a valid status code must be provided.
   * @throws IOException If an error occurs writing to the client
   */
  public void close(int status, ByteBuffer data) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          return;
        }

        // Send any partial data we have
        try {
          doFlush(false);
        } finally {
          closed = true;
        }

        upgradeOutbound.write(0x88);
        if (status == 0) {
          upgradeOutbound.write(0);
        } else if (data == null || data.position() == data.limit()) {
          upgradeOutbound.write(2);
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
        } else {
          upgradeOutbound.write(2 + data.limit() - data.position());
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }
        upgradeOutbound.flush();

        bb = null;
        cb = null;
        upgradeOutbound = null;
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
  /**
   * Writes the provided bytes as the payload in a new WebSocket frame.
   *
   * @param buffer The bytes to include in the payload.
   * @param finalFragment Do these bytes represent the final fragment of a WebSocket message?
   * @throws IOException
   */
  private void doWriteBytes(ByteBuffer buffer, boolean finalFragment) throws IOException {

    if (closed) {
      throw new IOException(sm.getString("outbound.closed"));
    }

    // Work out the first byte
    int first = 0x00;
    if (finalFragment) {
      first = first + 0x80;
    }
    if (firstFrame) {
      if (text.booleanValue()) {
        first = first + 0x1;
      } else {
        first = first + 0x2;
      }
    }
    // Continuation frame is OpCode 0
    upgradeOutbound.write(first);

    if (buffer.limit() < 126) {
      upgradeOutbound.write(buffer.limit());
    } else if (buffer.limit() < 65536) {
      upgradeOutbound.write(126);
      upgradeOutbound.write(buffer.limit() >>> 8);
      upgradeOutbound.write(buffer.limit() & 0xFF);
    } else {
      // Will never be more than 2^31-1
      upgradeOutbound.write(127);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(buffer.limit() >>> 24);
      upgradeOutbound.write(buffer.limit() >>> 16);
      upgradeOutbound.write(buffer.limit() >>> 8);
      upgradeOutbound.write(buffer.limit() & 0xFF);
    }

    // Write the content
    upgradeOutbound.write(buffer.array(), buffer.arrayOffset(), buffer.limit());
    upgradeOutbound.flush();

    // Reset
    if (finalFragment) {
      text = null;
      firstFrame = true;
    } else {
      firstFrame = false;
    }
    bb.clear();
  }