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