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