apps/dtntest/dtntest.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/dtntest/dtntest.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,989 @@
+/*
+ *    Copyright 2006 Intel Corporation
+ * 
+ *    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 <errno.h>
+#include <oasys/debug/Log.h>
+#include <oasys/io/FileUtils.h>
+#include <oasys/io/NetUtils.h>
+#include <oasys/tclcmd/ConsoleCommand.h>
+#include <oasys/tclcmd/TclCommand.h>
+#include <oasys/util/Getopt.h>
+#include <oasys/util/OptParser.h>
+
+#include <dtn_api.h>
+#include <dtn_ipc.h>
+#include <APIEndpointIDOpt.h>
+
+typedef std::map<int, dtn_handle_t> HandleMap;
+
+struct State : public oasys::Singleton<State> {
+    State() : handle_num_(0) {}
+        
+    HandleMap handles_;
+    int handle_num_;
+};
+
+template <> State* oasys::Singleton<State>::instance_ = 0;
+
+extern int dtnipc_version;
+
+//----------------------------------------------------------------------
+class DTNOpenCommand : public oasys::TclCommand {
+public:
+    oasys::OptParser parser_;
+    
+    struct OpenOpts {
+        u_int16_t             version_;
+    };
+    
+    OpenOpts opts_;
+    
+    void init_opts() {
+        opts_.version_ = DTN_IPC_VERSION;
+    }
+    
+    DTNOpenCommand() : TclCommand("dtn_open") {
+        parser_.addopt(new oasys::UInt16Opt("version", &opts_.version_));
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        if (argc < 1 || argc > 2) {
+            wrong_num_args(argc, argv, 1, 1, 2);
+            return TCL_ERROR;
+        }
+
+        init_opts();
+        
+        const char* invalid = 0;
+        if (! parser_.parse(argc - 1, argv + 1, &invalid)) {
+            resultf("invalid option '%s'", invalid);
+            return TCL_ERROR;
+        }
+
+        dtnipc_version = opts_.version_;
+        dtn_handle_t handle;
+        int err = dtn_open(&handle);
+        if (err != DTN_SUCCESS) {
+            resultf("can't connect to dtn daemon: %s",
+                    dtn_strerror(err));
+            return TCL_ERROR;
+        }
+
+        int n = State::instance()->handle_num_++;
+        State::instance()->handles_[n] = handle;
+
+        resultf("%d", n);
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNCloseCommand : public oasys::TclCommand {
+public:
+    DTNCloseCommand() : TclCommand("dtn_close") {}
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        if (argc != 2) {
+            wrong_num_args(argc, argv, 1, 2, 2);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+        dtn_close(h);
+
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+oasys::EnumOpt::Case FailureActionCases[] = {
+    {"drop",  DTN_REG_DROP},
+    {"defer", DTN_REG_DEFER},
+    {"exec",  DTN_REG_EXEC},
+    {0, 0}
+};
+
+//----------------------------------------------------------------------
+oasys::BitFlagOpt::Case SessionFlagCases[] = {
+    {"subscribe", DTN_SESSION_SUBSCRIBE},
+    {"publish",   DTN_SESSION_PUBLISH},
+    {"custody",   DTN_SESSION_CUSTODY},
+    {0, 0}
+};
+
+class DTNRegisterCommand : public oasys::TclCommand {
+public:
+    oasys::OptParser parser_;
+
+    struct RegistrationOpts {
+        dtn_endpoint_id_t endpoint_;
+        int               failure_action_;
+        int               session_flags_;
+        u_int             expiration_;
+        std::string       script_;
+        bool              init_passive_;
+    };
+    
+    RegistrationOpts opts_;
+
+    void init_opts() {
+        memset(&opts_.endpoint_, 0, sizeof(opts_.endpoint_));
+        opts_.failure_action_ = DTN_REG_DROP;
+        opts_.session_flags_ = 0;
+        opts_.expiration_ = 0xffffffff;
+        opts_.script_ = "";
+        opts_.init_passive_ = false;
+    }
+
+    DTNRegisterCommand() : TclCommand("dtn_register")
+    {
+        parser_.addopt(new dtn::APIEndpointIDOpt("endpoint", &opts_.endpoint_));
+        parser_.addopt(new oasys::EnumOpt("failure_action",
+                                          FailureActionCases,
+                                          &opts_.failure_action_));
+        parser_.addopt(new oasys::BitFlagOpt("session_flags",
+                                             SessionFlagCases,
+                                             &opts_.session_flags_));
+        parser_.addopt(new oasys::UIntOpt("expiration", &opts_.expiration_));
+        parser_.addopt(new oasys::StringOpt("script", &opts_.script_));
+        parser_.addopt(new oasys::BoolOpt("init_passive",
+                                          &opts_.init_passive_));
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need at least cmd, handle, endpoint, and expiration
+        if (argc < 4) {
+            wrong_num_args(argc, argv, 1, 4, INT_MAX);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        init_opts();
+        const char* invalid = 0;
+        if (! parser_.parse(argc - 2, argv + 2, &invalid)) {
+            resultf("invalid option '%s'", invalid);
+            return TCL_ERROR;
+        }
+
+        if (opts_.endpoint_.uri[0] == 0) {
+            resultf("must set endpoint id");
+            return TCL_ERROR;
+        }
+        
+        if (opts_.expiration_ == 0xffffffff) {
+            resultf("must set expiration");
+            return TCL_ERROR;
+        }
+        
+        dtn_reg_info_t reginfo;
+        memset(&reginfo, 0, sizeof(reginfo));
+
+        dtn_copy_eid(&reginfo.endpoint, &opts_.endpoint_);
+        reginfo.flags = opts_.failure_action_ | opts_.session_flags_;
+        reginfo.expiration = opts_.expiration_;
+        reginfo.script.script_len = opts_.script_.length();
+        reginfo.script.script_val = (char*)opts_.script_.c_str();
+        reginfo.init_passive = opts_.init_passive_;
+
+        dtn_reg_id_t regid = 0;
+        
+        int ret = dtn_register(h, &reginfo, &regid);
+        if (ret != DTN_SUCCESS) {
+            resultf("error in dtn_register: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        resultf("%u", regid);
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNUnregisterCommand : public oasys::TclCommand {
+public:
+    DTNUnregisterCommand() : TclCommand("dtn_unregister") {}
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)interp;
+
+        if (argc != 3) {
+            wrong_num_args(argc, argv, 1, 3, 3);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        dtn_reg_id_t regid = atoi(argv[2]);
+
+        int err = dtn_unregister(h, regid);
+        if (err != DTN_SUCCESS) {
+            resultf("error in dtn_unregister: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+        
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+oasys::EnumOpt::Case PriorityCases[] = {
+    {"bulk",      COS_BULK},
+    {"normal",    COS_NORMAL},
+    {"expedited", COS_EXPEDITED},
+    {0, 0}
+};
+
+class DTNSendCommand : public oasys::TclCommand {
+public:
+    struct SendOpts {
+        int    regid_;
+        dtn_endpoint_id_t source_;
+        dtn_endpoint_id_t dest_;
+        dtn_endpoint_id_t replyto_;
+        int    priority_;
+        bool   custody_xfer_;
+        bool   receive_rcpt_;
+        bool   custody_rcpt_;
+        bool   forward_rcpt_;
+        bool   delivery_rcpt_;
+        bool   deletion_rcpt_;
+        u_int  expiration_;
+        std::string sequence_id_;
+        std::string obsoletes_id_;
+        char   payload_data_[DTN_MAX_BUNDLE_MEM];
+        size_t payload_data_len_;
+        char   payload_file_[DTN_MAX_PATH_LEN];
+        size_t payload_file_len_;
+        u_int  block_type_;
+        u_int  block_flags_;
+        char   block_content_[DTN_MAX_BLOCK_LEN];
+        size_t block_content_len_;
+    };
+    
+    // we use a single parser and options struct for the command for
+    // improved efficiency
+    oasys::OptParser parser_;
+    SendOpts opts_;
+
+    void init_opts()
+    {
+        opts_.regid_ = DTN_REGID_NONE;
+        
+        memset(&opts_.source_,  0, sizeof(opts_.source_));
+        memset(&opts_.dest_,    0, sizeof(opts_.dest_));
+        memset(&opts_.replyto_, 0, sizeof(opts_.replyto_));
+        
+        opts_.priority_      = COS_NORMAL;
+        opts_.custody_xfer_  = 0;
+        opts_.receive_rcpt_  = 0;
+        opts_.custody_rcpt_  = 0;
+        opts_.forward_rcpt_  = 0;
+        opts_.delivery_rcpt_ = 0;
+        opts_.deletion_rcpt_ = 0;
+        opts_.expiration_    = 5 * 60;
+        opts_.sequence_id_   = "";
+        opts_.obsoletes_id_  = "";
+
+        memset(&opts_.payload_data_, 0, sizeof(opts_.payload_data_));
+        opts_.payload_data_len_ = 0;
+        memset(&opts_.payload_file_, 0, sizeof(opts_.payload_file_));
+        opts_.payload_file_len_ = 0;
+        
+        opts_.block_type_  = 0;
+        opts_.block_flags_ = 0;
+        memset(&opts_.block_content_, 0, sizeof(opts_.block_content_));
+        opts_.block_content_len_ = 0;
+    }
+
+    DTNSendCommand() : TclCommand("dtn_send")
+    {
+        parser_.addopt(new oasys::IntOpt("regid", &opts_.regid_));
+        parser_.addopt(new dtn::APIEndpointIDOpt("source", &opts_.source_));
+        parser_.addopt(new dtn::APIEndpointIDOpt("dest", &opts_.dest_));
+        parser_.addopt(new dtn::APIEndpointIDOpt("replyto", &opts_.replyto_));
+        parser_.addopt(new oasys::EnumOpt("priority", PriorityCases,
+                                          &opts_.priority_));
+        parser_.addopt(new oasys::BoolOpt("custody_xfer",
+                                          &opts_.custody_xfer_));
+        parser_.addopt(new oasys::BoolOpt("receive_rcpt",
+                                          &opts_.receive_rcpt_));
+        parser_.addopt(new oasys::BoolOpt("custody_rcpt",
+                                          &opts_.custody_rcpt_));
+        parser_.addopt(new oasys::BoolOpt("forward_rcpt",
+                                          &opts_.forward_rcpt_));
+        parser_.addopt(new oasys::BoolOpt("delivery_rcpt",
+                                          &opts_.delivery_rcpt_));
+        parser_.addopt(new oasys::BoolOpt("deletion_rcpt",
+                                          &opts_.deletion_rcpt_));
+        parser_.addopt(new oasys::UIntOpt("expiration",
+                                          &opts_.expiration_));
+        parser_.addopt(new oasys::StringOpt("sequence_id",
+                                            &opts_.sequence_id_));
+        parser_.addopt(new oasys::StringOpt("obsoletes_id",
+                                            &opts_.obsoletes_id_));
+        parser_.addopt(new oasys::CharBufOpt("payload_data",
+                                             opts_.payload_data_,
+                                             &opts_.payload_data_len_,
+                                             sizeof(opts_.payload_data_)));
+        parser_.addopt(new oasys::CharBufOpt("payload_file",
+                                             opts_.payload_file_,
+                                             &opts_.payload_file_len_,
+                                             sizeof(opts_.payload_file_)));
+        parser_.addopt(new oasys::UIntOpt("block_type", &opts_.block_type_));
+        parser_.addopt(new oasys::UIntOpt("block_flags", &opts_.block_flags_));
+        parser_.addopt(new oasys::CharBufOpt("block_content",
+                                             opts_.block_content_,
+                                             &opts_.block_content_len_,
+                                             sizeof(opts_.block_content_)));
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need at least the command, handle, source, dest, and payload
+        if (argc < 5) {
+            wrong_num_args(argc, argv, 1, 5, INT_MAX);
+            return TCL_ERROR;
+        }
+        
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+        
+        dtn_handle_t h = iter->second;
+        
+        // now parse all the options
+        init_opts();
+        const char* invalid = 0;
+        if (! parser_.parse(argc - 2, argv + 2, &invalid)) {
+            resultf("invalid option '%s'", invalid);
+            return TCL_ERROR;
+        }
+
+        // validate that source, dest, and some payload was specified
+        if (opts_.source_.uri[0] == 0) {
+            resultf("must set source endpoint id");
+            return TCL_ERROR;
+        }
+        if (opts_.dest_.uri[0] == 0) {
+            resultf("must set dest endpoint id");
+            return TCL_ERROR;
+        }
+        if (opts_.payload_data_len_ == 0 && opts_.payload_file_len_ == 0) {
+            resultf("must set payload");
+            return TCL_ERROR;
+        }
+
+        dtn_bundle_spec_t spec;
+        memset(&spec, 0, sizeof(spec));
+        dtn_copy_eid(&spec.source, &opts_.source_);
+        dtn_copy_eid(&spec.dest,   &opts_.dest_);
+        if (opts_.replyto_.uri[0] != 0) {
+            dtn_copy_eid(&spec.replyto, &opts_.replyto_);
+        }
+        spec.priority = (dtn_bundle_priority_t)opts_.priority_;
+        if (opts_.custody_xfer_)  spec.dopts |= DOPTS_CUSTODY;
+        if (opts_.receive_rcpt_)  spec.dopts |= DOPTS_RECEIVE_RCPT;
+        if (opts_.custody_rcpt_)  spec.dopts |= DOPTS_CUSTODY_RCPT;
+        if (opts_.forward_rcpt_)  spec.dopts |= DOPTS_FORWARD_RCPT;
+        if (opts_.delivery_rcpt_) spec.dopts |= DOPTS_DELIVERY_RCPT;
+        if (opts_.deletion_rcpt_) spec.dopts |= DOPTS_DELETE_RCPT;
+        spec.expiration = opts_.expiration_;
+
+        if (opts_.sequence_id_ != "") {
+            spec.sequence_id.data.data_val = const_cast<char*>(opts_.sequence_id_.c_str());
+            spec.sequence_id.data.data_len = opts_.sequence_id_.length();
+        }
+        
+        if (opts_.obsoletes_id_ != "") {
+            spec.obsoletes_id.data.data_val = const_cast<char*>(opts_.obsoletes_id_.c_str());
+            spec.obsoletes_id.data.data_len = opts_.obsoletes_id_.length();
+        }
+        
+        dtn_bundle_payload_t payload;
+        memset(&payload, 0, sizeof(payload));
+        if (opts_.payload_data_len_ != 0) {
+            dtn_set_payload(&payload, DTN_PAYLOAD_MEM,
+                            opts_.payload_data_, opts_.payload_data_len_);
+        } else {
+            dtn_set_payload(&payload, DTN_PAYLOAD_FILE,
+                            opts_.payload_file_, opts_.payload_file_len_);
+
+        }
+
+        dtn_extension_block_t block;
+        memset(&block, 0, sizeof(block));
+        if (opts_.block_type_ > 0 && opts_.block_type_ < 255) {
+            block.type = opts_.block_type_;
+            if (opts_.block_flags_ < 255) {
+                block.flags = opts_.block_flags_;
+            }
+            block.data.data_len = opts_.block_content_len_;
+            block.data.data_val = opts_.block_content_;
+
+            spec.blocks.blocks_len = 1;
+            spec.blocks.blocks_val = &block;
+        }
+        
+        dtn_bundle_id_t id;
+        memset(&id, 0, sizeof(id));
+        
+        int ret = dtn_send(h, opts_.regid_, &spec, &payload, &id);
+        if (ret != DTN_SUCCESS) {
+            resultf("error in dtn_send: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        resultf("%s,%llu.%llu",
+                id.source.uri, id.creation_ts.secs, id.creation_ts.seqno);
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNBindCommand : public oasys::TclCommand {
+public:
+    DTNBindCommand() : TclCommand("dtn_bind") {}
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)interp;
+
+        if (argc != 3) {
+            wrong_num_args(argc, argv, 1, 3, 3);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        dtn_reg_id_t regid = atoi(argv[2]);
+
+        int err = dtn_bind(h, regid);
+        if (err != DTN_SUCCESS) {
+            resultf("error in dtn_bind: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+        
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNUnbindCommand : public oasys::TclCommand {
+public:
+    DTNUnbindCommand() : TclCommand("dtn_unbind") {}
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)interp;
+
+        if (argc != 3) {
+            wrong_num_args(argc, argv, 1, 3, 3);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        dtn_reg_id_t regid = atoi(argv[2]);
+
+        int err = dtn_unbind(h, regid);
+        if (err != DTN_SUCCESS) {
+            resultf("error in dtn_unbind: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+        
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNRecvCommand : public oasys::TclCommand {
+public:
+    oasys::OptParser parser_;
+
+    struct RecvOpts {
+        bool   payload_mem_;
+        bool   payload_file_;
+        u_int  timeout_;
+    };
+    
+    RecvOpts opts_;
+
+    void init_opts() {
+        memset(&opts_, 0, sizeof(opts_));
+    }
+
+    DTNRecvCommand() : TclCommand("dtn_recv")
+    {
+        parser_.addopt(new oasys::BoolOpt("payload_mem", &opts_.payload_mem_));
+        parser_.addopt(new oasys::BoolOpt("payload_file", &opts_.payload_file_));
+        parser_.addopt(new oasys::UIntOpt("timeout", &opts_.timeout_));
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need at least cmd, handle, payload, and timeout
+        if (argc < 3) {
+            wrong_num_args(argc, argv, 1, 3, INT_MAX);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        init_opts();
+
+        const char* invalid = 0;
+        if (! parser_.parse(argc - 2, argv + 2, &invalid)) {
+            resultf("invalid option '%s'", invalid);
+            return TCL_ERROR;
+        }
+
+        if (opts_.payload_mem_ == false && opts_.payload_file_ == false) {
+            resultf("must set payload location");
+            return TCL_ERROR;
+        }
+
+        dtn_bundle_spec_t spec;
+        memset(&spec, 0, sizeof(spec));
+
+        dtn_bundle_payload_t payload;
+        memset(&payload, 0, sizeof(payload));
+        
+        int err = dtn_recv(h, &spec,
+                           opts_.payload_mem_ ? DTN_PAYLOAD_MEM : DTN_PAYLOAD_FILE,
+                           &payload, opts_.timeout_);
+        if (err != DTN_SUCCESS) {
+            resultf("error in dtn_recv: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        int payload_size;
+        if (opts_.payload_mem_) {
+            // return the size
+            payload_size = payload.buf.buf_len;
+        } else {
+            char payload_path[PATH_MAX];
+            memcpy(payload_path, payload.filename.filename_val,
+                   payload.filename.filename_len);
+            payload_path[payload.filename.filename_len] = 0;
+
+            payload_size = oasys::FileUtils::size(payload_path);
+            
+            err = unlink(payload_path);
+            if (err != 0) {
+                log_err("error unlinking payload file '%s': %s",
+                        payload_path, strerror(errno));
+            }
+        }
+        
+        dtn_free_payload(&payload);
+
+        Tcl_Obj* result = Tcl_NewListObj(0, NULL);
+
+#define APPEND_STRING_VAL(key, val, val_len)                                 \
+        if (Tcl_ListObjAppendElement(interp, result,                         \
+                                     Tcl_NewStringObj(key, -1)) != TCL_OK || \
+            Tcl_ListObjAppendElement(interp, result,                         \
+                                     Tcl_NewStringObj(val, val_len)) != TCL_OK)\
+        {                                                                    \
+            resultf("error appending list element");                         \
+            return TCL_ERROR;                                                \
+        }
+
+#define APPEND_INT_VAL(key, val)                                             \
+        if (Tcl_ListObjAppendElement(interp, result,                         \
+                                     Tcl_NewStringObj(key, -1)) != TCL_OK || \
+            Tcl_ListObjAppendElement(interp, result,                         \
+                                     Tcl_NewIntObj(val)) != TCL_OK)          \
+        {                                                                    \
+            resultf("error appending list element");                         \
+            return TCL_ERROR;                                                \
+        }
+
+        APPEND_STRING_VAL("source",  spec.source.uri,  -1);
+        APPEND_STRING_VAL("dest",    spec.dest.uri,    -1);
+        APPEND_STRING_VAL("replyto", spec.replyto.uri, -1);
+
+        char tmp[256];
+        snprintf(tmp, 256, "%llu.%llu", spec.creation_ts.secs, spec.creation_ts.seqno);
+        
+        APPEND_STRING_VAL("creation_ts", tmp, -1);
+
+        APPEND_INT_VAL("payload_size", payload_size);
+
+        APPEND_STRING_VAL("sequence_id",
+                          spec.sequence_id.data.data_val,
+                          spec.sequence_id.data.data_len);
+        APPEND_STRING_VAL("obsoletes_id",
+                          spec.obsoletes_id.data.data_val,
+                          spec.obsoletes_id.data.data_len);
+        
+        set_objresult(result);
+
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNSessionUpdateCommand : public oasys::TclCommand {
+public:
+    DTNSessionUpdateCommand() : TclCommand("dtn_session_update")
+    {
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need cmd, handle, and timeout
+        if (argc != 3) {
+            wrong_num_args(argc, argv, 1, 3, 3);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+        dtn_handle_t h = iter->second;
+
+        int timeout = atoi(argv[2]);
+
+        unsigned int status = 0;
+        dtn_endpoint_id_t session;
+        memset(session.uri, 0, sizeof(session.uri));
+
+        int err = dtn_session_update(h, &status, &session, timeout);
+        if (err != DTN_SUCCESS) {
+            resultf("error in dtn_session_update: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        resultf("%u %s", status, session.uri);
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNPollChannelCommand : public oasys::TclCommand {
+public:
+    DTNPollChannelCommand() : TclCommand("dtn_poll_channel")
+    {
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need cmd and handle
+        if (argc != 2) {
+            wrong_num_args(argc, argv, 1, 2, 2);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        int fd = dtn_poll_fd(h);
+        if (fd < 0) {
+            resultf("error in dtn_poll_fd: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        Tcl_Channel chan = oasys::TclCommandInterp::instance()->
+                           register_file_channel((ClientData)fd, TCL_READABLE);
+        if (!chan) {
+            resultf("error making tcl channel");
+            return TCL_ERROR;
+        }
+        set_result(Tcl_GetChannelName(chan));
+
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNBeginPollCommand : public oasys::TclCommand {
+public:
+    DTNBeginPollCommand() : TclCommand("dtn_begin_poll")
+    {
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need cmd, handle, and timeout
+        if (argc != 3) {
+            wrong_num_args(argc, argv, 1, 3, 3);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        int timeout = atoi(argv[2]);
+
+        int err = dtn_begin_poll(h, timeout);
+        if (err < 0) {
+            resultf("error in dtn_begin_poll: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTNCancelPollCommand : public oasys::TclCommand {
+public:
+    DTNCancelPollCommand() : TclCommand("dtn_cancel_poll")
+    {
+    }
+    
+    int exec(int argc, const char **argv,  Tcl_Interp* interp)
+    {
+        (void)argc;
+        (void)argv;
+        (void)interp;
+
+        // need cmd and handle
+        if (argc != 2) {
+            wrong_num_args(argc, argv, 1, 2, 2);
+            return TCL_ERROR;
+        }
+
+        int n = atoi(argv[1]);
+        HandleMap::iterator iter = State::instance()->handles_.find(n);
+        if (iter == State::instance()->handles_.end()) {
+            resultf("invalid dtn handle %d", n);
+            return TCL_ERROR;
+        }
+
+        dtn_handle_t h = iter->second;
+
+        // XXX/demmer should keep a reference to the tcl channel so we
+        // can deregister it
+        int err = dtn_cancel_poll(h);
+        if (err != 0) {
+            resultf("error in dtn_cancel_poll: %s",
+                    dtn_strerror(dtn_errno(h)));
+            return TCL_ERROR;
+        }
+        
+        return TCL_OK;
+    }
+};
+
+//----------------------------------------------------------------------
+class ShutdownCommand : public oasys::TclCommand {
+public:
+    ShutdownCommand() : TclCommand("shutdown") {}
+    static void call_exit(void* clientData);
+    int exec(int argc, const char **argv,  Tcl_Interp* interp);
+};
+
+void
+ShutdownCommand::call_exit(void* clientData)
+{
+    (void)clientData;
+    exit(0);
+}
+
+//----------------------------------------------------------------------
+int
+ShutdownCommand::exec(int argc, const char **argv, Tcl_Interp* interp)
+{
+    (void)argc;
+    (void)argv;
+    (void)interp;
+    Tcl_CreateTimerHandler(0, ShutdownCommand::call_exit, 0);
+    return TCL_OK;
+}
+
+//----------------------------------------------------------------------
+int
+main(int argc, char** argv)
+{
+    oasys::TclCommandInterp* interp;
+    oasys::ConsoleCommand* console_cmd;
+    std::string conf_file;
+    bool conf_file_set = false;
+    bool daemon = false;
+
+    oasys::Log::init();
+    
+    oasys::TclCommandInterp::init("dtn-test");
+    interp = oasys::TclCommandInterp::instance();
+    interp->logpathf("/dtn-test/tclcmd");
+
+    oasys::Getopt opts;
+    opts.addopt(
+        new oasys::StringOpt('c', "conf", &conf_file, "<conf>",
+                             "set the configuration file", &conf_file_set));
+
+    opts.addopt(
+        new oasys::BoolOpt('d', "daemon", &daemon,
+                           "run as a daemon"));
+    
+    int remainder = opts.getopt(argv[0], argc, argv);
+    if (remainder != argc) 
+    {
+        fprintf(stderr, "invalid argument '%s'\n", argv[remainder]);
+        opts.usage("dtn-test");
+        exit(1);
+    }
+
+    console_cmd = new oasys::ConsoleCommand("dtntest% ");
+    interp->reg(console_cmd);
+    interp->reg(new DTNOpenCommand());
+    interp->reg(new DTNCloseCommand());
+    interp->reg(new DTNRegisterCommand());
+    interp->reg(new DTNUnregisterCommand());
+    interp->reg(new DTNBindCommand());
+    interp->reg(new DTNUnbindCommand());
+    interp->reg(new DTNSendCommand());
+    interp->reg(new DTNRecvCommand());
+    interp->reg(new DTNSessionUpdateCommand());
+    interp->reg(new DTNPollChannelCommand());
+    interp->reg(new DTNBeginPollCommand());
+    interp->reg(new DTNCancelPollCommand());
+    interp->reg(new ShutdownCommand());
+
+    if (conf_file_set) {
+        interp->exec_file(conf_file.c_str());
+    }
+    
+    log_notice_p("/dtn-test", "dtn-test starting up...");
+    
+    if (console_cmd->port_ != 0) {
+        log_notice_p("/dtn-test", "starting console on %s:%d",
+                     intoa(console_cmd->addr_), console_cmd->port_);
+        interp->command_server("dtn-test", console_cmd->addr_, console_cmd->port_);
+    }
+
+    if (daemon || (console_cmd->stdio_ == false)) {
+        oasys::TclCommandInterp::instance()->event_loop();
+    } else {
+        oasys::TclCommandInterp::instance()->
+            command_loop(console_cmd->prompt_.c_str());
+    }
+
+    log_notice_p("/dtn-test", "dtn-test shutting down...");
+    delete State::instance();
+    oasys::TclCommandInterp::shutdown();
+    oasys::Log::shutdown();
+}