/**
   * "Helper routine to add a bundle at the indicated position." [DTN2]
   *
   * @throws BundleLockNotHeldByCurrentThread
   * @throws InterruptedException
   */
  private void add_bundle(final Bundle b, final int pos)
      throws BundleListLockNotHoldByCurrentThread, BundleLockNotHeldByCurrentThread,
          InterruptedException {
    if (!lock_.isHeldByCurrentThread()) throw new BundleListLockNotHoldByCurrentThread();
    if (!b.get_lock().isHeldByCurrentThread()) throw new BundleLockNotHeldByCurrentThread();

    if (b.is_queued_on(this)) {
      Log.e(
          TAG,
          String.format(
              "ERROR in add bundle: " + "bundle id %d already on list [%s]", b.bundleid(), name_));

      return;
    }

    list_.add(pos, b);

    b.mappings().add(this);

    Log.d(
        TAG,
        String.format(
            "bundle id %d is added to list [%s] , the size become",
            b.bundleid(), name_, list_.size()));
  }
  /**
   * Helper routine to remove a bundle from the indicated position. This is called by other public
   * functions such as pop_front This is the function will actually post the bundle free event to
   * the Bundle daemon
   *
   * @param pos Position to delete a flag indicate whether to free the bundle as well
   * @param free whether to free the bundle. This freeing will remove Bundle from storage including
   *     its payload
   * @throws BundleListLockNotHoldByCurrentThread
   * @returns the bundle that, before this call, was at the position
   */
  private Bundle del_bundle(final int pos, boolean free)
      throws BundleListLockNotHoldByCurrentThread {

    Bundle b = list_.get(pos);
    assert (lock_.isHeldByCurrentThread());

    if (!lock_.isHeldByCurrentThread()) throw new BundleListLockNotHoldByCurrentThread();

    b.get_lock().lock();

    try {

      Log.d(
          TAG,
          String.format("bundle id %d del_bundle: deleting mapping [%s]", b.bundleid(), name_));

      if (!b.mappings().contains(this)) {
        Log.e(
            TAG,
            String.format(
                "ERROR in del bundle: " + "bundle id %d has no mapping for list [%s]",
                b.bundleid(), name_));
      } else {
        b.mappings().remove(this);
      }

      // "remove the bundle from the list" [DTN2]
      list_.remove(b);

      if (free) BundleDaemon.getInstance().post(new BundleFreeEvent(b));

      return b;
    } catch (BundleLockNotHeldByCurrentThread e) {
      Log.e(TAG, e.getMessage());
      return null;

    } finally {
      b.get_lock().unlock();
    }
  }
  /** Delete any fragments that are no longer needed given the incoming (non-fragment) bundle. */
  public void delete_obsoleted_fragments(Bundle bundle) {

    FragmentState state;

    // cons up the key to do the table lookup and look for reassembly state
    String[] hash_key = new String[1];
    hash_key[0] = "";

    get_hash_key(bundle, hash_key);
    state = fragment_table_.get(hash_key);

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "checking for obsolete fragments id=%s hash=%s...",
                bundle.bundleid(), hash_key[0]));

    if (state == null) {
      Logger.getInstance().error(TAG, String.format("no reassembly state for key %s", hash_key[0]));
      return;
    }

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "found reassembly state... deleting %d fragments", state.num_fragments()));

    state.fragment_list().get_lock().lock();
    try {

      while (state.fragment_list().size() > 0) {
        BundleDaemon.getInstance()
            .post(
                new BundleDeleteRequest(
                    state.fragment_list().pop_back(false),
                    status_report_reason_t.REASON_NO_ADDTL_INFO));
      }

      assert (state.fragment_list().size() == 0)
          : TAG + ": delete_obsoleted_fragments Size not 0"; // moved into
      // events

    } finally {
      state.fragment_list().get_lock().unlock();
    }

    fragment_table_.remove(hash_key);
  }
  /**
   * "Search the list for a bundle with the given id." [DTN2]
   *
   * @return the found bundle otherwise, null.
   */
  public Bundle find(int bundleid) {

    lock_.lock();
    try {
      Iterator<Bundle> iter = list_.iterator();
      while (iter.hasNext()) {
        Bundle bundle = iter.next();
        if (bundle.bundleid() == bundleid) return bundle;
      }
      return null;

    } finally {
      lock_.unlock();
    }
  }
  /**
   * Given a newly arrived bundle fragment, append it to the table of fragments and see if it allows
   * us to reassemble the bundle. If it does, a ReassemblyCompletedEvent will be posted.
   *
   * @param fragment Newly received fragment
   */
  public void process_for_reassembly(Bundle fragment) {
    FragmentState state;

    assert (fragment.is_fragment()) : TAG + ": process_for_reassembly() not a fragment";

    String[] hash_key = new String[1];
    get_hash_key(fragment, hash_key);

    FragmentState iter = fragment_table_.get(hash_key);

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "processing bundle fragment id=%s hash=%s %s",
                fragment.bundleid(), hash_key, fragment.is_fragment()));

    if (iter == null) {

      state = new FragmentState();

      fragment.copy_metadata(state.bundle());
      state.bundle().set_is_fragment(false);
      state.bundle().payload().set_length(fragment.orig_length());
      fragment_table_.put(hash_key[0], state);
    } else {
      state = iter;
      Logger.getInstance()
          .debug(
              TAG,
              String.format(
                  "found reassembly state for key %s (%s fragments)",
                  hash_key, state.fragment_list().size()));
    }

    // stick the fragment on the reassembly list
    state.add_fragment(fragment);

    // store the fragment data in the partially reassembled bundle file
    int fraglen = fragment.payload().length();

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "write_data: length_=%s src_offset=%s dst_offset=%s len %s",
                state.bundle().payload().length(), 0, fragment.frag_offset(), fraglen));

    state.bundle().payload().write_data(fragment.payload(), 0, fraglen, fragment.frag_offset());

    // reassembled bundle, but eventually reassembly will have to do much
    // more
    if (fragment.frag_offset() == 0 && state.bundle().recv_blocks().size() > 0) {
      BlockInfo block_i;

      ListIterator<BlockInfo> entry = fragment.recv_blocks().listIterator();

      while (entry.hasNext()) {
        block_i = entry.next();
        state.bundle().recv_blocks().add(block_i);
      }
    }

    // check see if we're done
    if (state.check_completed()) {
      return;
    }

    BundleDaemon.getInstance()
        .post_at_head(new ReassemblyCompletedEvent(state.bundle(), state.fragment_list()));
    assert (state.fragment_list().size() == 0)
        : TAG + ": process_for_reassembly size not 0"; // moved into the event
    fragment_table_.remove(hash_key);
  }
  /**
   * "Parse the supplied chunk of arriving data and append it to the rcvd_blocks_ list in the given
   * bundle, finding the appropriate BlockProcessor element and calling its receive() handler.
   *
   * <p>When called repeatedly for arriving chunks of data, this properly fills in the entire
   * bundle, including the in_blocks_ record of the arriving blocks and the payload (which is stored
   * externally)." [DTN2]
   *
   * @return "the length of data consumed or -1 on protocol error, plus sets last to true if the
   *     bundle is complete." [DTN2]
   */
  public static int consume(Bundle bundle, IByteBuffer data, int len, boolean[] last) {

    int old_position = data.position();
    int origlen = len;
    last[0] = false;

    BlockInfoVec recv_blocks = bundle.recv_blocks();

    // "special case for first time we get called, since we need to
    // create a BlockInfo struct for the primary block without knowing
    // the typecode or the length" [DTN2]
    if (recv_blocks.isEmpty()) {
      BPF.getInstance()
          .getBPFLogger()
          .debug(TAG, "consume: got first block... " + "creating primary block info");
      recv_blocks.append_block(find_processor(bundle_block_type_t.PRIMARY_BLOCK), null);
    }

    // "loop as long as there is data left to process" [DTN2]
    while (len != 0) {
      BPF.getInstance()
          .getBPFLogger()
          .debug(TAG, String.format("consume: %d bytes left to process", len));
      BlockInfo info = recv_blocks.back();

      // "if the last received block is complete, create a new one
      // and push it onto the vector. at this stage we consume all
      // blocks, even if there's no BlockProcessor that understands
      // how to parse it" [DTN2]
      if (info.complete()) {
        data.mark();
        byte bundle_block_type_byte = data.get();
        bundle_block_type_t type = bundle_block_type_t.get(bundle_block_type_byte);
        data.reset();
        info = recv_blocks.append_block(find_processor(type), null);
        BPF.getInstance()
            .getBPFLogger()
            .debug(
                TAG,
                String.format(
                    "consume: previous block complete, " + "created new BlockInfo type %s",
                    info.owner().block_type()));
      }

      // "now we know that the block isn't complete, so we tell it to
      // consume a chunk of data" [DTN2]
      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "consume: block processor %s type %s incomplete, "
                      + "calling consume (%d bytes already buffered)",
                  info.owner().block_type(), info.type(), info.contents().position()));

      int cc = info.owner().consume(bundle, info, data, len);
      if (cc < 0) {
        BPF.getInstance()
            .getBPFLogger()
            .error(TAG, String.format("consume: protocol error handling block %s", info.type()));
        return -1;
      }

      // "decrement the amount that was just handled from the overall
      // total. verify that the block was either completed or
      // consumed all the data that was passed in." [DTN2]
      len -= cc;
      data.position(data.position() + cc);

      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "consume: consumed %d bytes of block type %s (%s)",
                  cc, info.type(), info.complete() ? "complete" : "not complete"));

      if (info.complete()) {
        // check if we're done with the bundle
        if ((info.flags() & block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode()) > 0) {
          last[0] = true;
          break;
        }

      } else {
        assert (len == 0);
      }
    }

    BPF.getInstance()
        .getBPFLogger()
        .debug(
            TAG,
            String.format(
                "bundle id %d consume completed, %d/%d bytes consumed %s",
                bundle.bundleid(), origlen - len, origlen, last[0] ? "(completed bundle)" : ""));

    data.position(old_position);
    return origlen - len;
  }
  /**
   * "Copies out a chunk of formatted bundle data at a specified offset from the provided
   * BlockInfoVec." [DTN2]
   *
   * @return "the length of the chunk produced (up to the supplied length) and sets last to true if
   *     the bundle is complete." [DTN2]
   */
  public static int produce(
      final Bundle bundle,
      final BlockInfoVec blocks,
      IByteBuffer data,
      int offset,
      int len,
      boolean[] last) {

    int old_position = data.position();
    int origlen = len;
    last[0] = false;

    if (len == 0) return 0;

    assert (!blocks.isEmpty());

    Iterator<BlockInfo> iter = blocks.iterator();

    BlockInfo current_block = iter.next();
    // "advance past any blocks that are skipped by the given offset."[DTN2]
    while (offset >= current_block.full_length()) {

      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "BundleProtocol::produce skipping block type %s "
                      + "since offset %d >= block length %d",
                  current_block.type().toString(), offset, current_block.full_length()));

      offset -= current_block.full_length();
      current_block = iter.next();
    }
    // "the offset value now should be within the current block" [DTN2]

    while (true) {
      // The first time remainder will be minus from leftover offset above
      // but later on it will be the full content of the block
      int remainder = current_block.full_length() - offset;
      int tocopy = Math.min(len, remainder);
      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "BundleProtocol::produce copying %d/%d bytes from "
                      + "block type %s at offset %d",
                  tocopy, remainder, current_block.type().toString(), offset));

      current_block.owner().produce(bundle, current_block, data, offset, tocopy);

      len -= tocopy;

      // move the position of IByteBuffer
      data.position(data.position() + tocopy);

      // "if we've copied out the full amount the user asked for,
      // we're done. note that we need to check the corner case
      // where we completed the block exactly to properly set the
      // last bit" [DTN2]
      if (len == 0) {
        if ((tocopy == remainder)
            && ((current_block.flags()
                    & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode())
                > 0)) {

          last[0] = true;
        }

        break;
      }

      // "we completed the current block, so we're done if this
      // is the last block, even if there's space in the user buffer"
      // [DTN2]
      assert (tocopy == remainder);
      if ((current_block.flags() & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode())
          > 0) {

        last[0] = true;
        break;
      }

      // advance to next block
      current_block = iter.next();
      offset = 0;
    }

    BPF.getInstance()
        .getBPFLogger()
        .debug(
            TAG,
            String.format(
                "BundleProtocol::produce complete: "
                    + "produced %d bytes, bundle id %d, status is  %s",
                origlen - len, bundle.bundleid(), last[0] ? "complete" : "not complete"));

    data.position(old_position);
    return origlen - len;
  }
  /** "Constructor-like function to create a new custody signal bundle." [DTN2] */
  public static Bundle create_custody_signal(
      final Bundle orig_bundle,
      final EndpointID source_eid,
      boolean succeeded,
      BundleProtocol.custody_signal_reason_t reason) {

    Bundle bundle = new Bundle(location_t.MEMORY);

    bundle.source().assign(source_eid);
    if (orig_bundle.custodian().equals(EndpointID.NULL_EID())) {
      Logger.getInstance()
          .error(
              TAG,
              String.format(
                  "create_custody_signal(for bundle id %d): "
                      + "custody signal cannot be generated due to custodian is  null eid",
                  orig_bundle.bundleid()));
    }
    bundle.dest().assign(orig_bundle.custodian());
    bundle.replyto().assign(EndpointID.NULL_EID());
    bundle.custodian().assign(EndpointID.NULL_EID());
    bundle.set_is_admin(true);

    bundle.set_expiration(orig_bundle.expiration());

    int sdnv_encoding_len = 0;
    int signal_len = 0;

    // "format of custody signals:
    //
    // 1 byte admin payload type and flags
    // 1 byte status code
    // SDNV [Fragment Offset (if present)]
    // SDNV [Fragment Length (if present)]
    // SDNVx2 Time of custody signal
    // SDNVx2 Copy of bundle X's Creation Timestamp
    // SDNV Length of X's source endpoint ID
    // vari Source endpoint ID of bundle X

    //
    // first calculate the length
    //

    // the non-optional, fixed-length fields above:" [DTN2]
    signal_len = 1 + 1;

    // "the 2 SDNV fragment fields:" [DTN2]
    if (orig_bundle.is_fragment()) {
      signal_len += SDNV.encoding_len(orig_bundle.frag_offset());
      signal_len += SDNV.encoding_len(orig_bundle.orig_length());
    }

    // "Time field, set to the current time:" [DTN2]
    DTNTime now = new DTNTime();

    signal_len += DTNTime.SDNV_encoding_len(now);

    // "The bundle's creation timestamp:" [DTN2]
    signal_len += BundleTimestamp.SDNV_encoding_len(orig_bundle.creation_ts());

    // the Source Endpoint ID length and value
    signal_len += SDNV.encoding_len(orig_bundle.source().length()) + orig_bundle.source().length();

    //
    // "We got all the data ready, now format the buffer" [DTN2]
    //
    IByteBuffer bp = new SerializableByteBuffer(signal_len);
    int len = signal_len;
    bp.rewind();
    // "Admin Payload Type and flags" [DTN2]
    byte type_and_flags_byte =
        (byte) (BundleProtocol.admin_record_type_t.ADMIN_CUSTODY_SIGNAL.getCode() << 4);
    if (orig_bundle.is_fragment()) {
      type_and_flags_byte |= BundleProtocol.admin_record_flags_t.ADMIN_IS_FRAGMENT.getCode();
    }
    bp.put(type_and_flags_byte);
    len--;

    // Status_Flag_Byte consists of Success flag and reason code
    byte status_flag_byte = (byte) ((succeeded ? 1 : 0) << 7 | (reason.getCode() & 0x7f));
    bp.put(status_flag_byte);
    len--;

    // "The 2 Fragment Fields" [DTN2]
    if (orig_bundle.is_fragment()) {
      sdnv_encoding_len = SDNV.encode(orig_bundle.frag_offset(), bp, len);
      assert (sdnv_encoding_len > 0);
      len -= sdnv_encoding_len;

      sdnv_encoding_len = SDNV.encode(orig_bundle.orig_length(), bp, len);
      assert (sdnv_encoding_len > 0);
      len -= sdnv_encoding_len;
    }

    // DTNTime which is a time of signal field
    sdnv_encoding_len = DTNTime.SDNV_encoding_len(now);
    assert (sdnv_encoding_len > 0);
    DTNTime.encodeSDNV(now, bp);
    len -= sdnv_encoding_len;

    // "Copy of bundle X's Creation Timestamp" [DTN2]
    sdnv_encoding_len = BundleTimestamp.SDNV_encoding_len(orig_bundle.creation_ts());
    assert (sdnv_encoding_len > 0);
    BundleTimestamp.encodeSDNV(orig_bundle.creation_ts(), bp);
    len -= sdnv_encoding_len;

    // "The Endpoint ID length and data" [DTN2]
    sdnv_encoding_len = SDNV.encode(orig_bundle.source().length(), bp, len);
    assert (sdnv_encoding_len > 0);
    len -= sdnv_encoding_len;

    assert (len == orig_bundle.source().length());
    bp.put(orig_bundle.source().byte_array());

    //
    // "Finished generating the payload" [DTN2]
    //
    bp.rewind();
    bundle.payload().set_data(bp, signal_len);
    return bundle;
  }