@Test
  public void testDecodingFileWithBufferedSessionData() throws Exception {
    final ReadableByteChannel channel =
        new ReadableByteChannelMock(
            new String[] {"stuff; ", "more stuff; ", "a lot more stuff!"}, Consts.ASCII);

    final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
    final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
    final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);

    final int i = inbuf.fill(channel);
    Assert.assertEquals(7, i);

    createTempFile();
    final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
    try {
      final FileChannel fchannel = testfile.getChannel();
      long pos = 0;
      while (!decoder.isCompleted()) {
        final long bytesRead = decoder.transfer(fchannel, pos, 10);
        if (bytesRead > 0) {
          pos += bytesRead;
        }
      }

      // count everything except the initial 7 bytes that went to the session buffer
      Assert.assertEquals(testfile.length() - 7, metrics.getBytesTransferred());
    } finally {
      testfile.close();
    }
    Assert.assertEquals(
        "stuff; more stuff; a lot more stuff!", CodecTestUtils.readFromFile(this.tmpfile));
  }
  @Test
  public void testDecodingFromSessionBuffer() throws Exception {
    final ReadableByteChannel channel =
        new ReadableByteChannelMock(new String[] {"stuff;", "more stuff"}, Consts.ASCII);

    final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
    final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();

    inbuf.fill(channel);

    Assert.assertEquals(6, inbuf.length());

    final IdentityDecoder decoder = new IdentityDecoder(channel, inbuf, metrics);

    final ByteBuffer dst = ByteBuffer.allocate(1024);

    int bytesRead = decoder.read(dst);
    Assert.assertEquals(6, bytesRead);
    Assert.assertEquals("stuff;", CodecTestUtils.convert(dst));
    Assert.assertFalse(decoder.isCompleted());
    Assert.assertEquals(0, metrics.getBytesTransferred()); // doesn't count if from session buffer

    dst.clear();
    bytesRead = decoder.read(dst);
    Assert.assertEquals(10, bytesRead);
    Assert.assertEquals("more stuff", CodecTestUtils.convert(dst));
    Assert.assertFalse(decoder.isCompleted());
    Assert.assertEquals(10, metrics.getBytesTransferred());

    dst.clear();
    bytesRead = decoder.read(dst);
    Assert.assertEquals(-1, bytesRead);
    Assert.assertTrue(decoder.isCompleted());
    Assert.assertEquals(10, metrics.getBytesTransferred());

    dst.clear();
    bytesRead = decoder.read(dst);
    Assert.assertEquals(-1, bytesRead);
    Assert.assertTrue(decoder.isCompleted());
    Assert.assertEquals(10, metrics.getBytesTransferred());
  }
  @Override
  public void consumeData(final IOSession iosession, final ServerState sessionState)
      throws IOException, SMTPProtocolException {
    Args.notNull(iosession, "IO session");
    Args.notNull(sessionState, "Session state");

    SessionInputBuffer buf = this.iobuffers.getInbuf();

    synchronized (sessionState) {
      for (; ; ) {
        int bytesRead = buf.fill(iosession.channel());
        try {
          SMTPCommand command = this.parser.parse(buf, bytesRead == -1);
          if (command == null) {
            if (bytesRead == -1 && !sessionState.isTerminated() && this.pendingActions.isEmpty()) {
              throw new UnexpectedEndOfStreamException();
            } else {
              break;
            }
          }
          Action<ServerState> action = this.commandHandler.handle(command);
          this.pendingActions.add(action);
        } catch (SMTPErrorException ex) {
          SMTPReply reply = new SMTPReply(ex.getCode(), ex.getEnhancedCode(), ex.getMessage());
          this.pendingActions.add(new SimpleAction(reply));
        } catch (SMTPProtocolException ex) {
          SMTPReply reply =
              new SMTPReply(
                  SMTPCodes.ERR_PERM_SYNTAX_ERR_COMMAND, new SMTPCode(5, 3, 0), ex.getMessage());
          this.pendingActions.add(new SimpleAction(reply));
        }
      }

      if (!this.pendingActions.isEmpty()) {
        iosession.setEvent(SelectionKey.OP_WRITE);
      }
    }
  }
  @Override
  public void consumeData(final IOSession iosession, final ClientState sessionState)
      throws IOException, SMTPProtocolException {
    Args.notNull(iosession, "IO session");
    Args.notNull(sessionState, "Session state");

    SessionInputBuffer buf = this.iobuffers.getInbuf();

    int bytesRead = buf.fill(iosession.channel());
    SMTPReply reply = this.parser.parse(buf, bytesRead == -1);

    if (reply != null) {
      switch (this.codecState) {
        case SERVICE_READY_EXPECTED:
          if (reply.getCode() == SMTPCodes.SERVICE_READY) {
            this.codecState = CodecState.EHLO_READY;
            iosession.setEvent(SelectionKey.OP_WRITE);
          } else {
            this.codecState = CodecState.COMPLETED;
            sessionState.setReply(reply);
          }
          break;
        case EHLO_RESPONSE_EXPECTED:
          if (reply.getCode() == SMTPCodes.OK) {

            Set<String> extensions = sessionState.getExtensions();

            List<String> lines = reply.getLines();
            if (lines.size() > 1) {
              for (int i = 1; i < lines.size(); i++) {
                String line = lines.get(i);
                extensions.add(line.toUpperCase(Locale.US));
              }
            }
            this.codecState = CodecState.COMPLETED;
            sessionState.setReply(reply);
          } else if (reply.getCode() == SMTPCodes.ERR_PERM_SYNTAX_ERR_COMMAND) {
            this.codecState = CodecState.HELO_READY;
            iosession.setEvent(SelectionKey.OP_WRITE);
          } else {
            this.codecState = CodecState.COMPLETED;
            sessionState.setReply(reply);
          }
          break;
        case HELO_RESPONSE_EXPECTED:
          this.codecState = CodecState.COMPLETED;
          sessionState.setReply(reply);
          break;
        default:
          if (reply.getCode() == SMTPCodes.ERR_TRANS_SERVICE_NOT_AVAILABLE) {
            sessionState.setReply(reply);
            this.codecState = CodecState.COMPLETED;
          } else {
            throw new SMTPProtocolException("Unexpected reply: " + reply);
          }
      }
    } else {
      if (bytesRead == -1 && !sessionState.isTerminated()) {
        throw new UnexpectedEndOfStreamException();
      }
    }
  }