/** "Remove matching deferred transmission entries." [DTN2] */
  void remove_from_deferred(final Bundle bundle, int actions) {

    ContactManager cm = BundleDaemon.getInstance().contactmgr();

    cm.get_lock().lock();
    try {
      final LinkSet links = cm.links();
      Iterator<Link> iter = links.iterator();

      while (iter.hasNext()) {
        Link link = iter.next();

        // "a bundle might be deleted immediately after being loaded
        // from storage, meaning that remove_from_deferred is called
        // before the deferred list is created (since the link isn't
        // fully set up yet). so just skip the link if there's no
        // router info, and therefore no deferred list" [DTN2]
        if (link.router_info() == null) {
          continue;
        }

        DeferredList deferred = deferred_list(link);
        ForwardingInfo info = deferred.find(bundle);
        if (info != null) {
          if ((info.action().getCode() & actions) > 0) {
            Log.d(
                TAG, String.format("removing bundle %s from link %s deferred list", bundle, link));
            deferred.del(bundle);
          }
        }
      }
    } finally {
      cm.get_lock().unlock();
    }
  }
  @Override
  protected void handle_link_created(LinkCreatedEvent event) {
    Link link = event.link();
    assert (link != null) : "TableBasedRouter: handle_link_created: link is null";
    assert (!link.isdeleted()) : "TableBasedRouter: handle_link_available: link is deleted";

    link.set_router_info(new DeferredList(link));

    add_nexthop_route(link);
  }
  @Override
  protected void handle_contact_down(ContactDownEvent event) {
    Link link = event.contact().link();
    assert (link != null) : "TableBasedRouter : handle_contact_down : link is null";
    assert (!link.isdeleted()) : "TableBasedRouter : handle_contact_down : link is deleted";

    // "if there are any bundles queued on the link when it goes down,
    // schedule a timer to cancel those transmissions and reroute the
    // bundles in case the link takes too long to come back up" [DTN2]

    int num_queued = link.queue().size();
    if (num_queued != 0) {
      RerouteTimer reroute_timer = reroute_timers_.get(link.name());
      if (reroute_timer == null) {
        Log.d(
            TAG,
            String.format(
                "link %s went down with %d bundles queued, "
                    + "scheduling reroute timer in %d seconds",
                link.name(), num_queued, link.params().potential_downtime()));
        RerouteTimer t = new RerouteTimer(this, link);
        t.schedule_in(link.params().potential_downtime());

        reroute_timers_.put(link.name(), t);
      }
    }
  }
  @Override
  protected void handle_link_deleted(LinkDeletedEvent event) {
    Link link = event.link();
    assert (link != null) : "TableBasedRouter: handle_link_deleted: link is null";

    route_table_.del_entries_for_nexthop(link);

    RerouteTimer t = reroute_timers_.get(link.name());
    if (t != null) {
      Log.d(TAG, String.format("link %s deleted, cancelling reroute timer", link.name()));
      reroute_timers_.remove(link.name());
      t.cancel();
    }
  }
  /**
   * "Helper accessor to return the deferred queue for a link" [DTN2]
   *
   * @param link
   * @return
   */
  public DeferredList deferred_list(final Link link) {
    DeferredList dq = (DeferredList) link.router_info();
    assert dq != null : "TableBasedRouter: deferred_list() deferedList is null";

    if (dq == null) dq = new DeferredList(link);
    return dq;
  }
  @Override
  protected void handle_link_available(LinkAvailableEvent event) {
    Link link = event.link();
    assert (link != null) : "TableBasedRouter: handle_link_available: link is null";
    assert (!link.isdeleted()) : "TableBasedRouter: handle_link_available: link is deleted";

    // "if it is a discovered link, we typically open it" [DTN2]
    if (config_.open_discovered_links()
        && !link.isopen()
        && link.type() == Link.link_type_t.OPPORTUNISTIC
        && event.reason() == ContactEvent.reason_t.DISCOVERY) {
      actions_.open_link(link);
    }

    // "check if there's anything to be forwarded to the link" [DTN2]
    check_next_hop(link);
  }
  @Override
  protected void handle_contact_up(ContactUpEvent event) {
    Link link = event.contact().link();
    assert (link != null) : "TabledBasedRouter: handle_contact_up : link is null";
    assert (!link.isdeleted()) : "TabledBasedRouter: handle_contact_up : link is deleted";

    if (!link.isopen()) {
      Log.e(
          TAG,
          String.format("contact up(link %s): event delivered but link not open", link.name()));
    }

    add_nexthop_route(link);
    check_next_hop(link);

    // "check if there's a pending reroute timer on the link, and if
    // so, cancel it.
    //
    // note that there's a possibility that a link just bounces
    // between up and down states but can't ever really send a bundle
    // (or part of one), which we don't handle here since we can't
    // distinguish that case from one in which the CL is actually
    // sending data, just taking a long time to do so." [DTN2]

    RerouteTimer reroute_timer = reroute_timers_.get(link.name());
    if (reroute_timer != null) {
      Log.d(TAG, String.format("link %s reopened, cancelling reroute timer", link.name()));
      reroute_timers_.remove(link.name());
      reroute_timer.cancel();
    }
  }
  /**
   * "When new links are added or opened, and if we're configured to add nexthop routes, try to add
   * a new route for the given link." [DTN2]
   */
  public void add_nexthop_route(final Link link) {

    // "If we're configured to do so, create a route entry for the eid
    // specified by the link when it connected, using the
    // scheme-specific code to transform the URI to wildcard
    // the service part" [DTN2]
    EndpointID eid = link.remote_eid();
    if (config_.add_nexthop_routes() && !eid.equals(EndpointID.NULL_EID())) {
      EndpointIDPattern eid_pattern = new EndpointIDPattern(link.remote_eid());

      // "attempt to build a route pattern from link's remote_eid" [DTN2]
      if (!eid_pattern.append_service_wildcard())
        // "else assign remote_eid as-is" [DTN2]
        eid_pattern.assign(link.remote_eid());

      RouteEntryVec ignored = new RouteEntryVec();
      if (route_table_.get_matching(eid_pattern, link, ignored) == 0) {
        RouteEntry entry = new RouteEntry(eid_pattern, link);
        entry.set_action(ForwardingInfo.action_t.FORWARD_ACTION);
        add_route(entry);
      }
    }
  }
 public DeferredList(final Link link) {
   list_ = new BundleList(link.name() + ":deferred");
   info_ = new HashMap<Bundle, ForwardingInfo>();
   count_ = 0;
 }
  /**
   * Helper function for rerouting
   *
   * @param link
   */
  void reroute_bundles(final Link link) {

    assert !link.isdeleted() : "TableBasedRouter : reroute_bundles, link is deleted";

    // "if the reroute timer fires, the link should be down and there
    // should be at least one bundle queued on it." [DTN2]
    if (link.state() != Link.state_t.UNAVAILABLE) {
      Log.w(
          TAG,
          String.format(
              "reroute timer fired but link %s state is %s, not UNAVAILABLE",
              link, link.state().toString()));
      return;
    }

    Log.d(
        TAG,
        String.format(
            "reroute timer fired -- cancelling %s bundles on link %s",
            link.queue().size(), link.toString()));

    link.queue().get_lock().lock();
    try {
      while (!link.queue().empty()) {
        Bundle bundle = link.queue().front();
        actions_.cancel_bundle(bundle, link);
        assert !bundle.is_queued_on(link.queue())
            : "TableBasedRouter : reroute_bundles, bundle is not queued on link";
      }

      // "there should never have been any in flight since the link is
      // unavailable" [DTN2]
      assert link.inflight().empty()
          : "TableBasedRouter : reroute_bundles, link on flight list is not empty";

    } finally {
      link.queue().get_lock().unlock();
    }
  }
  /**
   * "Called when the next hop link is available for transmission (i.e. either when it first arrives
   * and the contact is brought up or when a bundle is completed and it's no longer busy).
   *
   * <p>Loops through the bundle list and calls fwd_to_matching on all bundles." [DTN2]
   */
  protected void check_next_hop(Link next_hop) {

    // "if the link isn't open, there's nothing to do now" [DTN2]
    if (!next_hop.isopen()) {
      Log.d(
          TAG,
          String.format(
              "check_next_hop %s -> %s: link not open...", next_hop.name(), next_hop.nexthop()));
      return;
    }

    // "if the link queue doesn't have space (based on the low water
    // mark) don't do anything" [DTN2]
    if (!next_hop.queue_has_space()) {
      Log.d(
          TAG,
          String.format(
              "check_next_hop %s -> %s: no space in queue...",
              next_hop.name(), next_hop.nexthop()));
      return;
    }

    Log.d(
        TAG,
        String.format(
            "check_next_hop %s -> %s: checking deferred bundle list...",
            next_hop.name(), next_hop.nexthop()));

    // "because the loop below will remove the current bundle from
    // the deferred list, invalidating any iterators pointing to its
    // position, make sure to advance the iterator before processing
    // the current bundle" [DTN2]
    DeferredList deferred = deferred_list(next_hop);

    deferred.list().get_lock().lock();
    try {

      Iterator<Bundle> iter = deferred.list().begin();
      while (iter.hasNext()) {
        if (next_hop.queue_is_full()) {
          Log.d(
              TAG,
              String.format(
                  "check_next_hop %s: link queue is full, stopping loop", next_hop.name()));
          break;
        }

        Bundle bundle = iter.next();

        ForwardingInfo info = deferred.find(bundle);
        assert info != null
            : "TableBasedRouter: check_next_hop, ForwardingInfo regarding Bundle is null";

        // "if should_fwd returns false, then the bundle was either
        // already transmitted or is in flight on another node. since
        // it's possible that one of the other transmissions will
        // fail, we leave it on the deferred list for now, relying on
        // the transmitted handlers to clean up the state" [DTN2]
        if (!this.should_fwd(bundle, next_hop, info.action())) {
          Log.d(TAG, String.format("check_next_hop: not forwarding to link %s", next_hop.name()));
          continue;
        }

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

        // "remove the bundle from the deferred list" [DTN2]
        Log.d(
            TAG,
            String.format(
                "check_next_hop: sending bundle %d to %s", bundle.bundleid(), next_hop.name()));

        iter.remove();
        actions_.queue_bundle(bundle, next_hop, info.action(), info.custody_spec());
      }
    } catch (BundleListLockNotHoldByCurrentThread e) {
      Log.e(TAG, "Table Based Router " + e.toString());
    } finally {
      deferred.list().get_lock().unlock();
    }
  }
  /** "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;
  }