apps/tca_admin/TcaController.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/tca_admin/TcaController.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,959 @@
+/*
+ *    Copyright 2005-2006 University of Waterloo
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <dtn-config.h>
+#endif
+
+//#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+//#include <strings.h>
+//#include <stdlib.h>
+//#include <sys/time.h>
+
+//#include <string>
+#include "dtn_api.h"
+#include "TcaController.h"
+
+
+static const int debug = 1;
+
+
+// RECV_TIMEOUT can be made very large. It's just used as a timeout for a
+// blocking function call to receive bundle.
+static const int RECV_TIMEOUT = 30000;  // 30s
+
+
+// REG_EXPIRATION_TIME is how long the registration lasts. We want it to
+// last forever, but we're up against maxint.
+// The value specified here is multiplied by 1000 in class Registration
+// to get milliseconds, and I think that deeper in the actual timer code it's
+// converted to a signed int, so 2000000 (23 days) is about as high as we
+// can go here.
+static const u_int32_t REG_EXPIRATION_TIME = 2000000; 
+
+
+
+// Fake spec constructor. For conveniently building a dtn_bundle_spec_t
+
+bool
+make_spec(dtn_bundle_spec_t& spec,
+          std::string source,
+          std::string dest,
+          std::string replyto,
+          int expiration,
+          dtn_bundle_priority_t priority = COS_NORMAL,
+          dtn_bundle_delivery_opts_t dopts = DOPTS_NONE
+          )
+{
+    memset(&spec, 0, sizeof(spec));
+
+    if (dtn_parse_eid_string(&spec.source, source.c_str()))
+    {
+        fprintf(stderr, "make_spec: invalid source eid '%s'\n",
+                    source.c_str());
+        return false;
+    }
+
+    if (dtn_parse_eid_string(&spec.dest, dest.c_str()))
+    {
+        fprintf(stderr, "make_spec: invalid dest eid '%s'\n",
+                    dest.c_str());
+        return false;
+    }
+
+    if (dtn_parse_eid_string(&spec.replyto, replyto.c_str()))
+    {
+        fprintf(stderr, "make_spec: invalid replyto eid '%s'\n",
+                    replyto.c_str());
+        return false;
+    }
+
+    spec.priority = priority;
+    spec.dopts = dopts;
+    spec.expiration = expiration;
+
+    return true;
+}
+
+
+static bool
+check_nargs(const dtn::TcaControlBundle& cb, uint n_expected)
+{
+    if (cb.args_.size() != n_expected)
+    {
+        printf("TcaController: bundle '%s' contains wrong number of args. "
+                "%d expected.\n", cb.str().c_str(), n_expected);
+        return false;
+    }
+    return true;
+}
+
+
+TcaController::TcaController(TcaController::Role role,
+                             const std::string& link_id,
+                             const std::string& ask_addr,
+                             const std::string& adv_str,
+                             int registry_ttl, int control_ttl)
+    : role_(role), link_id_(link_id),
+      ask_addr_(ask_addr), adv_str_(adv_str),
+      registry_ttl_(registry_ttl), control_ttl_(control_ttl)
+
+{
+
+}
+
+
+TcaController::~TcaController()
+{
+    dtn_close(handle_);
+}
+
+
+bool
+TcaController::dtn_reg(dtn_endpoint_id_t& eid, dtn_reg_id_t& id)
+{   
+    // register eid for specified app, eg. "tca://hail/admin"
+
+    // create a new registration
+    dtn_reg_info_t reginfo;
+    // dtn_reg_id_t regid;
+    int ret;
+
+    memset(&reginfo, 0, sizeof(reginfo));
+    dtn_copy_eid(&reginfo.endpoint, &eid);
+    reginfo.flags = DTN_REG_DEFER;
+    reginfo.regid = DTN_REGID_NONE;
+    reginfo.expiration = REG_EXPIRATION_TIME;
+    if ((ret = dtn_register(handle_, &reginfo, &id)) != 0) {
+        fprintf(stderr, "error creating registration: %d (%s)\n",
+                ret, dtn_strerror(dtn_errno(handle_)));
+        return false;
+    }    
+    // if (debug) printf("dtn_register succeeded, id 0x%x\n", id);
+    
+    printf("TcaController::dtn_reg: app registered as %s, id=0x%x\n",
+            eid.uri, id);
+
+    return true;
+}
+
+
+bool
+TcaController::init(bool tidy)
+{
+    // open the ipc handle
+    int err = dtn_open(&handle_);
+    if (err != DTN_SUCCESS) {
+        fprintf(stderr, "fatal error opening dtn handle: %s\n",
+                dtn_strerror(err));
+        return false;
+    }
+
+    printf("TcaController::init: dtn_open succeeded\n");
+
+    // build local eid and register it:
+    dtn_reg_id_t app_id;
+    dtn_build_local_eid(handle_, &local_eid_, "/admin");
+    if (!dtn_reg(local_eid_, app_id)) return false;
+
+    /*
+    // test: register another eid and make sure that works
+    dtn_reg_id_t test_id;
+    dtn_endpoint_id_t test_eid;
+    dtn_build_local_eid(handle_, &test_eid, "/testing123");
+    if (!dtn_reg(test_eid, test_id)) return false;
+    */
+
+    // discard any pending bundles iff 'tidy' option specified
+    if (tidy) eat_bundles();
+
+    if (role_ == TCA_GATEWAY)
+    {
+        // now initialize the list of live registry nodes
+        registry_.init_nodes();
+        if (!registry_.init_addrs())
+        {
+            // This is an unrecoverable failure condition -- no dht nodes
+            // available. The gateway will not be able to do its job,
+            // so we might as well exit().
+            fprintf(stderr,
+                "TcaController fatal error: no registry nodes available!\n");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+void
+TcaController::run()
+{
+
+    if (ask_addr_.length() != 0)
+    {
+        // user has specified an ask address. Send out an ask bundle.
+        ask(ask_addr_);
+    }
+   
+    dtn_bundle_spec_t spec;
+    std::string payload;
+
+    for (;;)
+    {
+        if (recv_bundle(spec, payload, RECV_TIMEOUT))
+        {
+            handle_bundle_received(spec, payload);
+        }
+    }
+}
+
+
+bool
+TcaController::handle_bundle_received(const dtn_bundle_spec_t& spec,
+                                      const std::string& payload)
+{
+    dtn::TcaControlBundle cb(payload);
+
+    // handle control bundles:
+    switch (cb.type_)
+    {
+        case dtn::TcaControlBundle::CB_LINK_ANNOUNCE:
+            handle_link_announce(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ASK:
+            handle_ask(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ASK_RECEIVED:
+            handle_ask_received(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ASK_SENT:
+            handle_ask_sent(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ADV:
+            handle_adv(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ADV_SENT:
+            handle_adv_sent(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_REG_RECEIVED:
+            handle_reg_received(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_UNB:
+            handle_unb(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_COA:
+            // all of the real coa logic is in the bundle_tranmitted handler
+            // in bundle layer
+            break;
+        case dtn::TcaControlBundle::CB_COA_SENT:
+            handle_coa_sent(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_ROUTES:
+            handle_routes(spec, cb);
+            break;
+        case dtn::TcaControlBundle::CB_LINK_AVAILABLE:
+            break;
+        case dtn::TcaControlBundle::CB_LINK_UNAVAILABLE:
+            break;
+        case dtn::TcaControlBundle::CB_CONTACT_UP:
+            break;
+        case dtn::TcaControlBundle::CB_CONTACT_DOWN:
+            break;
+        default:
+            printf("TcaController: unrecognized bundle code received: '%s'\n",
+                cb.code_.c_str());
+    }
+
+    return true;
+}
+
+
+
+bool
+TcaController::handle_reg_received(const dtn_bundle_spec_t& spec,
+                                   const dtn::TcaControlBundle& cb)
+{
+    switch (role_)
+    {
+        case TCA_MOBILE:
+            // TODO: This isn't an error. Just add the link addr and forward
+            // the bundle to default route. Exactly the same logic as
+            // for a router.
+            return route_reg(spec, cb);
+            break;
+        case TCA_ROUTER:
+            return route_reg(spec, cb);
+            break;
+        case TCA_GATEWAY:
+            return gate_reg(spec, cb);
+            break;
+    }   
+
+    return false;
+}
+
+
+bool
+TcaController::handle_unb(const dtn_bundle_spec_t& spec,
+                          const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    
+    if (role_ != TCA_GATEWAY)
+    {
+        printf("TcaController error: non-gateway received unb bundle.\n");
+        return false;
+    }
+
+    TcaEndpointID dest_eid(cb.args_[0]);
+    RegRecord rr;
+
+    if (!get_registration(dest_eid, rr))
+    {
+        printf("TcaController: bind failed: unregistered node [%s]\n",
+            dest_eid.c_str());
+        // TODO: What should happen here?
+        // dest may never have registered, or may have previously registered
+        // but expired. Depending how we want to handle registration
+        // lifetime, we may need to re-try this get_registration later.
+        return false;
+    }
+
+    // Found it! Add a route.
+    // Note: There's no point deleting old routes for this endpoint. We
+    // got here because no old routes exist!
+
+    if (!add_route(dest_eid.str(), rr.link_addr_)) return false;
+
+    return true;
+}
+
+
+bool
+TcaController::handle_coa_sent(const dtn_bundle_spec_t& spec,
+                               const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    
+    if (role_ == TCA_MOBILE)
+    {
+        printf("TcaController error: mobile received coa_sent bundle.\n");
+        return false;
+    }
+
+    // TODO: update this to use ControlBundle class
+    const std::string& src = cb.args_[0];   // coa src (gateway)
+    const std::string& dest = cb.args_[1];  // coa dest (mobile)
+    const std::string& link = cb.args_[2];  // last hop to mobile
+
+    // if we're a router or a gateway, delete routes to the bundle dest:
+    TcaEndpointID pattern = dest;
+    pattern.set_app("*");
+    del_route(pattern.str());
+    
+    // if the coa bundle originated at *this* gateway node then we have to
+    // update the registry and finally add the route back to the 
+    // registering mobile
+    if (src == local_eid_.uri)
+    {
+        TcaEndpointID tca_dest = dest;
+        if (!do_registration(tca_dest, link_id_)) return false;
+
+        tca_dest.set_app("*");
+        if (!add_route(tca_dest.str(), link)) return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaController::handle_link_announce(const dtn_bundle_spec_t& spec,
+                                    const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+            
+    // Note: the port number is omitted from the link_announce bundle.
+    // It is up to TcaController to decide which port to probe.
+    if (!check_nargs(cb, 1)) return false;
+    std::string link_spec = cb.args_[0];
+    link_spec += ":5000";
+    return ask(link_spec);
+}
+
+
+bool
+TcaController::handle_ask(const dtn_bundle_spec_t& spec,
+                          const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    (void)cb;
+    printf("error -- we should never receive an ask bundle!\n");
+
+    return true;
+}
+
+
+bool
+TcaController::handle_ask_received(const dtn_bundle_spec_t& spec,
+                                   const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    dtn::TcaWrappedBundle wb(cb);
+
+    // respond by adding a route and sending adv
+
+    TcaEndpointID src_eid = wb.source();
+    src_eid.set_app("*");
+
+    if (!add_route(src_eid.str(), wb.args_[2])) return false;
+    
+    // respond with adv
+    // note that adv is a regular bundle addressed to asking node eid
+    // (not an inter-layer WrappedBundle)
+    std::string response = "adv:";
+    response += wb.dest();  // echo original ask dest
+    response += "\t";
+    response += adv_str_;       // send adv string
+    send_bundle(wb.source(), response);
+    
+    return true;
+}
+
+
+bool
+TcaController::handle_ask_sent(const dtn_bundle_spec_t& spec,
+                               const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    dtn::TcaWrappedBundle wb(cb);
+
+    // respond by deleting the route
+    TcaEndpointID dest_eid = wb.dest(); // dest of original ask
+    dest_eid.set_app("*");
+
+    if (!del_route(dest_eid.str())) return false;
+    return true;
+}
+
+
+bool
+TcaController::handle_adv(const dtn_bundle_spec_t& spec,
+                          const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    (void)cb;
+    // TODO: This is the place to be smart and try to decide on a
+    // router to use for the default route.
+    // For now, do nothing.
+    return true;
+}
+
+
+bool
+TcaController::handle_adv_sent(const dtn_bundle_spec_t& spec,
+                               const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    dtn::TcaWrappedBundle wb(cb);
+
+    // respond by deleting the route
+    // TODO: This is actually more complicated! We should only delete the
+    // route if it was created by the ask bundle that spawned this adv
+    // (ie. if it was a pre-existing route, we should keep it!)
+    // Best idea on how to do that is to use the route table to store
+    // extra info on creating the entry, indicating it's a temp route.
+    TcaEndpointID dest_eid = wb.dest(); // dest of original ask
+    dest_eid.set_app("*");
+
+    if (!del_route(dest_eid.str())) return false;
+    return true;
+}
+
+
+bool
+TcaController::handle_routes(const dtn_bundle_spec_t& spec,
+                             const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+
+    printf("routes:\n");
+    for (unsigned int i=0; i<cb.args_.size(); ++i)
+    {
+        printf("    %s \n", cb.args_[i].c_str());
+    }
+    return true;
+}
+
+
+bool
+TcaController::route_reg(const dtn_bundle_spec_t& spec,
+                         const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    // A (small) problem is that we will get 2 links for a node that
+    // already has an link defined explicitly in the dtn.conf file.
+    // Another link will be auto-created with a different name, same params.
+    // We could be smarter and check for this.
+
+    if (!check_nargs(cb, 4)) return false;
+
+    dtn::TcaWrappedBundle wb(cb);
+
+    std::string mobile_eid = wb.args_[2];
+    std::string last_hop = wb.args_[3];
+
+    if (last_hop != "NULL")
+    {
+        // The register bundle did not originate at this node.
+        // Add a link and route back to mobile.
+
+        mobile_eid = wb.args_[2];
+        last_hop = wb.args_[3];
+
+        TcaEndpointID pattern(mobile_eid);
+
+        // Delete the old route to mobile.
+        // This is the correct behaviour because there should not
+        // be any static routes for a tca mobile, only the one obtained
+        // through the previous registration, and that one's now invalid.
+        pattern.set_app("*");
+        printf("deleteing routes for %s\n", pattern.str().c_str());
+        if (!del_route(pattern.str())) return false;
+
+        // Create the new route to mobile.
+        if (!add_route(pattern.str(), last_hop)) return false;
+    }
+
+    // Now propagate the registry bundle (actually send a new one with
+    // last_hop set to local node).
+    // Skip this step if we're a gateway.
+    
+    if (role_ != TCA_GATEWAY)
+    {
+        std::string new_payload = "register:";
+        new_payload += mobile_eid;      // original mobile id
+        new_payload += "\t";
+        new_payload += link_id_;        // link id of this router (last hop)
+
+        if (!send_bundle("tca://registry", new_payload)) return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaController::gate_reg(const dtn_bundle_spec_t& spec,
+                        const dtn::TcaControlBundle& cb)
+{
+    (void)spec;
+    dtn::TcaWrappedBundle wb(cb);
+
+    std::string mobile_eid = wb.args_[2];
+    std::string last_hop = wb.args_[3];
+
+    TcaEndpointID src(mobile_eid);
+
+    // First, get the old gateway for the source endpoint. We'll need it later.
+    // It's ok if it doesn't exist -- then this is a new registration.
+    // printf("TcaController: reading old registration...\n");
+    RegRecord old_reg;
+    bool prev_reg_exists = get_registration(src, old_reg);
+
+    if (prev_reg_exists) 
+    {
+        // if (old_gateway == me) don't bother re-registering
+        if (old_reg.link_addr_ == link_id_)
+        {
+            printf("TcaController: ignoring re-registration with same"
+                    " gateway\n");
+
+            // TODO: We still need to destruct old reverse path, even
+            // if the mobile has re-registerd in the same gateway.
+            // In this case, only part of the route is different.
+            // Probably each router should check for this case on
+            // handling the reg bundle -- if duplicate for same endpoint
+            // destruct old route and construct new one. Don't even propagate
+            // the reg. up to the gateway.
+            return true;
+        }
+        else
+        {
+            printf("TcaController: initiating change-of-address\n");
+
+            // There is a previous registration with another gateway.
+            // We need to send a coa bundle to that gateway. Don't re-register
+            // until this is done (handle_bundle_transmitted).
+
+            // First we need to create a route to the old gateway,
+            // as in gate_unbound_bundle.
+
+            // Construct a route pattern based on the source eid.
+            // Note: we use the special application "admin.coa" to ensure
+            // that this route is only used for the coa bundle.
+            TcaEndpointID pattern = src;
+            pattern.set_app("admin.coa");
+
+            printf("TcaController: adding route %s -> %s\n",
+                    pattern.c_str(), old_reg.link_addr_.c_str());
+
+            if (!add_route(pattern.str(), old_reg.link_addr_)) return false;
+
+
+            TcaEndpointID dest_eid = src;
+            dest_eid.set_app("admin.coa");
+
+            // Construct coa bundle.
+            // source:  this gateway
+            // dest:    original mobile with special app "admin.coa"
+
+            // This is a bit of a kludge...
+            // The last thing done by the gateway after issuing the coa
+            // bundle and updating the registry, is to create the route
+            // back to the mobile (just like during routing of a reg bundle)
+            // We'll need the last_hop link to do that. So we put it in
+            // the body of the coa bundle, to be used on this very same node
+            // on bundle transmitted.
+
+            std::string coa_payload = "coa:";
+            coa_payload += last_hop;
+
+            dtn_bundle_spec_t coa_spec;
+            if (!make_spec(coa_spec, local_eid_.uri, dest_eid.str(),
+                    local_eid_.uri, control_ttl_, COS_NORMAL, DOPTS_NONE))
+                return false;
+
+            // We need to set non-zero expiration or else the bundle
+            // expires as soon as it arrives.
+            // The default values are ok for the rest of the bundle fields.
+
+            if (!send_bundle(coa_spec, coa_payload)) return false;
+
+            // Note: I thought we might have a problem here. The send_bundle
+            // will cause a BundleReceivedEvent in the daemon, but it's ok
+            // because the BL checks for a null last-hop, indicating that
+            // the bundle came from this node.
+        }
+
+        // Note: We do *not* update the link and route just yet.
+        // Do it on handle_bundle_transmitted instead, at which point
+        // we should delete the temporary admin.coa route (to the mobile's
+        // old location and add a new route to the mobile's new location.
+    }
+
+    else
+    {
+        // No previous registration so no change-of-address required.
+        // Go ahead and register.
+        // update the registry entry for the source endpoint
+        // printf("TcaController: doing new registration...\n");
+        if (!do_registration(src, link_id_)) return false;
+
+        // update local link and route
+        if (!route_reg(spec, cb)) return false;
+    }
+
+    return true;
+
+}
+
+
+bool
+TcaController::ask(const std::string& link)
+{
+    // "ask" on an unknown link
+    // Idea: create temporary route and send ask bundle
+    // on handling the corresponding adv, delete the route
+    
+
+    std::string host = "tca://anonymous";
+    // omit prefix "tcp://" due to slashes:
+    host += link.substr(6, link.length());
+
+    std::string pattern = host + "/*";
+
+    if (!add_route(pattern, link)) return false;
+
+    std::string payload = "ask:";
+    payload += link_id_;            // our own link, for return route
+
+    std::string dest = host + "/admin.ask";
+
+    if (!send_bundle(dest, payload))
+    {
+        fprintf(stderr,
+                "TcaController::ask: error: failed to send ask bundle\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaController::get_routes()
+{
+    if (!send_bundle("tca://localhost/bundlelayer", "get_routes:tca://*"))
+    {
+        fprintf(stderr, "error: failed to send get_routes bundle\n");
+        return false;
+    }
+   
+    return true;
+}
+
+
+bool
+TcaController::add_route(const std::string& route_pattern,
+                         const std::string& link)
+{
+    std::string body = "add_route:";
+    body += route_pattern;
+    body += "\t";
+    body += link;
+
+    // testing -- try to add a route
+    if (!send_bundle("tca://localhost/bundlelayer", body))
+    {
+        fprintf(stderr, "error: failed to send add_route bundle\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaController::del_route(const std::string& route_pattern)
+{
+    std::string body = "del_route:";
+    body += route_pattern;
+
+    if (!send_bundle("tca://localhost/bundlelayer", body))
+    {
+        fprintf(stderr, "error: failed to send del_route bundle\n");
+        return false;
+    }
+   
+    return true;
+}
+
+
+bool
+TcaController::get_registration(const TcaEndpointID& eid, RegRecord& rr)
+{
+    rr.host_ = eid.get_hostid();
+    if (registry_.read(rr))
+    {
+        printf("TcaController: found registry entry %s\n", rr.str().c_str());
+        return true;
+    }
+    else
+    {
+        printf("TcaController: no registry entry for host %s\n",
+                rr.host_.c_str());
+        return false;
+    }
+}
+
+
+// TODO: Make this async, with periodic refresh
+bool
+TcaController::do_registration(const TcaEndpointID& eid,
+                               const std::string& link_addr)
+{
+    RegRecord rr(eid.get_hostid(), link_addr);
+
+    if (registry_.write(rr, registry_ttl_))
+    {
+        printf("TcaController: wrote registry entry %s\n", rr.str().c_str());
+        return true;
+    }
+    else
+    {
+        printf("TcaController: failed to write registry entry %s\n",
+                    rr.str().c_str());
+        return false;
+    }
+
+}
+
+
+bool
+TcaController::test_all()
+{
+    get_routes();
+
+    add_route("tca://booyah", "tcp://9.8.7.6:54321");
+    sleep(5);
+
+    get_routes();
+
+    del_route("tca://booyah");
+    sleep(5);
+
+    get_routes();
+    
+    return true;
+}
+
+
+void
+TcaController::eat_bundles(bool verbose)
+{
+    // check for any bundles queued for this registration and discard them.
+
+    dtn_bundle_spec_t recv_spec;
+    std::string payload;
+
+    printf("checking for queued bundles...\n");
+
+    if (verbose)
+    {
+        while (recv_bundle(recv_spec, payload, 0))
+            printf("    discarding bundle: %s\n", payload.c_str());
+    }
+    else
+    {
+        dtn_bundle_payload_t    recv_payload;
+        int ret;
+        do
+        {
+            memset(&recv_spec, 0, sizeof(recv_spec));
+            memset(&recv_payload, 0, sizeof(recv_payload));
+        
+            ret = dtn_recv(handle_, &recv_spec,
+                       DTN_PAYLOAD_MEM, &recv_payload, 0);
+
+            if (ret == 0)
+            {
+                fprintf(stderr, "error: unexpected bundle already queued... "
+                        "discarding\n");
+            }
+            else if (dtn_errno(handle_) != DTN_ETIMEOUT)
+            {
+                fprintf(stderr, "error: "
+                    "unexpected error checking for queued bundles: %s\n",
+                    dtn_strerror(dtn_errno(handle_)));
+                return;
+            }
+        } while (ret == 0);
+    }
+}
+
+
+bool
+TcaController::send_bundle(const dtn_bundle_spec_t& spec,
+                           const std::string& payload)
+{
+    printf("send_bundle: [%s] -> [%s] : '%s'\n",
+                spec.source.uri, spec.dest.uri, payload.c_str());
+
+    dtn_bundle_payload_t send_payload;
+    memset(&send_payload, 0, sizeof(send_payload));
+    dtn_set_payload(&send_payload, DTN_PAYLOAD_MEM,
+            const_cast<char*>(payload.c_str()), payload.length());
+
+    dtn_bundle_id_t bundle_id;
+    memset(&bundle_id, 0, sizeof(bundle_id));
+
+    int r = 0;    
+    if ((r = dtn_send(handle_, DTN_REGID_NONE,
+                      const_cast<dtn_bundle_spec_t*>(&spec),
+                      &send_payload, &bundle_id)) != 0)
+    {
+        fprintf(stderr, "TcaController::send_bundle error %d (%s)\n",
+                r, dtn_strerror(dtn_errno(handle_)));
+        return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaController::recv_bundle(dtn_bundle_spec_t& spec,
+                           std::string& payload,
+                           unsigned int timeout)
+{   
+    dtn_bundle_payload_t recv_payload;
+    memset(&spec, 0, sizeof(spec));
+    memset(&recv_payload, 0, sizeof(recv_payload));
+        
+    // now we block waiting for the echo reply
+    // DK: original timeout was -1, causes compile warning with gcc
+    int r;
+    if ((r = dtn_recv(handle_, &spec,
+                        DTN_PAYLOAD_MEM, &recv_payload, timeout)) < 0)
+    {
+        // fprintf(stderr, "TcaController::recv_bundle error %d (%s)\n",
+        //        r, dtn_strerror(dtn_errno(handle_)));
+        return false;
+    }
+
+    int n = recv_payload.buf.buf_len;
+    char s_buf[n+1];
+    memcpy(s_buf, recv_payload.buf.buf_val, n);
+    s_buf[n] = '\0';
+
+    payload = s_buf;
+
+    printf("%d bytes from [%s]: %s\n",
+               // recv_payload.buf.buf_len,
+               n,
+               spec.source.uri,
+               payload.c_str());
+
+    return true;
+}
+
+
+// a more convenient send, using reasonable defaults for most args
+bool
+TcaController::send_bundle(const std::string& dest,
+                           const std::string& payload)
+{
+    // printf("send_bundle: [%s] -> [%s] : '%s'\n",
+    //              local_eid_.uri, dest.c_str(), payload.c_str());
+
+    dtn_bundle_spec_t spec;
+    memset(&spec, 0, sizeof(spec));
+
+    // set the source and reply_to to be the local eid
+    dtn_copy_eid(&spec.source, &local_eid_);
+    dtn_copy_eid(&spec.replyto, &local_eid_);
+
+    if (dtn_parse_eid_string(&spec.dest, dest.c_str()))
+    {
+        fprintf(stderr, "TcaController::send_bundle: invalid destination"
+                " eid string, %s\n", dest.c_str());
+        return false;
+    }
+
+    // set the expiration time and the return receipt option
+    spec.priority = COS_NORMAL;
+    spec.dopts = DOPTS_NONE;
+    spec.expiration = control_ttl_;
+
+    return send_bundle(spec, payload);
+}
+
+
+