From 0194e2ba9aac31ebede275ae06bbca2cde6516b1 Mon Sep 17 00:00:00 2001 From: Jeff Weston Date: Wed, 11 Sep 2019 12:08:00 -0400 Subject: [PATCH] v1.5b2 --- README-PyNorm.txt | 37 +- VERSION.TXT | 26 + examples/java/NormMsgr.java | 789 ++++++++++++++ examples/java/normMsgr.sh | 2 + examples/normDataRecv.cpp | 13 +- examples/normMsgr.cpp | 972 ++++++++++++++++++ examples/normStreamRecv.cpp | 8 +- examples/normStreamSend.cpp | 35 +- examples/python/normMsgr.py | 396 +++++++ include/normApi.h | 34 +- include/normMessage.h | 18 +- include/normNode.h | 5 +- include/normObject.h | 9 +- include/normSegment.h | 2 +- include/normSession.h | 8 +- include/normSimAgent.h | 14 +- include/normVersion.h | 6 +- makefiles/Makefile.common | 7 + makefiles/android/jni/Android.mk | 8 + makefiles/android/jni/Application.mk | 1 + makefiles/android/project.properties | 2 +- makefiles/java/Makefile.common | 3 +- makefiles/java/Makefile.macosx | 17 + setup.py | 13 + src/common/normApi.cpp | 82 +- src/common/normNode.cpp | 23 +- src/common/normObject.cpp | 51 +- src/common/normSegment.cpp | 2 +- src/common/normSession.cpp | 116 ++- src/common/normSimAgent.cpp | 483 +++++---- src/common/pcap2norm.cpp | 19 +- src/java/jni/normSessionJni.cpp | 13 +- src/java/jni/normSessionJni.h | 10 +- .../src/mil/navy/nrl/norm/NormObject.java | 3 +- .../src/mil/navy/nrl/norm/NormSession.java | 12 +- .../navy/nrl/norm/enums/NormSyncPolicy.java | 1 + src/pynorm/constants.py | 36 +- src/pynorm/core.py | 48 +- src/pynorm/event.py | 8 +- src/pynorm/instance.py | 23 +- src/pynorm/object.py | 22 +- src/pynorm/session.py | 65 +- waf | Bin 87674 -> 90909 bytes wscript | 1 + 44 files changed, 3066 insertions(+), 377 deletions(-) create mode 100644 examples/java/NormMsgr.java create mode 100755 examples/java/normMsgr.sh create mode 100644 examples/normMsgr.cpp create mode 100644 examples/python/normMsgr.py create mode 100644 makefiles/java/Makefile.macosx create mode 100644 setup.py diff --git a/README-PyNorm.txt b/README-PyNorm.txt index fd7d5c7..114b178 100644 --- a/README-PyNorm.txt +++ b/README-PyNorm.txt @@ -18,7 +18,7 @@ scripts. Requirements ------------ -PyNORM has been tested on Python versions 2.5 and 2.6. The code may work on +PyNORM has been tested on Python versions 2.5, 2.6, and 2.7. The code may work on earlier versions, but was not tested. PyNORM requires NORM to be built as a shared library (.so, .dylib, .dll), so @@ -29,6 +29,41 @@ PATH environment variable. On Windows, PyNORM requires the "PyWin32" module, available at: http://sf.net/projects/pywin32 + +------------- +Installation +------------- +A "setup.py" script is included that installs the 'pynorm' packages. Note, as +mentioned above, the NORM shared library must be installed on your system. The +'waf' install script will do this or it can be built with the NORM 'makefiles' +and manually emplaced. If you use the 'setup.py' script, you do _not_ need +to use the '--build-python' option of the 'waf' configuration script even if you +use 'waf' to build the NORM shared library. The 'waf' build tool can also be used +to install the 'pynorm' package. + +For example, to build and install "libnorm.so" and "pynorm" on Linux, use the +following steps. + +cd norm/makefiles +make -f Makefile.linux libnorm.so +sudo cp libnorm.so /usr/local/lib/ +cd ../ +sudo python setup.py install + +On MacOS, the same steps can be used, but using "libnorm.dylib" instead. + +----------------------- +Examples +----------------------- +Some 'pynorm' *.py examples are in the "norm/examples" directory. + +The 'normMsgr.py' example is functionally equivalent to the 'normMsgr.cpp' +example and the 'java/NormMsgr.java' example and illustrates multi-threaded +use of the 'pynorm' package for NORM-based data transmission and reception. +Note the "NORM_OBJECT_DATA" model is used and may provide out-of-order delivery +of the transmitted messages. A similar 'normStreamer.py' example will be +provided in the future to illustrate the NORM ordered, message stream delivery +mode of operation. ----------------------- "Extra" Package Modules diff --git a/VERSION.TXT b/VERSION.TXT index 34eaa7d..d420bd4 100644 --- a/VERSION.TXT +++ b/VERSION.TXT @@ -1,6 +1,32 @@ NORM Version History +Version 1.5b2 +============= + - Fixed bug with "graceful" stream closure when the sender application + did not first flush the stream before calling NormStreamClose() + - Added NORM_TX_FLUSH_COMPLETED notification for active stream + flushes, including the end of transmission flush when a stream + has been gracefully closed. + - Added NORM_SYNC_STREAM sync policy that is equivalent to NORM_SYNC_CURRENT + but attempts to sync to _start_ of stream (byte offset 0) + - Fixed issue with ackowledged, graceful stream closure to allow application + to choose to reset watermark before closing stream. The app needs to close + streams that remain unacknowledged at closure (i.e. catch TX_WATERMARK_COMPLETED + notification check acking status). Acknowledged, graceful shutdown streams, + are automatically closed. + - Fixed issue with compilation for NormObjectSize constructor, MSB(), and LSB() + methods on 32-bit systems. Thanks to Bill Skiba. + Version 1.5b1 +============= + - Rolled up all of the features described in Version 1.4b4 below + as a new "stable release" even though some minor documentation + issues are pending. + - Also includes the beginnings of code for "auto population" of + the sender acking node list for easier (less precoordination) + invocation of ACK-based flow control, etc + +Version 1.4b4 ============= - Added option to set NORM receiver "rx cache count max", i.e. the maximum number of pending object for which the receiver will keep diff --git a/examples/java/NormMsgr.java b/examples/java/NormMsgr.java new file mode 100644 index 0000000..6ed9680 --- /dev/null +++ b/examples/java/NormMsgr.java @@ -0,0 +1,789 @@ + +// This is a Java implementation of the same +// NORM "messenger" approach as normMsgr.cpp + +import java.util.concurrent.Semaphore; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.HashMap; +import java.util.LinkedList; + +import mil.navy.nrl.norm.NormEvent; +import mil.navy.nrl.norm.NormInstance; +import mil.navy.nrl.norm.NormNode; +import mil.navy.nrl.norm.NormSession; +import mil.navy.nrl.norm.NormObject; +import mil.navy.nrl.norm.NormData; +import mil.navy.nrl.norm.enums.NormEventType; +import mil.navy.nrl.norm.enums.NormObjectType; +import mil.navy.nrl.norm.enums.NormAckingStatus; +import mil.navy.nrl.norm.enums.NormSyncPolicy; + +public class NormMsgr +{ + static final int MSG_SIZE_MAX = 65536; + static final int MSG_HDR_SIZE = 2; + + private static Random randGen = new Random(System.currentTimeMillis()); + + // These members track NORM's "tx ready" status + private Semaphore normTxLock; // this acts as a mutex to manage "tx ready" status in a thread safe way + private Semaphore normTxReady; // this blocks data enqueuing until tx ready + private boolean norm_tx_vacancy; + private int norm_tx_queue_count; + private int norm_tx_queue_max; + private boolean norm_tx_watermark_pending; + + public NormInstance normInstance; + private NormSession normSession; + private boolean norm_acking; + + // This HashMap is where we cache enqueued Messages until NORM_TX_OBJECT_PURGED + // (In the C++ version we used the NormObjectSetUserData() which isn't available in NORM Java API) + private HashMap input_msg_list = new HashMap(); + + // Messages received from NORM are enqueued here until + // retrieved by the OutputWriter thread + private MessageQueue output_msg_queue; + private Semaphore normRxLock; // protects inter-thread access to output_msg_queue + private Semaphore normRxReady; // blocks OutputWriter when output_msg_queue is empty + + // Porbably will build these congestion control modes into the NORM API + public enum NormCCMode + { + NORM_FIXED, + NORM_CC, + NORM_CCE, + NORM_CCL; + } + + // Constructor + public NormMsgr() throws java.io.IOException, InterruptedException + { + normTxLock = new Semaphore(1); + normTxReady = new Semaphore(1); + try { + normInstance = new NormInstance(); + normInstance.setDebugLevel(3); + } + catch (java.io.IOException ex) { + System.err.println(ex); + throw(ex); + } + // Default parameter values + norm_tx_vacancy = true; + norm_tx_queue_max = 2048; // this is set largish, since I'm testing with _small_ messages + norm_tx_queue_count = 0; + norm_tx_watermark_pending = false; + norm_acking = false; + + normRxLock = new Semaphore(1); + normRxReady = new Semaphore(1); + output_msg_queue = new MessageQueue(); + normRxReady.acquire(); // nothing received yet + } + + public boolean openNormSession(String addr, short port, long nodeId) + { + try + { + normSession = normInstance.createSession(addr, port, nodeId); + if (null == normSession) + { + System.err.println("normMsgr error: unable to create NORM session"); + return false; + } + // Set some default parameters (maybe we should put parameter setting in Start()) + normSession.setRxCacheLimit(2*norm_tx_queue_max); // we let the receiver track some extra objects + normSession.setDefaultSyncPolicy(NormSyncPolicy.NORM_SYNC_ALL); + normSession.setDefaultUnicastNack(true); + normSession.setTxCacheBounds(10*1024*1024, norm_tx_queue_max, norm_tx_queue_max); + + normSession.setCongestionControl(true, true); + + //normSession.setMessageTrace(true); + } + catch (java.io.IOException ex) + { + System.err.println(ex); + } + return true; + } // end NormMsgr::openNormSession() + + public void addAckingNode(long nodeId) + { + try { + normSession.addAckingNode(nodeId); + } + catch (java.io.IOException ex) { + System.err.println(ex); + return; + } + norm_acking = true; + } // end NormMsgr::addAckingNode() + + public boolean setNormMulticastInterface(String ifaceName) + { + try { + normSession.setMulticastInterface(ifaceName); + } + catch (java.io.IOException ex) { + System.err.println(ex); + return false; + } + return true; + } // end NormMsgr::setMulticastInterface() + + public void setNormCCMode(NormCCMode ccMode) + { + switch (ccMode) + { + case NORM_CC: // default TCP-friendly congestion control + normSession.setEcnSupport(false, false, false); + break; + case NORM_CCE: // "wireless-ready" ECN-only congestion control + normSession.setEcnSupport(true, true); + break; + case NORM_CCL: // "loss tolerant", non-ECN congestion control + normSession.setEcnSupport(false, false, true); + break; + case NORM_FIXED: + normSession.setEcnSupport(false, false, false); + break; + } + if (NormCCMode.NORM_FIXED != ccMode) + normSession.setCongestionControl(true); + else + normSession.setCongestionControl(false); + } // end NormMsgr::setNormCCMode() + + public void setNormTxRate(double bitsPerSecond) + { + normSession.setTxRate(bitsPerSecond); + } + + public void setNormDebugLevel(int level) + {normInstance.setDebugLevel(level);} + + public void setNormMessageTrace(boolean state) + {normSession.setMessageTrace(state);} + + public boolean start(boolean send, boolean recv) + { + // Start NORM sender and/or receiver operation + boolean recvStarted = false; + try + { + if (recv) + { + normSession.startReceiver(10*1024*1024); + recvStarted = true; + } + if (send) + { + if (norm_acking) + { + // ack-based flow control enabled on command-line, + // so disable timer-based flow control + normSession.setFlowControl(0.0); + } + // Pick a random instance id for now + int instanceId = randGen.nextInt(); + normSession.startSender(instanceId, 10*1024*1024, 1400, (short)16, (short)4); + } + } + catch (java.io.IOException ex) + { + System.err.println(ex); + if (recvStarted) normSession.stopReceiver(); + return false; + } + return true; + } // end NormMsgr::start() + + public boolean sendMessage(Message msg) + { + // Future version will support NORM_OBJECT_STREAM as an option + while (!enqueueMessageObject(msg)); // keep trying until success (we're blocked by "normTxReady" semaphore) + return true; + } // end NormMsgr::sendMessage() + + public boolean enqueueMessageObject(Message msg) + { + try + { + normTxReady.acquire(); // caller will be blocked if NORM is not "tx ready" + normTxLock.acquire(); // this guarantees protected access to "tx ready" state variables + } + catch (InterruptedException ex) + { + System.err.println(ex); + if (!normTxReady.tryAcquire()) + normTxReady.release(); + return false; + } + NormData obj = null; + try { + obj = normSession.dataEnqueue(msg.getBuffer(), 0, msg.getSize()); + } + catch (java.io.IOException ex) { + //System.err.println(ex); + obj = null; + } + if (null == obj) + { + // Note we don't call normTxReady.release() here, it's up to the + // NormEventHandler to do that upon NORM_TX_QUEUE_EMPTY (or VACANCY for streams) + //System.err.println("NormMsgr::SendMessage() warning: data enqueue was blocked."); + norm_tx_vacancy = false; // there was no room at the inn + normTxLock.release(); + return false; + } + //System.err.println("caching msg for object " + obj + "\n"); + // Cache the msg associated with the resultant tx object. We use the + // input_msg_list HashMap so we can remove the msg upon NORM_TX_OBJECT_PURGED + input_msg_list.put(obj, msg); + + if (norm_acking) + { + norm_tx_queue_count++; + if (!norm_tx_watermark_pending && (norm_tx_queue_count >= (norm_tx_queue_max / 2))) + { + try { + normSession.setWatermark(obj); + norm_tx_watermark_pending = true; + } + catch (java.io.IOException ex) { + System.err.println(ex); + } + } + if (norm_tx_queue_count >= norm_tx_queue_max) + { + // We've filled our tx cache, so don't call normTxReady.release() + // NormEventHandler will do this upon watermark completion + normTxLock.release(); + return true; + } + } + normTxReady.release(); + normTxLock.release(); + return true; + } + + public void onNormTxObjectPurged(NormObject normObject) + { + try { + normTxLock.acquire(); + } + catch (InterruptedException ex) { + System.err.println(ex); + return; // Should kill program here (or maybe we should acquire uninterruptably)? + } + if (NormObjectType.NORM_OBJECT_DATA == normObject.getType()) + { + // removed "msg" will get garbage-collected + //System.err.println("purging msg for object " + (NormData)normObject + "\n"); + Message msg = input_msg_list.remove((NormData)normObject); + if (null == msg) // Shouldn't happen (and it doesn't after adding normTxLock.acquire()) + System.err.println("normMsgr warning: purged invalid object?!"); + } + normTxLock.release(); + } // end NormMsgr::onNormTxObjectPurged() + + // These next two methods are used by the NormEventHandler to update the + // NORM "tx ready" status variables in a thread-safe manner. Release of + // the "normTxReady" semaphore will unblock the InputThread if it was blocked + // due filling up the NORM tx queue. + // (The "!wasTxReady" check in these avoids any race condition with the normTxReady semaphore + public void onNormTxQueueVacancy() + { + try { + normTxLock.acquire(); + } + catch (InterruptedException ex) { + System.err.println(ex); + return; // Should kill program here (maybe we should acquire uninterruptably) + } + boolean wasTxReady = norm_tx_vacancy && (norm_acking ? (norm_tx_queue_count < norm_tx_queue_max) : true); + norm_tx_vacancy = true; + boolean isTxReady = norm_acking ? (norm_tx_queue_count < norm_tx_queue_max) : true; + if (!wasTxReady && isTxReady) + { + if (normTxReady.tryAcquire()) + System.err.println("NormMsgr::setNormTxVacancy() warning: normTxReady wasn't locked?!"); + normTxReady.release(); + } + normTxLock.release(); + } // end NormMsgr::onNormTxQueueVacancy() + + public void onNormTxWatermarkCompleted() + { + try { + normTxLock.acquire(); + } + catch (InterruptedException ex) { + System.err.println(ex); + return; // Should kill program here (or maybe we should acquire uninterruptably)? + } + boolean wasTxReady = norm_tx_vacancy && (norm_acking ? (norm_tx_queue_count < norm_tx_queue_max) : true); + norm_tx_queue_count -= (norm_tx_queue_max / 2); + norm_tx_watermark_pending = false; + boolean isTxReady = norm_tx_vacancy && (norm_acking ? (norm_tx_queue_count < norm_tx_queue_max) : true); + if (!wasTxReady && isTxReady) + { + if (normTxReady.tryAcquire()) + System.err.println("NormMsgr::decrementNormTxQueueCount() warning: normTxReady wasn't locked?!"); + normTxReady.release(); + } + normTxLock.release(); + } // end NormMsgr::onNormTxQueueVacancy() + + public void onNormRxObjectCompleted(NormObject obj) + { + try { + normRxLock.acquire(); + } + catch (InterruptedException ex) { + System.err.println(ex); + return; // Should kill program here (or maybe we should acquire uninterruptably)? + } + if (NormObjectType.NORM_OBJECT_DATA == obj.getType()) + { + // It's a message, so put it in the output_msg_queue for the OutputWriter thread + boolean wasEmpty = output_msg_queue.isEmpty(); + Message msg = new Message(((NormData)obj).getData(), (int)obj.getSize()); + output_msg_queue.append(msg); + if (wasEmpty) normRxReady.release(); // this will unblock the waiting OutputWriter + } + normRxLock.release(); + } // end NormMsgr::onNormRxObjectCompleted() + + // Called by OutputWriter thread to fetch received messages + public Message getRxMsg() + { + try { + normRxReady.acquire(); // will block if output_msg_queue is empty + normRxLock.acquire(); + } + catch (InterruptedException ex) { + System.err.println(ex); + return null; + } + Message msg = output_msg_queue.removeHead(); + if (!output_msg_queue.isEmpty()) + normRxReady.release(); + normRxLock.release(); + if (null == msg) // shouldn't happen + System.err.println("NormMsgr warning: output_msg_queue unexpectedly empty?!"); + return msg; + } + + public void RunThreads() + { + InputReader inputReader = new InputReader(this); + System.err.println("main thread starting input thread ..."); + inputReader.start(); + + NormEventHandler normEventHandler = new NormEventHandler(this); + System.err.println("main thread starting norm event handler thread ..."); + normEventHandler.start(); + + OutputWriter outputWriter = new OutputWriter(this); + System.err.println("main thread starting output thread ..."); + outputWriter.start(); + + // For now, we put the "inputReader" thread in the driver's seat. + // In the future, we'll be acquiring a semaphore shared among the + // threads and determine what thread(s) are signaling the parent + // and their status(es) + System.err.println("main thread waiting on input thread ..."); + inputReader.acquireLock(); // when acquired, indicates thread is done + + try { + inputReader.join(); + normInstance.stopInstance(); + normInstance.destroyInstance(); + normEventHandler.join(); + } + catch (InterruptedException ex) { + System.err.println(ex); + } + System.err.println("main thread exiting ..."); + } + + public class Message + { + private ByteBuffer msgBuffer; + + // Constructors + public Message(int size) + { + msgBuffer = ByteBuffer.allocateDirect(size); + } + public Message(byte[] buffer, int size) + { + msgBuffer = ByteBuffer.wrap(buffer, 0, size); + } + + public int getSize() + {return msgBuffer.capacity();} + public ByteBuffer getBuffer() + {return msgBuffer;} + + } // end class NormMsgr::Message + + public class MessageQueue extends LinkedList + { + public void append(Message msg) + {addLast(msg);} + public void prepend(Message msg) + {addFirst(msg);} + public Message removeHead() + {return removeFirst();} + public Message removeTail() + {return removeLast();} + public boolean isEmpty() + { + if (null == peekFirst()) + return true; + else + return false; + } + } // end class NormMsgr::MessageQueue + + private class InputReader extends Thread + { + private NormMsgr parentMsgr; + private boolean inputDone; + private Semaphore threadLock; + + public InputReader(NormMsgr parent) + { + parentMsgr = parent; + inputDone = false; + threadLock = new Semaphore(1); + } + + public boolean acquireLock() + { + try + { + threadLock.acquire(); + return true; + } + catch (InterruptedException ex) + { + System.err.println(ex); + return false; + } + } + + @Override + public void run() + { + System.err.println("input thread acquiring its lock ..."); + try + { + threadLock.acquire(); + } + catch (InterruptedException ex) + { + System.err.println(ex); + return; + } + System.err.println("input thread entering its main loop ..."); + byte[] msgHeader = new byte[MSG_HDR_SIZE]; + byte[] tmpBuffer = new byte[MSG_SIZE_MAX]; + while (!inputDone) + { + // Read in one message at time. + int want = MSG_HDR_SIZE; + int got = 0; + while (got < want) + { + try + { + int result = System.in.read(msgHeader, got, want - got); + if (-1 == result) + { + System.err.println("normMsgr: input end-of-file!"); + inputDone = true; + threadLock.release(); + System.err.println("input thread exiting 1 ..."); + return; + } + //System.err.println("input thread read " + result + " bytes of msg header\n"); + got += result; + } + catch (java.io.IOException ex) + { + System.err.println(ex); + } + } + int msgSize = 256*msgHeader[0] + msgHeader[1]; + //System.err.println("msg header is (256 * " + msgHeader[0] + ") + " + msgHeader[1] + " = " + msgSize); + got = 0; + msgSize -= 2; // already read header + while (got < msgSize) + { + try + { + int result = System.in.read(tmpBuffer, got, msgSize - got); + if (-1 == result) + { + System.err.println("normMsgr: input end-of-file!"); + inputDone = true; + threadLock.release(); + System.err.println("input thread exiting 2 ..."); + return; + } + //System.err.println("input thread read " + result + "bytes of message data\n"); + got += result; + } + catch (java.io.IOException ex) + { + System.err.println(ex); + } + } + int totalSize = msgSize + 2; + //System.err.println("normMsgr: read a " + totalSize + " byte message ...\n"); + + Message msg = new Message(msgSize); + msg.getBuffer().put(tmpBuffer, 0, msgSize); + parentMsgr.sendMessage(msg); + + } + System.err.println("input thread releasing its lock ..."); + threadLock.release(); + System.err.println("input thread exiting 3 ..."); + + } // end InputReader::run() + } // end class NormMsgr::InputReader + + + private class NormEventHandler extends Thread + { + private NormMsgr parentMsgr; + + public NormEventHandler(NormMsgr parent) + { + parentMsgr = parent; + } + + @Override + public void run() + { + try + { + System.err.println("entering NormEventHandler loop ..."); + NormEvent event; + while (null != (event = normInstance.getNextEvent())) + { + //System.err.println(event); + NormSession session = event.getSession(); + switch (event.getType()) + { + case NORM_TX_QUEUE_EMPTY: + case NORM_TX_QUEUE_VACANCY: + // This will unblock the InputThread if it was blocked + parentMsgr.onNormTxQueueVacancy(); + break; + + case NORM_TX_WATERMARK_COMPLETED: + if (NormAckingStatus.NORM_ACK_SUCCESS == session.getAckingStatus(NormNode.NORM_NODE_ANY)) + { + // This will unblock the InputThread if it was blocked + //System.err.println("sender tx watermark completed ..."); + parentMsgr.onNormTxWatermarkCompleted(); + } + else + { + // TBD - we could see who didn't ACK and possibly remove them + // from our acking list. For now, we are infinitely + // persistent by resetting watermark ack request + session.resetWatermark(); + } + break; + + case NORM_TX_OBJECT_PURGED: + parentMsgr.onNormTxObjectPurged(event.getObject()); + break; + + case NORM_RX_OBJECT_COMPLETED: + parentMsgr.onNormRxObjectCompleted(event.getObject()); + break; + + case NORM_REMOTE_SENDER_INACTIVE: + // optionally free memory resources that were in + // use by this remote sender + break; + + default: + break; + } + } + System.err.println("NormEventHandler got null event ..."); + } + catch (java.io.IOException ex) + { + System.err.println("NormGetNextEvent IOException:"); + System.err.println(ex); + } + System.err.println("exiting NormEventHandler ..."); + } // end NormEventHandler::run() + } // end class NormMsgr::NormEventHandler + + private class OutputWriter extends Thread + { + private NormMsgr parentMsgr; + boolean outputDone = false; + + // Constructors + public OutputWriter(NormMsgr parent) + { + parentMsgr = parent; + } + + @Override + public void run() + { + while (!outputDone) + { + Message msg = parentMsgr.getRxMsg(); // this will block if none available + // Make and write 2-byte length header (adding to the length to account for header, too) + int msgSize = msg.getSize() + MSG_HDR_SIZE; + byte[] msgHeader = new byte[MSG_HDR_SIZE]; + // This currently assumes 2-byte MSG_HDR_SIZE + msgHeader[0] = (byte)((msgSize >> 8) & 0xff); + msgHeader[1] = (byte)(msgSize & 0xff); + System.out.write(msgHeader, 0, MSG_HDR_SIZE); + // Write message content + System.out.write(msg.getBuffer().array(), 0, msg.getSize()); + } + System.err.println("NormMsgr output thread exiting ..."); + } + } // end class NormMsgr::OutputWriter() + + public static void usage() + { + System.err.println("Usage: normMsgr id {send &| recv} [addr [/]][ack [,,...]\n" + + " [cc|cce|ccl|rate ][interface ][debug ][trace]\n"); + } + + public static void main(String[] args) throws Throwable + { + // Default parameters + String sessionAddr = "224.1.2.3"; + short sessionPort = 6003; + long nodeId = 0; + boolean send = false; + boolean recv = false; + String[] ackerList = null; + String mcastIface = null; + int debugLevel = 0; + boolean normTrace = false; + NormCCMode ccMode = NormCCMode.NORM_CC; + double txRate = 1.0e+06; // only applies for NORM_FIXED ccMode + + int i = 0; + while (i < args.length) + { + String cmd = args[i++]; + if (cmd.equals("id")) + { + nodeId = Integer.parseInt(args[i++]); + } + else if (cmd.equals("send")) + { + send = true; + } + else if (cmd.equals("recv")) + { + recv = true; + } + else if (cmd.equals("addr")) + { + String[] items = args[i++].split("/"); + sessionAddr = items[0]; + if (items.length > 1) + sessionPort = (short)Integer.parseInt(items[1]); + } + else if (cmd.equals("interface")) + { + mcastIface = args[i++]; + } + else if (cmd.equals("cce")) + { + ccMode = NormCCMode.NORM_CCE; + } + else if (cmd.equals("cce")) + { + ccMode = NormCCMode.NORM_CCL; + } + else if (cmd.equals("rate")) + { + ccMode = NormCCMode.NORM_FIXED; + txRate = Double.parseDouble(args[i++]); + } + else if (cmd.equals("ack")) + { + ackerList = args[i++].split(","); + } + else if (cmd.equals("debug")) + { + debugLevel = Integer.parseInt(args[i++]); + } + else if (cmd.equals("trace")) + { + normTrace = true; + } + else + { + System.err.println("normMsgr error: invalid command '" + cmd + "'"); + usage(); + return; + } + } + if (!send && !recv) + { + System.err.println("normMsgr error: not configured to send or recv!"); + usage(); + return; + } + if (0 == nodeId) + { + System.err.println("normMsgr error: no local 'id' provided!"); + usage(); + return; + } + + NormMsgr msgr = new NormMsgr(); + + msgr.setNormDebugLevel(debugLevel); + + msgr.openNormSession(sessionAddr, sessionPort, nodeId); + + if (null != mcastIface) + msgr.setNormMulticastInterface(mcastIface); + + if (null != ackerList) + { + for (i = 0; i < ackerList.length; i++) + msgr.addAckingNode(Integer.parseInt(ackerList[i])); + } + + msgr.setNormCCMode(ccMode); + if (NormCCMode.NORM_FIXED == ccMode) + msgr.setNormTxRate(txRate); + + msgr.setNormMessageTrace(normTrace); + + msgr.start(send, recv); + + msgr.RunThreads(); + + System.err.println("normMsgr: main() exiting ..."); + } + + +} // end class NormMsgr diff --git a/examples/java/normMsgr.sh b/examples/java/normMsgr.sh new file mode 100755 index 0000000..a492eab --- /dev/null +++ b/examples/java/normMsgr.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormMsgr $@ diff --git a/examples/normDataRecv.cpp b/examples/normDataRecv.cpp index 3a3626a..58764f5 100644 --- a/examples/normDataRecv.cpp +++ b/examples/normDataRecv.cpp @@ -87,10 +87,11 @@ int main(int argc, char* argv[]) NormInstanceHandle instance = NormCreateInstance(); // 2) Create a NormSession using default "automatic" local node id + fprintf(stderr, "joining session at addr/port %s/%hu\n", sessionAddr, sessionPort); NormSessionHandle session = NormCreateSession(instance, sessionAddr, sessionPort, - NORM_NODE_ANY); + 2);//NORM_NODE_ANY); // NOTE: These are debugging routines available @@ -124,8 +125,13 @@ int main(int argc, char* argv[]) { // Uncomment to allow multiple NORM processes on same session port number //NormSetRxPortReuse(session, true, sessionAddr); + + // NormSetDefaultUnicastNack(session, true); } + // Set a big rx cache for our current testing + NormSetRxCacheLimit(session, 4096); + // 3) Start the receiver with 1 Mbyte buffer per sender NormStartReceiver(session, 1024*1024); @@ -141,7 +147,7 @@ int main(int argc, char* argv[]) { case NORM_RX_OBJECT_NEW: gettimeofday(&startTime, NULL); - fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_NEW event ...\n"); + //fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_NEW event ...\n"); break; case NORM_RX_OBJECT_INFO: @@ -174,9 +180,10 @@ int main(int argc, char* argv[]) case NORM_RX_OBJECT_COMPLETED: { gettimeofday(&endTime, NULL); - fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_COMPLETED event ...\n"); + //fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_COMPLETED event ...\n"); unsigned int objSize = NormObjectGetSize(theEvent.object); const char* dataPtr = NormDataAccessData(theEvent.object); + // next 3 lines are temp for normMsgr testing // Validate that the data is complete/accurate // a) compare data size against the size embedded in the "INFO" char dataInfo[8192]; diff --git a/examples/normMsgr.cpp b/examples/normMsgr.cpp new file mode 100644 index 0000000..1ba8017 --- /dev/null +++ b/examples/normMsgr.cpp @@ -0,0 +1,972 @@ + +#include "normApi.h" + +#include // for printf(), etc +#include // for srand() +#include // for strrchr(), memset(), etc +#include // for gettimeofday() +#include // for htons() +#include // for, well, fnctl() +#include // obvious child +#include // embarrassingly obvious + +class NormMsgr +{ + public: + NormMsgr(); + ~NormMsgr(); + + // some day build these directly into NORM API + enum CCMode {NORM_FIXED, NORM_CC, NORM_CCE, NORM_CCL}; + + enum + { + MSG_SIZE_MAX = 65535, + MSG_HEADER_SIZE = 2 + }; + + // helper class and methods for message management + class MessageQueue; + class Message + { + friend class MessageQueue; + public: + Message(); + Message(char* buffer, unsigned int size); + ~Message(); + + bool Init(unsigned int size); + void Destroy(); + + unsigned int GetSize() const + {return msg_size;} + const char* GetHeader() const + {return msg_header;} + char* AccessHeader() + {return msg_header;} + const char* GetBuffer() const + {return msg_buffer;} + char* AccessBuffer() + {return msg_buffer;} + + void ResetIndex() + {msg_index = 0;} + void IncrementIndex(unsigned int count) + {msg_index += count;} + unsigned int GetIndex() const + {return msg_index;} + + bool IsComplete() const + {return (msg_index == (MSG_HEADER_SIZE + msg_size));} + + private: + unsigned int msg_size; + unsigned int msg_index; + char msg_header[MSG_HEADER_SIZE]; // this could be externalized to NormMsgr::input_msg_header / output_msg_header members + char* msg_buffer; + Message* prev; + Message* next; + }; // end class NormMsgr::Message + + class MessageQueue + { + public: + MessageQueue(); + ~MessageQueue(); + + bool IsEmpty() const + {return (NULL == head);} + + void Prepend(Message& msg); + void Append(Message& msg); + void Remove(Message& msg); + Message* RemoveHead(); + Message* RemoveTail(); + + void Destroy(); + + private: + Message* head; + Message* tail; + }; // end class NormMsgr::MessageQueue + Message* NewMessage(unsigned int size); + + bool OpenNormSession(NormInstanceHandle instance, + const char* addr, + unsigned short port, + NormNodeId nodeId); + void CloseNormSession(); + + void SetNormCongestionControl(CCMode ccMode); + void SetNormTxRate(double bitsPerSecond) + { + assert(NORM_SESSION_INVALID != norm_session); + NormSetTxRate(norm_session, bitsPerSecond); + } + void SetNormMulticastInterface(const char* ifaceName) + { + assert(NORM_SESSION_INVALID != norm_session); + NormSetMulticastInterface(norm_session, ifaceName); + } + void SetNormMessageTrace(bool state) + { + assert(NORM_SESSION_INVALID != norm_session); + NormSetMessageTrace(norm_session, state); + } + void AddAckingNode(NormNodeId ackId) + { + assert(NORM_SESSION_INVALID != norm_session); + NormAddAckingNode(norm_session, ackId); + norm_acking = true; // invoke ack-based flow control + } + + bool Start(bool sender, bool receiver); + void Stop() + {is_running = false;} + bool IsRunning() const + {return is_running;} + void HandleNormEvent(const NormEvent& event); + + // Sender methods + FILE* GetInputFile() const + {return input_file;} + bool InputNeeded() const + {return input_needed;} + bool InputMessageReady() const + {return ((NULL != input_msg) && !input_needed);} + bool ReadInput(); + + bool NormTxReady() const; + bool SendMessage(); + bool EnqueueMessageObject(); + + // Receiver methods + FILE* GetOutputFile() const + {return output_file;} + void SetOutputReady() + {output_ready = true;} + bool OutputReady() const + {return output_ready;} + bool OutputPending() const + {return output_pending;} + bool WriteOutput(); + + private: + bool is_running; + FILE* input_file; // stdin by default + bool input_needed; // + Message* input_msg; // current input message being read/sent* + MessageQueue input_msg_list; // list of enqueued messages (in norm sender cache) + + NormSessionHandle norm_session; + unsigned int norm_tx_queue_max; // max number of objects that can be enqueued at once + unsigned int norm_tx_queue_count; // count of unacknowledged enqueued objects (TBD - optionally track size too) + bool norm_tx_watermark_pending; + bool norm_tx_vacancy; + bool norm_acking; + + FILE* output_file; + bool output_ready; + bool output_pending; + Message* output_msg; + MessageQueue output_msg_queue; + +}; // end class NormMsgr + +NormMsgr::NormMsgr() + : input_file(stdin), input_needed(true), input_msg(NULL), + norm_session(NORM_SESSION_INVALID), norm_tx_queue_max(2048), norm_tx_queue_count(0), + norm_tx_watermark_pending(false), norm_tx_vacancy(true), norm_acking(false), + output_file(stdout), output_ready(true), output_pending(false), output_msg(NULL) +{ +} + +NormMsgr::~NormMsgr() +{ + if (NULL != input_msg) delete input_msg; + input_msg_list.Destroy(); + if (NULL != output_msg) delete output_msg; + output_msg_queue.Destroy(); +} + +bool NormMsgr::OpenNormSession(NormInstanceHandle instance, const char* addr, unsigned short port, NormNodeId nodeId) +{ + norm_session = NormCreateSession(instance, addr, port, nodeId); + if (NORM_SESSION_INVALID == norm_session) + { + fprintf(stderr, "normMsgr error: unable to create NORM session\n"); + return false; + } + // Set some default parameters (maybe we should put parameter setting in Start()) + NormSetRxCacheLimit(norm_session, norm_tx_queue_max); + NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_ALL); + + NormSetDefaultUnicastNack(norm_session, true); + NormSetTxCacheBounds(norm_session, 10*1024*1024, norm_tx_queue_max, norm_tx_queue_max); + + //NormSetMessageTrace(norm_session, true); + return true; +} // end NormMsgr::OpenNormSession() + +void NormMsgr::CloseNormSession() +{ + if (NORM_SESSION_INVALID == norm_session) return; + NormDestroySession(norm_session); + norm_session = NORM_SESSION_INVALID; +} // end NormMsgr::CloseNormSession() + +void NormMsgr::SetNormCongestionControl(CCMode ccMode) +{ + assert(NORM_SESSION_INVALID != norm_session); + switch (ccMode) + { + case NORM_CC: // default TCP-friendly congestion control + NormSetEcnSupport(norm_session, false, false, false); + break; + case NORM_CCE: // "wireless-ready" ECN-only congestion control + NormSetEcnSupport(norm_session, true, true); + break; + case NORM_CCL: // "loss tolerant", non-ECN congestion control + NormSetEcnSupport(norm_session, false, false, true); + break; + case NORM_FIXED: + NormSetEcnSupport(norm_session, false, false, false); + break; + } + if (NORM_FIXED != ccMode) + NormSetCongestionControl(norm_session, true); + else + NormSetCongestionControl(norm_session, false); +} // end NormMsgr::SetNormCongestionControl() + +bool NormMsgr::Start(bool sender, bool receiver) +{ + if (receiver) + { + if (!NormStartReceiver(norm_session, 10*1024*1024)) + { + fprintf(stderr, "normMsgr error: unable to start NORM receiver\n"); + return false; + } + } + if (sender) + { + if (norm_acking) + { + // ack-based flow control enabled on command-line, + // so disable timer-based flow control + NormSetFlowControl(norm_session, 0.0); + } + // Pick a random instance id for now + struct timeval currentTime; + gettimeofday(¤tTime, NULL); + srand(currentTime.tv_usec); // seed random number generator + NormSessionId instanceId = (NormSessionId)rand(); + if (!NormStartSender(norm_session, instanceId, 10*1024*1024, 1400, 16, 4)) + { + fprintf(stderr, "normMsgr error: unable to start NORM sender\n"); + if (receiver) NormStopReceiver(norm_session); + return false; + } + } + is_running = true; + return true; +} // end NormMsgr::Start(); + +// Returns "true" when a complete message has been read +bool NormMsgr::ReadInput() +{ + assert(input_needed); + while (input_needed) + { + size_t readLength; + char* bufferPtr; + if ((NULL == input_msg) || (input_msg->GetIndex() < MSG_HEADER_SIZE)) + { + if (NULL == input_msg) + { + // Allocate a new message/buffer for input + if (NULL == (input_msg = new Message())) + { + perror("normMsgr new Message error"); + Stop(); // fatal out of memory error + return false; + } + } + // need to read msg "length" header + readLength = MSG_HEADER_SIZE - input_msg->GetIndex(); + bufferPtr = input_msg->AccessHeader() + input_msg->GetIndex(); + } + else + { + unsigned int offset = input_msg->GetIndex() - MSG_HEADER_SIZE; + readLength = input_msg->GetSize() - offset; + bufferPtr = input_msg->AccessBuffer() + offset; + } + size_t result = fread(bufferPtr, 1, readLength, input_file); + if (result > 0) + { + input_msg->IncrementIndex(result); + if (result < readLength) + { + // Still need more input + // (wait for next input notification to read more) + return false; + } + else if (MSG_HEADER_SIZE == input_msg->GetIndex()) + { + // We have now read the message size header + // TBD - support other message header formats? + // (for now, assume 2-byte message length header) + uint16_t msgSize ; + memcpy(&msgSize, input_msg->GetHeader(), MSG_HEADER_SIZE); + msgSize = ntohs(msgSize) - MSG_HEADER_SIZE; + if (!input_msg->Init(msgSize)) + { + perror("normMsgr: input message initialization error"); + Stop(); // fatal out of memory error + return false; + } + } + if (input_msg->IsComplete()) // have read complete header and message + { + // We have now read in the complete message + input_needed = false; + } + } + else + { + if (feof(input_file)) + { + // end-of-file reached, TBD - trigger final flushing and wrap-up + fprintf(stderr, "normMsgr: input end-of-file detected ...\n"); + } + else if (ferror(input_file)) + { + switch (errno) + { + case EINTR: + continue; // interupted, try again + case EAGAIN: + // input starved, wait for next notification + break; + default: + perror("normMsgr error reading input"); + break; + } + } + return false; + } + } + return true; +} // end NormMsgr::ReadInput() + +bool NormMsgr::WriteOutput() +{ + while (output_pending) + { + size_t writeLength; + const char* bufferPtr; + if (output_msg->GetIndex() < MSG_HEADER_SIZE) + { + writeLength = MSG_HEADER_SIZE - output_msg->GetIndex(); + bufferPtr = output_msg->GetHeader() + output_msg->GetIndex(); + } + else + { + unsigned int offset = output_msg->GetIndex() - MSG_HEADER_SIZE; + writeLength = output_msg->GetSize() - offset; + bufferPtr = output_msg->GetBuffer() + offset; + } + size_t result = fwrite(bufferPtr, 1, writeLength, output_file); + output_msg->IncrementIndex(result); + if (result < writeLength) + { + if (feof(output_file)) + { + // end-of-file reached, TBD - stop acting as receiver, signal sender we're done? + fprintf(stderr, "normMsgr: output end-of-file detected ...\n"); + } + else if (ferror(input_file)) + { + switch (errno) + { + case EINTR: + continue; // interupted, try again + case EAGAIN: + // input starved, wait for next notification + output_ready = false; + break; + default: + perror("normMsgr error writing output"); + break; + } + } + return false; + } + if (output_msg->IsComplete()) + { + delete output_msg; + output_msg = output_msg_queue.RemoveHead(); + if (NULL != output_msg) + output_msg->ResetIndex(); + else + output_pending = false; + break; + } + } + return true; +} // end NormMsgr::WriteOutput() + +bool NormMsgr::NormTxReady() const +{ + // This returns true if new tx data can be enqueued to NORM + // This is based on the state with respect to prior successful data + // data enqueuing (or stream writing) and NORM_TX_QUEUE_EMPTY or + // NORM_TX_QUEUE_VACANCY notifications (tracked by the "norm_tx_vacancy" variable, + // _and_ (if ack-based flow control is enabled) the norm_tx_queue_count or + // norm_stream_buffer_count status. + if (norm_tx_vacancy) + { + if (norm_tx_queue_count >= norm_tx_queue_max) + return false; // still waiting for ACK + return true; + } + else + { + return false; + } +} // end NormMsgr::NormTxReady() + +bool NormMsgr::SendMessage() +{ + // TBD - call EnqueueMessageObject() or WriteMessageStream() + // depending upon on configured mode + if (EnqueueMessageObject()) + { + // Our buffered message was sent, so reset input indices + // and request next message from input + input_msg_list.Append(*input_msg); + input_msg = NULL; + input_needed = true; + return true; + } + // else will be prompted to retry by NORM event (queue vacancy, watermark completion) + return false; +} // end NormMsgr::SendMessage() + +bool NormMsgr::EnqueueMessageObject() +{ + if (norm_acking) + { + assert(norm_tx_queue_count < norm_tx_queue_max); + if (norm_tx_queue_count >= norm_tx_queue_max) + return false; + } + + // Enqueue the message data for transmission + NormObjectHandle object = NormDataEnqueue(norm_session, input_msg->AccessBuffer(), input_msg->GetSize()); + if (NORM_OBJECT_INVALID == object) + { + // This might happen if a non-acking receiver is present and + // has nacked for the oldest object in the queue even if all + // of our acking receivers have acknowledged it. + norm_tx_vacancy = false; + return false; + } + NormObjectSetUserData(object, input_msg); // so we can remove/delete upon purge + if (norm_acking) + { + // ack-based flow control has been enabled + norm_tx_queue_count++; + if (!norm_tx_watermark_pending && (norm_tx_queue_count >= (norm_tx_queue_max / 2))) + { + NormSetWatermark(norm_session, object); + norm_tx_watermark_pending = true; + } + } + return true; +} // end NormMsgr::EnqueueMessageObject() + +void NormMsgr::HandleNormEvent(const NormEvent& event) +{ + switch (event.type) + { + case NORM_TX_QUEUE_EMPTY: + case NORM_TX_QUEUE_VACANCY: + norm_tx_vacancy = true; + break; + + case NORM_GRTT_UPDATED: + //fprintf(stderr, "new GRTT = %lf\n", NormGetGrttEstimate(norm_session)); + break; + + case NORM_TX_WATERMARK_COMPLETED: + if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session)) + { + //fprintf(stderr, "WATERMARK COMPLETED\n"); + norm_tx_queue_count -= (norm_tx_queue_max / 2); + norm_tx_watermark_pending = false; + } + else + { + // TBD - we could see who didn't ACK and possibly remove them + // from our acking list. For now, we are infinitely + // persistent by resetting watermark ack request + NormResetWatermark(norm_session); + } + break; + + case NORM_TX_OBJECT_PURGED: + { + NormDataDetachData(event.object); + Message* msg = (Message*)NormObjectGetUserData(event.object); + if (NULL != msg) + { + input_msg_list.Remove(*msg); + delete msg; + } + break; + } + case NORM_RX_OBJECT_COMPLETED: + { + char* data = NormDataDetachData(event.object); + if (NULL != data) + { + Message* msg = new Message(data, NormObjectGetSize(event.object)); + if (NULL == msg) + { + perror("normMsgr new receive Message error"); + delete[] data; // TBD - may need to finally implement NormDelete() function! + // TBD Stop() as a fatal out of memory error? + break; + } + if (NULL == output_msg) + { + output_msg = msg; + output_msg->ResetIndex(); + output_pending = true; + } + else + { + output_msg_queue.Append(*msg); + } + } + // else TBD - "termination" info-only object? + break; + } + + default: + break; + } +} // end NormMsgr::HandleNormEvent() + + +void Usage() +{ + fprintf(stderr, "Usage: normMsgr id {send &| recv} [addr [/]][ack [,,...]\n" + " [cc|cce|ccl|rate ][interface ][debug ][trace]\n"); +} +int main(int argc, char* argv[]) +{ + // REQUIRED parameters initiailization + NormNodeId nodeId = NORM_NODE_NONE; + bool send = false; + bool recv = false; + + char sessionAddr[64]; + strcpy(sessionAddr, "224.1.2.3"); + unsigned int sessionPort = 6003; + + NormNodeId ackingNodeList[256]; + unsigned int ackingNodeCount = 0; + + double txRate = 0.0; // used for non-default NORM_FIXED ccMode + NormMsgr::CCMode ccMode = NormMsgr::NORM_CC; + const char* mcastIface = NULL; + + int debugLevel = 0; + bool trace = false; + + // Parse command-line + int i = 1; + while (i < argc) + { + const char* cmd = argv[i++]; + size_t len = strlen(cmd); + if (0 == strncmp(cmd, "send", len)) + { + send = true; + } + else if (0 == strncmp(cmd, "recv", len)) + { + recv = true; + } + else if (0 == strncmp(cmd, "addr", len)) + { + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'addr[/port]' value!\n"); + Usage(); + return -1; + } + const char* addrPtr = argv[i++]; + const char* portPtr = strchr(addrPtr, '/'); + if (NULL == portPtr) + { + strncpy(sessionAddr, addrPtr, 63); + sessionAddr[63] = '\0'; + } + else + { + size_t addrLen = portPtr - addrPtr; + if (addrLen > 63) addrLen = 63; // should issue error message + strncpy(sessionAddr, addrPtr, addrLen); + sessionAddr[addrLen] = '\0'; + portPtr++; + sessionPort = atoi(portPtr); + } + } + else if (0 == strncmp(cmd, "id", len)) + { + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'id' value!\n"); + Usage(); + return -1; + } + nodeId = atoi(argv[i++]); + } + else if (0 == strncmp(cmd, "ack", len)) + { + // comma-delimited acking node id list + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'id' value!\n"); + Usage(); + return -1; + } + const char* alist = argv[i++]; + while ((NULL != alist) && (*alist != '\0')) + { + // TBD - Do we need to skip leading white space? + if (1 != sscanf(alist, "%d", ackingNodeList + ackingNodeCount)) + { + fprintf(stderr, "nodeMsgr error: invalid acking node list!\n"); + Usage(); + return -1; + } + ackingNodeCount++; + alist = strchr(alist, ','); + if (NULL != alist) alist++; // point past comma + } + } + else if (0 == strncmp(cmd, "rate", len)) + { + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'rate' value!\n"); + Usage(); + return -1; + } + if (1 != sscanf(argv[i++], "%lf", &txRate)) + { + fprintf(stderr, "nodeMsgr error: invalid transmit rate!\n"); + Usage(); + return -1; + } + // set fixed-rate operation + ccMode = NormMsgr::NORM_FIXED; + } + else if (0 == strcmp(cmd, "cc")) + { + ccMode = NormMsgr::NORM_CC; + } + else if (0 == strcmp(cmd, "cce")) + { + ccMode = NormMsgr::NORM_CCE; + } + else if (0 == strcmp(cmd, "ccl")) + { + ccMode = NormMsgr::NORM_CCL; + } + else if (0 == strncmp(cmd, "interface", len)) + { + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'interface' !\n"); + Usage(); + return -1; + } + mcastIface = argv[i++]; + } + else if (0 == strncmp(cmd, "debug", len)) + { + if (i >= argc) + { + fprintf(stderr, "nodeMsgr error: missing 'interface' !\n"); + Usage(); + return -1; + } + debugLevel = atoi(argv[i++]); + } + else if (0 == strncmp(cmd, "trace", len)) + { + trace = true; + } + else if (0 == strncmp(cmd, "help", len)) + { + Usage(); + return 0; + } + else + { + fprintf(stderr, "nodeMsgr error: invalid command \"%s\"!\n", cmd); + Usage(); + return -1; + } + } + + if (!send && !recv) + { + fprintf(stderr, "normMsgr error: not configured to send or recv!\n"); + Usage(); + return -1; + } + if (NORM_NODE_NONE == nodeId) + { + fprintf(stderr, "normMsgr error: no local 'id' provided!\n"); + Usage(); + return -1; + } + + // TBD - should provide more error checking of calls + NormInstanceHandle normInstance = NormCreateInstance(); + NormSetDebugLevel(debugLevel); + + NormMsgr normMsgr; + + if (!normMsgr.OpenNormSession(normInstance, sessionAddr, sessionPort, (NormNodeId)nodeId)) + { + fprintf(stderr, "normMsgr error: unable to open NORM session\n"); + NormDestroyInstance(normInstance); + return false; + } + + for (unsigned int i = 0; i < ackingNodeCount; i++) + normMsgr.AddAckingNode(ackingNodeList[i]); + + normMsgr.SetNormCongestionControl(ccMode); + if (NormMsgr::NORM_FIXED == ccMode) + normMsgr.SetNormTxRate(txRate); + if (NULL != mcastIface) + normMsgr.SetNormMulticastInterface(mcastIface); + + if (trace) normMsgr.SetNormMessageTrace(true); + + // TBD - set NORM session parameters + normMsgr.Start(send, recv); + + int normfd = NormGetDescriptor(normInstance); + // Get input/output descriptors and set to non-blocking i/o + int inputfd = fileno(normMsgr.GetInputFile()); + if (-1 == fcntl(inputfd, F_SETFL, fcntl(inputfd, F_GETFL, 0) | O_NONBLOCK)) + perror("normMsgr: fcntl(inputfd, O_NONBLOCK) error"); + int outputfd = fileno(normMsgr.GetOutputFile()); + if (-1 == fcntl(outputfd, F_SETFL, fcntl(outputfd, F_GETFL, 0) | O_NONBLOCK)) + perror("normMsgr: fcntl(outputfd, O_NONBLOCK) error"); + fd_set fdsetInput, fdsetOutput; + FD_ZERO(&fdsetInput); + FD_ZERO(&fdsetOutput); + while (normMsgr.IsRunning()) + { + int maxfd = normfd; + FD_SET(normfd, &fdsetInput); + if (normMsgr.InputNeeded()) + { + //fprintf(stderr, "NEED INPUT ...\n"); + FD_SET(inputfd, &fdsetInput); + if (inputfd > maxfd) + maxfd = inputfd; + } + else + { + FD_CLR(inputfd, &fdsetInput); + } + int result; + if (normMsgr.OutputPending() && !normMsgr.OutputReady()) + { + FD_SET(outputfd, &fdsetOutput); + if (outputfd > maxfd) maxfd = outputfd; + result = select(maxfd+1, &fdsetInput, &fdsetOutput, NULL, NULL); + } + else + { + FD_CLR(outputfd, &fdsetOutput); + result = select(maxfd+1, &fdsetInput, NULL, NULL, NULL); + } + switch (result) + { + case -1: + switch (errno) + { + case EINTR: + case EAGAIN: + continue; + default: + perror("normMsgr select() error"); + // TBD - stop NormMsgr + break; + } + break; + case 0: + // shouldn't occur for now (no timeout) + continue; + default: + if (FD_ISSET(inputfd, &fdsetInput)) + { + normMsgr.ReadInput(); + } + if (FD_ISSET(normfd, &fdsetInput)) + { + NormEvent event; + if (NormGetNextEvent(normInstance, &event)) + normMsgr.HandleNormEvent(event); + } + if (FD_ISSET(outputfd, &fdsetOutput)) + { + normMsgr.SetOutputReady(); + } + break; + } + // As a result of reading input or NORM notification events, + // we may be ready to send a message if it's been read + if (normMsgr.InputMessageReady() && normMsgr.NormTxReady()) + normMsgr.SendMessage(); + // and/or output a received message if we need + if (normMsgr.OutputPending() && normMsgr.OutputReady()) + normMsgr.WriteOutput(); // TBD - implement output async i/o notification as needed + + } // end while(normMsgr.IsRunning() + NormDestroyInstance(normInstance); +} // end main() + +NormMsgr::Message::Message() + : msg_size(0), msg_index(0), msg_buffer(NULL), + prev(NULL), next(NULL) +{ +} + +NormMsgr::Message::Message(char* buffer, unsigned int size) + : msg_size(size), msg_index(size+MSG_HEADER_SIZE), msg_buffer(buffer), + prev(NULL), next(NULL) +{ + assert(2 == MSG_HEADER_SIZE); + uint16_t msgSize = size + MSG_HEADER_SIZE; + msgSize = htons(msgSize); + memcpy(msg_header, &msgSize, MSG_HEADER_SIZE); +} + +NormMsgr::Message::~Message() +{ + // in future we may need to use NormDelete() + // to delete msg_buffer if it came from NORM + if (NULL != msg_buffer) + { + delete[] msg_buffer; + msg_buffer = NULL; + } +} + +bool NormMsgr::Message::Init(unsigned int size) +{ + if ((NULL != msg_buffer) && (size != msg_size)) + { + delete[] msg_buffer; + msg_buffer = NULL; + } + if (NULL == msg_buffer) + msg_buffer = new char[size]; + if (NULL != msg_buffer) + { + assert(2 == MSG_HEADER_SIZE); + uint16_t msgSize = size + MSG_HEADER_SIZE; + msgSize = htons(msgSize); + memcpy(msg_header, &msgSize, MSG_HEADER_SIZE); + msg_index = MSG_HEADER_SIZE; // empty message + msg_size = size; + return true; + } + else + { + msg_index = msg_size = 0; + return false; + } +} // end NormMsgr::Message::Init() + +NormMsgr::MessageQueue::MessageQueue() + : head(NULL), tail(NULL) +{ +} + +NormMsgr::MessageQueue::~MessageQueue() +{ +} + +void NormMsgr::MessageQueue::Destroy() +{ + while (NULL != head) + { + Message* msg = RemoveHead(); + delete msg; + } +} // end NormMsgr::MessageQueue::Destroy() + +void NormMsgr::MessageQueue::Prepend(Message& msg) +{ + msg.prev = NULL; + if (NULL != head) + head->prev = &msg; + else + tail = &msg; + msg.next = head; + head = &msg; +} // end NormMsgr::MessageQueue::Prepend() + +void NormMsgr::MessageQueue::Append(Message& msg) +{ + msg.next = NULL; + if (NULL != tail) + tail->next = &msg; + else + head = &msg; + msg.prev = tail; + tail = &msg; +} // end NormMsgr::MessageQueue::Append() + +void NormMsgr::MessageQueue::Remove(Message& msg) +{ + if (NULL == msg.prev) + head = msg.next; + else + msg.prev->next = msg.next; + if (NULL == msg.next) + tail = msg.prev; + else + msg.next->prev = msg.prev; + msg.prev = msg.next = NULL; +} // end NormMsgr::MessageQueue::Remove() + +NormMsgr::Message* NormMsgr::MessageQueue::RemoveHead() +{ + Message* msg = head; + if (NULL != head) Remove(*head); + return msg; +} // end NormMsgr::MessageQueue::RemoveHead() + +NormMsgr::Message* NormMsgr::MessageQueue::RemoveTail() +{ + Message* msg = tail; + if (NULL != tail) Remove(*tail); + return msg; +} // end NormMsgr::MessageQueue::RemoveTail() diff --git a/examples/normStreamRecv.cpp b/examples/normStreamRecv.cpp index 6995f89..8fe95ca 100644 --- a/examples/normStreamRecv.cpp +++ b/examples/normStreamRecv.cpp @@ -42,7 +42,7 @@ int main(int argc, char* argv[]) // 2) Create a NormSession using default "automatic" local node id NormSessionHandle session = NormCreateSession(instance, - "127.0.0.1", + "224.1.2.3", 6003, NORM_NODE_ANY); @@ -51,7 +51,7 @@ int main(int argc, char* argv[]) // (Need to include "protolib/common/protoDebug.h" for this NormSetDebugLevel(3); // Uncomment to turn on debug NORM message tracing - //NormSetMessageTrace(session, true); + NormSetMessageTrace(session, true); // Uncomment to write debug output to file "normLog.txt" //NormOpenDebugLog(instance, "normLog.txt"); @@ -129,7 +129,6 @@ int main(int argc, char* argv[]) } case NORM_RX_OBJECT_UPDATED: { - //fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_UPDATED event ...\n"); if (theEvent.object != stream) { @@ -176,11 +175,12 @@ int main(int argc, char* argv[]) msgSync = false; continue; // try to re-sync and read again } + fprintf(stderr, "read %u bytes from stream ...\n", numBytes); msgIndex += numBytes; if (msgIndex == msgLen) { // Complete message read - //fprintf(stderr, "normStreamRecv msg: %s\n", msgBuffer+2); + fprintf(stderr, "normStreamRecv msg: %s\n", msgBuffer+2); msgLen = msgIndex = 0; // reset state variables for next message } else diff --git a/examples/normStreamSend.cpp b/examples/normStreamSend.cpp index b385f15..73ab113 100644 --- a/examples/normStreamSend.cpp +++ b/examples/normStreamSend.cpp @@ -1,5 +1,5 @@ /****************************************************************************** - Simple NORM_OBJECT_DATA sender example app using the NORM API + Simple NORM_OBJECT_STREAM sender example app using the NORM API USAGE: @@ -17,9 +17,7 @@ // Notes: -// 1) A single file is sent. -// 2) The program exits upon NORM_TX_FLUSH_COMPLETED notification (or user ) -// 3) NORM receiver should be started first (before sender starts) +// 1) A series of text messages are sent over a stream #include "normApi.h" // for NORM API @@ -33,10 +31,11 @@ int main(int argc, char* argv[]) { // 0) Default parameter values - const unsigned int MSG_LENGTH_MIN = 1400; - const unsigned int MSG_LENGTH_MAX = 1400; + const int MSG_COUNT_MAX = 10; + const unsigned int MSG_LENGTH_MIN = 40; + const unsigned int MSG_LENGTH_MAX = 40; UINT32 streamBufferSize = 4*1024*1024; // 1 Mbyte stream buffer size - double normRate = 1.0e+07; // 1 Mbps default NORM tx rate for fixed rate operation (bits/sec units here) + double normRate = 1.0e+07; // 10 Mbps default NORM tx rate for fixed rate operation (bits/sec units here) double msgRate = -1.0; //1e+06; // 32 kbits/sec default message rate // 1) Create a NORM API "NormInstance" @@ -45,7 +44,7 @@ int main(int argc, char* argv[]) // 2) Create a NormSession using default "automatic" local node id (based on IP addr) // TBD - add an option to set a specific NormNodeId NormSessionHandle session = NormCreateSession(instance, - "loon", //"224.1.2.3", + "224.1.2.3", 6003, 1);//NORM_NODE_ANY); @@ -53,7 +52,7 @@ int main(int argc, char* argv[]) // (not necessary for normal app use) NormSetDebugLevel(3); // Uncomment to turn on debug NORM message tracing - //NormSetMessageTrace(session, true); + NormSetMessageTrace(session, true); // Uncomment to turn on some random packet loss //NormSetTxLoss(session, 25.0); // 25% packet loss for testing purposes @@ -177,6 +176,10 @@ int main(int argc, char* argv[]) int result = select(normfd+1, &fdset, NULL, NULL, timeoutPtr); + bool keepSending = true; + if ((MSG_COUNT_MAX > 0) && (msgCount >= (unsigned int)MSG_COUNT_MAX)) + keepSending = false; + if (result > 0) { // Get and handle NORM API event @@ -194,7 +197,7 @@ int main(int argc, char* argv[]) else fprintf(stderr, "normStreamSend: NORM_TX_QUEUE_EMPTY event ...\n"); */ - if (!vacancy) + if (keepSending && (bytesWritten < msgLen)) { // Finish writing remaining pending message content (as much as can be written) bytesWritten += NormStreamWrite(stream, msgData + bytesWritten, msgLen - bytesWritten); @@ -219,7 +222,7 @@ int main(int argc, char* argv[]) break; case NORM_GRTT_UPDATED: - fprintf(stderr, "normStreamRecv: NORM_GRTT_UPDATED event ...\n"); + fprintf(stderr, "normStreamSend: NORM_GRTT_UPDATED event ...\n"); break; default: @@ -234,6 +237,7 @@ int main(int argc, char* argv[]) break; } + // This code writes _new_ message(s) to the stream _if_ there is "vacancy" // and it is time based on "msgRate" and how much time has passed since "lastTime" struct timeval currentTime; @@ -244,7 +248,7 @@ int main(int argc, char* argv[]) else timeDelta -= 1.0e-06 * (lastTime.tv_usec - currentTime.tv_usec); timeAccumulator += timeDelta; - while (vacancy && (timeAccumulator > delayTime)) + while (keepSending && vacancy && (timeAccumulator > delayTime)) { timeAccumulator -= delayTime; // subtract last message tx duration from accumulator // Fill buffer with new message "data" text character (a-z) @@ -270,6 +274,12 @@ int main(int argc, char* argv[]) NormStreamMarkEom(stream); msgCount++; delayTime = (msgRate > 0.0) ? ((double)msgLen / msgRate) : 0.0; + if ((MSG_COUNT_MAX > 0) && ((unsigned int)msgCount >= MSG_COUNT_MAX)) + { + fprintf(stderr, "closing stream after %u messages ...\n", msgCount); + NormStreamClose(stream, true); // gracefully close stream + keepSending = false; + } } } if (timeAccumulator <= delayTime) @@ -279,6 +289,7 @@ int main(int argc, char* argv[]) } lastTime = currentTime; + } // end while (keepGoing) NormStopSender(session); diff --git a/examples/python/normMsgr.py b/examples/python/normMsgr.py new file mode 100644 index 0000000..4ee6305 --- /dev/null +++ b/examples/python/normMsgr.py @@ -0,0 +1,396 @@ + +from threading import Thread, Lock +import sys +import random +import pynorm +import signal +import time +from collections import deque + +MSG_HDR_SIZE = 2 + +class InputThread(Thread): + """This thread reads 'messages" from STDIN and send them""" + + def __init__(self, parent, *args, **kwargs): + super(InputThread, self).__init__(*args, **kwargs) + self.setDaemon(True) ;# this is "child" daemon thread + self.msgr = parent + + def run(self): + while True: + try: + msgHdr = bytearray(sys.stdin.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) + except: + sys.stderr.write("normMsgr: input thread end-of-file 2 ...\n") + return + msgr.sendMessage(msgBuffer) ;# will block if NORM not "tx ready" + +class OutputThread(Thread): + """This thread writes received 'messages" to STDOUT.""" + + def __init__(self, parent, *args, **kwargs): + super(OutputThread, self).__init__(*args, **kwargs) + self.setDaemon(True) ;# this is "child" daemon thread + self.msgr = parent + + def run(self): + while True: + msg = msgr.getRxMsg() ;# will block if none ready + msgLen = len(msg) + MSG_HDR_SIZE + msgHeader = bytearray(MSG_HDR_SIZE) + msgHeader[0] = (msgLen >> 8) & 0x00ff + msgHeader[1] = msgLen & 0x00ff + sys.stdout.write(msgHeader) + sys.stdout.write(msg) + del msg + + +class NormMsgr: + """This class keeps state for NORM tx/rx operations""" + + def __init__(self): + self.normInstance = pynorm.Instance() + self.normSession = None + # Sender state members + self.normTxLock = Lock() ;# for thread-safe access to NORM tx state variables + self.normTxReady = Lock() + self.norm_tx_vacancy = True + self.norm_tx_queue_count = 0 + self.norm_tx_queue_max = 2048 + self.norm_tx_watermark_pending = False + self.norm_acking = False + self.tx_msg_cache = {} + # Receiver state members + self.normRxLock = Lock() + self.normRxReady = Lock() + self.normRxReady.acquire() ;# no rx messages yet + self.output_msg_queue = deque() + random.seed(None) ;# seeds with current time + + def openNormSession(self, addr, port, nodeId): + # 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.setDefaultUnicastNack(True); + self.normSession.setTxCacheBounds(10*1024*1024, self.norm_tx_queue_max, self.norm_tx_queue_max); + self.normSession.setCongestionControl(True, True); + return self.normSession + + def addAckingNode(self, nodeId): + self.normSession.addAckingNode(nodeId); + self.norm_acking = True + + def setNormMulticastInterface(self, ifaceName): + self.normSession.setMulticastInterface(ifaceName) + + def setNormCCMode(self, ccMode): + if ccMode == "cc": + self.normSession.setEcnSupport(False, False, False) + elif ccMode == "cce": + self.normSession.setEcnSupport(True, True) + elif ccMode == "ccl": + self.normSession.setEcnSupport(False, False, True) + elif ccMode == "fixed": + self.normSession.setEcnSupport(False, False, False) + else: + raise Exception("normMsgr: invalid ccMode \"%s\"" % ccMode) + if ccMode != "fixed": + self.normSession.setCongestionControl(True) + else: + self.normSession.setCongetstionControl(False) + + def setNormTxRate(self, bitsPerSecond): + self.normSession.setTxRate(bitsPerSecond) + + def setNormDebugLevel(self, level): + self.normInstance.setDebugLevel(level) + + def setNormMessageTrace(self, state): + self.normSession.setMessageTrace(state) + + def start(self, send, recv): + if (recv): + self.normSession.startReceiver(10*1024*1024) + if (send): + if (self.norm_acking): + self.normSession.setFlowControl(0.0) + # We use a random sender instanceId in case of stop/restart + instanceId = random.randint(0, 0xffff) + self.normSession.startSender(instanceId, 10*1024*1024, 1400, 16, 4); + + def stop(self): + del self.normInstance + self.normInstance = None + + def sendMessage(self, msgBuf): + # caller will be blocked if NORM is (or becomes) not "tx ready" + while not self.enqueueMessageObject(msgBuf): + #sys.stderr.write("enqueue message was blocked\n") + continue + + def enqueueMessageObject(self, msgBuf): + self.normTxReady.acquire() ;# this blocks until NORM is "tx ready" + with self.normTxLock: + #sys.stderr.write("normMsgr: sending %d byte message payload ...\n" % len(msgBuf)) + obj = self.normSession.dataEnqueue(msgBuf) + if obj is None: + self.norm_tx_vacancy = False ;# will be cleared by NORM_TX_QUEUE_EMPTY, etc + return False + # cache the sent msgBuf until NORM_TX_OBJECT_PURGED + self.tx_msg_cache[obj] = msgBuf + if (self.norm_acking): + # Manage ack-based flow control state + self.norm_tx_queue_count += 1 + if not self.norm_tx_watermark_pending: + if self.norm_tx_queue_count >= self.norm_tx_queue_max/2: + #sys.stderr.write("setting watermark ...\n") + self.normSession.setWatermark(obj) + #sys.stderr.write("watermark set.\n"); + self.norm_tx_watermark_pending = True + if self.norm_tx_queue_count >= self.norm_tx_queue_max: + # Don't release "normTxReady" since cache is filled + # (Will be released upon NORM_TX_WATERMARK_COMPLETED) + return True + self.normTxReady.release() + return True + + def onNormTxObjectPurged(self, obj): + with self.normTxLock: + if pynorm.NORM_OBJECT_DATA == obj.getType(): + del self.tx_msg_cache[obj] + + def onNormTxQueueVacancy(self): + with self.normTxLock: + wasTxReady = self.norm_tx_vacancy + if wasTxReady and self.norm_acking: + wasTxReady = self.norm_tx_queue_count < self.norm_tx_queue_max + self.norm_tx_vacancy = True + if self.norm_acking: + isTxReady = self.norm_tx_queue_count < self.norm_tx_queue_max + else: + isTxReady = False + if isTxReady and not wasTxReady: + if self.normTxReady.acquire(False): + sys.stderr.write("normMsgr onNormTxQueueVacancy() warning: normTxReady wasn't locked?!\n") + #sys.stderr.write("tx vacancy releasing norm tx ready ...\n"); + self.normTxReady.release() + + def onNormTxWatermarkCompleted(self): + with self.normTxLock: + wasTxReady = self.norm_tx_vacancy + if wasTxReady and self.norm_acking: + wasTxReady = self.norm_tx_queue_count < self.norm_tx_queue_max + self.norm_tx_watermark_pending = False + self.norm_tx_queue_count -= self.norm_tx_queue_max / 2 + isTxReady = self.norm_tx_vacancy + if isTxReady and self.norm_acking: + isTxReady = self.norm_tx_queue_count < self.norm_tx_queue_max + else: + isTxReady = False + if isTxReady and not wasTxReady: + if self.normTxReady.acquire(False): + sys.stderr.write("normMsgr onNormTxWatermarkCompleted() warning: normTxReady wasn't locked?!\n") + #sys.stderr.write("watermark completion releasing norm tx ready ...\n") + self.normTxReady.release() + + def onNormRxObjectCompleted(self, obj): + with self.normRxLock: + if pynorm.NORM_OBJECT_DATA == obj.getType(): + if 0 != len(self.output_msg_queue): + wasEmpty = False + else: + wasEmpty = True + msg = obj.getData() + self.output_msg_queue.append(msg) + if wasEmpty: + #sys.stderr.write("releasing normRxReady ...\n") + self.normRxReady.release() ;# unblocks waiting OutputThread + + def getRxMsg(self): + self.normRxReady.acquire() ;# blocks if output_msg_queue is empty + with self.normRxLock: + msg = self.output_msg_queue.popleft() + if 0 != len(self.output_msg_queue): + self.normRxReady.release() ;# not empty yet + return msg + + def getNextNormEvent(self): + if self.normInstance is None: + return None + else: + return self.normInstance.getNextEvent() + +class NormEventHandler(Thread): + """This thread calls normInstance.getNextEvent() and handles the events""" + + def __init__(self, parentMsgr, *args, **kwargs): + super(NormEventHandler, self).__init__(*args, **kwargs) + self.setDaemon(True) ;# this is "child" daemon thread + self.lock = Lock() + self.msgr = parentMsgr + + def run(self): + self.lock.acquire() + while True: + try: + event = self.msgr.getNextNormEvent() + except: + sys.stderr.write("get next event exception\n"); + self.lock.release() + return + if event is None: + break + if pynorm.NORM_EVENT_INVALID == event.type: + continue + elif pynorm.NORM_TX_QUEUE_EMPTY == event.type or pynorm.NORM_TX_QUEUE_VACANCY == event.type: + msgr.onNormTxQueueVacancy() + elif pynorm.NORM_TX_WATERMARK_COMPLETED == event.type: + if pynorm.NORM_ACK_SUCCESS == event.session.getAckingStatus(): + # All receivers acknowledged + msgr.onNormTxWatermarkCompleted() + else: + # TBD - we could see who didn't ACK and possibly remove them + # 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: + msgr.onNormTxObjectPurged(event.object) + elif pynorm.NORM_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 thread exiting ...\n"); + self.lock.release() + +def usage(): + sys.stderr.write("Usage: normMsgr.py id {send &| recv} [addr [/]][ack [,,...]\n" + + " [cc|cce|ccl|rate ][interface ][debug ][trace]\n") + +# Default parameters +nodeId = None +sessionAddr = "224.1.2.3" +sessionPort = 6003 +send = False +recv = False +ccMode = "cc" +txRate = None +ackerList = [] +debugLevel = 3 +normTrace = False +mcastIface = None + +# Parse command-line +cmd = None +val = None +try: + i = 1 + while i < len(sys.argv): + cmd = sys.argv[i] + i += 1 + if "id" == cmd: + val = sys.argv[i] + nodeId = int(val) + i += 1 + elif "addr" == cmd: + val = sys.argv[i] + i += 1 + if "/" in val: + field = val.split('/') + sessionAddr = field[0] + sessionPort = int(field[1]) + else: + sessionAddr = val + elif "send" == cmd: + send = True + elif "recv" == cmd: + recv = True + elif "cc" == cmd: + ccMode = "cc" + elif "cce" == cmd: + ccMode = "cce" + elif "ccl" == cmd: + ccMode = "ccl" + elif "rate" == cmd: + val = sys.argv[i] + rxRate = float(val) + ccMode = "fixed" + i += 1 + elif "ack" == cmd: + alist = sys.argv[i].split(',') + for val in alist: + ackerList.append(int(val)) + elif "debug" == cmd: + val = sys.argv[i] + debugLevel = int(val) + i += 1 + elif "trace" == cmd: + normTrace = True + else: + sys.stderr.write("normMsgr error: invalid command \"%s\"\n" % cmd) +except Exception as e: + sys.stderr.write("normMsgr \"" + cmd + " " + val + "\" argument error: " + e.__str__() + "\n") + usage() + sys.exit(-1) + +if not send and not recv: + sys.stderr.write("normMsgr error: not configured to send or recv!\n") + usage() + sys.exit(-1) + +if nodeId is None: + sys.stderr.write("normMsgr error: no local 'id' provided!\n") + usage() + sys.exit(-1) + +# Instantiate a NormMsgr and set its parameters +msgr = NormMsgr() +msgr.setNormDebugLevel(debugLevel) +sys.stderr.write("normMsgr: opening norm session ...\n") +msgr.openNormSession(sessionAddr, sessionPort, nodeId) +if mcastIface: + msgr.setNormMulticastInterface(mcastIface) +for node in ackerList: + msgr.addAckingNode(node) +msgr.setNormCCMode(ccMode); +if "fixed" == ccMode: + msgr.setNormTxRate(txRate) +msgr.setNormMessageTrace(normTrace) +msgr.start(send, recv) + +sys.stderr.write("normMsgr: starting NormEventHandler ...\n") +normEventHandler = NormEventHandler(msgr) +normEventHandler.start() + +if send: + sys.stderr.write("normMsgr: starting input thread ...\n") + inputThread = InputThread(msgr) + inputThread.start() + +if recv: + sys.stderr.write("normMsgr: starting output thread ...\n") + outputThread = OutputThread(msgr) + outputThread.start() + +# The main thread just sits on a loop that sleeps and wakes up +# once in a while. We could have made any of the other threads +# the main loop if we had wanted. Since all the other threads +# were child "daemons", they will get killed when this main exits +# TBD - provide for a graceful/clean sender/receiver termination +try: + sys.stderr.write("normMsgr: running (use Crtl-C to exit) ...\n") + while True: + time.sleep(5) + #sys.stderr.write("woke up ...\n") +except KeyboardInterrupt: + #sys.stderr.write("exception while waiting on input thread ..\n"); + pass + +sys.stderr.write("normMsgr: Done.\n") diff --git a/include/normApi.h b/include/normApi.h index 81e79ff..7708f66 100644 --- a/include/normApi.h +++ b/include/normApi.h @@ -58,6 +58,10 @@ typedef uint32_t UINT32; extern "C" { #endif /* __cplusplus */ +#define NORM_VERSION_MAJOR 1 +#define NORM_VERSION_MINOR 0 +#define NORM_VERSION_PATCH 0 + /** NORM API Types */ typedef const void* NormInstanceHandle; extern NORM_API_LINKAGE @@ -143,7 +147,8 @@ typedef enum NormProbingMode NORM_API_LINKAGE typedef enum NormSyncPolicy { - NORM_SYNC_CURRENT, // attempt to receiver current/new objects only + NORM_SYNC_CURRENT, // attempt to receiver current/new objects only, join mid-stream + NORM_SYNC_STREAM, // sync to current stream, but to beginning of stream NORM_SYNC_ALL // attempt to receive old and new objects } NormSyncPolicy; @@ -170,7 +175,7 @@ typedef enum NormEventType NORM_REMOTE_SENDER_NEW, NORM_REMOTE_SENDER_ACTIVE, NORM_REMOTE_SENDER_INACTIVE, - NORM_REMOTE_SENDER_PURGED, + NORM_REMOTE_SENDER_PURGED, // not yet implemented NORM_RX_CMD_NEW, NORM_RX_OBJECT_NEW, NORM_RX_OBJECT_INFO, @@ -192,6 +197,12 @@ typedef struct /** NORM API General Initialization and Operation Functions */ + +NORM_API_LINKAGE +int NormGetVersion(int* major DEFAULT((int*)0), + int* minor DEFAULT((int*)0), + int* patch DEFAULT((int*)0)); + NORM_API_LINKAGE NormInstanceHandle NormCreateInstance(bool priorityBoost DEFAULT(false)); @@ -252,6 +263,9 @@ NormSessionHandle NormCreateSession(NormInstanceHandle instanceHandle, NORM_API_LINKAGE void NormDestroySession(NormSessionHandle sessionHandle); +NORM_API_LINKAGE +bool NormIsUnicastAddress(const char* address); + NORM_API_LINKAGE void NormSetUserData(NormSessionHandle sessionHandle, const void* userData); @@ -358,6 +372,9 @@ double NormGetReportInterval(NormSessionHandle sessionHandle); /** NORM Sender Functions */ +NORM_API_LINKAGE +NormSessionId NormGetRandomSessionId(); + NORM_API_LINKAGE bool NormStartSender(NormSessionHandle sessionHandle, NormSessionId instanceId, @@ -457,10 +474,19 @@ NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle, const char* infoPtr DEFAULT((const char*)0), unsigned int infoLen DEFAULT(0)); +NORM_API_LINKAGE +void NormObjectSetUserData(NormObjectHandle objectHandle, const void* userData); + +NORM_API_LINKAGE +const void* NormObjectGetUserData(NormObjectHandle objectHandle); + // TBD - we should add a "bool watermark" option to "graceful" stream closure??? NORM_API_LINKAGE void NormStreamClose(NormObjectHandle streamHandle, bool graceful DEFAULT(false)); +NORM_API_LINKAGE +unsigned int NormGetStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize); + NORM_API_LINKAGE unsigned int NormStreamWrite(NormObjectHandle streamHandle, const char* buffer, @@ -508,8 +534,8 @@ void NormSetAutoAckingNodes(NormSessionHandle sessionHandle, NormTrackingStatus trackingStatus); NORM_API_LINKAGE -NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, - NormNodeId nodeId DEFAULT(NORM_NODE_ANY)); +NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, + NormNodeId nodeId DEFAULT(NORM_NODE_ANY)); NORM_API_LINKAGE bool NormGetNextAckingNode(NormSessionHandle sessionHandle, diff --git a/include/normMessage.h b/include/normMessage.h index 67be134..26bcf5d 100644 --- a/include/normMessage.h +++ b/include/normMessage.h @@ -110,23 +110,27 @@ class NormObjectSize { public: #ifdef WIN32 +#define _FILE_OFFSET_BITS 64 typedef __int64 Offset; #else typedef off_t Offset; -#endif +#endif // if/else WIN32 NormObjectSize() : size(0) {} NormObjectSize(Offset theSize) : size(theSize) {} NormObjectSize(UINT16 msb, UINT32 lsb) { size = (Offset)lsb; - size |= (sizeof(Offset) > 4) ? (((Offset)msb) << 32): (Offset)0; +#if _FILE_OFFSET_BITS > 32 + size |= ((Offset)msb) << 32; +#endif } Offset GetOffset() const {return size;} - UINT16 MSB() const - {return ((sizeof(Offset) > 4) ? (UINT16)((size >> 32) & 0x0000ffff) : 0);} - UINT32 LSB() const - {return ((UINT32)(size & 0xffffffff));} - +#if _FILE_OFFSET_BITS > 32 + UINT16 MSB() const {return ((UINT16)((size >> 32) & 0x0000ffff));} +#else + UINT16 MSB() const {return 0;} +#endif + UINT32 LSB() const {return ((UINT32)(size & 0xffffffff));} // Operators bool operator==(const NormObjectSize& b) const {return (b.size == size);} diff --git a/include/normNode.h b/include/normNode.h index 7fa08f6..511d2f2 100644 --- a/include/normNode.h +++ b/include/normNode.h @@ -263,8 +263,9 @@ class NormSenderNode : public NormNode enum SyncPolicy { - SYNC_CURRENT, // sync to detect transmit point, iff NORM_DATA from first FEC block - SYNC_ALL // permiscuously sync as far back as possible + SYNC_CURRENT, // sync to detect transmit point, iff NORM_DATA from first FEC block unless stream + SYNC_STREAM, // same as SYNC_CURRENT, but attempts to recover stream block zero + SYNC_ALL // permiscuously sync as far back as possible given rx cache size }; NormSenderNode(class NormSession& theSession, NormNodeId nodeId); diff --git a/include/normObject.h b/include/normObject.h index 40e5e79..c1dbfbf 100644 --- a/include/normObject.h +++ b/include/normObject.h @@ -64,6 +64,12 @@ class NormObject class NormSenderNode* GetSender() const {return sender;} NormNodeId GetSenderNodeId() const; + // for NORM API usage only + void SetUserData(const void* userData) + {user_data = userData;} + const void* GetUserData() const + {return user_data;} + bool IsOpen() {return (0 != segment_size);} // Opens (inits) object for tx operation bool Open(const NormObjectSize& objectSize, @@ -291,6 +297,8 @@ class NormObject bool accepted; bool notify_on_update; + const void* user_data; // for NORM API usage only + NormObject* next; }; // end class NormObject @@ -616,7 +624,6 @@ class NormObjectTable const NormObjectTable& table; bool reset; NormObjectId index; - bool backwards; }; private: diff --git a/include/normSegment.h b/include/normSegment.h index 35f2e48..ff1a6f0 100644 --- a/include/normSegment.h +++ b/include/normSegment.h @@ -19,7 +19,7 @@ class NormSegmentPool void Put(char* segment) { ASSERT(seg_count < seg_total); - *((char**)segment) = seg_list; // this might make a warning on Solaris + *((char**)((void*)segment)) = seg_list; // this might make a warning on Solaris seg_list = segment; seg_count++; } diff --git a/include/normSession.h b/include/normSession.h index f0d0714..d031e5e 100644 --- a/include/normSession.h +++ b/include/normSession.h @@ -303,8 +303,10 @@ class NormSession bool SendMessage(NormMsg& msg); void ActivateTimer(ProtoTimer& timer) {session_mgr.ActivateTimer(timer);} - void SetUserData(const void* userData) {user_data = userData;} - const void* GetUserData() {return user_data;} + void SetUserData(const void* userData) + {user_data = userData;} + const void* GetUserData() const + {return user_data;} // Sender methods void SenderSetBaseObjectId(NormObjectId baseId) @@ -335,7 +337,7 @@ class NormSession bool RequeueTxObject(NormObject* obj); - void DeleteTxObject(NormObject* obj); + void DeleteTxObject(NormObject* obj, bool notify); NormObject* SenderFindTxObject(NormObjectId objectId) {return tx_table.Find(objectId);} diff --git a/include/normSimAgent.h b/include/normSimAgent.h index 173b5e6..a3d6869 100644 --- a/include/normSimAgent.h +++ b/include/normSimAgent.h @@ -31,6 +31,12 @@ class NormSimAgent : public NormController, public ProtoMessageSink NORM_CCE, // strict ECN-based congestion control NORM_CCL // "loss-tolerant" congestion control }; + + // These functions support ACK-based flow-controlled streaming using + // some additional state variables (stream_buffer_max, stream_buffer_count, etc) + static unsigned int ComputeStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize); + unsigned int WriteToStream(const char* buffer, unsigned int numBytes); + bool AddAckingNode(NormNodeId nodeId); protected: NormSimAgent(ProtoTimerMgr& timerMgr, @@ -49,7 +55,7 @@ class NormSimAgent : public NormController, public ProtoMessageSink private: void OnInputReady(); - bool FlushStream(); + bool FlushStream(bool eom);// = true); virtual void Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, @@ -107,6 +113,12 @@ class NormSimAgent : public NormController, public ProtoMessageSink NormStreamObject* stream; bool auto_stream; + unsigned int stream_buffer_max; + unsigned int stream_buffer_count; + unsigned int stream_bytes_remain; + bool watermark_pending; + bool flow_control; + bool push_mode; NormStreamObject::FlushMode flush_mode; char* tx_msg_buffer; diff --git a/include/normVersion.h b/include/normVersion.h index 923bce8..fa86254 100644 --- a/include/normVersion.h +++ b/include/normVersion.h @@ -30,8 +30,12 @@ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ********************************************************************/ +// This defines the NORM source code version +// The real code library version is given by NORM_VERSION +// in "normApi.h" + #ifndef _NORM_VERSION #define _NORM_VERSION -#define VERSION "1.5b1" +#define VERSION "1.5b2" #endif // _NORM_VERSION diff --git a/makefiles/Makefile.common b/makefiles/Makefile.common index f9a6374..a53bffd 100644 --- a/makefiles/Makefile.common +++ b/makefiles/Makefile.common @@ -168,7 +168,14 @@ normThreadTest2: $(TTEST2_OBJ) libnorm.a $(LIBPROTO) mkdir -p ../bin cp $@ ../bin/$@ +# (normMsgr) message sender/receiver +MSGR_SRC = $(EXAMPLE)/normMsgr.cpp +MSGR_OBJ = $(MSGR_SRC:.cpp=.o) +normMsgr: $(MSGR_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(MSGR_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ # (pcap2norm) - parses pcap (e.g. tcpdump) file and prints NORM trace PCAP_SRC = $(COMMON)/pcap2norm.cpp diff --git a/makefiles/android/jni/Android.mk b/makefiles/android/jni/Android.mk index 5895d8c..6332712 100644 --- a/makefiles/android/jni/Android.mk +++ b/makefiles/android/jni/Android.mk @@ -52,4 +52,12 @@ LOCAL_SRC_FILES := \ ../../../src/unix/unixPostProcess.cpp include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := normMsgr +LOCAL_STATIC_LIBRARIES := norm +LOCAL_SRC_FILES := \ + ../../../examples/normMsgr.cpp +include $(BUILD_EXECUTABLE) + $(call import-module,protolib/makefiles/android/jni) diff --git a/makefiles/android/jni/Application.mk b/makefiles/android/jni/Application.mk index b448d58..1737164 100644 --- a/makefiles/android/jni/Application.mk +++ b/makefiles/android/jni/Application.mk @@ -1,2 +1,3 @@ APP_ABI := all APP_PLATFORM := android-9 +NDK_MODULE_PATH := ../..:../../protolib/ diff --git a/makefiles/android/project.properties b/makefiles/android/project.properties index 616f300..5ca7d62 100644 --- a/makefiles/android/project.properties +++ b/makefiles/android/project.properties @@ -9,4 +9,4 @@ android.library=true # Project target. -target=android-16 +target=android-14 diff --git a/makefiles/java/Makefile.common b/makefiles/java/Makefile.common index be0feaf..0d512d3 100644 --- a/makefiles/java/Makefile.common +++ b/makefiles/java/Makefile.common @@ -35,6 +35,7 @@ LIB_OBJ = $(LIB_SRC:.cpp=.o) LIB_DEP = $(LIB_SRC:.cpp=.d) all: libmil_navy_nrl_norm.$(SYSTEM_SOEXT) + # NORM JNI shared library libmil_navy_nrl_norm.$(SYSTEM_SOEXT): $(LIB_OBJ) $(LIBNORM) $(LIBPROTO) @@ -51,7 +52,7 @@ $(LIBPROTO): -include $(LIB_DEP) %.d: %.cpp - $(CC) -MM -MT $(@:.d=.o) -MF $@ $(CPPFLAGS) $< + $(CC) -MM -MT $(@:.d=.o) -MF $@ $(CFLAGS) $< clean: rm -f $(NORMJNI)/*.o \ diff --git a/makefiles/java/Makefile.macosx b/makefiles/java/Makefile.macosx new file mode 100644 index 0000000..9044e98 --- /dev/null +++ b/makefiles/java/Makefile.macosx @@ -0,0 +1,17 @@ +# +# MacOSX NORM-JNI Makefile definitions +# + +# System specific additional libraries, include paths, etc +SYSTEM_INCLUDES = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/darwin +SYSTEM_LIBS = +SYSTEM_SRC = + +SYSTEM = macosx + +CC = g++ +SYSTEM_CFLAGS = -Wall -Wcast-align -fPIC -arch x86_64 -arch i386 +SYSTEM_SOFLAGS = -dynamiclib +SYSTEM_SOEXT = dylib + +include Makefile.common diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6bc73d2 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +# This Python script can be used to install the Python 'pynorm' package +import platform +from distutils.core import setup + +# Note to use 'pynorm", you will need to have the libnorm shared library +# (libnorm.so or libnorm.dylib, etc) installed where your Python installation +# will find it with dlopen() (e.g. /usr/local/lib or something) + + +setup(name='pynorm', + version = '1.0', + packages = ['pynorm', 'pynorm.extra'], + package_dir={'' : 'src'}) diff --git a/src/common/normApi.cpp b/src/common/normApi.cpp index 43e6b2d..ff53b56 100644 --- a/src/common/normApi.cpp +++ b/src/common/normApi.cpp @@ -640,7 +640,7 @@ void NormInstance::Shutdown() notify_event = NULL; } #else - if (notify_fd[0] > 0) + if (notify_fd[0] >= 0) { close(notify_fd[0]); // close read end of pipe close(notify_fd[1]); // close write end of pipe @@ -715,6 +715,15 @@ UINT32 NormInstance::CountCompletedObjects(NormSession* session) // NORM API FUNCTION IMPLEMENTATIONS // +NORM_API_LINKAGE +int NormGetVersion(int* major, int* minor, int* patch) +{ + if (NULL != major) *major = NORM_VERSION_MAJOR; + if (NULL != minor) *minor = NORM_VERSION_MINOR; + if (NULL != patch) *patch = NORM_VERSION_PATCH; + return NORM_VERSION_MAJOR; +} // end NormGetVersion() + /** NORM API Initialization */ NORM_API_LINKAGE @@ -818,6 +827,7 @@ bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent, bo instance->dispatcher.ResumeThread(); if (!instance->WaitForEvent()) { + // Indication that NormInstance is dead return false; } // re-suspend thread after wait @@ -828,10 +838,23 @@ bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent, bo instance->dispatcher.ResumeThread(); } } - return result; + // We do this so we only return "false" when NormInstance is dead + if (!result && (NULL != theEvent)) + theEvent->type = NORM_EVENT_INVALID; + return true; } // end NormGetNextEvent() +NORM_API_LINKAGE +bool NormIsUnicastAddress(const char* address) +{ + ProtoAddress addr; + if ((NULL != address) && addr.ResolveFromString(address)) + return addr.IsUnicast(); + else + return false; // not valid, so not unicast +} // end NormIsUnicast() + /** NORM Session Creation and Control Functions */ NORM_API_LINKAGE NormSessionHandle NormCreateSession(NormInstanceHandle instanceHandle, @@ -1179,6 +1202,7 @@ void NormCloseDebugPipe(NormInstanceHandle instanceHandle) NORM_API_LINKAGE void NormSetDebugLevel(unsigned int level) { + // Sets underlying Protolib debug leve SetDebugLevel(level); } @@ -1218,6 +1242,15 @@ double NormGetReportInterval(NormSessionHandle sessionHandle) /** NORM Sender Functions */ +NORM_API_LINKAGE +NormSessionId NormGetRandomSessionId() +{ + ProtoTime currentTime; + currentTime.GetCurrentTime(); + srand(currentTime.usec()); // seed random number generator + return (NormSessionId)rand(); +} // end NormGetRandomSessionId() + NORM_API_LINKAGE bool NormStartSender(NormSessionHandle sessionHandle, NormSessionId sessionId, @@ -1576,6 +1609,15 @@ void NormStreamClose(NormObjectHandle streamHandle, bool graceful) } } // end NormStreamClose() +NORM_API_LINKAGE +unsigned int NormGetStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize) +{ + // This same computation is performed in NormStreamObject::Open() in "normObject.cpp" + unsigned int numBlocks = bufferBytes / (blockSize * segmentSize); + if (numBlocks < 2) numBlocks = 2; // NORM enforces a 2-block minimum buffer size + return (numBlocks * blockSize); +} // end NormGetStreamBufferSegmentCount() + NORM_API_LINKAGE unsigned int NormStreamWrite(NormObjectHandle streamHandle, const char* buffer, @@ -1704,16 +1746,18 @@ bool NormSetWatermark(NormSessionHandle sessionHandle, NORM_API_LINKAGE bool NormResetWatermark(NormSessionHandle sessionHandle) { - bool result = false; NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; session->SenderResetWatermark(); - result = true; instance->dispatcher.ResumeThread(); + return true; + } + else + { + return false; } - return true; } // end NormResetWatermark() NORM_API_LINKAGE @@ -1874,7 +1918,6 @@ bool NormStartReceiver(NormSessionHandle sessionHandle, NORM_API_LINKAGE void NormStopReceiver(NormSessionHandle sessionHandle) { - NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (instance && instance->dispatcher.SuspendThread()) { @@ -2165,13 +2208,34 @@ void NormObjectCancel(NormObjectHandle objectHandle) if (sender) sender->DeleteObject(obj); else - obj->GetSession().DeleteTxObject(obj); + obj->GetSession().DeleteTxObject(obj, false); instance->PurgeObjectNotifications(objectHandle); instance->dispatcher.ResumeThread(); } } } // end NormObjectCancel() +NORM_API_LINKAGE +void NormObjectSetUserData(NormObjectHandle objectHandle, const void* userData) +{ + NormInstance* instance = NormInstance::GetInstanceFromObject(objectHandle); + if (instance) + { + if (instance->dispatcher.SuspendThread()) + { + ((NormObject*)objectHandle)->SetUserData(userData); + instance->dispatcher.ResumeThread(); + } + } +} // end NormObjectSetUserData() + +NORM_API_LINKAGE +const void* NormObjectGetUserData(NormObjectHandle objectHandle) +{ + return (((NormObject*)objectHandle)->GetUserData()); +} // end NormNormObjectGetUserData() + + NORM_API_LINKAGE void NormObjectRetain(NormObjectHandle objectHandle) { @@ -2409,6 +2473,10 @@ void NormNodeRelease(NormNodeHandle nodeHandle) // is done so that this current approach is not very costly) // + +// Not sure why this function exists. It just counts the number +// of RX_OBJECT_COMPLETED events currently in notification queue +// which is a temporary and transitory thing??? NORM_API_LINKAGE UINT32 NormCountCompletedObjects(NormSessionHandle sessionHandle) { diff --git a/src/common/normNode.cpp b/src/common/normNode.cpp index 0daf7d2..c15b2c4 100644 --- a/src/common/normNode.cpp +++ b/src/common/normNode.cpp @@ -334,7 +334,7 @@ bool NormSenderNode::AllocateBuffers(UINT8 fecId, { if (NULL == (decoder = new NormDecoderRS8)) { - PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderMDP error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString()); Close(); return false; } @@ -959,7 +959,7 @@ void NormSenderNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) bool freshBlock = true; NormBlockId prevBlockId = 0; NormBlock* block = NULL; - while ((requestLength = req.Unpack(buffer, bufferLen))) + while (0 != (requestLength = req.Unpack(buffer, bufferLen))) { // Point "buffer" to next request and adjust "bufferLen" buffer += (requestLength/4); @@ -1081,8 +1081,7 @@ void NormSenderNode::CalculateGrttResponse(const struct timeval& currentTime, if (grttResponse.tv_usec < grtt_recv_time.tv_usec) { grttResponse.tv_sec = grttResponse.tv_sec - grtt_recv_time.tv_sec - 1; - grttResponse.tv_usec = 1000000 - (grtt_recv_time.tv_usec - - grttResponse.tv_usec); + grttResponse.tv_usec = 1000000 - (grtt_recv_time.tv_usec - grttResponse.tv_usec); } else { @@ -1511,9 +1510,15 @@ void NormSenderNode::HandleObjectMessage(const NormObjectMsg& msg) session.Notify(NormController::RX_OBJECT_NEW, this, obj); if (obj->Accepted()) { - // (TBD) Do I _need_ to call "StreamUpdateStatus()" here? if (obj->IsStream()) - (static_cast(obj))->StreamUpdateStatus(blockId); + { + // This initial "StreamUpdateStatus()" syncs the stream according to our sync policy + NormStreamObject* stream = static_cast(obj); + if (SYNC_CURRENT != sync_policy) + stream->StreamResync(NormBlockId(0)); + else + stream->StreamUpdateStatus(blockId); + } PLOG(PL_DETAIL, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu new obj>%hu\n", LocalNodeId(), GetId(), (UINT16)objectId); } @@ -1597,6 +1602,7 @@ bool NormSenderNode::SyncTest(const NormObjectMsg& msg) const switch (sync_policy) { case SYNC_CURRENT: // default, more conservative "sync policy" + case SYNC_STREAM: { // Allow sync on stream at any time bool result = msg.FlagIsSet(NormObjectMsg::FLAG_STREAM); @@ -1697,6 +1703,7 @@ void NormSenderNode::Sync(NormObjectId objectId) switch (sync_policy) { case SYNC_CURRENT: // this is the usual default + case SYNC_STREAM: sync_id = next_id = max_pending_object = objectId; break; case SYNC_ALL: // gratuitously sync for anything in our "range" @@ -2299,7 +2306,7 @@ void NormSenderNode::UpdateRecvRate(const struct timeval& currentTime, unsigned if (prev_update_time.tv_sec || prev_update_time.tv_usec) { double interval = (double)(currentTime.tv_sec - prev_update_time.tv_sec); - if (currentTime.tv_usec > prev_update_time.tv_sec) + if (currentTime.tv_sec > prev_update_time.tv_sec) interval += 1.0e-06*(double)(currentTime.tv_usec - prev_update_time.tv_usec); else interval -= 1.0e-06*(double)(prev_update_time.tv_usec - currentTime.tv_usec); @@ -3218,7 +3225,7 @@ bool NormLossEstimator2::Update(const struct timeval& currentTime, event_time = event_time_orig = currentTime; newLossEvent = true; - TRACE("time>%lf new loss event fraction = %lf\n", ProtoTime(currentTime).GetValue(), LossFraction()); + //TRACE("time>%lf new loss event fraction = %lf\n", ProtoTime(currentTime).GetValue(), LossFraction()); } } diff --git a/src/common/normObject.cpp b/src/common/normObject.cpp index fd2d85e..3c2141f 100644 --- a/src/common/normObject.cpp +++ b/src/common/normObject.cpp @@ -15,7 +15,8 @@ NormObject::NormObject(NormObject::Type theType, transport_id(transportId), segment_size(0), pending_info(false), repair_info(false), current_block_id(0), next_segment_id(0), max_pending_block(0), max_pending_segment(0), - info_ptr(NULL), info_len(0), first_pass(true), accepted(false), notify_on_update(true) + info_ptr(NULL), info_len(0), first_pass(true), accepted(false), notify_on_update(true), + user_data(NULL), next(NULL) { if (theSender) { @@ -158,11 +159,14 @@ bool NormObject::Open(const NormObjectSize& objectSize, { small_block_size = large_block_size = numData; small_block_count = large_block_count = numBlocks.LSB(); - final_segment_size = segmentSize; - NormStreamObject* stream = static_cast(this); - // The call here to "StreamResync()" make us attempt to repair back to object start. - // TBD - we may only want to do this for the SYNC_ALL sync policy? - stream->StreamResync(NormBlockId(0)); + final_segment_size = segmentSize; + if (NULL == sender) + { + // This inits sender stream state to set things tx pending + // (the receiver stream is sync'd according to sync policy) + NormStreamObject* stream = static_cast(this); + stream->StreamResync(NormBlockId(0)); + } } else { @@ -2594,7 +2598,7 @@ bool NormStreamObject::Open(UINT32 bufferSize, UINT16 segmentSize, numData; if (sender) { - // receive streams have already be pre-opened + // receive streams have already beeen pre-opened segmentSize = segment_size; numData = ndata; } @@ -2634,6 +2638,7 @@ bool NormStreamObject::Open(UINT32 bufferSize, // (TBD) we really only need one set of indexes & offset // since our objects are exclusively read _or_ write read_init = true; + read_index.block = read_index.segment = 0; write_index.block = write_index.segment = 0; tx_index.block = tx_index.segment = 0; @@ -2682,7 +2687,6 @@ void NormStreamObject::Close(bool graceful) { if (graceful && (NULL == sender)) { - stream_closing = true; Terminate(); //SetFlushMode(FLUSH_ACTIVE); //Flush(); @@ -2818,6 +2822,19 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) stream_sync = true; stream_sync_id = blockId; stream_next_id = blockId + pending_mask.GetSize(); + + if (read_init) + { + // This is a fresh rx stream, so init the read indices + // TBD - we may wish to do this here only for + // a "NORM_SYNC_STREAM" sync policy (beginning of current stream) + // This is a "fresh" stream + read_init = false; + read_index.block = blockId; + read_index.segment = 0; + read_offset = 0; + } + // Since we're doing a resync including "read_init", dump any buffered data // (TBD) this may not be necessary??? and is thus currently commented-out code /*read_init = true; @@ -3232,7 +3249,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek else if (session.RcvrIsLowDelay()) { // Has the sender moved forward to the next FEC blocks - long delta = max_pending_block - read_index.block; + INT32 delta = max_pending_block - read_index.block; if (delta > session.RcvrGetMaxDelay()) forceForward = true; } @@ -3281,7 +3298,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek else if (session.RcvrIsLowDelay()) { // Has the sender moved forward to the next FEC blocks - long delta = max_pending_block - read_index.block; + INT32 delta = max_pending_block - read_index.block; if (delta > session.RcvrGetMaxDelay()) forceForward = true; } @@ -3504,8 +3521,9 @@ void NormStreamObject::Terminate() { // Flush stream and create a ZERO length segment to send Flush(); // should eom be set for this call? + stream_closing = true; NormBlock* block = stream_buffer.Find(write_index.block); - if (!block) + if (NULL == block) { if (NULL == (block = block_pool.Get())) { @@ -3572,9 +3590,9 @@ void NormStreamObject::Terminate() } else { - // Make sure the segment is not still referenced - ASSERT(0 == NormDataMsg::ReadStreamPayloadMsgStart(segment)); - ASSERT(0 == NormDataMsg::ReadStreamPayloadLength(segment)); + // Make sure the segment is not still referenced (TBD - why was I checking this???) + //ASSERT(0 == NormDataMsg::ReadStreamPayloadMsgStart(segment)); + //ASSERT(0 == NormDataMsg::ReadStreamPayloadLength(segment)); } NormDataMsg::WriteStreamPayloadOffset(segment, write_offset); @@ -3600,7 +3618,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) PLOG(PL_ERROR, "NormStreamObject::Write() error: stream is closing (len:%lu eom:%d)\n", len, eom); len = 0; } - break; + break; } // This old code detected buffer "fullness" by offset instead of segment index // but, the problem there was when apps wrote & flushed messages smaller than @@ -3884,8 +3902,7 @@ void NormObjectTable::SetRangeMax(UINT16 rangeMax) NormSession& session = obj->GetSession(); if (NULL == sender) { - session.Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, obj); - session.DeleteTxObject(obj); + session.DeleteTxObject(obj, true); } else { diff --git a/src/common/normSegment.cpp b/src/common/normSegment.cpp index 41b0a6b..b735444 100644 --- a/src/common/normSegment.cpp +++ b/src/common/normSegment.cpp @@ -66,7 +66,7 @@ char* NormSegmentPool::Get() if (ptr) { //memcpy(&seg_list, ptr, sizeof(char*)); - seg_list = *((char**)ptr); + seg_list = *((char**)((void*)ptr)); seg_count--; //#ifdef NORM_DEBUG overrun_flag = false; diff --git a/src/common/normSession.cpp b/src/common/normSession.cpp index 9ddff5a..8c0f723 100644 --- a/src/common/normSession.cpp +++ b/src/common/normSession.cpp @@ -257,7 +257,7 @@ bool NormSession::Open() result &= tx_socket->SetMulticastInterface(interface_name); if (!result) { - PLOG(PL_FATAL, "NormSession::Open() t_socket::SetMulticastInterface() error\n"); + PLOG(PL_FATAL, "NormSession::Open() tx_socket::SetMulticastInterface() error\n"); Close(); return false; } @@ -529,12 +529,12 @@ double NormSession::GetTxRate() } } // end NormSession::GetTxRate() -/* + static double PoissonRand(double mean) { return(-log(((double)rand())/((double)RAND_MAX))*mean); } -*/ + // This hack can give us a tx rate interval that is POISSON instead of PERIODIC static inline double GetTxInterval(unsigned int msgSize, double txRate) @@ -974,7 +974,8 @@ void NormSession::Serve() // If command is enqueued if (SenderQueueAppCmd()) return; } - + + bool watermarkJustCompleted = false; if (watermark_pending && !flush_timer.IsActive()) { // Determine next message (objectId::blockId::segmentId) to be sent @@ -1072,12 +1073,14 @@ void NormSession::Serve() if (watermark_flushes) flush_count = GetTxRobustFactor(); } + watermarkJustCompleted = true; } } else { // The sender tx position is < watermark // Reset non-acked acking nodes since sender has rewound + // TBD - notify application that watermark ack has been reset ??? if (watermark_active) { watermark_active = false; @@ -1089,7 +1092,7 @@ void NormSession::Serve() } } // end if (watermark_pending) - if (obj) + if (NULL != obj) { NormObjectMsg* msg = (NormObjectMsg*)GetMessageFromPool(); if (msg) @@ -1150,24 +1153,33 @@ void NormSession::Serve() { SenderQueueFlush(); } - else if (GetTxRobustFactor() == flush_count) + else if (GetTxRobustFactor() == flush_count) //xxx { - PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender flush complete ...\n", + + PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender stream flush complete ...\n", LocalNodeId()); + Notify(NormController::TX_FLUSH_COMPLETED, (NormSenderNode*)NULL, stream); flush_count++; data_active = false; if (stream->IsClosing()) { - stream->Close(); - Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, stream); - DeleteTxObject(stream); - obj = NULL; + // If the stream just failed end-of-stream watermark flush, we don't + // close the stream yet, but instead give app chance to reset watermark + bool watermarkFailed = watermarkJustCompleted && (obj->GetId() == watermark_object_id) && + (ACK_FAILURE == SenderGetAckingStatus(NORM_NODE_ANY)); + if (!watermarkFailed) + { + // end of stream was successfully acknowledged + stream->Close(); + DeleteTxObject(stream, true); + obj = NULL; + } } } } } //ASSERT(stream->IsPending() || stream->IsRepairPending() || stream->IsClosing()); - if (!posted_tx_queue_empty && !stream->IsClosing() && stream->IsPending()) + if (!posted_tx_queue_empty && !stream->IsClosing() && stream->IsPending()) // post if pending || !repair_timer.IsActive() || (repair_timer.GetRepeatCount() == 0) ??? { //data_active = false; @@ -1204,14 +1216,13 @@ void NormSession::Serve() } else if (GetTxRobustFactor() == flush_count) { - PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender flush complete ...\n", - LocalNodeId()); + PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender flush complete ...\n", LocalNodeId()); Notify(NormController::TX_FLUSH_COMPLETED, (NormSenderNode*)NULL, (NormObject*)NULL); flush_count++; data_active = false; - } + } } } // end NormSession::Serve() @@ -1401,7 +1412,7 @@ bool NormSession::SenderQueueWatermarkFlush() watermark_pending = false; NormAckingNode* nodeNone = NULL; acking_success_count = 0; - while ((next = static_cast(iterator.GetNextNode()))) + while (NULL != (next = static_cast(iterator.GetNextNode()))) { // Save NORM_NODE_NONE for last if (NORM_NODE_NONE == next->GetId()) @@ -1447,7 +1458,7 @@ bool NormSession::SenderQueueWatermarkFlush() if (watermark_pending) { - // (TBD) we should increment the "flush_count" here only iff the watemark + // (TBD) we should increment the "flush_count" here only iff the watermark // corresponds to our "last_tx_object_id", etc //if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) // flush_count++; @@ -1524,8 +1535,7 @@ void NormSession::SenderQueueFlush() { PLOG(PL_ERROR, "NormSession::SenderQueueFlush() node>%lu message_pool exhausted! (couldn't flush)\n", LocalNodeId()); - } - + } } else { @@ -1769,8 +1779,8 @@ bool NormSession::QueueTxObject(NormObject* obj) double delay = GetFlowControlDelay() - oldest->GetNackAge(); if (delay < 1.0e-06) { - Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, oldest); - DeleteTxObject(oldest); + if (FlowControlIsActive()) DeactivateFlowControl(); + DeleteTxObject(oldest, true); } else { @@ -1828,11 +1838,12 @@ bool NormSession::RequeueTxObject(NormObject* obj) } } // end NormSession::RequeueTxObject() -void NormSession::DeleteTxObject(NormObject* obj) +void NormSession::DeleteTxObject(NormObject* obj, bool notify) { ASSERT(NULL != obj); if (tx_table.Remove(obj)) { + Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, obj); NormObjectId objectId = obj->GetId(); tx_pending_mask.Unset(objectId); tx_repair_mask.Unset(objectId); @@ -1863,8 +1874,7 @@ bool NormSession::SetTxCacheBounds(NormObjectSize sizeMax, // Remove oldest (hopefully non-pending ) object NormObject* oldest = tx_table.Find(tx_table.RangeLo()); ASSERT(NULL != oldest); - Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, oldest); - DeleteTxObject(oldest); + DeleteTxObject(oldest, true); count = tx_table.GetCount(); } if (tx_cache_count_max < DEFAULT_TX_CACHE_MAX) @@ -2110,7 +2120,7 @@ void NormSession::OnPktCapture(ProtoChannel& theChannel, if (numBytes == 0) break; // no more packets to receive // Map ProtoPktETH instance into buffer and init for processing - ProtoPktETH ethPkt((UINT32*)ethBuffer, BUFFER_MAX - 2); + ProtoPktETH ethPkt((UINT32*)((void*)ethBuffer), BUFFER_MAX - 2); if (!ethPkt.InitFromBuffer(numBytes)) { PLOG(PL_ERROR, "NormSession::OnPktCapture() error: bad Ether frame\n"); @@ -2973,9 +2983,13 @@ void NormSession::SenderHandleAckMessage(const struct timeval& currentTime, cons ext.GetCCSequence()); if (wasUnicast && probe_proactive && Address().IsMulticast()) { - // for suppression of unicast cc feedback - advertise_repairs = true; - QueueMessage(NULL); + // if it's the CLR, it doesn't suppress anyone, don't advertise + if (!ext.CCFlagIsSet(NormCC::CLR)) + { + // for suppression of unicast cc feedback + advertise_repairs = true; + QueueMessage(NULL); + } } break; } @@ -3006,27 +3020,46 @@ void NormSession::SenderHandleAckMessage(const struct timeval& currentTime, cons (watermark_segment_id == flushAck.GetFecSymbolId(fec_m))) { acker->MarkAckReceived(); + /* This code was an attempt to expedite delivery of the TX_WATERMARK_COMPLETED + notification to the application, but breaks some other desired behavior. + watermark_pending = false; + acking_success_count = 0; + NormNodeTreeIterator iterator(acking_node_tree); + NormAckingNode* next; + while (NULL != (next = static_cast(iterator.GetNextNode()))) + { + if (next->IsPending()) + watermark_pending = true; + else if (next->AckReceived() || (NORM_NODE_NONE == next->GetId())) + acking_success_count++; + } + if (!watermark_pending) + { + PLOG(PL_DEBUG, "NormSession::SenderHandleAckMessage() node>%lu watermark ack finished.\n"); + Notify(NormController::TX_WATERMARK_COMPLETED, (NormSenderNode*)NULL, (NormObject*)NULL); + } + */ } else { // This can happen when new watermarks are set when an old watermark is still // pending (i.e. receivers may still be in the process of replying) - PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received wrong watermark ACK?!\n"); + PLOG(PL_DEBUG, "NormSession::SenderHandleAckMessage() received wrong watermark ACK?!\n"); } } else { - PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received redundant watermark ACK?!\n"); + PLOG(PL_DEBUG, "NormSession::SenderHandleAckMessage() received redundant watermark ACK?!\n"); } } else { - PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received watermark ACK from unknown acker?!\n"); + PLOG(PL_WARN, "NormSession::SenderHandleAckMessage() received watermark ACK from unknown acker?!\n"); } } else { - PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received unsolicited watermark ACK?!\n"); + PLOG(PL_DEBUG, "NormSession::SenderHandleAckMessage() received unsolicited watermark ACK?!\n"); } break; @@ -3953,7 +3986,7 @@ bool NormSession::SenderBuildRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - PLOG(PL_FATAL, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); + PLOG(PL_WARN, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case prevForm = NormRepairRequest::INVALID; break; @@ -3981,21 +4014,26 @@ bool NormSession::SenderBuildRepairAdv(NormCmdRepairAdvMsg& cmd) currentId, 0, ndata, 0); break; } - cmd.PackRepairRequest(req); + prevForm = NormRepairRequest::INVALID; + if (0 == cmd.PackRepairRequest(req)) + { + PLOG(PL_WARN, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); + // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case + break; + } objectCount = 0; } if (!repairEntireObject) { if ((NormRepairRequest::INVALID != prevForm) && currentObject->IsRepairPending()) { + prevForm = NormRepairRequest::INVALID; if (0 == cmd.PackRepairRequest(req)) { - PLOG(PL_FATAL, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); + PLOG(PL_WARN, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case - prevForm = NormRepairRequest::INVALID; break; } - prevForm = NormRepairRequest::INVALID; currentObject->AppendRepairAdv(cmd); } else @@ -4167,7 +4205,6 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) tx_timer.Deactivate(); tx_socket->StartOutputNotification(); return false; - } } else @@ -4177,7 +4214,8 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) if (message_queue.IsEmpty()) { - tx_timer.Deactivate(); + if (tx_timer.IsActive()) + tx_timer.Deactivate(); // Check that any possible notifications posted in // the previous call to Serve() may have caused a // change in sender state making it ready to send diff --git a/src/common/normSimAgent.cpp b/src/common/normSimAgent.cpp index a726407..98976e1 100644 --- a/src/common/normSimAgent.cpp +++ b/src/common/normSimAgent.cpp @@ -25,8 +25,10 @@ NormSimAgent::NormSimAgent(ProtoTimerMgr& timerMgr, tx_object_size_min(0), tx_object_size_max(0), tx_repeat_count(0), tx_repeat_interval(0.0), tx_requeue(0), tx_requeue_count(0), - stream(NULL), auto_stream(false), push_mode(false), - flush_mode(NormStreamObject::FLUSH_PASSIVE), + stream(NULL), auto_stream(false), + stream_buffer_max(0), stream_buffer_count(0), + stream_bytes_remain(0), watermark_pending(false), + flow_control(false), push_mode(false), flush_mode(NormStreamObject::FLUSH_PASSIVE), msg_sync(false), mgen_bytes(0), mgen_pending_bytes(0), tracing(false), log_file_ptr(NULL), tx_loss(0.0), rx_loss(0.0) { @@ -90,6 +92,7 @@ const char* const NormSimAgent::cmd_list[] = "+push", // "on" means real-time push stream advancement (non-blocking) "+flush", // stream flush mode "-doFlush", // invoke flushing of stream + "+ackingNode", // ; add to acking node list "+unicastNacks", // receivers will unicast feedback "+silentReceiver", // receivers will not transmit NULL @@ -520,7 +523,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { if (session) { - if (stream) + if (NULL != stream) { PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) stream already open!\n"); return false; @@ -545,6 +548,14 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) stream->SetPushMode(push_mode); auto_stream = false; tx_msg_len = tx_msg_index = 0; + if (flow_control) + { + // ACK-based flow control has been enabled, so initialize + stream_buffer_max = ComputeStreamBufferSegmentCount(tx_object_size, segment_size, ndata); + stream_buffer_max -= ndata; // a little safety margin + stream_buffer_count = stream_bytes_remain = 0; + watermark_pending = false; + } } else { @@ -571,7 +582,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { if (session) { - return FlushStream(); + return FlushStream(true); // TBD - provide control over EOM marking here? } else { @@ -592,6 +603,10 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) } return true; } + else if (!strncmp("ackingNode", cmd, len)) + { + return AddAckingNode(atoi(val)); + } else if (!strncmp("unicastNacks", cmd, len)) { if (!strcmp(val, "on")) @@ -623,6 +638,68 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) return true; } // end NormSimAgent::ProcessCommand() +unsigned int NormSimAgent::ComputeStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize) +{ + // This same computation is performed in NormStreamObject::Open() in "normObject.cpp" + unsigned int numBlocks = bufferBytes / (blockSize * segmentSize); + if (numBlocks < 2) numBlocks = 2; // NORM enforces a 2-block minimum buffer size + return (numBlocks * blockSize); +} // end NormSimAgent::ComputeStreamBufferSegmentCount() + +unsigned int NormSimAgent::WriteToStream(const char* buffer, + unsigned int numBytes) +{ + ASSERT(NULL != stream); + if (!flow_control) + { + return stream->Write(buffer, numBytes, false); + } + else if (stream_buffer_count < stream_buffer_max) + { + // This method uses stream->Write(), but limits writes by explicit ACK-based flow control status + // 1) How many buffer bytes are available? + unsigned int bytesAvailable = segment_size * (stream_buffer_max - stream_buffer_count); + bytesAvailable -= stream_bytes_remain; // unflushed segment portiomn + if (numBytes <= bytesAvailable) + { + unsigned int totalBytes = numBytes + stream_bytes_remain; + unsigned int numSegments = totalBytes / segment_size; + stream_bytes_remain = totalBytes % segment_size; + stream_buffer_count += numSegments; + } + else + { + numBytes = bytesAvailable; + stream_buffer_count = stream_buffer_max; + } + // 2) Write to the stream + unsigned int bytesWritten = stream->Write(buffer, numBytes, false); + + ASSERT(bytesWritten == numBytes); // this could fail if timer-based flow control is left enabled + // We had a "stream is closing" case here somehow? Need to make sure we don't try + // to write to norm stream + + // 3) Do we need to issue a watermark ACK request? + if (!watermark_pending && (stream_buffer_count >= (stream_buffer_max >> 1))) + { + //TRACE("NormSimAgent::WriteToStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n", + // stream_buffer_count, stream_buffer_max, NormStreamGetBufferUsage(tx_stream)); + session->SenderSetWatermark(stream->GetId(), + stream->FlushBlockId(), + stream->FlushSegmentId(), + false); + watermark_pending = true; + } + return bytesWritten; + } + else + { + PLOG(PL_DETAIL, "NormSimAgent::WriteToStream() is blocked pending acknowledgment from receiver\n"); + return 0; + } +} // end NormSimAgent::WriteToStream() + + void NormSimAgent::SetCCMode(NormCC ccMode) { cc_mode = ccMode; @@ -693,15 +770,15 @@ bool NormSimAgent::SendMessage(unsigned int len, const char* txBuffer) { if (session) { - if (!stream) + if (NULL == stream) { - if(!(tx_msg_buffer = new char[65536])) + if(NULL == (tx_msg_buffer = new char[65536])) { PLOG(PL_FATAL, "NormSimAgent::SendMessage() error allocating tx_msg_buffer: %s\n", strerror(errno)); return false; } - if (!(stream = session->QueueTxStream(tx_object_size))) + if (NULL == (stream = session->QueueTxStream(tx_object_size))) { PLOG(PL_FATAL, "NormSimAgent::SendMessage() error opening stream!\n"); return false; @@ -736,27 +813,64 @@ bool NormSimAgent::SendMessage(unsigned int len, const char* txBuffer) void NormSimAgent::OnInputReady() { - if (tx_msg_index < tx_msg_len) + if (auto_stream) { - unsigned int bytesWrote = stream->Write(tx_msg_buffer+tx_msg_index, - tx_msg_len - tx_msg_index, - false); + // sending a dummy byte stream (fill the stream buffer up) + char buffer[NormMsg::MAX_SIZE]; + bool inputReady = true; + while (inputReady) + { + unsigned int numBytes = WriteToStream(buffer, segment_size); + inputReady = (numBytes == segment_size); + } + } + else if (tx_msg_index < tx_msg_len) + { + unsigned int bytesWrote; + if (flow_control) + { + bytesWrote = WriteToStream(tx_msg_buffer+tx_msg_index, + tx_msg_len - tx_msg_index); + } + else + { + bytesWrote = WriteToStream(tx_msg_buffer+tx_msg_index, + tx_msg_len - tx_msg_index); + } tx_msg_index += bytesWrote; if (tx_msg_index == tx_msg_len) { - // Mark EOM _and_ flush (using set flush mode) - stream->Write(NULL, 0, true); + // Mark EOM _and_ flush (using preset flush mode) + FlushStream(true); tx_msg_index = tx_msg_len = 0; } } -} // end NormSimAgent::InputReady() +} // end NormSimAgent::OnInputReady() // Do an _active_ flush of the stream (for use at end-of-transmission) -bool NormSimAgent::FlushStream() +bool NormSimAgent::FlushStream(bool eom) { if (stream && session && session->IsSender()) { - stream->Flush(true); + stream->Flush(eom); + if (flow_control && (NormStreamObject::FLUSH_ACTIVE == flush_mode)) + { + if (0 != stream_bytes_remain) + { + stream_buffer_count++; + stream_bytes_remain = 0; + if (!watermark_pending && (stream_buffer_count >= (stream_buffer_max >> 1))) + { + //TRACE("NormSimAgent::FlushStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n", + // stream_buffer_count, stream_buffer_max, NormStreamGetBufferUsage(tx_stream)); + session->SenderSetWatermark(stream->GetId(), + stream->FlushBlockId(), + stream->FlushSegmentId(), + false); + watermark_pending = true; + } + } + } return true; } else @@ -780,22 +894,8 @@ void NormSimAgent::Notify(NormController::Event event, // Can queue a new object or write to stream for transmission if ((NULL != object) && (object == stream)) { - if (auto_stream) - { - // sending a dummy byte stream (fill the stream buffer up) - char buffer[NormMsg::MAX_SIZE]; - bool inputReady = true; - while (inputReady) - { - unsigned int numBytes = stream->Write(buffer, segment_size, false); - inputReady = (numBytes == segment_size); - } - } - else - { - // Stream starved, ask for input from "source" ? - OnInputReady(); - } + // Stream starved, get input + OnInputReady(); } else { @@ -807,9 +907,29 @@ void NormSimAgent::Notify(NormController::Event event, } break; - case TX_OBJECT_SENT: - if ((0 != tx_requeue) && (object != stream)) - { + case TX_WATERMARK_COMPLETED: + if (flow_control) + { + if (NormSession::ACK_SUCCESS == session->SenderGetAckingStatus(NORM_NODE_ANY)) + { + // if we've been blocking normal input, unblock it and prompt for more + // (we could post a vacancy notification here? + watermark_pending = false; + bool wasBlocked = stream_buffer_count >= stream_buffer_max; + stream_buffer_count -= (stream_buffer_max >> 1); + if (wasBlocked) OnInputReady(); + } + else + { + // reset watermark request + session->SenderResetWatermark(); + } + } + break; + + case TX_OBJECT_SENT: + if ((0 != tx_requeue) && (object != stream)) + { if (0 != tx_requeue_count) { if (session->RequeueTxObject(object)) @@ -829,8 +949,8 @@ void NormSimAgent::Notify(NormController::Event event, // reset "tx_requeue_count" for next tx object tx_requeue_count = tx_requeue; } - } - break; + } + break; case RX_OBJECT_NEW: { @@ -890,147 +1010,147 @@ void NormSimAgent::Notify(NormController::Event event, case RX_OBJECT_INFO: switch(object->GetType()) { - case NormObject::FILE: - case NormObject::DATA: - case NormObject::STREAM: - default: - break; + case NormObject::FILE: + case NormObject::DATA: + case NormObject::STREAM: + default: + break; } // end switch(object->GetType()) break; case RX_OBJECT_UPDATED: switch (object->GetType()) { - case NormObject::FILE: - // (TBD) update progress - break; - - case NormObject::STREAM: - { - // Read the stream when it's updated - if (msg_sink) - { - bool dataReady = true; - while (dataReady) - { - if (0 == mgen_pending_bytes) - { - // Reading (at least part of) 2 byte MGEN msg len header - unsigned int want = 2 - mgen_bytes; - unsigned int got = want; - bool findMsgSync = msg_sync ? false : true; - if ((static_cast(object))->Read(mgen_buffer + mgen_bytes, - &got, findMsgSync)) + case NormObject::FILE: + // (TBD) update progress + break; + + case NormObject::STREAM: + { + // Read the stream when it's updated + if (msg_sink) + { + bool dataReady = true; + while (dataReady) + { + if (0 == mgen_pending_bytes) { - mgen_bytes += got; - msg_sync = true; - if (got != want) dataReady = false; + // Reading (at least part of) 2 byte MGEN msg len header + unsigned int want = 2 - mgen_bytes; + unsigned int got = want; + bool findMsgSync = msg_sync ? false : true; + if ((static_cast(object))->Read(mgen_buffer + mgen_bytes, + &got, findMsgSync)) + { + mgen_bytes += got; + msg_sync = true; + if (got != want) dataReady = false; + } + else + { + if (msg_sync) + { + PLOG(PL_WARN, "NormSimAgent::Notify(1) detected stream break\n"); + //ASSERT(0); + mgen_bytes = mgen_pending_bytes = 0; + msg_sync = false; + continue; + } + else + { + break; + } + } + if (2 == mgen_bytes) + { + UINT16 msgSize; + memcpy(&msgSize, mgen_buffer, sizeof(UINT16)); + mgen_pending_bytes = ntohs(msgSize) - 2; + } + } + if (mgen_pending_bytes) + { + // Save the first part for MGEN logging + if (mgen_bytes < 64) + { + unsigned int want = MIN(mgen_pending_bytes, 62); + unsigned int got = want; + if ((static_cast(object))->Read(mgen_buffer+mgen_bytes, + &got)) + { + mgen_pending_bytes -= got; + mgen_bytes += got; + if (got != want) dataReady = false; + } + else + { + PLOG(PL_WARN, "NormSimAgent::Notify(2) detected stream break\n"); + mgen_bytes = mgen_pending_bytes = 0; + msg_sync = false; + continue; + } + } + while (dataReady && mgen_pending_bytes) + { + char buffer[256]; + unsigned int want = MIN(256, mgen_pending_bytes); + unsigned int got = want; + if ((static_cast(object))->Read(buffer, &got)) + { + mgen_pending_bytes -= got; + mgen_bytes += got; + if (got != want) dataReady = false; + } + else + { + PLOG(PL_WARN, "NormSimAgent::Notify(3) detected stream break\n"); + mgen_bytes = mgen_pending_bytes = 0; + msg_sync = false; + break; + } + } + if (msg_sync && (0 == mgen_pending_bytes)) + { + ProtoAddress srcAddr; + srcAddr.ResolveFromString(sender->GetAddress().GetHostString()); + srcAddr.SetPort(sender->GetAddress().GetPort()); + msg_sink->HandleMessage(mgen_buffer,mgen_bytes,srcAddr); + mgen_bytes = 0; + } + } // end if (mgen_pending_bytes) + } // end while(dataReady) + } + else + { + // Simply read and discard the byte stream for sim purposes + char buffer[1024]; + unsigned int want = 1024; + unsigned int got = want; + while (1) + { + if ((static_cast(object))->Read(buffer, &got)) + { + // Break when data is no longer available + if (got != want) break; } else { - if (msg_sync) - { - PLOG(PL_WARN, "NormSimAgent::Notify(1) detected stream break\n"); - //ASSERT(0); - mgen_bytes = mgen_pending_bytes = 0; - msg_sync = false; - continue; - } - else - { - break; - } - } - if (2 == mgen_bytes) - { - UINT16 msgSize; - memcpy(&msgSize, mgen_buffer, sizeof(UINT16)); - mgen_pending_bytes = ntohs(msgSize) - 2; + PLOG(PL_WARN, "NormSimAgent::Notify() detected stream break\n"); } + got = want = 1024; } - if (mgen_pending_bytes) - { - // Save the first part for MGEN logging - if (mgen_bytes < 64) - { - unsigned int want = MIN(mgen_pending_bytes, 62); - unsigned int got = want; - if ((static_cast(object))->Read(mgen_buffer+mgen_bytes, - &got)) - { - mgen_pending_bytes -= got; - mgen_bytes += got; - if (got != want) dataReady = false; - } - else - { - PLOG(PL_WARN, "NormSimAgent::Notify(2) detected stream break\n"); - mgen_bytes = mgen_pending_bytes = 0; - msg_sync = false; - continue; - } - } - while (dataReady && mgen_pending_bytes) - { - char buffer[256]; - unsigned int want = MIN(256, mgen_pending_bytes); - unsigned int got = want; - if ((static_cast(object))->Read(buffer, &got)) - { - mgen_pending_bytes -= got; - mgen_bytes += got; - if (got != want) dataReady = false; - } - else - { - PLOG(PL_WARN, "NormSimAgent::Notify(3) detected stream break\n"); - mgen_bytes = mgen_pending_bytes = 0; - msg_sync = false; - break; - } - } - if (msg_sync && (0 == mgen_pending_bytes)) - { - ProtoAddress srcAddr; - srcAddr.ResolveFromString(sender->GetAddress().GetHostString()); - srcAddr.SetPort(sender->GetAddress().GetPort()); - msg_sink->HandleMessage(mgen_buffer,mgen_bytes,srcAddr); - mgen_bytes = 0; - } - } // end if (mgen_pending_bytes) - } // end while(dataReady) - } - else - { - // Simply read and discard the byte stream for sim purposes - char buffer[1024]; - unsigned int want = 1024; - unsigned int got = want; - while (1) - { - if ((static_cast(object))->Read(buffer, &got)) - { - // Break when data is no longer available - if (got != want) break; - } - else - { - PLOG(PL_WARN, "NormSimAgent::Notify() detected stream break\n"); - } - got = want = 1024; } + break; } + + case NormObject::DATA: + PLOG(PL_FATAL, "NormSimAgent::Notify() DATA objects not supported...\n"); + ASSERT(0); + break; + + default: + // should never occur break; - } - - case NormObject::DATA: - PLOG(PL_FATAL, "NormSimAgent::Notify() DATA objects not supported...\n"); - ASSERT(0); - break; - - default: - // should never occur - break; } // end switch (object->GetType()) break; case RX_OBJECT_COMPLETED: @@ -1064,7 +1184,6 @@ void NormSimAgent::Notify(NormController::Event event, break; } case RX_OBJECT_ABORTED: - { switch(object->GetType()) { case NormObject::FILE: @@ -1092,10 +1211,10 @@ void NormSimAgent::Notify(NormController::Event event, break; } break; - } - default: - PLOG(PL_DEBUG, "NormSimAgent::Notify() unhandled NormEvent type\n"); + default: + PLOG(PL_DEBUG, "NormSimAgent::Notify() unhandled NormEvent type\n"); + break; } // end switch(event) } // end NormSimAgent::Notify() @@ -1111,7 +1230,6 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) else { // Queue a NORM_OBJECT_SIM as long as there are repeats - if (tx_repeat_count > 0) tx_repeat_count--; if (tx_object_size_min > 0) { tx_object_size = tx_object_size_max - tx_object_size_min; @@ -1121,6 +1239,7 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) NormObject* object = session->QueueTxSim(tx_object_size); if (NULL != object) { + if (tx_repeat_count > 0) tx_repeat_count--; PLOG(PL_DEBUG, "NormSimAgent::OnIntervalTimeout(() Queued file size: %lu bytes\n", tx_object_size); if (NULL != log_file_ptr) { @@ -1136,8 +1255,10 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) } else { - TRACE("NormSimAgent::OnIntervalTimeout() Error queueing tx object.\n"); - PLOG(PL_WARN, "NormSimAgent::OnIntervalTimeout() Error queueing tx object.\n"); + // Note, probably was flow controlled, so we don't decrement the repeat count here and the + // next QUEUE_VACANCY event will trigger another attempt to enqueue + TRACE("NormSimAgent::OnIntervalTimeout() warning: unable to enqueue tx object. (flow control?)\n"); + PLOG(PL_WARN, "NormSimAgent::OnIntervalTimeout() warning: unable to enqueue tx object. (flow control?)\n"); } } interval_timer.SetInterval(tx_object_interval); @@ -1150,17 +1271,27 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) return false; } } // end NormSimAgent::OnIntervalTimeout() - +bool NormSimAgent::AddAckingNode(NormNodeId nodeId) +{ + if (NULL == session) + { + PLOG(PL_FATAL, "NormSimAgent::AddAckingNode() error: sender not started!\n"); + return false; + } + flow_control = true; // assume ACK flow control is desired + return session->SenderAddAckingNode(nodeId); +} // end NormSimAgent::AddAckingNode() + bool NormSimAgent::StartSender() { - if (session) + if (NULL != session) { PLOG(PL_FATAL, "NormSimAgent::StartSender() Error! sender or receiver already started!\n"); return false; } // Validate our session settings - if (!address) + if (NULL == address) { PLOG(PL_FATAL, "NormSimAgent::StartSender() Error! no session address given."); return false; @@ -1168,7 +1299,7 @@ bool NormSimAgent::StartSender() // Create a new session on multicast group/port session = session_mgr.NewSession(address, port, GetAgentId()); - if (session) + if (NULL != session) { // Common session parameters session->SetTxRate(tx_rate); @@ -1203,13 +1334,13 @@ bool NormSimAgent::StartSender() bool NormSimAgent::StartReceiver() { - if (session) + if (NULL != session) { PLOG(PL_FATAL, "NormSimAgent::StartReceiver() Error! sender or receiver already started!\n"); return false; } // Validate our session settings - if (!address) + if (NULL == address) { PLOG(PL_FATAL, "NormSimAgent::StartReceiver() Error! no session address given."); return false; diff --git a/src/common/pcap2norm.cpp b/src/common/pcap2norm.cpp index 538096b..fa2268d 100644 --- a/src/common/pcap2norm.cpp +++ b/src/common/pcap2norm.cpp @@ -317,6 +317,10 @@ void NormTrace2(const struct timeval& currentTime, break; } } + struct timeval sendTime; + cc.GetSendTime(sendTime); + double delay = ProtoTime::Delta(ProtoTime(currentTime), ProtoTime(sendTime)); + PLOG(PL_ALWAYS, "delay>%lf ", delay); break; } default: @@ -328,6 +332,7 @@ void NormTrace2(const struct timeval& currentTime, case NormMsg::ACK: case NormMsg::NACK: { + PLOG(PL_ALWAYS, "%s ", MSG_NAME[msgType]); // look for NormCCFeedback extension NormHeaderExtension ext; while (msg.GetNextExtension(ext)) @@ -335,10 +340,22 @@ void NormTrace2(const struct timeval& currentTime, if (NormHeaderExtension::CC_FEEDBACK == ext.GetType()) { clrFlag = ((NormCCFeedbackExtension&)ext).CCFlagIsSet(NormCC::CLR); + // Print ccRtt (only valid if pcap file is from sender node) + double ccRtt = NormUnquantizeRtt(((NormCCFeedbackExtension&)ext).GetCCRtt()); + PLOG(PL_ALWAYS, "ccRtt:%lf ", ccRtt); break; } } - PLOG(PL_ALWAYS, "%s ", MSG_NAME[msgType]); + // Print locally measured rtt (only valid if pcap file is from sender node) + struct timeval grttResponse; + if (NormMsg::NACK == msgType) + static_cast(msg).GetGrttResponse(grttResponse); + else + static_cast(msg).GetGrttResponse(grttResponse); + + double rtt = ProtoTime::Delta(ProtoTime(currentTime), ProtoTime(grttResponse)); + PLOG(PL_ALWAYS, "rtt:%lf ", rtt); + PLOG(PL_ALWAYS, "len>%hu %s\n", length, clrFlag ? "(CLR)" : ""); if (NormMsg::NACK == msgType) { diff --git a/src/java/jni/normSessionJni.cpp b/src/java/jni/normSessionJni.cpp index fc02bb8..1c7322c 100644 --- a/src/java/jni/normSessionJni.cpp +++ b/src/java/jni/normSessionJni.cpp @@ -69,12 +69,12 @@ JNIEXPORT void JNICALL PKGNAME(NormSession_setRxPortReuse) } JNIEXPORT void JNICALL PKGNAME(NormSession_setEcnSupport) - (JNIEnv *env, jobject obj, jboolean ecnEnable, jboolean ignoreLoss) { + (JNIEnv *env, jobject obj, jboolean ecnEnable, jboolean ignoreLoss, jboolean tolerateLoss) { NormSessionHandle session; session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); - NormSetEcnSupport(session, ecnEnable, ignoreLoss); + NormSetEcnSupport(session, ecnEnable, ignoreLoss, tolerateLoss); } JNIEXPORT void JNICALL PKGNAME(NormSession_setMulticastInterface) @@ -509,6 +509,15 @@ JNIEXPORT void JNICALL PKGNAME(NormSession_cancelWatermark) NormCancelWatermark(session); } +JNIEXPORT void JNICALL PKGNAME(NormSession_resetWatermark) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormResetWatermark(session); +} + JNIEXPORT void JNICALL PKGNAME(NormSession_addAckingNode) (JNIEnv *env, jobject obj, jlong nodeId) { NormSessionHandle session; diff --git a/src/java/jni/normSessionJni.h b/src/java/jni/normSessionJni.h index 0fe1610..e3ea161 100644 --- a/src/java/jni/normSessionJni.h +++ b/src/java/jni/normSessionJni.h @@ -45,7 +45,7 @@ JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setRxPortReuse * Signature: (ZZ)V */ JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setEcnSupport - (JNIEnv *, jobject, jboolean, jboolean); + (JNIEnv *, jobject, jboolean, jboolean, jboolean); /* * Class: mil_navy_nrl_norm_NormSession @@ -319,6 +319,14 @@ JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setWatermark JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_cancelWatermark (JNIEnv *, jobject); +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: cancelWatermark + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_resetWatermark + (JNIEnv *, jobject); + /* * Class: mil_navy_nrl_norm_NormSession * Method: addAckingNode diff --git a/src/java/src/mil/navy/nrl/norm/NormObject.java b/src/java/src/mil/navy/nrl/norm/NormObject.java index 0bd9da8..154f842 100644 --- a/src/java/src/mil/navy/nrl/norm/NormObject.java +++ b/src/java/src/mil/navy/nrl/norm/NormObject.java @@ -49,9 +49,8 @@ public class NormObject { */ public boolean equals(Object obj) { if (obj instanceof NormObject) { - return handle == ((NormObject)obj).handle; + return (handle == ((NormObject)obj).handle); } - return false; } } diff --git a/src/java/src/mil/navy/nrl/norm/NormSession.java b/src/java/src/mil/navy/nrl/norm/NormSession.java index 0ac5f87..d6fdf11 100644 --- a/src/java/src/mil/navy/nrl/norm/NormSession.java +++ b/src/java/src/mil/navy/nrl/norm/NormSession.java @@ -49,16 +49,17 @@ public class NormSession { public void setTxPort(int port) { setTxPort(port, false, null); } - public native void setTxPort(int port, boolean enableReuse, String txAddress); public void setRxPortReuse(boolean enable) { setRxPortReuse(enable, null, null, 0); } - public native void setRxPortReuse(boolean enable, String rxBindAddress, String senderAddress, int senderPort); - public native void setEcnSupport(boolean ecnEnable, boolean ignoreLoss); + public void setEcnSupport(boolean ecnEnable, boolean ignoreLoss) { + setEcnSupport(ecnEnable, ignoreLoss, false); + } + public native void setEcnSupport(boolean ecnEnable, boolean ignoreLoss, boolean tolerateLoss); public native void setMulticastInterface(String interfaceName) throws IOException; @@ -97,6 +98,9 @@ public class NormSession { public native void setTxSocketBuffer(long bufferSize) throws IOException; + public void setCongestionControl(boolean enable) { + setCongestionControl(enable, true); + } public native void setCongestionControl(boolean enable, boolean adjustRate); public native void setTxRateBounds(double rateMin, double rateMax); @@ -156,6 +160,8 @@ public class NormSession { throws IOException; public native void cancelWatermark(); + + public native void resetWatermark(); public native void addAckingNode(long nodeId) throws IOException; diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java b/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java index 62bf19c..20d6c46 100644 --- a/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java +++ b/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java @@ -13,5 +13,6 @@ package mil.navy.nrl.norm.enums; */ public enum NormSyncPolicy { NORM_SYNC_CURRENT, + NORM_SYNC_STREAM, NORM_SYNC_ALL; } diff --git a/src/pynorm/constants.py b/src/pynorm/constants.py index 9308cca..4f24bca 100644 --- a/src/pynorm/constants.py +++ b/src/pynorm/constants.py @@ -36,6 +36,11 @@ NORM_ACK_SUCCESS = 3 NORM_PROBE_NONE = 0 NORM_PROBE_PASSIVE = 1 NORM_PROBE_ACTIVE = 2 + +# enum NormSyncPolicy +NORM_SYNC_CURRENT = 0 +NORM_SYNC_STREAM = 1 +NORM_SYNC_ALL = 2 # enum NormRepairBoundary NORM_BOUNDARY_BLOCK = 0 @@ -50,20 +55,23 @@ NORM_TX_WATERMARK_COMPLETED = 4 NORM_TX_CMD_SENT = 5 NORM_TX_OBJECT_SENT = 6 NORM_TX_OBJECT_PURGED = 7 -NORM_LOCAL_SENDER_CLOSED = 8 -NORM_REMOTE_SENDER_NEW = 9 -NORM_REMOTE_SENDER_ACTIVE = 10 -NORM_REMOTE_SENDER_INACTIVE = 11 -NORM_REMOTE_SENDER_PURGED = 12 -NORM_RX_CMD_NEW = 13 -NORM_RX_OBJECT_NEW = 14 -NORM_RX_OBJECT_INFO = 15 -NORM_RX_OBJECT_UPDATED = 16 -NORM_RX_OBJECT_COMPLETED = 17 -NORM_RX_OBJECT_ABORTED = 18 -NORM_GRTT_UPDATED = 19 -NORM_CC_ACTIVE = 20 -NORM_CC_INACTIVE = 21 +NORM_TX_RATE_CHANGED = 8 +NORM_LOCAL_SENDER_CLOSED = 9 +NORM_REMOTE_SENDER_NEW = 10 +NORM_REMOTE_SENDER_ACTIVE = 11 +NORM_REMOTE_SENDER_INACTIVE = 12 +NORM_REMOTE_SENDER_PURGED = 13 +NORM_RX_CMD_NEW = 14 +NORM_RX_OBJECT_NEW = 15 +NORM_RX_OBJECT_INFO = 16 +NORM_RX_OBJECT_UPDATED = 17 +NORM_RX_OBJECT_COMPLETED = 18 +NORM_RX_OBJECT_ABORTED = 19 +NORM_GRTT_UPDATED = 20 +NORM_CC_ACTIVE = 21 +NORM_CC_INACTIVE = 22 + + 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 diff --git a/src/pynorm/core.py b/src/pynorm/core.py index cf2f62f..1d0b74c 100644 --- a/src/pynorm/core.py +++ b/src/pynorm/core.py @@ -232,8 +232,13 @@ def get_libnorm(): libnorm.NormSetFlowControl.argtypes = [ctypes.c_void_p, ctypes.c_double] libnorm.NormSetCongestionControl.restype = None - libnorm.NormSetCongestionControl.argtypes = [ctypes.c_void_p, - ctypes.c_bool, ctypes.c_bool] + libnorm.NormSetCongestionControl.argtypes = [ctypes.c_void_p, ctypes.c_bool, ctypes.c_bool] + + libnorm.NormSetEcnSupport.restype = None + libnorm.NormSetEcnSupport.argtypes = [ctypes.c_void_p, ctypes.c_bool, ctypes.c_bool, ctypes.c_bool] + + libnorm.NormSetFlowControl.restype = None + libnorm.NormSetFlowControl.argtypes = [ctypes.c_void_p, ctypes.c_double] libnorm.NormSetTxRateBounds.restype = None libnorm.NormSetTxRateBounds.argtypes = [ @@ -313,8 +318,7 @@ def get_libnorm(): libnorm.NormStreamMarkEom.restype = None libnorm.NormStreamMarkEom.argtypes = [ctypes.c_void_p] - 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.NormResetWatermark.restype = ctypes.c_bool @@ -348,6 +352,9 @@ def get_libnorm(): libnorm.NormStopReceiver.restype = None libnorm.NormStopReceiver.argtypes = [ctypes.c_void_p] + + libnorm.NormSetDefaultSyncPolicy.restype = None + libnorm.NormSetDefaultSyncPolicy.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormSetRxCacheLimit.restype = None libnorm.NormSetRxCacheLimit.argtypes = [ctypes.c_void_p, ctypes.c_ushort] @@ -357,19 +364,19 @@ def get_libnorm(): libnorm.NormSetRxSocketBuffer.errcheck = errcheck_bool libnorm.NormSetSilentReceiver.restype = None - libnorm.NormSetSilentReceiver.argtypes = [ctypes.c_void_p, ctypes.c_bool, - ctypes.c_int] - + libnorm.NormSetSilentReceiver.argtypes = [ctypes.c_void_p, ctypes.c_bool, ctypes.c_int] + + libnorm.NormSetMessageTrace.restype = None + libnorm.NormSetMessageTrace.argtypes = [ctypes.c_void_p, ctypes.c_bool] + libnorm.NormSetDefaultUnicastNack.restype = None - libnorm.NormSetDefaultUnicastNack.argtypes = [ctypes.c_void_p, - ctypes.c_bool] + libnorm.NormSetDefaultUnicastNack.argtypes = [ctypes.c_void_p, ctypes.c_bool] libnorm.NormNodeSetUnicastNack.restype = None libnorm.NormNodeSetUnicastNack.argtypes = [ctypes.c_void_p, ctypes.c_bool] libnorm.NormSetDefaultNackingMode.restype = None - libnorm.NormSetDefaultNackingMode.argtypes = [ctypes.c_void_p, - ctypes.c_int] + libnorm.NormSetDefaultNackingMode.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormNodeSetNackingMode.restype = None libnorm.NormNodeSetNackingMode.argtypes = [ctypes.c_void_p, ctypes.c_int] @@ -378,24 +385,19 @@ def get_libnorm(): libnorm.NormObjectSetNackingMode.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormSetDefaultRepairBoundary.restype = None - libnorm.NormSetDefaultRepairBoundary.argtypes = [ctypes.c_void_p, - ctypes.c_int] + libnorm.NormSetDefaultRepairBoundary.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormNodeSetRepairBoundary.restype = None - libnorm.NormNodeSetRepairBoundary.argtypes = [ctypes.c_void_p, - ctypes.c_int] + libnorm.NormNodeSetRepairBoundary.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormSetDefaultRxRobustFactor.restype = None - libnorm.NormSetDefaultRxRobustFactor.argtypes = [ctypes.c_void_p, - ctypes.c_int] + libnorm.NormSetDefaultRxRobustFactor.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormNodeSetRxRobustFactor.restype = None - libnorm.NormNodeSetRxRobustFactor.argtypes = [ctypes.c_void_p, - ctypes.c_int] + libnorm.NormNodeSetRxRobustFactor.argtypes = [ctypes.c_void_p, ctypes.c_int] libnorm.NormStreamRead.restype = ctypes.c_bool - libnorm.NormStreamRead.argtypes = [ctypes.c_void_p, ctypes.c_char_p, - ctypes.POINTER(ctypes.c_uint)] + libnorm.NormStreamRead.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)] libnorm.NormStreamRead.errcheck = return_bool libnorm.NormStreamSeekMsgStart.restype = ctypes.c_bool @@ -446,10 +448,10 @@ def get_libnorm(): libnorm.NormFileRename.argtypes = [ctypes.c_void_p, ctypes.c_char_p] libnorm.NormFileRename.errcheck = errcheck_bool - libnorm.NormDataAccessData.restype = ctypes.c_char_p + libnorm.NormDataAccessData.restype = ctypes.c_void_p libnorm.NormDataAccessData.argtypes = [ctypes.c_void_p] - libnorm.NormDataDetachData.restype = ctypes.c_char_p + libnorm.NormDataDetachData.restype = ctypes.c_void_p libnorm.NormDataDetachData.argtypes = [ctypes.c_void_p] libnorm.NormObjectGetSender.restype = ctypes.c_void_p diff --git a/src/pynorm/event.py b/src/pynorm/event.py index 0def624..8439932 100644 --- a/src/pynorm/event.py +++ b/src/pynorm/event.py @@ -13,11 +13,11 @@ from pynorm.node import Node from pynorm.object import Object class Event(object): - def __init__(self, type, session, sender, object): - self._type = type + def __init__(self, eventType, session, sender, normObject): + self._type = eventType self._session = session self._sender = sender - self._object = object + self._object = normObject # Properties type = property(lambda self: self._type) @@ -57,6 +57,8 @@ class Event(object): 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: diff --git a/src/pynorm/instance.py b/src/pynorm/instance.py index 86499ed..237603c 100644 --- a/src/pynorm/instance.py +++ b/src/pynorm/instance.py @@ -9,6 +9,7 @@ import ctypes from select import select from weakref import WeakValueDictionary from platform import system +import sys if system() == 'Windows': import win32event @@ -48,6 +49,9 @@ class Instance(object): def setCacheDirectory(self, path): libnorm.NormSetCacheDirectory(self, path) + + def setDebugLevel(self, level): + libnorm.NormSetDebugLevel(level) def openDebugLog(self, path): libnorm.NormOpenDebugLog(self, path) @@ -64,23 +68,26 @@ class Instance(object): if not self._select(timeout): return None - libnorm.NormGetNextEvent(self, ctypes.byref(self._estruct), True) + 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.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) + sender = self._senders[self._estruct.sender] = Node(self._estruct.sender) try: object = self._objects[self._estruct.object] except KeyError: - object = self._objects[self._estruct.object] =\ - Object(self._estruct.object) - return Event(self._estruct.type, - self._sessions[self._estruct.session], sender, object) + object = self._objects[self._estruct.object] = Object(self._estruct.object) + return Event(self._estruct.type, self._sessions[self._estruct.session], sender, object) def getDescriptor(self): return libnorm.NormGetDescriptor(self) diff --git a/src/pynorm/object.py b/src/pynorm/object.py index 7bd56ee..2889066 100644 --- a/src/pynorm/object.py +++ b/src/pynorm/object.py @@ -18,7 +18,7 @@ class Object(object): def __init__(self, object): libnorm.NormObjectRetain(object) self._object = object - + def getType(self): return libnorm.NormObjectGetType(self) @@ -56,12 +56,19 @@ class Object(object): def renameFile(self, name): libnorm.NormFileRename(self, name) - def accessData(self): + # Because 'ctypes.string_at()' makes a _copy_ of the data, we don't + # support the usual NORM accessData / detachData options. We use + # NormDataAccessData() to get a pointer to the data we copy and + # let the underlying NORM release the received object and free the + # memory. + def getData(self): return ctypes.string_at(libnorm.NormDataAccessData(self), self.size) - def detachData(self): - return ctypes.string_at(libnorm.NormDataDetachData(self), self.size) - + #def accessData(self): + # return ctypes.string_at(libnorm.NormDataAccessData(self), self.size) + #def detachData(self): + # return ctypes.string_at(libnorm.NormDataDetachData(self), self.size) + def getSender(self): return Node(libnorm.NormObjectGetSender(self)) @@ -133,4 +140,7 @@ class Object(object): return cmp(self._as_parameter_, other._as_parameter_) def __hash__(self): - return self._as_parameter_ + return hash(self._as_parameter_) + + def __equ__(self, other): + return self._object == other._object diff --git a/src/pynorm/session.py b/src/pynorm/session.py index c11067a..7655f29 100644 --- a/src/pynorm/session.py +++ b/src/pynorm/session.py @@ -23,13 +23,13 @@ class Session(object): localId - NormNodeId """ self._instance = instance - self._session = libnorm.NormCreateSession(instance, address, port, - localId) + self._session = libnorm.NormCreateSession(instance, address, port, localId) self.sendGracefulStop = False self.gracePeriod = 0 def destroy(self): libnorm.NormDestroySession(self) + del self._instance._sessions[self] def setUserData(self, data): """data should be a string""" @@ -66,13 +66,12 @@ class Session(object): libnorm.NormSetReportInterval(self, interval) ## Sender functions - def startSender(self, sessionId, bufferSpace, segmentSize, blockSize, - numParity): + def startSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity): libnorm.NormStartSender(self, sessionId, bufferSpace, segmentSize, blockSize, numParity) - def stopSender(self, graceful=False): - libnorm.NormStopSender(self, graceful) + def stopSender(self): + libnorm.NormStopSender(self) def setTxRate(self, rate): libnorm.NormSetTxRate(self, rate) @@ -80,11 +79,17 @@ class Session(object): def setTxSocketBuffer(self, size): libnorm.NormSetTxSocketBuffer(self, size) - def setCongestionControl(self, cc): - libnorm.NormSetCongestionControl(self, cc) + def setCongestionControl(self, ccEnable, adjustRate=True): + 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, min, max): - libnorm.NormSetTxRateBounds(self, min, max) + def setTxRateBounds(self, rateMin, rateMax): + libnorm.NormSetTxRateBounds(self, rateMin, rateMax) def setTxCacheBounds(self, sizeMax, countMin, countMax): libnorm.NormSetTxCacheBounds(self, sizeMax, countMin, countMax) @@ -95,17 +100,17 @@ class Session(object): def getGrttEstimate(self): return libnorm.NormGetGrttEstimate(self) - def setGrttEstimate(self, grtt): + def setGrttEstimate(self, grttMax): libnorm.NormSetGrttEstimate(self, grtt) def setGrttMax(self, max): - libnorm.NormSetGrttMax(self, max) + libnorm.NormSetGrttMax(self, grttMax) def setGrttProbingMode(self, mode): libnorm.NormSetGrttProbingMode(self, mode) - def setGrttProbingInterval(self, min, max): - libnorm.NormSetGrttProbingInterval(self, min, max) + def setGrttProbingInterval(self, intervalMin, intervalMax): + libnorm.NormSetGrttProbingInterval(self, intervalMin, intervalMax) def setBackoffFactor(self, factor): libnorm.NormSetBackoffFactor(self, factor) @@ -114,22 +119,25 @@ class Session(object): libnorm.NormSetGroupSize(self, size) def fileEnqueue(self, filename, info=""): - return Object(libnorm.NormFileEnqueue(self, filename, info, - len(info))) + return Object(libnorm.NormFileEnqueue(self, filename, info, len(info))) def dataEnqueue(self, data, info=""): - return Object(libnorm.NormDataEnqueue(self, data, - len(data), info, len(info))) + return Object(libnorm.NormDataEnqueue(self, data, len(data), info, len(info))) - def requeueObject(self, object): - libnorm.NormRequeueObject(self, object) + def requeueObject(self, normObject): + libnorm.NormRequeueObject(self, normObject) def streamOpen(self, bufferSize, info=""): - return Object(libnorm.NormStreamOpen(self, bufferSize, info, - len(info))) + return Object(libnorm.NormStreamOpen(self, bufferSize, info, len(info))) - def setWatermark(self, object): - libnorm.NormSetWatermark(self, object) + def setWatermark(self, normObject, overrideFlush=False): + libnorm.NormSetWatermark(self, normObject, overrideFlush) + + def resetWatermark(self): + libnorm.NormResetWatermark(self) + + def cancelWatermark(self): + libnorm.NormCancelWatermark(self) def addAckingNode(self, nodeId): libnorm.NormAddAckingNode(self, nodeId) @@ -148,6 +156,9 @@ class Session(object): """This will be called automatically if the receiver is active""" libnorm.NormStopReceiver(self, gracePeriod) + def setRxCacheLimit(self, count): + libnorm.NormSetRxCacheLimit(self, count) + def setRxSocketBuffer(self, size): libnorm.NormSetRxSocketBuffer(self, size) @@ -158,12 +169,18 @@ class Session(object): def setDefaultUnicastNack(self, mode): libnorm.NormSetDefaultUnicastNack(self, mode) + + def setDefaultSyncPolicy(self, policy): + libnorm.NormSetDefaultSyncPolicy(self, policy) def setDefaultNackingMode(self, mode): libnorm.NormSetDefaultNackingMode(self, mode) def setDefaultRepairBoundary(self, boundary): libnorm.NormSetDefaultRepairBoundary(self, boundary) + + def setMessageTrace(self, state): + libnorm.NormSetMessageTrace(self, state) ## Properties nodeId = property(getNodeId) diff --git a/waf b/waf index e1e34d431afba28d52add07a6094226a1a74374b..4e68fedc548287026a69560acb79f5c030596550 100755 GIT binary patch delta 87749 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 I4JE}W1iD+_%4ztf#CBp>M= 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

5HG{H7cH1?cD`kmu9@ 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 delta 84489 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 M++k)E|e zNK8MOxML8tiLWjcVGe`GqOI~(>i^9~`MfZaPCK$emUkNx8P78MPJgFX`p$ahz4cUN zMiMIo7{VRwL)h%%Vf~cBFbRbI-&WY}BNi+}=RE!89CXe24#aYIR){&lF$0M71zH9i zchmqdf<$plb1A}CjJr1U({D_(WUoFq59c7dV6$ARH@<+ASi*Kp>4p}p;Kra+#nTx& zmL=Y{e_dswjuO~rg@4lUOaX&t;@g&QM-jrE=Qv&`Q_|jW)EPdYqX5Y5aDWAoZjl#sPAs!J5a&!_SpXHvbsBPs`|>`+tXgIXbRt&fkDgX;&z z1ttPO_WKaU=o*B(C}b5g+k@PDN=~uxV+YNUnHA)FrcJc&?SH2>&?N$M`K85FX*q@X zbYy`b`yDYDEoUdd>t6)c3_zGt>!CF_0v5QBPED{zMk$fqIP(R2SRmmmXwFZgpp56RD#H$Y7nZP zh)hDBl%nMq0DoL)#sM?%EGM)K=D#f$+pj<%9i{1Q)ap$x71(Ow;9YV_{=aoE<)uSd zV2K51yH*)yAb{60;Vei-h`6a_9HlF0-3CHHLY{uBFd2 zw>$h3l3xAiSKr|vN0r2E7WV%i@!7+P=Oq*pRZ6o9cYoH^Z*!0jB}dtS5q;aV?%V3g z?%^$IXTQp^xPIN0tOV>I zXKrSAbq41pM9BjFvH7?egF91?k?qtHB9ip@9Rfhe*RpuY7$HIh6sg-tp$t(MnS{nl zKoVL=Q-79YZr!D{sZ`3@N;R5GO=`@yRNB7=fuo67|RxFsw;qAadK=J$s57^c(fgfsUAN!LMlzE?A z%D4FmkcyPz$K_{nxg!C+%Fk;25FZu-$Sfkv(tj+^Ya=SjB%0MDd(f64sKw4YK*m?L zPfY`ToLaUdVhDF3;DACCEd_6sPX+jr39 zhD@xFMJ&>C9?7)!S3$2&Y6!&;JZrqb)E95xrD>Hp+g>BWxoj6iWzk=Tbr#Fqwd3^`h)a zJ#0lIrK=KA2_q#@d_BLT@csX(e^o0Vu%MZsD7Uj_Lz>3BzVgNpWDw|!?CGE_QWUz? zGRHc&mu8|2uu79LSRvkNmcPPtF!ivOaDPJXdVb!yxz4g$-f4y^tFlg#CQ-`DTmb;} z6I9I}^4rUFBADOqW1+#wf_Rj;O#`#Lx{t~hu-bkOoRcOt;4+sPVVEi_BRV>W55MG} z@imsxkw)u}x*adjaTvJ{=0bp4e06Zpwtm;Q-8pZ>O~W9)`OY1fgRnWrN`%bOV1FPX zxNysnA`dCX|E4HLnUiRZ8Y3l%^NfhXC!5tH)RTIm6=s9tcNQ&rWJvY!7HKe|e8P#F zbdqKg_jkyCcAziL*fM`TzkBJV^dks#u>>Sp#&9f@5`Q&lZGG#p<@NgF>(8Gly)H`olJu`GjP4h*vnYdjmo?d%1cCOc5f=nG>b zMKnW12tF=n_J|QC$w29sMH&>MO24XXRe~$f6O`KiGWQ1KR#SxHmUvm`g`)U4_RJ_bk)A+ ztDE8DeiJxNv?$EEW?LH?r++9MxQ!oGWiI`kL)deGVIw66B%}PizY!5aNCi>WcZuV( zFz-cn8t-6M$QvUiVT6Po>!)lUo>aU(4u1mz-2nRCvbLdB2FQnVB`NLys3QrPgGBo$zb z5rl_nKHtk@M^U+%Y{2}+88Jm}{uia^=lq4*_k(UJ{h!>KQd*EkY0Jsl#g^<;b{6xMq)l!xqY&KBkmo~a1B@b`U=%$aK*nb#dsB_1hN-&#zPeiew zmPOUtV{^v?Uy4uE-}!QI05` zf=f-y2O5YsDg<(r0|cg=I87arAc)BZG9o}kV+g8{M5Umxmi{j3G>0Q4&CSr%WO{68 zG8h<$B7zRI(|?0ZQWu=T(xAeLMj(KeHKn_lIV_C}W|YAs$TE`4QkI1aM$5|r!2DVx zAjqWJR8n){wnA<9$N8z5WT1ZU>F^oAv=%`^1Vk)h>LHsow65-vc-?i~n=+O%xv4~O z_j%_SAjN>hRj)8B?PU-$Ua}Q{FhNzQ1f+6hCP;wnl7E=QHY#eAwj{D*(rYM5vn@=N zQn8F$$ys{$9+Rsybk`+{YEq)Xw2C~YA&>hrB_O@n7D$M8laG614qFif5EZ^rGOI9( zEM&eTCC!I&C~{(VlQtGbCZm$=v!jqKoEsR)w#?2>`02QfscBB^?9;3-1a8O&!fS1xmMu38kM#3m#-AdyCF;9QdU$aI9{iCYlo zhJ?y(m|!BOU6|IFO)}YtV5DFo>a_yXmm?)CBY!10ZOpeI3nU!O8_q0aBt#SuHypSj zy?rh{NF=an+7wJIFLX4*!R@tB#yQBzjDop=rdhUq`M+a-MW;%UOwN$wK^EQ8w{Shwy?&JIr;KpUV2=lsvq~j5PLC<4mmwx*$nBnrq9nL(a5qbG zWPi5|;n|2}1Y29f1qf!Kx?m(Ay^mNY$j;yvQ$YQumQw~Ksakui+!F}Tg)#eh9y49R!wn- zA_2@vFhs=hp-ty~aJ6|STfa*uobe1wS%2JyLk{MbG{b9aG%kA_&Q7>_6y-ZJ%rna` z9@$oUVb8X!WP=jOV#xn1(19Q@4ymcadK4jKmflh~0o#~b-6?RHh816Y6gu_CcdG+{ zPRd{tBtr}^lt4rvkm_I+AhIbVDl!8jC^9hEK?ySzlVyq~42en+ve*HmG`Q0wg@4M6 zgZmf;;v+_zvgiYceL|!F2M-TYqMnpC5!~RTca98L)XD+J8Zf(9Q<8Yuxw-%ig!6I>VWr)5(Tss##kOo!hfp;kFsP zy6AZ!$Leade%*sb8$yle9us#$6g%bOQ<~kpV8?v(v8UO$VgS}aDZd*qJ1FWwQKH0i zJI-jU=JR|aJem}VG#%UQ4q%<~rtik5(cR3n5EsF4K- zP$MM-RALk(k9?6?E(O|H0CJ6lj-@SRNHRnr5wc+m5K=1HQi6(A?wa~;+bS{$%AhI~ ziyBhUP>pEYD6KyuCDy`GYZZTpq|BkCSgjdhrfAwRY*w=>G!+z6BH1ctNg-^;sU)eY z%}kq#A~dZA$7g@0;{hLbW?}6>)~L3zBuGUW2u4W=MN0|zBPF`;?w?m(`1ZTocU9|! z%bfa)QDEsza?yr3&kkc|@KAWf6>(K-;%tO#Jr!mW;0q*A+Kr z%~89C-_@B66Ed0wO{Ei-mt=)WE@Wn5rr}btNf^vq`?t|c+$VHt@7-qIlPu(TmsadS zGVvdz(K_?{v(F5otn=pMBP9#Dm?J(f7v}m*rCHt>9K)f_xS@aN?m5LH4#(rk$yQBm zho^Se8~Qfi7Iy5vg{aS!GrhuAd~WYa@GV%U&78_B4@L%O(>K!H;$$(b%)TF2(K;XU zL+6Z)kCD=E0(PW#3-?>UVy~&8Em})1@FNbgwXPI9L+gD*_xRC2O7?i6>K~B4X`sYId<>%f$!f;3YPKrDYpJiK+PxSK@-5*0>HEky zKoo>C>!1Tf2P&jw1xW!)h#_#bt!K=Emw(ka4ted0Zu}yOU_=Jjk!b8ioiG9~3Cf5{#oE&QpKC6bGPl3lyH1U|#?rKWT_y zC_uOabV1uBQV@$GfJs6rJe(k9vXMn2DwYVN21#gh27&Z8MX(>0s#y;NoIl^m-m4 zrq+^-GUiCSW1K8yGvPNVGQ{6{>%a+ls-@RXVib)s1x_7hoK1S*x-g4LZ2yO)JJdE zhU1gxk0`SK`5I9(=Kd#w(p5fr)M}MfZ%D0uOKarcw>`Ka{~+Z`ayiT7gOqu3t@a}- zYBq^XtjZ@zp5C~AtT{TRS5i>0f(!!~&#>eYXW$+KWs1{zK*sX7rJ_co?1vd3h6#YG zYyp3UT+F4R@=ghABPGg9#S1J3F+fP_ZD1i4u;TW6fClmU*bAa47(M%U`wrGVUL|CG zA!~j%r{ik0zha#T>c>r+&9KbN5G-0sDlJp*0HYsK{6IdM!!W_zEDYzWBwB!k5|f}# zRRY&h8;9??#)6Tkl?22(Lb62(zw+iCcrAY$LDbiAEU4>ia>{0sc2?Z`A(J5|y#FKG zevykJYt**6b1;l*0azt2VUY}BR2)cCD#3{b5?jT&rAurQZ5sx>8>X;U9ow%u$1d1& zmn?2>mx}SK@m#4*hx>^XHd#x7W7N{G}IkQ8<$C@qB(y*uCk~un6B&=$sAv+IRpcqMk%!Ters>4%olmJkOizEV+ zA!K9d*a?CWK#CN|5QJ3pq$tk;pm@(sqlpyuiY7DtJcV+3lmQqZvLF|Tg-gI;P>qiR z^ZoNP%C#%X83U|E6Ma3KTN{5Zw5t@=Wj5JZ$dLvN5*FFT_m*6af+#3$10Y=MO3AdW zX%y6%HH~dVRb=ivSOh?0dMCvOQeEYH{D15=q~C1(su zRTOgJ!+>d?XHp0A(ok1`CviN(3CQe?q*)~*&+iOAOw46Jy)DW@X`Nz7mW_ro?&O&S zhAC`NZIC)hVjWE^!IFOqDwQb+I9Oss5GcT?8sbQ#=<32?Klf}bmQb-IU!bpFlkTryhBpS#;v_!07 zzGFBYF-^8MqT(Bh>QeFJ#Wf3r2+1UZObyp)AN0xSgx#wRruK0yG)K|T;cr~w!u z^8pa0*(q5Rgg}1~hl0Rj2v8Y_yoUhycF`hE0f=wra8n2%mH2+--(Tx|jf8zdLPkaP zxKT)|X&^D|@O!h9x4`(e8WRg@BTyx(EZwFfDyvg=q9ebqg(gH<#|vi|ZmqVZnpgXm zk0j!0kZS}t+tmK4>nhH4FOLAwhA__uThmjr;NZ1Cmgj#*Mf0U{*7zG9UQy+D)2}TU z0nQo_>_C7Aydd?R+D<3YQk+#kzXE*aggwyOAZq|#Cqs%V96=;RK>EUxDvbEj(FFmF zQ3!(~44nhm6j<-9m(VMj^^FFj;Qa7MMgJ!VBnSq}nc<^=|05+IjhxQ_`JhNbA~1y+ zE0x7XeXW02+fcCL*xo7}kwDZwN}y^s{vqo5{W^#2_?JIyJXnw3XY=`%!3PnBCCe3R zY<-DX2$mb`o;7ZmIALi9A{sr|v1dbCw;n~Ph9Bt!M70pk3^}X|C z8%|YK7$CD}sJb4>vFcssoP+kc`>DQZh!4PbGmev@nbE5sc9(TBI|td(i=wCmmJ9|@ zGNFHoxrQ;V)P^9LAgLIMVqAmFFxt(rY+YO!WiY)%(rpj51f`d0Xm~>K)h5%kUSAPw z6(?xU7_l(TDeC-sHo31L^Lj?1!z>mp>Q^qoU3UmMUP}yo-GY-T`j<^mjo-E|H{V>F zUcpXLK-0X0d&Bd#jg8@rx3G2OF-BsA+IW8iL<837XCgroEP>d3y!v6o;z;&z+|H9q zAb3R_v=~lB72Ck5dU`x4f+k(}`pC1(fn08aj|(NXqXLxJb6Ih*@N5=s5W1GL1CbCc zmNo&8PT=uJoa#8%{39wVtE$pf7Orb}>BhBh>y>%oYLgJ^k9+6k~4DoWDXu(VP^u~bNv z1dwl3I|nnwqswsaV7kqSC>vue{Yrm5H3*QGYAcmW1PSm>?!v-@KRFO+((Ao@H2~8hK_9Hv2Lp zB$8S`io+v)LJ(6$bY;vGQigwSBP9W8gaHsKC*bACuF!w&#M)~V1lgJJ zI9UTP9uI$s^)m}{FW5ULFKA*SKFbSd@ZjWH3jiFPsk<&1k#IhK>nBWE`rd>VY^Fj( zy)?bRNE4iZ2P+Q9NQOE8HG4TBP~op(haIPsPy?9NM|P8uQ8zSsS#gk zxX8%b>~c#@+dej7xfoKfSv=@!L{5itBPA}S~qD6HuRV<03ZVxGP%%7bu4-Xf}fzyBeE~Yj(TNVpTPjx&v zl27dEd}rDtDkCbHpRGYNytZIkv*5j&5UkPkv+tQ_`*QSm68n2fuYL}nC|(9L?`46k zz+S=83o-ybU@^U&$jZa>pFEKk?f@Ypg2x%dW1e(xf7EcwF4%LxGJ?FpgmWmmUOQP1F7$zZDbpp#vp>VssGXn?51kSNR`*Yr&x@X9pXYiM7Lqba z7D#5IERBCDFey;WNvtBw%ob%BQp|`%mRUAr3=w`7uTyEML^sT$NR1v+5-=Yq?A5-z z{Jd?=%Z2V-;Bhv{(9bM!V*)VDqccTN`M_ahpjXExaxGm$#2rCGh?aUptQL}1Ey>JN zSOj?+va&m8g#U^-jyQr6pM&WA-ctt@katEQh--ft07U?h+Z!H3yUgY3_oUoWK}}TH zX=kdIC2H=o(|kmPAaL50eG|V$I0~)DV=F!b_ ztCfE;uSUEz;WaRjStMz+F7?2Z21S!Xv=k*sg)9YG3mU|%wow-F$%ZT-jD({@nc=c> zgKNO1ClLl}S)?SW3ki%N`t+oL>3Zm-%?tK7EGMy;WLXX-e(R_Jt04s-#DK;az^p4q zp=)7mup}cl%RBq|V9;55L(G+lf-R5n9pP#W`xEBln(IloJLZ``PqUdVTCi7 z5X=z2QIs(cM;YMI>f|tH8hH;?eJq*1S?NmmXBa&O8LHUDjHLK_CpZ&$SOoYp$)yY3 z;Do3PjDfL`2J9(_DMUn2M%<8cMlF<>$Y;Q+pkc{W7#d-i0Kts0O46T0CtuH zn$nRh)W()uWjnIXDvpJdIMozG1*KF$G#fUB5z5UoGK`>@+?j?Ljj4ju2m}=B!tI(w zGD4;ZG@&rdjIK;-$%7Q9R1Uef3{DoB;?lyfxKt37rqI(}?(OD5M|XH_;_GYRyF0cdvS9xmSoQqy4ER1vIy&R zp-{M5;k3gkyD_Oln-3s(>6d!v9vGzf_ekiRQrNBPuA$@i7D{dBZTW zAj`Q#&KHF84zJ< zz~Wi%>oFY?^pvlAp_3)E_)y-9H4?b-n-K63gV~r(CSkg9lR8TdZSkU&i_>3#UL-ts zXAczNbGR5L;vbsXrH78Tp{|}6#s)&B<`^AQNZ~w8tYQTE%R1`%@>72;ZML4lKCdU9 zUjsQT>k-W0xIx%*eFRvrC@e+o;jpl35YeHqfni+8g+p0EiNY2TiP=LV^V>(=@e679L2Fh;A-7Odv7~NSGxwX%D1Y2g7=F$pi7g$gDr1BPHE#Om&2# zmZNf?%HeEf5z`q;R~dgtJf4vH;IW7?Bn1g$M9H>nEhfa1TKDdhz)g`8_b1!c{GzzP zKjKXTri!;yCt#i5cH8qTfDWouU6~OM#zyB(01kub{aIss-kOS^b|_41@v&QdB&^<8 zN057>j3A35kb<#@(r5%DC8S1zdQeE3s!~)FBdWWCE+ep+W9xshs`Qs2mIv5tAz^Vn zVeg8ifwR`gagt0vh8c-$rXSj*$5(KeFp4V3U{Im~I90I*TVpa7KojB?n4e^2G%Puj zhIy2Ua#5(Z_iNobx%nP#cG3H#eaHt+l#P>yg<6;pjcl7WQkB9~L0y;c`85PZHeOm9 zs$HGsy@t3;WyOE$r6kc>pq8x4D7@I&)GTI(BPyZFtUr06wL=i!=Vz-UB|LR<2x9(I zVT#V>WRzdy3vt}sPW2F{PL7PeaTNbFD}F>GLS*E3b3)aOlcAKo$VARE0O0cxqDbe$e*c)5?74vKAk#bGWS`m z?$j+p#D=KI8ECtB$Qs2j0?`uDBut`fGWP5 z1K|Wcp>=ZHcfKcdA%fc*p<@~^2!vAbMIN#O5CtuI6UX_n#e@RuKtd+Ija?O6zYam1 zcAI}R)s%iPBPGa$5%&mX^@9GADq+=r`TdlV$H^Wb7)~*a5jDUB>_Rv#)Np1>OcdE1%$-Vj*He?TSnCytx>C8t)gHE#*#=yiW@0u63vl< zHGpo|HyAOLp{7R*pI2Q7au{E8vmq8e9xP8*1Qo!A%&-2}w~^iu=U zz3@DD0P7M`^b}At2g@d7?FE0xA}Dd`H7#M#h70jGoc2$Sm%%fEk` zxXCJQX6?l+Dkzyt&*kiaABPEFzMka%T2NRJFNK#e;6j8NvTAH=ZYgsjIEv@wMgbSC+MM{A} zKquw>8QS8bk2(_31jLC*RtQ>xsYrh>*Fu{@tQvqNB1YyYTDOoJ+!ZZ2z?3R^wvEGHT($tlKt- z<{x?Np>5^j@wLPca>ry+?6^%)h}{%`bXCME=k#9|(2TG)`8mf`R>4r%ZJ_5?V8d&C zV#AMPi-J2ow{{w=VYXq!&Kz1!jM<6wuC(}-yr-SD;WN~>2#4y(+}-V^`ZyRRkleV> z2uzzJD)?X5>SnuGIJ|$S!KXW-cV|fP)6pPxIi8PgT38vDFwl=>qTJ+imx?x;@5RmT z)*&IhJYeJ$zV=J*=6t*3&2iw52m(+8?(+OmTK8b`m*b^ab@TLG!BuQ;LTbyuzIM_ zp{-SLGWl-%@>yvrw4~;gl0J+IZw?FySu+2oNT&DoG$?r@bAeJd+Iss z#qi0Y9jV%O64-y40tIh4x?@D0qiZ;By6t--CBdc>*7M@JR?)@V!)mzM{&tM&XlG$C zhGQ^%`P#a^Q3Dt^exDBxp^h9JT)!L3kUDW8CPxsQ=IV7hV)@L@9s6Ed;jbr;MU^d0 zawV*$8gTD0jL@i4=TVZd<%u01JLxz;+0N~58QVDN;G};>BPEE=Oj|Xsk5<@Xa%*W^ zVj-uOvXf!9h3G0>IoEAmzE8$8xqhY*JlQ(4q#ir((3`HEc*(S5d2^}d#2H7Q7~@_% z{n?1VGe>`ZAEmp{@%LW#lg^1`A|I#`Ll-%$IGQ6Wq>W*l3t4XwBP!-lTRlx#D&mM+ zz6HFV9H;rq5Xf+?Q(m|mIXMxk|>IwQ1XdD?o?vTKdg za`f$vhg{T~X~pH^BPDa65AU#SjTmss8P_>>BfNipEU6U7fs?t^^#C`-=wShUA0J zS?Ju3?Nr97h+u}nYaMG+2_%P3nOynA^wYBkgS_N2YP};R#ws`;&|dC)9@hbI?E?XX z62N~rS49b9l?5>bJTYexCP?S8$ui0n(s$y8CE177pFMUQ&BKk5`t4MHUmWP!SjC5t zFTpRLI4Gu?7a?28b4@Fa9vwl30&_2^W$vPxOJd2W-3xTgwa{=5a>*J{sGn(~t^sg5 zh6b7dfv$vh;aSSHJ3J8ANuURs?>LZKmNd?O_Q zSOlFB+o6of9%p}DQ8HB9V|bx)n#2HwB@2VyYr5^fEgHlDljUM07%oCiD5k-l-VlFi zllQ~dJGvP2A;iugV-C;y91FU^vK=U=c1&hCc@G>N z`0t}>sKOlwRQ=!ErNM=mtjiQ+a{GT*i7B`vC5xDsXgoK)pOb%%o4+qh36Gx!x^o%p zOj3u{2ZCUhUjZe$<2_ln+sls@i-nQQ?^9K@wCbL^K4FmVCh@E!mnRh}@w>p3II|L^ zn~t1HUYj$Qj~(SyPGo|)P9dXyA&BbW^!u&YQM2h{p9z#Gfp2lT&KnR!&PjhV$`xZD zNF6E7#27O99BKIFpO<^*z3NwH)OK5kH{-rxf|SPv52DmSPF$?=qrSL!9@4 zKZ>p?)A8^%*6QC{bJ>M5&)R>dQ_vI0{H}7$(MUr`8)ZrJ#YBk-&o}xdPNAq#=iz^XkaZtDzBkxPnrl^+;9Ge@nM>F?Pw z?snZa0z5VPh@~$hI;;H-7klz1cSKV&P-vlRNDLRB7DQm__LLfsY5E^5OvA_yBOp5b zR#@5w2e>RSqa1u4!JhQu3Ec-n%4!}Q^6tMsC>gfXk<;sLsA79t#N`YAhI^X8s#I3h6FtuRxE11yKZXC z+S>G6n#Qt$0+c*iNjQg4#E0GmhD?D#t;Dd|EJBMdC>|p!_(P6V>7G2!I_7k^t^j;a zgTe8Cqt?0_IA(uUGvbI5S@m~3b4SFe@m@4qH=3GlO4@_E2uLClBPydz0T~4vF@cUQ z^k?DTX|L(s*-&1M=jPq=-8*E{*?D1qwS8t^PQ5(J!$-o&)u*o`9CuNK|kkdnU<;(`vB4st;=aiKMPN;EbTn=U2 z}wkzT+o=w_W2F*BRXk}QAKWay=lc=#x0H-xl~W=U=t(=STA zI3obbcwI1~X~C3aqa!8rldH@)SE6wf*c-;gJ1!H3m|>GL&qSLT#4$L`%gS4xd(KWC z6FR$sl@nNYCBVnJY{8K)fzCa~a)K5;iFtPvrQ8oHAaMwkF@W4m?AEinYYuD{>9(8; z3^0GN%xOc$9af%}1;>_n-5gS$UMi}c*c5oq zME>=|HU~_(>fG*`Bab5~j0;#-t7kp4CN+O}%g86UwVW9?vz~5dw=nE=l76=Hrx-pN z)6UbF6%S0u-R|h4BPBPWdgUf>EX9eKTQe}OMwS@)Nik~+6k(@?Rf~azT}LA4N0O}C-FzgncGSYm6>>@XfqeGY=-Q1Na;h$GxC>Hecogwa$5#)cA zk|`p{NWqN~i83r(oHFF~ezQ?Ti6nr+6wr2~;3R|+r*czCP%23vnIrDeDc}pIjZ_db z`Gq0w0^p#%x*YJ86|1ips|lJH_Z(HSy!`ycr&bo*Y{Je zD#~sdUTMgBY~()?{A4@l;wY}4pJ0FE1GEUkX=s6B2CuB??FYjUP-{f1Wv@Wpq}iio zc^x|f0tmoXT+#$cF$;gA`kf!kpL6i7x#Q-D#0yVZBbE0ZJOr&-R|{% zJuCI+6(qzeN~V}Gl4Zy;S~7=Co_vE4dHNk06WLW&-aC$V0g1Ct$|!-XgRFm;k-8L6 zQH>g#IZG8Pk}UcxkHqEU&yGaZ%m-<(?1z+U3DgkC?F9~1LBV;1h7b~>#@fxZX-icS zUNux+D%-*=jEE=PnRx#xgVjf=od@qac=}2o4k-R8E7)+H&a}6!L&?zS{?;>CVmD52kHLKg z`38o%4<8gqfH@gM@S})_MD>AyJ4A03%82?98xq_`kdQ2K+G+Agnst9=o_HdVXBp5( z(aE4LJNhIh7+^?43-KT}CxPIc?*pt=Ocn#vh;uAL7z3HiBPFkXzb@Xa#8b;pDYZF` z5a%N>DXALQB^-oi&aF(#V+DRwLifEDrWI$`+#@QTNQg+OCb3#8Nk`9ZTb9`x#v(wF zQU@ZIkzfQM2v1a`C{%xz^h^ZOX*lB$A(91p@k&Y8gnNza7s_g?qiNGQygk6V$8|8WIQ1@8p_X2fj5g}?aAZ%i>K%w^B1IS-HZmhM@s>`0do+Jxs663wBPDWW5@Uz8 zbfGbTdN3)2WR!_g#sVQe2d%L@9-oJ?(uwhyk_Q+P93XvSl^G

2*NS3m^}_U}qjD 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 diff --git a/wscript b/wscript index 29fc37b..019941a 100644 --- a/wscript +++ b/wscript @@ -51,6 +51,7 @@ def build(ctx): target = 'norm', includes = ['include'], export_includes = ['include'], + vnum = '1.0.0', use = ctx.env.USE_BUILD_NORM, source = ['src/common/{0}.cpp'.format(x) for x in [ 'galois',