Define message-summary dict keys as constants
[ric-plt/xapp-frame-py.git] / ricxappframe / rmr / rmr.py
1 # ==================================================================================
2 #       Copyright (c) 2019-2020 Nokia
3 #       Copyright (c) 2018-2020 AT&T Intellectual Property.
4 #
5 #   Licensed under the Apache License, Version 2.0 (the "License");
6 #   you may not use this file except in compliance with the License.
7 #   You may obtain a copy of the License at
8 #
9 #          http://www.apache.org/licenses/LICENSE-2.0
10 #
11 #   Unless required by applicable law or agreed to in writing, software
12 #   distributed under the License is distributed on an "AS IS" BASIS,
13 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 #   See the License for the specific language governing permissions and
15 #   limitations under the License.
16 # ==================================================================================
17 import uuid
18 from ctypes import POINTER, Structure
19 from ctypes import c_int, c_char, c_char_p, c_void_p, memmove, cast, create_string_buffer
20
21 from ricxappframe.rmr.exceptions import BadBufferAllocation, MeidSizeOutOfRange, InitFailed
22 from ricxappframe.rmr.rmrclib.rmrclib import rmr_c_lib, get_constants, state_to_status
23
24 ##############
25 # PRIVATE API
26 ##############
27
28
29 def _get_rmr_constant(key: str, default=None):
30     """
31     Gets the constant with the named key from the RMR C library.
32     Returns None if the value is not a simple type. This happens
33     during sphinx autodoc document generation, which mocks the
34     rmrclib package to work without the RMR shared object file,
35     and the response is something like this:
36     <class 'ricxappframe.rmr.rmrclib.rmrclib.get_constants.get'>
37     Workaround for https://github.com/sphinx-doc/sphinx/issues/7422
38     """
39     val = get_constants().get(key, default)
40     return val if isinstance(val, (type(None), bool, bytes, float, int, str)) else None
41
42
43 # argtypes and restype are important:
44 # https://stackoverflow.com/questions/24377845/ctype-why-specify-argtypes
45 def _wrap_rmr_function(funcname, restype, argtypes):
46     """
47     Simplify wrapping ctypes functions.
48
49     Parameters
50     ----------
51     funcname: str
52         Name of library method
53     restype: class
54         Name of ctypes class; e.g., c_char_p
55     argtypes: list
56         List of ctypes classes; e.g., [ c_char_p, int ]
57
58     Returns
59     -------
60     _FuncPointer:
61         Pointer to C library function
62 """
63     func = rmr_c_lib.__getattr__(funcname)
64     func.restype = restype
65     func.argtypes = argtypes
66     return func
67
68
69 ##############
70 # PUBLIC API
71 ##############
72
73 # Publish constants from RMR C-language header files for use by importers of this library.
74 # TODO: Are there others that will be useful?
75 #: Typical size message to receive; size is not limited
76 RMR_MAX_RCV_BYTES = _get_rmr_constant('RMR_MAX_RCV_BYTES')
77 #: Multi-threaded initialization flag
78 RMRFL_MTCALL = _get_rmr_constant('RMRFL_MTCALL', 0x02)  # initialization flags
79 #: Empty flag
80 RMRFL_NONE = _get_rmr_constant('RMRFL_NONE', 0x0)
81 #: State constant for OK
82 RMR_OK = _get_rmr_constant('RMR_OK', 0x00)
83 #: State constant for no endpoint based on msg type
84 RMR_ERR_NOENDPT = _get_rmr_constant('RMR_ERR_NOENDPT')
85 #: State constant for retry
86 RMR_ERR_RETRY = _get_rmr_constant('RMR_ERR_RETRY')
87 #: State constant for timeout
88 RMR_ERR_TIMEOUT = _get_rmr_constant('RMR_ERR_TIMEOUT')
89
90 # Publish keys used in the message summary dict as constants
91
92 # message payload, bytes
93 RMR_MS_PAYLOAD = "payload"
94 # payload length, integer
95 RMR_MS_PAYLOAD_LEN = "payload length"
96 # message type, integer
97 RMR_MS_MSG_TYPE = "message type"
98 # subscription ID, integer
99 RMR_MS_SUB_ID = "subscription id"
100 # transaction ID, bytes
101 RMR_MS_TRN_ID = "transaction id"
102 # state of message processing, integer; e.g., 0
103 RMR_MS_MSG_STATE = "message state"
104 # state of message processing converted to string; e.g., RMR_OK
105 RMR_MS_MSG_STATUS = "message status"
106 # number of bytes usable in the payload, integer
107 RMR_MS_PAYLOAD_MAX = "payload max size"
108 # managed entity ID, bytes
109 RMR_MS_MEID = "meid"
110 # message source, string; e.g., host:port
111 RMR_MS_MSG_SOURCE = "message source"
112 # transport state, integer
113 RMR_MS_ERRNO = "errno"
114
115
116 class rmr_mbuf_t(Structure):
117     """
118     Mirrors public members of type rmr_mbuf_t from RMR header file src/common/include/rmr.h
119
120     | typedef struct {
121     |    int     state;          // state of processing
122     |    int     mtype;          // message type
123     |    int     len;            // length of data in the payload (send or received)
124     |    unsigned char* payload; // transported data
125     |    unsigned char* xaction; // pointer to fixed length transaction id bytes
126     |    int sub_id;             // subscription id
127     |    int      tp_state;      // transport state (a.k.a errno)
128     |
129     | these things are off limits to the user application
130     |
131     |    void*   tp_buf;         // underlying transport allocated pointer
132     |    void*   header;         // internal message header (whole buffer: header+payload)
133     |    unsigned char* id;      // if we need an ID in the message separate from the xaction id
134     |    int flags;              // various MFL (private) flags as needed
135     |    int alloc_len;          // the length of the allocated space (hdr+payload)
136     | } rmr_mbuf_t;
137
138     RE PAYLOADs type below, see the documentation for c_char_p:
139        class ctypes.c_char_p
140             Represents the C char * datatype when it points to a zero-terminated string.
141             For a general character pointer that may also point to binary data, POINTER(c_char)
142             must be used. The constructor accepts an integer address, or a bytes object.
143     """
144
145     _fields_ = [
146         ("state", c_int),
147         ("mtype", c_int),
148         ("len", c_int),
149         ("payload", POINTER(c_char)),  # according to the following the python bytes are already unsigned
150                                        # https://bytes.com/topic/python/answers/695078-ctypes-unsigned-char
151         ("xaction", POINTER(c_char)),
152         ("sub_id", c_int),
153         ("tp_state", c_int),
154     ]
155
156
157 _rmr_init = _wrap_rmr_function('rmr_init', c_void_p, [c_char_p, c_int, c_int])
158
159
160 def rmr_init(uproto_port: c_char_p, max_msg_size: int, flags: int) -> c_void_p:
161     """
162     Prepares the environment for sending and receiving messages.
163     Refer to RMR C documentation for method::
164
165         extern void* rmr_init(char* uproto_port, int max_msg_size, int flags)
166
167     This function raises an exception if the returned context is None.
168
169     Parameters
170     ----------
171     uproto_port: c_char_p
172         Pointer to bytes built from the port number as a string; e.g., b'4550'
173     max_msg_size: integer
174         Maximum message size to receive
175     flags: integer
176         RMR option flags
177
178     Returns
179     -------
180     c_void_p:
181         Pointer to RMR context
182     """
183     mrc = _rmr_init(uproto_port, max_msg_size, flags)
184     if mrc is None:
185         raise InitFailed()
186     return mrc
187
188
189 _rmr_ready = _wrap_rmr_function('rmr_ready', c_int, [c_void_p])
190
191
192 def rmr_ready(vctx: c_void_p) -> int:
193     """
194     Checks if a routing table has been received and installed.
195     Refer to RMR C documentation for method::
196
197         extern int rmr_ready(void* vctx)
198
199     Parameters
200     ----------
201     vctx: ctypes c_void_p
202         Pointer to RMR context
203
204     Returns
205     -------
206     1 for yes, 0 for no
207     """
208     return _rmr_ready(vctx)
209
210
211 _rmr_close = _wrap_rmr_function('rmr_close', None, [c_void_p])
212
213
214 def rmr_close(vctx: c_void_p):
215     """
216     Closes the listen socket.
217     Refer to RMR C documentation for method::
218
219         extern void rmr_close(void* vctx)
220
221     Parameters
222     ----------
223     vctx: ctypes c_void_p
224         Pointer to RMR context
225
226     Returns
227     -------
228     None
229     """
230     _rmr_close(vctx)
231
232
233 _rmr_set_stimeout = _wrap_rmr_function('rmr_set_stimeout', c_int, [c_void_p, c_int])
234
235
236 def rmr_set_stimeout(vctx: c_void_p, rloops: int) -> int:
237     """
238     Sets the configuration for how RMR will retry message send operations.
239     Refer to RMR C documentation for method::
240
241         extern int rmr_set_stimeout(void* vctx, int rloops)
242
243     Parameters
244     ----------
245     vctx: ctypes c_void_p
246         Pointer to RMR context
247     rloops: int
248         Number of retry loops
249
250     Returns
251     -------
252     0 on success, -1 on failure
253     """
254     return _rmr_set_stimeout(vctx, rloops)
255
256
257 _rmr_alloc_msg = _wrap_rmr_function('rmr_alloc_msg', POINTER(rmr_mbuf_t), [c_void_p, c_int])
258
259
260 def rmr_alloc_msg(vctx: c_void_p, size: int,
261                   payload=None, gen_transaction_id=False, mtype=None,
262                   meid=None, sub_id=None, fixed_transaction_id=None):
263     """
264     Allocates and returns a buffer to write and send through the RMR library.
265     Refer to RMR C documentation for method::
266
267         extern rmr_mbuf_t* rmr_alloc_msg(void* vctx, int size)
268
269     Optionally populates the message from the remaining arguments.
270
271     TODO: on next API break, clean up transaction_id ugliness. Kept for now to preserve API.
272
273     Parameters
274     ----------
275     vctx: ctypes c_void_p
276         Pointer to RMR context
277     size: int
278         How much space to allocate
279     payload: bytes
280         if not None, attempts to set the payload
281     gen_transaction_id: bool
282         if True, generates and sets a transaction ID.
283         Note, option fixed_transaction_id overrides this option
284     mtype: bytes
285         if not None, sets the sbuf's message type
286     meid: bytes
287         if not None, sets the sbuf's meid
288     sub_id: bytes
289         if not None, sets the sbuf's subscription id
290     fixed_transaction_id: bytes
291         if not None, used as the transaction ID.
292         Note, this overrides the option gen_transaction_id
293
294     Returns
295     -------
296     c_void_p:
297         Pointer to rmr_mbuf structure
298     """
299     sbuf = _rmr_alloc_msg(vctx, size)
300     try:
301         # make sure the alloc worked
302         sbuf.contents
303
304         # set specified fields
305         if payload:
306             set_payload_and_length(payload, sbuf)
307
308         if fixed_transaction_id:
309             set_transaction_id(sbuf, fixed_transaction_id)
310         elif gen_transaction_id:
311             generate_and_set_transaction_id(sbuf)
312
313         if mtype:
314             sbuf.contents.mtype = mtype
315
316         if meid:
317             rmr_set_meid(sbuf, meid)
318
319         if sub_id:
320             sbuf.contents.sub_id = sub_id
321
322         return sbuf
323
324     except ValueError:
325         raise BadBufferAllocation
326
327
328 _rmr_realloc_payload = _wrap_rmr_function('rmr_realloc_payload', POINTER(rmr_mbuf_t), [POINTER(rmr_mbuf_t), c_int, c_int, c_int])  # new_len, copy, clone
329
330
331 def rmr_realloc_payload(ptr_mbuf: c_void_p, new_len: int, copy=False, clone=False):
332     """
333     Allocates and returns a message buffer large enough for the new length.
334     Refer to RMR C documentation for method::
335
336         extern rmr_mbuf_t* rmr_realloc_payload(rmr_mbuf_t*, int, int, int)
337
338     Parameters
339     ----------
340     ptr_mbuf: c_void_p
341         Pointer to rmr_mbuf structure
342     new_len: int
343         Length
344     copy: bool
345         Whether to copy the original paylod
346     clone: bool
347         Whether to clone the original buffer
348
349     Returns
350     -------
351     c_void_p:
352         Pointer to rmr_mbuf structure
353     """
354     return _rmr_realloc_payload(ptr_mbuf, new_len, copy, clone)
355
356
357 _rmr_free_msg = _wrap_rmr_function('rmr_free_msg', None, [POINTER(rmr_mbuf_t)])
358
359
360 def rmr_free_msg(ptr_mbuf: c_void_p):
361     """
362     Releases the message buffer.
363     Refer to RMR C documentation for method::
364
365         extern void rmr_free_msg(rmr_mbuf_t* mbuf )
366
367     Parameters
368     ----------
369     ptr_mbuf: c_void_p
370         Pointer to rmr_mbuf structure
371
372     Returns
373     -------
374     None
375     """
376     if ptr_mbuf is not None:
377         _rmr_free_msg(ptr_mbuf)
378
379
380 _rmr_payload_size = _wrap_rmr_function('rmr_payload_size', c_int, [POINTER(rmr_mbuf_t)])
381
382
383 def rmr_payload_size(ptr_mbuf: c_void_p) -> int:
384     """
385     Gets the number of bytes available in the payload.
386     Refer to RMR C documentation for method::
387
388         extern int rmr_payload_size(rmr_mbuf_t* msg)
389
390     Parameters
391     ----------
392     ptr_mbuf: c_void_p
393         Pointer to rmr_mbuf structure
394
395     Returns
396     -------
397     int:
398         Number of bytes available
399     """
400     return _rmr_payload_size(ptr_mbuf)
401
402
403 """
404 The following functions all seem to have the same interface
405 """
406
407 _rmr_send_msg = _wrap_rmr_function('rmr_send_msg', POINTER(rmr_mbuf_t), [c_void_p, POINTER(rmr_mbuf_t)])
408
409
410 def rmr_send_msg(vctx: c_void_p, ptr_mbuf: POINTER(rmr_mbuf_t)) -> POINTER(rmr_mbuf_t):
411     """
412     Sends the message according to the routing table and returns an empty buffer.
413     Refer to RMR C documentation for method::
414
415         extern rmr_mbuf_t* rmr_send_msg(void* vctx, rmr_mbuf_t* msg)
416
417     Parameters
418     ----------
419     vctx: ctypes c_void_p
420         Pointer to RMR context
421     ptr_mbuf: c_void_p
422         Pointer to rmr_mbuf structure
423
424     Returns
425     -------
426     c_void_p:
427         Pointer to rmr_mbuf structure
428     """
429     return _rmr_send_msg(vctx, ptr_mbuf)
430
431
432 # TODO: the old message (Send param) is actually optional, but I don't know how to specify that in Ctypes.
433 _rmr_rcv_msg = _wrap_rmr_function('rmr_rcv_msg', POINTER(rmr_mbuf_t), [c_void_p, POINTER(rmr_mbuf_t)])
434
435
436 def rmr_rcv_msg(vctx: c_void_p, ptr_mbuf: POINTER(rmr_mbuf_t)) -> POINTER(rmr_mbuf_t):
437     """
438     Waits for a message to arrive, and returns it.
439     Refer to RMR C documentation for method::
440
441         extern rmr_mbuf_t* rmr_rcv_msg(void* vctx, rmr_mbuf_t* old_msg)
442
443     Parameters
444     ----------
445     vctx: ctypes c_void_p
446         Pointer to RMR context
447     ptr_mbuf: c_void_p
448         Pointer to rmr_mbuf structure
449
450     Returns
451     -------
452     c_void_p:
453         Pointer to rmr_mbuf structure
454     """
455     return _rmr_rcv_msg(vctx, ptr_mbuf)
456
457
458 _rmr_torcv_msg = _wrap_rmr_function('rmr_torcv_msg', POINTER(rmr_mbuf_t), [c_void_p, POINTER(rmr_mbuf_t), c_int])
459
460
461 def rmr_torcv_msg(vctx: c_void_p, ptr_mbuf: POINTER(rmr_mbuf_t), ms_to: int) -> POINTER(rmr_mbuf_t):
462     """
463     Waits up to the timeout value for a message to arrive, and returns it.
464     Refer to RMR C documentation for method::
465
466         extern rmr_mbuf_t* rmr_torcv_msg(void* vctx, rmr_mbuf_t* old_msg, int ms_to)
467
468     Parameters
469     ----------
470     vctx: ctypes c_void_p
471         Pointer to RMR context
472     ptr_mbuf: c_void_p
473         Pointer to rmr_mbuf structure
474     ms_to: int
475         Time out value in milliseconds
476
477     Returns
478     -------
479     c_void_p:
480         Pointer to rmr_mbuf structure
481     """
482     return _rmr_torcv_msg(vctx, ptr_mbuf, ms_to)
483
484
485 _rmr_rts_msg = _wrap_rmr_function('rmr_rts_msg', POINTER(rmr_mbuf_t), [c_void_p, POINTER(rmr_mbuf_t)])
486
487
488 def rmr_rts_msg(vctx: c_void_p, ptr_mbuf: POINTER(rmr_mbuf_t), payload=None, mtype=None) -> POINTER(rmr_mbuf_t):
489     """
490     Sends a message to the originating endpoint and returns an empty buffer.
491     Refer to RMR C documentation for method::
492
493         extern rmr_mbuf_t* rmr_rts_msg(void* vctx, rmr_mbuf_t* msg)
494
495     additional features beyond c-rmr:
496         if payload is not None, attempts to set the payload
497         if mtype is not None, sets the sbuf's message type
498
499     Parameters
500     ----------
501     vctx: ctypes c_void_p
502         Pointer to an RMR context
503     ptr_mbuf: ctypes c_void_p
504         Pointer to an RMR message buffer
505     payload: bytes
506         Payload
507     mtype: bytes
508         Message type
509
510     Returns
511     -------
512     c_void_p:
513         Pointer to rmr_mbuf structure
514     """
515
516     if payload:
517         set_payload_and_length(payload, ptr_mbuf)
518
519     if mtype:
520         ptr_mbuf.contents.mtype = mtype
521
522     return _rmr_rts_msg(vctx, ptr_mbuf)
523
524
525 _rmr_call = _wrap_rmr_function('rmr_call', POINTER(rmr_mbuf_t), [c_void_p, POINTER(rmr_mbuf_t)])
526
527
528 def rmr_call(vctx: c_void_p, ptr_mbuf: POINTER(rmr_mbuf_t)) -> POINTER(rmr_mbuf_t):
529     """
530     Sends a message, waits for a response and returns it.
531     Refer to RMR C documentation for method::
532
533         extern rmr_mbuf_t* rmr_call(void* vctx, rmr_mbuf_t* msg)
534
535     Parameters
536     ----------
537     ptr_mbuf: ctypes c_void_p
538         Pointer to an RMR message buffer
539
540     Returns
541     -------
542     c_void_p:
543         Pointer to rmr_mbuf structure
544     """
545     return _rmr_call(vctx, ptr_mbuf)
546
547
548 _rmr_bytes2meid = _wrap_rmr_function('rmr_bytes2meid', c_int, [POINTER(rmr_mbuf_t), c_char_p, c_int])
549
550
551 def rmr_set_meid(ptr_mbuf: POINTER(rmr_mbuf_t), byte_str: bytes) -> int:
552     """
553     Sets the managed entity field in the message and returns the number of bytes copied.
554     Refer to RMR C documentation for method::
555
556         extern int rmr_bytes2meid(rmr_mbuf_t* mbuf, unsigned char const* src, int len);
557
558     Caution:  the meid length supported in an RMR message is 32 bytes, but C applications
559     expect this to be a nil terminated string and thus only 31 bytes are actually available.
560
561     Raises: exceptions.MeidSizeOutOfRang
562
563     Parameters
564     ----------
565     ptr_mbuf: ctypes c_void_p
566         Pointer to an RMR message buffer
567     byte_tr: bytes
568         Managed entity ID value
569
570     Returns
571     -------
572     int:
573         number of bytes copied
574     """
575     max = _get_rmr_constant("RMR_MAX_MEID", 32)
576     if len(byte_str) >= max:
577         raise MeidSizeOutOfRange
578
579     return _rmr_bytes2meid(ptr_mbuf, byte_str, len(byte_str))
580
581
582 # CAUTION:  Some of the C functions expect a mutable buffer to copy the bytes into;
583 #           if there is a get_* function below, use it to set up and return the
584 #           buffer properly.
585
586 # extern unsigned char*  rmr_get_meid(rmr_mbuf_t* mbuf, unsigned char* dest);
587 # we don't provide direct access to this function (unless it is asked for) because it is not really useful to provide your own buffer.
588 # Rather, rmr_get_meid does this for you, and just returns the string.
589 _rmr_get_meid = _wrap_rmr_function('rmr_get_meid', c_char_p, [POINTER(rmr_mbuf_t), c_char_p])
590
591
592 def rmr_get_meid(ptr_mbuf: POINTER(rmr_mbuf_t)) -> bytes:
593     """
594     Gets the managed entity ID (meid) from the message header.
595     This is a python-friendly version of RMR C method::
596
597         extern unsigned char* rmr_get_meid(rmr_mbuf_t* mbuf, unsigned char* dest);
598
599     Parameters
600     ----------
601     ptr_mbuf: ctypes c_void_p
602         Pointer to an RMR message buffer
603
604     Returns
605     -------
606     bytes:
607         Managed entity ID
608     """
609     sz = _get_rmr_constant("RMR_MAX_MEID", 32)  # size for buffer to fill
610     buf = create_string_buffer(sz)
611     _rmr_get_meid(ptr_mbuf, buf)
612     return buf.value
613
614
615 _rmr_get_src = _wrap_rmr_function('rmr_get_src', c_char_p, [POINTER(rmr_mbuf_t), c_char_p])
616
617
618 def rmr_get_src(ptr_mbuf: POINTER(rmr_mbuf_t), dest: c_char_p) -> c_char_p:
619     """
620     Copies the message-source information to the buffer.
621     Refer to RMR C documentation for method::
622
623         extern unsigned char* rmr_get_src(rmr_mbuf_t* mbuf, unsigned char* dest);
624
625     Parameters
626     ----------
627     ptr_mbuf: ctypes POINTER(rmr_mbuf_t)
628         Pointer to an RMR message buffer
629     dest: ctypes c_char_p
630         Pointer to a buffer to receive the message source
631
632     Returns
633     -------
634     string:
635         message-source information
636     """
637     return _rmr_get_src(ptr_mbuf, dest)
638
639
640 _rmr_set_vlevel = _wrap_rmr_function('rmr_set_vlevel', None, [c_int])
641
642
643 def rmr_set_vlevel(new_level: c_int):
644     """
645     Sets the verbosity level which determines the messages RMR writes to standard error.
646     Refer to RMR C documentation for method::
647
648         void rmr_set_vlevel( int new_level )
649
650     Parameters
651     ----------
652     new_level: int
653         New logging verbosity level, an integer in the range 0 (none) to 5 (debug).
654     """
655     _rmr_set_vlevel(new_level)
656
657
658 # Methods that exist ONLY in rmr-python, and are not wrapped methods
659 # In hindsight, I wish i put these in a separate module, but leaving this here to prevent api breakage.
660
661
662 def get_payload(ptr_mbuf: c_void_p) -> bytes:
663     """
664     Gets the binary payload from the rmr_buf_t*.
665
666     Parameters
667     ----------
668     ptr_mbuf: ctypes c_void_p
669         Pointer to an rmr message buffer
670
671     Returns
672     -------
673     bytes:
674         the message payload
675     """
676     # Logic came from the answer here: https://stackoverflow.com/questions/55103298/python-ctypes-read-pointerc-char-in-python
677     sz = ptr_mbuf.contents.len
678     CharArr = c_char * sz
679     return CharArr(*ptr_mbuf.contents.payload[:sz]).raw
680
681
682 def get_xaction(ptr_mbuf: c_void_p) -> bytes:
683     """
684     Gets the transaction ID from the rmr_buf_t*.
685
686     Parameters
687     ----------
688     ptr_mbuf: ctypes c_void_p
689         Pointer to an rmr message buffer
690
691     Returns
692     -------
693     bytes:
694         the transaction id
695     """
696     val = cast(ptr_mbuf.contents.xaction, c_char_p).value
697     sz = _get_rmr_constant("RMR_MAX_XID", 0)
698     return val[:sz]
699
700
701 def message_summary(ptr_mbuf: c_void_p) -> dict:
702     """
703     Builds a dict with the contents of an RMR message.
704
705     Parameters
706     ----------
707     ptr_mbuf: ctypes c_void_p
708         Pointer to an RMR message buffer
709
710     Returns
711     -------
712     dict:
713         Message content as key-value pairs; keys are defined as RMR_MS_* constants.
714     """
715     return {
716         RMR_MS_PAYLOAD: get_payload(ptr_mbuf) if ptr_mbuf.contents.state == RMR_OK else None,
717         RMR_MS_PAYLOAD_LEN: ptr_mbuf.contents.len,
718         RMR_MS_MSG_TYPE: ptr_mbuf.contents.mtype,
719         RMR_MS_SUB_ID: ptr_mbuf.contents.sub_id,
720         RMR_MS_TRN_ID: get_xaction(ptr_mbuf),
721         RMR_MS_MSG_STATE: ptr_mbuf.contents.state,
722         RMR_MS_MSG_STATUS: state_to_status(ptr_mbuf.contents.state),
723         RMR_MS_PAYLOAD_MAX: rmr_payload_size(ptr_mbuf),
724         RMR_MS_MEID: rmr_get_meid(ptr_mbuf),
725         RMR_MS_MSG_SOURCE: get_src(ptr_mbuf),
726         RMR_MS_ERRNO: ptr_mbuf.contents.tp_state,
727     }
728
729
730 def set_payload_and_length(byte_str: bytes, ptr_mbuf: c_void_p):
731     """
732     Sets an rmr payload and content length.
733
734     Parameters
735     ----------
736     byte_str: bytes
737         the bytes to set the payload to
738     ptr_mbuf: ctypes c_void_p
739         Pointer to an rmr message buffer
740
741     Returns
742     -------
743     None
744     """
745     if rmr_payload_size(ptr_mbuf) < len(byte_str):  # existing message payload too small
746         ptr_mbuf = rmr_realloc_payload(ptr_mbuf, len(byte_str), True)
747
748     memmove(ptr_mbuf.contents.payload, byte_str, len(byte_str))
749     ptr_mbuf.contents.len = len(byte_str)
750
751
752 def generate_and_set_transaction_id(ptr_mbuf: c_void_p):
753     """
754     Generates a UUID and sets the RMR transaction id to it
755
756     Parameters
757     ----------
758     ptr_mbuf: ctypes c_void_p
759         Pointer to an rmr message buffer
760     """
761     set_transaction_id(ptr_mbuf, uuid.uuid1().hex.encode("utf-8"))
762
763
764 def set_transaction_id(ptr_mbuf: c_void_p, tid_bytes: bytes):
765     """
766     Sets an RMR transaction id
767     TODO: on next API break, merge these two functions. Not done now to preserve API.
768
769     Parameters
770     ----------
771     ptr_mbuf: ctypes c_void_p
772         Pointer to an rmr message buffer
773     tid_bytes: bytes
774         bytes of the desired transaction id
775     """
776     sz = _get_rmr_constant("RMR_MAX_XID", 0)
777     memmove(ptr_mbuf.contents.xaction, tid_bytes, sz)
778
779
780 def get_src(ptr_mbuf: c_void_p) -> str:
781     """
782     Gets the message source (likely host:port)
783
784     Parameters
785     ----------
786     ptr_mbuf: ctypes c_void_p
787         Pointer to an rmr message buffer
788
789     Returns
790     -------
791     string:
792         message source
793     """
794     sz = _get_rmr_constant("RMR_MAX_SRC", 64)  # size to fill
795     buf = create_string_buffer(sz)
796     rmr_get_src(ptr_mbuf, buf)
797     return buf.value.decode()