/** Delete reassembly state for a bundle. */
  public void delete_fragment(Bundle fragment) {

    FragmentState state;

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

    // cons up the key to do the table lookup and look for reassembly state
    String[] hash_key = new String[1];
    get_hash_key(fragment, hash_key);

    state = fragment_table_.get(hash_key);

    // remove the fragment from the reassembly list
    boolean erased = state.erase_fragment(fragment);

    // fragment was not in reassembly list, simply return
    if (!erased) {
      return;
    }

    // note that the old fragment data is still kept in the
    // partially-reassembled bundle file, but there won't be metadata
    // to indicate as such

    // delete reassembly state if no fragments now exist
    if (state.num_fragments() == 0) {
      fragment_table_.remove(hash_key);
    }
  }
  /**
   * Given the given fragmentation threshold, determine whether the given bundle should be split
   * into several smaller bundles. If so, this returns true and generates a bunch of bundle received
   * events for the individual fragments.
   *
   * @param bundle Bundle to split into fragments
   * @param link Bundle to send on the given link
   * @param max_length Maximum length of the fragment
   * @return FragmentState that has the list of created fragments for the given bundle.
   */
  public FragmentState proactively_fragment(Bundle bundle, final Link link, int max_length) {
    int payload_len = bundle.payload().length();

    Bundle fragment;

    FragmentState state = new FragmentState(bundle);

    int todo = payload_len;
    int offset = 0;
    int count = 0;

    BlockInfoVec first_frag_blocks = new BlockInfoVec();
    BlockInfoVec all_frag_blocks = new BlockInfoVec();
    BlockInfoVec this_frag_blocks = first_frag_blocks;

    ListIterator<BlockInfo> entry = bundle.xmit_link_block_set().find_blocks(link).listIterator();

    while (entry.hasNext()) {
      BlockInfo block_info = entry.next();

      if ((block_info.type() == bundle_block_type_t.PRIMARY_BLOCK)
          || (block_info.type() == bundle_block_type_t.PAYLOAD_BLOCK)) {

        all_frag_blocks.add(block_info);
        first_frag_blocks.add(block_info);

      } else if ((block_info.flags() & block_flag_t.BLOCK_FLAG_REPLICATE.getCode()) > 0) {
        all_frag_blocks.add(block_info);
      } else {
        first_frag_blocks.add(block_info);
      }
    }

    do {
      fragment = create_fragment(bundle, link, this_frag_blocks, offset, max_length);
      assert (fragment != null) : TAG + ": proactively_fragment() fragment not valid";

      state.add_fragment(fragment);
      offset += fragment.payload().length();
      todo -= fragment.payload().length();
      this_frag_blocks = all_frag_blocks;
      ++count;

    } while (todo > 0);

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "proactively fragmenting " + "%s byte payload into %s %s byte fragments",
                payload_len, count, max_length));

    String[] hash_key = new String[1];
    get_hash_key(fragment, hash_key);
    fragment_table_.put(hash_key[0], state);

    return state;
  }
  /** 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);
  }
  /**
   * 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);
  }
  /**
   * Remove Fragment state from the fragment table
   *
   * @param fragment FragmentState to remove
   */
  public void erase_fragment_state(FragmentState fragment_state) {
    String[] hash_key = new String[1];

    get_hash_key(fragment_state.bundle(), hash_key);
    fragment_table_.remove(hash_key);
  }