private int preReadProcess(int allowed) {
    if (allowed < 1) {
      Debug.out("allowed < 1");
    }

    decode_array[0] =
        payload_buffer == null
            ? null
            : payload_buffer.getBuffer(
                SS); // ensure the decode array has the latest payload pointer

    int bytes_available = 0;
    boolean shrink_remaining_buffers = false;
    int start_buff = reading_length_mode ? 1 : 0;
    boolean marked = false;

    for (int i = start_buff; i < 2; i++) { // set buffer limits according to bytes allowed
      ByteBuffer bb = decode_array[i];

      if (bb == null) {
        Debug.out("preReadProcess:: bb[" + i + "] == null, decoder destroyed=" + destroyed);

        throw (new RuntimeException("decoder destroyed"));
      }

      if (shrink_remaining_buffers) {
        bb.limit(0); // ensure no read into this next buffer is possible
      } else {
        int remaining = bb.remaining();

        if (remaining < 1) continue; // skip full buffer

        if (!marked) {
          pre_read_start_buffer = i;
          pre_read_start_position = bb.position();
          marked = true;
        }

        if (remaining > allowed) { // read only part of this buffer
          bb.limit(bb.position() + allowed); // limit current buffer
          bytes_available += bb.remaining();
          shrink_remaining_buffers = true; // shrink any tail buffers
        } else { // full buffer is allowed to be read
          bytes_available += remaining;
          allowed -= remaining; // count this buffer toward allowed and move on to the next
        }
      }
    }

    return bytes_available;
  }
  public int performStreamDecode(Transport transport, int max_bytes) throws IOException {
    try {
      protocol_bytes_last_read = 0;
      data_bytes_last_read = 0;

      int bytes_remaining = max_bytes;

      while (bytes_remaining > 0) {

        if (destroyed) {

          // destruction currently isn't thread safe so one thread can destroy the decoder (e.g.
          // when closing a connection)
          // while the read-controller is still actively processing the us
          // throw( new IOException( "BTMessageDecoder already destroyed" ));
          break;
        }

        if (is_paused) {
          break;
        }

        int bytes_possible = preReadProcess(bytes_remaining);

        if (bytes_possible < 1) {
          Debug.out("ERROR BT: bytes_possible < 1");
          break;
        }

        if (reading_length_mode) {
          transport.read(decode_array, 1, 1); // only read into length buffer
        } else {
          transport.read(
              decode_array, 0, 2); // read into payload buffer, and possibly next message length
        }

        int bytes_read = postReadProcess();

        bytes_remaining -= bytes_read;

        if (bytes_read < bytes_possible) {
          break;
        }

        if (reading_length_mode && last_received_was_keepalive) {
          // hack to stop a 0-byte-read after receiving a keep-alive message
          // otherwise we won't realize there's nothing left on the line until trying to read again
          last_received_was_keepalive = false;
          break;
        }
      }

      return max_bytes - bytes_remaining;

    } catch (NullPointerException e) {

      // due to lack of synchronization here the buffers can be nullified by a concurrent 'destroy'
      // turn this into something less scarey

      throw (new IOException("Decoder has most likely been destroyed"));
    }
  }
  public ByteBuffer destroy() {
    if (destroyed) {
      Debug.out("Trying to redestroy message decoder, stack trace follows: " + this);
      Debug.outStackTrace();
    }

    is_paused = true;
    destroyed = true;

    // there's a concurrency issue with the decoder whereby it can be destroyed while will being
    // messed with. Don't
    // have the energy to look into it properly atm so just try to ensure that it doesn't bork too
    // badly (parg: 29/04/2012)
    // only occasional but does have potential to generate direct buffer mem leak ;(

    int lbuff_read = 0;
    int pbuff_read = 0;
    length_buffer.limit(SS, 4);

    DirectByteBuffer plb = payload_buffer;

    if (reading_length_mode) {
      lbuff_read = length_buffer.position(SS);
    } else { // reading payload
      length_buffer.position(SS, 4);
      lbuff_read = 4;
      pbuff_read = plb == null ? 0 : plb.position(SS);
    }

    ByteBuffer unused = ByteBuffer.allocate(lbuff_read + pbuff_read); // TODO convert to direct?

    length_buffer.flip(SS);
    unused.put(length_buffer.getBuffer(SS));

    try {
      if (plb != null) {
        plb.flip(SS);
        unused.put(
            plb.getBuffer(
                SS)); // Got a buffer overflow exception here in the past - related to PEX?
      }
    } catch (RuntimeException e) {
      Debug.out("hit known threading issue");
    }

    unused.flip();

    length_buffer.returnToPool();

    if (plb != null) {
      plb.returnToPool();
      payload_buffer = null;
    }

    try {
      for (int i = 0; i < messages_last_read.size(); i++) {
        Message msg = (Message) messages_last_read.get(i);
        msg.destroy();
      }
    } catch (RuntimeException e) {
      // happens if messages modified by alt thread...
      Debug.out("hit known threading issue");
    }
    messages_last_read.clear();

    return unused;
  }