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); NormSetSilentReceiver(norm_session, true);
// So that the listener can construct (unsent) ACKs without failure // 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); NormSetDefaultUnicastNack(norm_session, true);
// Note we use a _small_ buffer size here since a "listening" socket isn't // Note we use a _small_ buffer size here since a "listening" socket isn't

View File

@ -70,7 +70,10 @@ class NormStreamer
{ {
loopback = state; loopback = state;
if (NORM_SESSION_INVALID != norm_session) if (NORM_SESSION_INVALID != norm_session)
{
NormSetMulticastLoopback(norm_session, state); NormSetMulticastLoopback(norm_session, state);
NormSetLoopback(norm_session, state); // XXX test code
}
} }
void SetFtiInfo(bool state) void SetFtiInfo(bool state)
{ {
@ -567,8 +570,11 @@ bool NormStreamer::OpenNormSession(NormInstanceHandle instance, const char* addr
{ {
NormSetRxPortReuse(norm_session, true); NormSetRxPortReuse(norm_session, true);
if (loopback) if (loopback)
{
NormSetMulticastLoopback(norm_session, true); NormSetMulticastLoopback(norm_session, true);
}
} }
NormSetLoopback(norm_session, loopback);
// Set some default parameters (maybe we should put parameter setting in Start()) // Set some default parameters (maybe we should put parameter setting in Start())
NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM); NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM);
@ -889,11 +895,11 @@ unsigned int NormStreamer::WriteToStream(const char* buffer, unsigned int numByt
if (ack_ex) if (ack_ex)
{ {
const char* req = "Hello, acker"; 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 else
{ {
NormSetWatermark(norm_session, tx_stream); NormSetWatermark(norm_session, tx_stream);//, true);
} }
tx_watermark_pending = true; tx_watermark_pending = true;
@ -1178,7 +1184,6 @@ void NormStreamer::HandleNormEvent(const NormEvent& event)
break; break;
case NORM_TX_WATERMARK_COMPLETED: case NORM_TX_WATERMARK_COMPLETED:
TRACE("NORM_TX_WATERMARK_COMPLETED ...\n");
if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session)) if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session))
{ {
//fprintf(stderr, "WATERMARK COMPLETED\n"); //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 // TBD - should provide more error checking of NORM API calls
TRACE("creating instance ...\n");
NormInstanceHandle normInstance = NormCreateInstance(boostPriority); NormInstanceHandle normInstance = NormCreateInstance(boostPriority);
TRACE("restarting instance ...\n");
//NormRestartInstance(normInstance); // xxx
TRACE("instance restarted.\n");
NormSetDebugLevel(debugLevel); NormSetDebugLevel(debugLevel);
if ((NULL != logFile) && !NormOpenDebugLog(normInstance, logFile)) 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): def __init__(self, parent, *args, **kwargs):
super(InputThread, self).__init__(*args, **kwargs) super(InputThread, self).__init__(*args, **kwargs)
#self.setDaemon(True) ;# this is "child" daemon thread (setDaemon is deprecated)
self.daemon = True self.daemon = True
self.msgr = parent self.msgr = parent
def run(self): def run(self):
while True: while True:
try: try:
msgHdr = bytearray(sys.stdin.read(MSG_HDR_SIZE)) msgHdr = sys.stdin.buffer.read(MSG_HDR_SIZE)
except: except:
sys.stderr.write("normMsgr: input thread end-of-file 1 ...\n") sys.stderr.write("normMsgr: input thread end-of-file 1 ...\n")
return return
try: try:
msgSize = 256*int(msgHdr[0]) + int(msgHdr[1]) msgSize = 256*int(msgHdr[0]) + int(msgHdr[1])
msgBuffer = sys.stdin.read(msgSize - 2) msgBuffer = sys.stdin.buffer.read(msgSize - 2)
except: except:
sys.stderr.write("normMsgr: input thread end-of-file 2 ...\n") sys.stderr.write("normMsgr: input thread end-of-file 2 ...\n")
return return
@ -38,7 +37,7 @@ class OutputThread(Thread):
def __init__(self, parent, *args, **kwargs): def __init__(self, parent, *args, **kwargs):
super(OutputThread, self).__init__(*args, **kwargs) super(OutputThread, self).__init__(*args, **kwargs)
self.setDaemon(True) ;# this is "child" daemon thread self.daemon = True
self.msgr = parent self.msgr = parent
def run(self): def run(self):
@ -48,11 +47,15 @@ class OutputThread(Thread):
msgHeader = bytearray(MSG_HDR_SIZE) msgHeader = bytearray(MSG_HDR_SIZE)
msgHeader[0] = (msgLen >> 8) & 0x00ff msgHeader[0] = (msgLen >> 8) & 0x00ff
msgHeader[1] = msgLen & 0x00ff msgHeader[1] = msgLen & 0x00ff
sys.stdout.write(msgHeader) try:
sys.stdout.write(msg) 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 del msg
class NormMsgr: class NormMsgr:
"""This class keeps state for NORM tx/rx operations""" """This class keeps state for NORM tx/rx operations"""
@ -79,10 +82,12 @@ class NormMsgr:
# Create a NormSession and set some default parameters # Create a NormSession and set some default parameters
self.normSession = self.normInstance.createSession(addr, port, nodeId) 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.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.setDefaultUnicastNack(True);
self.normSession.setTxCacheBounds(10*1024*1024, self.norm_tx_queue_max, self.norm_tx_queue_max); self.normSession.setTxCacheBounds(10*1024*1024, self.norm_tx_queue_max, self.norm_tx_queue_max);
self.normSession.setCongestionControl(True, True); self.normSession.setCongestionControl(True, True);
self.normSession.setRxPortReuse(True)
self.normSession.setMulticastLoopback(True)
return self.normSession return self.normSession
def addAckingNode(self, nodeId): def addAckingNode(self, nodeId):
@ -204,7 +209,7 @@ class NormMsgr:
def onNormRxObjectCompleted(self, obj): def onNormRxObjectCompleted(self, obj):
with self.normRxLock: with self.normRxLock:
if pynorm.NORM_OBJECT_DATA == obj.getType(): if pynorm.ObjectType.DATA == obj.getType():
if 0 != len(self.output_msg_queue): if 0 != len(self.output_msg_queue):
wasEmpty = False wasEmpty = False
else: else:
@ -250,12 +255,12 @@ class NormEventHandler(Thread):
return return
if event is None: if event is None:
break break
if pynorm.NORM_EVENT_INVALID == event.type: if pynorm.EventType.EVENT_INVALID == event.type:
continue 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() msgr.onNormTxQueueVacancy()
elif pynorm.NORM_TX_WATERMARK_COMPLETED == event.type: elif pynorm.EventType.TX_WATERMARK_COMPLETED == event.type:
if pynorm.NORM_ACK_SUCCESS == event.session.getAckingStatus(): if pynorm.EventType.ACK_SUCCESS == event.session.getAckingStatus():
# All receivers acknowledged # All receivers acknowledged
msgr.onNormTxWatermarkCompleted() msgr.onNormTxWatermarkCompleted()
else: else:
@ -263,12 +268,12 @@ class NormEventHandler(Thread):
# from our acking list. For now, we are infinitely # from our acking list. For now, we are infinitely
# persistent by resetting watermark ack request # persistent by resetting watermark ack request
event.session.resetWatermark() event.session.resetWatermark()
elif pynorm.NORM_TX_OBJECT_PURGED == event.type: elif pynorm.EventType.TX_OBJECT_PURGED == event.type:
msgr.onNormTxObjectPurged(event.object) msgr.onNormTxObjectPurged(event.object)
elif pynorm.NORM_RX_OBJECT_COMPLETED == event.type: elif pynorm.EventType.RX_OBJECT_COMPLETED == event.type:
msgr.onNormRxObjectCompleted(event.object) msgr.onNormRxObjectCompleted(event.object)
#else: #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"); sys.stderr.write("normMsgr: NormEventHandler thread exiting ...\n");
self.lock.release() self.lock.release()
@ -395,4 +400,9 @@ except KeyboardInterrupt:
#sys.stderr.write("exception while waiting on input thread ..\n"); #sys.stderr.write("exception while waiting on input thread ..\n");
pass 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") 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.instance import Instance
from pynorm.core import libnorm, NormError from pynorm.core import libnorm, NormError
from pynorm.constants import * from pynorm.constants import *
from pynorm.event import Event
from pynorm.session import Session
from pynorm.object import Object
def setDebugLevel(level): def setDebugLevel(level: DebugLevel):
libnorm.NormSetDebugLevel(level) libnorm.NormSetDebugLevel(level.value)
def getDebugLevel(): def getDebugLevel() -> DebugLevel:
return libnorm.NormGetDebugLevel() return DebugLevel( libnorm.NormGetDebugLevel() )

View File

@ -7,75 +7,117 @@ from __future__ import absolute_import
import ctypes import ctypes
import enum
from pynorm.core import libnorm 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 # Constants
# enum NormObjectType # enum NormObjectType
NORM_OBJECT_NONE = 0 class ObjectType(enum.Enum):
NORM_OBJECT_DATA = 1 NONE = 0
NORM_OBJECT_FILE = 2 DATA = 1
NORM_OBJECT_STREAM = 3 FILE = 2
STREAM = 3
# enum NormFlushMode # enum NormFlushMode
NORM_FLUSH_NONE = 0 class FlushMode(enum.Enum):
NORM_FLUSH_PASSIVE = 1 '''
NORM_FLUSH_ACTIVE = 2 NormStreamFlush
NormStreamSetAutoFlush
'''
NONE = 0
PASSIVE = 1
ACTIVE = 2
# enum NormNackingMode # enum NormNackingMode
NORM_NACK_NONE = 0 class NackingMode(enum.Enum):
NORM_NACK_INFO_ONLY = 1 '''
NORM_NACK_NORMAL = 2 only used for NormSetDefaultNackingMode
'''
NONE = 0
INFO_ONLY = 1
NORMAL = 2
# enum NormAckingStatus # enum NormAckingStatus
NORM_ACK_INVALID = 0 class AckingStatus(enum.Enum):
NORM_ACK_FAILURE = 1 '''
NORM_ACK_PENDING = 2 the return value of NormGetAckingStatus
NORM_ACK_SUCCESS = 3 '''
INVALID = 0
FAILURE = 1
PENDING = 2
SUCCESS = 3
# enum NormProbingMode # enum NormProbingMode
NORM_PROBE_NONE = 0 class ProbingMode(enum.Enum):
NORM_PROBE_PASSIVE = 1 '''
NORM_PROBE_ACTIVE = 2 used in NormSetGrttProbingMode
'''
NONE = 0
PASSIVE = 1
ACTIVE = 2
# enum NormSyncPolicy # enum NormSyncPolicy
NORM_SYNC_CURRENT = 0
NORM_SYNC_STREAM = 1
NORM_SYNC_ALL = 2
# enum NormRepairBoundary class SyncPolicy(enum.Enum):
NORM_BOUNDARY_BLOCK = 0 '''
NORM_BOUNDARY_OBJECT = 1 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 # enum NormEventType
NORM_EVENT_INVALID = 0 class EventType(enum.Enum):
NORM_TX_QUEUE_VACANCY = 1 EVENT_INVALID = 0
NORM_TX_QUEUE_EMPTY = 2 TX_QUEUE_VACANCY = 1
NORM_TX_FLUSH_COMPLETED = 3 TX_QUEUE_EMPTY = 2
NORM_TX_WATERMARK_COMPLETED = 4 TX_FLUSH_COMPLETED = 3
NORM_TX_CMD_SENT = 5 TX_WATERMARK_COMPLETED = 4
NORM_TX_OBJECT_SENT = 6 TX_CMD_SENT = 5
NORM_TX_OBJECT_PURGED = 7 TX_OBJECT_SENT = 6
NORM_TX_RATE_CHANGED = 8 TX_OBJECT_PURGED = 7
NORM_LOCAL_SENDER_CLOSED = 9 TX_RATE_CHANGED = 8
NORM_REMOTE_SENDER_NEW = 10 LOCAL_SENDER_CLOSED = 9
NORM_REMOTE_SENDER_RESET = 11 REMOTE_SENDER_NEW = 10
NORM_REMOTE_SENDER_ADDRESS = 12 REMOTE_SENDER_RESET = 11
NORM_REMOTE_SENDER_ACTIVE = 13 REMOTE_SENDER_ADDRESS = 12
NORM_REMOTE_SENDER_INACTIVE = 14 REMOTE_SENDER_ACTIVE = 13
NORM_REMOTE_SENDER_PURGED = 15 REMOTE_SENDER_INACTIVE = 14
NORM_RX_CMD_NEW = 16 REMOTE_SENDER_PURGED = 15
NORM_RX_OBJECT_NEW = 17 RX_CMD_NEW = 16
NORM_RX_OBJECT_INFO = 18 RX_OBJECT_NEW = 17
NORM_RX_OBJECT_UPDATED = 19 RX_OBJECT_INFO = 18
NORM_RX_OBJECT_COMPLETED = 20 RX_OBJECT_UPDATED = 19
NORM_RX_OBJECT_ABORTED = 21 RX_OBJECT_COMPLETED = 20
NORM_RX_ACK_REQUEST = 22 RX_OBJECT_ABORTED = 21
NORM_GRTT_UPDATED = 23 RX_ACK_REQUEST = 22
NORM_CC_ACTIVE = 24 GRTT_UPDATED = 23
NORM_CC_INACTIVE = 25 CC_ACTIVE = 24
NORM_ACKING_NODE_NEW = 26 CC_INACTIVE = 25
NORM_SEND_ERROR = 27 ACKING_NODE_NEW = 26
NORM_USER_TIMEOUT = 28 SEND_ERROR = 27
USER_TIMEOUT = 28
NORM_INSTANCE_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_INSTANCE_INVALID").value 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 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.argtypes = [ctypes.c_void_p, ctypes.c_uint8]
libnorm.NormSetTOS.errcheck = errcheck_bool 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.restype = ctypes.c_bool
libnorm.NormSetLoopback.argtypes = [ctypes.c_void_p, ctypes.c_byte] libnorm.NormSetLoopback.argtypes = [ctypes.c_void_p, ctypes.c_byte]
libnorm.NormSetLoopback.errcheck = errcheck_bool libnorm.NormSetLoopback.errcheck = errcheck_bool
@ -321,6 +325,7 @@ def get_libnorm():
libnorm.NormStreamMarkEom.restype = None libnorm.NormStreamMarkEom.restype = None
libnorm.NormStreamMarkEom.argtypes = [ctypes.c_void_p] 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.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_bool]
libnorm.NormSetWatermark.errcheck = errcheck_bool libnorm.NormSetWatermark.errcheck = errcheck_bool
@ -336,9 +341,16 @@ def get_libnorm():
libnorm.NormRemoveAckingNode.restype = None libnorm.NormRemoveAckingNode.restype = None
libnorm.NormRemoveAckingNode.argtypes = [ctypes.c_void_p, ctypes.c_uint32] 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.restype = ctypes.c_int
libnorm.NormGetAckingStatus.argtypes = [ctypes.c_void_p, ctypes.c_uint32] 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.restype = ctypes.c_bool
libnorm.NormSendCommand.argtypes = [ctypes.c_void_p, ctypes.c_char_p, 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 from pynorm.object import Object
class Event(object): class Event(object):
def __init__(self, eventType, session, sender, normObject): def __init__(self, eventType:c.EventType, session:Session, sender, normObject):
self._type = eventType self._type:c.EventType = c.EventType(eventType)
self._session = session self._session:Session = session
self._sender = sender self._sender:Node = sender
self._object = normObject self._object:Object = normObject
# Properties # Properties
type = property(lambda self: self._type) type:c.EventType = property(lambda self: self._type)
session = property(lambda self: self._session) session:Session = property(lambda self: self._session)
sender = property(lambda self: self._sender) sender:Node = property(lambda self: self._sender)
object = property(lambda self: self._object) object:Object = property(lambda self: self._object)
## Private functions ## Private functions
@property @property
@ -41,63 +41,4 @@ class Event(object):
return cmp(self.type, type) return cmp(self.type, type)
def __str__(self): def __str__(self):
if self.type == c.NORM_EVENT_INVALID: return self._type.name
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"

View File

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

View File

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

View File

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

View File

@ -6,16 +6,18 @@ By: Tom Wambold <wambold@itd.nrl.navy.mil>
from __future__ import absolute_import from __future__ import absolute_import
import ctypes import ctypes
import locale
from typing import Optional
import pynorm.constants as c import pynorm.constants as c
from pynorm.core import libnorm, NormError from pynorm.core import libnorm, NormError
from pynorm.object import Object from pynorm.object import Object
class Session(object): class Session(object):
"""This represents a session tied to a particular NORM instance""" """This represents a session tied to a particular NORM instance"""
locale_encoding:str = locale.getpreferredencoding()
## Public functions ## 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 instance - An instance of NormInstance
address - String of multicast address to join address - String of multicast address to join
@ -23,7 +25,7 @@ class Session(object):
localId - NormNodeId localId - NormNodeId
""" """
self._instance = instance 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.sendGracefulStop = False
self.gracePeriod = 0 self.gracePeriod = 0
@ -31,195 +33,253 @@ class Session(object):
libnorm.NormDestroySession(self) libnorm.NormDestroySession(self)
del self._instance._sessions[self] del self._instance._sessions[self]
def setUserData(self, data): def setUserData(self, data:str):
"""data should be a string""" """data should be a string"""
libnorm.NormSetUserData(self, data.encode('utf-8')) libnorm.NormSetUserData(self, data.encode('utf-8'))
def getUserData(self): def getUserData(self)->str:
data = libnorm.NormGetUserData(self) data = libnorm.NormGetUserData(self)
return data.decode('utf-8') if data else None return data.decode('utf-8') if data else None
def getNodeId(self): def getNodeId(self):
return libnorm.NormGetLocalNodeId(self) return libnorm.NormGetLocalNodeId(self)
def setTxPort(self, txPort, enableReuse=False, txBindAddr=None): def setTxPort(self, txPort:int, enableReuse:bool=False, txBindAddr:Optional[str]=None):
libnorm.NormSetTxPort(self, txPort, enableReuse, txBindAddr) libnorm.NormSetTxPort(self, txPort, enableReuse,
txBindAddr.encode('utf-8') if txBindAddr else None )
def setRxPortReuse(self, enable, rxBindAddr=None, senderAddr=None, senderPort=0): def setTxOnly(self, txOnly:bool=False, connectToSessionAddress:bool=False):
libnorm.NormSetRxPortReuse(self, enable, rxBindAddr.encode('utf-8'), senderAddr.encode('utf-8'), senderPort) 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')) libnorm.NormSetMulticastInterface(self, iface.encode('utf-8'))
def setSSM(self, srcAddr): def setSSM(self, srcAddr:str):
libnorm.NormSetSSM(self, srcAddr.encode('utf-8')) libnorm.NormSetSSM(self, srcAddr.encode('utf-8'))
def setTTL(self, ttl): def setTTL(self, ttl) -> bool:
libnorm.NormSetTTL(self, ttl) return libnorm.NormSetTTL(self, ttl)
def setTOS(self, tos): def setTOS(self, tos) -> bool:
libnorm.NormSetTOS(self, tos) return libnorm.NormSetTOS(self, tos)
def setLoopback(self, loop): def setLoopback(self, loopbackEnable:bool=False):
libnorm.NormSetLoopback(self, loop) 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) return libnorm.NormGetReportInterval(self)
def setReportInterval(self, interval): def setReportInterval(self, interval:int):
libnorm.NormSetReportInterval(self, interval) libnorm.NormSetReportInterval(self, interval)
## Sender functions ## Sender functions
def startSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId=0): def startSender(self, sessionId:int, bufferSpace:int, segmentSize:int, blockSize:int, numParity:int, fecId=0) -> bool:
libnorm.NormStartSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId) return libnorm.NormStartSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity, fecId)
def stopSender(self): def stopSender(self):
libnorm.NormStopSender(self) libnorm.NormStopSender(self)
def setTxRate(self, rate): def setTxRate(self, txRate:float):
libnorm.NormSetTxRate(self, rate) 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) 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) libnorm.NormSetCongestionControl(self, ccEnable, adjustRate)
def setEcnSupport(self, ecnEnable, ignoreLoss=False, tolerateLoss=False): def setEcnSupport(self, ecnEnable, ignoreLoss=False, tolerateLoss=False):
libnorm.NormSetEcnSupport(self, ecnEnable, ignoreLoss, tolerateLoss) libnorm.NormSetEcnSupport(self, ecnEnable, ignoreLoss, tolerateLoss)
def setFlowControl(self, flowControlFactor): def setFlowControl(self, flowControlFactor):
libnorm.NormSetFlowControl(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) 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) libnorm.NormSetTxCacheBounds(self, sizeMax, countMin, countMax)
def setAutoParity(self, parity): def setAutoParity(self, parity:int):
libnorm.NormSetAutoParity(self, parity) libnorm.NormSetAutoParity(self, parity)
def getGrttEstimate(self): def getGrttEstimate(self) -> float:
return libnorm.NormGetGrttEstimate(self) return libnorm.NormGetGrttEstimate(self)
def setGrttEstimate(self, grttMax): def setGrttEstimate(self, grtt:float):
libnorm.NormSetGrttEstimate(self, grtt) return libnorm.NormSetGrttEstimate(self, grtt)
def setGrttMax(self, max): def setGrttMax(self, grttMax:float):
libnorm.NormSetGrttMax(self, grttMax) return libnorm.NormSetGrttMax(self, grttMax)
def setGrttProbingMode(self, mode): def setGrttProbingMode(self, probingMode:c.ProbingMode):
libnorm.NormSetGrttProbingMode(self, mode) libnorm.NormSetGrttProbingMode(self, probingMode.value)
def setGrttProbingInterval(self, intervalMin, intervalMax): def setGrttProbingInterval(self, intervalMin:int, intervalMax:int):
libnorm.NormSetGrttProbingInterval(self, intervalMin, intervalMax) libnorm.NormSetGrttProbingInterval(self, intervalMin, intervalMax)
def setBackoffFactor(self, factor): def setBackoffFactor(self, factor:int):
libnorm.NormSetBackoffFactor(self, factor) libnorm.NormSetBackoffFactor(self, factor)
def setGroupSize(self, size): def setGroupSize(self, size:int):
libnorm.NormSetGroupSize(self, size) 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? # 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: 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 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 # Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification # 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 return obj
def dataEnqueue(self, data, info=""): def dataEnqueue(self, data:bytes, info:bytes=b""):
# TBD - allow for case of info being None? # 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: 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 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 # Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification # 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 return obj
def streamOpen(self, bufferSize, info=""): def streamOpen(self, bufferSize:int, info=b""):
# TBD - allow for case of info being None? # 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: 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 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 # Put a reference of the object in our instance "_objects" cache to avoid creation
# of duplicative Python NORM Object during event notification # 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 return obj
def requeueObject(self, normObject): def requeueObject(self, normObject):
libnorm.NormRequeueObject(self, normObject) libnorm.NormRequeueObject(self, normObject)
def sendCommand(self, cmdBuffer, robust=False): def sendCommand(self, cmdBuffer:bytes, robust:bool=False) -> bool:
return libnorm.NormSendCommand(self, cmdBuffer.encode('utf-8'), len(cmdBuffer), robust) return libnorm.NormSendCommand(self, cmdBuffer, len(cmdBuffer), robust)
def cancelCommand(self): def cancelCommand(self):
libnorm.NormCancelCommand(self) libnorm.NormCancelCommand(self)
def setWatermark(self, normObject, overrideFlush=False): def setWatermark(self, normObject:Object, overrideFlush=False) -> bool:
libnorm.NormSetWatermark(self, normObject, overrideFlush) return libnorm.NormSetWatermark(self, normObject, overrideFlush)
def resetWatermark(self): def resetWatermark(self) -> bool:
libnorm.NormResetWatermark(self) return libnorm.NormResetWatermark(self)
def cancelWatermark(self): def cancelWatermark(self) -> None:
libnorm.NormCancelWatermark(self) libnorm.NormCancelWatermark(self)
def addAckingNode(self, nodeId): def addAckingNode(self, nodeId:int) -> bool:
libnorm.NormAddAckingNode(self, nodeId) return libnorm.NormAddAckingNode(self, nodeId)
def removeAckingNode(self, nodeId): def removeAckingNode(self, nodeId:int) -> None:
libnorm.NormRemoveAckingNode(self, nodeId) 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): def getAckingStatus(self, nodeId:int=c.NORM_NODE_ANY) -> c.AckingStatus:
return libnorm.NormGetAckingStatus(self, nodeId) return c.AckingStatus( libnorm.NormGetAckingStatus(self, nodeId) )
## Receiver functions ## Receiver functions
def startReceiver(self, bufferSpace): def startReceiver(self, bufferSpace:int) -> bool:
libnorm.NormStartReceiver(self, bufferSpace) '''
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""" """This will be called automatically if the receiver is active"""
libnorm.NormStopReceiver(self) libnorm.NormStopReceiver(self)
def setRxCacheLimit(self, count): def setRxCacheLimit(self, count:int):
libnorm.NormSetRxCacheLimit(self, count) libnorm.NormSetRxCacheLimit(self, count)
def setRxSocketBuffer(self, size:int):
def setRxSocketBuffer(self, size):
libnorm.NormSetRxSocketBuffer(self, size) libnorm.NormSetRxSocketBuffer(self, size)
def setSilentReceiver(self, silent, maxDelay=None): def setSilentReceiver(self, silent:bool, maxDelay:Optional[int]=None):
if maxDelay == None: if maxDelay == None:
maxDelay = -1 maxDelay = -1
libnorm.NormSetSilentReceiver(self, silent, maxDelay) libnorm.NormSetSilentReceiver(self, silent, maxDelay)
def setDefaultUnicastNack(self, mode): def setDefaultUnicastNack(self, enable:bool):
libnorm.NormSetDefaultUnicastNack(self, mode) libnorm.NormSetDefaultUnicastNack(self, enable)
def setDefaultSyncPolicy(self, policy):
libnorm.NormSetDefaultSyncPolicy(self, policy)
def setDefaultNackingMode(self, mode): def setDefaultSyncPolicy(self, policy:c.SyncPolicy):
libnorm.NormSetDefaultNackingMode(self, mode) libnorm.NormSetDefaultSyncPolicy(self, policy.value)
def setDefaultRepairBoundary(self, boundary): def setDefaultNackingMode(self, mode:c.NackingMode):
libnorm.NormSetDefaultRepairBoundary(self, boundary) 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): def setMessageTrace(self, state):
libnorm.NormSetMessageTrace(self, state) libnorm.NormSetMessageTrace(self, state)
## Properties ## Properties
nodeId = property(getNodeId) nodeId:int = property(getNodeId)
grtt = property(getGrttEstimate, setGrttEstimate) grtt:float = property(getGrttEstimate, setGrttEstimate)
userData = property(getUserData, setUserData) userData:str = property(getUserData, setUserData)
reportInterval = property(getReportInterval, setReportInterval) reportInterval:int = property(getReportInterval, setReportInterval)
## Private functions ## Private functions
def __del__(self): def __del__(self):
@ -233,6 +293,8 @@ class Session(object):
return self._session return self._session
def __cmp__(self, other): def __cmp__(self, other):
def cmp(a, b):
return (a > b) - (a < b)
try: try:
return cmp(self._as_parameter_, other._as_parameter) return cmp(self._as_parameter_, other._as_parameter)
except AttributeError: except AttributeError: