updated pynorm Python binding to use Python 3 contributions from Honglei

pull/77/head
bebopagogo 2022-11-11 20:17:30 -05:00
parent 6318f9bf62
commit 92927c8a37
12 changed files with 832 additions and 322 deletions

View File

@ -519,8 +519,6 @@ bool NormSocket::Listen(UINT16 serverPort, const char* groupAddr, const char* se
NormSetSilentReceiver(norm_session, true);
// So that the listener can construct (unsent) ACKs without failure
// (I.e., these ACKs are never sent by the listener but this call is
// needed as a "work around" existing NORM "connectionless" behavior)
NormSetDefaultUnicastNack(norm_session, true);
// Note we use a _small_ buffer size here since a "listening" socket isn't

View File

@ -70,7 +70,10 @@ class NormStreamer
{
loopback = state;
if (NORM_SESSION_INVALID != norm_session)
{
NormSetMulticastLoopback(norm_session, state);
NormSetLoopback(norm_session, state); // XXX test code
}
}
void SetFtiInfo(bool state)
{
@ -567,8 +570,11 @@ bool NormStreamer::OpenNormSession(NormInstanceHandle instance, const char* addr
{
NormSetRxPortReuse(norm_session, true);
if (loopback)
{
NormSetMulticastLoopback(norm_session, true);
}
}
NormSetLoopback(norm_session, loopback);
// Set some default parameters (maybe we should put parameter setting in Start())
NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM);
@ -889,11 +895,11 @@ unsigned int NormStreamer::WriteToStream(const char* buffer, unsigned int numByt
if (ack_ex)
{
const char* req = "Hello, acker";
NormSetWatermarkEx(norm_session, tx_stream, req, strlen(req) + 1);
NormSetWatermarkEx(norm_session, tx_stream, req, strlen(req) + 1);//, true);
}
else
{
NormSetWatermark(norm_session, tx_stream);
NormSetWatermark(norm_session, tx_stream);//, true);
}
tx_watermark_pending = true;
@ -1178,7 +1184,6 @@ void NormStreamer::HandleNormEvent(const NormEvent& event)
break;
case NORM_TX_WATERMARK_COMPLETED:
TRACE("NORM_TX_WATERMARK_COMPLETED ...\n");
if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session))
{
//fprintf(stderr, "WATERMARK COMPLETED\n");
@ -2051,11 +2056,7 @@ int main(int argc, char* argv[])
}
// TBD - should provide more error checking of NORM API calls
TRACE("creating instance ...\n");
NormInstanceHandle normInstance = NormCreateInstance(boostPriority);
TRACE("restarting instance ...\n");
//NormRestartInstance(normInstance); // xxx
TRACE("instance restarted.\n");
NormSetDebugLevel(debugLevel);
if ((NULL != logFile) && !NormOpenDebugLog(normInstance, logFile))

423
examples/python/normCast.py Normal file
View File

@ -0,0 +1,423 @@
'''
LD_LIBRARY_PATH=/home/honglei/norm_test/
'''
# sender/Win10:
#--id 34252 --send "C:\Users\Admin\AppData\Roaming\Wing Pro 9" --repeat 1 --addr 224.1.2.4/6003 --txaddr 10.65.39.191/8002 --cc rate 5000 --ack auto --grttprobing active --debug 4
# receiver/Debian10
#--id 172042016 --recv "~/recvFiles" --addr 224.1.2.4/6003 --interface ens33 --unicast_nack --debug 4
import sys
import os
if os.name =='nt':
os.add_dll_directory(os.path.dirname(__file__))
import argparse
import traceback
import logging
from typing import Optional
import enum
class CCEnum(enum.Enum):
CC = "cc"
CCE = "cce"
CCL = "ccl"
Fixed = "rate"
class FlushEnum(enum.Enum):
none='none'
passive='passive'
active='active'
class GrttEnum(enum.Enum):
none='none'
passive='passive'
active='active'
class EnumAction(argparse.Action):
"""
Argparse action for handling Enums
from https://stackoverflow.com/questions/43968006/support-for-enum-arguments-in-argparse
"""
def __init__(self, **kwargs):
# Pop off the type value
enum_type = kwargs.pop("type", None)
# Ensure an Enum subclass is provided
if enum_type is None:
raise ValueError("type must be assigned an Enum when using EnumAction")
if not issubclass(enum_type, enum.Enum):
raise TypeError("type must be an Enum when using EnumAction")
# Generate choices from the Enum
#kwargs.setdefault("choices", tuple(e.value for e in enum_type)) #if e!=CCEnum.rate else "rate <bitsPerSecond>"
super(EnumAction, self).__init__(**kwargs)
self._enum = enum_type
def __call__(self, parser, namespace, values:list|str, option_string=None):
# Convert value back into an Enum
if isinstance(values,list):
if len(values) ==2:
value = int(values[1])
elif len(values) ==1:
value = self._enum( values[0].lower())
else:
value = self._enum(values.lower() )
setattr(namespace, self.dest, value)
'''
norm.cpp:
fprintf(stderr, "Usage: normCast {send <file/dir list> &| recv <rxCacheDir>} [silent {on|off}]\n"
" [repeat <interval> [updatesOnly]] [id <nodeIdInteger>]\n"
" [addr <addr>[/<port>]][txaddr <addr>[/<port>]][txport <port>]\n"
" [interface <name>][reuse][loopback]\n"
" [ack auto|<node1>[,<node2>,...]] [segment <bytes>]\n"
" [block <count>] [parity <count>] [auto <count>]\n"
" [cc|cce|ccl|rate <bitsPerSecond>] [rxloss <lossFraction>]\n"
" [txloss <lossFraction>] [flush {none|passive|active}]\n"
" [grttprobing {none|passive|active}] [grtt <secs>]\n"
" [ptos <value>] [processor <processorCmdLine>] [saveaborts]\n"
" [sentprocessor <processorCmdLine>]\n"
" [purgeprocessor <processorCmdLine>] [buffer <bytes>]\n"
" [txsockbuffer <bytes>] [rxsockbuffer <bytes>]\n"
" [debug <level>] [trace] [log <logfile>]\n");
'''
# Instantiate the parser
def get_arg_options():
parser = argparse.ArgumentParser(description='Optional app description')
## Required positional argument
parser.add_argument('--id', type=int, #required=True,
help='id <nodeIdInteger>')
parser.add_argument('--send', '-S',nargs='+',
help='send <file/dir list>')
parser.add_argument('--recv', '-R',
help='recv <rxCacheDir>')
parser.add_argument('--repeat','-r', type=int, default=60,
help='[repeat <interval> [updatesOnly]]')
parser.add_argument('--interface', '-i',
help='addr <addr>[/<port>]')
parser.add_argument('--addr', '-a',
help='addr <addr>[/<port>]')
parser.add_argument('--txaddr', '-t',
help='txaddr <addr>[/<port>]')
parser.add_argument('--txport', type=int,
help='txport <port>')
parser.add_argument('--ack', nargs='+', #nargs='?',
help='ack auto|<node1>[,<node2>,...')
#To create an option that needs no value, set the action [docs] of it to 'store_const', 'store_true' or 'store_false'
parser.add_argument('--loopback', action='store_true',help='')
parser.add_argument('--segment', type=int,
help='segment <bytes>')
parser.add_argument('--block', type=int,
help='[block <count>] ')
parser.add_argument('--parity', type=int,
help='[parity <count>]')
parser.add_argument('--auto', type=int,
help='[auto <count>]')
parser.add_argument('--grttprobing', type=GrttEnum, action=EnumAction,
help='[grttprobing {none|passive|active}]')
parser.add_argument('--grtt', type=int, help='[grtt <secs>]')
parser.add_argument('--ptos', type=int,
help='[ptos <value>]')
parser.add_argument('--flush', type=FlushEnum, action=EnumAction,
help='[flush {none|passive|active}]')
parser.add_argument('--silent', action='store_true',help='')
parser.add_argument('--unicast_nack', action='store_true', help='')
parser.add_argument('--txloss', type=float, help='[txloss <lossFraction>]')
parser.add_argument('--rxloss', type=float, help='[rxloss <lossFraction>]')
parser.add_argument('--buffer', type=int, help='[buffer <bytes>]')
parser.add_argument('--txsockbuffer', type=int, help=' [txsockbuffer <bytes>]')
parser.add_argument('--rxsockbuffer', type=int, help='[rxsockbuffer <bytes>]')
parser.add_argument('--debug', type=int, help='[debug <level>]')
parser.add_argument('--cc',type=CCEnum, nargs='+', action=EnumAction,
help='[cc|cce|ccl|rate <bitsPerSecond>] '
)
args = parser.parse_args()
return args
import pynorm
def create_session(instance:pynorm.Instance, opts:argparse.Namespace):
'''
use opts to create a NORM session
'''
sessionAddr ='224.1.2.3'
sessionPort = 6003
if opts.addr:
args = opts.addr.split('/')
if len(args) >0:
sessionAddr = args[0]
if len(args) ==2:
sessionPort = int(args[1])
session = instance.createSession(sessionAddr, sessionPort, localId=opts.id)
sessionTxAddr:str = None
sessionTxPort = 8002
if opts.txaddr:
args = opts.txaddr.split('/')
if len(args) >0:
sessionTxAddr = args[0]
if len(args) ==2:
sessionTxPort = int(args[1])
if sessionTxAddr:
session.setTxPort(txPort=sessionTxPort, txBindAddr=sessionTxAddr)
if opts.interface:
session.setMulticastInterface(opts.interface)
if opts.loopback:
session.setLoopback(loopbackEnable=True)
if opts.silent:
session.setSilentReceiver(True)
if opts.unicast_nack:
session.setDefaultUnicastNack(enable=True)
autoAck:bool = False
ackingNodeList:list[int] =[]
if opts.ack:
if 'auto' == opts.ack[0]:
autoAck = True
session.setAutoAckingNodes(pynorm.TrackingStatus.RECEIVERS)
else:
ackingNodeList = [ int(i) for i in opts.ack]
for ackingNondeID in ackingNodeList:
session.addAckingNode(ackingNondeID)
if opts.txloss and opts.txloss>0:
session.setTxLoss(opts.txloss)
if opts.rxloss and opts.rxloss>0:
session.setRxLoss(opts.rxloss)
# Congestion Control
if opts.cc:
if isinstance(opts.cc,int):
session.setTxRate(opts.cc)
session.setEcnSupport(ecnEnable=False)
else:
if opts.cc == CCEnum.CC: # default TCP-friendly congestion control
session.setEcnSupport(ecnEnable=False)
elif opts.cc == CCEnum.CCE: #"wireless-ready" ECN-only congestion control
session.setEcnSupport(ecnEnable=True, ignoreLoss=True)
elif opts.cc == CCEnum.CCL: # "loss tolerant", non-ECN congestion control
session.setEcnSupport(ecnEnable=False, ignoreLoss=False, tolerateLoss=True)
defaultBufferSpace = 64*1024*1024
defaultTxSocketBufferSize = 4*1024*1024
defaultRxSocketBufferSize = 6*1024*1024
bufferSpace = opts.buffer if opts.buffer else defaultBufferSpace
if opts.send:
if opts.ack:
session.setFlowControl(flowControlFactor=0) #// ack-based flow control enabled on command-line, so disable timer-based flow control
session.setBackoffFactor(0)
#FEC
session.startSender(sessionId=opts.id,
bufferSpace=bufferSpace,
segmentSize=opts.segment if opts.segment else 1400,
blockSize=opts.block if opts.block else 64,
numParity=opts.parity if opts.parity else 0,
fecId=0)
if opts.auto:
session.setAutoParity(opts.auto)
session.setTxSocketBuffer(opts.txsockbuffer if opts.txsockbuffer else defaultTxSocketBufferSize )
if opts.recv:
session.startReceiver(bufferSpace=bufferSpace)
session.setRxSocketBuffer(opts.rxsockbuffer if opts.rxsockbuffer else defaultRxSocketBufferSize )
if opts.grttprobing:
session.setGrttProbingMode(pynorm.ProbingMode[ opts.grttprobing.value.upper() ] )
return session
from pynorm import EventType
from typing import Iterable
def listFiles( dirFileList:list[str]) -> Iterable[str]:
for dirFile in dirFileList:
if os.path.isfile(dirFile):
yield dirFile
elif os.path.isdir(dirFile):
for filename in os.scandir(dirFile):
if filename.is_file():
yield filename.path
else:
raise FileExistsError(f"{dirFile} is not a valid file/dir")
import logging
import ipaddress
class NormCaster():
def __init__(self,instance:pynorm.Instance, opts:argparse.Namespace ):
self.opts:argparse.Namespace = opts
self.recvDir:Optional[str] = os.path.expanduser(opts.recv) if opts.recv else None
self.instance:pynorm.Instance = instance
self.session:pynorm.Session = create_session(instance, opts)
self.fileIterator:Iterable[str] = listFiles(opts.send)
self.is_running:bool = True
self.pendingSendFilePath:Optional[str] = None #
def addOneFile(self,session):
'''
'''
if self.pendingSendFilePath is None:
try:
self.pendingSendFilePath:str = self.fileIterator.__next__()
except StopIteration:
self.is_running = False
return
obj:Optional[pynorm.Object] = session.fileEnqueue(self.pendingSendFilePath, info= self.pendingSendFilePath.replace("\\","/").encode() )
if obj:
if self.opts.ack:
session.setWatermark(obj,True)
logging.info(f"add file:{self.pendingSendFilePath} {obj._object}")
self.pendingSendFilePath = None # succeed enqued
else:
logging.warning(f"fileEnqueue: {sendFilePath} failure!")
def handle_norm_event(self, event:pynorm.Event):
session:pynorm.Session = event.session
evtType:pynorm.EventType = event.type
logging.info(evtType )
if evtType in (EventType.TX_QUEUE_EMPTY, EventType.TX_QUEUE_VACANCY):
self.addOneFile(session)
elif evtType == EventType.GRTT_UPDATED:
pass
elif evtType == EventType.TX_WATERMARK_COMPLETED:
if pynorm.AckingStatus.SUCCESS == session.getAckingStatus():
logging.warning( "normCast: NORM_TX_WATERMARK_COMPLETED, NORM_ACK_SUCCESS");
self.addOneFile(session)
else:
logging.warning( "normCast: NORM_TX_WATERMARK_COMPLETED, _NOT_ NORM_ACK_SUCCESS");
obj = event.object
if obj is None:
session.resetWatermark()
elif evtType == EventType.TX_FLUSH_COMPLETED:
pass
elif evtType == EventType.TX_OBJECT_PURGED:
obj = event.object
if obj and obj.type== pynorm.ObjectType.FILE:
logging.info(f"normCast: send file purged: {obj.info.decode()}")
elif evtType == EventType.TX_OBJECT_SENT:
obj = event.object
if obj and obj.type== pynorm.ObjectType.FILE:
logging.info(f"initial send complete: {obj.info.decode()}")
self.addOneFile(session)
elif evtType == EventType.ACKING_NODE_NEW:
sender = event.sender
logging.info(f"normCast: new acking node: {sender.id} IP address{sender.address}")
elif evtType == EventType.REMOTE_SENDER_INACTIVE:
pass
elif evtType == EventType.RX_OBJECT_ABORTED:
obj = event.object
if obj and obj.type is not pynorm.ObjectType.FILE:
logging.error("normCast: received invalid object type?!")
return
filePath = event.object.filename
event.object.cancel()
#remove temparary file if recv aborted.
os.remove(filePath)
logging.info("")
elif evtType == EventType.RX_OBJECT_INFO:
pass
elif evtType == EventType.RX_OBJECT_COMPLETED:
obj = event.object
if obj and obj.type is not pynorm.ObjectType.FILE:
logging.error("normCast: received invalid object type?!")
return
file_path = event.object.info.decode()
fileName = os.path.split(file_path)[-1]
path = os.path.join( self.recvDir, fileName )
oldPath = event.object.filename
logging.debug (f"{oldPath=}")
try:
if os.path.isfile(path):
os.remove(path)
os.rename(src=oldPath, dst=path)
except Exception as e:
logging.error ( traceback.format_exc() )
def getAckNodesStatus(self,session):
isSuccess, nodeID, nodeAckStatus = session.getNextAckingNode()
while isSuccess:
ip=ipaddress.IPv4Address(nodeID)
if pynorm.AckingStatus.SUCCESS == nodeAckStatus:
logging.info( f"normCast: node {nodeID} (IP address: {str(ip)}) acnkowledged.")
else:
logging.info( f"normCast: node {nodeID} (IP address: {str(ip)}) failed to acnkowledge.")
isSuccess, nodeID, nodeAckStatus = session.getNextAckingNode()
def run(self):
while self.is_running:
event: Optional[pynorm.Event,bool,None] = self.instance.getNextEvent(timeout=self.opts.repeat)
while event:
self.handle_norm_event(event)
event = self.instance.getNextEvent(timeout=self.opts.repeat)
if event is None and self.opts.send:
self.addOneFile(self.session) # means timeout
logging.warning("normCast exits!")
def main(argv):
opts:argparse.Namespace = get_arg_options()
instance = pynorm.Instance()
if opts.debug:
instance.setDebugLevel(level=pynorm.DebugLevel(opts.debug) )
if opts.recv:
recvPath:str = os.path.expanduser( opts.recv )
if not os.path.isdir(recvPath): #create is not exists!
os.makedirs( recvPath )
instance.setCacheDirectory( recvPath )
normCast:NormCaster = NormCaster(instance, opts)
if opts.send:
normCast.addOneFile(normCast.session)
normCast.run()
if __name__ == '__main__':
logging.getLogger().setLevel(logging.INFO)
sys.exit(main(sys.argv))

View File

@ -14,20 +14,19 @@ class InputThread(Thread):
def __init__(self, parent, *args, **kwargs):
super(InputThread, self).__init__(*args, **kwargs)
#self.setDaemon(True) ;# this is "child" daemon thread (setDaemon is deprecated)
self.daemon = True
self.msgr = parent
def run(self):
while True:
try:
msgHdr = bytearray(sys.stdin.read(MSG_HDR_SIZE))
msgHdr = sys.stdin.buffer.read(MSG_HDR_SIZE)
except:
sys.stderr.write("normMsgr: input thread end-of-file 1 ...\n")
return
try:
msgSize = 256*int(msgHdr[0]) + int(msgHdr[1])
msgBuffer = sys.stdin.read(msgSize - 2)
msgBuffer = sys.stdin.buffer.read(msgSize - 2)
except:
sys.stderr.write("normMsgr: input thread end-of-file 2 ...\n")
return
@ -38,7 +37,7 @@ class OutputThread(Thread):
def __init__(self, parent, *args, **kwargs):
super(OutputThread, self).__init__(*args, **kwargs)
self.setDaemon(True) ;# this is "child" daemon thread
self.daemon = True
self.msgr = parent
def run(self):
@ -48,11 +47,15 @@ class OutputThread(Thread):
msgHeader = bytearray(MSG_HDR_SIZE)
msgHeader[0] = (msgLen >> 8) & 0x00ff
msgHeader[1] = msgLen & 0x00ff
sys.stdout.write(msgHeader)
sys.stdout.write(msg)
try:
sys.stdout.buffer.write(msgHeader)
sys.stdout.buffer.write(msg)
sys.stdout.flush()
except:
sys.stderr.write("normMsgr: output thread exiting ...\n")
return
del msg
class NormMsgr:
"""This class keeps state for NORM tx/rx operations"""
@ -79,10 +82,12 @@ class NormMsgr:
# Create a NormSession and set some default parameters
self.normSession = self.normInstance.createSession(addr, port, nodeId)
self.normSession.setRxCacheLimit(2*self.norm_tx_queue_max) ;# we let the receiver track some extra objects
self.normSession.setDefaultSyncPolicy(pynorm.NORM_SYNC_ALL);
self.normSession.setDefaultSyncPolicy(pynorm.SyncPolicy.ALL);
self.normSession.setDefaultUnicastNack(True);
self.normSession.setTxCacheBounds(10*1024*1024, self.norm_tx_queue_max, self.norm_tx_queue_max);
self.normSession.setCongestionControl(True, True);
self.normSession.setRxPortReuse(True)
self.normSession.setMulticastLoopback(True)
return self.normSession
def addAckingNode(self, nodeId):
@ -204,7 +209,7 @@ class NormMsgr:
def onNormRxObjectCompleted(self, obj):
with self.normRxLock:
if pynorm.NORM_OBJECT_DATA == obj.getType():
if pynorm.ObjectType.DATA == obj.getType():
if 0 != len(self.output_msg_queue):
wasEmpty = False
else:
@ -250,12 +255,12 @@ class NormEventHandler(Thread):
return
if event is None:
break
if pynorm.NORM_EVENT_INVALID == event.type:
if pynorm.EventType.EVENT_INVALID == event.type:
continue
elif pynorm.NORM_TX_QUEUE_EMPTY == event.type or pynorm.NORM_TX_QUEUE_VACANCY == event.type:
elif pynorm.EventType.TX_QUEUE_EMPTY == event.type or pynorm.EventType.TX_QUEUE_VACANCY == event.type:
msgr.onNormTxQueueVacancy()
elif pynorm.NORM_TX_WATERMARK_COMPLETED == event.type:
if pynorm.NORM_ACK_SUCCESS == event.session.getAckingStatus():
elif pynorm.EventType.TX_WATERMARK_COMPLETED == event.type:
if pynorm.EventType.ACK_SUCCESS == event.session.getAckingStatus():
# All receivers acknowledged
msgr.onNormTxWatermarkCompleted()
else:
@ -263,12 +268,12 @@ class NormEventHandler(Thread):
# from our acking list. For now, we are infinitely
# persistent by resetting watermark ack request
event.session.resetWatermark()
elif pynorm.NORM_TX_OBJECT_PURGED == event.type:
elif pynorm.EventType.TX_OBJECT_PURGED == event.type:
msgr.onNormTxObjectPurged(event.object)
elif pynorm.NORM_RX_OBJECT_COMPLETED == event.type:
elif pynorm.EventType.RX_OBJECT_COMPLETED == event.type:
msgr.onNormRxObjectCompleted(event.object)
#else:
# sys.stderr.write("normMsgr: NormEventHandler warning: unhandled event: %s\n" % str(event))
#sys.stderr.write("normMsgr: NormEventHandler warning: unhandled event: %s\n" % pynorm.EventType(event.type))
sys.stderr.write("normMsgr: NormEventHandler thread exiting ...\n");
self.lock.release()
@ -395,4 +400,9 @@ except KeyboardInterrupt:
#sys.stderr.write("exception while waiting on input thread ..\n");
pass
# TBD - shut the NormEventHandler thread down in a graceful way
if send:
inputThread.join()
if recv:
outputThread.join()
sys.stderr.write("normMsgr: Done.\n")

View File

@ -6,9 +6,12 @@ By: Tom Wambold <wambold@itd.nrl.navy.mil>
from pynorm.instance import Instance
from pynorm.core import libnorm, NormError
from pynorm.constants import *
from pynorm.event import Event
from pynorm.session import Session
from pynorm.object import Object
def setDebugLevel(level):
libnorm.NormSetDebugLevel(level)
def setDebugLevel(level: DebugLevel):
libnorm.NormSetDebugLevel(level.value)
def getDebugLevel():
return libnorm.NormGetDebugLevel()
def getDebugLevel() -> DebugLevel:
return DebugLevel( libnorm.NormGetDebugLevel() )

View File

@ -7,75 +7,117 @@ from __future__ import absolute_import
import ctypes
import enum
from pynorm.core import libnorm
#enum ProtoDebugLevel {PL_FATAL, PL_ERROR, PL_WARN, PL_INFO, PL_DEBUG, PL_TRACE, PL_DETAIL, PL_MAX, PL_ALWAYS};
class DebugLevel(enum.IntEnum):
FATAL = 0
ERROR = 1
WARNNING = 2
INFO =3
DEBUG =4
TRACE =5
DETAIL=6
MAX =7
ALWAYS=8
# Constants
# enum NormObjectType
NORM_OBJECT_NONE = 0
NORM_OBJECT_DATA = 1
NORM_OBJECT_FILE = 2
NORM_OBJECT_STREAM = 3
class ObjectType(enum.Enum):
NONE = 0
DATA = 1
FILE = 2
STREAM = 3
# enum NormFlushMode
NORM_FLUSH_NONE = 0
NORM_FLUSH_PASSIVE = 1
NORM_FLUSH_ACTIVE = 2
class FlushMode(enum.Enum):
'''
NormStreamFlush
NormStreamSetAutoFlush
'''
NONE = 0
PASSIVE = 1
ACTIVE = 2
# enum NormNackingMode
NORM_NACK_NONE = 0
NORM_NACK_INFO_ONLY = 1
NORM_NACK_NORMAL = 2
class NackingMode(enum.Enum):
'''
only used for NormSetDefaultNackingMode
'''
NONE = 0
INFO_ONLY = 1
NORMAL = 2
# enum NormAckingStatus
NORM_ACK_INVALID = 0
NORM_ACK_FAILURE = 1
NORM_ACK_PENDING = 2
NORM_ACK_SUCCESS = 3
class AckingStatus(enum.Enum):
'''
the return value of NormGetAckingStatus
'''
INVALID = 0
FAILURE = 1
PENDING = 2
SUCCESS = 3
# enum NormProbingMode
NORM_PROBE_NONE = 0
NORM_PROBE_PASSIVE = 1
NORM_PROBE_ACTIVE = 2
class ProbingMode(enum.Enum):
'''
used in NormSetGrttProbingMode
'''
NONE = 0
PASSIVE = 1
ACTIVE = 2
# enum NormSyncPolicy
NORM_SYNC_CURRENT = 0
NORM_SYNC_STREAM = 1
NORM_SYNC_ALL = 2
# enum NormRepairBoundary
NORM_BOUNDARY_BLOCK = 0
NORM_BOUNDARY_OBJECT = 1
class SyncPolicy(enum.Enum):
'''
used in NormSetDefaultSyncPolicy
'''
CURRENT = 0
STREAM = 1
ALL = 2
class TrackingStatus(enum.Enum): #enum NormTrackingStatus
NONE = 0
RECEIVERS = 1
SENDERS = 2
ALL = 3
class RepairBoundary(enum.Enum):
BLOCK = 0
OBJECT = 1
# enum NormEventType
NORM_EVENT_INVALID = 0
NORM_TX_QUEUE_VACANCY = 1
NORM_TX_QUEUE_EMPTY = 2
NORM_TX_FLUSH_COMPLETED = 3
NORM_TX_WATERMARK_COMPLETED = 4
NORM_TX_CMD_SENT = 5
NORM_TX_OBJECT_SENT = 6
NORM_TX_OBJECT_PURGED = 7
NORM_TX_RATE_CHANGED = 8
NORM_LOCAL_SENDER_CLOSED = 9
NORM_REMOTE_SENDER_NEW = 10
NORM_REMOTE_SENDER_RESET = 11
NORM_REMOTE_SENDER_ADDRESS = 12
NORM_REMOTE_SENDER_ACTIVE = 13
NORM_REMOTE_SENDER_INACTIVE = 14
NORM_REMOTE_SENDER_PURGED = 15
NORM_RX_CMD_NEW = 16
NORM_RX_OBJECT_NEW = 17
NORM_RX_OBJECT_INFO = 18
NORM_RX_OBJECT_UPDATED = 19
NORM_RX_OBJECT_COMPLETED = 20
NORM_RX_OBJECT_ABORTED = 21
NORM_RX_ACK_REQUEST = 22
NORM_GRTT_UPDATED = 23
NORM_CC_ACTIVE = 24
NORM_CC_INACTIVE = 25
NORM_ACKING_NODE_NEW = 26
NORM_SEND_ERROR = 27
NORM_USER_TIMEOUT = 28
class EventType(enum.Enum):
EVENT_INVALID = 0
TX_QUEUE_VACANCY = 1
TX_QUEUE_EMPTY = 2
TX_FLUSH_COMPLETED = 3
TX_WATERMARK_COMPLETED = 4
TX_CMD_SENT = 5
TX_OBJECT_SENT = 6
TX_OBJECT_PURGED = 7
TX_RATE_CHANGED = 8
LOCAL_SENDER_CLOSED = 9
REMOTE_SENDER_NEW = 10
REMOTE_SENDER_RESET = 11
REMOTE_SENDER_ADDRESS = 12
REMOTE_SENDER_ACTIVE = 13
REMOTE_SENDER_INACTIVE = 14
REMOTE_SENDER_PURGED = 15
RX_CMD_NEW = 16
RX_OBJECT_NEW = 17
RX_OBJECT_INFO = 18
RX_OBJECT_UPDATED = 19
RX_OBJECT_COMPLETED = 20
RX_OBJECT_ABORTED = 21
RX_ACK_REQUEST = 22
GRTT_UPDATED = 23
CC_ACTIVE = 24
CC_INACTIVE = 25
ACKING_NODE_NEW = 26
SEND_ERROR = 27
USER_TIMEOUT = 28
NORM_INSTANCE_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_INSTANCE_INVALID").value
NORM_SESSION_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_SESSION_INVALID").value

View File

@ -169,6 +169,10 @@ def get_libnorm():
libnorm.NormSetTOS.argtypes = [ctypes.c_void_p, ctypes.c_uint8]
libnorm.NormSetTOS.errcheck = errcheck_bool
libnorm.NormSetMulticastLoopback.restype = ctypes.c_bool
libnorm.NormSetMulticastLoopback.argtypes = [ctypes.c_void_p, ctypes.c_byte]
libnorm.NormSetMulticastLoopback.errcheck = errcheck_bool
libnorm.NormSetLoopback.restype = ctypes.c_bool
libnorm.NormSetLoopback.argtypes = [ctypes.c_void_p, ctypes.c_byte]
libnorm.NormSetLoopback.errcheck = errcheck_bool
@ -321,6 +325,7 @@ def get_libnorm():
libnorm.NormStreamMarkEom.restype = None
libnorm.NormStreamMarkEom.argtypes = [ctypes.c_void_p]
libnorm.NormSetWatermark.restype = ctypes.c_bool
libnorm.NormSetWatermark.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_bool]
libnorm.NormSetWatermark.errcheck = errcheck_bool
@ -336,9 +341,16 @@ def get_libnorm():
libnorm.NormRemoveAckingNode.restype = None
libnorm.NormRemoveAckingNode.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
libnorm.NormSetAutoAckingNodes.restype = None
libnorm.NormSetAutoAckingNodes.argtypes = [ctypes.c_void_p, ctypes.c_int]
libnorm.NormGetAckingStatus.restype = ctypes.c_int
libnorm.NormGetAckingStatus.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
libnorm.NormGetNextAckingNode.restype = ctypes.c_bool
libnorm.NormGetNextAckingNode.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
libnorm.NormSendCommand.restype = ctypes.c_bool
libnorm.NormSendCommand.argtypes = [ctypes.c_void_p, ctypes.c_char_p,

View File

@ -13,17 +13,17 @@ from pynorm.node import Node
from pynorm.object import Object
class Event(object):
def __init__(self, eventType, session, sender, normObject):
self._type = eventType
self._session = session
self._sender = sender
self._object = normObject
def __init__(self, eventType:c.EventType, session:Session, sender, normObject):
self._type:c.EventType = c.EventType(eventType)
self._session:Session = session
self._sender:Node = sender
self._object:Object = normObject
# Properties
type = property(lambda self: self._type)
session = property(lambda self: self._session)
sender = property(lambda self: self._sender)
object = property(lambda self: self._object)
type:c.EventType = property(lambda self: self._type)
session:Session = property(lambda self: self._session)
sender:Node = property(lambda self: self._sender)
object:Object = property(lambda self: self._object)
## Private functions
@property
@ -41,63 +41,4 @@ class Event(object):
return cmp(self.type, type)
def __str__(self):
if self.type == c.NORM_EVENT_INVALID:
return "NORM_EVENT_INVALID"
elif self.type == c.NORM_TX_QUEUE_VACANCY:
return "NORM_TX_QUEUE_VACANCY"
elif self.type == c.NORM_TX_QUEUE_EMPTY:
return "NORM_TX_QUEUE_EMPTY"
elif self.type == c.NORM_TX_FLUSH_COMPLETED:
return "NORM_TX_FLUSH_COMPLETED"
elif self.type == c.NORM_TX_WATERMARK_COMPLETED:
return "NORM_TX_WATERMARK_COMPLETED"
elif self.type == c.NORM_TX_CMD_SENT:
return "NORM_TX_CMD_SENT"
elif self.type == c.NORM_TX_OBJECT_SENT:
return "NORM_TX_OBJECT_SENT"
elif self.type == c.NORM_TX_OBJECT_PURGED:
return "NORM_TX_OBJECT_PURGED"
elif self.type == c.NORM_TX_RATE_CHANGED:
return "NORM_TX_RATE_CHANGED"
elif self.type == c.NORM_LOCAL_SENDER_CLOSED:
return "NORM_LOCAL_SENDER_CLOSED"
elif self.type == c.NORM_REMOTE_SENDER_NEW:
return "NORM_REMOTE_SENDER_NEW"
elif self.type == c.NORM_REMOTE_SENDER_RESET:
return "NORM_REMOTE_SENDER_RESET"
elif self.type == c.NORM_REMOTE_SENDER_ADDRESS:
return "NORM_REMOTE_SENDER_ADDRESS"
elif self.type == c.NORM_REMOTE_SENDER_ACTIVE:
return "NORM_REMOTE_SENDER_ACTIVE"
elif self.type == c.NORM_REMOTE_SENDER_INACTIVE:
return "NORM_REMOTE_SENDER_INACTIVE"
elif self.type == c.NORM_REMOTE_SENDER_PURGED:
return "NORM_REMOTE_SENDER_PURGED"
elif self.type == c.NORM_RX_CMD_NEW:
return "NORM_RX_CMD_NEW"
elif self.type == c.NORM_RX_OBJECT_NEW:
return "NORM_RX_OBJECT_NEW"
elif self.type == c.NORM_RX_OBJECT_INFO:
return "NORM_RX_OBJECT_INFO"
elif self.type == c.NORM_RX_OBJECT_UPDATED:
return "NORM_RX_OBJECT_UPDATED"
elif self.type == c.NORM_RX_OBJECT_COMPLETED:
return "NORM_RX_OBJECT_COMPLETED"
elif self.type == c.NORM_RX_OBJECT_ABORTED:
return "NORM_RX_OBJECT_ABORTED"
elif self.type == c.NORM_RX_ACK_REQUEST:
return "NORM_RX_ACK_REQUEST"
elif self.type == c.NORM_GRTT_UPDATED:
return "NORM_GRTT_UPDATED"
elif self.type == c.NORM_CC_ACTIVE:
return "NORM_CC_ACTIVE"
elif self.type == c.NORM_CC_INACTIVE:
return "NORM_CC_INACTIVE"
elif self.type == c.NORM_ACKING_NODE_NEW:
return "NORM_ACKING_NODE_NEW"
elif self.type == c.NORM_SEND_ERROR:
return "NORM_SEND_ERROR"
elif self.type == c.NORM_USER_TIMEOUT:
return "NORM_USER_TIMEOUT"
else:
return "Unknown event type"
return self._type.name

View File

@ -47,60 +47,63 @@ class Instance(object):
def restart(self):
libnorm.NormRestartInstance(self)
def setCacheDirectory(self, path):
libnorm.NormSetCacheDirectory(self, path.encode('utf-8'))
def setDebugLevel(self, level):
libnorm.NormSetDebugLevel(level)
def setCacheDirectory(self, path:str):
libnorm.NormSetCacheDirectory(self, path.encode())
def openDebugLog(self, path):
libnorm.NormOpenDebugLog(self, path.encode('utf-8'))
def setDebugLevel(self, level:c.DebugLevel):
libnorm.NormSetDebugLevel(int(level))
def openDebugLog(self, path:str):
libnorm.NormOpenDebugLog(self, path.encode())
def closeDebugLog(self):
libnorm.NormCloseDebugLog(self)
def openDebugPipe(self, pipeName):
libnorm.NormOpenDebugPipe(self, pipeName.encode('utf-8'))
def openDebugPipe(self, pipeName:str):
libnorm.NormOpenDebugPipe(self, pipeName.encode())
def getNextEvent(self, timeout=None):
# Use python's select because letting the C NormGetNextEvent block
# seems to stop signals (CTRL+C) from killing the process
if not self._select(timeout):
return None
result = libnorm.NormGetNextEvent(self, ctypes.byref(self._estruct), True)
if not result:
sys.stderr.write("NormInstance.getNextEvent() warning: no more NORM events\n")
return False
# Note a NORM_EVENT_INVALID can be OK (with NORM_SESSION_INVALID)
if self._estruct.type == c.NORM_EVENT_INVALID:
return Event(c.NORM_EVENT_INVALID, None, None, None)
if self._estruct.type == c.EventType.EVENT_INVALID:
return Event(c.EventType.EVENT_INVALID, None, None, None)
if self._estruct.session == c.NORM_SESSION_INVALID:
raise NormError("No new event")
try:
sender = self._senders[self._estruct.sender]
except KeyError:
sender = self._senders[self._estruct.sender] = Node(self._estruct.sender)
try:
obj = self._objects[self._estruct.object]
except KeyError:
obj = self._objects[self._estruct.object] = Object(self._estruct.object)
if self._estruct.sender:
try:
sender = self._senders[self._estruct.sender]
except KeyError:
sender = self._senders[self._estruct.sender] = Node(self._estruct.sender)
else:
sender = None
if self._estruct.object:
try:
obj = self._objects[self._estruct.object]
except KeyError:
obj = self._objects[self._estruct.object] = Object(self._estruct.object)
else:
obj = None
return Event(self._estruct.type, self._sessions[self._estruct.session], sender, obj)
def getDescriptor(self):
def getDescriptor(self) -> int:
return libnorm.NormGetDescriptor(self)
def fileno(self):
def fileno(self) -> int:
return libnorm.NormGetDescriptor(self)
def createSession(self, address, port, localId=None):
def createSession(self, address:str, port:int, localId:int=None):
if localId == None:
localId = c.NORM_NODE_ANY
session = Session(self, address, port, localId)
session:Session = Session(self, address, port, localId)
self._sessions[session._session] = session
return session
@ -118,7 +121,7 @@ class Instance(object):
else:
# Windows wants milliseconds...
timeout *= 1000
rv = win32event.WaitForSingleObject(self.getDescriptor(), timeout)
rv = win32event.WaitForSingleObject(self.getDescriptor(), int(timeout) )
return True if rv == win32event.WAIT_OBJECT_0 else False
def _select_everythingelse(self, timeout):
@ -143,6 +146,8 @@ class Instance(object):
return __next__()
def __cmp__(self, other):
def cmp(a, b):
return (a > b) - (a < b)
return cmp(self._as_parameter_, other._as_parameter)
def __hash__(self):

View File

@ -7,6 +7,8 @@ from __future__ import absolute_import
import ctypes
from typing import Tuple
import ipaddress
import pynorm.constants as c
from pynorm.core import libnorm, NormError
@ -18,13 +20,13 @@ class Node(object):
libnorm.NormNodeRetain(node)
self._node = node
def getId(self):
def getId(self) -> int:
id = libnorm.NormNodeGetId(self)
if id == c.NORM_NODE_ANY:
return None
return id
def getAddress(self):
def getAddress(self) -> (str,int):
try:
return self._address
except AttributeError:
@ -34,31 +36,35 @@ class Node(object):
if not libnorm.NormNodeGetAddress(self, buf, ctypes.byref(size),
ctypes.byref(port)):
raise NormError("Node getAddress failed")
self._address = (buf.value, port.value)
if len(buf.value)==4:
ip = ipaddress.IPv4Address(buf.value)
elif len(buf.value) == 16: #ipv6 is 128bit
ip = ipaddress.IPv6Address(buf.value)
self._address = (str(ip), port.value)
return self._address
def getCommand(self, buf):
def getCommand(self, buf:bytes) -> bool:
return libnorm.NormNodeGetCommand(self, buf, len(buf))
def getGrtt(self):
def getGrtt(self) -> float:
grtt = libnorm.NormNodeGetGrtt(self)
if grtt == -1.0:
raise NormError("getGrtt failed")
return grtt
def setUnicastNack(self, mode):
def setUnicastNack(self, mode:bool):
libnorm.NormNodeSetUnicastNack(self, mode)
def setNackingMode(self, mode):
libnorm.NormNodeSetNackingMode(self, mode)
def setNackingMode(self, mode:c.NackingMode):
libnorm.NormNodeSetNackingMode(self, mode.value)
def setRepairBoundary(self, boundary):
libnorm.NormNodeSetRepairBoundary(self, boundary)
def setRepairBoundary(self, boundary:c.RepairBoundary):
libnorm.NormNodeSetRepairBoundary(self, boundary.value)
## Properties
id = property(getId)
address = property(getAddress)
grtt = property(getGrtt)
id:int = property(getId)
address:Tuple[str,int] = property(getAddress)
grtt:float = property(getGrtt)
## Private Functions
def __del__(self):
@ -72,6 +78,8 @@ class Node(object):
return "Node - Id=%i" % self.id
def __cmp__(self, other):
def cmp(a, b):
return (a > b) - (a < b)
return cmp(self._as_parameter_, other._as_parameter_)
def __hash__(self):

View File

@ -4,23 +4,24 @@ By: Tom Wambold <wambold@itd.nrl.navy.mil>
"""
from __future__ import absolute_import
import ctypes
import locale
import pynorm.constants as c
from pynorm.core import libnorm, NormError
from pynorm.node import Node
class Object(object):
"""Represents a NORM object instance"""
locale_encoding:str = locale.getpreferredencoding()
## Public functions
def __init__(self, object):
libnorm.NormObjectRetain(object)
self._object = object
def __init__(self, object_id:int):
libnorm.NormObjectRetain(object_id)
self._object:int = object_id # type NormObjectHandle
def getType(self):
return libnorm.NormObjectGetType(self)
def getType(self) -> c.ObjectType:
value = libnorm.NormObjectGetType(self)
return c.ObjectType(value)
def hasObjectInfo(self):
return libnorm.NormObjectHasInfo(self)
@ -37,23 +38,25 @@ class Object(object):
raise NormError("No object info received yet.")
return buf.value
def getSize(self):
def getSize(self) -> int:
return libnorm.NormObjectGetSize(self)
def getBytesPending(self):
def getBytesPending(self) -> int:
return libnorm.NormObjectGetBytesPending(self)
def cancel(self):
def cancel(self) -> None:
libnorm.NormObjectCancel(self)
def getFileName(self):
# TBD - should we do something to get file name size first?
def getFileName(self) -> str:
buf = ctypes.create_string_buffer(256)
libnorm.NormFileGetName(self, buf, ctypes.sizeof(buf))
return buf.value
success = libnorm.NormFileGetName(self, buf, ctypes.sizeof(buf))
if success:
return buf.value.decode(Object.locale_encoding)
else:
return None
def renameFile(self, name):
libnorm.NormFileRename(self, name.encode('utf-8'))
def renameFile(self, name:str) -> bool:
return libnorm.NormFileRename(self, name.encode(Object.locale_encoding))
# Because 'ctypes.string_at()' makes a _copy_ of the data, we don't
# support the usual NORM accessData / detachData options. We use
@ -71,23 +74,23 @@ class Object(object):
def getSender(self):
return Node(libnorm.NormObjectGetSender(self))
def setNackingMode(self, mode):
libnorm.NormObjectSetNackingMode(self, mode)
def setNackingMode(self, mode:c.NackingMode):
libnorm.NormObjectSetNackingMode(self, mode.value)
## Stream sending functions
def streamClose(self, graceful=False):
libnorm.NormStreamClose(self, graceful)
def streamWrite(self, msg):
return libnorm.NormStreamWrite(self, msg.encode('utf-8'), len(msg))
def streamWrite(self, msg:bytes):
return libnorm.NormStreamWrite(self, msg, len(msg))
def streamFlush(self, eom=False, flushmode=c.NORM_FLUSH_PASSIVE):
libnorm.NormStreamFlush(self, eom, flushmode)
def streamFlush(self, eom=False, flushmode:c.FlushMode = c.FlushMode.PASSIVE):
libnorm.NormStreamFlush(self, eom, flushmode.value)
def streamSetAutoFlush(self, mode):
libnorm.NormStreamSetAutoFlush(self, mode)
def streamSetAutoFlush(self, flushMode:c.FlushMode):
libnorm.NormStreamSetAutoFlush(self, flushMode.value)
def streamPushEnable(self, push):
def streamPushEnable(self, push:bool):
libnorm.NormStreamSetPushEnable(self, push)
def streamHasVacancy(self):
@ -97,11 +100,11 @@ class Object(object):
libnorm.NormStreamMarkEom(self)
## Stream receiving functions
def streamRead(self, size):
def streamRead(self, size) -> (int,bytes):
buf = ctypes.create_string_buffer(size)
numBytes = ctypes.c_uint(ctypes.sizeof(buf))
libnorm.NormStreamRead(self, buf, ctypes.byref(numBytes))
return (numBytes, buf.value)
return (numBytes.value, buf)
def streamSeekMsgStart(self):
return libnorm.NormStreamSeekMsgStart(self)
@ -110,32 +113,34 @@ class Object(object):
return libnorm.NormStreamGetReadOffset(self)
## Properties
type = property(getType)
info = property(getInfo)
size = property(getSize)
bytesPending = property(getBytesPending)
filename = property(getFileName, renameFile)
sender = property(getSender)
type:c.ObjectType = property(getType)
info:bytes = property(getInfo)
size:int = property(getSize)
bytesPending:int = property(getBytesPending)
filename:bytes = property(getFileName, renameFile)
sender:Node = property(getSender)
handle:int = property( lambda self:self._object )
## Private functions
def __copy__(self):
return Object(self._object)
def __deepcopy__(self, memo):
return Object(self._object)
def __del__(self):
libnorm.NormObjectRelease(self._object)
libnorm.NormObjectRelease(self)
@property
def _as_parameter_(self):
return self._object
def __str__(self):
if self.type == c.NORM_OBJECT_DATA:
return "NORM_OBJECT_DATA"
elif self.type == c.NORM_OBJECT_FILE:
return "NORM_OBJECT_FILE"
elif self.type == c.NORM_OBJECT_STREAM:
return "NORM_OBJECT_STREAM"
elif self.type == c.NORM_OBJECT_NONE:
return "NORM_OBJECT_NONE"
return self.type.name
def __cmp__(self, other):
def cmp(a, b):
return (a > b) - (a < b)
return cmp(self._as_parameter_, other._as_parameter_)
def __hash__(self):

View File

@ -6,16 +6,18 @@ By: Tom Wambold <wambold@itd.nrl.navy.mil>
from __future__ import absolute_import
import ctypes
import locale
from typing import Optional
import pynorm.constants as c
from pynorm.core import libnorm, NormError
from pynorm.object import Object
class Session(object):
"""This represents a session tied to a particular NORM instance"""
locale_encoding:str = locale.getpreferredencoding()
## Public functions
def __init__(self, instance, address, port, localId=c.NORM_NODE_ANY):
def __init__(self, instance, address:str, port:int, localId=c.NORM_NODE_ANY):
"""
instance - An instance of NormInstance
address - String of multicast address to join
@ -23,7 +25,7 @@ class Session(object):
localId - NormNodeId
"""
self._instance = instance
self._session = libnorm.NormCreateSession(instance, address.encode('utf-8'), port, localId)
self._session:int = libnorm.NormCreateSession(instance, address.encode('utf-8'), port, localId)
self.sendGracefulStop = False
self.gracePeriod = 0
@ -31,195 +33,253 @@ class Session(object):
libnorm.NormDestroySession(self)
del self._instance._sessions[self]
def setUserData(self, data):
def setUserData(self, data:str):
"""data should be a string"""
libnorm.NormSetUserData(self, data.encode('utf-8'))
def getUserData(self):
def getUserData(self)->str:
data = libnorm.NormGetUserData(self)
return data.decode('utf-8') if data else None
def getNodeId(self):
return libnorm.NormGetLocalNodeId(self)
def setTxPort(self, txPort, enableReuse=False, txBindAddr=None):
libnorm.NormSetTxPort(self, txPort, enableReuse, txBindAddr)
def setTxPort(self, txPort:int, enableReuse:bool=False, txBindAddr:Optional[str]=None):
libnorm.NormSetTxPort(self, txPort, enableReuse,
txBindAddr.encode('utf-8') if txBindAddr else None )
def setRxPortReuse(self, enable, rxBindAddr=None, senderAddr=None, senderPort=0):
libnorm.NormSetRxPortReuse(self, enable, rxBindAddr.encode('utf-8'), senderAddr.encode('utf-8'), senderPort)
def setTxOnly(self, txOnly:bool=False, connectToSessionAddress:bool=False):
libnorm.NormSetTxOnly(self, txOnly, connectToSessionAddress)
def setMulticastInterface(self, iface):
def setRxPortReuse(self, enable:bool, rxBindAddr:Optional[str]=None, senderAddr:Optional[str]=None, senderPort:int=0):
'''
This function allows the user to control the port reuse and binding behavior for the receive socket used for the given NORM sessionHandle.
When the enablReuse parameter is set to true, reuse of the NormSession port number by multiple NORM instances or sessions is enabled.
'''
libnorm.NormSetRxPortReuse(self, enable,
rxBindAddr.encode('utf-8') if rxBindAddr else None,
senderAddr.encode('utf-8') if senderAddr else None,
senderPort)
def setMulticastInterface(self, iface:str):
libnorm.NormSetMulticastInterface(self, iface.encode('utf-8'))
def setSSM(self, srcAddr):
def setSSM(self, srcAddr:str):
libnorm.NormSetSSM(self, srcAddr.encode('utf-8'))
def setTTL(self, ttl):
libnorm.NormSetTTL(self, ttl)
def setTTL(self, ttl) -> bool:
return libnorm.NormSetTTL(self, ttl)
def setTOS(self, tos):
libnorm.NormSetTOS(self, tos)
def setTOS(self, tos) -> bool:
return libnorm.NormSetTOS(self, tos)
def setLoopback(self, loop):
libnorm.NormSetLoopback(self, loop)
def setLoopback(self, loopbackEnable:bool=False):
libnorm.NormSetLoopback(self, loopbackEnable)
def setMulticastLoopback(self, enable:bool):
libnorm.NormSetMulticastLoopback(self, enable)
def setTxLoss(self, percent:float):
libnorm.NormSetTxLoss(percent)
def setRxLoss(self, percent:float):
libnorm.NormSetRxLoss(percent)
def getReportInterval(self):
def getReportInterval(self) -> int:
return libnorm.NormGetReportInterval(self)
def setReportInterval(self, interval):
def setReportInterval(self, interval:int):
libnorm.NormSetReportInterval(self, interval)
## Sender functions
def startSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId=0):
libnorm.NormStartSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId)
def startSender(self, sessionId:int, bufferSpace:int, segmentSize:int, blockSize:int, numParity:int, fecId=0) -> bool:
return libnorm.NormStartSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId)
def stopSender(self):
libnorm.NormStopSender(self)
def setTxRate(self, rate):
libnorm.NormSetTxRate(self, rate)
def setTxRate(self, txRate:float):
libnorm.NormSetTxRate(self, txRate)
def setTxSocketBuffer(self, size):
def getTxRate(self) -> float:
return libnorm.NormGetTxRate(self)
def setTxSocketBuffer(self, size:int):
libnorm.NormSetTxSocketBuffer(self, size)
def setCongestionControl(self, ccEnable, adjustRate=True):
def setCongestionControl(self, ccEnable:bool, adjustRate:bool=True):
'''
must called before startSender
'''
libnorm.NormSetCongestionControl(self, ccEnable, adjustRate)
def setEcnSupport(self, ecnEnable, ignoreLoss=False, tolerateLoss=False):
libnorm.NormSetEcnSupport(self, ecnEnable, ignoreLoss, tolerateLoss)
def setFlowControl(self, flowControlFactor):
libnorm.NormSetFlowControl(self, flowControlFactor)
def setTxRateBounds(self, rateMin, rateMax):
def setTxRateBounds(self, rateMin, rateMax) -> bool:
'''
If both rateMin and rateMax are greater than or equal to zero,
but (rateMax < rateMin), the rate bounds will remain unset or unchanged and the function will return false.
'''
if rateMin > rateMax:
return False
libnorm.NormSetTxRateBounds(self, rateMin, rateMax)
return True
def setTxCacheBounds(self, sizeMax, countMin, countMax):
def setTxCacheBounds(self, sizeMax:int, countMin:int, countMax:int):
libnorm.NormSetTxCacheBounds(self, sizeMax, countMin, countMax)
def setAutoParity(self, parity):
def setAutoParity(self, parity:int):
libnorm.NormSetAutoParity(self, parity)
def getGrttEstimate(self):
def getGrttEstimate(self) -> float:
return libnorm.NormGetGrttEstimate(self)
def setGrttEstimate(self, grttMax):
libnorm.NormSetGrttEstimate(self, grtt)
def setGrttEstimate(self, grtt:float):
return libnorm.NormSetGrttEstimate(self, grtt)
def setGrttMax(self, max):
libnorm.NormSetGrttMax(self, grttMax)
def setGrttMax(self, grttMax:float):
return libnorm.NormSetGrttMax(self, grttMax)
def setGrttProbingMode(self, mode):
libnorm.NormSetGrttProbingMode(self, mode)
def setGrttProbingMode(self, probingMode:c.ProbingMode):
libnorm.NormSetGrttProbingMode(self, probingMode.value)
def setGrttProbingInterval(self, intervalMin, intervalMax):
def setGrttProbingInterval(self, intervalMin:int, intervalMax:int):
libnorm.NormSetGrttProbingInterval(self, intervalMin, intervalMax)
def setBackoffFactor(self, factor):
def setBackoffFactor(self, factor:int):
libnorm.NormSetBackoffFactor(self, factor)
def setGroupSize(self, size):
def setGroupSize(self, size:int):
libnorm.NormSetGroupSize(self, size)
def fileEnqueue(self, filename, info=""):
def setTxRobustFactor(self, robustFactor:int):
libnorm.NormSetTxRobustFactor(self, robustFactor)
def fileEnqueue(self, filename:str, info:bytes=b""):
# TBD - allow for case of info being None?
result = libnorm.NormFileEnqueue(self, filename.encode('utf-8'), info.encode('utf-8'), len(info))
result = libnorm.NormFileEnqueue(self, filename.encode(self.locale_encoding), info, len(info))
if ctypes.c_void_p.in_dll(libnorm, "NORM_OBJECT_INVALID") == result:
return None; # enqueue not successful due to flow control or sender cache limit
else
else:
# Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification
obj = self._instance._objects[self._estruct.object] = Object(result)
obj = self._instance._objects[result] = Object(result)
return obj
def dataEnqueue(self, data, info=""):
def dataEnqueue(self, data:bytes, info:bytes=b""):
# TBD - allow for case of info being None?
result = libnorm.NormDataEnqueue(self, data.encode('utf-8'), len(data), info.encode('utf-8'), len(info))
result = libnorm.NormDataEnqueue(self, data, len(data), info, len(info))
if ctypes.c_void_p.in_dll(libnorm, "NORM_OBJECT_INVALID") == result:
return None; # enqueue not successful due to flow control or sender cache limit
else
else:
# Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification
obj = self._instance._objects[self._estruct.object] = Object(result)
obj = self._instance._objects[result] = Object(result)
return obj
def streamOpen(self, bufferSize, info=""):
def streamOpen(self, bufferSize:int, info=b""):
# TBD - allow for case of info being None?
result = libnorm.NormStreamOpen(self, bufferSize, info.encode('utf-8'), len(info))
result = libnorm.NormStreamOpen(self, bufferSize, info, len(info))
if ctypes.c_void_p.in_dll(libnorm, "NORM_OBJECT_INVALID") == result:
return None; # stream open/enqueue not successful due to flow control or sender cache limit
else
else:
# Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification
obj = self._instance._objects[self._estruct.object] = Object(result)
obj = self._instance._objects[result] = Object(result)
return obj
def requeueObject(self, normObject):
libnorm.NormRequeueObject(self, normObject)
def sendCommand(self, cmdBuffer, robust=False):
return libnorm.NormSendCommand(self, cmdBuffer.encode('utf-8'), len(cmdBuffer), robust)
def sendCommand(self, cmdBuffer:bytes, robust:bool=False) -> bool:
return libnorm.NormSendCommand(self, cmdBuffer, len(cmdBuffer), robust)
def cancelCommand(self):
libnorm.NormCancelCommand(self)
def setWatermark(self, normObject, overrideFlush=False):
libnorm.NormSetWatermark(self, normObject, overrideFlush)
def resetWatermark(self):
libnorm.NormResetWatermark(self)
def cancelWatermark(self):
def setWatermark(self, normObject:Object, overrideFlush=False) -> bool:
return libnorm.NormSetWatermark(self, normObject, overrideFlush)
def resetWatermark(self) -> bool:
return libnorm.NormResetWatermark(self)
def cancelWatermark(self) -> None:
libnorm.NormCancelWatermark(self)
def addAckingNode(self, nodeId):
libnorm.NormAddAckingNode(self, nodeId)
def addAckingNode(self, nodeId:int) -> bool:
return libnorm.NormAddAckingNode(self, nodeId)
def removeAckingNode(self, nodeId):
def removeAckingNode(self, nodeId:int) -> None:
libnorm.NormRemoveAckingNode(self, nodeId)
def setAutoAckingNodes(self, trackingStatus:c.TrackingStatus) -> None:
libnorm.NormSetAutoAckingNodes(self, trackingStatus.value)
def getNextAckingNode(self, nodeID:int=c.NORM_NODE_NONE) -> (bool, int,int):
'''
bool NormGetNextAckingNode(NormSessionHandle sessionHandle,
NormNodeId* nodeId,
NormAckingStatus* ackingStatus DEFAULT(0));
'''
ackingStatus = 0
nodeIdBytes = ctypes.c_uint(ctypes.sizeof(nodeId))
ackingStatusBytes = ctypes.c_uint(ctypes.sizeof(ackingStatus))
isSuccess = libnorm.NormGetNextAckingNode(self, ctypes.byref(nodeIdBytes),ctypes.byref(ackingStatusBytes) )
return (isSuccess, nodeIdBytes.value, c.AckingStatus(ackingStatusBytes.value) if isSuccess else c.AckingStatus.INVALID)
def getAckingStatus(self, nodeId=c.NORM_NODE_ANY):
return libnorm.NormGetAckingStatus(self, nodeId)
def getAckingStatus(self, nodeId:int=c.NORM_NODE_ANY) -> c.AckingStatus:
return c.AckingStatus( libnorm.NormGetAckingStatus(self, nodeId) )
## Receiver functions
def startReceiver(self, bufferSpace):
libnorm.NormStartReceiver(self, bufferSpace)
def startReceiver(self, bufferSpace:int) -> bool:
'''
The bufferSpace parameter is used to set a limit on the amount of bufferSpace allocated by the receiver per active NormSender within the session.
The appropriate bufferSpace to use is a function of expected network delay*bandwidth product and packet loss characteristics.
'''
return libnorm.NormStartReceiver(self, bufferSpace)
def stopReceiver(self):
def stopReceiver(self) -> None:
"""This will be called automatically if the receiver is active"""
libnorm.NormStopReceiver(self)
def setRxCacheLimit(self, count):
def setRxCacheLimit(self, count:int):
libnorm.NormSetRxCacheLimit(self, count)
def setRxSocketBuffer(self, size):
def setRxSocketBuffer(self, size:int):
libnorm.NormSetRxSocketBuffer(self, size)
def setSilentReceiver(self, silent, maxDelay=None):
def setSilentReceiver(self, silent:bool, maxDelay:Optional[int]=None):
if maxDelay == None:
maxDelay = -1
libnorm.NormSetSilentReceiver(self, silent, maxDelay)
def setDefaultUnicastNack(self, mode):
libnorm.NormSetDefaultUnicastNack(self, mode)
def setDefaultSyncPolicy(self, policy):
libnorm.NormSetDefaultSyncPolicy(self, policy)
def setDefaultUnicastNack(self, enable:bool):
libnorm.NormSetDefaultUnicastNack(self, enable)
def setDefaultNackingMode(self, mode):
libnorm.NormSetDefaultNackingMode(self, mode)
def setDefaultSyncPolicy(self, policy:c.SyncPolicy):
libnorm.NormSetDefaultSyncPolicy(self, policy.value)
def setDefaultRepairBoundary(self, boundary):
libnorm.NormSetDefaultRepairBoundary(self, boundary)
def setDefaultNackingMode(self, mode:c.NackingMode):
libnorm.NormSetDefaultNackingMode(self, mode.value)
def setDefaultRepairBoundary(self, boundary:c.RepairBoundary):
libnorm.NormSetDefaultRepairBoundary(self, boundary.value)
def setDefaultRxRobustFactor(self, rxRobustFactor:int):
libnorm.NormSetDefaultRxRobustFactor(rxRobustFactor)
def setMessageTrace(self, state):
libnorm.NormSetMessageTrace(self, state)
## Properties
nodeId = property(getNodeId)
grtt = property(getGrttEstimate, setGrttEstimate)
userData = property(getUserData, setUserData)
reportInterval = property(getReportInterval, setReportInterval)
nodeId:int = property(getNodeId)
grtt:float = property(getGrttEstimate, setGrttEstimate)
userData:str = property(getUserData, setUserData)
reportInterval:int = property(getReportInterval, setReportInterval)
## Private functions
def __del__(self):
@ -233,6 +293,8 @@ class Session(object):
return self._session
def __cmp__(self, other):
def cmp(a, b):
return (a > b) - (a < b)
try:
return cmp(self._as_parameter_, other._as_parameter)
except AttributeError: