public boolean matches(Object actual) {
      if (actual instanceof WriteRequest) {
        WriteRequest w2 = (WriteRequest) actual;

        return expected.getMessage().equals(w2.getMessage())
            && expected.getFuture().isWritten() == w2.getFuture().isWritten();
      }
      return false;
    }
  @Test
  public void testWriteEmptyFile() throws Exception {
    AbstractStreamWriteFilter<M> filter = createFilter();
    M message = createMessage(new byte[0]);

    WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture());

    NextFilter nextFilter = EasyMock.createMock(NextFilter.class);
    /*
     * Record expectations
     */
    nextFilter.messageSent(session, writeRequest);

    /*
     * Replay.
     */
    EasyMock.replay(nextFilter);

    filter.filterWrite(nextFilter, session, writeRequest);

    /*
     * Verify.
     */
    EasyMock.verify(nextFilter);

    assertTrue(writeRequest.getFuture().isWritten());
  }
  /**
   * Tests when the contents of the file fits into one write buffer.
   *
   * @throws Exception when something goes wrong
   */
  @Test
  public void testWriteSingleBufferFile() throws Exception {
    byte[] data = new byte[] {1, 2, 3, 4};

    AbstractStreamWriteFilter<M> filter = createFilter();
    M message = createMessage(data);

    WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture());

    NextFilter nextFilter = EasyMock.createMock(NextFilter.class);
    /*
     * Record expectations
     */
    nextFilter.filterWrite(
        EasyMock.eq(session), eqWriteRequest(new DefaultWriteRequest(IoBuffer.wrap(data))));
    nextFilter.messageSent(session, writeRequest);

    /*
     * Replay.
     */
    EasyMock.replay(nextFilter);

    filter.filterWrite(nextFilter, session, writeRequest);
    filter.messageSent(nextFilter, session, writeRequest);

    /*
     * Verify.
     */
    EasyMock.verify(nextFilter);

    assertTrue(writeRequest.getFuture().isWritten());
  }
  public void wrap(NextFilter nextFilter, WriteRequest writeRequest, IoBuffer buf)
      throws AuthException {
    int start = buf.position();
    int len = buf.remaining() - LINE_TERMINATOR.length;
    if (len == 0) throw new AuthException("Decryption failed");

    // HMAC(Ki, {SeqNum, msg})[0..9]
    byte[] originalMessage = new byte[len];
    buf.get(originalMessage);
    byte[] mac = AuthDigestMD5IoFilter.computeMACBlock(session, originalMessage, false);

    // Calculate padding
    int bs = encCipher.getBlockSize();
    byte[] padding;

    if (bs > 1) {
      int pad = bs - ((len + 10) % bs); // add 10 for HMAC[0..9]
      padding = new byte[pad];
      for (int i = 0; i < pad; i++) padding[i] = (byte) pad;
    } else padding = EMPTY_BYTE_ARRAY;

    byte[] toBeEncrypted = new byte[len + padding.length + 10];

    // {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])}
    System.arraycopy(originalMessage, start, toBeEncrypted, 0, len);
    System.arraycopy(padding, 0, toBeEncrypted, len, padding.length);
    System.arraycopy(mac, 0, toBeEncrypted, len + padding.length, 10);

    // CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])})
    byte[] cipherBlock;
    try {
      // Do CBC (chaining) across packets
      cipherBlock = encCipher.update(toBeEncrypted);

      // update() can return null
      if (cipherBlock == null) throw new IllegalBlockSizeException("" + toBeEncrypted.length);
    } catch (IllegalBlockSizeException e) {
      throw new AuthException("Invalid block size for cipher", e);
    }

    IoBuffer out = IoBuffer.allocate(cipherBlock.length + 2 + 4 + LINE_TERMINATOR.length);
    out.put(cipherBlock);
    out.put(mac, 10, 6); // messageType & sequenceNum
    out.put(LINE_TERMINATOR);
    out.flip();

    if (out.limit()
        > ((Integer) session.getAttribute(AuthDigestMD5Command.CLIENT_MAXBUF)).intValue())
      throw new AuthException("Data exceeds client maxbuf capability");

    nextFilter.filterWrite(session, new DefaultWriteRequest(out, writeRequest.getFuture()));
  }
  private void clearWriteRequestQueue(S session) {
    WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
    WriteRequest req;

    List<WriteRequest> failedRequests = new ArrayList<WriteRequest>();

    if ((req = writeRequestQueue.poll(session)) != null) {
      Object message = req.getMessage();

      if (message instanceof IoBuffer) {
        IoBuffer buf = (IoBuffer) message;

        // The first unwritten empty buffer must be
        // forwarded to the filter chain.
        if (buf.hasRemaining()) {
          buf.reset();
          failedRequests.add(req);
        } else {
          IoFilterChain filterChain = session.getFilterChain();
          filterChain.fireMessageSent(req);
        }
      } else {
        failedRequests.add(req);
      }

      // Discard others.
      while ((req = writeRequestQueue.poll(session)) != null) {
        failedRequests.add(req);
      }
    }

    // Create an exception and notify.
    if (!failedRequests.isEmpty()) {
      WriteToClosedSessionException cause = new WriteToClosedSessionException(failedRequests);

      for (WriteRequest r : failedRequests) {
        session.decreaseScheduledBytesAndMessages(r);
        r.getFuture().setException(cause);
      }

      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(cause);
    }
  }
  /**
   * Tests when the contents of the file doesn't fit into one write buffer.
   *
   * @throws Exception when something goes wrong
   */
  @Test
  public void testWriteSeveralBuffersStream() throws Exception {
    AbstractStreamWriteFilter<M> filter = createFilter();
    filter.setWriteBufferSize(4);

    byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    byte[] chunk1 = new byte[] {1, 2, 3, 4};
    byte[] chunk2 = new byte[] {5, 6, 7, 8};
    byte[] chunk3 = new byte[] {9, 10};

    M message = createMessage(data);
    WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture());

    WriteRequest chunk1Request = new DefaultWriteRequest(IoBuffer.wrap(chunk1));
    WriteRequest chunk2Request = new DefaultWriteRequest(IoBuffer.wrap(chunk2));
    WriteRequest chunk3Request = new DefaultWriteRequest(IoBuffer.wrap(chunk3));

    NextFilter nextFilter = EasyMock.createMock(NextFilter.class);
    /*
     * Record expectations
     */
    nextFilter.filterWrite(EasyMock.eq(session), eqWriteRequest(chunk1Request));
    nextFilter.filterWrite(EasyMock.eq(session), eqWriteRequest(chunk2Request));
    nextFilter.filterWrite(EasyMock.eq(session), eqWriteRequest(chunk3Request));
    nextFilter.messageSent(EasyMock.eq(session), eqWriteRequest(writeRequest));

    /*
     * Replay.
     */
    EasyMock.replay(nextFilter);

    filter.filterWrite(nextFilter, session, writeRequest);
    filter.messageSent(nextFilter, session, chunk1Request);
    filter.messageSent(nextFilter, session, chunk2Request);
    filter.messageSent(nextFilter, session, chunk3Request);

    /*
     * Verify.
     */
    EasyMock.verify(nextFilter);

    assertTrue(writeRequest.getFuture().isWritten());
  }
  private boolean flushNow(S session, long currentTime) {
    if (!session.isConnected()) {
      scheduleRemove(session);
      return false;
    }

    final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();

    final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();

    // Set limitation for the number of written bytes for read-write
    // fairness. I used maxReadBufferSize * 3 / 2, which yields best
    // performance in my experience while not breaking fairness much.
    final int maxWrittenBytes =
        session.getConfig().getMaxReadBufferSize()
            + (session.getConfig().getMaxReadBufferSize() >>> 1);
    int writtenBytes = 0;
    WriteRequest req = null;

    try {
      // Clear OP_WRITE
      setInterestedInWrite(session, false);

      do {
        // Check for pending writes.
        req = session.getCurrentWriteRequest();

        if (req == null) {
          req = writeRequestQueue.poll(session);

          if (req == null) {
            break;
          }

          session.setCurrentWriteRequest(req);
        }

        int localWrittenBytes = 0;
        Object message = req.getMessage();

        if (message instanceof IoBuffer) {
          localWrittenBytes =
              writeBuffer(
                  session, req, hasFragmentation, maxWrittenBytes - writtenBytes, currentTime);

          if ((localWrittenBytes > 0) && ((IoBuffer) message).hasRemaining()) {
            // the buffer isn't empty, we re-interest it in writing
            writtenBytes += localWrittenBytes;
            setInterestedInWrite(session, true);
            return false;
          }
        } else if (message instanceof FileRegion) {
          localWrittenBytes =
              writeFile(
                  session, req, hasFragmentation, maxWrittenBytes - writtenBytes, currentTime);

          // Fix for Java bug on Linux
          // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
          // If there's still data to be written in the FileRegion,
          // return 0 indicating that we need
          // to pause until writing may resume.
          if ((localWrittenBytes > 0) && (((FileRegion) message).getRemainingBytes() > 0)) {
            writtenBytes += localWrittenBytes;
            setInterestedInWrite(session, true);
            return false;
          }
        } else {
          throw new IllegalStateException(
              "Don't know how to handle message of type '"
                  + message.getClass().getName()
                  + "'.  Are you missing a protocol encoder?");
        }

        if (localWrittenBytes == 0) {
          // Kernel buffer is full.
          setInterestedInWrite(session, true);
          return false;
        }

        writtenBytes += localWrittenBytes;

        if (writtenBytes >= maxWrittenBytes) {
          // Wrote too much
          scheduleFlush(session);
          return false;
        }
      } while (writtenBytes < maxWrittenBytes);
    } catch (Exception e) {
      if (req != null) {
        req.getFuture().setException(e);
      }

      IoFilterChain filterChain = session.getFilterChain();
      filterChain.fireExceptionCaught(e);
      return false;
    }

    return true;
  }