applib/dtn_ipc.c
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/applib/dtn_ipc.c	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,382 @@
+/*
+ *    Copyright 2004-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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <oasys/compat/inet_aton.h>
+#include <oasys/compat/inttypes.h>
+
+#include "dtn_ipc.h"
+#include "dtn_errno.h"
+#include "dtn_types.h"
+
+/* exposed globally for testing purposes only */
+int dtnipc_version = DTN_IPC_VERSION;
+
+const char*
+dtnipc_msgtoa(u_int8_t type)
+{
+#define CASE(_type) case _type : return #_type; break;
+    
+    switch(type) {
+        CASE(DTN_OPEN);
+        CASE(DTN_CLOSE);
+        CASE(DTN_LOCAL_EID);
+        CASE(DTN_REGISTER);
+        CASE(DTN_UNREGISTER);
+        CASE(DTN_FIND_REGISTRATION);
+        CASE(DTN_CHANGE_REGISTRATION);
+        CASE(DTN_BIND);
+        CASE(DTN_SEND);
+        CASE(DTN_RECV);
+        CASE(DTN_BEGIN_POLL);
+        CASE(DTN_CANCEL_POLL);
+        CASE(DTN_CANCEL);
+        CASE(DTN_SESSION_UPDATE);
+
+    default:
+        return "(unknown type)";
+    }
+    
+#undef CASE
+}
+
+/*
+ * Initialize the handle structure.
+ */
+int
+dtnipc_open(dtnipc_handle_t* handle)
+{
+    int remote_version, ret;
+    char *env, *end;
+    struct sockaddr_in sa;
+    in_addr_t ipc_addr;
+    u_int16_t ipc_port;
+    u_int32_t handshake;
+    u_int port;
+
+    // zero out the handle
+    memset(handle, 0, sizeof(dtnipc_handle_t));
+
+    // check for debugging
+    if (getenv("DTNAPI_DEBUG") != 0) {
+        handle->debug = 1;
+    }
+    
+    // note that we leave eight bytes free to be used for the framing
+    // -- the type code and length for send (which is only five
+    // bytes), and the return code and length for recv (which is
+    // actually eight bytes)
+    xdrmem_create(&handle->xdr_encode, handle->buf + 8,
+                  DTN_MAX_API_MSG - 8, XDR_ENCODE);
+    
+    xdrmem_create(&handle->xdr_decode, handle->buf + 8,
+                  DTN_MAX_API_MSG - 8, XDR_DECODE);
+
+    // open the socket
+    handle->sock = socket(PF_INET, SOCK_STREAM, 0);
+    if (handle->sock < 0)
+    {
+        handle->err = DTN_ECOMM;
+        dtnipc_close(handle);
+        return -1;
+    }
+
+    // check for DTNAPI environment variables overriding the address /
+    // port defaults
+    ipc_addr = htonl(INADDR_LOOPBACK);
+    ipc_port = DTN_IPC_PORT;
+    
+    if ((env = getenv("DTNAPI_ADDR")) != NULL) {
+        if (inet_aton(env, (struct in_addr*)&ipc_addr) == 0)
+        {
+            fprintf(stderr, "DTNAPI_ADDR environment variable (%s) "
+                    "not a valid ip address\n", env);
+            exit(1);
+        }
+    }
+
+    if ((env = getenv("DTNAPI_PORT")) != NULL) {
+        port = strtoul(env, &end, 10);
+        if (*end != '\0' || port > 0xffff)
+        {
+            fprintf(stderr, "DTNAPI_PORT environment variable (%s) "
+                    "not a valid ip port\n", env);
+            exit(1);
+        }
+        ipc_port = (u_int16_t)port;
+    }
+
+    // connect to the server
+    memset(&sa, 0, sizeof(sa));
+    sa.sin_family = AF_INET;
+    sa.sin_addr.s_addr = ipc_addr;
+    sa.sin_port = htons(ipc_port);
+    
+    ret = connect(handle->sock, (const struct sockaddr*)&sa, sizeof(sa));
+    if (ret != 0) {
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: error connecting to server: %s\n",
+                    strerror(errno));
+        }
+
+        handle->err = DTN_ECOMM;
+        dtnipc_close(handle);
+        return -1;
+    }
+
+    if (handle->debug) {
+        fprintf(stderr, "dtn_ipc: connected to server: fd %d\n", handle->sock);
+    }
+
+    // send the session initiation to the server on the handshake
+    // port. it consists of DTN_OPEN in the high 16 bits and IPC
+    // version in the low 16 bits
+    handshake = htonl(DTN_OPEN << 16 | dtnipc_version);
+    ret = write(handle->sock, &handshake, sizeof(handshake));
+    if (ret != sizeof(handshake)) {
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: handshake error\n");
+        }
+        handle->err = DTN_ECOMM;
+        dtnipc_close(handle);
+        return -1;
+    }
+    handle->total_sent += ret;
+
+    // wait for the handshake response
+    handshake = 0;
+    ret = read(handle->sock, &handshake, sizeof(handshake));
+    if (ret != sizeof(handshake) || (ntohl(handshake) >> 16) != DTN_OPEN) {
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: handshake error\n");
+        }
+        dtnipc_close(handle);
+        handle->err = DTN_ECOMM;
+        return -1;
+    }
+
+    handle->total_rcvd += ret;
+
+    remote_version = (ntohl(handshake) & 0x0ffff);
+    if (remote_version != dtnipc_version) {
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: version mismatch\n");
+        }
+        dtnipc_close(handle);
+        handle->err = DTN_EVERSION;
+        return -1;
+    }
+    
+    return 0;
+}
+
+/*
+ * Clean up the handle. dtnipc_open must have already been called on
+ * the handle.
+ */
+int
+dtnipc_close(dtnipc_handle_t* handle)
+{
+    int ret;
+    
+    // first send a close over RPC
+    if (handle->err != DTN_ECOMM) {
+        ret = dtnipc_send_recv(handle, DTN_CLOSE);
+    } else {
+        ret = -1;
+    }
+    
+    xdr_destroy(&handle->xdr_encode);
+    xdr_destroy(&handle->xdr_decode);
+
+    if (handle->sock > 0) {
+        close(handle->sock);
+    }
+
+    handle->sock = 0;
+
+    return ret;
+}
+      
+
+/*
+ * Send a message over the dtn ipc protocol.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+int
+dtnipc_send(dtnipc_handle_t* handle, dtnapi_message_type_t type)
+{
+    int ret;
+    u_int32_t len, msglen, origlen;
+    
+    // pack the message code in the fourth byte of the buffer and the
+    // message length into the next four. we don't use xdr routines
+    // for these since we need to be able to decode the length on the
+    // other side to make sure we've read the whole message, and we
+    // need the type to know which xdr decoder to call
+    handle->buf[3] = type;
+
+    len = xdr_getpos(&handle->xdr_encode);
+    msglen = len + 5;
+    len = htonl(len);
+    memcpy(&handle->buf[4], &len, sizeof(len));
+    
+    // reset the xdr encoder
+    xdr_setpos(&handle->xdr_encode, 0);
+    
+    // send the message, looping until it's all sent
+    origlen = msglen;
+    char* bp = &handle->buf[3];
+    do {
+        ret = write(handle->sock, bp, msglen);
+        handle->total_sent += ret;
+        
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: send(%s) wrote %d/%d bytes (%s) "
+                    "(total sent/rcvd %u/%u)\n",
+                    dtnipc_msgtoa(type), ret, origlen,
+                    ret == -1 ? strerror(errno) : "success",
+                    handle->total_sent, handle->total_rcvd);
+        }
+
+        if (ret <= 0) {
+            if (errno == EINTR)
+                continue;
+            
+            handle->err = DTN_ECOMM;
+            dtnipc_close(handle);
+            return -1;
+        }
+
+        bp     += ret;
+        msglen -= ret;
+        
+    } while (msglen > 0);
+
+    return 0;
+}
+
+/*
+ * Receive a message on the ipc channel. May block if there is no
+ * pending message.
+ *
+ * Sets status to the server-returned status code and returns the
+ * length of any reply message on success, returns -1 on internal
+ * error.
+ */
+int
+dtnipc_recv(dtnipc_handle_t* handle, int* status)
+{
+    int ret;
+    u_int32_t len, nread;
+    u_int32_t statuscode;
+
+    // reset the xdr decoder before reading in any data
+    xdr_setpos(&handle->xdr_decode, 0);
+
+    // read the status code and length
+    ret = read(handle->sock, handle->buf, 8);
+    handle->total_rcvd += ret;
+
+    // make sure we got it all
+    if (ret != 8) {
+        handle->err = DTN_ECOMM;
+        dtnipc_close(handle);
+        return -1;
+    }
+    
+    memcpy(&statuscode, handle->buf, sizeof(statuscode));
+    statuscode = ntohl(statuscode);
+    *status = statuscode;
+    
+    memcpy(&len, &handle->buf[4], sizeof(len));
+    len = ntohl(len);
+    
+    if (handle->debug) {
+        fprintf(stderr, "dtn_ipc: recv() read %d/8 bytes for status (%s): "
+                "status %d len %d (total sent/rcvd %u/%u)\n",
+                ret, ret == -1 ? strerror(errno) : "success",
+                *status, len, handle->total_sent, handle->total_rcvd);
+    }
+
+    // read the rest of the message 
+    nread = 8;
+    while (nread < len + 8) {
+        ret = read(handle->sock,
+                   &handle->buf[nread], sizeof(handle->buf) - nread);
+        handle->total_rcvd += ret;
+        
+        if (handle->debug) {
+            fprintf(stderr, "dtn_ipc: recv() read %d/%d bytes (%s)\n",
+                    ret, len, ret == -1 ? strerror(errno) : "success");
+        }
+
+        if (ret <= 0) {
+            if (errno == EINTR)
+                continue;
+            
+            handle->err = DTN_ECOMM;
+            dtnipc_close(handle);
+            return -1;
+        }
+
+        nread += ret;
+    }
+
+    return len;
+}
+
+
+/**
+ * Send a message and wait for a response over the dtn ipc protocol.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+int dtnipc_send_recv(dtnipc_handle_t* handle, dtnapi_message_type_t type)
+{
+    int status;
+
+    // send the message
+    if (dtnipc_send(handle, type) < 0) {
+        return -1;
+    }
+
+    // wait for a response
+    if (dtnipc_recv(handle, &status) < 0) {
+        return -1;
+    }
+
+    // handle server-side errors
+    if (status != DTN_SUCCESS) {
+        handle->err = status;
+        return -1;
+    }
+
+    return 0;
+}