/**
   * "Check whether the Bundle should be forwarded to the give route or not" [DTN2]
   *
   * @param bundle the Bundle to check
   * @param route the route to check
   */
  protected boolean should_fwd(final Bundle bundle, RouteEntry route) {

    if (route == null) return false;

    EndpointID prevhop = reception_cache_.lookup(bundle);
    if (prevhop != null) {
      if (prevhop.equals(route.link().remote_eid()) && !prevhop.equals(EndpointID.NULL_EID())) {
        Log.d(
            TAG,
            String.format(
                "should_fwd bundle %d: " + "skip %s since bundle arrived from the same node",
                bundle.bundleid(), route.link().name()));
        return false;
      }
    }

    return super.should_fwd(bundle, route.link(), route.action());
  }
  /**
   * "Check the route table entries that match the given bundle and have not already been found in
   * the bundle history. If a match is found, call fwd_to_nexthop on it." [DTN2]
   *
   * @param bundle the bundle to forward
   * @return "the number of links on which the bundle was queued (i.e. the number of matching route
   *     entries.)" [DTN2]
   */
  protected int route_bundle(Bundle bundle) {

    RouteEntryVec matches = new RouteEntryVec();

    Log.d(TAG, String.format("route_bundle: checking bundle %d", bundle.bundleid()));

    // "check to see if forwarding is suppressed to all nodes" [DTN2]
    if (bundle
            .fwdlog()
            .get_count(
                EndpointIDPattern.WILDCARD_EID(),
                ForwardingInfo.state_t.SUPPRESSED.getCode(),
                ForwardingInfo.ANY_ACTION)
        > 0) {
      Log.i(
          TAG,
          String.format(
              "route_bundle: " + "ignoring bundle %d since forwarding is suppressed",
              bundle.bundleid()));
      return 0;
    }

    Link null_link = null;
    route_table_.get_matching(bundle.dest(), null_link, matches);

    // "sort the matching routes by priority, allowing subclasses to
    // override the way in which the sorting occurs" [DTN2]
    sort_routes(matches);

    Log.d(
        TAG,
        String.format(
            "route_bundle bundle id %d: checking %d route entry matches",
            bundle.bundleid(), matches.size()));

    int count = 0;
    Iterator<RouteEntry> itr = matches.iterator();
    while (itr.hasNext()) {
      RouteEntry route = itr.next();
      Log.d(
          TAG,
          String.format(
              "checking route entry %s link %s (%s)",
              route.toString(), route.link().name(), route.link()));

      if (!should_fwd(bundle, route)) {
        continue;
      }

      if (deferred_list(route.link()).list().contains(bundle)) {
        Log.d(
            TAG,
            String.format(
                "route_bundle bundle %d: " + "ignoring link %s since already deferred",
                bundle.bundleid(), route.link().name()));
        continue;
      }

      // "because there may be bundles that already have deferred
      // transmission on the link, we first call check_next_hop to
      // get them into the queue before trying to route the new
      // arrival, otherwise it might leapfrog the other deferred
      // bundles" [DTN2]
      check_next_hop(route.link());

      if (!fwd_to_nexthop(bundle, route)) {
        continue;
      }

      ++count;
    }

    Log.d(
        TAG,
        String.format(
            "route_bundle bundle id %d: forwarded on %d links", bundle.bundleid(), count));
    return count;
  }
  /** "Try to forward a bundle to a next hop route." [DTN2] */
  protected boolean fwd_to_nexthop(Bundle bundle, RouteEntry route) {

    Link link = route.link();

    // "if the link is available and not open, open it" [DTN2]
    if (link.isNotUnavailable() && (!link.isopen()) && (!link.isopening())) {
      Log.d(TAG, String.format("opening %s because a message is intended for it", link.name()));
      actions_.open_link(link);
    }

    // "if the link is open and has space in the queue, then queue the
    // bundle for transmission there" [DTN2]
    if (link.isopen() && !link.queue_is_full()) {
      Log.d(TAG, String.format("queuing %d on %s", bundle.bundleid(), link.name()));
      actions_.queue_bundle(bundle, link, route.action(), route.custody_spec());
      return true;
    }

    // "otherwise we can't send the bundle now, so put it on the link's
    // deferred list and log reason why we can't forward it" [DTN2]
    DeferredList deferred = deferred_list(link);
    if (!bundle.is_queued_on(deferred.list())) {
      ForwardingInfo info =
          new ForwardingInfo(
              ForwardingInfo.state_t.NONE,
              route.action(),
              link.name_str(),
              0xffffffff,
              link.remote_eid(),
              route.custody_spec());
      deferred.add(bundle, info);
    } else {
      Log.w(
          TAG,
          String.format(
              "bundle %d already exists on deferred list of link %s",
              bundle.bundleid(), link.name()));
    }

    if (!link.isNotUnavailable()) {
      Log.d(
          TAG,
          String.format(
              "can't forward bundle %d to %s because link not available",
              bundle.bundleid(), link.name()));
    } else if (!link.isopen()) {
      Log.d(
          TAG,
          String.format(
              TAG,
              "can't forward bundle %d to %s because link not open",
              bundle.bundleid(),
              link.name()));
    } else if (link.queue_is_full()) {
      Log.d(
          TAG,
          String.format(
              TAG,
              "can't forward bundle %d to %s because link queue is full",
              bundle.bundleid(),
              link.name()));
    } else {
      Log.d(TAG, String.format(TAG, "can't forward %d to %s", bundle.bundleid(), link.name()));
    }

    return false;
  }