@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());
  }
  /**
   * Tests that the filter just passes objects which aren't FileRegion's through to the next filter.
   *
   * @throws Exception when something goes wrong
   */
  @Test
  public void testWriteNonFileRegionMessage() throws Exception {
    AbstractStreamWriteFilter<M> filter = createFilter();

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

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

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

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

    /*
     * Verify.
     */
    EasyMock.verify(nextFilter);
  }
  @Override
  public void fire() {
    IoSession session = getSession();
    NextFilter nextFilter = getNextFilter();
    IoEventType type = getType();

    if (DEBUG) {
      LOGGER.debug("Firing a {} event for session {}", type, session.getId());
    }

    switch (type) {
      case MESSAGE_RECEIVED:
        Object parameter = getParameter();
        nextFilter.messageReceived(session, parameter);
        break;

      case MESSAGE_SENT:
        WriteRequest writeRequest = (WriteRequest) getParameter();
        nextFilter.messageSent(session, writeRequest);
        break;

      case WRITE:
        writeRequest = (WriteRequest) getParameter();
        nextFilter.filterWrite(session, writeRequest);
        break;

      case CLOSE:
        nextFilter.filterClose(session);
        break;

      case EXCEPTION_CAUGHT:
        Throwable throwable = (Throwable) getParameter();
        nextFilter.exceptionCaught(session, throwable);
        break;

      case SESSION_IDLE:
        nextFilter.sessionIdle(session, (IdleStatus) getParameter());
        break;

      case SESSION_OPENED:
        nextFilter.sessionOpened(session);
        break;

      case SESSION_CREATED:
        nextFilter.sessionCreated(session);
        break;

      case SESSION_CLOSED:
        nextFilter.sessionClosed(session);
        break;

      default:
        throw new IllegalArgumentException("Unknown event type: " + type);
    }

    if (DEBUG) {
      LOGGER.debug("Event {} has been fired for session {}", type, session.getId());
    }
  }
  @Test
  public void testWritesWriteRequestQueueWhenFinished() throws Exception {
    AbstractStreamWriteFilter<M> filter = createFilter();
    M message = createMessage(new byte[0]);

    WriteRequest wrs[] =
        new WriteRequest[] {
          new DefaultWriteRequest(new Object(), new DummyWriteFuture()),
          new DefaultWriteRequest(new Object(), new DummyWriteFuture()),
          new DefaultWriteRequest(new Object(), new DummyWriteFuture())
        };
    Queue<WriteRequest> queue = new LinkedList<WriteRequest>();
    queue.add(wrs[0]);
    queue.add(wrs[1]);
    queue.add(wrs[2]);

    /*
     * Make up the situation.
     */
    session.setAttribute(filter.CURRENT_STREAM, message);
    session.setAttribute(filter.CURRENT_WRITE_REQUEST, new DefaultWriteRequest(message));
    session.setAttribute(filter.WRITE_REQUEST_QUEUE, queue);

    /*
     * Record expectations
     */
    NextFilter nextFilter = EasyMock.createMock(NextFilter.class);
    nextFilter.filterWrite(session, wrs[0]);
    nextFilter.filterWrite(session, wrs[1]);
    nextFilter.filterWrite(session, wrs[2]);
    nextFilter.messageSent(EasyMock.eq(session), eqWriteRequest(new DefaultWriteRequest(message)));

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

    filter.messageSent(nextFilter, session, new DefaultWriteRequest(new Object()));
    assertEquals(0, queue.size());

    /*
     * Verify.
     */
    EasyMock.verify(nextFilter);
  }
  /**
   * 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());
  }
  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()));
  }
  public void unwrap(NextFilter nextFilter, IoBuffer buf) {
    try {
      int len = buf.remaining();
      if (len == 0) throw new AuthException("Decryption failed");

      byte[] encryptedMsg = new byte[len - 6];
      byte[] msgType = new byte[2];
      byte[] seqNum = new byte[4];

      // Get cipherMsg; msgType; sequenceNum
      buf.get(encryptedMsg);
      buf.get(msgType);
      buf.get(seqNum);

      // Decrypt message - CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg}[0..9])})
      byte[] decryptedMsg;

      try {
        // Do CBC (chaining) across packets
        decryptedMsg = decCipher.update(encryptedMsg);

        // update() can return null
        if (decryptedMsg == null) throw new IllegalBlockSizeException("" + encryptedMsg.length);
      } catch (IllegalBlockSizeException e) {
        throw new AuthException("Illegal block sizes used with chosen cipher", e);
      }

      byte[] msgWithPadding = new byte[decryptedMsg.length - 10];
      byte[] mac = new byte[10];

      System.arraycopy(decryptedMsg, 0, msgWithPadding, 0, msgWithPadding.length);
      System.arraycopy(decryptedMsg, msgWithPadding.length, mac, 0, 10);

      int msgLength = msgWithPadding.length;
      int blockSize = decCipher.getBlockSize();

      if (blockSize > 1) {
        // get value of last octet of the byte array
        msgLength -= (int) msgWithPadding[msgWithPadding.length - 1];
        if (msgLength < 0)
          //  Discard message and do not increment sequence number
          throw new AuthException("Decryption failed");
      }

      byte[] msg = new byte[msgLength];
      System.arraycopy(msgWithPadding, 0, msg, 0, msgLength);

      // Re-calculate MAC to ensure integrity
      byte[] expectedMac = AuthDigestMD5IoFilter.computeMACBlock(session, msg, true);

      byte[] fullMac = new byte[16];
      System.arraycopy(mac, 0, fullMac, 0, 10);
      System.arraycopy(msgType, 0, fullMac, 10, 2);
      System.arraycopy(seqNum, 0, fullMac, 12, 4);

      if (isValidMAC(fullMac, expectedMac)) {
        IoBuffer out = IoBuffer.allocate(msgLength + LINE_TERMINATOR.length);
        out.put(msg);
        out.put(LINE_TERMINATOR);
        out.flip();

        nextFilter.messageReceived(session, out);
      }
    } catch (Exception ex) {
      log.debug(ex.getMessage());
      nextFilter.messageReceived(session, "\r\n");
    }

    if (session instanceof AbstractIoSession)
      ((AbstractIoSession) session).increaseReadMessages(System.currentTimeMillis());
  }