@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());
    }
  }
  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());
  }