Add transport provider status to message buffer 66/666/2 1.1.0
authorE. Scott Daniels <daniels@research.att.com>
Thu, 8 Aug 2019 14:15:43 +0000 (10:15 -0400)
committerE. Scott Daniels <daniels@research.att.com>
Thu, 8 Aug 2019 16:06:37 +0000 (12:06 -0400)
This change adds a new field to the message buffer: tp_status.
The field allows the underlying "errno" value set by the
transport provider to be returned, in addition to the RMR
status, as a part of the overall message. In some environments
wrapper code (bindings) does not hav access to the errno value
and thus was unable to have the additional information on
send and receive failures.

The new field constitutes a backwards compatable change to RMR's
API, however the python wrapper API does not change. Minor version
bump, but the wrapper will see only a patch level bump.

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I1b8a863318e2b2515441f7339b41c2856e46c1e4

12 files changed:
CHANGES
CMakeLists.txt
doc/src/man/rmr_alloc_msg.3.xfm
src/bindings/rmr-python/Changelog.md
src/bindings/rmr-python/rmr/rmr.py
src/bindings/rmr-python/rmr/rmr_mocks/rmr_mocks.py
src/bindings/rmr-python/setup.py
src/bindings/rmr-python/tox.ini
src/rmr/common/include/rmr.h
src/rmr/nng/src/rmr_nng.c
src/rmr/nng/src/sr_nng_static.c
test/rmr_nng_api_static_test.c

diff --git a/CHANGES b/CHANGES
index 955d4fd..3e1ae6d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,16 @@
 API and build change  and fixe summaries. Doc correctsions
 and/or changes are not mentioned here; see the commit messages.
 
+2019 August 8; version 1.1.0 (API change)
+       This change should be backward compatable/non-breaking
+       A new field has been added to the message buffer (rmr_mbuf_t).
+       This field (tp_state) is used to communicate the errno value
+       that the transport mechanism might set during send and/or
+       receive operations.  C programmes should continue to use
+       errno directly, but in some environments wrappers may not be
+       able to access errno and this provides the value to them. 
+       See the rmr_alloc_msg manual page for more details.
+       
 2019 August 6; version 1.0.45 (build changes)
        Support for the Nanomsg transport library has been dropped.
                The library librmr.* will no longer be included in packages.
index 094cb54..f1aab62 100644 (file)
@@ -35,8 +35,8 @@ project( rmr LANGUAGES C )
 cmake_minimum_required( VERSION 3.5 )
 
 set( major_version "1" )               # should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "0" )
-set( patch_level "45" )
+set( minor_version "1" )
+set( patch_level "0" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
 set( install_inc "include/rmr" )
index cd9da7f..5377871 100644 (file)
@@ -1,6 +1,6 @@
 .if false
 ==================================================================================
-       Copyright (c) 2019 Nokia 
+       Copyright (c) 2019 Nokia
        Copyright (c) 2018-2019 AT&T Intellectual Property.
 
    Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,17 +41,17 @@ rmr_mbuf_t* rmr_alloc_msg( void* ctx, int size );
 &uindent
 
 &h2(DESCRIPTION)
-The &cw(rmr_alloc_msg) function is used to allocate a buffer which the user 
+The &cw(rmr_alloc_msg) function is used to allocate a buffer which the user
 programme can write into and then send through the RMR library.
 The buffer is allocated such that sending it requires no additional copying
-out of the buffer. 
+out of the buffer.
 If the value passed in &cw(size) is 0, then the default size supplied on the
-&ital(rmr_init) call will be used. 
+&ital(rmr_init) call will be used.
 The &ital(ctx) parameter is the void context pointer that was returned by
 the &ital(rmr_init) function.
 
 &space
-The pointer to the message buffer returned is a structure which has some 
+The pointer to the message buffer returned is a structure which has some
 user application visible fields; the structure is described in &cw(rmr.h,)
 and is illustrated below.
 
@@ -63,52 +63,75 @@ typedef struct {
     int len;
     unsigned char* payload;
     unsigned char* xaction;
+       int sub_id;
+       int tp_state;
 } rmr_mbuf_t;
 &ex_end
 
 &space
 &beg_dlist(.75i : ^&bold_font )
-&ditem(state )  Is the current buffer state.  Following a call to &cw(rmr_send_msg) 
+&ditem(state)  Is the current buffer state.  Following a call to &cw(rmr_send_msg)
 the state indicates whether the buffer was successfully sent which determines
 exactly what the payload points to.  If the send failed, the payload referenced
-by the buffer is the message that failed to send (allowing the application to 
-attempt a retransmission).  
+by the buffer is the message that failed to send (allowing the application to
+attempt a retransmission).
 When the state is &cw(RMR_OK) the buffer represents an empty buffer that the application
 may fill in in preparation to send.
 
 &half_space
-&ditem(mtype )  When sending a message, the application is expected to set this field
+&ditem(mtype)  When sending a message, the application is expected to set this field
 to the appropriate message type value (as determined by the user programme). Upon send
 this value determines how the RMR library will route the message.
 For a buffer which has been received, this field will contain the message type that was
-set by the sending application. 
+set by the sending application.
 
 &half_space
-&ditem(len ) The application using a buffer to send a message is expected to set the
+&ditem(len) The application using a buffer to send a message is expected to set the
 length value to the actual number of bytes that it placed into the message. This
 is likely less than the total number of bytes that the message can carry.
 For a message buffer that is passed to the application as the result of a receive
-call, this will be the value that the sending application supplied and should 
+call, this will be the value that the sending application supplied and should
 indicate the number of bytes in the payload which are valid.
 
 &half_space
-&ditem(payload ) The payload is a pointer to the actual received data.  The
+&ditem(payload) The payload is a pointer to the actual received data.  The
 user programme may read and write from/to the memory referenced by the payload
 up until the point in time that the buffer is used on a &cw(rmr_send, rmr_call)
-or &cw(rmr_reply) function call.  
+or &cw(rmr_reply) function call.
 Once the buffer has been passed back to a RMR library function the user programme
 should &bold(NOT) make use of the payload pointer.
 
 
 &half_space
-&ditem(xaction) The &ital(xaction) field is a pointer to a fixed sized area in 
-the message into which the user may write a transaction ID.  
+&ditem(xaction) The &ital(xaction) field is a pointer to a fixed sized area in
+the message into which the user may write a transaction ID.
 The ID is optional with the exception of when the user application uses the &cw(rmr_call)
 function to send a message and wait for the reply; the underlying RMR processing
 expects that the matching reply message will also contain the same data in the
 &ital(xaction) field.
 &end_dlist
 
+&half_space
+&ditem(sub_id)
+This value is the subscription ID. It, in combination with the message type is used
+by rmr to determine the target endpoint when sending a message.
+If the application to application protocol does not warrant the use of a subscription
+ID, the RMR constant RMR_VOID_SUBID should be placed in this field.
+When an application is forwarding or returning a buffer to the sender, it is the
+application's responsibility to set/reset this value.
+
+&half_space
+&ditem(tp_state)
+For C applications making use of RMR, the state of a transport based failure will
+often be available via &cw(errno.)
+However, some wrapper environments may not have direct access to the C-lib &cw(errno)
+value.
+RMR send and receive operations will place the current value of &cw(errno) into this
+field which should make it available to wrapper functions.
+User applications are strongly cautioned against relying on the value of errno as
+some transport mechanisms may not set this value on all calls.
+This value should also be ignored any time the message status is &cw(RMR_OK.)
+
 &h2(RETURN VALUE)
 The function returns a pointer to a &cw(rmr_mbuf) structure, or NULL on error.
 
index a1c0836..101f9e8 100644 (file)
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [0.10.4] - 8/08/2019
+    * Fix underlying problem getting errno from some environments; now references new RMR message field to get errno value.
+       * Add /usr/local/lib64 to tox environment variable to support systems where libraries natually install in lib64 rather than lib.
+
 ## [0.10.3] - 7/31/2019
     * (Correctly) Include license here per Jira RICPLT-1855
 
index bc65237..aeb9e18 100644 (file)
@@ -18,7 +18,7 @@ import uuid
 import json
 from ctypes import RTLD_GLOBAL, Structure, c_int, POINTER, c_char, c_char_p, c_void_p, memmove, cast
 from ctypes import CDLL
-from ctypes import create_string_buffer, pythonapi
+from ctypes import create_string_buffer
 
 # https://docs.python.org/3.7/library/ctypes.html
 # https://stackoverflow.com/questions/2327344/ctypes-loading-a-c-shared-library-that-has-dependencies/30845750#30845750
@@ -89,13 +89,6 @@ def _state_to_status(stateno):
     return sdict.get(stateno, "UNKNOWN STATE")
 
 
-def _errno():
-    """Suss out the C error number value which might be useful in understanding
-    an underlying reason when RMr returns a failure.
-    """
-    return c_int.in_dll(pythonapi, "errno").value
-
-
 ##############
 # PUBLIC API
 ##############
@@ -116,6 +109,7 @@ class rmr_mbuf_t(Structure):
        unsigned char* payload;    // transported data
        unsigned char* xaction;    // pointer to fixed length transaction id bytes
        int sub_id;                // subscription id
+       int      tp_state;         // transport state (a.k.a errno)
                                   // these things are off limits to the user application
        void*   tp_buf;            // underlying transport allocated pointer (e.g. nng message)
        void*   header;            // internal message header (whole buffer: header+payload)
@@ -142,6 +136,7 @@ class rmr_mbuf_t(Structure):
         ),  # according to th following the python bytes are already unsinged https://bytes.com/topic/python/answers/695078-ctypes-unsigned-char
         ("xaction", POINTER(c_char)),
         ("sub_id", c_int),
+        ("tp_state", c_int),
     ]
 
 
@@ -292,7 +287,7 @@ def message_summary(ptr_to_rmr_buf_t):
         "payload max size": rmr_payload_size(ptr_to_rmr_buf_t),
         "meid": meid,
         "message source": get_src(ptr_to_rmr_buf_t),
-        "errno": _errno(),
+        "errno": ptr_to_rmr_buf_t.contents.tp_state,
     }
 
 
index 395bfb3..6037e1f 100644 (file)
@@ -34,6 +34,10 @@ def rcv_mock_generator(msg_payload, msg_type, msg_state, jsonb, timeout=0):
         sbuf.contents.payload = payload
         sbuf.contents.len = len(payload)
         sbuf.contents.state = msg_state
+        if msg_state != 0:                # set something in transport state if 'error'
+            sbuf.contents.tp_state = 99
+        else:
+            sbuf.contents.tp_state = 0
         return sbuf
 
     return f
@@ -48,6 +52,10 @@ def send_mock_generator(msg_state):
 
     def f(_unused, sbuf):
         sbuf.contents.state = msg_state
+        if msg_state != 0:                # set something in transport state if 'error'
+            sbuf.contents.tp_state = 99
+        else:
+            sbuf.contents.tp_state = 0
         return sbuf
 
     return f
@@ -63,6 +71,7 @@ class _Sbuf_Contents:
         self.payload = ""
         self.xaction = uuid.uuid1().hex.encode("utf-8")
         self.sub_id = 0
+        self.tp_state = 0
 
     def __str__(self):
         return str(
@@ -73,6 +82,7 @@ class _Sbuf_Contents:
                 "payload": self.payload,
                 "xaction": self.xaction,
                 "sub_id": self.sub_id,
+                "tp_state": self.tp_state,
             }
         )
 
index daa205e..1df9311 100644 (file)
@@ -29,9 +29,9 @@ def _long_descr():
 
 setup(
     name="rmr",
-    version="0.10.3",
+    version="0.10.4",
     packages=find_packages(),
-    author="Tommy Carpenter",
+    author="Tommy Carpenter, E. Scott Daniels",
     description="Python wrapper for RIC RMR",
     url="https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/lib/rmr",
     license="Apache 2.0",
index c6f498d..d1ecb24 100644 (file)
@@ -22,7 +22,7 @@ deps=
     pytest
     coverage
     pytest-cov
-setenv = LD_LIBRARY_PATH = /usr/local/lib/
+setenv = LD_LIBRARY_PATH = /usr/local/lib/:/usr/local/lib64
 commands=pytest --verbose --cov {envsitepackagesdir}/rmr  --cov-report html
 
 [testenv:flake8]
index a824a2a..5802cc6 100644 (file)
@@ -78,6 +78,14 @@ extern "C" {
 
        (All fields are exposed such that if a wrapper needs to dup the storage as it passes
        into or out of their environment they dup it all, not just what we choose to expose.)
+
+       NOTE:
+       State is the RMR state of processing on the message. The transport state (tp_state)
+       will be set to mirror the value of errno for wrappers unable to access errno directly,
+       but will only be set if state is not RMR_OK. Even then, the value may be suspect as
+       the underlying transport mechanism may not set errno. It is strongly recommended that
+       user applications use tp_state only for dianostic purposes to convey additional information
+       in a log message.
 */
 typedef struct {
        int     state;                                  // state of processing
@@ -86,6 +94,7 @@ typedef struct {
        unsigned char* payload;         // transported data
        unsigned char* xaction;         // pointer to fixed length transaction id bytes
        int sub_id;                                     // subscription id
+       int             tp_state;                       // transport state (errno) valid only if state != RMR_OK, and even then may not be valid
 
                                                                // these things are off limits to the user application
        void*   tp_buf;                         // underlying transport allocated pointer (e.g. nng message)
index 7e0b37e..3529c46 100644 (file)
@@ -254,6 +254,7 @@ extern rmr_mbuf_t*  rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ) {
                errno = EINVAL;                                                                                         // if msg is null, this is their clue
                if( msg != NULL ) {
                        msg->state = RMR_ERR_BADARG;
+                       msg->tp_state = errno;
                }
                return msg;
        }
@@ -262,6 +263,7 @@ extern rmr_mbuf_t*  rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ) {
        if( msg->header == NULL ) {
                fprintf( stderr, "[ERR] rmr_send_msg: message had no header\n" );
                msg->state = RMR_ERR_NOHDR;
+               msg->tp_state = errno;
                return msg;
        }
 
@@ -345,6 +347,7 @@ extern rmr_mbuf_t* rmr_call( void* vctx, rmr_mbuf_t* msg ) {
                if( msg->state != RMR_ERR_RETRY ) {
                        msg->state = RMR_ERR_CALLFAILED;                // errno not available to all wrappers; don't stomp if marked retry
                }
+               msg->tp_state = errno;
                return msg;
        }
 
@@ -365,10 +368,11 @@ extern rmr_mbuf_t* rmr_rcv_msg( void* vctx, rmr_mbuf_t* old_msg ) {
        rmr_mbuf_t*     qm;                             // message that was queued on the ring
 
        if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+               errno = EINVAL;
                if( old_msg != NULL ) {
                        old_msg->state = RMR_ERR_BADARG;
+                       old_msg->tp_state = errno;
                }
-               errno = EINVAL;
                return old_msg;
        }
        errno = 0;
@@ -402,10 +406,11 @@ extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ) {
        rmr_mbuf_t* msg;
 
        if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+               errno = EINVAL;
                if( old_msg != NULL ) {
                        old_msg->state = RMR_ERR_BADARG;
+                       old_msg->tp_state = errno;
                }
-               errno = EINVAL;
                return old_msg;
        }
 
@@ -457,6 +462,7 @@ extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ) {
        nready = epoll_wait( eps->ep_fd, eps->events, 1, ms_to );     // block until something or timedout
        if( nready <= 0 ) {                                             // we only wait on ours, so we assume ready means it's ours
                msg->state = RMR_ERR_TIMEOUT;
+               msg->tp_state = errno;
        } else {
                return rcv_msg( ctx, msg );                                                             // receive it and return it
        }
@@ -480,10 +486,11 @@ extern rmr_mbuf_t* rmr_rcv_specific( void* vctx, rmr_mbuf_t* msg, char* expect,
        int     exp_len = 0;                    // length of expected ID
 
        if( (ctx = (uta_ctx_t *) vctx) == NULL ) {
+               errno = EINVAL;
                if( msg != NULL ) {
                        msg->state = RMR_ERR_BADARG;
+                       msg->tp_state = errno;
                }
-               errno = EINVAL;
                return msg;
        }
 
@@ -827,6 +834,7 @@ extern rmr_mbuf_t* rmr_mt_rcv( void* vctx, rmr_mbuf_t* mbuf, int max_wait ) {
                errno = EINVAL;
                if( mbuf ) {
                        mbuf->state = RMR_ERR_BADARG;
+                       mbuf->tp_state = errno;
                }
                return mbuf;
        }
@@ -835,6 +843,7 @@ extern rmr_mbuf_t* rmr_mt_rcv( void* vctx, rmr_mbuf_t* mbuf, int max_wait ) {
                errno = EINVAL;
                if( mbuf != NULL ) {
                        mbuf->state = RMR_ERR_NOTSUPP;
+                       mbuf->tp_state = errno;
                }
                return mbuf;
        }
@@ -897,6 +906,9 @@ extern rmr_mbuf_t* rmr_mt_rcv( void* vctx, rmr_mbuf_t* mbuf, int max_wait ) {
                }
        }
 
+       if( mbuf ) {
+               mbuf->tp_state = errno;
+       }
        return mbuf;
 }
 
@@ -928,9 +940,10 @@ extern rmr_mbuf_t* rmr_mt_call( void* vctx, rmr_mbuf_t* mbuf, int call_id, int m
        long    nano_sec;                       // max wait xlated to nano seconds
        int             state;
        
+       errno = EINVAL;
        if( (ctx = (uta_ctx_t *) vctx) == NULL || mbuf == NULL ) {
-               errno = EINVAL;
                if( mbuf ) {
+                       mbuf->tp_state = errno;
                        mbuf->state = RMR_ERR_BADARG;
                }
                return mbuf;
@@ -938,11 +951,13 @@ extern rmr_mbuf_t* rmr_mt_call( void* vctx, rmr_mbuf_t* mbuf, int call_id, int m
 
        if( ! (ctx->flags & CFL_MTC_ENABLED) ) {
                mbuf->state = RMR_ERR_NOTSUPP;
+               mbuf->tp_state = errno;
                return mbuf;
        }
 
        if( call_id > MAX_CALL_ID || call_id < 2 ) {                                    // 0 and 1 are reserved; user app cannot supply them
                mbuf->state = RMR_ERR_BADARG;
+               mbuf->tp_state = errno;
                return mbuf;
        }
 
@@ -984,6 +999,7 @@ extern rmr_mbuf_t* rmr_mt_call( void* vctx, rmr_mbuf_t* mbuf, int call_id, int m
        mbuf = mtosend_msg( ctx, mbuf, 0 );                                             // use internal function so as not to strip call-id; should be nil on success!
        if( mbuf ) {
                if( mbuf->state != RMR_OK ) {
+                       mbuf->tp_state = errno;
                        return mbuf;                                                                    // timeout or unable to connect or no endpoint are most likely issues
                }
        }
index 79e6793..4a71826 100644 (file)
@@ -402,6 +402,9 @@ static inline rmr_mbuf_t* realloc_msg( rmr_mbuf_t* old_msg, int tr_len  ) {
        reuse.  They have their reasons I guess.  Thus, we will free
        the old transport buffer if user passes the message in; at least
        our mbuf will be reused.
+
+       When msg->state is not ok, this function must set tp_state in the message as some API 
+       fucntions return the message directly and do not propigate errno into the message.
 */
 static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) {
        int state;
@@ -428,11 +431,14 @@ static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) {
 
        msg->state = nng_recvmsg( ctx->nn_sock, (nng_msg **) &msg->tp_buf, NO_FLAGS );                  // blocks hard until received
        if( (msg->state = xlate_nng_state( msg->state, RMR_ERR_RCVFAILED )) != RMR_OK ) {
+               msg->tp_state = errno;
                return msg;
        }
 
+       msg->tp_state = 0;
        if( msg->tp_buf == NULL ) {             // if state is good this _should_ not be nil, but parninoia says check anyway
                msg->state = RMR_ERR_EMPTY;
+               msg->tp_state = 0;
                return msg;
        }
 
@@ -446,6 +452,7 @@ static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) {
                                msg->mtype, msg->state, msg->len,  msg->payload - (unsigned char *) msg->header );
        } else {
                msg->state = RMR_ERR_EMPTY;
+               msg->tp_state = 0;
                msg->len = 0;
                msg->alloc_len = rsize;
                msg->payload = NULL;
@@ -509,6 +516,9 @@ static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) {
 
        Called by rmr_send_msg() and rmr_rts_msg(), etc. and thus we assume that all pointer
        validation has been done prior.
+
+       When msg->state is not ok, this function must set tp_state in the message as some API 
+       fucntions return the message directly and do not propigate errno into the message.
 */
 static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock, int retries ) {
        int state;
@@ -560,6 +570,7 @@ static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock
                // future: this should not happen as all buffers we deal with are zc buffers; might make sense to remove the test and else
                msg->state = RMR_ERR_SENDFAILED;
                errno = ENOTSUP;
+               msg->tp_state = errno;
                return msg;
                /*
                NOT SUPPORTED
@@ -607,6 +618,10 @@ static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock
        message type is used.  If the initial lookup, with a subid, fails, then a
        second lookup using just the mtype is tried.
 
+       When msg->state is not OK, this function must set tp_state in the message as 
+       some API fucntions return the message directly and do not propigate errno into 
+       the message.
+
        CAUTION: this is a non-blocking send.  If the message cannot be sent, then
                it will return with an error and errno set to eagain. If the send is
                a limited fanout, then the returned status is the status of the last
@@ -629,6 +644,7 @@ static  rmr_mbuf_t* mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
                if( msg != NULL ) {
                        msg->state = RMR_ERR_BADARG;
                        errno = EINVAL;                                                                                 // must ensure it's not eagain
+                       msg->tp_state = errno;
                }
                return msg;
        }
@@ -638,6 +654,7 @@ static  rmr_mbuf_t* mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
                fprintf( stderr, "rmr_send_msg: ERROR: message had no header\n" );
                msg->state = RMR_ERR_NOHDR;
                errno = EBADMSG;                                                                                        // must ensure it's not eagain
+               msg->tp_state = errno;
                return msg;
        }
 
@@ -667,6 +684,7 @@ static  rmr_mbuf_t* mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
 
                        msg->state = RMR_ERR_NOENDPT;
                        errno = ENXIO;                                                                                  // must ensure it's not eagain
+                       msg->tp_state = errno;
                        return msg;                                                                                             // caller can resend (maybe) or free
                }
 
index a3c8350..ca2e6f2 100644 (file)
@@ -184,12 +184,14 @@ static int rmr_api_test( ) {
        msg->len = 100;
        msg->mtype = 1;
        msg->state = 999;
+       msg->tp_state = 999;
        errno = 999;
        msg = rmr_send_msg( rmc, msg );
        errors += fail_if_nil( msg, "send_msg_ did not return a message on send "  );
        if( msg ) {
                errors += fail_not_equal( msg->state, RMR_ERR_NOENDPT, "send_msg did not return no endpoints before rtable added "  );
                errors += fail_if( errno == 0, "send_msg did not set errno "  );
+               errors += fail_if( msg->tp_state == 999, "send_msg did not set tp_state (1)" );
        }
 
        gen_rt( rmc );          // --- after this point there is a dummy route table so send and rts calls should be ok
@@ -197,6 +199,7 @@ static int rmr_api_test( ) {
        msg->len = 100;
        msg->mtype = 1;
        msg->state = 999;
+       msg->tp_state = 999;
        errno = 999;
        msg = rmr_send_msg( rmc, msg );
        errors += fail_if_nil( msg, "send_msg_ did not return a message on send "  );
@@ -205,6 +208,7 @@ static int rmr_api_test( ) {
                errors += fail_if( errno != 0, "send_msg set errno for send that should work "  );
                v = rmr_payload_size( msg );
                errors += fail_if( v != 2048, "send_msg did not allocate new buffer with correct size "  );
+               errors += fail_if( msg->tp_state == 999, "send_msg did not set tp_state (2)" );
        }
 
        rmr_set_stimeout( NULL, 0 );