public Transfer get_data (ByteBuffer buffer) 
    {
        if (buffer == null)
            buffer = buf;
        
        buffer.clear();

        while (buffer.hasRemaining ()) {

            //  If there are no more data to return, run the state machine.
            //  If there are still no data, return what we already have
            //  in the buffer.
            if (to_write == 0) {
                //  If we are to encode the beginning of a new message,
                //  adjust the message offset.

                if (!next())
                    break;
            }
            
            //  If there is file channel to send, 
            //  send current buffer and the channel together
            
            if (write_channel != null) {
                buffer.flip ();
                Transfer t = new Transfer.FileChannelTransfer (buffer, write_channel,
                                                    (long) write_pos, (long) to_write);
                write_pos = 0;
                to_write = 0;

                return t;
            }
            //  If there are no data in the buffer yet and we are able to
            //  fill whole buffer in a single go, let's use zero-copy.
            //  There's no disadvantage to it as we cannot stuck multiple
            //  messages into the buffer anyway. Note that subsequent
            //  write(s) are non-blocking, thus each single write writes
            //  at most SO_SNDBUF bytes at once not depending on how large
            //  is the chunk returned from here.
            //  As a consequence, large messages being sent won't block
            //  other engines running in the same I/O thread for excessive
            //  amounts of time.
            if (buf.position () == 0 && to_write >= buffersize) {
                Transfer t;
                ByteBuffer b = ByteBuffer.wrap (write_buf);
                b.position (write_pos);
                t = new Transfer.ByteBufferTransfer (b);
                write_pos = 0;
                to_write = 0;

                return t;
            }

            //  Copy data to the buffer. If the buffer is full, return.
            int to_copy = Math.min (to_write, buffer.remaining ());
            if (to_copy > 0) {
                buffer.put (write_buf, write_pos, to_copy);
                write_pos += to_copy;
                to_write -= to_copy;
            }
        }

        buffer.flip ();
        return new Transfer.ByteBufferTransfer (buffer);

    }