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