From 4ecd0ad025d002ffe6d38eb335a0dfeda72f59d4 Mon Sep 17 00:00:00 2001 From: Jeff Weston Date: Wed, 11 Sep 2019 12:06:47 -0400 Subject: [PATCH] v1.5b1 --- BUILD.TXT | 72 + NormDeveloperGuide.html | 4404 ----------- NormDeveloperGuide.pdf | Bin 948062 -> 0 bytes NormSocketBindingNotes.txt | 177 + NormUserGuide.doc | Bin 113664 -> 0 bytes NormUserGuide.pdf | Bin 271698 -> 0 bytes README-Java.txt | 83 + README-PyNorm.txt | 43 + README.md | 20 +- TODO.TXT | 69 +- VERSION.TXT | 136 + common/normEncoderRS8.h | 70 - doc/NormDeveloperGuide.html | 347 + doc/NormDeveloperGuide.pdf | Bin 0 -> 459998 bytes doc/NormDeveloperGuide.xml | 6985 +++++++++++++++++ NormLogo.gif => doc/NormLogo.gif | Bin doc/NormUserGuide.pdf | Bin 0 -> 84288 bytes doc/NormUserGuide.xml | 1163 +++ npcUsage.pdf => doc/npcUsage.pdf | Bin examples/README.TXT | 80 +- examples/java/NormFileRecv.java | 58 + examples/java/NormFileSend.java | 44 + examples/java/NormFileSendRecv.java | 103 + examples/java/NormStreamRecv.java | 109 + examples/java/NormStreamSend.java | 110 + examples/java/README.TXT | 72 + examples/java/filerecv.sh | 2 + examples/java/filesend.sh | 2 + examples/java/streamrecv.sh | 2 + examples/java/streamsend.sh | 4 + examples/normDataExample.cpp | 119 + examples/normDataRecv.cpp | 248 + examples/normDataSend.cpp | 227 + examples/normFileRecv.cpp | 15 +- examples/normFileSend.cpp | 9 +- examples/normStreamRecv.cpp | 238 + examples/normStreamSend.cpp | 290 + examples/python/debugStats.py | 64 + examples/python/fecBufferStats.py | 45 + examples/python/fileRecvManager.py | 94 + examples/python/normFileFeedback.py | 117 + examples/python/normFileRecv.py | 71 + examples/python/normFileSend.py | 64 + examples/python/rawRecv.py | 102 + examples/python/streamChat.py | 107 + {common => include}/galois.h | 0 {common => include}/normApi.h | 267 +- include/normEncoder.h | 56 + .../normEncoder.h => include/normEncoderMDP.h | 50 +- include/normEncoderRS16.h | 68 + include/normEncoderRS8.h | 69 + {common => include}/normFile.h | 0 {common => include}/normMessage.h | 1012 ++- {common => include}/normNode.h | 210 +- {common => include}/normObject.h | 98 +- {common => include}/normPostProcess.h | 0 {common => include}/normSegment.h | 38 +- {common => include}/normSession.h | 452 +- {common => include}/normSimAgent.h | 33 +- {common => include}/normVersion.h | 2 +- {unix => makefiles}/Makefile.arm-linux | 1 + makefiles/Makefile.common | 243 + {unix => makefiles}/Makefile.freebsd | 2 +- {unix => makefiles}/Makefile.hpux | 0 makefiles/Makefile.iphone | 58 + {unix => makefiles}/Makefile.linux | 7 +- {unix => makefiles}/Makefile.macosx | 11 +- {unix => makefiles}/Makefile.mklinux | 0 {unix => makefiles}/Makefile.netbsd | 0 {unix => makefiles}/Makefile.sgi | 0 {unix => makefiles}/Makefile.solaris | 0 {unix => makefiles}/Makefile.solx86 | 0 makefiles/android/AndroidManifest.xml | 8 + makefiles/android/README | 52 + makefiles/android/ant.properties | 18 + makefiles/android/build.xml | 85 + makefiles/android/jni/Android.mk | 55 + makefiles/android/jni/Application.mk | 2 + makefiles/android/proguard-project.txt | 20 + makefiles/android/project.properties | 12 + makefiles/java/Makefile.common | 65 + makefiles/java/Makefile.linux | 17 + makefiles/java/build.xml | 62 + makefiles/win32/Norm.sln | 63 + makefiles/win32/Norm.suo | Bin 0 -> 62464 bytes makefiles/win32/NormDll.vcproj | 251 + makefiles/win32/NormLib.vcproj | 230 + makefiles/win32/NormLib.vcproj.vs2008 | 942 +++ makefiles/win32/norm/norm.vcproj | 243 + makefiles/win32/normTest.vcproj | 229 + makefiles/win32/npc.vcproj | 225 + .../win32}/win32PostProcess.cpp | 0 {wince => makefiles/wince}/Norm.vco | Bin {wince => makefiles/wince}/Norm.vcp | 0 {wince => makefiles/wince}/Norm.vcw | 0 .../wince}/normApp/normApp.vcp | 0 ns/ns-2.1b7a-Makefile.in | 432 - ns/ns-2.1b9-Makefile.in | 536 -- src/common/fecTest.cpp | 135 + {common => src/common}/galois.cpp | 0 src/common/n2m.cpp | 414 + {common => src/common}/normApi.cpp | 667 +- {common => src/common}/normApp.cpp | 795 +- src/common/normEncoder.cpp | 54 + .../common/normEncoderMDP.cpp | 108 +- src/common/normEncoderRS16.cpp | 888 +++ {common => src/common}/normEncoderRS8.cpp | 257 +- {common => src/common}/normFile.cpp | 61 +- {common => src/common}/normMessage.cpp | 204 +- {common => src/common}/normNode.cpp | 1517 ++-- {common => src/common}/normObject.cpp | 1223 +-- {common => src/common}/normPostProcess.cpp | 4 +- {common => src/common}/normPrecode.cpp | 112 +- {common => src/common}/normSegment.cpp | 91 +- {common => src/common}/normSession.cpp | 2386 ++++-- {common => src/common}/normSimAgent.cpp | 671 +- {common => src/common}/normTest.cpp | 101 +- {common => src/common}/normThreadTest.cpp | 22 +- src/common/pcap2norm.cpp | 359 + {common => src/common}/raft.cpp | 135 +- {common => src/common}/rules.txt | 0 src/java/.classpath | 6 + src/java/.project | 17 + src/java/jni/normDataJni.cpp | 31 + src/java/jni/normDataJni.h | 21 + src/java/jni/normEventJni.cpp | 35 + src/java/jni/normEventJni.h | 21 + src/java/jni/normFileJni.cpp | 39 + src/java/jni/normFileJni.h | 29 + src/java/jni/normInstanceJni.cpp | 210 + src/java/jni/normInstanceJni.h | 133 + src/java/jni/normJni.cpp | 226 + src/java/jni/normJni.h | 85 + src/java/jni/normNodeJni.cpp | 144 + src/java/jni/normNodeJni.h | 103 + src/java/jni/normObjectJni.cpp | 127 + src/java/jni/normObjectJni.h | 85 + src/java/jni/normSessionJni.cpp | 680 ++ src/java/jni/normSessionJni.h | 445 ++ src/java/jni/normStreamJni.cpp | 126 + src/java/jni/normStreamJni.h | 93 + src/java/src/mil/navy/nrl/norm/NormData.java | 15 + src/java/src/mil/navy/nrl/norm/NormEvent.java | 62 + src/java/src/mil/navy/nrl/norm/NormFile.java | 19 + .../src/mil/navy/nrl/norm/NormInstance.java | 63 + src/java/src/mil/navy/nrl/norm/NormNode.java | 46 + .../src/mil/navy/nrl/norm/NormObject.java | 57 + .../src/mil/navy/nrl/norm/NormSession.java | 192 + .../src/mil/navy/nrl/norm/NormStream.java | 43 + .../navy/nrl/norm/enums/NormAckingStatus.java | 15 + .../navy/nrl/norm/enums/NormEventType.java | 34 + .../navy/nrl/norm/enums/NormFlushMode.java | 14 + .../navy/nrl/norm/enums/NormNackingMode.java | 14 + .../navy/nrl/norm/enums/NormObjectType.java | 15 + .../navy/nrl/norm/enums/NormProbingMode.java | 14 + .../nrl/norm/enums/NormRepairBoundary.java | 13 + .../navy/nrl/norm/enums/NormSyncPolicy.java | 17 + .../navy/nrl/norm/io/INormEventListener.java | 14 + .../mil/navy/nrl/norm/io/NormInputStream.java | 267 + .../navy/nrl/norm/io/NormOutputStream.java | 309 + .../nrl/norm/io/StreamBreakException.java | 16 + src/pynorm/__init__.py | 14 + src/pynorm/constants.py | 73 + src/pynorm/core.py | 505 ++ src/pynorm/event.py | 89 + src/pynorm/extra/__init__.py | 4 + src/pynorm/extra/logparser.py | 114 + src/pynorm/extra/manager.py | 158 + src/pynorm/instance.py | 137 + src/pynorm/node.py | 75 + src/pynorm/object.py | 136 + src/pynorm/session.py | 192 + {ns => src/sim/ns}/README.txt | 0 {ns => src/sim/ns}/example.tcl | 0 {ns => src/sim/ns}/nackCount.cpp | 0 {ns => src/sim/ns}/normcc.tcl | 75 +- {ns => src/sim/ns}/ns226-Makefile.in | 0 {ns => src/sim/ns}/nsNormAgent.cpp | 0 {ns => src/sim/ns}/nsNormAgent.h | 0 {ns => src/sim/ns}/simplenorm.tcl | 12 +- {ns => src/sim/ns}/sizeAve.cpp | 0 {ns => src/sim/ns}/suppress.tcl | 0 .../sim/opnet}/11.5/OpnetNormProcess.h | 0 .../sim/opnet}/11.5/norm_protolib.pr.cpp | 0 .../sim/opnet}/11.5/norm_protolib.pr.m | Bin .../sim/opnet}/12.0/OpnetNormProcess.h | 0 .../sim/opnet}/12.0/norm_protolib.pr.cpp | 0 .../sim/opnet}/12.0/norm_protolib.pr.m | Bin {unix => src/unix}/unixPostProcess.cpp | 0 unix/Makefile.common | 118 - waf | Bin 0 -> 87674 bytes win32/Norm.dsw | 77 - win32/Norm.opt | Bin 68096 -> 0 bytes win32/Norm.sln | 68 - win32/Norm.suo | Bin 20992 -> 0 bytes win32/NormDll.vcproj | 171 - win32/NormLib.dsp | 216 - win32/NormLib.vcproj | 326 - win32/norm/norm.dsp | 115 - win32/norm/norm.vcproj | 230 - win32/normTest.dsp | 103 - win32/normTest.vcproj | 164 - win32/npc.vcproj | 160 - wscript | 158 + 204 files changed, 29178 insertions(+), 10600 deletions(-) create mode 100644 BUILD.TXT delete mode 100644 NormDeveloperGuide.html delete mode 100644 NormDeveloperGuide.pdf create mode 100644 NormSocketBindingNotes.txt delete mode 100644 NormUserGuide.doc delete mode 100644 NormUserGuide.pdf create mode 100644 README-Java.txt create mode 100644 README-PyNorm.txt delete mode 100644 common/normEncoderRS8.h create mode 100644 doc/NormDeveloperGuide.html create mode 100644 doc/NormDeveloperGuide.pdf create mode 100644 doc/NormDeveloperGuide.xml rename NormLogo.gif => doc/NormLogo.gif (100%) create mode 100644 doc/NormUserGuide.pdf create mode 100644 doc/NormUserGuide.xml rename npcUsage.pdf => doc/npcUsage.pdf (100%) create mode 100644 examples/java/NormFileRecv.java create mode 100644 examples/java/NormFileSend.java create mode 100644 examples/java/NormFileSendRecv.java create mode 100644 examples/java/NormStreamRecv.java create mode 100644 examples/java/NormStreamSend.java create mode 100644 examples/java/README.TXT create mode 100755 examples/java/filerecv.sh create mode 100755 examples/java/filesend.sh create mode 100755 examples/java/streamrecv.sh create mode 100755 examples/java/streamsend.sh create mode 100644 examples/normDataExample.cpp create mode 100644 examples/normDataRecv.cpp create mode 100644 examples/normDataSend.cpp create mode 100644 examples/normStreamRecv.cpp create mode 100644 examples/normStreamSend.cpp create mode 100755 examples/python/debugStats.py create mode 100755 examples/python/fecBufferStats.py create mode 100755 examples/python/fileRecvManager.py create mode 100755 examples/python/normFileFeedback.py create mode 100755 examples/python/normFileRecv.py create mode 100755 examples/python/normFileSend.py create mode 100755 examples/python/rawRecv.py create mode 100755 examples/python/streamChat.py rename {common => include}/galois.h (100%) rename {common => include}/normApi.h (64%) create mode 100644 include/normEncoder.h rename common/normEncoder.h => include/normEncoderMDP.h (70%) create mode 100644 include/normEncoderRS16.h create mode 100644 include/normEncoderRS8.h rename {common => include}/normFile.h (100%) rename {common => include}/normMessage.h (59%) rename {common => include}/normNode.h (73%) rename {common => include}/normObject.h (86%) rename {common => include}/normPostProcess.h (100%) rename {common => include}/normSegment.h (89%) rename {common => include}/normSession.h (53%) rename {common => include}/normSimAgent.h (76%) rename {common => include}/normVersion.h (98%) rename {unix => makefiles}/Makefile.arm-linux (99%) create mode 100644 makefiles/Makefile.common rename {unix => makefiles}/Makefile.freebsd (99%) rename {unix => makefiles}/Makefile.hpux (100%) create mode 100644 makefiles/Makefile.iphone rename {unix => makefiles}/Makefile.linux (86%) rename {unix => makefiles}/Makefile.macosx (82%) rename {unix => makefiles}/Makefile.mklinux (100%) rename {unix => makefiles}/Makefile.netbsd (100%) rename {unix => makefiles}/Makefile.sgi (100%) rename {unix => makefiles}/Makefile.solaris (100%) rename {unix => makefiles}/Makefile.solx86 (100%) create mode 100644 makefiles/android/AndroidManifest.xml create mode 100644 makefiles/android/README create mode 100644 makefiles/android/ant.properties create mode 100644 makefiles/android/build.xml create mode 100644 makefiles/android/jni/Android.mk create mode 100644 makefiles/android/jni/Application.mk create mode 100644 makefiles/android/proguard-project.txt create mode 100644 makefiles/android/project.properties create mode 100644 makefiles/java/Makefile.common create mode 100644 makefiles/java/Makefile.linux create mode 100644 makefiles/java/build.xml create mode 100644 makefiles/win32/Norm.sln create mode 100644 makefiles/win32/Norm.suo create mode 100644 makefiles/win32/NormDll.vcproj create mode 100644 makefiles/win32/NormLib.vcproj create mode 100644 makefiles/win32/NormLib.vcproj.vs2008 create mode 100644 makefiles/win32/norm/norm.vcproj create mode 100644 makefiles/win32/normTest.vcproj create mode 100644 makefiles/win32/npc.vcproj rename {win32 => makefiles/win32}/win32PostProcess.cpp (100%) rename {wince => makefiles/wince}/Norm.vco (100%) rename {wince => makefiles/wince}/Norm.vcp (100%) rename {wince => makefiles/wince}/Norm.vcw (100%) rename {wince => makefiles/wince}/normApp/normApp.vcp (100%) delete mode 100644 ns/ns-2.1b7a-Makefile.in delete mode 100644 ns/ns-2.1b9-Makefile.in create mode 100644 src/common/fecTest.cpp rename {common => src/common}/galois.cpp (100%) create mode 100644 src/common/n2m.cpp rename {common => src/common}/normApi.cpp (74%) rename {common => src/common}/normApp.cpp (67%) create mode 100644 src/common/normEncoder.cpp rename common/normEncoder.cpp => src/common/normEncoderMDP.cpp (78%) create mode 100644 src/common/normEncoderRS16.cpp rename {common => src/common}/normEncoderRS8.cpp (77%) rename {common => src/common}/normFile.cpp (92%) rename {common => src/common}/normMessage.cpp (67%) rename {common => src/common}/normNode.cpp (61%) rename {common => src/common}/normObject.cpp (76%) rename {common => src/common}/normPostProcess.cpp (94%) rename {common => src/common}/normPrecode.cpp (90%) rename {common => src/common}/normSegment.cpp (89%) rename {common => src/common}/normSession.cpp (58%) rename {common => src/common}/normSimAgent.cpp (50%) rename {common => src/common}/normTest.cpp (85%) rename {common => src/common}/normThreadTest.cpp (94%) create mode 100644 src/common/pcap2norm.cpp rename {common => src/common}/raft.cpp (78%) rename {common => src/common}/rules.txt (100%) create mode 100644 src/java/.classpath create mode 100644 src/java/.project create mode 100644 src/java/jni/normDataJni.cpp create mode 100644 src/java/jni/normDataJni.h create mode 100644 src/java/jni/normEventJni.cpp create mode 100644 src/java/jni/normEventJni.h create mode 100644 src/java/jni/normFileJni.cpp create mode 100644 src/java/jni/normFileJni.h create mode 100644 src/java/jni/normInstanceJni.cpp create mode 100644 src/java/jni/normInstanceJni.h create mode 100644 src/java/jni/normJni.cpp create mode 100644 src/java/jni/normJni.h create mode 100644 src/java/jni/normNodeJni.cpp create mode 100644 src/java/jni/normNodeJni.h create mode 100644 src/java/jni/normObjectJni.cpp create mode 100644 src/java/jni/normObjectJni.h create mode 100644 src/java/jni/normSessionJni.cpp create mode 100644 src/java/jni/normSessionJni.h create mode 100644 src/java/jni/normStreamJni.cpp create mode 100644 src/java/jni/normStreamJni.h create mode 100644 src/java/src/mil/navy/nrl/norm/NormData.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormEvent.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormFile.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormInstance.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormNode.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormObject.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormSession.java create mode 100644 src/java/src/mil/navy/nrl/norm/NormStream.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormAckingStatus.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormEventType.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormFlushMode.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormNackingMode.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormObjectType.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormProbingMode.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormRepairBoundary.java create mode 100644 src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java create mode 100644 src/java/src/mil/navy/nrl/norm/io/INormEventListener.java create mode 100644 src/java/src/mil/navy/nrl/norm/io/NormInputStream.java create mode 100644 src/java/src/mil/navy/nrl/norm/io/NormOutputStream.java create mode 100644 src/java/src/mil/navy/nrl/norm/io/StreamBreakException.java create mode 100644 src/pynorm/__init__.py create mode 100644 src/pynorm/constants.py create mode 100644 src/pynorm/core.py create mode 100644 src/pynorm/event.py create mode 100644 src/pynorm/extra/__init__.py create mode 100644 src/pynorm/extra/logparser.py create mode 100644 src/pynorm/extra/manager.py create mode 100644 src/pynorm/instance.py create mode 100644 src/pynorm/node.py create mode 100644 src/pynorm/object.py create mode 100644 src/pynorm/session.py rename {ns => src/sim/ns}/README.txt (100%) rename {ns => src/sim/ns}/example.tcl (100%) rename {ns => src/sim/ns}/nackCount.cpp (100%) rename {ns => src/sim/ns}/normcc.tcl (80%) rename {ns => src/sim/ns}/ns226-Makefile.in (100%) rename {ns => src/sim/ns}/nsNormAgent.cpp (100%) rename {ns => src/sim/ns}/nsNormAgent.h (100%) rename {ns => src/sim/ns}/simplenorm.tcl (94%) rename {ns => src/sim/ns}/sizeAve.cpp (100%) rename {ns => src/sim/ns}/suppress.tcl (100%) rename {opnet => src/sim/opnet}/11.5/OpnetNormProcess.h (100%) rename {opnet => src/sim/opnet}/11.5/norm_protolib.pr.cpp (100%) rename {opnet => src/sim/opnet}/11.5/norm_protolib.pr.m (100%) rename {opnet => src/sim/opnet}/12.0/OpnetNormProcess.h (100%) rename {opnet => src/sim/opnet}/12.0/norm_protolib.pr.cpp (100%) rename {opnet => src/sim/opnet}/12.0/norm_protolib.pr.m (100%) rename {unix => src/unix}/unixPostProcess.cpp (100%) delete mode 100644 unix/Makefile.common create mode 100755 waf delete mode 100644 win32/Norm.dsw delete mode 100644 win32/Norm.opt delete mode 100644 win32/Norm.sln delete mode 100644 win32/Norm.suo delete mode 100644 win32/NormDll.vcproj delete mode 100644 win32/NormLib.dsp delete mode 100644 win32/NormLib.vcproj delete mode 100644 win32/norm/norm.dsp delete mode 100644 win32/norm/norm.vcproj delete mode 100644 win32/normTest.dsp delete mode 100644 win32/normTest.vcproj delete mode 100644 win32/npc.vcproj create mode 100644 wscript diff --git a/BUILD.TXT b/BUILD.TXT new file mode 100644 index 0000000..fd851aa --- /dev/null +++ b/BUILD.TXT @@ -0,0 +1,72 @@ +Building NORM +============= + +NORM can be built using the Waf build tool, included in the distribution. +To see a full list of options, run: + + ./waf -h + + +Configuring +----------- + +To perform the configure checks, run: + + ./waf configure + +Some options for the configure step: + + --prefix= - Directory to install files to (Default - /usr/local) + + --debug - Builds a debug build (with debugging symbols), otherwise an + optimized library is built. + + --build-python - Builds the Python extension + --build-java - Builds the Java extension + You must set the JAVA_HOME environment variable to the location of your + JDK directory + +Building +-------- + +To build the library, simply run: + + ./waf + +To build examples along with the library, run: + + ./waf --targets=ex1,ex2,... + +Where ex1,ex2 is the name of the example you want to build (see ./waf list). +Additionally, you can add the "--targets=*" flag to build all the example +programs. + +Installing +---------- + +To install, run: + + ./waf install + +This will install the compiled library and headers to wherever your prefix was +specified. (See configure flags) + +Uninstalling +------------ + +Waf tracks the files it installs, so run: + + ./waf uninstall + +to remove all files from a previous ./waf install + +Cleaning +-------- + + ./waf clean + +will delete all compiled files and configuration settings. + + ./waf distclean + +will do the same as clean, and additionally delete the waf cache files. diff --git a/NormDeveloperGuide.html b/NormDeveloperGuide.html deleted file mode 100644 index cbd802b..0000000 --- a/NormDeveloperGuide.html +++ /dev/null @@ -1,4404 +0,0 @@ - - - - - NORM Developer’s Guide - - - - - - - - - - - - - - - - -
-

-
-

- NORM
Developer’s -
Guide
(version 1.4b1)

-
-

Background

-

This -document describes an application programming interface (API) for the -Nack-Oriented -Reliable Multicast (NORM) -protocol implementation developed by the Protocol Engineering and -Advance Networking (PROTEAN) -Research Group of the United States Naval -Research Laboratory -(NRL). The NORM protocol provides general purpose reliable data -transport for applications wishing to use Internet Protocol (IP) -Multicast services for group data delivery. NORM can also support -unicast (point-to-point) data communication and may be used for such -when deemed appropriate. The current NORM protocol specification is -given in the Internet -Engineering Task Force -(IETF) RFC -3940.

-

The -NORM protocol is designed to provide end-to-end reliable transport of -bulk data objects or streams over generic IP multicast routing and -forwarding services. NORM uses a selective, negative acknowledgement -(NACK) mechanism for transport reliability and offers additional -protocol mechanisms to conduct reliable multicast sessions with -limited "a priori" coordination among senders and -receivers. A congestion control scheme is specified to allow the NORM -protocol fairly share available network bandwidth with other -transport protocols such as Transmission Control Protocol (TCP). It -is capable of operating with both reciprocal multicast routing among -senders and receivers and with asymmetric connectivity (possibly a -unicast return path) from the senders to receivers. The protocol -offers a number of features to allow different types of applications -or possibly other higher level transport protocols to utilize its -service in different ways. The protocol leverages the use of -FEC-based repair and other proven reliable multicast transport -techniques in its design.

-
-

The - NRL NORM library attempts to provide a general useful capability for - development of reliable multicast applications for bulk file or - other data delivery as well as support of stream-based transport - with possible real-time delivery requirements. The API allows access - to many NORM protocol parameters and control functions to tailor - performance for specific applications. While default parameters, - where provided, can be useful to a potential wide range of - requirements, the many different possible group communication - paradigms dictate different needs for different applications. Even - with NORM, the developer should have a thorough understanding of the - specific application's group communication needs.

-

Overview

-

API Initialization

-

Session Creation and Control

-

Data Transport

-

- Data Transmission

-

- Data Reception

-

API Event Notification

-

Build Notes

-

Unix Platforms

-

Win32/WiNCE Platforms

-

API Reference

-

API Variable Types and Constants

-

- NormInstanceHandle

-

- NormSessionHandle

-

- NormSessionId

-

- NormNodeHandle

-

- NormNodeId

-

- NormObjectHandle

-

- NormObjectType

-

- NormSize

-

- NormObjectTransportId

-

- NormEventType

-

- NormEvent

-

- NormDescriptor

-

- NormFlushMode

-

- NormProbingMode

-

- NormNackingMode

-

- NormRepairBoundary

-

- NormAckingStatus

-

API Initialization and Operation

-

- NormCreateInstance()

-

- NormDestroyInstance()

-

- NormStopInstance()

-

- NormRestartInstance()

-

- NormSetCacheDirectory()

-

- NormGetNextEvent()

-

- NormGetDescriptor()

-

Session Creation and Control Functions

-

- NormCreateSession()

-

- NormDestroySession()

-

- NormSetUserData()

-

- NormGetUserData()

-

- NormGetLocalNodeId()

-

- NormSetTxPort()

-

- NormSetRxPortReuse()

-

- NormSetMulticastInterface()

-

- NormSetTTL()

-

- NormSetTOS()

-

- NormSetLoopback()

-

NORM Sender Functions

-

- NormStartSender()

-

- NormStopSender()

-

- NormSetTransmitRate()

-

- NormSetTxSocketBuffer()

-

- NormSetCongestionControl()

-

- NormSetTransmitRateBounds()

-

- NormSetTransmitCacheBounds()

-

- NormSetAutoParity()

-

- NormGetGrttEstimate()

-

- NormSetGrttEstimate()

-

- NormSetGrttMax()

-

- NormSetGrttProbingMode()

-

- NormSetGrttProbingInterval()

-

- NormSetBackoffFactor()

-

- NormSetGroupSize()

-

- NormFileEnqueue()

-

- NormDataEnqueue()

-

- NormRequeueObject()

-

- NormStreamOpen()

-

- NormStreamClose()

-

- NormStreamWrite()

-

- NormStreamFlush()

-

- NormStreamSetAutoFlush()

-

- NormStreamSetPushEnable()

-

- NormStreamHasVacancy()

-

- NormStreamMarkEom()

-

- NormSetWatermark()

-

- NormAddAckingNode()

-

- NormRemoveAckingNode()

-

- NormGetAckingStatus()

-

NORM Receiver Functions

-

- NormStartReceiver()

-

- NormStopReceiver()

-

- NormSetRxSocketBuffer()

-

- NormSetSilentReceiver()

-

- NormSetDefaultUnicastNack()

-

- NormNodeSetUnicastNack()

-

- NormSetDefaultNackingMode()

-

- NormNodeSetNackingMode()

-

- NormObjectSetNackingMode()

-

- NormSetDefaultRepairBoundary()

-

- NormNodeSetRepairBoundary()

-

- NormStreamRead()

-

- NormStreamSeekMsgStart()

-

- NormStreamGetReadOffset()

-

NORM Object Functions

-

- NormObjectGetType()

-

- NormObjectHasInfo()

-

- NormObjectGetInfoLength()

-

- NormObjectGetInfo()

-

- NormObjectGetSize()

-

- NormObjectGetBytesPending()

-

- NormObjectCancel()

-

- NormObjectRetain()

-

- NormObjectRelease()

-

- NormFileGetName()

-

- NormFileRename()

-

- NormDataAccessData()

-

- NormDataDetachData()

-

- NormObjectGetSender()

-

NORM Node Functions

-

- NormNodeGetId()

-

- NormNodeGetAddress()

-

- NormNodeGetGrtt()

-

- NormNodeRetain()

-

- NormNodeRelease()

-
-

Overview

-

The -NORM API has been designed to provide simple, straightforward access -to and control of NORM protocol state and functions. Functions are -provided to create and initialize instances of the NORM API and -associated transport sessions (NormSessions). Subsequently, -NORM data transmission (NormSender) operation can be activated -and the application can queue various types of data (NormObjects) -for reliable transport. Additionally or alternatively, NORM reception -(NormReceiver) operation can also be enabled on a per-session -basis and the protocol implementation alerts the application of -receive events.

-

By -default, the NORM API will create an operating system thread in which -the NORM protocol engine runs. This allows user application code and -the underlying NORM code to execute somewhat independently of one -another. The NORM protocol thread notifies the application of various -protocol events through a thread-safe event dispatching mechanism and -API calls are provided to allow the application to control NORM -operation. (Note: API mechanisms for lower-level, non-threaded -control and execution of the NORM protocol engine code may -also be provided in the future.)

-

The -NORM API operation can be roughly summarized with the following -categories of functions:

-
    -
  1. - API Initialization

    -
  2. - Session Creation and Control

    -
  3. - Data Transport

    -
  4. - API Event Notification

    -
-

Note -the order of these categories roughly reflects the order of function -calls required to use NORM in an application. The first step is to -create and initialize, as needed, at least one instance of the NORM -API. Then one or more NORM transport sessions (where a “session” -corresponds to data exchanges on a given multicast group (or unicast -address) and host port number) may be created and controlled. -Applications may participate as senders and/or receivers within a -NORM session. NORM senders transmit data to the session destination -address (usually an IP multicast group) while receivers are notified -of incoming data. The NORM API provides and event notification scheme -to notify the application of significant sender and receiver events. -There are also a number support functions provided for the -application to control and monitor its participation within a NORM -transport session.

-

API Initialization

-

The -NORM API requires that an application explicitly create at least one -instance of the NORM protocol engine which is subsequently used as a -conduit for further NORM API calls. By default, the NORM protocol -engine runs in its own operating system thread and interacts with the -application in a thread-safe manner through the API calls and event -dispatching mechanism. -

-

In -general, only a single thread should access the NormGetNextEvent() -API call for a given NormInstance. This -function serves as the conduit for delivering NORM protocol engine -events to the application. A NORM application can be designed to be -single-threaded, even with multiple active NormSessions, but -also multiple API instances can be created (see NormCreateInstance()) -as needed for applications with specific requirements for accessing -and controlling participation in multiple NormSessions from -separate operating system multiple threads. Or, alternatively, a -single NormInstance could be used, with a -"master thread" serving as an intermediary between the -NormGetNextEvent() function, -demultiiplexing and dispatching events as appropriate to other "child -threads" that are created to handle "per-NormSession" -input/output. The advantage of this alternative approach is that the -end result would be one NORM protocol engine thread plus one "master -thread" plus one "child thread" per NormSession -instead of two threads (protocol engine plus application thread) per -NormSession if such multi-threaded operation is needed by the -application.

-

Session Creation and Control

-

Once -an API instance has been successfully created, the application may -then create NORM transport session instances as needed. The -application can participate in each session as a sender and/or -receiver of data. If an application is participating as a sender, it -may enqueue data transport objects for transmission. The control of -transmission is largely left to the senders and API calls are -provided to control transmission rate, FEC parameters, etc. -Applications participating as receivers will be notified via the NORM -API's event dispatching mechanism of pending and completed reliable -reception of data along with other significant events. Additionally, -API controls for some optional NORM protocol mechanisms, such as -positive acknowledgment collection, are also provided.

-

Note -when multiple senders are involved, receivers allocate system -resources (buffer space) for each active sender. With a very large -number of concurrently active senders, this may translate to -significant memory allocation on receiver nodes. Currently, the API -allows the application to control how much buffer space is allocated -for each active sender (NOTE: In the future, API functions may be -provided limit the number of active senders monitored and/or provide -the application with finer control over receive buffer allocation, -perhaps on a per sender basis). -

-

Data Transport

-

The -NORM protocol supports transport of three basic types of data -content. These include the types NORM_OBJECT_FILE -and NORM_OBJECT_DATA which -represent predetermined, fixed-size application data content. The -only differentiation with respect to these two types is the implicit -“hint” to the receiver to use non-volatile (i.e. file system) -storage or memory. This “hint” lets the receiver allocate -appropriate storage space with no other information on the incoming -data. The NORM implementation reads/writes data for the -NORM_OBJECT_FILE type directly -from/to file storage, while application memory space is accessed for -the NORM_OBJECT_DATA type. The -third data content type, NORM_OBJECT_STREAM, -represents unbounded, possibly persistent, streams of data content. -Using this transport paradigm, traditional, byte-oriented streaming -transport service (e.g. similar to that provided by a TCP socket) can -be provided. Additionally, NORM has provisions for -application-defined message-oriented transport where receivers can -recover message boundaries without any “handshake” with the -sender. Stream content is buffered by the NORM implementation for -transmission/retransmission and as it is received.

-

Data Transmission

-

The -behavior of data transport operation is largely placed in the control -of the NORM sender(s). NORM senders controls their data transmission -rate, forward error correction (FEC) encoding settings, and -parameters controlling feedback from the receiver group. Multiple -senders may operate in a session, each with independent transmission -parameters. NORM receivers learn needed parameter values from fields -in NORM message headers.

-

NORM -transport “objects” (file, data, or stream) are queued for -transmission by NORM senders. NORM senders may also cancel -transmission of objects at any time. The NORM sender controls the -transmission rate either manually (fixed transmission rate) or -automatically when NORM congestion control operation is enabled. The -NORM congestion control mechanism is designed to be "friendly" -to other data flows on the network, fairly sharing available -bandwidth.

-

By -default, the NORM sender transmits application-enqueued data content, -providing repair transmissions (usually in the form of FEC messages) -only when requested by NACKs from the receivers. However, the -application may also configure NORM to proactively send some amount -of FEC content along with the original data content to create a -"robust" transmission that, in some cases, may be reliably -received without any NACKing activity. This can allow for some degree -of reliable protocol operation even without receiver feedback -available. NORM senders may also requeue (within the limits of -"transmit cache" settings) objects for repeat transmission, -and receivers may combine together multiple transmissions to reliably -receive content. Additionally, hybrid proactive/reactive FEC repair -operation is possible with the receiver NACK process as a "backup" -for when network packet loss exceeds the repair capability of the -proactive FEC settings.

-

The -NRL NORM implementation also supports optional collection of positive -acknowledgment from a subset of the receiver group at -application-determined positions during data transmission. The NORM -API allows the application to specify the receiver subset ("acking -node list") and set "watermark" points for which -positive acknowledgement is collected. This process can provide the -application with explicit flow control for an application-determined -critical set of receivers in the group.

-

For -a NORM application to perform data transmission, it must first create -a session using NormCreateSession() and -make a call to NormStartSender() before -sending actual user data. The functions NormEnqueueFile(), -NormEnqueueData(), and NormStreamWrite() -are available for the application to pass data to the NORM protocol -engine for transmission. Note that to use NormStreamWrite(), -a "sender stream" must first be created using -NormStreamOpen(). -

-

The -calls to enqueue transport objects or write to a stream may be called -at any time, but the NORM_TX_QUEUE_EMPTY -and NORM_TX_QUEUE_VACANCY -notification events (see NormGetNextEvent()) -provide useful cues for when these functions may be successfully -called. Typically, an application might catch both -NORM_TX_QUEUE_EMPTY and -NORM_TX_QUEUE_VACANCY event types -as cues for enqueuing additional transport objects or writing to a -stream. However, an application may choose to cue off of -NORM_TX_QUEUE_EMPTY only if it -wishes to provide the "freshest" data to NORM for -transmission. The advantage of additionally using -NORM_TX_QUEUE_VACANCY is that if -the application uses this cue to fill up NORM transport object or -stream buffers, it can keep the NORM stream busy sending data and -realize the highest possible transmission rate when attempting very -high speed communication (Otherwise, the NORM protocol engine may -experience some "dead air time" waiting for the application -thread to respond to a NORM_TX_QUEUE_EMPTY -event). Note the sender application can control buffer depths as -needed with the NormSetTransmitCacheBounds() and NormStreamOpen() -calls.

-

Another -cue that can be leveraged by the sender application to determine when -it is appropriate to enqueue (or write) additional data for -transmission is the NORM_TX_WATERMARK_COMPLETED -event. This event is posted when the flushing or explicit positive -acknowledgment collection process has completed for a "watermark" -point in transmission that was set by the sender (see -NormSetWatermark() and -NormAddAckingNode()). A list of -NormNodeIds can be supplied from which -explicit acknowledgement is expected and/or the NormNodeId -NORM_NODE_NONE can be set (using NormAddAckingNode()) -for completion of a NACK-based version of the watermark flushing -procedure. This flushing process can be used as a flow control -mechanism for NORM applications. Note this is distinct from NORM's -congestion control mechanism that, while it provides network-friendly -transmission rate control, does guarantee flow control to receiving -nodes.

-

Data Reception

-

NORM -receiver applications learn of active senders and their corresponding -pending and completed data transfers, etc via the API event -dispatching mechanism. By default, NORM receivers use NACK messages -to request repair of transmitted content from the originating sender -as needed to achieve reliable transfer. Some API functions are -available to provide some additional control over the NACKing -behavior, such as initially NACKing for NORM_INFO content only or -even to the extent of disabling receiver feedback (silent receiver or -emission-controlled (EMCON) operation) entirely. Otherwise, the -parameters and operation of reliable data transmission are left to -sender applications and receivers learn of sender parameters in NORM -protocol message headers and are instructed by NORM_CMD messages from -the sender(s).

-

API Event Notification

-

An -asynchronous event dispatching mechanism is provided to notify the -application of significant NORM protocol events. The centerpiece of -this is the NormGetNextEvent() function -which can be used to retrieve the next NORM protocol engine event in -the form of a NormEvent structure. This -function will typically block until a NormEvent -occurs. However, non-blocking operation may be achieved by using the -NormGetDescriptor() call to get a value -(file descriptor (Unix) or HANDLE (Win32) suitable for use in a -asynchronous I/O monitoring functions such as select() (Unix) -or MsgWaitForMultipleObjects() (Win32). The descriptor will be -signaled when a NormEvent is available. -For Win32 platforms, dispatching of a user-defined Windows message -for NORM event notification is also planned for a future update to -the API.

-

Build Notes

-

To -build applications that use the NORM library, a path to the -"normApi.h" header file must be provided and the linker -step needs to reference the NORM library file ("libnorm.a" -for Unix platforms and "Norm.lib" for Win32 platforms). -NORM also depends upon the NRL Protean Protocol Prototyping toolkit -"Protokit" library (a.k.a "Protolib") (static -library files "libProtokit.a" for Unix and "Protokit.lib" -for Win32). Shared or dynamically-linked versions of these libraries -may also be built from the NORM source code or provided. Depending -upon the platform, some additional library dependencies may be -required to support the needs of NORM and/or Protokit. These are -described below.

-

Unix Platforms

-

NORM -has been built and tested on Linux (various architectures), MacOS -(BSD), Solaris, and IRIX (SGI) platforms. The code should be readily -portable to other Unix platforms.

-

To -support IPv6 operation, the NORM and the Protokit library must -be compiled with the "HAVE_IPV6" macro defined. This is -default in the NORM and Protokit Makefiles for platforms that -support IPv6. It is important that NORM and Protokit be built -with this macro defined the same. With NORM, it is recommended that -"large file support" options be enabled when possible.

-

The -NORM API uses threading so that the NORM protocol engine may run -independent of the application. Thus the "POSIX Threads" -library must be included ("-'pthread") in the linking step. -MacOS/BSD also requires the addition of the "-lresolv" -(resolver) library and Solaris requires the dynamic loader, -network/socket, and resolver libraries ("–lnsl –lsocket -–lresolv") to achieve successful compilation. The Makefiles in -the NORM source code distribution are a reference for these -requirements. Note that MacOS 9 and earlier are not supported.

-

Additionally, -it is critical that the _FILE_OFFSET_BITS macro be consistently -defined for the NORM library build and the application build using -the library. The distributed NORM Makefiles have -–D_FILE_OFFSET_BITS=64 set in the compilation to enable "large -file support". Applications built using NORM should have the -same compilation option set to operate correctly (The definition of -the NormSize type in "normApi.h" -depends upon this compilation flag).

-

Win32/WiNCE Platforms

-

NORM -has been built using Microsoft's Visual C++ (6.0 and .NET) and -Embedded VC++ 4.2 environments. In addition to proper macro -definitions (e.g., HAVE_IPV6, etc) that are included in the -respective "Protokit" and "NORM" project files, -it is important that common code generation settings be used when -building the NORM application. The NORM and Protokit projects are -built with the "Multithreading DLL" library usage set. The -NORM API requires multithreading support. This is a critical setting -and numerous compiler and linker errors will result if this is not -properly set for your application project.

-

NORM -and Protokit also depend on the Winsock 2.0 ("ws2_32.lib" -(or "ws2.lib" (WinCE)) and the IP Helper API -("iphlpapi.lib") libraries and these must be included in -the project "Link" attributes.

-

An -additional note is that a bug in VC++ 6.0 and earlier compilers -(includes embedded VC++ 4.x compilers) prevent compilation of -Protokit-based code with debugging capabilities enabled. -However, this has been resolved in VC++ .NET and is hoped to be -resolved in the future for the WinCE build tools.

-

Operation -on Windows NT4 (and perhaps other older Windows operating systems) -requires that the compile time macro WINVER=0x0400 -defined. This is because the version of the IP Helper API library -(iphlpapi.lib) used by Protolib (and hence NORM) for -this system doesn't support some of the functions defined for this -library. This may be related to IPv6 support issues so it may be -possible that the Protolib build could be tweaked to provide a -single binary executable suitable for IPv4 operation only across a -large range of Windows platforms.

-

API Reference

-

This -section provides a reference to the NORM API variable types, -constants and functions.

-

API Variable Types and Constants

-

The -NORM API defines and enumerates a number of supporting variable types -and values which are used in different function calls. The variable -types are described here.

-

NormInstanceHandle

-

The -NormInstanceHandle type is returned when -a NORM API instance is created (see NormCreateInstance()). -This handle can be subsequently used for API calls which require -reference to a specific NORM API instance. By default, each NORM API -instance instantiated creates an operating system thread for protocol -operation. Note that multiple NORM transport sessions may be created -for a single API instance. In general, it is expected that -applications will create a single NORM API instance, but some -multi-threaded application designs may prefer multiple corresponding -NORM API instances. The value NORM_INSTANCE_INVALID -corresponds to an invalid API instance.

-

NormSessionHandle

-

The -NormSessionHandle type is used to -reference NORM transport sessions which have been created using the -NormCreateSession() API call. Multiple -NormSessionHandles may be associated with -a given NormInstanceHandle. The special -value NORM_SESSION_INVALID is used to -refer to invalid session references.

-

NormSessionId

-

The -NormSessionId type is used by -applications to uniquely identify their instance of participation as -a sender within a NormSession. This type is a parameter to the -NormStartSender() function. Robust -applications can use different NormSessionId -values when initiating sender operation so that receivers can -discriminate when a sender has terminated and restarted (whether -intentional or due to system failure). For example, an application -could cache its prior NormSessionId value -in non-volatile storage which could then be recovered and incremented -(for example) upon system restart to produce a new value. The -NormSessionId value is used for the value -of the instance_id field in NORM protocol sender messages (see -the NORM protocol specification) and receivers use this field to -detect sender restart within a NormSession.

-

NormNodeHandle

-

The -NormNodeHandle type is used to reference -state kept by the NORM implementation with respect to other -participants within a NormSession. Most typically, the -NormNodeHandle is used by receiver -applications to dereference information about remote senders of data -as needed. The special value NORM_NODE_INVALID -corresponds to an invalid reference.

-

NormNodeId

-

The -NormNodeId type corresponds to a 32-bit -numeric value which should uniquely identify a participant (node) in -a given NormSession. The NormNodeGetId() -function can be used to retrieve this value given a valid -NormNodeHandle. The special value -NORM_NODE_NONE corresponds to an invalid -(or null) node while the value NORM_NODE_ANY -serves as a wildcard value for some functions.

-

NormObjectHandle

-

The -NormObjectHandle type is used to -reference state kept for data transport objects being actively -transmitted or received. The state kept for NORM transport objects is -temporary, but the NORM API provides a function to persistently -retain state associated with a sender or receiver NormObjectHandle -(see NormObjectRetain()) if needed. For -sender objects, unless explicitly retained, the NormObjectHandle -can be considered valid until the referenced object is explicitly -canceled (see NormObjectCancel()) or -purged from the sender transmission queue (see the event -NORM_TX_OBJECT_PURGED). For -receiver objects, these handles should be treated as valid only until -a subsequent call to NormGetNextEvent() -unless, again, specifically retained. The special value -NORM_OBJECT_INVALID corresponds to -an invalid transport object reference.

-

NormObjectType

-

The -NormObjectType type is an enumeration of -possible NORM data transport object types. As previously mentioned, -valid types include:

-
    -
  1. NORM_OBJECT_FILE

    -
  2. - NORM_OBJECT_DATA, and

    -
  3. NORM_OBJECT_STREAM

    -
-

Given -a NormObjectHandle, the application may -determine an object's type using the NormObjectGetType() -function call. A special NormObjectType -value, NORM_OBJECT_NONE, indicates -an invalid object type. -

-

NormSize

-

The -NormSize is the type used for NormObject -size information. For example, the NormObjectGetSize() -function returns a value of type NormSize. -The range of NormSize values depends upon -the operating system and NORM library compilation settings. With -"large file support" enabled, as is the case with -distributed NORM library "Makefiles", the NormSize -type is a 64-bit integer. However, some platforms may support only -32-bit object sizes.

-

NormObjectTransportId

-

The -NormObjectTransportId type is a 16-bit -numerical value assigned to NormObjects by senders during -active transport. These values are temporarily unique with respect to -a given sender within a NormSession and may be "recycled" -for use for future transport objects. NORM sender nodes assign these -values in a monotonically increasing fashion during the course of a -session as part of protocol operation. Typically, the application -should not need access to these values, but an API call -NormObjectGetTransportId() is provided to -retrieve these values if needed. (Note this type may be deprecated -– it may not be needed at all if the NormObjectRequeue() function -(TBD) is implemented using handles only, but _some_ applications -requiring persistence even after a system reboot may need the ability -to recall previous transport ids?)

-

NormEventType

-

The -NormEventType is an enumeration of NORM -API events. "Events" are used by the NORM API to signal the -application of significant NORM protocol operation events (e.g., -receipt of a new receive object, etc). A description of possible -NormEventType values and their -interpretation is given below. The function call NormGetNextEvent() -is used to retrieve events from the NORM protocol engine.

-

NormEvent

-

The -NormEvent type is a structure used to -describe significant NORM protocol events. This structure is defined -as follows:

-

-typedef -struct
{
    NormEventType     type;
    NormSessionHandle session;
    NormNodeHandle    node;
    NormObjectHandle  object;
} -NormEvent;

-

The -type field indicates the NormEventType -and determines how the other fields should be interpreted. Note that -not all NormEventType fields are relevant -to all events. The session, node, -and object fields indicate the applicable -NormSessionHandle, NormNodeHandle, -and NormObjectHandle, respectively, to -which the event applies. NORM protocol events are made available to -the application via the NormGetNextEvent() -function call.

-

NormDescriptor

-

The -NormDescriptor type provides reference to -a file descriptor (Unix) or HANDLE (Win32). For a given -NormInstanceHandle, the -NormGetDescriptor() function can be used -to retrieve a NormDescriptor value that may, in turn, used in -appropriate system calls (e.g. select() or -MsgWaitForMultipleObjects()) to asynchronously monitor the -NORM protocol engine for notification events (see NormEvent -description). -

-

NormFlushMode

-

The -NormFlushMode type consists of the -following enumeration:

-

-enum -NormFlushMode
{
    NORM_FLUSH_NONE,
    NORM_FLUSH_PASSIVE,
    NORM_FLUSH_ACTIVE
};

-

The -interpretation of these values is given in the descriptions of -NormStreamFlush() and -NormStreamSetAutoFlush() functions.

-

NormProbingMode

-

The -NormProbingMode type consists of the -following enumeration:

-

-enum -NormProbingMode
{
    NORM_PROBE_NONE,
    NORM_PROBE_PASSIVE,
    NORM_PROBE_ACTIVE
};

-

The -interpretation of these values is given in the description of -NormSetGrttProbingMode() function.

-

NormNackingMode

-

The -NormNackingMode type consists of the -following enumeration:

-

-enum -NormNackingMode
{
    NORM_NACK_NONE,
    NORM_NACK_INFO_ONLY,
    NORM_NACK_NORMAL
};

-

The -interpretation of these values is given in the descriptions of the -NormSetDefaultNackingMode(), -NormNodeSetNackingMode() and -NormObjectSetNackingMode() functions.

-

NormRepairBoundary

-

The -NormRepairBoundary types consists of the -following enumeration:

-

-enum -NormRepairBoundary
{
    NORM_BOUNDARY_BLOCK,
    NORM_BOUNDARY_OBJECT
};

-

The -interpretation of these values is given in the descriptions of the -NormSetDefaultRepairBoundary() and -NormNodeSetRepairBoundary() functions.

-

NormAckingStatus

-

The -NormAckingStatus consist of the following -enumeration:

-

-enum -NormAckingStatus
{
    NORM_ACK_INVALID,
    NORM_ACK_FAILURE,
    NORM_ACK_PENDING,
    NORM_ACK_SUCCESS
};

-

The -interpretation of these values is given in the descriptions of the -NormGetAckingStatus() function.

-

API Initialization and Operation

-

The -first step in using the NORM API is to create an "instance" -of the NORM protocol engine. Note that multiple instances may be -created by the application if necessary, but generally only a single -instance is required since multiple NormSessions may be -managed under a single NORM API instance.

-

NormCreateInstance()

-

Synopsis

-

-#include <normApi.h>

-

-NormInstanceHandle -NormCreateInstance(bool priorityBoost = false);

-

Description

-

This -function creates an instance of a NORM protocol engine and is the -necessary first step before any other API functions may be used. With -the instantiation of the NORM protocol engine, an operating system -thread is created for protocol execution. The returned -NormInstanceHandle value may be used in -subsequent API calls as needed, such NormCreateSesssion(), -etc. The optional priorityBoost -parameter, when set to a value of true, -specifies that the NORM protocol engine thread be run with higher -priority scheduling. On Win32 platforms, this corresponds to -THREAD_PRIORITY_TIME_CRITICAL and -on Unix systems with the sched_setscheduler() -API, an attempt to get the maximum allowed SCHED_FIFO -priority is made. The use of this option should be carefully -evaluated since, depending upon the application's scheduling priority -and NORM API usage, this may have adverse effects instead of a -guaranteed performance increase!

-

Return Values

-

A -value of NORM_INSTANCE_INVALID is -returned upon failure. The function will only fail if system -resources are unavailable to allocate the instance and/or create the -corresponding thread.

-

NormDestroyInstance()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormDestroyInstance(NormInstanceHandle instance);

-

Description

-

The -NormDestroyInstance() function -immediately shuts down and destroys the NORM protocol engine instance -referred to by the instance -parameter. The application should make no subsequent references to -the indicated NormInstanceHandle or any -other API handles or objects associated with it. However, the -application is still responsible for releasing any object handles it -has retained (see NormObjectRetain() and -NormObjectRelease()). -

-

Return Values

-

The -function has no return value.

-

NormStopInstance()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStopInstance(NormInstanceHandle instance);

-

Description

-

This -function immediately stops the NORM protocol engine thread -corresponding to the given instance -parameter. It also posts a "dummy" notification event so -that if another thread is blocked on a call to NormGetNextEvent(), -that thread will be released. Hence, for some multithreaded uses of -the NORM API, this function may be useful as a preliminary step to -safely coordinate thread shutdown before a call is made to -NormDestroyInstance(). After -NormStopInstance() is called and any -pending events posted prior to its call have been retrieved, -NormGetNextEvent() will return a value of -false.

-

When -this function is invoked, state for any NormSessions -associated with the given instance -is "frozen". The complementary function, -NormRestartInstance() can be subsequently -used to "unfreeze" and resume NORM protocol operation (a -new thread is created and started).

-

Return Values

-

The -function has no return value.

-

NormRestartInstance()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormRestartInstance(NormInstanceHandle instance);

-

Description

-

This -function creates and starts an operating system thread to resume NORM -protocol engine operation for the given instance -that was previously stopped by a call to NormStopInstance(). -It is not expected that this function will be used often, but there -may be special application cases where "freezing" and later -resuming NORM protocol operation may be useful. -

-

Return Values

-

The -function returns true when the -NORM protocol engine thread is successfully restarted, and false -otherwise.

-



-

-

NormSetCacheDirectory()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetCacheDirectory(NormInstanceHandle instance,
                           const -char*        cachePath);

-

Description

-

This -function sets the directory path used by receivers to cache -newly-received NORM_OBJECT_FILE -objects. This function must be called before any file objects may be -received and thus should be called before any calls to -NormStartReceiver() are made. However, -note that the cache directory may be changed even during active NORM -reception. In this case, the new specified directory path will be -used for subsequently-received files. Any files received before a -directory path change will remain in the previous cache location. -Note that the NormFileRename() function -may be used to rename, and thus potentially move, received files -after reception has begun. -

-

The -instance parameter specifies the -NORM protocol engine instance (all NormSessions associated with that -instance share the same cache -path) and the cachePath is a -string specifying a valid (and writable) directory path. The function -returns true on success and false -on failure. The failure conditions are that the indicated directory -does not exist or the process does not have permissions to write.

-

NormGetNextEvent()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormGetNextEvent(NormInstanceHandle instance,
                      NormEvent*         theEvent);

-

Description

-

This -function retrieves the next available NORM protocol event from the -protocol engine. The instance -parameter specifies the applicable NORM protocol engine, and the -theEvent parameter must be -a valid pointer to a NormEvent structure -capable of receiving the NORM event information. For expected -reliable protocol operation, the application should make every -attempt to retrieve and process NORM notification events in a timely -manner.

-

Note -that this is currently the only blocking call in the NORM API. But -non-blocking operation may be achieved by using the -NormGetDescriptor() function to obtain a -descriptor (or HANDLE for WIN32) suitable for asynchronous -input/output (I/O) notification using such system calls as select() -(UNIX) or WaitForMultipleObjects() -(WIN32). The descriptor is signaled when a notification event is -pending and a call to NormGetNextEvent() -will not block.

-

NORM Notification Event Types

-

The -following table enumerates the possible NormEvent -values and describes how these notifications should be -interpreted as they are retrieved by the application via the -NormGetNextEvent() function call.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- Sender Notification Event Types:

-
-

NORM_TX_QUEUE_VACANCY

-
-

- This event indicates that there is room for additional transmit - objects to be enqueued, or, if the handle of NORM_OBJECT_STREAM - is given in the corresponding event "object" field, the - application may successfully write to the indicated stream object. - Note this event is not dispatched until a call to - NormEnqueueFile(), NormEnqueueData(), - or NormStreamWrite() fails because of - a filled transmit cache or stream buffer.

-
-

NORM_TX_QUEUE_EMPTY

-
-

- This event indicates the NORM protocol engine has no new data - pending transmission and the application may enqueue additional - objects for transmission. If the handle of a sender - NORM_OBJECT_STREAM is given in - the corresponding event "object" field, this indicates - the stream transmit buffer has been emptied and the sender - application may write to the stream (Use of NORM_TX_QUEUE_VACANCY - may be preferred for this purpose since it allows the application - to keep the NORM protocol engine busier sending data, resulting in - higher throughput when attempting very high transfer rates).

-
-

NORM_TX_FLUSH_COMPLETED

-
-

- This event indicates that the flushing process the NORM sender - observes when it no longer has data ready for transmission has - completed. The completion of the flushing process is a reasonable - indicator (with a sufficient NORM "robust factor" value) - that the receiver set no longer has any pending repair requests. - Note the use of NORM's optional positive acknowledgement feature - is more deterministic in this regards, but this notification is - useful when there are non-acking (NACK-only) receivers. The - default NORM robust factor of 20 (20 flush messages are sent at - end-of-transmission) provides a high assurance of reliable - transmission, even with packet loss rates of 50%.

-
-

NORM_TX_WATERMARK_COMPLETED

-
-

- This event indicates that the flushing process initiated by a - prior application call to NormSetWatermark() - has completed The posting of this event indicates the appropriate - time for the application to make a call NormGetAckingStatus() - to determine the results of the watermark flushing process.

-
-

NORM_TX_OBJECT_SENT

-
-

- This event indicates that the transport object referenced by the - event's "object" field has completed at least one pass - of total transmission. Note that this does not guarantee that - reliable transmission has yet completed; only that the entire - object content has been transmitted. Depending upon network - behavior, several rounds of NACKing and repair transmissions may - be required to complete reliable transfer.

-
-

NORM_TX_OBJECT_PURGED

-
-

- This event indicates that the NORM protocol engine will no longer - refer to the transport object identified by the event's "object' - field. Typically, this will occur when the application has - enqueued more objects than space available within the set sender - transmit cache bounds (see NormSetTransmitCacheBounds()). - Posting of this notification means the application is free to free - any resources (memory, files, etc) associated with the indicated - "object". After this event, the given "object" - handle (NormObjectHandle) is no longer - valid unless it is specifically retained by the application.

-
-

NORM_LOCAL_SENDER_CLOSED

-
-

- This event is posted when the NORM protocol engine completes the - "graceful shutdown" of its participation as a sender in - the indicated "session" (see NormStopSender()).

-
-

NORM_CC_ACTIVE

-
-

- This event indicates that congestion control feedback from - receivers has begun to be received (This also implies that - receivers in the group are actually present and can be used as a - cue to begin data transmission.). Note that congestion control - must be enabled (see NormSetCongestionControl()) - for this event to be posted. Congestion control feedback can be - assumed to be received until a NORM_CC_INACTIVE - event is posted.

-
-

NORM_CC_INACTIVE

-
-

- This event indicates there has been no recent congestion control - feedback received from the receiver set and that the local NORM - sender has reached its minimum transmit rate. Applications may - wish to refrain from new data transmission until a NORM_CC_ACTIVE - event is posted. This notification is only posted when congestion - control operation is enabled (see NormSetCongestionControl()) - and a previous NORM_CC_ACTIVE - event has occurred.

-
-

- Receiver Notification Event Types:

-
-

NORM_REMOTE_SENDER_NEW

-
-

- This event is posted when a receiver first receives messages from - a specific remote NORM sender. This marks the beginning of the - interval during which the application may reference the provided - "node" handle (NormNodeHandle).

-
-

NORM_REMOTE_SENDER_ACTIVE

-
-

- This event is posted when a previously inactive (or new) remote - sender is detected operating as an active sender within the - session.

-
-

NORM_REMOTE_SENDER_INACTIVE

-
-

- This event is posted after a significant period of inactivity (no - sender messages received) of a specific NORM sender within the - session. The NORM protocol engine frees buffering resources - allocated for this sender when it becomes inactive.

-
-

NORM_REMOTE_SENDER_PURGED

-
-

- This event is posted when the NORM protocol engine frees resources - for, and thus invalidates the indicated "node" handle.

-
-

NORM_RX_OBJECT_NEW

-
-

- This event is posted when reception of a new transport object - begins and marks the beginning of the interval during which the - specified "object" (NormObjectHandle) - is valid.

-
-

NORM_RX_OBJECT_INFO

-
-

- This notification is posted when the NORM_INFO content for the - indicated "object" is received.

-
-

NORM_RX_OBJECT_UPDATED

-
-

- This event indicates that the identified receive "object" - has newly received data content.

-
-

NORM_RX_OBJECT_COMPLETED

-
-

- This event is posted when a receive object is completely received, - including available NORM_INFO content. Unless the application - specifically retains the "object" handle, the indicated - NormObjectHandle becomes invalid and - must no longer be referenced.

-
-

NORM_RX_OBJECT_ABORTED

-
-

- This notification is posted when a pending receive object's - transmission is aborted by the remote sender. Unless the - application specifically retains the "object" handle, - the indicated NormObjectHandle becomes - invalid and must no longer be referenced.

-
-

- Miscellaneous Notification Event Types

-
-

NORM_GRTT_UPDATED

-
-

- This notification indicates that either the local sender estimate - of GRTT has changed, or that a remote sender's estimate of GRTT - has changed. The "sender" member of the NormEvent - is set to NORM_NODE_INVALID if - the local sender's GRTT estimate has changed or to the - NormNodeHandle of the remote sender - that has updated its estimate of GRTT.

-
-

NORM_EVENT_INVALID

-
-

- This NormEventType indicates an - invalid or "null" notification which should be ignored.

-
-



-

-

Return Values

-

This -function generally blocks the thread of application execution until a -NormEvent is available and returns true -when a NormEvent is available. However, -there are some exceptional cases when the function may immediately -return even when no event is pending. In these cases, the return -value is false. -

-

WIN32 Note: A future -version of this API will provide an option to have a user-defined -Window message posted when a NORM API event is pending. (Also some -event filtering calls may be provided (e.g. avoid the potentially -numerous NORM_RX_OBJECT_UPDATED -events if not needed by the application)).

-

NormGetDescriptor()

-

Synopsis

-

-#include <normApi.h>

-

-NormDescriptor -NormGetDescriptor(NormInstanceHandle instance);

-

Description

-

This -function is used to retrieve a NormDescriptor -(integer file descriptor (UNIX) or HANDLE (WIN32)) suitable for -asynchronous I/O notification to avoid blocking calls to -NormGetNextEvent(). A NormDescriptor -is available for each protocol engine instance. -The descriptor (or WIN32 HANDLE) is suitable for use as an input (or -"read") descriptor which is signaled when a NORM protocol -event is ready for retrieval via NormGetNextEvent(). -Hence, a call to NormGetNextEvent() will -not block when the descriptor has been signaled. The select() -system call (UNIX) (or WaitForMultipleObjects() -(WIN32)) can be used to detect when the returned NormDescriptor -is signaled. For the select() call usage, -the NORM descriptor should be treated as a "read" -descriptor.

-

Return Values

-

A -descriptor is returned which is valid until a call to -NormDestroyInstance() is made. Upon -error, a value of NORM_DESCRIPTOR_INVALID -is returned.

-

Session Creation and Control Functions

-

Whether -participating in a NORM protocol session as a sender, receiver, or -both, there are some common API calls used to instantiate a -NormSession and set some common session parameters. Functions -are provided to control network socket and multicast parameters. -Additionally, a "user data" value may be associated with a -NormSessionHandle for programming -convenience when dealing with multiple sessions.

-

NormCreateSession()

-

Synopsis

-

-#include <normApi.h>

-

-NormSessionHandle -NormCreateSession(NormInstanceHandle instance,
                                    const -char*        address,
                                    unsigned -short     port,
                                    NormNodeId         localId);

-

Description

-

This -function creates a NORM protocol session (NormSession) using -the address (multicast or unicast) parameters provided. While session -state is allocated and initialized, active session participation does -not begin until a call is made to NormStartSender() -and/or NormStartReceiver() to join the -specified multicast group (if applicable) and start protocol -operation. The following parameters are required in this function -call:

- - - - - - - - - - - - - - - - - - - -
-

- instance

-
-

- This must be a valid NormInstanceHandle - previously obtained with a call to NormCreateInstance().

-
-

- address

-
-

- This points to a string containing an IP address (e.g. dotted - decimal IPv4 address (or IPv6 address) or name resolvable to a - valid IP address. The specified address - (along with the port number) - determines the destination of NORM messages sent. For multicast - sessions, NORM senders and receivers must use a common multicast - address and port number. For unicast sessions, the sender and - receiver must use a common port number, but specify the other - node's IP address as the session address (Although note that - receiver-only unicast nodes who are providing unicast feedback to - senders will not generate any messages to the session IP address - and the address parameter value - is thus inconsequential for this special case).

-
-

- port

-
-

- This must be a valid, unused port number corresponding to the - desired NORM session address. See the address - parameter description for more details.

-
-

- localId

-
-

- The localId parameter specifies the - NormNodeId that should be used to - identify the application's presence in the NormSession. All - participant's in a NormSession should use unique localId - values. The application may specify a value of NORM_NODE_ANY - or NORM_NODE_ANY for - the localId parameter. In this case, - the NORM implementation will attempt to pick an identifier based - on the host computer's "default" IP address (based on - the computer's default host name). Note there is a chance that - this approach may not provide unique node identifiers in some - situations and the NORM protocol does not currently provide a - mechanism to detect or resolve NormNodeId collisions. Thus, - the application should explicitly specify the localId - unless there is a high degree of confidence that the default IP - address will provide a unique identifier.

-
-

Return Values

-

The -returned NormSessionHandle value is valid -until a call to NormDestroySession() is -made. A value of NORM_SESSION_INVALID -is returned upon error.

-

NormDestroySession()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormDestroySession(NormSessionHandle session);

-

Description

-

This -function immediately terminates the application's participation in -the NormSession identified by the session -parameter and frees any resources used by that session. An exception -to this is that the application is responsible for releasing any -explicitly retained NormObjectHandles -(See NormObjectRetain() and -NormObjectRelease()).

-

Return Values

-

This -function has no returned values.

-

NormSetUserData()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetUserData(NormSessionHandle session, const void* userData);

-

Description

-

This -function allows the application to attach a value to the -previously-created NormSession instance specified by the -session parameter. This value is -not used or interpreted by NORM, but is available to the application -for use at the programmer's discretion. The set userData -value can be later retrieved using the NormGetUserData() -function call.

-

Return Values

-

This -function has no returned values.

-

NormGetUserData()

-

Synopsis

-

-#include <normApi.h>

-

-const void* -NormGetUserData(NormSessionHandle session);

-

Description

-

This -function retrieves the "user data" value set for the -specified session with a prior call to NormSetUserData().

-

Return Values

-

This -function returns the user data value set for the specified session. -If no user data value has been previously set a NULL -(i.e., (const void*)0) value is -returned.

-

NormGetLocalNodeId()

-

Synopsis

-

-#include <normApi.h>

-

-NormNodeId -NormGetLocalNodeId(NormSessionHandle session);

-

Description

-

This -function retrieves the NormNodeId value -used for the application's participation in the NormSession -identified by the session -parameter. The value may have been explicitly set during the -NormCreateSession() call or derived using -the host computer's "default" IP network address.

-

Return Values

-

The -returned value indicates the NormNode identifier used by the -NORM protocol engine for the local application's participation in the -specified NormSession.

-

NormSetTxPort()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetTxPort(NormSessionHandle session,
                   unsigned -short txPort);

-

Description

-

This -function is used to force NORM to use a specific port number for UDP -packets sent for the specified session. -By default, NORM uses separate port numbers for packet transmission -and session packet reception (the receive port is specified as part -of the NormCreateSession() call), -allowing the operating system to pick a freely available port for -transmission. This call allows the application to pick a specific -port number for transmission, and furthermore allows the application -to even specify the same port number for transmission as is used for -reception. However, the use of separate transmit/receive ports allows -NORM to discriminate when unicast feedback is occurring and thus it -is not generally recommended that the transmit port be set to the -same value as the session receive -port. -

-

Note -this call must be made before any calls to -NormStartSender() or NormStartReceiver() -for the given session to succeed.

-

Return Values

-

This -function has no return values.

-

NormSetRxPortReuse()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetRxPortReuse(NormSessionHandle -session,
                        bool              enable,
                        bool              bindToSessionAddr -= true);

-

Description

-

This -function allows the user to control the port reuse and binding -behavior for the receive socket used for the given NORM session. -When the enable parameter is set -to true, reuse of the NormSession -port number is enabled, and, if the bindToSessionAddr -is also set to true (default), the -underlying socket is also bound (see the bind() -system call) to the NormSession destination address instead of -the default behavior of binding to INADDR_ANY. -

-

When -this call is not made, the default binding to IP address INADDR_ANY -(equivalent to when this call is made and bindToSessionAddr -is set to false) allows the -NormSession receive socket to receive any multicast or -unicast transmissions to the session port number provided in the call -to NormCreateSession(). This allows a -NORM receiver to receive from senders sending to a multicast session -address or the receiver's unicast address. Enabling port reuse and -binding the session destination address allows multiple NORM sessions -on the same port number, but participating in different multicast -groups.

-

Note -this call must be made before any calls to -NormStartSender() or NormStartReceiver() -for the given session to succeed.

-

This -call could also be used in conjunction with -NormSetMulticastInterface() so that -multiple NormSessions, using the same port and multicast -address, could separately cover multiple network interfaces (and some -sort of application-layer bridging of reliable multicast could be -realized if desired).

-

Return Values

-

This -function has no return values.

-

NormSetMulticastInterface()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetMulticastInterface(NormSessionHandle -session,
                               const -char*       interfaceName);

-

Description

-

This -function specifies which host network interface is used for IP -Multicast transmissions and group membership. This should be called -before any call to NormStartSender() -or NormStartReceiver() is made so that -the IP multicast group is joined on the proper host interface. -However, if a call to NormSetMulticastInterface() -is made after either of these function calls, the call will -not affect the group membership interface, but only dictate that a -possibly different network interface is used for transmitted NORM -messages. Thus, the code:

-

-NormSetMulticastInterface(session, -"interface1");
NormStartReceiver(session, -...);
NormSetMulticastInterface(session, "interface2");

-

will -result in NORM group membership (i.e. multicast reception) being -managed on "interface1" while NORM multicast transmissions -are made via "interface2".

-

Return Values

-

A -return value of true indicates -success while a return value of false -indicates that the specified interface was valid. This function will -always return true if made before -calls to NormStartSender() or -NormStartReceiver(). However, those calls -may fail if an invalid interface is specified.

-

NormSetTTL()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetTTL(NormSessionHandle session,
                unsigned -char     ttl);

-

Description

-

This -function specifies the time-to-live (ttl) -for IP Multicast datagrams generated by NORM for the specified -session. The IP TTL field limits -the number of router "hops" that a generated multicast -packet may traverse before being dropped. For example, if TTL is -equal to one, the transmissions will be limited to the local area -network (LAN) of the host computers network interface. Larger TTL -values should be specified to span large networks. Also note that -some multicast router configurations use artificial "TTL -threshold" values to constrain some multicast traffic to an -administrative boundary. In these cases. the NORM TTL setting must -also exceed the router "TTL threshold" in order for the -NORM traffic to be allowed to exit the administrative area.

-

Return Values

-

A -return value of true indicates -success while a return value of false -indicates that the specified ttl -could not be set. This function will always return true -if made before calls to NormStartSender() -or NormStartReceiver(). However, those -calls may fail if the desired ttl -value cannot be set..

-

NormSetTOS()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetTOS(NormSessionHandle session,
                unsigned -char     tos);

-

Description

-

This -function specifies the type-of-service (tos) -field value used in IP Multicast datagrams generated by NORM for the -specified session. The IP TOS -field value can be used as an indicator that a "flow" of -packets may merit special Quality-of-Service (QoS) treatment by -network devices. Users should refer to applicable QoS information for -their network to determine the expected interpretation and treatment -(if any) of packets with explicit TOS marking.

-

Return Values

-

A -return value of true indicates -success while a return value of false -indicates that the specified tos -could not be set. This function will always return true -if made before calls to NormStartSender() -or NormStartReceiver(). However, those -calls may fail if the desired tos -value cannot be set..

-

NormSetLoopback()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetLoopback(NormSessionHandle session,
                     bool              loopbackEnable);

-

Description

-

This -function enables or disables loopback operation for the indicated -NORM session. If loopbackEnable -is set to true, loopback operation -is enabled which allows the application to receive its own message -traffic. Thus, an application which is both actively receiving and -sending may receive its own transmissions. Note it is expected that -this option would be principally be used for test purposes and that -applications would generally not need to transfer data to themselves. -If loopbackEnable is false, -the application is prevented from receiving its own NORM message -transmissions. By default, loopback operation is disabled when a -NormSession is created.

-

Return Values

-

This -function has no return values.

-

NORM Sender Functions

-

The -functions described in this section apply only to NORM sender -operation. Applications may participate strictly as senders or as -receivers, or may act as both in the context of a NORM protocol -session. The NORM sender is responsible for most parameters -pertaining to its transmission of data. This includes transmission -rate, data segmentation sizes, FEC coding parameters, stream buffer -sizes, etc.

-

NormStartSender()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormStartSender(NormSessionHandle sessionHandle
                     NormSessionId     sessionId
                     unsigned -long     bufferSpace
                     unsigned -short    segmentSize,
                     unsigned -char     blockSize,
                     unsigned -char     numParity);

-

Description

-

The -application's participation as a sender within a specified -NormSession begins when this function is called. This includes -protocol activity such as congestion control and/or group round-trip -timing (GRTT) feedback collection and application API activity such -as posting of sender-related NormEvents. The parameters -required for this function call include:

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- sessionHandle

-
-

- This must be a valid NormSessionHandle - previously obtained with a call to NormCreateSession().

-
-

- sessionId

-
-

- Application-defined value used as the instance_id field of - NORM sender messages for the application's participation within a - session. Receivers can detect when a sender has terminated and - restarted if the application uses different sessionId - values when initiating sender operation. For example, a robust - application could cache previous sessionId - values in non-volatile storage and gracefully recover (without - confusing receivers) from a total system shutdown and reboot by - using a new sessionId value - upon restart.

-
-

- bufferSpace

-
-

- This specifies the maximum memory space the NORM protocol engine - is allowed to use to buffer any sender calculated FEC segments and - repair state for the session. The optimum bufferSpace - value is function of the network topology bandwidth*delay - product and packet loss characteristics. If the bufferSpace - limit is too small, the protocol may operate less efficiently as - the sender is required to possibly recalculate FEC parity segments - and/or provide less efficient repair transmission strategies - (resort to explicit repair) when state is dropped due to - constrained buffering resources. However, note the protocol will - still provide reliable transfer. A large bufferSpace - allocation is safer at the expense of possibly committing more - memory resources.

-
-

- segmentSize

-
-

- This parameter sets the maximum payload size (in bytes) of - NORM sender messages (not including any NORM message header - fields). A sender's segmentSize value - is also used by receivers to limit the payload content of some - feedback messages (e.g. NORM_NACK message content, etc.) generated - in response to that sender. Note different senders within a - NormSession may use different segmentSize - values. Generally, the appropriate segment size to use is - dependent upon the types of networks forming the multicast - topology, but applications may choose different values for other - purposes. Note that application designers MUST account for the - size of NORM message headers when selecting a segmentSize. - For example, the NORM_DATA message header for a NORM_OBJECT_STREAM - with full header extensions is 48 bytes in length. In this case, - the UDP payload size of these messages generated by NORM would be - up to (48 + segmentSize) bytes.

-
-

- blockSize

-
-

- This parameter sets the number of source symbol segments (packets) - per coding block, for the systematic Reed-Solomon FEC code used in - the current NORM implementation. For traditional systematic block - code "(n,k)" nomenclature, the blockSize - value corresponds to "k". NORM logically segments - transport object data content into coding blocks and the blockSize - parameter determines the number of source symbol segments - (packets) comprising a single coding block where each source - symbol segment is up to segmentSize - bytes in length.. A given block's parity symbol segments are - calculated using the corresponding set of source symbol segments. - The maximum blockSize allowed - by the 8-bit Reed-Solomon codes in NORM is 255, with the further - limitation that (blockSize + - numParity) ≤ 255.

-
-

- numParity

-
-

- This parameter sets the maximum number of parity symbol segments - (packets) the sender is willing to calculate per FEC coding - block. The parity symbol segments for a block are calculated from - the corresponding blockSize - source symbol segments. In the "(n,k)" - nomenclature mention above, the numParity - value corresponds to "n-k". A property of the - Reed-Solomon FEC codes used in the current NORM implementation is - that one parity segment can fill any one erasure (missing segment - (packet)) for a coding block. For a given blockSize, - the maximum numParity value is - (255 – blockSize). However, - note that computational complexity increases significantly with - increasing numParity values and - applications may wish to be conservative with respect to numParity - selection, given anticipated network packet loss conditions and - group size scalability concerns. Additional FEC code options may - be provided for this NORM implementation in the future with - different parameters, capabilities, trade-offs, and computational - requirements.

-
-

These -parameters are currently immutable with respect to a sender's -participation within a NormSession. Sender operation must be -stopped (see NormStopSender()) and -restarted with another call to NormStartSender() -if these parameters require alteration. The API may be extended in -the future to support additional flexibility here, if required. For -example, the NORM protocol "sessionId" field may -possibly be leveraged to permit a node to establish multiple virtual -presences as a sender within a NormSession in the future. This -would allow the sender to provide multiple concurrent streams of -transport, with possibly different FEC and other parameters if -appropriate within the context of a single NormSession. Again, -this extended functionality is not yet supported in this -implementation.

-

Return Values

-

A -value of true is returned upon -success and false upon failure. -The reasons failure may occur include limited system resources or -that the network sockets required for communication failed to open or -properly configure. (TBD – Provide a -NormGetError(NormSessionHandle session) -function to retrieve a more specific error indication for this and -other functions.)

-

NormStopSender()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStopSender(NormSessionHandle session, -
                    bool              graceful -= false);

-

Description

-

This -function terminates the application's participation in a NormSession -as a sender. By default, the sender will immediately exit the session -without notifying the receiver set of its intention. However a -"graceful shutdown" option is provided to terminate sender -operation gracefully, notifying the receiver set its pending exit -with appropriate protocol messaging. A NormEvent, -NORM_LOCAL_SENDER_CLOSED, is -dispatched when the graceful shutdown process has completed.

-

(NOTE: -The "graceful" -parameter is currently not available, and the current behavior of -this API call corresponds to the default behavior of graceful -= false). -The functionality described here will soon be supported in the API.

-

Return Values

-

This -function has no return values.

-

NormSetTransmitRate()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetTransmitRate(NormSessionHandle session, -
                         double            rate);

-

Description

-

This -function sets the transmission rate limit (in bits per second (bps)) -used for NormSender transmissions. For fixed-rate transmission -of NORM_OBJECT_FILE or -NORM_OBJECT_DATA, this limit -determines the data rate at which NORM protocol messages and data -content. For NORM_OBJECT_STREAM -transmissions, this is the maximum rate allowed for transmission. -Note that the application will need to consider the overhead of NORM -protocol headers when determining an appropriate transmission rate -for its purposes. When NORM congestion control is enabled (see -NormSetCongestionControl()), the rate set -here will be set, but congestion control operation may quickly -readjust the rate unless disabled. -

-

Return Values

-

This -function has no return values.

-

NormSetTxSocketBuffer()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetTxSocketBuffer(NormSessionHandle session, -
                           unsigned -int      bufferSize);

-

Description

-

This -function can be used to set a non-default socket buffer size for the -UDP socket used by the specified NORM session -for data transmission. The bufferSize -parameter specifies the desired socket buffer size in bytes. Large -transmit socket buffer sizes may be necessary to achieve high -throughput rates when NORM, as a user-space process, is unable to -precisely time its packet transmissions. Similarly, NORM receivers -may need to set large receive socket buffer sizes to achieve -sustained high data rate reception (see NormSetRxSocketBuffer()). -

-

Return Values

-

This -function returns true upon success -and false upon failure. Possible -failure modes include an invalid session parameter, a call to -NormStartReceiver() or NormStartSender() -has not yet been made for the session, or an invalid bufferSize -was given. Note some operating systems may require additional -configuration to use non-standard socket buffer sizes.

-

NormSetCongestionControl()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetTransmitRate(NormSessionHandle session, -
                         bool              enable);

-

Description

-

This -function enables (or disables) the NORM sender congestion control -operation for the session designated by the session -parameter. For best operation, this function should be called before -the call to NormStartSender() is made, -but congestion control operation can be dynamically enabled/disabled -during the course of sender operation. If the value of enable -is true, congestion control -operation is enabled while it is disabled for enable -equal to false. When congestion -control operation is enabled, the NORM sender automatically adjusts -its transmission rate based on feedback from receivers. If bounds on -transmission rate have been set (see NormSetTransmitRateBounds()) -the rate adjustment will remain within any set bounds. The rate set -by NormSetTransmitRate() has no effect -when congestion control operation is enabled. NORM's congestion -algorithm provides rate adjustment to fairly compete for available -network bandwidth with other TCP, NORM, or similarly governed traffic -flows.

-

Return Values

-

This -function has no return values.

-

NormSetTransmitRateBounds()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetTransmitRateBounds(NormSessionHandle session, -
                               double            rateMin,
                               double            rateMax);

-

Description

-

This -function sets the range of sender transmission rates within which the -NORM congestion control algorithm is allowed to operate. By default, -the NORM congestion control algorithm operates with no lower or upper -bound on its rate adjustment. This function allows this to be limited -where rateMin corresponds to the -minimum transmission rate (bps) and rateMax -corresponds to the maximum transmission rate. One or both of these -parameters may be set to values less than zero to remove one or both -bounds. For example "NormSetTransmitRate(session, --1.0, -64000.0)" -will set an upper limit of 64 kbps for the sender transmission rate -with no lower bound. These rate bounds apply only when congestion -control operation is enabled (see NormSetCongestionControl()). -If the current congestion control rate falls outside of the specified -bounds, the sender transmission rate will be adjusted to stay within -the set bounds.

-

Return Values

-

This -function returns true upon -success. If both rateMin and -rateMax are greater than or equal -to zero, but (rateMax < -rateMin), the rate bounds will -remain unset or unchanged and the function will return false.

-

NormSetTransmitCacheBounds()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetTransmitCacheBounds(NormSessionHandle session, -
                                NormSize          sizeMax,
                                unsigned -int      countMin,
                                unsigned -int      countMax);

-

Description

-

This -function sets limits that define the number and total size of pending -transmit objects a NORM sender will allow to be enqueued by the -application. Setting these bounds to large values means the NORM -protocol engine will keep history and state for previously -transmitted objects for a larger interval of time (depending upon the -transmission rate) when the application is actively enqueueing -additional objects in response to NORM_TX_QUEUE_EMPTY notifications. -This can allow more time for receivers suffering degraded network -conditions to make repair requests before the sender "purges" -older objects from its "transmit cache" when new objects -are enqueued. A NORM_TX_OBJECT_PURGED notification is issued when the -enqueuing of a new transmit object causes the NORM transmit cache to -overflow, indicating the NORM sender no longer needs to reference the -designated old transmit object and the application is free to release -related resources as needed. -

-

The -sizeMax parameter sets the maximum -total size, in bytes, of enqueued objects allowed, providing the -constraints of the countMin and -countMax parameters are met. The -countMin parameter sets the -minimum number of objects the application may enqueue, regardless of -the objects' sizes and the sizeMax -value. For example, the default sizeMax -value is 20 Mbyte and the default countMin -is 8, thus allowing the application to always have at least 8 pending -objects enqueued for transmission if it desires, even if their total -size is greater than 20 Mbyte. Similarly, the countMax -parameter sets a ceiling on how many objects may be enqueued, -regardless of their total sizes with respect to the sizeMax -setting. For example, the default countMax -value is 256, which means the application is never allowed to have -more than 256 objects pending transmission enqueued, even if they are -256 very small objects. Note that countMax -must be greater than or equal to countMin -and countMin is -recommended to be at least two.

-

Note -that in the case of NORM_OBJECT_FILE objects, some operating systems -impose limits (e.g. 256) on how many open files a process may have at -one time and it may be appropriate to limit the countMax -value accordingly. In other cases, a large countMin -or countMax may be desired to -allow the NORM sender to act as virtual cache of files or other data -available for reliable transmission. Future iterations of the NRL -NORM implementation may support alternative NORM receiver "group -join" policies that would allow the receivers to request -transmission of cached content.

-

Return Values

-

This -function has no return value.

-

NormSetAutoParity()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetAutoParity(NormSessionHandle sessionHandle, -
                       unsigned -char     autoParity);

-

Description

-

This -function sets the quantity of proactive "auto parity" -NORM_DATA messages sent at the end of each FEC coding block. By -default (i.e., autoParity = 0), -FEC content is sent only in response to repair requests -(NACKs) from receivers. But, by setting a non-zero value for -autoParity, the sender can -automatically accompany each coding block of transport object source -data segments (NORM_DATA messages) with the set number of FEC -segments. The number of source symbol messages (segments) per FEC -coding block is determined by the blockSize -parameter used when NormStartSender() was -called for the given sessionHandle.

-

The -use of proactively-sent "auto parity" may eliminate the -need for any receiver NACKing to achieve reliable transfer in -networks with low packet loss. However, note that the quantity of -"auto parity" set adds overhead to transport object -transmission. In networks with a predictable level of packet loss and -potentially large round-trip times, the use of "auto parity" -may allow lower latency in the reliable delivery process. Also, its -use may contribute to a smaller amount of receiver feedback as only -receivers with exceptional packet loss may need to NACK for -additional repair content.

-

The -value of autoParity set must be -less than or equal to the numParity -parameter set when NormStartSender() was -called for the given sessionHandle.

-

Return Values

-

This -function has no return values.

-

NormGetGrttEstimate()

-

Synopsis

-

-#include <normApi.h>

-

-double -NormGetGrttEstimate(NormSessionHandle session);

-

Description

-

This -function returns the sender's current estimate(in seconds) of group -round-trip timing (GRTT) for the given NORM session. -This function may be useful for applications to leverage for other -purposes the assessment of round-trip timing made by the NORM -protocol engine. For example, an application may scale its own -timeouts based on connectivity delays among participants in a NORM -session. Note that the NORM_GRTT_UPDATED -event is posted (see NormGetNextEvent()) -by the NORM protocol engine to indicate when changes in the local -sender or remote senders' GRTT estimate occurs.

-

Return Values

-

This -function returns the current sender group round-trip timing (GRTT) -estimate (in units of seconds). A value of -1.0 -is returned if an invalid session -value is provided.

-

NormSetGrttEstimate()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetGrttEstimate(NormSessionHandle session, -
                         double            grtt);

-

Description

-

This -function sets the sender's estimate of group round-trip timing (GRTT) -for the given NORM session. This -function is expected to most typically used to initialize the -sender's GRTT estimate prior to the call to NormStartSender() -when the application has a priori confidence that the default -initial GRTT value of 0.5 second is inappropriate. The sender GRTT -estimate will be updated during normal sender protocol operation -after sender startup or if this call is made while sender operation -is active. For experimental purposes (or very special application -needs), this API provides a mechanism to control or disable the -sender GRTT update process (see NormSetGrttProbing()). -The grtt value will be limited to -the maximum GRTT as set (see NormSetGrttMax()) -or the default maximum of 10 seconds. -

-

The -sender GRTT is advertised to the receiver group and is used to scale -various NORM protocol timers. The default NORM GRTT estimation -process dynamically measures round-trip timing to determine an -appropriate operating value. An overly-large GRTT estimate can -introduce additional latency into the reliability process (resulting -in a larger virtual delay*bandwidth product for the protocol -and potentially requiring more buffer space to maintain reliability). -An overly-small GRTT estimate may introduce the potential for -feedback implosion, limiting the scalability of group size. -

-

Also -note that the advertised GRTT estimate can also be limited by -transmission rate. When the sender transmission rate is low, the GRTT -is also governed to a lower bound of the nominal packet transmission -interval (i.e., 1/txRate). This maintains -the “event driven” nature of the NORM protocol with respect to -receiver reception of NORM sender data and commands.

-

Return Values

-

This -function has no return values.

-

NormSetGrttMax()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetGrttMax(NormSessionHandle session, -
                    double            grttMax);

-

Description

-

This -function sets the sender's maximum advertised GRTT value for the -given NORM session. The grttMax -parameter, in units of seconds, limits the GRTT used by the -group for scaling protocol timers, regardless of larger measured -round trip times. The default maximum for the NRL NORM library is 10 -seconds. See the NormSetGrttEstimate() -function description for the purpose of the NORM GRTT measurement -process.

-

Return Values

-

This -function has no return values.

-

NormSetGrttProbingMode()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetGrttProbingMode(NormSessionHandle session, -
                            NormProbingMode   probingMode);

-

Description

-

This -function sets the sender's mode of probing for round trip timing -measurement responses from the receiver set for the given NORM -session. Possible values for the -probingMode parameter include -NORM_PROBE_NONE, -NORM_PROBE_PASSIVE, and -NORM_PROBE_ACTIVE. The default -probing mode is NORM_PROBE_ACTIVE. -In this mode, the receiver set explicitly acknowledges NORM sender -GRTT probes (NORM_CMD(CC) messages) with NORM_ACK responses that are -group-wise suppressed. Note that NORM receivers also will include -their response to GRTT probing piggy-backed on any NORM_NACK messages -sent in this mode as well to minimize feedback. -

-

Note -that the NORM_PROBE_ACTIVE probing -mode is required and automatically set when NORM congestion -control operation is enabled (see NormSetCongestionControl()). -Thus, when congestion control is enabled, the -NormSetGrttProbingMode() function has no -effect.

-

If -congestion control operation is not enabled, the NORM applicaiton may -elect to reduce the volume of feedback traffic by setting the -probingMode to NORM_PROBE_PASSIVE. -Here, the NORM sender still transmits NORM_CMD(CC) probe messages -multiplexed with its data transmission, but the receiver set does not -explicitly acknowledge these probes. Instead the receiver set is -limited to piggy-backing responses when NORM_NACK messages are -generated. Note that this may, in some cases, introduce some -opportunity for bursts of large volume receiver feedback when the -sender’s estimate of GRTT is incorrect due to the reduced probing -feedback. But, in some controlled network environments, this option -for passive probing may provide some benefits in reducing protocol -overhead.

-

Finally, -the probingMode can be set to -NORM_PROBE_NONE to eliminate the -overhead (and benefits) of NORM GRTT measurement entirely. In this -case, the sender application must explicitly set its estimate of GRTT -using the NormSetGrttEstimate() function. -See this function for a description of the purpose of the NORM GRTT -measurement.

-

Return Values

-

This -function has no return values.

-

NormSetGrttProbingInterval()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetGrttProbingInterval(NormSessionHandle session, -
                                double            intervalMin,
                                double            intervalMax);

-

Description

-

This -function controls the sender GRTT measurement and estimation process -for the given NORM session. The -NORM sender multiplexes periodic transmission of NORM_CMD(CC) -messages with its ongoing data transmission or when data transmission -is idle. When NORM congestion control operation is enabled, these -probes are sent once per RTT of the current limiting receiver (with -respect to congestion control rate). In this case the intervalMin -and intervalMax parameters (in -units of seconds) control the rate at which the sender’s -estimate of GRTT is updated. At session start, the estimate is -updated at intervalMin and the -update interval time is doubled until intervalMax -is reached. This dynamic allows for a rapid initial estimation of -GRTT and a slower, steady-state update of GRTT. When congestion -control is disabled and NORM GRTT probing is enabled -(NORM_PROBE_ACTIVE or NORM_PROBE_PASSIVE) the intervalMin -and intervalMax values also -determine the rate at which NORM_CMD(CC) probes are transmitted by -the sender. Thus by setting larger values for intervalMin -and intervalMax, the NORM sender -application can reduce the overhead of the GRTT measurement process. -However, this also reduces the ability of NORM to adapt to changes in -GRTT.

-

The -default NORM GRTT intervalMin and -intervalMax values are 1.0 second -and 30.0 seconds, respectively.

-

Return Values

-

This -function has no return values.

-

NormSetBackoffFactor()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetBackoffFactor(NormSessionHandle session, -
                          double            backoffFactor);

-

Description

-

This -function sets the sender's "backoff factor" for the given -session.. The backoff factor is -used to scale various timeouts related to the NACK repair process. -The sender advertises its backoff factor setting to the receiver -group in NORM protocol message headers. The default backoff factor -for NORM sessions is 4.0. The -backoff factor is used to determine the maximum time that receivers -may delay NACK transmissions (and other feedback messages) as part of -NORM's probabilistic feedback suppression technique. For example, the -maximum NACK delay time is backoffFactor*GRTT. -Thus a large backoffFactor value -introduces latency into the NORM repair process. However, a small -backoffFactor value causes -feedback suppression to be less effective and increases the risk of -feedback implosion for large receiver group sizes. The default -setting of 4.0 provides reasonable -feedback suppression for moderate to large group sizes when multicast -feedback is possible. The NORM specification recommends a backoff -factor value of 6.0 when unicast -feedback is used. However, for demanding applications (with respect -to repair latency) when group sizes are modest, a small (even 0.0) -backoffFactor value can be -specified to reduce the latency of reliable data delivery.

-

Return Values

-

This -function has no return values.

-

NormSetGroupSize()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetGroupSize(NormSessionHandle session, -
                      unsigned -int      groupSize);

-

Description

-

This -function sets the sender's estimate of receiver group size for the -given session. The sender advertises its group size setting to the -receiver group in NORM protocol message headers that, in turn, use -this information to shape the distribution curve of their random -timeouts for the timer-based, probabilistic feedback suppression -technique used in the NORM protocol. Note that the groupSize -estimate does not have to be very accurate and values within an order -of magnitude of the actual group size tend to produce acceptable -performance. The default group size setting in NORM is 1,000 -and thus can work well for a wide range of actual receiver group -sizes. The penalty of an overly large estimate is statistically a -little more latency in reliable data delivery with respect to the -round trip time and some potential for excess feedback. A substantial -underestimation of group size increases the risk of feedback -implosion. Currently, the NORM implementation does not attempt to -automatically measure group size from receiver feedback. Applications -could add their own mechanism for this (perhaps keeping explicit -track of group membership), or it is possible that future versions of -the NRL NORM implementation may have some provision for automatic -group size estimation by the sender based on receiver feedback -messages.

-

Return Values

-

This -function has no return values.

-

NormFileEnqueue()

-

Synopsis

-

-#include <normApi.h>

-

-NormObjectHandle -NormFileEnqueue(NormSessionHandle session,
                                 const -char*       filename,
                                 const -char*       infoPtr = -NULL,
                                 unsigned -int      infoLen = 0);

-

Description

-

This -function enqueues a file for transmission within the specified NORM -session. Note that -NormStartSender() must have been -previously called before files or any transport objects may be -enqueued and transmitted. The fileName -parameter specifies the path to the file to be transmitted. The NORM -protocol engine read and writes directly from/to file system storage -for file transport, potentially providing for a very large virtual -"repair window" as needed for some applications. While -relative paths with respect to the current working directory may be -used, it is recommended that full paths be used when possible. The -optional infoPtr and infoLen -parameters are used to associate NORM_INFO content with the sent -transport object. The maximum allowed infoLen -corresponds to the segmentSize -used in the prior call to NormStartSender(). -The use and interpretation of the NORM_INFO content is left to the -application's discretion. Example usage of NORM_INFO content for -NORM_OBJECT_FILE might include -file name, creation date, MIME-type or other information which will -enable NORM receivers to properly handle the file when reception is -complete.

-

The -application is allowed to enqueue multiple transmit objects within in -the "transmit cache" limits (see NormSetTxCacheLimits()) -and enqueued objects are transmitted (and repaired as needed) within -the limits determined by automated congestion control (see -NormSetCongestionControl()) or fixed rate -(see NormSetTxRate()) parameters.

-

Return Values

-

A -NormObjectHandle is returned which the -application may use in other NORM API calls as needed. This handle -can be considered valid until the application explicitly cancels the -object's transmission (see NormObjectCancel()) -or a NORM_TX_OBJECT_PURGED event -is received for the given object. Note the application may use the -NormObjectRetain() method if it wishes to -refer to the object after the NORM_TX_OBJECT_PURGED -notification. In this case, the application, when finished with the -object, must use NormObjectRelease() to -free any resources used or else a memory leak condition will result. -A value of NORM_OBJECT_INVALID is -return upon error. Possible failure conditions include the specified -session is not operating as a -NormSender, insufficient memory resources were available, or -the "transmit cache" limits have been reached and all -previously enqueued NORM transmit objects are pending transmission. -Also the call will fail if the infoLen -parameter exceeds the local NormSender segmentSize -limit.

-

NormDataEnqueue()

-

Synopsis

-

-#include <normApi.h>

-

-NormObjectHandle -NormDataEnqueue(NormSessionHandle session,
                                 const -char*       dataPtr,
                                 unsigned -int      dataLen,
                                 const -char*       infoPtr = -NULL,
                                 unsigned -int      infoLen = 0);

-

Description

-

This -function enqueues a segment of application memory space for -transmission within the specified NORM session. -Note that NormStartSender() must have -been previously called before files or any transport objects may be -enqueued and transmitted. The dataPtr -parameter must be a valid pointer to the area of application memory -to be transmitted and the dataLen -parameter indicates the quantity of data to transmit. The NORM -protocol engine read and writes directly from/to application memory -space so it is important that the application does not modify (or -deallocate) the memory space during the time the NORM protocel engine -may access this area. The optional infoPtr -and infoLen parameters are used to -associate NORM_INFO content with the sent transport object. The -maximum allowed infoLen -corresponds to the segmentSize -used in the prior call to NormStartSender(). -The use and interpretation of the NORM_INFO content is left to the -application's discretion. Example usage of NORM_INFO content for -NORM_OBJECT_DATA might include -application-defined data typing or other information which will -enable NORM receiver applications to properly interpret the received -data when reception is complete. Of course, it is possible that the -application may embed such typing information in the object data -content itself. This is left to the application's discretion.

-

The -application is allowed to enqueue multiple transmit objects within in -the "transmit cache" limits (see NormSetTxCacheLimits()) -and enqueued objects are transmitted (and repaired as needed) within -the limits determined by automated congestion control (see -NormSetCongestionControl()) or fixed rate -(see NormSetTxRate()) parameters.

-

Return Values

-

A -NormObjectHandle is returned which the -application may use in other NORM API calls as needed. This handle -can be considered valid until the application explicitly cancels the -object's transmission (see NormObjectCancel()) -or a NORM_TX_OBJECT_PURGED event -is received for the given object. Note the application may use the -NormObjectRetain() method if it wishes to -refer to the object after the NORM_TX_OBJECT_PURGED -notification. In this case, the application, when finished with the -object, must use NormObjectRelease() -to free any resources used or else a memory leak condition will -result. A value of NORM_OBJECT_INVALID -is return upon error. Possible failure conditions include the -specified session is not operating -as a NormSender, insufficient memory resources were available, -or the "transmit cache" limits have been reached and all -previously enqueued NORM transmit objects are pending transmission. -Also the call will fail if the infoLen -parameter exceeds the local NormSender segmentSize -limit.

-

NormRequeueObject()

-

Synopsis

-

-#include <normApi.h>

-

-bool NormRequeueObject(NormSessionHandle session,
                       NormObjectHandle  object);

-

Description

-

This -function allows the application to resend (or reset transmission of) -a NORM_OBJECT_FILE or -NORM_OBJECT_DATA transmit object -that was previously enqueued for the indicated session. -This function is useful for applications sending to silent -(non-NACKing) receivers as it enables the receivers to take advantage -of multiple retransmissions of objects (including any auto-parity -set, see NormSetAutoParity()) to more -robustly receive content. The object -parameter must be a valid transmit NormObjectHandle that has not yet -been "purged" from the sender's transmit queue. Upon -success, the specified object will -be fully retransmitted using the same NORM object transport -identifier as was used on its initial transmission. This call may be -made at any time to restart transmission of a previously-enqueued -object, but the NORM_TX_OBJECT_SENT -or NORM_TX_FLUSH_COMPLETED -notifications can serve as good cues for an appropriate time to -resend an object. If multiple objects are re-queued, they will be -resent in order of their initial enqueuing.

-

The -transmit cache bounds set by NormSetTransmitCacheBounds() -determine the number of previously-sent objects retained in the -sender's transmit queue and that are thus eligible to be requeued for -retransmission. An object may be requeued via this call multiple -times, but each distinct requeue should be done after an indication -such as NORM_TX_OBJECT_SENT or -NORM_TX_FLUSH_COMPLETED for the -given object. Otherwise, the object will simply be reset from its -current transmission point to transmit from the beginning (i.e. -restart). Note that the object type NORM_OBJECT_STREAM -cannot currently be requeued.

-

(TBD – should a -"numRepeats" parameter be added to this function?)

-

Return Values

-

A -value of true is returned upon -success and a value of false is -returned upon failure. Possible reasons for failure include an -invalid object handle was provided (i.e. a non-transmit object or -transmit object that has been "purged" from the transmit -queue (see NORM_TX_OBJECT_PURGED)) -or the provided object was of type -NORM_OBJECT_STREAM.

-

NormStreamOpen()

-

Synopsis

-

-#include <normApi.h>

-

-NormObjectHandle -NormStreamOpen(NormSessionHandle session,
                                unsigned -int      bufferSize,
                                const -char*       infoPtr = -NULL,
                                unsigned -int      infoLen = 0);

-

Description

-

This -function opens a NORM_OBJECT_STREAM -sender object and enqueues it for transmission within the indicated -session. NormStream objects -provide reliable, in-order delivery of data content written to the -stream by the sender application. Note that no data is sent until -subsequent calls to NormStreamWrite() are -made unless NORM_INFO content is specified for the stream with the -infoPtr and infoLen -parameters. Example usage of NORM_INFO content for NORM_OBJECT_STREAM -might include application-defined data typing or other information -which will enable NORM receiver applications to properly interpret -the received stream as it is being received. The NORM protocol engine -buffers data written to the stream for original transmission and -repair transmissions as needed to achieve reliable transfer. The -bufferSize parameter controls the -size of the stream's "repair window" which limits how far -back the sender will "rewind" to satisfy receiver repair -requests. -

-

NORM, -as a NACK-oriented protocol, currently lacks a mechanism for -receivers to explicitly feedback flow control status to the -sender unless the sender leverages NORM's optional positive -acknowledgement (ACK) features. Thus, the bufferSize -selection plays an important role in NORM's reliability. Generally, a -larger bufferSize value is safer -with respect to reliability, but some applications may wish to limit -how far the sender rewinds to repair receivers with poor connectivity -with respect to the group at large. Such applications may set a -smaller bufferSize to avoid the -potential for large latency in data delivery. This may result in -breaks in the reliable delivery of stream data to some receivers, but -this form of quasi-reliability while limiting latency may be useful -for some types of applications (e.g. reliable real-time messaging, -video or sensor data transport). Note that NORM receivers can -"resync" to the sender after such breaks if the application -leverages the message boundary recovery features of NORM (see -NormStreamMarkEom()).

-

Note -that the current implementation of NORM is designed to support only -one active stream per session, and that any NORM_OBJECT_DATA -or NORM_OBJECT_FILE objects -enqueued for transmission will not begin transmission until an active -stream is closed. Applications requiring multiple streams or -concurrent file/data transfer should instantiate multiple -NormSessions as needed.

-

Note -there is no corresponding "open" call for receiver streams. -Receiver NORM_OBJECT_STREAMs are -automatically opened by the NORM protocol engine and the receiver -applications is notified of new streams via the NORM_RX_OBJECT_NEW -notification (see NormGetNextEvent()).

-

Return Values

-

A -NormObjectHandle is returned which the -application may use in other NORM API calls as needed. This handle -can be considered valid until the application explicitly cancels the -object's transmission (see NormObjectCancel()) -or a NORM_TX_OBJECT_PURGED event -is received for the given object. Note the application may use the -NormObjectRetain() method if it wishes to -refer to the object after the NORM_TX_OBJECT_PURGED -notification. In this case, the application, when finished with the -object, must use NormObjectRelease() -to free any resources used or else a memory leak condition will -result. A value of NORM_OBJECT_INVALID -is return upon error. Possible failure conditions include the -specified session is not operating -as a NormSender, insufficient memory resources were available, -or the "transmit cache" limits have been reached and all -previously enqueued NORM transmit objects are pending transmission. -Also the call will fail if the infoLen -parameter exceeds the local NormSender segmentSize -limit.

-

NormStreamClose()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStreamClose(NormObjectHandle -streamHandle,
                     bool             graceful -= false);

-

Description

-

This -function halts transfer of the stream specified by the streamHandle -parameter and releases any resources used unless the associated -object has been explicitly retained by a call to NormObjectRetain(). -No further calls to NormStreamWrite() -will be successful for the given streamHandle. -The optional graceful parameter, -when set to a value of true, may -be used by NORM senders to initiate "graceful" shutdown of -a transmit stream. In this case, the sender application will be -notified that stream has (most likely) completed reliable transfer -via the NORM_TX_OBJECT_PURGED -notification upon completion of the graceful shutdown process. When -the graceful option is set, -receivers are notified of the stream end via a "FLAG_STREAM_END" -flag in NORM_DATA message (Note the NRL NORM implementation uses a -portion of the NORM_DATA::payload_reserved -field for this purpose and proposes that this type of funtionality -be added to subsequent versions of the NORM protocol specification) -and will receive a NORM_RX_OBJECT_COMPLETED -notification after all received stream content has been read. -Otherwise, the stream is immediately terminated, regardless of -receiver state. In this case, this function is equivalent to the -NormObjectCancel() routine and may be -used for sender or receiver streams. So, it is expected this function -(NormStreamClose()) will typically be -used for transmit streams by NORM senders.

-

Return Values

-

This -function has no return values.

-

NormStreamWrite()

-

Synopsis

-

-#include <normApi.h>

-

-unsigned int -NormStreamWrite(NormObjectHandle streamHandle
                             const -char*      buffer,
                             unsigned -int     numBytes);

-

Description

-

This -function enqueues data for transmission within the NORM stream -specified by the streamHandle -parameter. The buffer parameter -must be a pointer to the data to be enqueued and the numBytes -parameter indicates the length of the data content. Note this call -does not block and will return immediately. The return value -indicates the number of bytes copied from the provided buffer to the -internal stream transmission buffers. Calls to this function will be -successful unless the stream's transmit buffer space is fully -occupied with data pending original or repair transmission if the -stream's "push mode" is set to false -(default, see NormStreamSetPushMode() for -details). If the stream's "push mode" is set to true, a -call to NormStreamWrite() will always -result in copying of application data to the stream at the cost of -previously enqueued data pending transmission (original or repair) -being dropped by the NORM protocol engine. While NORM NACK-based -reliability does not provide explicit flow control, there is some -degree of implicit flow control in limiting writing new data to the -stream against pending repairs. Other flow control strategies are -possible using the NormSetWatermark() -function.

-

The -NormEvents NORM_TX_QUEUE_EMPTY -and NORM_TX_QUEUE_VACANCY are -posted with the NormEvent::object field set to a valid sender -stream NormObjectHandle to indicate when -the stream is ready for writing via this function. Note that the -NORM_TX_QUEUE_VACANCY event type -is posted only after the stream's transmit buffer has been -completely filled. Thus, the application must make a call to -NormStreamWrite() that copies less than -the requested numBytes value -(return value less than numBytes) -before additional NORM_TX_QUEUE_VACANCY -events are posted for the given streamHandle -(i.e., the event type is not re-posted until the application has -again filled the available stream transmit buffer space). By cueing -off of NORM_TX_QUEUE_EMPTY, the -application can write its "freshest" available data to the -stream, but by cueing off of NORM_TX_QUEUE_VACANCY, -an application can keep the NORM protocol engine busiest, to achieve -the maximum possible throughput at high data rates.

-

Return Values

-

This -function returns the number of bytes of data successfully enqueued -for NORM stream transmission. If the underlying send stream buffer is -full, this function may return zero or a value less than the -requested numBytes. -

-

NormStreamFlush()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStreamFlush(NormObjectHandle -streamHandle,
                     bool             eom -= false,
                     NormFlushMode    flushMode -= NORM_FLUSH_PASSIVE);

-

Description

-

This -function causes an immediate "flush" of the transmit stream -specified by the streamHandle -parameter. Normally, unless NormSetAutoFlush() -has been invoked, the NORM protocol engine buffers data written to a -stream until it has accumulated a sufficient quantity to generate a -NORM_DATA message with a full payload (as designated by the -segmentSize parameter of the -NormStartSender() call). This results in -most efficient operation with respect to protocol overhead. However, -for some NORM streams, the application may not wish wait for such -accumulation when critical data has been written to a stream. The -default stream "flush" operation invoked via -NormStreamFlush() for flushMode -equal to NORM_FLUSH_PASSIVE causes -NORM to immediately transmit all enqueued data for the stream -(subject to session transmit rate limits), even if this results in -NORM_DATA messages with "small" payloads. If the optional -flushMode parameter is set to -NORM_FLUSH_ACTIVE, the application -can achieve reliable delivery of stream content up to the current -write position in an even more proactive fashion. In this case, the -sender additionally, actively transmits NORM_CMD(FLUSH) -messages after any enqueued stream content has been sent. This -immediately prompt receivers for repair requests which reduces -latency of reliable delivery, but at a cost of some additional -messaging. Note any such "active" flush activity will be -terminated upon the next subsequent write to the stream. If flushMode -is set to NORM_FLUSH_NONE, this -call has no effect other than the optional end-of-message marking -described here.

-

The -optional eom parameter, when set -to true, allows the sender -application to mark an end-of-message indication (see -NormStreamMarkEom()) for the stream and -initiate flushing in a single function call. The end-of-message -indication causes NORM to mark the first NORM_DATA message generated -following a subsequent write to the stream with the -NORM_FLAGS_MSG_START flag. This mechanism provide a means for message -boundary recovery when receivers join or re-sync to a sender -mid-stream.

-

Note -that frequent flushing, particularly for NORM_FLUSH_ACTIVE -operation, may result in more NORM protocol activity than usual, so -care must be taken in application design and deployment when -scalability to large group sizes is expected.

-

Return Values

-

This -function has no return values.

-

NormStreamSetAutoFlush()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStreamSetAutoFlush(NormObjectHandle -streamHandle
                            NormFlushMode    flushMode);

-

Description

-

This -function sets "automated flushing" for the NORM -transmit stream indicated by the streamHandle -parameter. By default, a NORM transmit stream is "flushed" -only when explicitly requested by the application (see -NormStreamFlush()). -However, to simplify programming, the NORM API allows that automated -flushing be enabled such that the "flush" operation occurs -every time the full requested buffer -provided to a NormStreamWrite() -call is successfully enqueued. This may be appropriate for messaging -applications where the provided buffers corresponds to an application -messages requiring immediate, full transmission. This may make the -NORM protocol perhaps more "chatty" than its typical "bulk -transfer" form of operation, but can provide a useful capability -for some applications. -

-

Possible -values for the flushMode parameter -include NORM_FLUSH_NONE, -NORM_FLUSH_PASSIVE, and -NORM_FLUSH_ACTIVE. The default -setting for a NORM stream is NORM_FLUSH_NONE -where no flushing occurs unless explicitly requested via -NormStreamFlush(). By -setting the automated flushMode to -NORM_FLUSH_PASSIVE, the only -action taken is to immediately transmit any data that has been -written to the stream, even if "runt" NORM_DATA messages -(with payloads less than the NormSender segmentSize -parameter) are generated as a result. If NORM_FLUSH_ACTIVE -is specified, the automated flushing operation is further augmented -with the additional transmission of NORM_CMD(FLUSH) messages to -proactively excite the receiver group for repair requests.

-

Return Values

-

This -function has no return values.

-

NormStreamSetPushEnable()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStreamSetPushEnable(NormObjectHandle -streamHandle
                             bool             pushEnable);

-

Description

-

This -function controls how the NORM API behaves when the application -attempts to enqueue new stream data for transmission when the -associated stream's transmit buffer is fully occupied with data -pending original or repair transmission. By default (pushEnable -== false), a call to -NormStreamWrite() will return a zero -value under this condition, indicating it was unable to enqueue the -new data. However, if pushEnable -is set to true for a given -streamHandle, the NORM protocol -engine will discard the oldest buffered stream data (even if it is -pending repair transmission or has never been transmitted) as needed -to enqueue the new data. Thus a call to NormStreamWrite() -will never fail to copy data. This behavior may be desirable for -applications where it is more important to quickly delivery new data -than to reliably deliver older data written to a stream. The default -behavior for a newly opened stream corresponds to pushEnable -equals false. This limits the rate -to which an application can write new data to the stream to the -current transmission rate and status of the reliable repair process.

-

Return Values

-

This -function has no return values.

-

NormStreamHasVacancy()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormStreamHasVacancy(NormObjectHandle streamHandle);

-

Description

-

This -function can be used to query whether the transmit stream, specified -by the streamHandle parameter, has -buffer space available so that the application may successfully make -a call to NormStreamWrite(). Normally, a -call to NormStreamWrite() itself can be -used to make this determination, but this function can be useful when -"push mode" has been enabled (see the description of the -NormStreamSetPushEnable() function) and -the application wants to avoid overwriting data previously written to -the stream that has not yet been transmitted. Note that when "push -mode" is enabled, a call to NormStreamWrite() -will always succeed, overwriting previously-enqueued data if -necessary. Normally, this function will return true after a -NORM_TX_QUEUE_VACANCY notification has been received for a given NORM -stream object.

-

Return Values

-

This -function returns a value of true -when there is transmit buffer space to which the application may -write and false otherwise.

-

NormStreamMarkEom()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStreamMarkEom(NormObjectHandle streamHandle);

-

Description

-

This -function allows the application to indicate to the NORM protocol -engine that the last data successfully written to the stream -indicated by streamHandle -corresponded to the end of an application-defined message boundary. -If the stream is either explicitly flushed at this point (see -NormStreamFlush()) or the last write had -exactly filled a NormSender segmentSize -NORM_DATA message payload, the beginning of the next write will -correspond to the beginning of a new NORM_DATA message. The -end-of-message indication given here will cause the NORM protocol -engine to flag this new message with NORM_FLAG_MSG_START which allows -receivers to recover message boundary synchronization even when -beginning reception mid-stream. Note that the marking is most -effective when explicit flushing is used which forces alignment of -application message boundaries with NORM_DATA messages. It is -anticipated that future versions of the NORM protocol specification -(and/or the NRL implementation) will provide additional, more -flexible stream control mechanisms (e.g. mid-segment message boundary -alignment) that allow for more robust message boundary recovery.

-

It -is recommended that the NormStreamMarkEom() -should be used with automated flushing modes (see -NormStreamSetAutoFlush()) while the -optional eom parameter of -NormStreamFlush() is instead used when -explicit flushing is practiced. End-of-message marking may be -used when no flushing is done, but note then there is no guarantee of -message boundary to NORM_DATA message alignment unless the -application message sizes correspond to multiples of the configured -NormSender segmentSize. -Again, note future versions of NORM and this implementation may -provide more flexibility here.

-

Return Values

-

This -function has no return values.

-

NormSetWatermark()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetWatermark(NormSessionHandle session,
                      NormObjectHandle  object);

-

Description

-

This -function specifies a "watermark" transmission point at -which NORM sender protocol operation should perform a flushing -process and/or positive acknowledgment collection for a given -session. For NORM_OBJECT_FILE -and NORM_OBJECT_DATA -transmissions, the positive acknowledgement collection will begin -when the specified object has been -completely transmitted. The object parameter must be a valid handle -to a previously-created sender object (see NormEnqueueFile(), -NormEnqueueData(), or NormStreamOpen()). -For NORM_OBJECT_STREAM -transmission, the positive acknowledgment collection begins -immediately, using the current position (offset of most recent data -written) of the sender stream as a reference. -

-

The -functions NormAddAckingNode() and -NormRemoveAckingNode() are used to manage -the list of NormNodeId values -corresponding to NORM receivers that are expected to explicitly -acknowledge the watermark flushing messages transmitted by the -sender. Note that the NormNodeId -NORM_NODE_NONE may be included in -the list. Inclusion of NORM_NODE_NONE -forces the watermark flushing process to proceed through a full -NORM_ROBUST_FACTOR number of -rounds before completing, prompting any receivers that have not -completed reliable reception to the given watermark point to NACK for -any repair needs. If NACKs occur, the flushing process is reset and -repeated until completing with no NACKs for data through the given -watermark transmission point are received. Thus, even without -explicit positive acknowledgment, the sender can use this process (by -adding NORM_NODE_NONE to the -session's list of acking nodes) -for a high level of assurance that the receiver set is "happy" -(completed reliable data reception) through the given object -(or stream transmission point). -

-

The -event NORM_TX_WATERMARK_COMPLETED -is posted for the given session -when the flushing process or positive acknowledgment collection has -completed. The process completes as soon as all listed receivers have -responded unless NORM_NODE_NONE is -included in the acking list. The sender application may use the -function NormGetAckingStatus() to -determine the degree of success of the flushing process in general or -for individual NormNodeId values. -

-

The -flushing is conducted concurrently with ongoing data transmission and -does not impede the progress of reliable data transfer. Thus the -sender may still enqueue NormObjects for transmission (or -write to the existing stream) and the positive acknowledgement -collection and flushing procedure will be multiplexed with the -ongoing data transmission. However, the sender application may wish -to defer from or limit itself in sending more data until a -NORM_TX_WATERMARK_COMPLETED event -is received for the given session. -This provides a form of sender->receiver(s) flow control which -does not exist in NORM's default protocol operation. If a subsequent -call is made to NormSetWatermark() before -the current acknowledgement request has completed, the pending -acknowledgment request is canceled and the new one begins.

-

Return Values

-

The -function returns true upon -successful establishment of the watermark point. The function may -return false upon failure (why -would it fail? – TBD).

-

NormAddAckingNode()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormAddAckingNode(NormSessionHandle session,
                       NormNodeId        nodeId);

-

Description

-

When -this function is called, the specified nodeId -is added to the list of NormNodes used when NORM sender -operation performs positive acknowledgement (ACK) collection for the -specified session. The optional -NORM positive acknowledgement collection occurs when a specified -transmission point (see NormSetWatermark()) -is reached or for specialized protocol actions such as -positively-acknowledged application-defined commands. Additionally a -value of nodeId equal to -NORM_NODE_NONE may be set to force -the watermark flushing process through a full NORM_ROBUST_FACTOR -number of rounds regardless of actual acking nodes. Otherwise the -flushing process is terminated when all of the nodes in the acking -node list have responded.

-

Return Values

-

The -function returns true upon success -and false upon failure. The only -failure condition is that insufficient memory resources were -available. If a specific nodeId is -added more than once, this has no effect.

-

NormRemoveAckingNode()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormRemoveAckingNode(NormSessionHandle session,
                          NormNodeId        nodeId);

-

Description

-

This -function deletes the specified nodeId -from the list of NormNodes used when NORM sender operation -performs positive acknowledgement (ACK) collection for the specified -session. Note that if the nodeId -NORM_NODE_NONE has been added to -the list, it too must be removed to change the watermark flushing -behavior if desired.

-

Return Values

-

The -function has no return values.

-

NormGetAckingStatus()

-

Synopsis

-

-#include <normApi.h>

-

-NormAckingStatus -NormGetAckingStatus(NormSessionHandle session,
                                     NormNodeId        nodeId -= -                                                       NORM_NODE_ANY);

-

Description

-

This -function queries the status of the watermark flushing process and/or -positive acknowledgment collection initiated by a prior call to -NormSetWatermark() for the given session. -In general, it is expected that applications will invoke this -function after the corresponding NORM_TX_WATERMARK_COMPLETED -event has been posted. Setting the default parameter value nodeId -= NORM_NODE_ANY returns a "status" -indication for the overall process. Also, individual nodeId -values may be queried using the NormNodeId -values of receivers that were included in previous calls to -NormAddAckingNode() to populate the -sender session's acking node list. -

-

If -the flushing/acknowledgment process is being used for application -flow control, the sender application may wish to reset the watermark -and flushing process (using NormSetWatermark()) -if the response indicates that some nodes have failed to respond. -However, note that the flushing/acknowledgment process itself does -elicit NACKs from receivers as needed and is interrupted and reset by -any repair response that occurs. Thus, even by the time the flushing -process has completed (and NORM_TX_WATERMARK_COMPLETED -is posted) once, this is an indication that the NORM protocol has -made a valiant attempt to deliver the content. Resetting the -watermark process can increase robustness, but it may be in -vain to repeat this process multiple times when likely network -connectivity has been lost or expected receivers have failed (dropped -out, shut down, etc).

-

Return Values

-

-Possible return values include:

- - - - - - - - - - - - - - - - - - - -
-

- NORM_ACK_INVALID

-
-

- The given session is invalid or the given nodeId - is not in the session's acking list.

-
-

- NORM_ACK_FAILURE

-
-

- The positive acknowledgement collection process did not receive - acknowledgment from every listed receiver (nodeId - = NORM_NODE_ANY) or the - identified nodeId did not - respond.

-
-

- NORM_ACK_PENDING

-
-

- The flushing process at large has not yet completed (nodeId - = NORM_NODE_ANY) or the given - individual nodeId is still - being queried for response.

-
-

- NORM_ACK_SUCCESS

-
-

- All receivers (nodeId = - NORM_NODE_ANY) responded with - positive acknowledgement or the given specific nodeId - did acknowledge.

-
-

NORM Receiver Functions

-

NormStartReceiver()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormStartReceiver(NormSessionHandle session, -
                       unsigned -long     bufferSpace);

-

Description

-

This -function initiates the application's participation as a receiver -within the NormSession identified by the session -parameter. The NORM protocol engine will begin providing the -application with receiver-related NormEvent notifications, -and, unless NormSetSilentReceiver(true) -is invoked, respond to senders with appropriate protocol messages. -The bufferSpace parameter is used -to set a limit on the amount of bufferSpace -allocated by the receiver per active NormSender within the -session. The appropriate bufferSpace -to use is a function of expected network delay*bandwidth -product and packet loss characteristics. A discussion of trade-offs -associated with NORM transmit and receiver buffer space selection is -provided later in this document. An insufficient bufferSpace -allocation will result in potentially inefficient protocol operation, -even though reliable operation may be maintained. In some cases of a -large delay*bandwidth product and/or severe packet loss, a -small bufferSpace allocation -(coupled with the lack of explicit flow control in NORM) may result -in the receiver "re-syncing" to the sender, resulting in -"outages" in the reliable transmissions from a sender (this -is analogous to a TCP connection timeout failure). -

-

Return Values

-

A -value of true is returned upon -success and false upon failure. -The reasons failure may occur include limited system resources or -that the network sockets required for session communication failed to -open or properly configure.

-

NormStopReceiver()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormStopReceiver(NormSessionHandle session, -
                      unsigned -int      gracePeriod = 0);

-

Description

-

This -function ends the application's participation as a receiver in the -NormSession specified by the session -parameter. By default, all receiver-related protocol activity is -immediately halted and all receiver-related resources are freed -(except for those which have been specifically retained (see -NormObjectRetain()). However, and -optional gracePeriod parameter is -provided to allow the receiver an opportunity to inform the group of -its intention. This is applicable when the local receiving NormNode -has been designated as an active congestion control representative -(i.e. current limiting receiver (CLR) or potential limiting receiver -(PLR)). In this case, a non-zero gracePeriod -value provides an opportunity for the receiver to respond to the -applicable sender(s) so the sender will not expect further congestion -control feedback from this receiver. The gracePeriod -integer value is used as a multiplier with the largest sender GRTT to -determine the actual time period for which the receiver will linger -in the group to provide such feedback (i.e. "grace time" = -(gracePeriod * GRTT)). -During this time, the receiver will not generate any requests for -repair or other protocol actions aside from response to applicable -congestion control probes. When the receiver is removed from the -current list of receivers in the sender congestion control probe -messages (or the gracePeriod -expires, whichever comes first), the NORM protocol engine will post a -NORM_LOCAL_RECEIVER_CLOSED event -for the applicable session, and -related resources are then freed.

-

Return Values

-

This -function has no return values.

-

NormSetRxSocketBuffer()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormSetRxSocketBuffer(NormSessionHandle session, -
                           unsigned -int      bufferSize);

-

Description

-

This -function allows the application to set an alternative, non-default -buffer size for the UDP socket used by the specified NORM session -for packet reception. This may be necessary for high speed NORM -sessions where the UDP receive socket buffer becomes a bottleneck -when the NORM protocol engine (which is running as a user-space -process) doesn't get to service the receive socket quickly enough -resulting in packet loss when the socket buffer overflows. The -bufferSize parameter specifies the -socket buffer size in bytes. Different operating systems and -sometimes system configurations allow different ranges of socket -buffer sizes to be set. Note that a call to NormStartReceiver() -(or NormStartSender()) must have been -previously made for this call to succeed (i.e., the socket must be -already open).

-

Return Values

-

This -function returns true upon success -and false upon failure. Possible -reasons for failure include, 1) the specified session -is not valid, 2) that NORM "receiver" (or "sender") -operation has not yet been started for the given session, -or 3) an invalid bufferSize -specification was given.

-

NormSetSilentReceiver()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetSilentReceiver(NormSessionHandle session, -
                           bool              silent,
                           INT32             maxDelay -= -1);

-

Description

-

This -function provides the option to configure a NORM receiver application -as a "silent receiver". This mode of receiver operation -dictates that the host does not generate any protocol messages while -operating as a receiver within the specified session. -Setting the silent parameter to -true enables silent receiver -operation while setting it to false -results in normal protocol operation where feedback is provided as -needed for reliability and protocol operation. Silent receivers are -dependent upon proactive FEC transmission (see NormSetAutoParity()) -or using repair information requested by other non-silent receivers -within the group to achieve reliable transfers.

-

The -optional maxDelay parameter is -most applicable for reception of the NORM_OBJECT_STREAM -type. The default value of maxDelay -= -1 corresponds to normal -operation where source data segments for incompletely-received FEC -coding blocks (or transport objects) are passed to the application -only when imposed buffer constraints (either the NORM_OBJECT_STREAM -buffer size (see NormStreamOpen()) or the -FEC receive buffer limit (see NormStartReceiver()) -require. Thus, the default behavior (maxDelay -= -1), causes the receiver to -buffer received FEC code blocks for as long as possible (within -buffer constraints as newer data arrives) before allowing the -application to read the data. Hence, the receive latency (delay) can -be quite long depending upon buffer size settings, transmission rate, -etc. When the maxDelay parameter -is set to a non-negative value, the value determines the maximum -number of FEC coding blocks (according to a NORM sender's current -transmit position) the receiver will cache an incompletely-received -FEC block before giving the applicatin the (incomplete) set of -received source segments. For example, a value of maxDelay -= 0 will provide the receive -application with any data from the previous FEC block as soon as a -subsequent FEC block is begun reception. However, this provide no -protection against the possibility of out-of-order delivery of -packets by the network. Therefore, if lower latency operation is -desired when using silent receivers, a minimum maxDelay -value of 1 is recommended. For -NORM_OBJECT_FILE and -NORM_OBJECT_DATA, the only impact -of a non-negative maxDelay value is that previous transport objects -will be immediately aborted when subsequent object begin reception. -Thus, it is not usually recommended to apply a non-negative maxDelay -value when NORM_OBJECT_STREAM is -not being used.

-

Return Values

-

This -function has no return values.

-

NormSetDefaultUnicastNack()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetDefaultUnicastNack(NormSessionHandle session, -
                               bool              state);

-

Description

-

This -function controls the default behavior determining the destination of -receiver feedback messages generated while participating in the -session. If state is true, -"unicast NACKing" is enabled for new remote senders -while it is disabled for state -equal to false. The NACKing behavior for current remote senders is -not affected. When "unicast NACKing" is disabled (default), -NACK messages are sent to the session address (usually a multicast -address) and port, but "unicast NACKing", when enabled, -causes receiver feedback messages to be sent to the unicast address -(and port) based on the source address of sender messages received. -For unicast NORM sessions, it is recommended that "unicast -NACKing" be enabled. Note that receiver feedback messages -subject to the state of "unicast NACKing" include -NACK-messages as well as some ACK messages such as congestion control -feedback. Explicitly solicited ACK messages, such as those used to -satisfy sender watermark acknowledgement requests (see -NormSetWatermark()) are always unicast to -the applicable sender. (TBD – provide API option so that all -messages are multicast.) The default session-wide behavior for -unicast NACKing can be overridden via the NormNodeSetUnicastNack() -function for individual remote senders.

-

Return Values

-

This -function has no return values.

-

NormNodeSetUnicastNack()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormNodeSetUnicastNack(NormNodeHandle senderNode, -
                            bool           state);

-

Description

-

This -function controls the the destination address of receiver feedback -messages generated in response to a specific remote NORM sender.. If -state is true, "unicast -NACKing" is enabled while it is disabled for state -equal to false. See the description of NormSetDefaultUnicastNack() -for details on 'unicast NACKing" behavior.

-

Return Values

-

This -function has no return values.

-

NormSetDefaultNackingMode()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetDefaultNackingMode(NormSessionHandle session, -
                               NormNackingMode   nackingMode);

-

Description

-

This -function sets the default "nacking mode" used when -receiving objects. This allows the receiver application some control -of its degree of participation in the repair process. By limiting -receivers to only request repair of objects in which they are really -interested in receiving, some overall savings in unnecessary network -loading might be realized. Available nacking modes include:

- - - - - - - - - - - - - - - -
-

NORM_NACK_NONE

-
-

- Do not transmit any repair requests for the newly received object.

-
-

NORM_NACK_INFO_ONLY

-
-

- Transmit repair requests for NORM_INFO content only as needed.

-
-

NORM_NACK_NORMAL

-
-

- Transmit repair requests for entire object as needed.

-
-

This -function specifies the default behavior with respect to any new -sender or object. This default behavior may be overridden for -specific sender nodes or specific object using -NormNodeSetNackingMode() or -NormObjectSetNackingMode(), respectively. -The receiver application's use of NORM_NACK_NONE -essentially disables a guarantee of reliable reception, although the -receiver may still take advantage of sender repair transmissions in -response to other receivers' requests. When the sender provides, -NORM_INFO content for transmitted objects, the NORM_NACK_INFO_ONLY -mode may allows the receiver to reliably receive object context -information from which it may choose to "upgrade" its -nacking mode for the specific object via the -NormObjectSetNackingMode() call. -Similarly, the receiver may changes its default nacking mode with -respect to specific senders via the NormNodeSetNackingMode() -call. The default "default nacking mode" when this call is -not made is NORM_NACK_NORMAL.

-

Return Values

-

This -function has no return values.

-

NormNodeSetNackingMode()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormNodeSetNackingMode(NormNodeHandle  nodeHandle, -
                            NormNackingMode -nackingMode);

-

Description

-

This -function sets the default "nacking mode" used for receiving -new objects from a specific sender as identified by the nodeHandle -parameter. This overrides the default nacking mode set for the -receive session. See NormSetDefaultNackingMode() -for a description of possible nackingMode -parameter values and other related information.

-

Return Values

-

This -function has no return values.

-

NormObjectSetNackingMode()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormObjectSetNackingMode(NormObjectHandle objectHandle, -
                              NormNackingMode  nackingMode);

-

Description

-

This -function sets the "nacking mode" used for receiving a -specific transport object as identified by the objectHandle -parameter. This overrides the default nacking mode set for the -applicable sender node. See NormSetDefaultNackingMode() -for a description of possible nackingMode -parameter values and other related information.

-

Return Values

-

This -function has no return values.

-

NormSetDefaultRepairBoundary()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormSetDefaultRepairBoundary(NormSessionHandle  sessionHandle, -
                                  NormRepairBoundary -repairBoundary);

-

Description

-

This -function allows the receiver application to customize, for a given -sessionHandle, at what points the -receiver initiates the NORM NACK repair process during protocol -operation. Normally, the NORM receiver initiates NACking for repairs -at the FEC code block and transport object boundaries. For smaller -block sizes, the NACK repair process is often/quickly initiated and -the repair of an object will occur, as needed, during the -transmission of the object. This default operation corresponds to -repairBoundary equal to -NORM_BOUNDARY_BLOCK. Using this -function, the application may alternatively, setting repairBoundary -equal to NORM_BOUNDARY_OBJECT, -cause the protocol to defer NACK process initiation until the current -transport object has been completely transmitted. This mode of -operation may be useful when it is desirable to allow receivers with -high quality network connectivity (perhaps requiring only a little -(or even no) "auto parity" (see NormSetAutoParity()) -to achieve reliable transfer) receive object transmission before any -extensive repair process that may be required to satisfy other -receivers with poor network connectivity. The repair boundary can -also be set for individual remote senders using the -NormNodeSetRepairBoundary() function.

-

Return Values

-

This -function has no return values.

-

NormNodeSetRepairBoundary()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormNodeSetRepairBoundary(NormNodeHandle     nodeHandle, -
                               NormRepairBoundary -repairBoundary);

-

Description

-

This -function allows the receiver application to customize, for the -specific remote sender referenced by the nodeHandle -parameter, at what points the receiver initiates the NORM NACK repair -process during protocol operation. See the description of -NormSetDefaultRepairBoundary() for -further details on the impact of setting the NORM receiver repair -boundary and possible values for the repairBoundary -parameter.

-

Return Values

-

This -function has no return values.

-

NormStreamRead()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormStreamRead(NormObjectHandle -streamHandle,
                    char*            buffer
                    unsigned -int*    numBytes);

-

Description

-

This -function can be used by the receiver application to read any -available data from an incoming NORM stream. NORM receiver -applications "learn" of available NORM streams via -NORM_RX_OBJECT_NEW notification -events. The streamHandle parameter -here must correspond to a valid NormObjectHandle -value provided during such a prior NORM_RX_OBJECT_NEW -notification. The buffer parameter -must be a pointer to an array where the received data can be stored -of a length as referenced by the numBytes -pointer. On successful completion, the numBytes -storage will be modified to indicate the actual number of bytes -copied into the provided buffer. -If the numBytes storage is -modified to a zero value, this indicates that no stream data was -currently available for reading.

-

Note -that NormStreamRead() is never a blocking -call and only returns failure (false) -when a break in the integrity of the received stream occurs. The -NORM_RX_OBJECT_UPDATE provides an -indication to when there is stream data available for reading. When -such notification occurs, the application should repeatedly -read from the stream until the numBytes -storage is set to zero, even if a false -value is returned. Additional NORM_RX_OBJECT_UPDATE -notifications might not be posted until the application has read all -available data.

-

Return Values

-

This -function normally returns a value of true. -However, if a break in the integrity of the reliable received stream -occurs (or the stream has been ended by the sender), a value of false -is returned to indicate the break. Unless the stream has been ended -(and the receiver application will receive NORM_RX_OBJECT_COMPLETED -notification for the stream in that case), the application may -continue to read from the stream as the NORM protocol will -automatically "resync" to streams, even if network -conditions are sufficiently poor that breaks in reliability occur. If -such a "break" and "resync" occurs, the -application may be able to leverage other NORM API calls such as -NormStreamSeekMsgStart() or -NormStreamGetOffset() if needed to -recover its alignment with received stream content. This depends upon -the nature of the application and its stream content.

-

NormStreamSeekMsgStart()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormStreamSeekMsgStart(NormObjectHandle streamHandle);

-

Description

-

This -function advances the read offset of the receive stream referenced by -the streamHandle parameter to -align with the next available message boundary. Message boundaries -are defined by the sender application using the NormStreamMarkEom() -call. Note that any received data prior to the next message boundary -is discarded by the NORM protocol engine and is not available to the -application (i.e., there is currently no "rewind" function -for a NORM stream). Also note this call cannot be used to skip -messages. Once a valid message boundary is found, the application -must read from the stream using NormStreamRead() -to further advance the read offset. The current offset (in bytes) for -the stream can be retrieved via NormStreamGetReadOffset().

-

Return Values

-

This -function returns a value of true -when start-of-message is found. The next call to NormStreamRead() -will retrieve data aligned with the message start. If no new message -boundary is found in the buffered receive data for the stream, the -function returns a value of false. -In this case, the application should defer repeating a call to this -function until a subsequent NORM_RX_OBJECT_UPDATE -notification is posted.

-

NormStreamGetReadOffset()

-

Synopsis

-

-#include <normApi.h>

-

-unsigned long -NormStreamGetReadOffset(NormObjectHandle streamHandle);

-

Description

-

This -function retrieves the current read offset value for the receive -stream indicated by the streamHandle -parameter. Note that for very long-lived streams, this value may -wrap. Thus, in general, applications should not be highly dependent -upon the stream offset, but this feature may be valuable for certain -applications which associate some application context with stream -position.

-

Return Values

-

This -function returns the current read offset in bytes. The return value -is undefined for sender streams. There is no error result. -

-

NORM Object Functions

-

The -functions described in this section may be used for sender or -receiver purposes to manage transmission and reception of NORM -transport objects. In most cases, the receiver will be the typical -user of these functions to retrieve additional information on -newly-received objects. All of these functions require a valid -NormObjectHandle argument which specifies -the applicable object. Note that NormObjectHandle -values obtained from a NormEvent -notification may be considered valid only until a subsequent -call to NormGetNextEvent(), unless -explicitly retained by the application (see NormObjectRetain()). -NormObjectHandle values obtained as a -result of NormFileEnqueue(), -NormDataEnqueue(), or NormOpenStream() -calls can be considered valid only until a corresponding -NORM_TX_OBJECT_PURGED notification -is posted or the object is dequeued using NormCancelObject(), -unless, again, otherwise explicitly retained (see -NormObjectRetain()).

-

NormObjectGetType()

-

Synopsis

-

-#include <normApi.h>

-

-NormObjectType -NormObjectGetType(NormObjectHandle objectHandle);

-

Description

-

This -function can be used to determine the object type (NORM_OBJECT_DATA, -NORM_OBJECT_FILE, or -NORM_OBJECT_STREAM) for the NORM -transport object identified by the objectHandle -parameter. The objectHandle must -refer to a current, valid transport object.

-

Return Values

-

This -function returns the NORM object type. Valid NORM object types -include NORM_OBJECT_DATA, -NORM_OBJECT_FILE, or -NORM_OBJECT_STREAM. A type value -of NORM_OBJECT_NONE will be -returned for an objectHandle value of NORM_OBJECT_INVALID.

-

NormObjectHasInfo()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormObjectHasInfo(NormObjectHandle objectHandle);

-

Description

-

This -function can be used to determine if the sender has associated any -NORM_INFO content with the transport object specified by the -objectHandle parameter. This can -even be used before the NORM_INFO content is delivered to the -receiver and a NORM_RX_OBJECT_INFO -notification is posted.

-

Return Values

-

A -value of true is returned if -NORM_INFO is (or will be) available for the specified transport -object. A value of false is -returned otherwise.

-

NormObjectGetInfoLength()

-

Synopsis

-

-#include <normApi.h>

-

-unsigned short -NormObjectGetInfoLength(NormObjectHandle objectHandle);

-

Description

-

This -function can be used to determine the length of currently available -NORM_INFO content (if any) associated with the transport object -referenced by the objectHandle -parameter.

-

Return Values

-

The -length of the NORM_INFO content, in bytes, of currently available for -the specified transport object is returned. A value of 0 -is returned if no NORM_INFO content is currently available or -associated with the object.

-

NormObjectGetInfo()

-

Synopsis

-

-#include <normApi.h>

-

-unsigned short -NormObjectGetInfo(NormObjectHandle -objectHandle,
                                 char*            buffer,
                                 unsigned -short   bufferLen);

-

Description

-

This -function copies any NORM_INFO content associated (by the sender -application) with the transport object specified by objectHandle -into the provided memory space referenced by the buffer -parameter. The bufferLen parameter -indicates the length of the buffer -space in bytes. If the provided bufferLen -is less than the actual NORM_INFO length, a partial copy will occur. -The actual length of NORM_INFO content available for the specified -object is returned. However, note that until a NORM_RX_OBJECT_INFO -notification is posted to the receive application, no NORM_INFO -content is available and a zero result will be returned, even if -NORM_INFO content may be subsequently available. The -NormObjectHasInfo() call can be used to -determine if any NORM_INFO content will ever be available for a -specified transport object (i.e., determine if the sender has -associated any NORM_INFO with the object in question).

-

Return Values

-

The -actual length of currently available NORM_INFO content -for the specified transport object is returned. This function can be -used to determine the length of NORM_INFO content for the object even -if a NULL buffer -value and zero bufferLen is -provided. A zero value is returned if NORM_INFO content has not yet -been received (or is non-existent) for the specified object.

-

NormObjectGetSize()

-

Synopsis

-

-#include <normApi.h>

-

-NormSize -NormObjectGetSize(NormObjectHandle objectHandle);

-

Description

-

This -function can be used to determine the size (in bytes) of the -transport object specified by the objectHandle -parameter. NORM can support large object sizes for the -NORM_OBJECT_FILE type, so -typically the NORM library is built with any necessary, related -macros defined such that operating system large file support is -enabled (e.g., "#define _FILE_OFFSET_BITS -64" or equivalent). The NormSize -type is defined accordingly, so the application should be built with -the same large file support configuration.

-

For -objects of type NORM_OBJECT_STREAM, -the size returned here corresponds to the stream buffer size set by -the sender application when opening the referenced stream object.

-

Return Values

-

A -size of the data content of the specified object, in bytes, is -returned. Note that it may be possible that some objects have zero -data content, but do have NORM_INFO content available.

-

NormObjectGetBytesPending()

-

Synopsis

-

-#include <normApi.h>

-

-NormSize -NormObjectGetBytesPending(NormObjectHandle objectHandle);

-

Description

-

This -function can be used to determine the progress of reception of the -NORM transport object identified by the objectHandle -parameter. This function indicates the number of bytes that are -pending reception (I.e., when the object is completely received, -"bytes pending" will equal ZERO). This function is not -necessarily applicable to objects of type NORM_OBJECT_STREAM which do -not have a finite size. Note it is possible that this function might -also be useful to query the "transmit pending" status of -sender objects, but it does not account for pending FEC repair -transmissions and thus may not produce useful results for this -purpose.

-

Return Values

-

A -number of object source data bytes pending reception (or -transmission) is returned.

-

NormObjectCancel()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormObjectCancel(NormObjectHandle objectHandle);

-

Description

-

This -function immediately cancels the transmission of a local sender -transport object or the reception of a specified object from a remote -sender as specified by the objectHandle -parameter. The objectHandle must -refer to a currently valid NORM transport object. Any resources used -by the transport object in question are immediately freed unless the -object has been otherwise retained by the application via the -NormObjectRetain() call. Unless the -application has retained the object in such fashion, the object in -question should be considered invalid and the application must not -again reference the objectHandle -after this call is made. -

-

If -the canceled object is a sender object not completely received by -participating receivers, the receivers will be informed of the -object's cancellation via the NORM protocol NORM_CMD(SQUELCH) message -in response to any NACKs requesting repair or retransmission of the -applicable object. In the case of receive objects, the NORM receiver -will not make further requests for repair of the indicated object, -but furthermore, will acknowledge the object as completed with -respect to any associated positive acknowledgement requests (see -NormSetWatermark()).

-

Return Values

-

This -function has no return value.

-

NormObjectRetain()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormObjectRetain(NormObjectHandle objectHandle);

-

Description

-

This -function "retains" the objectHandle -and any state associated with it for further use by the application -even when the NORM protocol engine may no longer require access to -the associated transport object. Normally, the application is -guaranteed that a given NormObjectHandle -is valid only while it is being actively transported by NORM (i.e., -for sender objects, from the time an object is created by the -application until it is canceled by the application or purged (see -the NORM_TX_OBJECT_PURGED -notification) by the protocol engine, or, for receiver objects, from -the time of the object's NORM_RX_OBJECT_NEW -notification until its reception is canceled by the application or a -NORM_RX_OBJECT_COMPLETED or -NORM_RX_OBJECT_ABORTED -notification is posted). Note that an application may refer to a -given object after any related notification until the application -makes a subsequent call to NormGetNextEvent(). -

-

When -the application makes a call to NormObjectRetain() -for a given objectHandle, the -application may use that objectHandle -value in any NORM API calls until the application makes a call to -NormObjectRelease() for the given object. -Note that the application MUST make a corresponding call to -NormObjectRelease() for each call it has -made to NormObjectRetain() in order to -free any system resources (i.e., memory) used by that object. Also -note that retaining a receive object also automatically retains any -state associated with the NormNodeHandle -corresponding to the remote sender of that receive object so -that the application may use NORM node API calls for the value -returned by NormObjectGetSender() as -needed.

-

Return Values

-

This -function has no return value.

-

NormObjectRelease()

-

Synopsis

-

-#include <normApi.h>

-

-void -NormObjectRelease(NormObjectHandle objectHandle);

-

Description

-

This -function complements the NormObjectRetain() -call by immediately freeing any resources associated with the given -objectHandle, assuming the -underlying NORM protocol engine no longer requires access to the -corresponding transport object. Note the NORM protocol engine -retains/releases state for associated objects for its own needs and -thus it is very unsafe for an application to call NormObjectRelease() -for an objectHandle for which it -has not previously explicitly retained via NormObjectRetain(). -

-

Return Values

-

This -function has no return value.

-

NormFileGetName()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormFileGetName(NormObjectHandle -fileHandle)
                     char*            nameBuffer,
                     unsigned -int     bufferLen);

-

Description

-

This -function copies the name, as a NULL-terminated string, of the file -object specified by the fileHandle -parameter into the nameBuffer of -length bufferLen bytes provided by -the application. The fileHandle -parameter must refer to a valid NormObjectHandle -for an object of type NORM_OBJECT_FILE. If the actual name is longer -than the provided bufferLen, a -partial copy will occur. Note that the file name consists of the -entire path name of the specified file object and the application -should give consideration to operating system file path lengths when -providing the nameBuffer.

-

Return Values

-

This -function returns true upon success and false upon failure. Possible -failure conditions include the fileHandle -does not refer to an object of type NORM_OBJECT_FILE.

-

NormFileRename()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormFileRename(NormObjectHandle fileHandle)
                    const -char*      fileName);

-

Description

-

This -function renames the file used to store content for the -NORM_OBJECT_FILE transport object -specified by the fileHandle -parameter. This allows receiver applications to rename (or move) -received files as needed. NORM uses temporary file names for received -files until the application explicitly renames the file. For example, -sender applications may choose to use the NORM_INFO content -associated with a file object to provide name and/or typing -information to receivers. The fileName -parameter must be a NULL-terminated string which should specify the -full desired path name to be used. NORM will attempt to create -sub-directories as needed to satisfy the request. Note that existing -files of the same name may be overwritten.

-

Return Values

-

This -function returns true upon success and false upon failure. Possible -failure conditions include the case where the fileHandle -does not refer to an object of type NORM_OBJECT_FILE -and where NORM was unable to successfully create any needed -directories and/or the file itself.

-

NormDataAccessData()

-

Synopsis

-

-#include <normApi.h>

-

-const char* -NormDataAccessData(NormObjectHandle objectHandle)

-

Description

-

This -function allows the application to access the data storage area -associated with a transport object of type NORM_OBJECT_DATA. -For example, the application may use this function to copy the -received data content for its own use. Alternatively, the application -may establish "ownership" for the allocated memory space -using the NormDataDetachData() -function if it is desired to avoid the copy.

-

If -the object specified by the objectHandle -parameter has no data content (or is not of type NORM_OBJECT_DATA), -a NULL value may be returned. The application MUST NOT attempt to -modify the memory space used by NORM_OBJECT_DATA -objects during the time an associated objectHandle -is valid. The length of data storage area can be determined with a -call to NormObjectGetSize() for the same -objectHandle value.

-

Return Values

-

This -function returns a pointer to the data storage area for the specified -transport object. A NULL value may be returned if the object has no -associated data content or is not of type NORM_OBJECT_DATA.

-

NormDataDetachData()

-

Synopsis

-

-#include <normApi.h>

-

-char* -NormDataDetachData(NormObjectHandle objectHandle)

-

Description

-

This -function allows the application to disassociate data storage -allocated by the NORM protocol engine for a receive object from the -NORM_OBJECT_DATA transport object -specified by the objectHandle -parameter. It is important that this function is called after -the NORM protocol engine has indicated it is finished with the data -object (i.e., after a NORM_TX_OBJECT_PURGED, -NORM_RX_OBJECT_COMPLETED, or -NORM_RX_OBJECT_ABORTED -notification event). But the application must call -NormDataDetachData() before a call -is made to NormObjectCancel() or -NormObjectRelease() for the object if it -plans to access the data content afterwards. Otherwise, the NORM -protocol engine will free the applicable memory space when the -associated NORM_OBJECT_DATA -transport object is deleted and the application will be unable to -access the received data unless it has previously copied the content.

-

Once -the application has used this call to "detach" the data -content, it is the application's responsibility to subsequently free -the data storage space as needed.

-

Return Values

-

This -function returns a pointer to the data storage area for the specified -transport object. A NULL value may be returned if the object has no -associated data content or is not of type NORM_OBJECT_DATA.

-

NormObjectGetSender()

-

Synopsis

-

-#include <normApi.h>

-

-NormNodeHandle -NormObjectGetSender(NormObjectHandle objectHandle)

-

Description

-

This -function retrieves the NormNodeHandle -corresponding to the remote sender of the transport object associated -with the given objectHandle -parameter. Note that the returned NormNodeHandle -value is only valid for the same period that the objectHandle -is valid. The returned NormNodeHandle may -optionally be retained for further use by the application using the -NormNodeRetain() function call. The -returned value can be used in the NORM Node Functions described later -in this document.

-

Return Values

-

This -function returns the NormNodeHandle -corresponding to the remote sender of the transport object associated -with the given objectHandle -parameter. A value of NORM_NODE_INVALID is returned if the specified -objectHandle references a locally -originated, sender object.

-

NORM Node Functions

-

The -functions described in this section may be used for NORM sender or -receiver (most typically receiver) purposes to retrieve additional -information about a NormNode, given a valid NormNodeHandle. -Note that, unless specifically retained (see NormNodeRetain()), -a NormNodeHandle provided in a NormEvent -notification should be considered valid only until a subsequent call -to NormGetNextEvent() is made. -NormNodeHandles retrieved using -NormObjectGetSender() can be considered -valid for the same period of time as the corresponding -NormObjectHandle is valid.

-

NormNodeGetId()

-

Synopsis

-

-#include <normApi.h>

-

-NormNodeId -NormNodeGetId(NormNodeHandle nodeHandle)

-

Description

-

This -function retrieves the NormNodeId -identifier for the remote participant referenced by the given -nodeHandle value. The NormNodeId -is a 32-bit value used within the NORM protocol to uniquely identify -participants within a NORM session. The participants identifiers are -assigned by the application or derived (by the NORM API code) from -the host computers default IP address.

-

Return Values

-

This -function returns the NormNodeId value -associated with the specified nodeHandle. -In the case nodeHandle is equal to -NORM_NODE_INVALID, the return -value will be NORM_NODE_NONE.

-

NormNodeGetAddress()

-

Synopsis

-

-#include <normApi.h>

-

-bool -NormNodeGetAddress(NormNodeHandle -nodeHandle,
                        char*           addrBuffer,
                        unsigned -int*   bufferLen,
                        unsigned -short* port = (unsigned short*)0);

-

Description

-

This -function retrieves the current network source address detected for -packets received from remote NORM sender referenced by the nodeHandle -parameter. The addrBuffer must be -a pointer to storage of bufferLen -bytes in length in which the referenced sender node's address will be -returned. Optionally, the remote sender source port number (see -NormSetTxPort()) is also returned if the -optional port pointer to storage -parameter is provided in the call. Note that in the case of Network -Address Translation (NAT) or other firewall activities, the source -address detected by the NORM receiver may not be the original address -of the original NORM sender.

-

Return Values

-

A -value of true is returned upon -success and false upon failure. An -invalid nodeHandle parameter value -would lead to failure.

-

NormNodeGetGrtt()

-

Synopsis

-

-#include <normApi.h>

-

-double NormNodeGetId(NormNodeHandle -nodeHandle)

-

Description

-

This -function retrieves the advertised estimate of group round-trip timing -(GRTT) for the remote sender referenced by the given nodeHandle -value. Newly-starting senders that have been participating as a -receiver within a group may wish to use this function to provide a -more accurate startup estimate of GRTT (see NormSetGrttEstimate()) -prior to a call to NormStartSender(). -Applications may use this information for other purpose as well. Note -that the NORM_GRTT_UPDATED event -is posted (see NormGetNextEvent()) by the -NORM protocol engine to indicate when changes in the local sender or -remote senders' GRTT estimate occurs.

-

Return Values

-

This -function returns the remote sender's advertised GRTT estimate in -units of seconds. A value of -1.0 -is returned upon failure. An invalid nodeHandle -parameter value will lead to such failure.

-

NormNodeRetain()

-

Synopsis

-

-#include <normApi.h>

-

-void NormNodeRetain(NormNodeHandle -nodeHandle)

-

Description

-

In -the same manner as the NormObjectRetain() -function, this function allows the application to retain state -associated with a given nodeHandle -value even when the underlying NORM protocol engine might normally -free the associated state and thus invalidate the NormNodeHandle. -If the application uses this function, it must make a corresponding -call to NormNodeRelease() when finished -with the node information to avoid a memory leak condition. -NormNodeHandle values (unless retained) -are valid from the time of a NORM_REMOTE_SENDER_NEW -notification until a complimentary NORM_REMOTE_SENDER_PURGED -notification. During that interval, the application will receive -NORM_REMOTE_SENDER_ACTIVE and -NORM_REMOTE_SENDER_INACTIVE -notifications according to the sender's message transmission activity -within the session.

-

It -is important to note that, if the NORM protocol engine posts a -NORM_REMOTE_SENDER_PURGED -notification for a given NormNodeHandle, -the NORM protocol engine could possibly, subsequently establish a -new, different NormNodeHandle value for -the same remote sender (i.e., one of equivalent NormNodeId) -if it again becomes active in the session. A new NormNodeHandle -may likely be established even if the application has retained the -previous NormNodeHandle value. Therefore, -to the application, it might appear that two different senders with -the same NormNodeId are participating if -these notifications are not carefully monitored. This behavior is -contingent upon how the application has configured the NORM protocol -engine to manage resources when there is potential for a large number -of remote senders within a session (related APIs are TBD). For -example, the application may wish to control which specific remote -senders for which it keeps state (or limit the memory resources used -for remote sender state, etc) and the NORM API may be extended in the -future to control this behavior.

-

Return Values

-

This -function has no return value.

-

NormNodeRelease()

-

Synopsis

-

-#include <normApi.h>

-

-void NormNodeRelease(NormNodeHandle -nodeHandle)

-

Description

-

In -complement to the NormNodeRetain() -function, this API call releases the specified nodeHandle -so that the NORM protocol engine may free associated resources as -needed. Once this call is made, the application should no longer -reference the specified NormNodeHandle, -unless it is still valid.

-

Return Values

-

This -function has no return value.

- - diff --git a/NormDeveloperGuide.pdf b/NormDeveloperGuide.pdf deleted file mode 100644 index 8097c77b5312217f4b9380b555cfdebf5a4463fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 948062 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

^1Op>msC^Y{D1z`Ku&^LdFZ=YFvs1FR(cDq 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

E>mHbC&%HHKZzF)OdQD6wZZzLzd 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�_)&FCWu)mN(lZ0 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 z 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%0DVVQg>7qm0KSM3hOcdq#Us0gZyl;2(hGUv# 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­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%0AXocD?#Ei3m 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

S%Kk9O{>=- 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

Mi`j}XH%^RPPZMh{q*SpW??O6*3YJi49#^A9J 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 K>+a#D1OEc!Y@$B^ diff --git a/NormSocketBindingNotes.txt b/NormSocketBindingNotes.txt new file mode 100644 index 0000000..11d0d9a --- /dev/null +++ b/NormSocketBindingNotes.txt @@ -0,0 +1,177 @@ +NORM SOCKET BINDING NOTES +========================= + +NORM supports a number of socket binding options to allow for multiple paradigms of use. The default operation of NORM involves two separate sockets: + +1) a "tx_socket" used for sender transmission and reception of unicast feedback, and +2) a "rx_socket" used for reception of packets sent to the "session" address/port + +By default both sockets are bound to INADDR_ANY with the rx_socket using the "session port" number and the +tx_socket using a port freely assigned by the operating system unless the NormSetTxPort() API is invoked. The INADDR_ANY binding allows the sockets to receive packets from any remote address/port thus support NORM's typical multicast operation compatible with both unicast and multicast feedback and even "many-to-one" unicast reception (if unicast feedback is set via NormSetDefaultUnicastNack()). + +Some optional binding behaviors are invoked depending upon some additional NORM API calls. For example, the "rx_socket" can be optionally bound to the session address. This restricts the rx_socket to receive only packets specifically directed to the configured session address. The session address must be a multicast address or valid local IP address for this to work. And in the latter case, the NormSession MUST be operated +only as a NORM receiver (i.e., NormStartSender() will result in transmissions to self only!) + +The rationale for the separate tx_socket and rx_socket usage was to enable NORM senders to detect when unicast feedback is used so that proper protocol behaviors are invoked (e.g. NORM_CMD(REPAIR_ADV)). Note that the code base _may_ be eventually updated so this is no longer necessary (e.g. through use of the "recvmsg()" system call +with proper socket configuration to receive UDP packet destination address header information). It may still be the case that a use case for separate send/recv sockets may persist and the code will continue to support that although it is possible that it may become the optional behavior rather than the default. + +bind semantics: localAddr / localPort + +connect semantics: remoteAddr / remotePort + ++-------------------------------+-------------------------------+---------------------------------------+ +| bind() state | connect() state | | ++---------------+---------------+---------------+---------------+ Behaviors | +| localAddr | localPort | remoteAddr | remotePort | | ++---------------+---------------+---------------+---------------+---------------------------------------+ +| INADDR_ANY | 0 | NONE | OS-assigned port, sendto() any | ++---------------+---------------+---------------+---------------+---------------------------------------+ +| INADDR_ANY | | NONE | from any to <*/port>, sendto() any | ++---------------+---------------+---------------+---------------+---------------------------------------+ +| | | NONE | from any to . sendto() any | ++---------------+---------------+---------------+---------------+---------------------------------------+ +| INADDR_ANY | | remoteAddr | remotePort | from remote to <*/port> | ++---------------+---------------+---------------+---------------+---------------------------------------+ +| | | remoteAddr | remotePort | from remote to only | ++---------------+---------------+---------------+---------------+---------------------------------------+ + +The Rules: + +If you don't bind(), you don't receive anything. + +If you connect() you can only send to the specified connected addr / port. + +Cannot connect to multicast addr. + +Can only bind to multicast or valid local IP addr + +With port reuse, each multicast socket will get a copy of matching inbound multicast packets. In fact, with an INADDR_ANY bind, the socket will get packets for _any_ group joined if the port number matches. But with an inbound unicast packet, only one socket with closest matching binding will get the packet. This includes +the bind() (i.e. local addr/port) _and_ connect() (i.e. remote addr/port) state with regard to the incoming packet where the most specific match is considered the best match. + +Thus these bind/connect combinations are important in order to achieve some desired set of behaviors. + + + +DESIRED BEHAVIORS: + +1) NORM sender / receiver operation with feedback to the session port and/or unicast feedback. + + +2) Binding packet reception for socket(s) to specific address(es) with or without port reuse + +NormSetTxPort() currently has option to bind the tx_socket to a specific local address (txAddress) +NormSetRxPortReuse() currently has option to bind rx_socket to multicast session address. + +TBD - Need to add "NormSetRxAddress()" to bind rx_socket to non-session local address + + +3) Connecting socket(s) to remote unicast addr/port so that reused port demuxing works properly + (This is probably applicable only to unicast sessions) + +So, the following conditions should be true to "connect" the socket: + +1) Session address is unicast +2) Port reuse is true. + + XXX - finish these thoughts and revise the implementation!!!!! + +REQUIREMENTS + + +Enumerate some use cases and the corresponding socket behavior requirements + +1) Default: sender/receiver multicast operation w/ mcast and ucast feedback + +tx_socket : sendto() session addr/port,sender(s) addr/port + recvfrom() receiver src addr/port to local ucast addr +rx_socket : recvfrom() sender addr/port to session addr/port + +If rxAddr is unspecified, can also receive to unicast + +2) Sender-only one-to-many + +tx_socket : sendto() session addr/port,sender(s) addr/port + recvfrom() receiver src addr/port to local ucast addr + +rx_socket : recvfrom() sender addr/port to session addr/port (mcast feedback) + + +3) Receive-only many-to-one unicasts + +tx_socket : sendto() senders addr/port +rx_socket : recvfrom() senders addr/port to session port + +4) Bi-directional unicast + +tx_socket : sendto() session addr/port +rx_socket : recvfrom() session addr/* to session port + + + + +IMPLEMENTATION NOTES: + +1) Default binding + +rx_socket : INADDR_ANY / + +tx_socket : INADDR_ANY / 0 (any port) + +Implementation Details: + +This is the default behavior if none of the additional API calls described below are made. + +--------------------------------------------------------------------------- +2) The NormSetTxPort(txPort, enableReuse, txAddr) lets this be modified by: + +a) if (txPort != sessionPort) tx_socket is bound to +b) if (txPort == sessionPort) rx_socket is used for transmission, too + +c) if "enableReuse" is true, PORT/ADDR REUSE is set for tx_socket + +d) if txAddr is valid, the tx_socket (or (TBD) rx_socket if txPort == sessionPort) is bound + to the given which must be a valid IP addr for the host (This means only + unicast packets for that address are received on that socket. Thus the binding is _not_ + made for the rx_socket case if the session address is a multicast address! + + +Implementation Details: + +TBD + + +--------------------------------------------------------------------------- +3) The NormSetRxPortReuse(enable, bindToSessionAddr) modifies the default binding as follows: + +a) If is "true", PORT/ADDR REUSE is set for the rx_socket + +b) if is "true" _and_ the session address is multicast, the rx_socket + binding is / . This enables the same port number to be + reused for different multicast session addresses _and_ restricts the rx_socket to receive + only packets destined for the sessionAddr. In this case, unicast feedback to the rx_socket + (i.e. in the case of above where txPort == sessionPort) will NOT work! + TBD - The code resolves this conflict by ... + + +Implementation Details: + +TBD + + + --------------------------------------------------------------------------- +4) The NormSetTxOnly() causes only the tx_socket to be opened and no rx_socket is opened. In fact, + the rx_socket is closed if it is already open (unless txPort == sessionPort ala above) + + +Implementation Details: + +TBD + + + + + + + + + diff --git a/NormUserGuide.doc b/NormUserGuide.doc deleted file mode 100644 index bd59f08d66eb9489bd63524baa9c56ca0a2b12ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113664 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 )5Y_RBs{3 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

HBHnVHV;BqL=w7Q(>*Lq0Rwq8j0{ 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 Z*@Wyo{@OJ_3}b^FcS1oSDlZ2AzW`2))};Ud diff --git a/README-Java.txt b/README-Java.txt new file mode 100644 index 0000000..cae9aca --- /dev/null +++ b/README-Java.txt @@ -0,0 +1,83 @@ +Java JNI bindings for NORM +========================== + +By: + Jason Rush + Peter Griffin + +The Java JNI bindings for NORM provide Java bindings for the NORM C API. + +For documentation about the main NORM API calls, refer to the NORM Developers +guide in the regular NORM distribution. + +------------ +Requirements +------------ + +Java JNI bindings for NORM requires at least Java 1.5; however, it has also +been tested with Java 1.6. + +The NORM library should be built prior to building the Java JNI bindings since +they link against it. + +Apache Ant is required for building the class files and jar file. + +------------ +Building +------------ + +The build files for Java JNI bindings for NORM are located in the +makefiles/java directory. + +To build the NORM jar file, execute Apache Ant in the makefiles/java directory: + + ant jar + +This will produce a norm-.jar file in the lib/ directory. + +To build the accompanying NORM native bindings library, execute make in the +makefiles/java directory with the correct make file for your system: + + make -f Makefile.linux + +This will produce a libmil_navy_nrl_norm.so file in the lib/ directory. + +------------ +Installation +------------ + +The norm-.jar file must be on the classpath. + +The libmil_navy_nrl_norm.so file must be installed in a location that Java +will search at runtime for native libraries. The search path is dependent +upon the operating system: + + Windows: + Windows system directories + Current working directory + PATH environment variable + + Unix: + Current working directory + LD_LIBRARY_PATH environment variable + +You can also specify the directory with the java.library.path Java system +property on startup: + + java -Djava.library.path=

... + +Finally the native library can also be installed in the JRE's library +directory. This allows all programs that use the JRE to access the library: + + /lib/i386/ + +If you observe an "java.lang.UnsatisfiedLinkError" exception while running +your application, you do not have the libmil_navy_nrl_norm.so installed in +the correct location. + +------------ +Examples +------------ + +Examples using the Java JNI bindings for NORM are provided in the examples/java +directory. diff --git a/README-PyNorm.txt b/README-PyNorm.txt new file mode 100644 index 0000000..fd7d5c7 --- /dev/null +++ b/README-PyNorm.txt @@ -0,0 +1,43 @@ +PyNORM - Python Wrapper for NORM & Extras +========================================= + +By: Tom Wambold + +PyNORM provides a thin wrapper around the NORM C API in the main package. It +also provides several additional modules in the extra package to provide higher +level usage of NORM. + +For documentation about the main NORM API calls, refer to the NORM Developers +guide in the regular NORM distribution. + +Also, the files in the examples/python directory have a lot of good info on how +to use the library. For simple apps, see the normFileSend and normFileReceive +scripts. + +------------ +Requirements +------------ + +PyNORM has been tested on Python versions 2.5 and 2.6. 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 +that Python's "ctypes" module can load it. Ctypes will look for the library +in similar places as your system's compiler. On UNIX systems, this is usually +/usr/lib and /usr/local/lib, etc. On Windows, this searches folders in the +PATH environment variable. + +On Windows, PyNORM requires the "PyWin32" module, available at: + http://sf.net/projects/pywin32 + +----------------------- +"Extra" Package Modules +----------------------- + +manager.py: + This provides a callback-driven event loop for NORM. It operates on a + separate thread, calling registered functions in response to NORM events. + +logparser.py: + This provides a function to reads NORM debug "reports" (printed at debug + level 3 or higher) from a log file, and parses them into Python objects. diff --git a/README.md b/README.md index 9b7981f..48020f3 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,11 @@ modification, are permitted provided that: (1) source code distributions retain this paragraph in its entirety, -(2) distributions including binary code include this paragraph in - its entirety in the documentation or other materials provided - with the distribution, and - -(3) all advertising materials mentioning features or use of this +(2) all advertising materials mentioning features or use of this software display the following acknowledgment: "This product includes software written and developed - by Brian Adamson and Joe Macker of the Naval Research - Laboratory (NRL)." + by the Naval Research Laboratory (NRL)." The name of NRL, the name(s) of NRL employee(s), or any entity of the United States Government may not be used to endorse or @@ -43,7 +38,7 @@ purposes, the authors would prefer that the code not be re-distributed so that users will obtain the latest (and most debugged/improved) release from the official NORM web site: - + SOURCE CODE @@ -63,15 +58,14 @@ The following items can be build from this source code release: be provided as additional documentation in the future) -3) norm (or norm.exe (WIN32) - command-line "demo" application +2) norm (or norm.exe (WIN32) - command-line "demo" application built from "common/normApp.cpp" (The included "Norm User's Guide" provides a rough overview - of how to use this demo app. This document requires some - updating to be accurate and complete. This demo application + of how to use this demo app. This demo application is useful for file transfers (and streaming on Unix)) -4) normTest (or normTest.exe (WIN32)) - very simple test application +3) normTest (or normTest.exe (WIN32)) - very simple test application (The "normTest" application (see "common/normTest.cpp") is really just a simple playground for preliminary testing of the NORM @@ -80,7 +74,7 @@ The following items can be build from this source code release: (and better-documented) examples of NORM API usage will be provided in the future. -5) There is also an "examples" directory in the source code tree +4) There is also an "examples" directory in the source code tree that contains some simplified examples of NORM API usage. The example set will be expanded as time progresses. diff --git a/TODO.TXT b/TODO.TXT index bc4d224..5eb5870 100644 --- a/TODO.TXT +++ b/TODO.TXT @@ -1,12 +1,44 @@ +TODO: + 0) Fix implementation of NORM_DATA_FLAG_REPAIR. +2) Add mechanism to allow window-based control of NORM server + transmission rate. + +7) Build "norm daemon" using API. + +10) Release wxNormChat project + +11) Test, release "naft" (updated "raft") for reliable UDP streaming. + +13) Fix ns-2 issue when Agent/NORM is attached to ns-2 node ZERO and + results in a NormNode attempting to be assigned NORM_NODE_NONE as + a node id. + +14) IMPORTANT: double check ACKing in reponse to watermark polling and + relation to cc feedback suppression, etc ... watermark acks should + be unicast and not affect cc feedback? + +17) Set objectId in TX_FLUSH_COMPLETED and TX_WATERMARK_COMPLETED events. + +18) Add APIs for managing remote server state kept at receiver + +22) Implement LDPC FEC code within NORM as alternative to Reed Solomon + +23) Add ability to control receiver cache on a per-sender basis? + (max_pending_range, etc) + +26) Add API calls to get error information + +27) Add API calls to read logged statistics + +========================= +COMPLETED: + 1) Implement unicast feedback of NACK messages and server re-advertisement for suppressing the unicast feedback. (COMPLETED) -2) Add mechanism to allow window-based control of NORM server - transmission rate. - 3) Implement TFMCC and/or PGM-CC congestion control mechanisms within NORM. (TFMCC COMPLETED) @@ -24,34 +56,16 @@ 6) Implement and document simple API to control and access NORM features. (COMPLETED and enhancement in progress) -7) Build "norm daemon" using API. +8) JAVA API for NRL NORM Protocol library (COMPLETED - Thanks Peter Griffin & Jason Rush) -8) JAVA API for NRL NORM Protocol library +9) Correct/complete user guide for "norm" demo app (COMPLETED) -9) Correct/complete user guide for "norm" demo app - -10) Release wxNormChat project - -11) Test, release "naft" (updated "raft") for reliable streaming. 12) Can we eliminate rapid clr norm-cc probing when data is not being actively sent? (COMPLETED) -13) Fix ns-2 issue when Agent/NORM is attached to ns-2 node ZERO and - results in a NormNode attempting to be assigned NORM_NODE_NONE as - a node id. - -14) IMPORTANT: double check ACKing in reponse to watermark polling and - relation to cc feedback suppression, etc ... watermark acks should - be unicast and not effect cc feedback? - -15) Implement TX_OBJECT_SENT event (COMPLETED) -16) Set NORM_DATA "NORM_FLAG_REPAIR" properly - -17) Set objectId in TX_FLUSH_COMPLETED and TX_WATERMARK_COMPLETED events. - -18) Add APIs for managing remote server state kept at receiver +15) Implement TX_OBJECT_SENT event (COMPLETED) 19) Fix "activity_timer" interval setting (COMPLETED) @@ -63,12 +77,7 @@ (COMPLETED - reimplemented, adding tx_repair_pending index to use instead of seeking each time) -22) Implement LDPC FEC code within NORM as alternative to Reed Solomon - -23) Add ability to control receiver cache on a per-sender basis? - (max_pending_range, etc) - 24) Look at NormStreamObject::StreamAdvance() for "push-enabled" streams (COMPLETED) - +25) Improved, consistent socket binding options diff --git a/VERSION.TXT b/VERSION.TXT index 57b55a6..34eaa7d 100644 --- a/VERSION.TXT +++ b/VERSION.TXT @@ -1,5 +1,138 @@ NORM Version History +Version 1.5b1 +============= + - Added option to set NORM receiver "rx cache count max", i.e. the + maximum number of pending object for which the receiver will keep + state (per sender). The "NormSetRxCacheLimit()" API call was added + to control this option. + - Implemented timer-based flow control so that NORM sender apps will not + force purge of old buffered objects (or stream data) for which there is + "recent" NACK activity. What is considered "recent" is based upon + "flowControlFactor*GRTT_sender*(1 + backoffFactor)". Note that a + "flowControlFactor" of 2.0 is default, but can be adjusted. A + new API call "NormSetFlowControl()" will be provided for this. Note that + if insufficient buffering is allocated, this flow control may limit + transmission rate for a given (delay*bandwidth, packetLoss conditions. + However, the likely alternative (if flow control is disabled) is that + reliability would not be perfectly maintained. Note that enabling the + sender "push" option of course overrides the flow control for streams. + For file/data objects, the app could choose to explicitly cancel + (dequeue) objects or disable the flow control if the app wishes to "push + ahead" regardless of impact to reliability. Note that when "pushing", + NORM strives to provide whatever reliability is possible given the + buffering/delay/bandwidth constraints the app imposes. + - Fixed problem with NormServeNode::PassiveRepairCheck() that caused + NORM to sometimes falsely acknowledge an object that was not yet + received (Thanks to Jeff Bush for finding this one). + - Added initial option for Explicit Congestion Notification (ECN) support + in "real-world" code. This includes raw packet capture (via ProtoCap) + so that ECN header info can be examined and setting ECT-Capable Transport + (ECT) bits in IP TOS/Trafffic Class header field. + - Fixed bug in NORM API "NormFileGetName()" call that would overrun buffer + (Thanks to Adrian Cox) + - Fixed bug with NORM probe_timer "lockup" when tx_rate is set to 0.0 + (Thanks again to Adrian Cox) + - Added experimental receiver "real-time" buffer management option. + (not yet in API). Causes receiver to enqueue new data in favor of + old, incomplete data when buffer-constrained. _May_ be useful for + apps where receipt of "fresh" data is more important than reliability. + - Added "requeue" option to "norm" test app to allow multiple retransmissions + of a file to be "stitched" together by a NORM receiver. See the + NormRequeueObject() API call. Thanks to Keith Hogie/ Ed Criscuolo + for suggesting this. + - Enhanced NormSetRxPortReuse() behavior when bindSessionToAddr is true + such that for unicast sessions a UDP socket "connect()" call is made + to effectively "bind" the NormSession rx_socket to the remote + unicast addr. This _should_ enable proper demultiplexing of + multiple unicast sessions using the same port. (Note that this + same technique _may_ be adopted in the future for Source-Specific + Multicast (SSM) support as well where a NormSession is associated + with a specific remote single sender.) + - Added code to NormServerNode::Activate() so that when a previously + INACTIVE remote sender becomes active again (reactivated) that + the receiver automatically kicks off a new cycle of self-initiated + NACKing for any pending objects. Note this code applies when + the "reactivation" is due to a non-object message (e.g. NORM_CMD) + as reception of an object message (NORM_DATA/ NORM_INFO) will + re-awaken NACKing as needed anyway. + (Thanks to Jeff Bush for helping determine the utility of this) + - The NORM source code tree has been re-organized. + - Added Python language binding to NORM API, examples, etc + (Thanks to Tom Wambold) + - Added WAF build scripts, etc (Thanks again to Tom Wambold) + - iPhone Makefile added (Thanks yet again to Tom Wambold) + - Bug fix to NORM receiver "rx_object_table" and "rx_pending_mask" + interplay in NormSenderNode class. (Thanks to Dave Talmage for + stressing NORM enough to find this) + - Bug fix with parsing of "limit" command in normApp.cpp + (Thanks again to Dave Talmage) + - Fixed bug in "NormSessionMgr::NewSession()" where a call + was made to NormSession::SetTxPort() that should not + have been! + - Changed API calls to use "SetTx-XXX" and "SetRx-XXX" more + consistently instead of "SetTransmit-XXX", etc. Sorry for + a change to a couple of key API calls, but this will be + nicer in the long term. + - Fixed problems with NormSession::SenderQueueAppCmd() and + NormSession::SenderQueueSquelch() where there as two different + mis-uses of the NormSession::msg_pool. Thanks to Richard Edell + (MITRE) for finding this one. + - Fixed problem introduced with new support of multiple "fec_id" + types (this version) where receiver processing of NORM_CMD + messages with FecPayloadId content (e.g. FLUSH, SQUELCH, etc) + _before_ reception of NORM_DATA would result in erroneous + messages being generated. + Thanks again to Richard Edell (MITRE) for identifying this. + - Added optional parameter to NormSetCongestionControl() to allow + NORM-CC operation to collect feedback but NOT actually adjust + transmission rate. Also added NORM_TX_RATE_CHANGED notification + to inform app of when rate change (or suggested change) occurs. + - Fixed issue where a small object where INFO was received but no + DATA was not causing receiver NACK process to start upon FLUSH + receipt as it should. (Thanks to John Unger for finding this!) + - Updated JAVA JNI binding code courtesy patches from Kenn Fynn + (Dark Corner Software) + - Fixed bug where remote sender fec_id value was not cached when + only NORM_INFO has been received (thanks to Qingjun Xiao of GSU) + - Added NormSetAutoAckingNodes() API call (and underlying code) to + allow sender acking node list to be "auto populated" based on + hearing any message from a receiver (and/or optionally sender). + Also added NormGetNextAckingNode() API call to allow iteration + through acking node list to determine status (learn of who is + listening, etc). A feature _may_ be added to notify the + sender application when new receivers are detected (and added + to the list) and maybe even allow per-receiver control of whether + an ACK should be solicited or not. I.e., the acking node list would + become more of a "tracked" receiver list. + - Added enhanced congestion control feature where the receiver always + limits the reported rate back to the sender at no more than double + the observed receive rate. Adds LIMIT flag to feedback cc flags + to facilitate this reporting. This is not part of RFC 5740 but not + incompatible with it ... just works a little better. In the future, + another way to do this might be to adjust the reported loss value + accordingly ... and that would be RFC 5740 compliant although there + should be an errata or addendum to the RFC for this. + - Added ECN-based congestion control and simple loss-tolerant + congestion control options. These will be better documented and + perhaps extended in the future. + - Added extended precision loss estimate reporting by using "RESERVED" + field in CC feedback extension header. Again, non-RFC 5740 but + backwards compatible. This helps over large bandwidth-delay links + to get to higher data rates. Some more work will be done in this area + such as perhaps returning to some form of slow start (exponential increase) + behavior after many consecutive RTT with no congestion events. Probably should + mirror the return to slow start behavior that occurs when data transmission is + idle. This needs to be evaluated as NORM may start being unfairly aggressive + in such environments. + - This code has the beginnings of an approach to enable automated population + of the sender "acking node list" + - This code release needs to be more fully documented, but was important + to get online since it has _many_ improvements over the prior + "stable release" + + + Version 1.4b3 ============= - Fixed many (hopefully all) cases where NORM_DATA message FLAG_REPAIR @@ -21,6 +154,9 @@ Version 1.4b3 - Fixed issue with NormSession::QueueTxObject() where ASSERT() failure occurred depending upon previous tx object enqueue/dequeue history (Thanks to Jeff Bush for finding this one) + - Changed repeated scheduling of receiver remote sender inactivity + timer to ensure that busy CPU doesn't result in premature + inactivity indication. - Fixed issue with fix for stream lockup problem from below. (this is in version 1.4b3a) diff --git a/common/normEncoderRS8.h b/common/normEncoderRS8.h deleted file mode 100644 index 7d1bdde..0000000 --- a/common/normEncoderRS8.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef _NORM_ENCODER_RS8 -#define _NORM_ENCODER_RS8 - -#include "protoDebug.h" -#include "protoDefs.h" // for UINT16 - -// (TBD) We're going to need to have a "NormEncoder" base class and then allow for variants - -class NormEncoder -{ - // Methods - public: - NormEncoder(); - ~NormEncoder(); - bool Init(int numData, int numParity, int vectorSize); - void Destroy(); - - // "Encode" must be called in order of source vector0, vector1, vector2, etc - void Encode(unsigned char *dataVector, unsigned char **parityVectorList); - int GetNumData() {return ndata;} - int GetNumParity() {return npar;} - int GetVectorSize() {return vector_size;} - - // Members - private: - int ndata; // max data pkts per block (k) - int npar; // No. of parity packets (n-k) - int vector_size; // Size of biggest vector to encode - unsigned char* enc_matrix; - int enc_index; - -}; // end class NormEncoder - - - -class NormDecoder -{ - public: - NormDecoder(); - ~NormDecoder(); - bool Init(int numData, int numParity, int vectorSize); - - // Note: "erasureLocs" must be in order from lowest to highest! - int Decode(unsigned char** vectorList, int ndata, UINT16 erasureCount, UINT16* erasureLocs); - int NumParity() {return npar;} - int VectorSize() {return vector_size;} - void Destroy(); - - // Members - private: - bool InvertDecodingMatrix(); // used in Decode() method - - - int ndata; // max data pkts per block (k) - int npar; // No. of parity packets (n-k) - int vector_size; // Size of biggest vector to encode - unsigned char* enc_matrix; - unsigned char* dec_matrix; - int* parity_loc; - - // These "inv_" members are used in InvertDecodingMatrix() - int* inv_ndxc; - int* inv_ndxr; - int* inv_pivt; - unsigned char* inv_id_row; - unsigned char* inv_temp_row; - -}; // end class NormDecoder - -#endif // _NORM_ENCODER_RS8 diff --git a/doc/NormDeveloperGuide.html b/doc/NormDeveloperGuide.html new file mode 100644 index 0000000..06058b7 --- /dev/null +++ b/doc/NormDeveloperGuide.html @@ -0,0 +1,347 @@ + + + NORM Developer's Guide (version 1.4b4) \ No newline at end of file diff --git a/doc/NormDeveloperGuide.pdf b/doc/NormDeveloperGuide.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ac297c48cd1b517161416206df3b082e3c98d555 GIT binary patch literal 459998 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

RuM>{~8X3|mu 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≫;O=F?au4F&@7Eq$BhiaNXo=T1iA-eS=`m`Uq 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%0A}!?@$&f#D!%- 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 H_~m~9iBLZQ literal 0 HcmV?d00001 diff --git a/doc/NormDeveloperGuide.xml b/doc/NormDeveloperGuide.xml new file mode 100644 index 0000000..9c7b386 --- /dev/null +++ b/doc/NormDeveloperGuide.xml @@ -0,0 +1,6985 @@ + + +

+ + <inlinemediaobject> + <imageobject> + <imagedata align="center" fileref="NormLogo.gif" scale="50" /> + </imageobject> + </inlinemediaobject> NORM Developer's Guide (version 1.4b4) + + NORM Developer's Guide + + + This document describes an application programming interface (API) + for the Nack-Oriented + Reliable Multicast (NORM) protocol implementation developed by + the Protocol Engineering and Advance Networking (PROTEAN) Research Group of the + United States Naval Research + Laboratory (NRL). The NORM protocol provides general purpose + reliable data transport for applications wishing to use Internet + Protocol (IP) Multicast services for group data delivery. NORM can also + support unicast (point-to-point) data communication and may be used for + such when deemed appropriate. The current NORM protocol specification is + given in the Internet Engineering Task + Force (IETF) RFC 3940. This + document is currently a reference guide to the NORM API of the NRL + reference implementation. More tutorial material may be include in a + future version of this document or a separate developer's tutorial may + be created at a later date. + + + + + Background + + This document describes an application programming interface (API) + for the Nack-Oriented + Reliable Multicast (NORM) protocol implementation developed by the + Protocol Engineering and Advance Networking (PROTEAN) Research Group of the + United States Naval Research + Laboratory (NRL). The NORM protocol provides general purpose + reliable data transport for applications wishing to use Internet Protocol + (IP) Multicast services for group data delivery. NORM can also support + unicast (point-to-point) data communication and may be used for such when + deemed appropriate. The current NORM protocol specification is given in + the Internet Engineering Task + Force (IETF) RFC 5740. + + The NORM protocol is designed to provide end-to-end reliable + transport of bulk data objects or streams over generic IP multicast + routing and forwarding services. NORM uses a selective, negative + acknowledgement (NACK) mechanism for transport reliability and offers + additional protocol mechanisms to conduct reliable multicast sessions with + limited "a priori" coordination among senders and receivers. A congestion + control scheme is specified to allow the NORM protocol to fairly share + available network bandwidth with other transport protocols such as + Transmission Control Protocol (TCP). It is capable of operating with both + reciprocal multicast routing among senders and receivers and with + asymmetric connectivity (possibly a unicast return path) from the senders + to receivers. The protocol offers a number of features to allow different + types of applications or possibly other higher-level transport protocols + to utilize its service in different ways. The protocol leverages the use + of FEC-based repair and other proven reliable multicast transport + techniques in its design. + + The NRL NORM library attempts to provide a general useful capability + for development of reliable multicast applications for bulk file or other + data delivery as well as support of stream-based transport with possible + real-time delivery requirements. The API allows access to many NORM + protocol parameters and control functions to tailor performance for + specific applications. While default parameters, where provided, can be + useful to a potential wide range of requirements, the many different + possible group communication paradigms dictate different needs for + different applications. Even with NORM, the developer should have a + thorough understanding of the specific application's group communication + needs. + + + + Overview + + The NORM API has been designed to provide simple, straightforward + access to and control of NORM protocol state and functions. Functions are + provided to create and initialize instances of the NORM API and associated + transport sessions (NormSessions). Subsequently, NORM + data transmission (NormSender) operation can be + activated and the application can queue various types of data + (NormObjects) for reliable transport. Additionally or + alternatively, NORM reception (NormReceiver) + operation can also be enabled on a per-session basis and the protocol + implementation alerts the application of receive events. + + By default, the NORM API will create an operating system thread in + which the NORM protocol engine runs. This allows user application code and + the underlying NORM code to execute somewhat independently of one another. + The NORM protocol thread notifies the application of various protocol + events through a thread-safe event dispatching mechanism and API calls are + provided to allow the application to control NORM operation. (Note: API + mechanisms for lower-level, non-threaded control and execution of the NORM + protocol engine code may also be provided in the future.) + + The NORM API operation can be roughly summarized with the following + categories of functions: + + + + API Initialization + + + + Session Creation and Control + + + + Data Transport + + + + API Event Notification + + + + Note the order of these categories roughly reflects the order of + function calls required to use NORM in an application. The first step is + to create and initialize, as needed, at least one instance of the NORM + API. Then one or more NORM transport sessions (where a "session" + corresponds to data exchanges on a given multicast group (or unicast + address) and host port number) may be created and controlled. Applications + may participate as senders and/or receivers within a NORM session. NORM + senders transmit data to the session destination address (usually an IP + multicast group) while receivers are notified of incoming data. The NORM + API provides and event notification scheme to notify the application of + significant sender and receiver events. There are also a number support + functions provided for the application to control and monitor its + participation within a NORM transport session. + + + API Initialization + + The NORM API requires that an application explicitly create at + least one instance of the NORM protocol engine that is subsequently used + as a conduit for further NORM API calls. By default, the NORM protocol + engine runs in its own operating system thread and interacts with the + application in a thread-safe manner through the API calls and event + dispatching mechanism. + + In general, only a single thread should access the NormGetNextEvent() + API call for a given NormInstance. This function + serves as the conduit for delivering NORM protocol engine events to the + application. A NORM application can be designed to be single-threaded, + even with multiple active NormSessions, but also multiple API instances + can be created (see NormCreateInstance()) + as needed for applications with specific requirements for accessing and + controlling participation in multiple NormSessions + from separate operating system multiple threads. Or, alternatively, a + single NormInstance could be used, with a "master + thread" serving as an intermediary between the NormGetNextEvent() + function, demultiplexing and dispatching events as appropriate to other + "child threads" that are created to handle + "per-NormSession" input/output. The advantage of + this alternative approach is that the end result would be one NORM + protocol engine thread plus one "master thread" plus one "child thread" + per NormSession instead of two threads (protocol + engine plus application thread) per NormSession if + such multi-threaded operation is needed by the application. + + + + Session Creation and Control + + Once an API instance has been successfully created, the + application may then create NORM transport session instances as needed. + The application can participate in each session as a sender and/or + receiver of data. If an application is participating as a sender, it may + enqueue data transport objects for transmission. The control of + transmission is largely left to the senders and API calls are provided + to control transmission rate, FEC parameters, etc. Applications + participating as receivers will be notified via the NORM API's event + dispatching mechanism of pending and completed reliable reception of + data along with other significant events. Additionally, API controls for + some optional NORM protocol mechanisms, such as positive acknowledgment + collection, are also provided. + + Note when multiple senders are involved, receivers allocate system + resources (buffer space) for each active sender. With a very large + number of concurrently active senders, this may translate to significant + memory allocation on receiver nodes. Currently, the API allows the + application to control how much buffer space is allocated for each + active sender (NOTE: In the future, API functions may be provided limit + the number of active senders monitored and/or provide the application + with finer control over receive buffer allocation, perhaps on a per + sender basis). + + + + Data Transport + + The NORM protocol supports transport of three basic types of data + content. These include the types NORM_OBJECT_FILE and + NORM_OBJECT_DATA which represent predetermined, + fixed-size application data content. The only differentiation with + respect to these two types is the implicit "hint" to the receiver to use + non-volatile (i.e. file system) storage or memory. This "hint" lets the + receiver allocate appropriate storage space with no other information on + the incoming data. The NORM implementation reads/writes data for the + NORM_OBJECT_FILE type directly from/to file storage, + while application memory space is accessed for the + NORM_OBJECT_DATA type. The third data content type, + NORM_OBJECT_STREAM, represents unbounded, possibly + persistent, streams of data content. Using this transport paradigm, + traditional, byte-oriented streaming transport service (e.g. similar to + that provided by a TCP socket) can be provided. Additionally, NORM has + provisions for application-defined message-oriented transport where + receivers can recover message boundaries without any "handshake" with + the sender. Stream content is buffered by the NORM implementation for + transmission/retransmission and as it is received. + + + Data Transmission + + The behavior of data transport operation is largely placed in + the control of the NORM sender(s). NORM senders controls their data + transmission rate, forward error correction (FEC) encoding settings, + and parameters controlling feedback from the receiver group. Multiple + senders may operate in a session, each with independent transmission + parameters. NORM receivers learn needed parameter values from fields + in NORM message headers. + + NORM transport "objects" (file, data, or stream) are queued for + transmission by NORM senders. NORM senders may also cancel + transmission of objects at any time. The NORM sender controls the + transmission rate either manually (fixed transmission rate) or + automatically when NORM congestion control operation is enabled. The + NORM congestion control mechanism is designed to be "friendly" to + other data flows on the network, fairly sharing available + bandwidth.NormSetAutoParity()) + to achieve reliable transfer) receive object transmission before any + extensive repair process that may be required to satisfy other + receivers with poor network connectivity. The repair boundary can also + be set for individual remote senders using the NormNodeSetRepairBoundary() + function.NORM_OBJECT_FILE objects. This function + must be called before any file objects may be received and thus should + be called before any calls to NormStartReceiver() + are made. However, note that the cache directory may be changed even + during active NORM reception. In this case, the new specified + directory path will be used for subsequently-received files. Any files + received before a directory path change will remain in the previous + cache location. Note that the NormFileRename() + function may be used to rename, and thus potentially move, received + files after reception has begun. + + By default, the NORM sender transmits application-enqueued data + content, providing repair transmissions (usually in the form of FEC + messages) only when requested by NACKs from the receivers. However, + the application may also configure NORM to proactively send some + amount of FEC content along with the original data content to create a + "robust" transmission that, in some cases, may be reliably received + without any NACKing activity. This can allow for some degree of + reliable protocol operation even without receiver feedback available. + NORM senders may also requeue (within the limits of "transmit cache" + settings) objects for repeat transmission, and receivers may combine + together multiple transmissions to reliably receive content. + Additionally, hybrid proactive/reactive FEC repair operation is + possible with the receiver NACK process as a "backup" for when network + packet loss exceeds the repair capability of the proactive FEC + settings. + + The NRL NORM implementation also supports optional collection of + positive acknowledgment from a subset of the receiver group at + application-determined positions during data transmission. The NORM + API allows the application to specify the receiver subset ("acking + node list") and set "watermark" points for which positive + acknowledgement is collected. This process can provide the application + with explicit flow control for an application-determined critical set + of receivers in the group. + + For a NORM application to perform data transmission, it must + first create a session using NormCreateSession() + and make a call to NormStartSender() + before sending actual user data. The functions NormFileEnqueue(), + NormDataEnqueue(), + and NormStreamWrite() + are available for the application to pass data to the NORM protocol + engine for transmission. Note that to use NormStreamWrite(), + a "sender stream" must first be created using NormStreamOpen(). + In the case of NormFileEnqueue() + and NormDataEnqueue(), + the NORM protocol engine directly accesses the application file or + memory space to refer to the transmitted content and does not make its + own copy of this data. + + The calls to enqueue transport objects or write to a stream may + be called at any time, but the NORM_TX_QUEUE_EMPTY + and NORM_TX_QUEUE_VACANCY notification events (see + NormGetNextEvent()) + provide useful cues for when these functions may be successfully + called. Typically, an application might catch both + NORM_TX_QUEUE_EMPTY and + NORM_TX_QUEUE_VACANCY event types as cues for + enqueuing additional transport objects or writing to a stream. + However, an application may choose to cue off of + NORM_TX_QUEUE_EMPTY only if it wishes to provide + the "freshest" data to NORM for transmission. The advantage of + additionally using NORM_TX_QUEUE_VACANCY is that if + the application uses this cue to fill up NORM transport object or + stream buffers, it can keep the NORM stream busy sending data and + realize the highest possible transmission rate when attempting very + high speed communication (Otherwise, the NORM protocol engine may + experience some "dead air time" waiting for the application thread to + respond to a NORM_TX_QUEUE_EMPTY event). Note the + sender application can control buffer depths as needed with the NormSetTxCacheBounds() + and NormStreamOpen() + calls. Additionally, it is possible for applications to configure the + transmit object "cache" (see NormSetTxCacheBounds()) + and use the NormRequeueObject() + call (for objects that have not yet received a + NORM_TX_OBJECT_PURGED notification) to effect a + sort of "data carousel" operation with repeated transmission of the + cached objects. The NORM_TX_OBJECT_SENT + notification can be used a cue to properly control the "requeue" + cycle(s). + + The NORM implementation provides a form of timer-based flow + control that limits how quickly sender applications may enqueue new + objects or stream data for transmission. The NormSetFlowControl() + call is provided to control this behavior, including the option to + disable it. This timer-based mechanism is a type of "soft" flow + control by allowing receivers "sufficient" time to request repair of + pending data the sender has enqueued. A more explicit form of flow + control using the optional "watermark flushing" mechanism is described + below. + + Another cue that can be leveraged by the sender application to + determine when it is appropriate to enqueue (or write) additional data + for transmission is the NORM_TX_WATERMARK_COMPLETED + event. This event is posted when the flushing or explicit positive + acknowledgment collection process has completed for a "watermark" + point in transmission that was set by the sender (see NormSetWatermark() + and NormAddAckingNode()). + A list of NormNodeId values can + be supplied from which explicit acknowledgement is expected and/or the + NormNodeId + NORM_NODE_NONE can be set (using NormAddAckingNode()) + for completion of a NACK-based version of the watermark flushing + procedure. This flushing process can be used as a flow control + mechanism for NORM applications. Note this is distinct from NORM's + congestion control mechanism that, while it provides network-friendly + transmission rate control, does guarantee flow control to receiving + nodes.NORM_NODE_NONE can be set (using NormAddAckingNode()) + for completion of a NACK-based version of the watermark flushing + procedure. This flushing process can be used as a flow control + mechanism for NORM applications. Note this is distinct from NORM's + congestion control mechanism that, while it provides network-friendly + transmission rate control, does guarantee flow control to receiving + nodes. + + + + Data Reception + + NORM receiver applications learn of active senders and their + corresponding pending and completed data transfers, etc via the API + event dispatching mechanism. By default, NORM receivers use NACK + messages to request repair of transmitted content from the originating + sender as needed to achieve reliable transfer. Some API functions are + available to provide some additional control over the NACKing + behavior, such as initially NACKing for NORM_INFO + content only or even to the extent of disabling receiver feedback + (silent receiver or emission-controlled (EMCON) operation) entirely. + Otherwise, the parameters and operation of reliable data transmission + are left to sender applications and receivers learn of sender + parameters in NORM protocol message headers and are instructed by + NORM_CMD messages from the sender(s). + + With respect to the NORM API, the receiver application is + informed of new senders and receive data objects via the the + NORM_REMOTE_SENDER_NEW and + NORM_RX_OBJECT_NEW notifications, respectfully. + Additionally, object reception progress is indicated with the + NORM_RX_OBJECT_UPDATED notification and this + also serves as an indicator for the + NORM_OBJECT_STREAM type that the receive + application should make calls to NormStreamRead() to + read newly received stream content. NORM sender status is also + conveyed via the NORM_REMOTE_SENDER_ACTIVE and + NORM_REMOTE_SENDER_INACTIVE notifications. For example, the receiver + application may use the + NORM_REMOTE_SENDER_INACTIVE as a cue to make + calls to NormNodeFreeBuffers() + and/or NormNodeDelete() to + free memory resources allocated for buffering received content for the + given sender. The amount of memory allocated per + sender is set in the NormStartReceiver() + call. + + + + + API Event Notification + + An asynchronous event dispatching mechanism is provided to notify + the application of significant NORM protocol events. The centerpiece of + this is the NormGetNextEvent() + function that can be used to retrieve the next NORM protocol engine + event in the form of a NormEvent structure. This + function will typically block until a NormEvent occurs. However, + non-blocking operation may be achieved by using the NormGetDescriptor() + call to get a NormDescriptor (file + descriptor) value (Unix int or Win32 HANDLE) + suitable for use in a asynchronous I/O monitoring functions such as the + Unix select() or Win32 + MsgWaitForMultipleObjects() system calls. The a + NormDescriptor will + be signaled when a NormEvent is available. + For Win32 platforms, dispatching of a user-defined Windows message for + NORM event notification is also planned for a future update to the NORM + API. + + + + + Build Notes + + To build applications that use the NORM library, a path to the + "normApi.h" header file must be provided and the linker step needs to + reference the NORM library file ("libnorm.a" for Unix + platforms and "Norm.lib" for Win32 platforms). NORM + also depends upon the NRL Protean Protocol Prototyping toolkit "Protokit" + library (a.k.a "Protolib") (static library files + "libProtokit.a" for Unix and + "Protokit.lib" for Win32). Shared or + dynamically-linked versions of these libraries may also be built from the + NORM source code or provided. Depending upon the platform, some additional + library dependencies may be required to support the needs of NORM and/or + Protokit. These are described below. + + The "makefiles" directory contains Unix Makefiles for various + platforms the "win32" and "wince" sub-directories there contain Microsoft + Visual C++ (VC++) and Embedded VC++ project files for building the NORM + implementation. Additionally, a "waf" (Python-based build tool) build + option is supported that can be used to build and install the NORM library + code on the supported platforms. Finally, Python and Java bindings to the + NORM API are included and "src/python" and "src/java" directories contain + the code for these and the "makefiles/java" directory contains Makefiles + to build the NORM Java JNI bindings. Note the "waf" tool can also be used + to build the Java and Python bindings. + + + Unix Platforms + + NORM has been built and tested on Linux (various architectures), + MacOS (BSD), Solaris, and IRIX (SGI) platforms. The code should be + readily portable to other Unix platforms. + + To support IPv6 operation, the NORM and the Protokit library must + be compiled with the "HAVE_IPV6" macro defined. + This is default in the NORM and Protokit Makefiles for platforms that + support IPv6. It is important that NORM and Protokit be built with this + macro defined the same. With NORM, it is recommended that "large file + support" options be enabled when possible. + + The NORM API uses threading so that the NORM protocol engine may + run independent of the application. Thus the "POSIX Threads" library + must be included ("-pthread") in the linking step. MacOS/BSD also + requires the addition of the "-lresolv" (resolver) library and Solaris + requires the dynamic loader, network/socket, and resolver libraries + ("-lnsl -lsocket -lresolv") to achieve successful compilation. The + Makefiles in the NORM source code distribution are a reference for these + requirements. Note that MacOS 9 and earlier are not supported. + + Additionally, it is critical that the + _FILE_OFFSET_BITS macro be consistently defined for + the NORM library build and the application build using the library. The + distributed NORM Makefiles have + -D_FILE_OFFSET_BITS=64 set in the compilation to + enable "large file support". Applications built using NORM should have + the same compilation option set to operate correctly (The definition of + the NormSize type in + "normApi.h" depends upon this compilation + flag). + + + + Win32/WiNCE Platforms + + NORM has been built using Microsoft's Visual C++ (6.0 and .NET) + and Embedded VC++ 4.2 environments. In addition to proper macro + definitions (e.g., HAVE_IPV6, etc) that are included in the respective + "Protokit" and "NORM" project files, it is important that common code + generation settings be used when building the NORM application. The NORM + and Protokit projects are built with the "Multi-threading DLL" library + usage set. The NORM API requires multi-threading support. This is a + critical setting and numerous compiler and linker errors will result if + this is not properly set for your application project. + + NORM and Protokit also depend on the Winsock 2.0 + ("ws2_32.lib" (or "ws2.lib" + (WinCE)) and the IP Helper API ("iphlpapi.lib") + libraries and these must be included in the project "Link" + attributes. + + An additional note is that a bug in VC++ 6.0 and earlier compilers + (includes embedded VC++ 4.x compilers) prevent compilation of + Protokit-based code with debugging capabilities enabled. However, this + has been resolved in VC++ .NET and is hoped to be resolved in the future + for the WinCE build tools. + + Operation on Windows NT4 (and perhaps other older Windows + operating systems) requires that the compile time macro + WINVER=0x0400 defined. This is because the version + of the IP Helper API library (iphlpapi.lib) used by + Protolib (and hence NORM) for this system doesn't + support some of the functions defined for this library. This may be + related to IPv6 support issues so it may be possible that the Protolib + build could be tweaked to provide a single binary executable suitable + for IPv4 operation only across a large range of Windows + platforms. + + + + + API Reference + + This section provides a reference to the NORM API variable types, + constants and functions. + + + API Variable Types and Constants + + The NORM API defines and enumerates a number of supporting + variable types and values which are used in different function calls. + The variable types are described here. + + + NormInstanceHandle + + The NormInstanceHandle + type is returned when a NORM API instance is created (see NormCreateInstance()). + This handle can be subsequently used for API calls which require + reference to a specific NORM API instance. By default, each NORM API + instance instantiated creates an operating system thread for protocol + operation. Note that multiple NORM transport sessions may be created + for a single API instance. In general, it is expected that + applications will create a single NORM API instance, but some + multi-threaded application designs may prefer multiple corresponding + NORM API instances. The value NORM_INSTANCE_INVALID + corresponds to an invalid API instance. + + + + NormSessionHandle + + The NormSessionHandle + type is used to reference NORM transport sessions which have been + created using the NormCreateSession() + API call. Multiple NormSessionHandle + values may be associated with a given NormInstanceHandle. + The special value NORM_SESSION_INVALID is used to + refer to invalid session references. + + + + NormSessionId + + The NormSessionId type + is used by applications to uniquely identify their instance of + participation as a sender within a NormSession. + This type is a parameter to the NormStartSender() + function. Robust applications can use different NormSessionId values + when initiating sender operation so that receivers can discriminate + when a sender has terminated and restarted (whether intentional or due + to system failure). For example, an application could cache its prior + NormSessionId + value in non-volatile storage which could then be recovered and + incremented (for example) upon system restart to produce a new value. + The NormSessionId value + is used for the value of the instance_id field in NORM protocol sender + messages (see the NORM protocol specification) and receivers use this + field to detect sender restart within a + NormSession. + + + + NormNodeHandle + + The NormNodeHandle type + is used to reference state kept by the NORM implementation with + respect to other participants within a + NormSession. Most typically, the NormNodeHandle is + used by receiver applications to dereference information about remote + senders of data as needed. The special value + NORM_NODE_INVALID corresponds to an invalid + reference. + + + + NormNodeId + + The NormNodeId type + corresponds to a 32-bit numeric value which should uniquely identify a + participant (node) in a given NormSession. The + NormNodeGetId() + function can be used to retrieve this value given a valid NormNodeHandle. The + special value NORM_NODE_NONE corresponds to an + invalid (or null) node while the value + NORM_NODE_ANY serves as a wild card value for some + functions. + + + + NormObjectHandle + + The NormObjectHandle + type is used to reference state kept for data transport objects being + actively transmitted or received. The state kept for NORM transport + objects is temporary, but the NORM API provides a function to + persistently retain state associated with a sender or receiver NormObjectHandle + (see NormObjectRetain()) + if needed. For sender objects, unless explicitly retained, the NormObjectHandle + can be considered valid until the referenced object is explicitly + canceled (see NormObjectCancel()) + or purged from the sender transmission queue (see the event + NORM_TX_OBJECT_PURGED). For receiver objects, these + handles should be treated as valid only until a subsequent call to + NormGetNextEvent() + unless, again, specifically retained. The special value + NORM_OBJECT_INVALID corresponds to an invalid + transport object reference. + + + + NormObjectType + + The NormObjectType type + is an enumeration of possible NORM data transport object types. As + previously mentioned, valid types include: + + + + NORM_OBJECT_FILE + + + + NORM_OBJECT_DATA, and + + + + NORM_OBJECT_STREAM + + + + Given a NormObjectHandle, + the application may determine an object's type using the NormObjectGetType() + function call. A special NormObjectType + value, NORM_OBJECT_NONE, indicates an invalid + object type. + + + + NormSize + + The NormSize + is the type used for NormObject size information. + For example, the NormObjectGetSize() + function returns a value of type NormSize. The range of + NormSize values + depends upon the operating system and NORM library compilation + settings. With "large file support" enabled, as is the case with + distributed NORM library "Makefiles", the NormSize type is a 64-bit + integer. However, some platforms may support only 32-bit object + sizes. + + + + NormObjectTransportId + + The NormObjectTransportId + type is a 16-bit numerical value assigned to + NormObjects by senders during active transport. + These values are temporarily unique with respect to a given sender + within a NormSession and may be "recycled" for + use for future transport objects. NORM sender nodes assign these + values in a monotonically increasing fashion during the course of a + session as part of protocol operation. Typically, the application + should not need access to these values, but an API call such as + NormObjectGetTransportId() + (TBD) may be provided to retrieve these values if + needed. (Note this type may be deprecated; i.e., it may not be needed + at since the NormRequeueObject() + function is implemented using handles only, but _some_ applications + requiring persistence even after a system reboot may need the ability + to recall previous transport ids?) + + + + NormEventType + + The NormEventType is an + enumeration of NORM API events. "Events" are used by the NORM API to + signal the application of significant NORM protocol operation events + (e.g., receipt of a new receive object, etc). A description of + possible NormEventType values + and their interpretation is given below. The function call NormGetNextEvent() + is used to retrieve events from the NORM protocol engine. + + + + NormEvent + + The NormEvent type is a + structure used to describe significant NORM protocol events. This + structure is defined as follows: + + typedef struct +{ + NormEventType type; + NormSessionHandle session; + NormNodeHandle node; + NormObjectHandle object; +} NormEvent; + + The type field indicates the NormEventType and + determines how the other fields should be interpreted. Note that not + all NormEventType fields + are relevant to all events. The session, + node, and object fields + indicate the applicable NormSessionHandle, + NormNodeHandle, and + NormObjectHandle, + respectively, to which the event applies. NORM protocol events are + made available to the application via the NormGetNextEvent() + function call. + + + + NormDescriptor + + The NormDescriptor type + can provide a reference to a corresponding file descriptor (Unix + int or Win32 HANDLE) for the + NormInstance. For a given NormInstanceHandle, + the NormGetDescriptor() + function can be used to retrieve a NormDescriptor + value that may, in turn, used in appropriate system calls (e.g. + select() or + MsgWaitForMultipleObjects()) to asynchronously + monitor the NORM protocol engine for notification events (see NormEvent + description). + + + + NormFlushMode + + The NormFlushMode type + consists of the following enumeration: + + enum NormFlushMode +{ + NORM_FLUSH_NONE, + NORM_FLUSH_PASSIVE, + NORM_FLUSH_ACTIVE +}; + + The use and interpretation of these values is given in the + descriptions of NormStreamFlush() + and NormStreamSetAutoFlush() + functions. + + + + NormProbingMode + + The NormProbingMode + type consists of the following enumeration: + + enum NormProbingMode +{ + NORM_PROBE_NONE, + NORM_PROBE_PASSIVE, + NORM_PROBE_ACTIVE +}; + + The use and interpretation of these values is given in the + description of NormSetGrttProbingMode() + function. + + + + NormSyncPolicy + + The NormSyncPolicy type + consists of the following enumeration: + + enum NormSyncPolicy +{ + NORM_SYNC_CURRENT, + NORM_SYNC_ALL +}; + + The use and interpretation of these values is given in the + descriptions of the NormSetDefaultSyncPolicy() + function. + + + + NormNackingMode + + The NormNackingMode + type consists of the following enumeration: + + enum NormNackingMode +{ + NORM_NACK_NONE, + NORM_NACK_INFO_ONLY, + NORM_NACK_NORMAL +}; + + The use and interpretation of these values is given in the + descriptions of the NormSetDefaultNackingMode(), + NormNodeSetNackingMode() + and NormObjectSetNackingMode() + functions. + + + + NormRepairBoundary + + The NormRepairBoundary + types consists of the following enumeration: + + enum NormRepairBoundary +{ + NORM_BOUNDARY_BLOCK, + NORM_BOUNDARY_OBJECT +}; + + The interpretation of these values is given in the descriptions + of the NormSetDefaultRepairBoundary() + and NormNodeSetRepairBoundary() + functions. + + + + NormAckingStatus + + The NormAckingStatus + consist of the following enumeration: + + enum NormAckingStatus +{ + NORM_ACK_INVALID, + NORM_ACK_FAILURE, + NORM_ACK_PENDING, + NORM_ACK_SUCCESS +}; + + The interpretation of these values is given in the descriptions + of the NormGetAckingStatus() + function. + + + + + API Initialization and Operation + + The first step in using the NORM API is to create an "instance" of + the NORM protocol engine. Note that multiple instances may be created by + the application if necessary, but generally only a single instance is + required since multiple NormSessions may be managed + under a single NORM API instance. + + + NormCreateInstance() + + + Synopsis + + #include <normApi.h> + +NormInstanceHandle NormCreateInstance(bool priorityBoost = false); + + + + Description + + This function creates an instance of a NORM protocol engine + and is the necessary first step before any other API functions may + be used. With the instantiation of the NORM protocol engine, an + operating system thread is created for protocol execution. The + returned NormInstanceHandle + value may be used in subsequent API calls as needed, such NormCreateSession(), + etc. The optional priorityBoost parameter, + when set to a value of true, specifies that the NORM protocol engine + thread be run with higher priority scheduling. On Win32 platforms, + this corresponds to + THREAD_PRIORITY_TIME_CRITICAL and on Unix + systems with the sched_setscheduler() API, an + attempt to get the maximum allowed SCHED_FIFO + priority is made. The use of this option should be carefully + evaluated since, depending upon the application's scheduling + priority and NORM API usage, this may have adverse effects instead + of a guaranteed performance increase! + + + + Return Values + + A value of NORM_INSTANCE_INVALID is + returned upon failure. The function will only fail if system + resources are unavailable to allocate the instance and/or create the + corresponding thread. + + + + + NormDestroyInstance() + + + Synopsis + + #include <normApi.h> + +void NormDestroyInstance(NormInstanceHandle instanceHandle); + + + + Description + + The NormDestroyInstance() + function immediately shuts down and destroys the NORM protocol + engine instance referred to by the + instanceHandle parameter. The application + should make no subsequent references to the indicated NormInstanceHandle + or any other API handles or objects associated with it. However, the + application is still responsible for releasing any object handles it + has retained (see NormObjectRetain() + and NormObjectRelease()). + + + + Return Values + + The function has no return value. + + + + + NormStopInstance() + + + Synopsis + + #include <normApi.h> + +void NormStopInstance(NormInstanceHandle instanceHandle); + + + + Description + + This function immediately stops the NORM protocol engine + thread corresponding to the given + instanceHandle parameter. It also posts a + "dummy" notification event so that if another thread is blocked on a + call to NormGetNextEvent(), + that thread will be released. Hence, for some multi-threaded uses of + the NORM API, this function may be useful as a preliminary step to + safely coordinate thread shutdown before a call is made to NormDestroyInstance(). + After NormStopInstance() + is called and any pending events posted prior to its call have been + retrieved, NormGetNextEvent() + will return a value of false. + + When this function is invoked, state for any + NormSessions associated with the given instance + is "frozen". The complementary function, NormRestartInstance() + can be subsequently used to "unfreeze" and resume NORM protocol + operation (a new thread is created and started). + + + + Return Values + + The function has no return value. + + + + + NormRestartInstance() + + + Synopsis + + #include <normApi.h> + +bool NormRestartInstance(NormInstanceHandle instanceHandle); + + + + Description + + This function creates and starts an operating system thread to + resume NORM protocol engine operation for the given + instanceHandle that was previously stopped by + a call to NormStopInstance(). + It is not expected that this function will be used often, but there + may be special application cases where "freezing" and later resuming + NORM protocol operation may be useful. + + + + Return Values + + The function returns true when the NORM + protocol engine thread is successfully restarted, and + false otherwise. + + + + + NormSetCacheDirectory() + + + Synopsis + + #include <normApi.h> + +bool NormSetCacheDirectory(NormInstanceHandle instanceHandle, + const char* cachePath); + + + + Description + + This function sets the directory path used by receivers to + cache newly-received NORM_OBJECT_FILE content. + The instanceHandle parameter specifies the + NORM protocol engine instance (all NormSessions + associated with that instanceHandle share the + same cache path) and the cachePath is a + string specifying a valid (and writable) directory path. + + + + Return Values + + The function returns true on success and + false on failure. The failure conditions are + that the indicated directory does not exist or the process does not + have permissions to write. + + + + + NormGetNextEvent() + + + Synopsis + + #include <normApi.h> + +bool NormGetNextEvent(NormInstanceHandle instanceHandle, + NormEvent* theEvent); + + + + Description + + This function retrieves the next available NORM protocol event + from the protocol engine. The instanceHandle + parameter specifies the applicable NORM protocol engine, and the + theEvent parameter must be a valid pointer to + a NormEvent + structure capable of receiving the NORM event information. For + expected reliable protocol operation, the application should make + every attempt to retrieve and process NORM notification events in a + timely manner. + + Note that this is currently the only blocking call in the NORM + API. But non-blocking operation may be achieved by using the NormGetDescriptor() + function to obtain a descriptor (int for Unix or + HANDLE for WIN32) suitable for asynchronous + input/output (I/O) notification using such system calls the Unix + select() or Win32 + WaitForMultipleObjects() calls. The descriptor + is signaled when a notification event is pending and a call to NormGetNextEvent() + will not block.NormGetNextEvent + + + + Return Values + + The function returns true when a NormEvent is successfully + retrieved, and false otherwise. Note that a + return value of false does + not indicate an error or signify end of NORM + operation. + + + + NORM Notification Event Types + + The following table enumerates the possible NormEvent values and + describes how these notifications should be interpreted as they are + retrieved by the application via the NormGetNextEvent() + function call. + + + + + + + + + + Sender Notifications: + + + + NORM_TX_QUEUE_VACANCY + + This event indicates that there is room for + additional transmit objects to be enqueued, or, if the + handle of NORM_OBJECT_STREAM is given in + the corresponding event "object" field, the application may + successfully write to the indicated stream object. Note this + event is not dispatched until a call to NormFileEnqueue(), + NormDataEnqueue(), + or NormStreamWrite() + fails because of a filled transmit cache or stream + buffer. + + + + NORM_TX_QUEUE_EMPTY + + This event indicates the NORM protocol engine + has no new data pending transmission and the application may + enqueue additional objects for transmission. If the handle + of a sender NORM_OBJECT_STREAM is given + in the corresponding event "object" field, this indicates + the stream transmit buffer has been emptied and the sender + application may write to the stream (Use of + NORM_TX_QUEUE_VACANCY may be preferred + for this purpose since it allows the application to keep the + NORM protocol engine busier sending data, resulting in + higher throughput when attempting very high transfer + rates). + + + + NORM_TX_FLUSH_COMPLETED + + This event indicates that the flushing process + the NORM sender observes when it no longer has data ready + for transmission has completed. The completion of the + flushing process is a reasonable indicator (with a + sufficient NORM "robust factor" value) that the receiver set + no longer has any pending repair requests. Note the use of + NORM's optional positive acknowledgement feature is more + deterministic in this regards, but this notification is + useful when there are non-acking (NACK-only) receivers. The + default NORM robust factor of 20 (20 flush messages are sent + at end-of-transmission) provides a high assurance of + reliable transmission, even with packet loss rates of + 50%. + + + + NORM_TX_WATERMARK_COMPLETED + + This event indicates that the flushing process + initiated by a prior application call to NormSetWatermark() + has completed The posting of this event indicates the + appropriate time for the application to make a call NormGetAckingStatus() + to determine the results of the watermark flushing + process. + + + + NORM_TX_OBJECT_SENT + + This event indicates that the transport object + referenced by the event's "object" field has completed at + least one pass of total transmission. Note that this does + not guarantee that reliable transmission has yet completed; + only that the entire object content has been transmitted. + Depending upon network behavior, several rounds of NACKing + and repair transmissions may be required to complete + reliable transfer. + + + + NORM_TX_OBJECT_PURGED + + This event indicates that the NORM protocol + engine will no longer refer to the transport object + identified by the event's "object' field. Typically, this + will occur when the application has enqueued more objects + than space available within the set sender transmit cache + bounds (see NormSetTxCacheBounds()). + Posting of this notification means the application is free + to free any resources (memory, files, etc) associated with + the indicated "object". After this event, the given "object" + handle (NormObjectHandle) + is no longer valid unless it is specifically retained by the + application.NormObjectHandle + + + + NORM_TX_CMD_SENT + + This event indicates that an application-defined + command previously enqueued with a call to NormSendCommand() + has been transmitted, including any repetition. + + + + NORM_TX_RATE_CHANGED + + This event indicates that NORM Congestion Control + operation has adjusted the transmission rate. The NormGetTxRate() + call may be used to retrieve the new corresponding + transmission rate. Note that if NormSetCongestionControl() + was called with its adjustRate + parameter set to false, then no actual + rate change has occurred and the rate value returned by + NormGetTxRate() + reflects a "suggested" rate and not the actual transmission + rate. + + + + NORM_LOCAL_SENDER_CLOSED + + This event is posted when the NORM protocol + engine completes the "graceful shutdown" of its + participation as a sender in the indicated "session" (see + NormStopSender()). + + + + NORM_CC_ACTIVE + + This event indicates that congestion control + feedback from receivers has begun to be received (This also + implies that receivers in the group are actually present and + can be used as a cue to begin data transmission.). Note that + congestion control must be enabled (see NormSetCongestionControl()) + for this event to be posted. Congestion control feedback can + be assumed to be received until a + NORM_CC_INACTIVE event is + posted. + + + + NORM_CC_INACTIVE + + This event indicates there has been no recent + congestion control feedback received from the receiver set + and that the local NORM sender has reached its minimum + transmit rate. Applications may wish to refrain from new + data transmission until a NORM_CC_ACTIVE + event is posted. This notification is only posted when + congestion control operation is enabled (see NormSetCongestionControl()) + and a previous NORM_CC_ACTIVE event has + occurred. + + + + Receiver + Notifications: + + + + NORM_REMOTE_SENDER_NEW + + This event is posted when a receiver first + receives messages from a specific remote NORM sender. This + marks the beginning of the interval during which the + application may reference the provided "node" handle (NormNodeHandle). + + + + NORM_REMOTE_SENDER_ACTIVE + + This event is posted when a previously inactive + (or new) remote sender is detected operating as an active + sender within the session. + + + + NORM_REMOTE_SENDER_INACTIVE + + This event is posted after a significant period + of inactivity (no sender messages received) of a specific + NORM sender within the session. The NORM protocol engine + frees buffering resources allocated for this sender when it + becomes inactive. + + + + NORM_REMOTE_SENDER_PURGED + + This event is posted when the NORM protocol + engine frees resources for, and thus invalidates the + indicated "node" handle. + + + + NORM_RX_OBJECT_NEW + + This event is posted when reception of a new + transport object begins and marks the beginning of the + interval during which the specified "object" (NormObjectHandle) + is valid. + + + + NORM_RX_OBJECT_INFO + + This notification is posted when the + NORM_INFO content for the indicated + "object" is received. + + + + NORM_RX_OBJECT_UPDATED + + This event indicates that the identified + receive "object" has newly received data + content. + + + + NORM_RX_OBJECT_COMPLETED + + This event is posted when a receive object is + completely received, including available + NORM_INFO content. Unless the application + specifically retains the "object" handle, the indicated + NormObjectHandle + becomes invalid and must no longer be + referenced. + + + + NORM_RX_OBJECT_ABORTED + + This notification is posted when a pending + receive object's transmission is aborted by the remote + sender. Unless the application specifically retains the + "object" handle, the indicated NormObjectHandle + becomes invalid and must no longer be + referenced. + + + + NORM_RX_CMD_NEW + + This event indicates that an application-defined + command has been received from a remote sender. The + NormEvent node element indicates the + NormNodeHandle value associated with the given + sender. The NormNodeGetCommand() + call can be used to retrieve the received command + content. + + + + Miscellaneous + Notifications: + + + + NORM_GRTT_UPDATED + + This notification indicates that either the + local sender estimate of GRTT has changed, or that a remote + sender's estimate of GRTT has changed. The "sender" member + of the NormEvent is + set to NORM_NODE_INVALID if the local + sender's GRTT estimate has changed or to the NormNodeHandle + of the remote sender that has updated its estimate of + GRTT. + + + + NORM_EVENT_INVALID + + This NormEventType + indicates an invalid or "null" notification which should be + ignored. + + + + + + + + Return Values + + This function generally blocks the thread of application + execution until a NormEvent is available + and returns true when a NormEvent is + available. However, there are some exceptional cases when the + function may immediately return even when no event is pending. In + these cases, the return value is false + indicating the NormEvent should be + ignored. + + Win32 Note: A future version of this API will + provide an option to have a user-defined Window message posted when + a NORM API event is pending. (Also some event filtering calls may be + provided (e.g. avoid the potentially numerous + NORM_RX_OBJECT_UPDATED events if not needed by + the application)). + + + + + NormGetDescriptor() + + + Synopsis + + #include <normApi.h> + +NormDescriptor NormGetDescriptor(NormInstanceHandle instance); + + + + Description + + This function is used to retrieve a NormDescriptor + (Unix int file descriptor or Win32 HANDLE) + suitable for asynchronous I/O notification to avoid blocking calls + to NormGetNextEvent(). + A NormDescriptor is + available for each protocol engine instance created using NormCreateInstance(). The + descriptor returned is suitable for use as an input (or "read") + descriptor which is signaled when a NORM protocol event is ready for + retrieval via NormGetNextEvent(). + Hence, a call to NormGetNextEvent() + will not block when the descriptor has been signaled. The Unix + select() or Win32 + WaitForMultipleObjects() system calls can be + used to detect when the NormDescriptor is + signaled. Note that for Unix select() call + usage, the NORM descriptor should be treated as a "read" + descriptor. + + + + Return Values + + A NormDescriptor value is + returned which is valid until a call to NormDestroyInstance() + is made. Upon error, a value of + NORM_DESCRIPTOR_INVALID is returned. + + + + + + Session Creation and Control Functions + + Whether participating in a NORM protocol session as a sender, + receiver, or both, there are some common API calls used to instantiate a + NormSession and set some common session parameters. + Functions are provided to control network socket and multicast + parameters. Additionally, a "user data" value may be associated with a + NormSessionHandle + for programming convenience when dealing with multiple sessions. + + + NormCreateSession() + + + Synopsis + + #include <normApi.h +NormSessionHandle NormCreateSession(NormInstanceHandle instance, + const char* address, + unsigned short port, + NormNodeId localId); + + + + Description + + This function creates a NORM protocol session + (NormSession) using the + address (multicast or unicast) and + port parameters provided. While session state + is allocated and initialized, active session participation does not + begin until a call is made to NormStartSender() + and/or NormStartReceiver() + to join the specified multicast group (if applicable) and start + protocol operation. The following parameters are required in this + function call: + + + + + + + + + + instance + + This must be a valid NormInstanceHandle + previously obtained with a call to NormCreateInstance(). + + + + address + + This points to a string containing an IP + address (e.g. dotted decimal IPv4 address (or IPv6 address) + or name resolvable to a valid IP address. The specified + address (along with the port number) determines the + destination of NORM messages sent. For multicast sessions, + NORM senders and receivers must use a common multicast + address and port number. For unicast sessions, the sender + and receiver must use a common port number, but specify the + other node's IP address as the session address (Although + note that receiver-only unicast nodes who are providing + unicast feedback to senders will not generate any messages + to the session IP address and the address parameter value is + thus inconsequential for this special case). + + + + port + + This must be a valid, unused port number + corresponding to the desired NORM session address. See the + address parameter description for more + details. + + + + localId + + The localId parameter + specifies the NormNodeId + that should be used to identify the application's presence + in the NormSession. All participant's + in a NormSession should use unique + localId values. The application may + specify a value of NORM_NODE_ANY or + NORM_NODE_ANY for the + localId parameter. In this case, the + NORM implementation will attempt to pick an identifier based + on the host computer's "default" IP address (based on the + computer's default host name). Note there is a chance that + this approach may not provide unique node identifiers in + some situations and the NORM protocol does not currently + provide a mechanism to detect or resolve NormNodeId + collisions. Thus, the application should explicitly specify + the localId unless there is a high + degree of confidence that the default IP address will + provide a unique identifier. + + + + + + + + Return Values + + The returned NormSessionHandle + value is valid until a call to NormDestroySession() + is made. A value of NORM_SESSION_INVALID is + returned upon error. + + + + + NormDestroySession() + + + Synopsis + + #include <normApi.h> + +void NormDestroySession(NormSessionHandle sessionHandle); + + + + Description + + This function immediately terminates the application's + participation in the NormSession identified by + the sessionHandle parameter and frees any + resources used by that session. An exception to this is that the + application is responsible for releasing any explicitly retained + NormObjectHandle + values (See NormObjectRetain() + and NormObjectRelease()). + + + + Return Values + + This function has no returned values. + + + + + NormSetUserData() + + + Synopsis + + #include <normApi.h> + +void NormSetUserData(NormSessionHandle sessionHandle, + const void* userData); + + + + Description + + This function allows the application to attach a value to the + previously-created NormSession instance + specified by the sessionHandle parameter. + This value is not used or interpreted by NORM, but is available to + the application for use at the programmer's discretion. The set + userData value can be later retrieved using + the NormGetUserData() + function call. + + + + Return Values + + This function has no returned values. + + + + + NormGetUserData() + + + Synopsis + + #include <normApi.h> + +const void* NormGetUserData(NormSessionHandle sessionHandle); + + + + Description + + This function retrieves the "user data" value set for the + specified sessionHandle with a prior call to + NormSetUserData(). + + + + Return Values + + This function returns the user data value set for the + specified session. If no user data value has been previously set, a + NULL (i.e., (const void*)0) value is + returned. + + + + + NormGetLocalNodeId() + + + Synopsis + + #include <normApi.h> + +NormNodeId NormGetLocalNodeId(NormSessionHandle sessionHandle); + + + + Description + + This function retrieves the NormNodeId value used + for the application's participation in the + NormSession identified by the + sessionHandle parameter. The value may have + been explicitly set during the NormCreateSession() + call or may have been automatically derived using the host + computer's "default" IP network address. + + + + Return Values + + The returned value indicates the NormNode + identifier used by the NORM protocol engine for the local + application's participation in the specified + NormSession. + + + + + NormSetTxPort() + + + Synopsis + + #include <normApi.h> + +bool NormSetTxPort(NormSessionHandle sessionHandle, + unsigned short txPort, + bool enableReuse = false, + const char* txBindAddress = (const char*)0); + + + + Description + + This function is used to force NORM to use a specific port + number for UDP packets sent for the specified + sessionHandle. Additionally, it can + optionally enable reuse of the specified port number and/or specify + a specific source address binding that is used for packet + transmission. By default, NORM uses separate port numbers for packet + transmission and session packet reception (the receive port is + specified as part of the NormCreateSession() + call), allowing the operating system to pick a freely available port + for transmission. This call allows the application to pick a + specific port number for transmission, and furthermore allows the + application to even specify the same port number for transmission as + is used for reception. However, the use of separate transmit/receive + ports allows NORM to discriminate when unicast feedback is occurring + and thus it is not generally recommended that the transmit port be + set to the same value as the session receive port. + + The enableReuse parameter, when set to + true, allows that the specified port may be + reused for multiple sessions, but care must be taken when enabling + this option. The txBindAddress parameter + allows specification of a specific source address binding for packet + transmission. The specified address MUST be a valid unicast IP + address assigned and configured for the host system. Additionally, + the address specified must be compatible with multicast routing + and/or the interfaces specified in any calls to NormSetMulticastInterface() + for the given session when IP multicast is used. + + When the txPort is set equal to the + session port number and a txBindAddress is + not specified or set equal to the session address, a single socket + is used for both transmission and reception. If the same port number + is desired for both packet transmission and reception, + and a specific source address binding is set, + then the enableReuse parameter MUST be (and + is automatically) set to true for successful + operation. In this case, the receive socket is bound to session + address if it is multicast and the transmit socket is bound to the + specified txAddress although both are bound + to the same port number. + + Note this call MUST be made before any + calls to NormStartSender() + or NormStartReceiver() + for the given session to work as described. + + + + Return Values + + This function returns true upon success + and false upon failure. Failure will occur if a + txBindAddress is provided that does not + correspond to a valid, configured IP address for the local host + system. + + + + + NormSetTxOnly() + + + Synopsis + + #include <normApi.h> + +void NormSetTxOnly(NormSessionHandle sessionHandle, + bool txOnly, + bool connectToSessionAddress = false); + + + + Description + + This function limits the NormSession to + perform NORM sender functions only. It also limits the underlying + NORM UDP socket usage to open only a single transmit socket + (tx_socket) and does not open or bind a receive + socket for the given session address or port number. Thus, if this + property is set, any NORM receivers MUST enable unicast feedback via + a call to the NormSetDefaultUnicastNack() + or appropriate NormNodeSetUnicastNack() + function in order for their feedback messages (NACKs and ACKs) to be + received by this sender. The purpose of this function is to allow + NORM sender sessions to be created as separate process from a + corresponding NORM receiver session for the same session address and + port number. By default (when this call is not made), a + NormSession, even when acting as only a sender + (see NormStartSender()) opens + two separate UDP sockets including a "receive" socket bound to the + session port number and a "transmit" socket used for message + transmission and reception of unicast feedback messages when + receivers are so configured. + + The optional connectToSessionAddress + parameter, when set to true, causes the + underlying NORM code to "connect()" the UDP + socket to the session (remote receiver) address and port number. If + the corresponding NORM remote receiver instance uses NormSetTxPort() to set its transmit + port to the same as the session port number, the result is a unique + binding between this "tx only" sender instance and the remote NORM + receiver instance. With proper use of NormSetRxPortReuse(), this + allows multiple senders to be properly associated (i.e., binded with + respect to UDP socket packet demultiplexing) with multiple receivers + on a single host (all using the same session port number). Note the + NORM receiver MUST also use the NormSetDefaultUnicastNack() + call so that its feedback messages are directed to the "tx only" + sender address/port. The motivation for this API call is to allow + systems have NORM sender and receiver instances in separate + processes supporting a set (e.g. a mesh) of unicast connections to + other hosts. The only constraint is that the senders uses a "tx + port" number that is different from the "rx port" number. This + enables firewall configurations that only open a pair of UDP ports + and allow for connection among an arbitrary number of hosts. This + option is really only relevant for unicast NORM sessions. + + + + Return Values + + This function has no return values. + + + + + NormSetRxPortReuse() + + + Synopsis + + #include <normApi.h> + +void NormSetRxPortReuse(NormSessionHandle session, + bool enableReuse, + const char* rxBindAddress = (const char*)0, + const char* senderAddress = (const char*)0, + UINT16 senderPort = 0); + + + + Description + + This function allows the user to control the port reuse and + binding behavior for the receive socket used for the given NORM + sessionHandle. When the + enablReuse parameter is set to + true, reuse of the + NormSession port number by multiple NORM + instances or sessions is enabled. + + If the optional rxBindAddress is + supplied (an IP address or host name in string form), the socket + will bind() to the given address when it is opened in a call to + NormStartReceiver() or NormStartSender(). The + rxBindAddress MUST be the session multicast + address (if it is a multicast session) or a valid local unicast + address in the case of NORM unicast operation. This binding limits + the socket to receive only packets destined for the specified + rxBindAddress. This allows multiple NORM + sessions to reuse the same port number, but use different multicast + addresses (or allow for multiple NORM sessions for multiple local + unicast addresses). + + The optional senderAddress and + senderPort parameters can be used to + connect() the underlying NORM receive socket to specific + address/port. This limits the socket to receiving only packets from + the specified + senderAddress/senderPort. + This, with receive port reuse enabled, allows for multiple NORM + receiver instances to be listening to different NORM senders and + have proper UDP socket demultiplexing occur. Note that it is also + possible to have single NORM receiver receive transmissions from + multiple senders, but in some cases it may be desirable for separate + NORM processes or threads to be used to handle reception from + separate senders. Thus, this socket binding option is + provided. + + When this call is not made in any form, the default socket + binding to IP address INADDR_ANY (equivalent to when this call is + made and rxBindAddress is set to + NULL) allows the + NormSession receive socket to receive any + multicast or unicast transmissions to the session port number + provided in the call to NormCreateSession(). + This allows a NORM receiver to receive from senders sending to a + multicast session address or the receiver's unicast address. As + mentioned, enabling port reuse and binding the session destination + address allows multiple NORM sessions on the same port number, but + participating in different multicast groups. + + Note this call MUST be made before any + calls to NormStartSender() + or NormStartReceiver() + for the given sessionHandle to + succeed. + + This call could also be used in conjunction with NormSetMulticastInterface() + so that multiple NormSessions, using the same + port and multicast address, could separately cover multiple network + interfaces (and some sort of application-layer bridging of reliable + multicast could be realized if desired). + + + + Return Values + + This function has no return values. + + + + + NormSetMulticastInterface() + + + Synopsis + + #include <normApi.h> + +bool NormSetMulticastInterface(NormSessionHandle session, + const char* interfaceName); + + + + Description + + This function specifies which host network interface is used + for IP Multicast transmissions and group membership. This should be + called before any call to NormStartSender() + or NormStartReceiver() + is made so that the IP multicast group is joined on the proper host + interface. However, if a call to NormSetMulticastInterface() + is made after either of these function calls, the call will not + affect the group membership interface, but only dictate that a + possibly different network interface is used for transmitted NORM + messages. Thus, the code: + + NormSetMulticastInterface(session, "interface1"); +NormStartReceiver(session, ...); +NormSetMulticastInterface(session, "interface2"); + + will result in NORM group membership (i.e. multicast + reception) being managed on "interface1" while + NORM multicast transmissions are made via + "interface2". + + + + Return Values + + A return value of true indicates success + while a return value of false indicates that + the specified interface was valid. This function will always return + true if made before calls to NormStartSender() + or NormStartReceiver(). + However, those calls may fail if an invalid interface was specified + with the call described here. + + + + + NormSetTTL() + + + Synopsis + + #include <normApi.h> + +bool NormSetTTL(NormSessionHandle session, + unsigned char ttl); + + + + Description + + This function specifies the time-to-live + (ttl) for IP Multicast datagrams generated by + NORM for the specified sessionHandle. The IP + TTL field limits the number of router "hops" that a generated + multicast packet may traverse before being dropped. For example, if + TTL is equal to one, the transmissions will be limited to the local + area network (LAN) of the host computers network interface. Larger + TTL values should be specified to span large networks. Also note + that some multicast router configurations use artificial "TTL + threshold" values to constrain some multicast traffic to an + administrative boundary. In these cases, the NORM TTL setting must + also exceed the router "TTL threshold" in order for the NORM traffic + to be allowed to exit the administrative area. + + + + Return Values + + A return value of true indicates success + while a return value of false indicates that + the specified ttl could not be set. This + function will always return true if made before + calls to NormStartSender() + or NormStartReceiver(). + However, those calls may fail if the desired + ttl value cannot be set. + + + + + NormSetTOS() + + + Synopsis + + #include <normApi.h> + +bool NormSetTOS(NormSessionHandle sessionHandle, + unsigned char tos); + + + + Description + + This function specifies the type-of-service + (tos) field value used in IP Multicast + datagrams generated by NORM for the specified + sessionHandle. The IP TOS field value can be + used as an indicator that a "flow" of packets may merit special + Quality-of-Service (QoS) treatment by network devices. Users should + refer to applicable QoS information for their network to determine + the expected interpretation and treatment (if any) of packets with + explicit TOS marking. + + + + Return Values + + A return value of true indicates success + while a return value of false indicates that + the specified tos could not be set. This + function will always return true if made before + calls to NormStartSender() + or NormStartReceiver(). + However, those calls may fail if the desired + tos value cannot be set. + + + + + NormSetLoopback() + + + Synopsis + + #include <normApi.h> + +void NormSetLoopback(NormSessionHandle sessionHandle, + bool loopbackEnable); + + + + Description + + This function enables or disables loopback operation for the + indicated NORM sessionHandle. If + loopbackEnable is set to + true, loopback operation is enabled which + allows the application to receive its own message traffic. Thus, an + application which is both actively receiving and sending may receive + its own transmissions. Note it is expected that this option would be + principally be used for test purposes and that applications would + generally not need to transfer data to themselves. If + loopbackEnable is false, the application is + prevented from receiving its own NORM message transmissions. By + default, loopback operation is disabled when a + NormSession is created. + + + + Return Values + + This function has no return values. + + + + + NormSetFragmentation() + + + Synopsis + + #include <normApi.h> + +bool NormSetFragmentation(NormSessionHandle sessionHandle, + bool fragmentation); + + + + Description + + This function sets an underlying socket option that enables or + disables IP datagram fragmentation by network intermediate systems + according to whether the fragmentation + parameter is set to a value of true or + false, respectively. If set to + true to enable fragmentation, the DF (don't + fragment) bit of the headers of NORM UDP/IP packets sent will be + cleared. Otherwise the DF bit is set and packets will not be + fragmented by network devices if they exceed a link Maximum + Transmission Unit (MTU) and will instead be dropped. For IP + Multicast destinations, some operating systems may always set the DF + bit of transmitted packets, regardless of the setting here and the + underlying socket option status. Typically, the DF bit is set (i.e., + fragmentation disabled) by default on most operating systems. + + + This call is not currently functional on the Mac OSX system + that does not support the needed + IP_MTU_DISCOVER or + IP_DONTFRAG socket options. + + + + Return Values + + This function returns true upon success + and false upon failure. + + + + + + <anchor id="_NORM_Sender_Functions" />NORM Sender + Functions + + The functions described in this section apply only to NORM sender + operation. Applications may participate strictly as senders or as + receivers, or may act as both in the context of a NORM protocol session. + The NORM sender is responsible for most parameters pertaining to its + transmission of data. This includes transmission rate, data segmentation + sizes, FEC coding parameters, stream buffer sizes, etc. + + + NormStartSender() + + + Synopsis + + #include <normApi.h> + +bool NormStartSender(NormSessionHandle sessionHandle, + NormSessionId instanceId, + unsigned long bufferSpace, + unsigned short segmentSize, + unsigned char blockSize, + unsigned char numParity); + + + + Description + + The application's participation as a sender within a specified + NormSession begins when this function is + called. This includes protocol activity such as congestion control + and/or group round-trip timing (GRTT) feedback collection and + application API activity such as posting of sender-related NormEvent + notifications. The parameters required for this function call + include: + + + + + + + + + + sessionHandle + + This must be a valid NormSessionHandle + previously obtained with a call to NormCreateSession(). + + + + instanceId + + Application-defined value used as the + instance_id field of NORM sender + messages for the application's participation within a + session. Receivers can detect when a sender has terminated + and restarted if the application uses different + instanceId values when initiating + sender operation. For example, a robust application could + cache previous instanceId values in + non-volatile storage and gracefully recover (without + confusing receivers) from a total system shutdown and reboot + by using a new instanceId value upon + restart. + + + + bufferSpace + + This specifies the maximum memory space (in + bytes) the NORM protocol engine is allowed to use to buffer + any sender calculated FEC segments and repair state for the + session. The optimum bufferSpace + value is function of the network topology bandwidth*delay + product and packet loss characteristics. If the + bufferSpace limit is too small, the + protocol may operate less efficiently as the sender is + required to possibly recalculate FEC parity segments and/or + provide less efficient repair transmission strategies + (resort to explicit repair) when state is dropped due to + constrained buffering resources. However, note the protocol + will still provide reliable transfer. A large + bufferSpace allocation is safer at + the expense of possibly committing more memory + resources. + + + + segmentSize + + This parameter sets the maximum payload size + (in bytes) of NORM sender messages (not including any NORM + message header fields). A sender's + segmentSize value is also used by + receivers to limit the payload content of some feedback + messages (e.g. NORM_NACK message content, + etc.) generated in response to that sender. Note different + senders within a NormSession may use + different segmentSize values. Generally, the appropriate + segment size to use is dependent upon the types of networks + forming the multicast topology, but applications may choose + different values for other purposes. Note that application + designers MUST account for the size of NORM message headers + when selecting a segmentSize. For + example, the NORM_DATA message header for + a NORM_OBJECT_STREAM with full header + extensions is 48 bytes in length. In this case, the UDP + payload size of these messages generated by NORM would be up + to (48 + segmentSize) + bytes. + + + + blockSize + + This parameter sets the number of source symbol + segments (packets) per coding block, for the systematic + Reed-Solomon FEC code used in the current NORM + implementation. For traditional systematic block code + "(n,k)" nomenclature, the blockSize + value corresponds to "k". NORM logically segments transport + object data content into coding blocks and the + blockSize parameter determines the + number of source symbol segments (packets) comprising a + single coding block where each source symbol segment is up + to segmentSize bytes in length.. A + given block's parity symbol segments are calculated using + the corresponding set of source symbol segments. The maximum + blockSize allowed by the 8-bit + Reed-Solomon codes in NORM is 255, with + the further limitation that + (blockSize + + numParity) <= + 255. + + + + numParity + + This parameter sets the maximum number of + parity symbol segments (packets) the sender is willing to + calculate per FEC coding block. The parity symbol segments + for a block are calculated from the corresponding + blockSize source symbol segments. In + the "(n,k)" nomenclature mention above, + the numParity value corresponds to + "n - k". A property of the Reed-Solomon + FEC codes used in the current NORM implementation is that + one parity segment can fill any one erasure (missing segment + (packet)) for a coding block. For a given + blockSize, the maximum numParity + value is (255 - + blockSize). However, note that + computational complexity increases significantly with + increasing numParity values and + applications may wish to be conservative with respect to + numParity selection, given + anticipated network packet loss conditions and group size + scalability concerns. Additional FEC code options may be + provided for this NORM implementation in the future with + different parameters, capabilities, trade-offs, and + computational requirements. + + + + + + These parameters are currently immutable with respect to a + sender's participation within a NormSession. + Sender operation must be stopped (see NormStopSender()) + and restarted with another call to NormStartSender() + if these parameters require alteration. The API may be extended in + the future to support additional flexibility here, if required. For + example, the NORM protocol "intance_id" field + may possibly be leveraged to permit a node to establish multiple + virtual presences as a sender within a + NormSession in the future. This would allow the + sender to provide multiple concurrent streams of transport, with + possibly different FEC and other parameters if appropriate within + the context of a single NormSession. Again, + this extended functionality is not yet supported in this + implementation. + + + + Return Values + + A value of true is returned upon success + and false upon failure. The reasons failure may + occur include limited system resources or that the network sockets + required for communication failed to open or properly configure. + (TBD - Provide a NormGetError(NormSessionHandle + sessionHandle) function to retrieve a more + specific error indication for this and other + functions.) + + + + + NormStopSender() + + + Synopsis + + #include <normApi.h> + +void NormStopSender(NormSessionHandle sessionHandle, + bool graceful = false); + + + + Description + + This function terminates the application's participation in a + NormSession as a sender. By default, the sender + will immediately exit the session identified by the + sessionHandle parameter without notifying the + receiver set of its intention. However a "graceful shutdown" option, + enabled by setting the graceful parameter to + true, is provided to terminate sender operation gracefully, + notifying the receiver set its pending exit with appropriate + protocol messaging. A NormEvent, + NORM_LOCAL_SENDER_CLOSED, is dispatched when the + graceful shutdown process has completed. + + + + Return Values + + This function has no return values. + + + + + NormSetTxRate() + + + Synopsis + + #include <normApi.h> + +void NormSetTxRate(NormSessionHandle sessionHandle, + double rate); + + + + Description + + This function sets the transmission + rate (in bits per second (bps)) limit used + for NormSender transmissions for the given + sessionHandle. For fixed-rate transmission of + NORM_OBJECT_FILE or + NORM_OBJECT_DATA, this limit determines the data + rate at which NORM protocol messages and data content are sent. For + NORM_OBJECT_STREAM transmissions, this is the + maximum rate allowed for transmission (i.e. if the application + writes to the stream at a lower rate, a lower average NORM + transmission rate will occur). Note that the application will need + to consider the overhead of NORM protocol headers when determining + an appropriate transmission rate for its purposes. When NORM + congestion control is enabled (see NormSetCongestionControl()), + the rate set here will be set, but congestion + control operation, if enabled, may quickly readjust the transmission + rate. + + + + Return Values + + This function has no return values. + + + + + NormGetTxRate() + + + Synopsis + + #include <normApi.h> + +double NormGetTxRate(NormSessionHandle sessionHandle); + + + + Description + + This function retrieves the current sender transmission rate + in units of bits per second (bps) for the given + sessionHandle. When NORM congestion control + is enabled (see NormSetCongestionControl()), + this reflects the current rate set (or suggested) by NORM congestion + control operation. Otherwise, this returns the rate that was set + with the NormSetTxRate() + call. + + + + Return Values + + This function returns the sender transmission rate in units of + bits per second (bps). + + + + + NormSetTxSocketBuffer() + + + Synopsis + + #include <normApi.h> + +bool NormSetTxSocketBuffer(NormSessionHandle sessionHandle, + unsigned int bufferSize); + + + + Description + + This function can be used to set a non-default socket buffer + size for the UDP socket used by the specified NORM + sessionHandle for data transmission. The + bufferSize parameter specifies the desired + socket buffer size in bytes. Large transmit socket buffer sizes may + be necessary to achieve high transmission rates when NORM, as a + user-space process, is unable to precisely time its packet + transmissions. Similarly, NORM receivers may need to set large + receive socket buffer sizes to achieve successful, sustained high + data rate reception (see NormSetRxSocketBuffer()). + Typically, it is more important to set the receive socket buffer + size (see NormSetRxSocketBuffer()) + as this maintains reliability (i.e. by avoiding receive socket + buffer overflow) at high data rates while setting a larger transmit + socket buffer size allows higher average transmission rates to be + achieved. + + + + Return Values + + This function returns true upon success + and false upon failure. Possible failure modes + include an invalid sessionHandle parameter, a + call to NormStartReceiver() + or NormStartSender() + has not yet been made for the session, or an invalid + bufferSize was given. Note some operating + systems may require additional system configuration to use + non-standard socket buffer sizes. + + + + + NormSetFlowControl() + + + Synopsis + + #include <normApi.h> + +void NormSetFlowControl(NormSessionHandle sessionHandle, + double flowControlFactor); + + + + Description + + This function controls a scaling factor that is used for + sender timer-based flow control for the the specified NORM + sessionHandle. Timer-based flow control works + by preventing the NORM sender application from enqueueing new + transmit objects or stream data that would purge "old" objects or + stream data when there has been recent NACK activity for those old + objects or data. If the flowControlFactor is + set to ZERO, then the flow control mechanism is + effectively disabled. Larger + flowControlFactor values enforce more robust + flow control by forcing the sender to maintain state longer, but + then larger transmit buffer, stream buffer, transmit cache bounds + and receive cache limits (see NormStartSender(), + NormStreamOpen(), + NormSetTxCacheBounds(), + and NormSetRxCacheLimit(), + respectively) may be needed to maintain throughput in larger + <delay*bandwidth, loss> conditions. Effectively, a larger + flowControlFactor can favor reliability over + throughput when buffer-constrained. + + The flowControlFactor is used to + compute a delay time for when a sender buffered object (or block of + stream data) may be released (i.e. purged) after transmission or + applicable NACKs reception. The delay time function is: + + flowControlDelay = flowControlFactor * GRTT * (backoffFactor + 1) + + where the "GRTT" is the sender's advertised + GRTT estimate and the backoffFactor is the + sender's configured timer-based feedback scaling factor. + + The default value (when this function is not called) of the + flowControlFactor is + 2.0. Note that a NORM application can also + implement more explicit, deterministic flow control through use of + the NormSetWatermark() + API call, potentially even requiring positive acknowledgement of + older data before enqueueing new data. Note that using the NormSetWatermark() + API call with a NORM_NODE_NONE member in acking + node list to force a "full" watermark flush is somewhat equivalent + to timer-based flow control with a + flowControlFactor equal to 2.0 * + txRobustFactor. + + If such explicit flow control is implemented by the + application, then a reduced flowControlFactor + (or even ZERO) may be used. If "push mode" is + enabled for a NORM_OBJECT_STREAM (see NormStreamSetPushEnable()), + then flow control has no effect for the stream. + + + + Return Values + + This function has no return values. + + + + + NormSetCongestionControl() + + + Synopsis + + #include <normApi.h> + +void NormSetCongestionControl(NormSessionHandle sessionHandle, + bool enable, + bool adjustRate = true); + + + + Description + + This function enables (or disables) the NORM sender congestion + control operation for the session designated by the + sessionHandle parameter. For best operation, + this function should be called before the call to NormStartSender() + is made, but congestion control operation can be dynamically + enabled/disabled during the course of sender operation. If the value + of the enable parameter is + true, congestion control operation is enabled + while it is disabled for enable equal to false. + When congestion control operation is enabled, the NORM sender + automatically adjusts its transmission rate based on feedback from + receivers. If bounds on transmission rate have been set (see NormSetTxRateBounds()) + the rate adjustment will remain within the set bounds. The + application will be notified of any changes to the sender + transmission rate via a NormEvent of type + NORM_TX_RATE_CHANGED. + + The rate set by NormSetTxRate() + has no effect when congestion control operation is enabled, + unless the adjustRate + parameter here is set to false. When the + adjustRate parameter is set to + false, the NORM Congestion Control operates as + usual, with feedback collected from the receiver set and the + "current limiting receiver" identified, except that no actual + adjustment is made to the sender's transmission rate. I.e., the + transmission rate that was set by NormSetTxRate() is observed + by the sender regardless of the feedback received. The + NORM_TX_RATE_CHANGED notification will still + occur as if the rate were being adjusted and the value returned by + NormGetTxRate() + reflects the rate that would have been used had the + adjustRate parameter been enabled even though + no actual rate change has occurred. The purpose of this variation of + NORM Congestion Control operation is to allow applications to get a + "suggested" rate from the NORM-CC mechanism. But, it is important to + note that this "suggested" rate may or may not be appropriate since + the operation of the NORM-CC algorithm is somewhat dependent on the + associated NORM sender load on the network. For example, the + "suggested" rate may be artificially high if the sender application + has not been correspondingly setting the rate and actively + transmitting data at that rate. This optional mode of operation is + provided for EXPERIMENTAL purposes and is NOT RECOMMENDED for + typical use of NORM. + + NORM's congestion algorithm provides rate adjustment to fairly + compete for available network bandwidth with other TCP, NORM, or + similarly governed traffic flows. + + (TBD - Describe the + NormSetEcnSupport() function as this + experimental option matures.) + + + + Return Values + + This function has no return values. + + + + + NormSetTxRateBounds() + + + Synopsis + + #include <normApi.h> + +bool NormSetTxRateBounds(NormSessionHandle sessionHandle, + double rateMin, + double rateMax); + + + + Description + + This function sets the range of sender transmission rates + within which the NORM congestion control algorithm is allowed to + operate for the given sessionHandle. By + default, the NORM congestion control algorithm operates with no + lower or upper bound on its rate adjustment. This function allows + this to be limited where rateMin corresponds + to the minimum transmission rate (bps) and + rateMax corresponds to the maximum + transmission rate. One or both of these parameters may be set to + values less than zero to remove one or both bounds. For example, the + call "NormSetTxRateBounds(session, + -1.0, 64000.0)" will set an upper limit of 64 kbps for the + sender transmission rate with no lower bound. These rate bounds + apply only when congestion control operation is enabled (see NormSetCongestionControl()). + If the current congestion control rate falls outside of the + specified bounds, the sender transmission rate will be adjusted to + stay within the set bounds. + + + + Return Values + + This function returns true upon success. + If both rateMin and + rateMax are greater than or equal to zero, + but (rateMax < + rateMin), the rate bounds will remain unset + or unchanged and the function will return + false. + + + + + NormSetTxCacheBounds() + + + Synopsis + + #include <normApi.h> + +void NormSetTxCacheBounds(NormSessionHandle sessionHandle, + NormSize sizeMax, + unsigned int countMin, + unsigned int countMax); + + + + Description + + This function sets limits that define the number and total + size of pending transmit objects a NORM sender will allow to be + enqueued by the application. Setting these bounds to large values + means the NORM protocol engine will keep history and state for + previously transmitted objects for a larger interval of time + (depending upon the transmission rate) when the application is + actively enqueueing additional objects in response to + NORM_TX_QUEUE_EMPTY notifications. This can allow + more time for receivers suffering degraded network conditions to + make repair requests before the sender "purges" older objects from + its "transmit cache" when new objects are enqueued. A + NORM_TX_OBJECT_PURGED notification is issued when + the enqueuing of a new transmit object causes the NORM transmit + cache to overflow, indicating the NORM sender no longer needs to + reference the designated old transmit object and the application is + free to release related resources as needed. + + The sizeMax parameter sets the maximum + total size, in bytes, of enqueued objects allowed, providing the + constraints of the countMin and + countMax parameters are met. The + countMin parameter sets the minimum number of + objects the application may enqueue, regardless of the objects' + sizes and the sizeMax value. For example, the + default sizeMax value is 20 Mbyte and the + default countMin is 8, thus allowing the + application to always have at least 8 pending objects enqueued for + transmission if it desires, even if their total size is greater than + 20 Mbyte. Similarly, the countMax parameter + sets a ceiling on how many objects may be enqueued, regardless of + their total sizes with respect to the sizeMax + setting. For example, the default countMax + value is 256, which means the application is never allowed to have + more than 256 objects pending transmission enqueued, even if they + are 256 very small objects. Note that + countMax must be greater than or equal to + countMin and countMin + is recommended to be at least two. + + Note that in the case of NORM_OBJECT_FILE + objects, some operating systems impose limits (e.g. 256) on how many + open files a process may have at one time and it may be appropriate + to limit the countMax value accordingly. In + other cases, a large countMin or + countMax may be desired to allow the NORM + sender to act as virtual cache of files or other data available for + reliable transmission. Future iterations of the NRL NORM + implementation may support alternative NORM receiver "group join" + policies that would allow the receivers to request transmission of + cached content. + + The utility of the NormRequeueObject() + API call also depends on the parameters set by this function. The + NormRequeueObject() + call will only succeed when the given + objectHandle corresponds to an object + maintained in the NORM senders "transmit cache". + + + + Return Values + + This function has no return value. + + + + + NormSetAutoParity() + + + Synopsis + + #include <normApi.h> + +void NormSetAutoParity(NormSessionHandle sessionHandle, + unsigned char autoParity); + + + + Description + + This function sets the quantity of proactive "auto parity" + NORM_DATA messages sent at the end of each FEC + coding block. By default (i.e., autoParity + = 0), FEC content is sent + only in response to repair requests (NACKs) from receivers. But, by + setting a non-zero value for autoParity, the + sender can automatically accompany each coding block of transport + object source data segments ((NORM_DATA messages) + with the set number of FEC segments. The number of source symbol + messages (segments) per FEC coding block is determined by the + blockSize parameter used when NormStartSender() + was called for the given + sessionHandle. + + The use of proactively-sent "auto parity" may eliminate the + need for any receiver NACKing to achieve reliable transfer in + networks with low packet loss. However, note that the quantity of + "auto parity" set adds overhead to transport object transmission. In + networks with a predictable level of packet loss and potentially + large round-trip times, the use of "auto parity" may allow lower + latency in the reliable delivery process. Also, its use may + contribute to a smaller amount of receiver feedback as only + receivers with exceptional packet loss may need to NACK for + additional repair content. + + The value of autoParity set must be + less than or equal to the numParity parameter + set when NormStartSender() + was called for the given + sessionHandle. + + + + Return Values + + This function has no return values. + + + + + NormGetGrttEstimate() + + + Synopsis + + #include <normApi.h> + +double NormGetGrttEstimate(NormSessionHandle sessionHandle); + + + + Description + + This function returns the sender's current estimate(in + seconds) of group round-trip timing (GRTT) for the given NORM + session. This function may be useful for applications to leverage + for other purposes the assessment of round-trip timing made by the + NORM protocol engine. For example, an application may scale its own + timeouts based on connectivity delays among participants in a NORM + session. Note that the NORM_GRTT_UPDATED event is + posted (see NormGetNextEvent()) + by the NORM protocol engine to indicate when changes in the local + sender or remote senders' GRTT estimate occurs. + + + + Return Values + + This function returns the current sender group round-trip + timing (GRTT) estimate (in units of seconds). A value of + -1.0 is returned if an invalid session value is + provided. + + + + + NormSetGrttEstimate() + + + Synopsis + + #include <normApi.h> + +void NormSetGrttEstimate(NormSessionHandle sessionHandle, + double grtt); + + + + Description + + This function sets the sender's estimate of group round-trip + time (GRTT) (in units of seconds) for the given NORM + sessionHandle. This function is expected to + most typically used to initialize the sender's GRTT estimate prior + to the call to NormStartSender() + when the application has a priori confidence that the default + initial GRTT value of 0.5 second is inappropriate. The sender GRTT + estimate will be updated during normal sender protocol operation + after sender startup or if this call is made while sender operation + is active. For experimental purposes (or very special application + needs), this API provides a mechanism to control or disable the + sender GRTT update process (see NormSetGrttProbingMode()). + The grtt value (in seconds) will be limited + to the maximum GRTT as set (see NormSetGrttMax()) + or the default maximum of 10 seconds. + + The sender GRTT is advertised to the receiver group and is + used to scale various NORM protocol timers. The default NORM GRTT + estimation process dynamically measures round-trip timing to + determine an appropriate operating value. An overly-large GRTT + estimate can introduce additional latency into the reliability + process (resulting in a larger virtual delay*bandwidth product for + the protocol and potentially requiring more buffer space to maintain + reliability). An overly-small GRTT estimate may introduce the + potential for feedback implosion, limiting the scalability of group + size. + + Also note that the advertised GRTT estimate can also be + limited by transmission rate. When the sender transmission rate is + low, the GRTT is also governed to a lower bound of the nominal + packet transmission interval (i.e., 1/txRate). + This maintains the "event driven" nature of the NORM protocol with + respect to receiver reception of NORM sender data and + commands. + + + + Return Values + + This function has no return values. + + + + + NormSetGrttMax() + + + Synopsis + + #include <normApi.h> + +void NormSetGrttMax(NormSessionHandle sessionHandle, + double grttMax); + + + + Description + + This function sets the sender's maximum advertised GRTT value + for the given NORM sessionHandle. The + grttMax parameter, in units of seconds, + limits the GRTT used by the group for scaling protocol timers, + regardless of larger measured round trip times. The default maximum + for the NRL NORM library is 10 seconds. See the NormSetGrttEstimate() + function description for the purpose of the NORM GRTT measurement + process. + + + + Return Values + + This function has no return values. + + + + + NormSetGrttProbingMode() + + + Synopsis + + #include <normApi.h> + +void NormSetGrttProbingMode(NormSessionHandle sessionHandle, + NormProbingMode probingMode); + + + + Description + + This function sets the sender's mode of probing for round trip + timing measurement responses from the receiver set for the given + NORM sessionHandle. Possible values for the + probingMode parameter include + NORM_PROBE_NONE, + NORM_PROBE_PASSIVE, and + NORM_PROBE_ACTIVE. The default probing mode is + NORM_PROBE_ACTIVE. In this mode, the receiver set + explicitly acknowledges NORM sender GRTT probes + ((NORM_CMD(CC) + messages) with NORM_ACK responses that are + group-wise suppressed. Note that NORM receivers also will include + their response to GRTT probing piggy-backed on any + NORM_NACK messages sent in this mode as well to + minimize feedback. + + Note that the NORM_PROBE_ACTIVE probing + mode is required and automatically set when NORM congestion control + operation is enabled (see NormSetCongestionControl()). + Thus, when congestion control is enabled, the NormSetGrttProbingMode() + function has no effect. + + If congestion control operation is not enabled, the NORM + application may elect to reduce the volume of feedback traffic by + setting the probingMode to + NORM_PROBE_PASSIVE. Here, the NORM sender still + transmits NORM_CMD(CC) probe + messages multiplexed with its data transmission, but the receiver + set does not explicitly acknowledge these probes. Instead the + receiver set is limited to opportunistically piggy-backing responses + when NORM_NACK messages are generated. Note that + this may, in some cases, introduce some opportunity for bursts of + large volume receiver feedback when the sender's estimate of GRTT is + incorrect due to the reduced probing feedback. But, in some + controlled network environments, this option for passive probing may + provide some benefits in reducing protocol overhead. + + Finally, the probingMode can be set to + NORM_PROBE_NONE to eliminate the overhead (and + benefits) of NORM GRTT measurement entirely. In this case, the + sender application must explicitly set its estimate of GRTT using + the NormSetGrttEstimate() + function. See this function for a description of the purpose of the + NORM GRTT measurement. + + + + Return Values + + This function has no return values. + + + + + NormSetGrttProbingInterval() + + + Synopsis + + #include <normApi.h> + +void NormSetGrttProbingInterval(NormSessionHandle sessionHandle, + double intervalMin, + double intervalMax); + + + + Description + + This function controls the sender GRTT measurement and + estimation process for the given NORM + sessionHandle. The NORM sender multiplexes + periodic transmission of NORM_CMD(CC) messages + with its ongoing data transmission or when data transmission is + idle. When NORM congestion control operation is enabled, these + probes are sent once per RTT of the current limiting receiver (with + respect to congestion control rate). In this case the + intervalMin and + intervalMax parameters (in units of seconds) + control the rate at which the sender's estimate of GRTT is updated. + At session start, the estimate is updated at + intervalMin and the update interval time is + doubled until intervalMax is reached. This + dynamic allows for a rapid initial estimation of GRTT and a slower, + steady-state update of GRTT. When congestion control is disabled and + NORM GRTT probing is enabled ((NORM_PROBE_ACTIVE + or NORM_PROBE_PASSIVE) the + intervalMin and + intervalMax values also determine the rate at + which NORM_CMD(CC) probes are transmitted by the + sender. Thus by setting larger values for + intervalMin and + intervalMax, the NORM sender application can + reduce the overhead of the GRTT measurement process. However, this + also reduces the ability of NORM to adapt to changes in GRTT. + + The default NORM GRTT intervalMin and + intervalMax values, i.e., when this call is + not made, are 1.0 second and + 30.0 seconds, respectively. + + + + Return Values + + This function has no return values. + + + + + NormSetBackoffFactor() + + + Synopsis + + #include <normApi.h> + +void NormSetBackoffFactor(NormSessionHandle sessionHandle, + double backoffFactor); + + + + Description + + This function sets the sender's "backoff factor" for the given + sessionHandle. The + backoffFactor (in units of seconds) is used + to scale various timeouts related to the NACK repair process. The + sender advertises its backoffFactor setting + to the receiver group in NORM protocol message headers. The default + backoffFactor for NORM sessions is + 4.0 seconds. The + backoffFactor is used to determine the + maximum time that receivers may delay NACK transmissions (and other + feedback messages) as part of NORM's probabilistic feedback + suppression technique. For example, the maximum NACK delay time is + backoffFactor*GRTT. Thus a + large backoffFactor value introduces latency into + the NORM repair process. However, a small backoffFactor value causes + feedback suppression to be less effective and increases the risk of + feedback implosion for large receiver group sizes. + + The default setting of 4.0 provides + reasonable feedback suppression for moderate to large group sizes + when multicast feedback is possible. The NORM specification + recommends a backoffFactor value of + 6.0 when unicast feedback is used. However, for + demanding applications (with respect to repair latency) when group + sizes are modest, a small (even 0.0) + backoffFactor value can be specified to reduce + the latency of reliable data delivery. + + + + Return Values + + This function has no return values. + + + + + NormSetGroupSize() + + + Synopsis + + #include <normApi.h> + +void NormSetGroupSize(NormSessionHandle sessionHandle, + unsigned int groupSize); + + + + Description + + This function sets the sender's estimate of receiver group + size for the given sessionHandle. The sender + advertises its groupSize setting to the + receiver group in NORM protocol message headers that, in turn, use + this information to shape the distribution curve of their random + timeouts for the timer-based, probabilistic feedback suppression + technique used in the NORM protocol. Note that the + groupSize estimate does not have to be very + accurate and values within an order of magnitude of the actual group + size tend to produce acceptable performance. + + The default groupSize setting in NORM + is 1,000 and thus can work well for a wide + range of actual receiver group sizes. The penalty of an overly large + estimate is statistically a little more latency in reliable data + delivery with respect to the round trip time and some potential for + excess feedback. A substantial underestimation of + groupSize increases the risk of feedback + implosion. Currently, the NORM implementation does not attempt to + automatically measure groupSize from receiver + feedback. Applications could add their own mechanism for this + (perhaps keeping explicit track of group membership), or it is + possible that future versions of the NRL NORM implementation may + have some provision for automatic groupSize + estimation by the sender based on receiver feedback messages. + + + + Return Values + + This function has no return values. + + + + + NormSetTxRobustFactor() + + + Synopsis + + #include <normApi.h> + +void NormSetTxRobustFactor(NormSessionHandle sessionHandle, + int txRobustFactor); + + + + Description + + This routine sets the "robustness factor" used for various + NORM sender functions. These functions include the number of + repetitions of "robustly-transmitted" NORM sender commands such as + NORM_CMD(FLUSH) or similar + application-defined commands, and the number of attempts that are + made to collect positive acknowledgement from receivers. These + commands are distinct from the NORM reliable data transmission + process, but play a role in overall NORM protocol operation. The + default txRobustFactor value is + 20. This relatively large value makes the NORM + sender end-of-transmission flushing and positive acknowledgement + collection functions somewhat immune from packet loss. However, for + some applications, the default value may make the NORM protocol more + "chatty" than desired (particularly if flushing is invoked often). + In other situations where the network connectivity may be + intermittent or extremely lossy, it may be useful to actually + increase this value. The default value (20) is + expected to provide reasonable operation across a wide range of + network conditions and application types. Since this value is not + communicated among NORM participants as part of the protocol + operation, it is important that applications consistently set this + value among all applications participating in a NORM session. + + Setting txRobustFactor to a value of + -1 makes the redundant transmission of these + commands continue indefinitely until completion. For example, with + positive acknowledgement collection, the request process will + continue indefinitely until all recipients requested acknowledge or + the request is canceled by the application. Similarly, flushing + commands would be transmitted repeatedly until data transmission is + resumed. Typically, setting txRobustFactor to + -1 is not recommended. + + + + Return Values + + This function has no return values. + + + + + NormFileEnqueue() + + + Synopsis + + #include <normApi.h> + +NormObjectHandle NormFileEnqueue(NormSessionHandle sessionHandle, + const char* filename, + const char* infoPtr = NULL, + unsigned int infoLen = 0); + + + + Description + + This function enqueues a file for transmission within the + specified NORM sessionHandle. Note that NormStartSender() + must have been previously called before files or any transport + objects may be enqueued and transmitted. The + fileName parameter specifies the path to the + file to be transmitted. The NORM protocol engine read and writes + directly from/to file system storage for file transport, potentially + providing for a very large virtual "repair window" as needed for + some applications. While relative paths with respect to the "current + working directory" may be used, it is recommended that full paths be + used when possible. The optional infoPtr and + infoLen parameters are used to associate + NORM_INFO content with the sent transport object. + The maximum allowed infoLen corresponds to + the segmentSize used in the prior call to + NormStartSender(). + The use and interpretation of the NORM_INFO + content is left to the application's discretion. Example usage of + NORM_INFO content for + NORM_OBJECT_FILE might include file name, + creation date, MIME-type or other information which will enable NORM + receivers to properly handle the file when reception is + complete. + + The application is allowed to enqueue multiple transmit + objects within in the "transmit cache" bounds (see NormSetTxCacheBounds()) + and enqueued objects are transmitted (and repaired as needed) within + the limits determined by automated congestion control (see NormSetCongestionControl()) + or fixed rate (see NormSetTxRate()) + parameters. + + + + Return Values + + A NormObjectHandle + is returned which the application may use in other NORM API calls as + needed. This handle can be considered valid until the application + explicitly cancels the object's transmission (see NormObjectCancel()) + or a NORM_TX_OBJECT_PURGED event is received for + the given object. Note the application may use the NormObjectRetain() + method if it wishes to refer to the object after the + NORM_TX_OBJECT_PURGED notification. In this case, + the application, when finished with the object, must use NormObjectRelease() + to free any resources used or else a memory leak condition will + result. A value of NORM_OBJECT_INVALID is return + upon error. Possible failure conditions include the specified + session is not operating as a NormSender, insufficient memory + resources were available, or the "transmit cache" limits have been + reached and all previously enqueued NORM transmit objects are + pending transmission. Also the call will fail if the + infoLen parameter exceeds the local + NormSender segmentSize + limit. + + + + + NormDataEnqueue() + + + Synopsis + + #include <normApi.h> + +NormObjectHandle NormDataEnqueue(NormSessionHandle sessionHandle, + const char* dataPtr, + unsigned int dataLen, + const char* infoPtr = NULL, + unsigned int infoLen = 0); + + + + Description + + This function enqueues a segment of application memory space + for transmission within the specified NORM + sessionHandle. Note that NormStartSender() + MUST have been previously called before files or any transport + objects may be enqueued and transmitted. The + dataPtr parameter must be a valid pointer to + the area of application memory to be transmitted and the + dataLen parameter indicates the quantity of + data to transmit. The NORM protocol engine read and writes directly + from/to application memory space so it is important that the + application does not modify (or deallocate) the memory space during + the time the NORM protocol engine may access this area. After + calling NormDataEnqueue() + for a specific application "dataPtr" memory space, the application + MUST NOT deallocate (or change the contents of) that memory space + until a NORM_TX_OBJECT_PURGED notification is + received for the given object or the application itself explicitly + cancels the object's transmission (see NormObjectCancel()). + + The optional infoPtr and + infoLen parameters are used to associate + NORM_INFO content with the sent transport object. + The maximum allowed infoLen corresponds to + the segmentSize used in the prior call to + NormStartSender(). + The use and interpretation of the NORM_INFO + content is left to the application's discretion. Example usage of + NORM_INFO content for + NORM_OBJECT_DATA might include + application-defined data typing or other information which will + enable NORM receiver applications to properly interpret the received + data when reception is complete. Of course, it is possible that the + application may embed such typing information in the object data + content itself. This is left to the application's discretion. + + The application is allowed to enqueue multiple transmit + objects within in the "transmit cache" bounds (see NormSetTxCacheBounds()) + and enqueued objects are transmitted (and repaired as needed) within + the limits determined by automated congestion control (see NormSetCongestionControl()) + or fixed rate (see NormSetTxRate()) + parameters. + + + + Return Values + + A NormObjectHandle + is returned which the application may use in other NORM API calls as + needed. This handle can be considered valid until the application + explicitly cancels the object's transmission (see NormObjectCancel()) + or a NORM_TX_OBJECT_PURGED event is received for + the given object. Note the application may use the NormObjectRetain() + method if it wishes to refer to the object after the + NORM_TX_OBJECT_PURGED notification. In this case, + the application, when finished with the object, must use NormObjectRelease() + to free any resources used or else a memory leak condition will + result. A value of NORM_OBJECT_INVALID is return + upon error. Possible failure conditions include the specified + session is not operating as a NormSender, insufficient memory + resources were available, or the "transmit cache" limits have been + reached and all previously enqueued NORM transmit objects are + pending transmission. Also the call will fail if the + infoLen parameter exceeds the local + NormSender segmentSize + limit. + + + + + NormRequeueObject() + + + Synopsis + + #include <normApi.h> + +boolNormRequeueObject(NormSessionHandle sessionHandle, + NormObjectHandle objectHandle); + + + + Description + + This function allows the application to resend (or reset + transmission of) a NORM_OBJECT_FILE or + NORM_OBJECT_DATA transmit object that was + previously enqueued for the indicated + sessionHandle. This function is useful for + applications sending to silent (non-NACKing) receivers as it enables + the receivers to take advantage of multiple retransmissions of + objects (including any auto-parity set, see NormSetAutoParity()) + to more robustly receive content. The + objectHandle parameter must be a valid + transmit NormObjectHandle + that has not yet been "purged" from the sender's transmit queue. + Upon success, the specified object will be fully retransmitted using + the same NORM object transport identifier as was used on its initial + transmission. This call may be made at any time to restart + transmission of a previously-enqueued object, but the + NORM_TX_OBJECT_SENT or + NORM_TX_FLUSH_COMPLETED notifications can serve + as good cues for an appropriate time to resend an object. If + multiple objects are re-queued, they will be resent in order of + their initial enqueueing. + + The transmit cache bounds set by NormSetTxCacheBounds() + determine the number of previously-sent objects retained in the + sender's transmit queue and that are thus eligible to be requeued + for retransmission. An object may be requeued via this call multiple + times, but each distinct requeue should be done after an indication + such as NORM_TX_OBJECT_SENT or + NORM_TX_FLUSH_COMPLETED for the given object. + Otherwise, the object will simply be reset from its current + transmission point to transmit from the beginning (i.e. restart). + Note that the object type NORM_OBJECT_STREAM + cannot currently be requeued. + + (TBD - should a "numRepeats" parameter be added to this + function?) + + + + Return Values + + A value of true is returned upon success + and a value of false is returned upon failure. + Possible reasons for failure include an invalid + objectHandle was provided (i.e. a + non-transmit object or transmit object that has been "purged" from + the transmit queue (see NORM_TX_OBJECT_PURGED)) + or the provided object was of type + NORM_OBJECT_STREAM. + + + + + NormStreamOpen() + + + Synopsis + + #include <normApi.h> + +NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle, + unsigned int bufferSize, + const char* infoPtr = NULL, + unsigned int infoLen = 0); + + + + Description + + This function opens a NORM_OBJECT_STREAM + sender object and enqueues it for transmission within the indicated + sessionHandle. NORM streams provide reliable, + in-order delivery of data content written to the stream by the + sender application. Note that no data is sent until subsequent calls + to NormStreamWrite() + are made unless NORM_INFO content is specified + for the stream with the infoPtr and + infoLen parameters. Example usage of + NORM_INFO content for + NORM_OBJECT_STREAM might include + application-defined data typing or other information which will + enable NORM receiver applications to properly interpret the received + stream as it is being received. The NORM protocol engine buffers + data written to the stream for original transmission and repair + transmissions as needed to achieve reliable transfer. The + bufferSize parameter controls the size of the + stream's "repair window" which limits how far back the sender will + "rewind" to satisfy receiver repair requests. + + NORM, as a NACK-oriented protocol, currently lacks a mechanism + for receivers to explicitly feedback flow control status to the + sender unless the sender application specifically leverages NORM's + optional positive-acknowledgement (ACK) features. Thus, the + bufferSize selection plays an important role + in reliable delivery of NORM stream content. Generally, a larger + bufferSize value is safer with respect to + reliability, but some applications may wish to limit how far the + sender rewinds to repair receivers with poor connectivity with + respect to the group at large. Such applications may set a smaller + bufferSize to avoid the potential for large + latency in data delivery (i.e. favor peak delivery latency over full + reliability). This may result in breaks in the reliable delivery of + stream data to some receivers, but this form of quasi-reliability + while limiting latency may be useful for some types of applications + (e.g. reliable real-time messaging, video or sensor or media data + transport). Note that NORM receivers can quickly, automatically + "resync" to the sender after such breaks if the application + leverages the application message boundary recovery features of NORM + (see NormStreamMarkEom()). + + Note that the current implementation of NORM is designed to + support only one active stream per session, and that any + NORM_OBJECT_DATA or + NORM_OBJECT_FILE objects enqueued for + transmission will not begin transmission until an active stream is + closed. Applications requiring multiple streams or concurrent + file/data transfer SHOULD generally instantiate multiple + NormSessions as needed. + + Note there is no corresponding "open" call for receiver + streams. Receiver NORM_OBJECT_STREAMs are + automatically opened by the NORM protocol engine and the receiver + applications is notified of new streams via the + NORM_RX_OBJECT_NEW notification (see NormGetNextEvent()). + + + + Return Values + + A NormObjectHandle + is returned which the application may use in other NORM API calls as + needed. This handle can be considered valid until the application + explicitly cancels the object's transmission (see NormObjectCancel()) + or a NORM_TX_OBJECT_PURGED event is received for + the given object. Note the application may use the NormObjectRetain() + method if it wishes to refer to the object after the + NORM_TX_OBJECT_PURGED notification. In this case, + the application, when finished with the object, must use NormObjectRelease() + to free any resources used or else a memory leak condition will + result. A value of NORM_OBJECT_INVALID is return + upon error. Possible failure conditions include the specified + session is not operating as a NormSender, + insufficient memory resources were available, or the "transmit + cache" bounds have been reached and all previously enqueued NORM + transmit objects are pending transmission. Also the call will fail + if the infoLen parameter exceeds the local + NormSender segmentSize + limit. + + + + + NormStreamClose() + + + Synopsis + + #include <normApi.h> + +void NormStreamClose(NormObjectHandle streamHandle, + bool graceful = false); + + + + Description + + This function halts transfer of the stream specified by the + streamHandle parameter and releases any + resources used unless the associated object has been explicitly + retained by a call to NormObjectRetain(). + No further calls to NormStreamWrite() + will be successful for the given + streamHandle. The optional graceful + parameter, when set to a value of true, may be used by NORM senders + to initiate "graceful" shutdown of a transmit stream. In this case, + the sender application will be notified that stream has (most + likely) completed reliable transfer via the + NORM_TX_OBJECT_PURGED notification upon + completion of the graceful shutdown process. When the + graceful option is set to + true, receivers are notified of the stream end + via an "stream end" stream control code in + NORM_DATA message and will receive a + NORM_RX_OBJECT_COMPLETED notification after all + received stream content has been read. Otherwise, the stream is + immediately terminated, regardless of receiver state. In this case, + this function is equivalent to the NormObjectCancel() + routine and may be used for sender or receiver streams. So, it is + expected this function (NormStreamClose()) + will typically be used for transmit streams by NORM senders. + + + + Return Values + + This function has no return values. + + + + + NormStreamWrite() + + + Synopsis + + #include <normApi.h> + +unsigned int NormStreamWrite(NormObjectHandle streamHandle + const char* buffer, + unsigned int numBytes); + + + + Description + + This function enqueues data for transmission within the NORM + stream specified by the streamHandle + parameter. The buffer parameter must be a + pointer to the data to be enqueued and the + numBytes parameter indicates the length of + the data content. Note this call does not block and will return + immediately. The return value indicates the number of bytes copied + from the provided buffer to the internal stream transmission + buffers. Calls to this function will be successful unless the + stream's transmit buffer space is fully occupied with data pending + original or repair transmission if the stream's "push mode" is set + to false (default, see NormStreamSetPushEnable() + for details). If the stream's "push mode" is set to true, a call to + NormStreamWrite() + will always result in copying of application data to the stream at + the cost of previously enqueued data pending transmission (original + or repair) being dropped by the NORM protocol engine. While NORM + NACK-based reliability does not provide explicit flow control, there + is some degree of implicit flow control in limiting writing new data + to the stream against pending repairs. Other flow control strategies + are possible using the NormSetWatermark() + function.NormSetWatermark() + function. + + The NormEvent values + NORM_TX_QUEUE_EMPTY and + NORM_TX_QUEUE_VACANCY are posted with the NormEvent::object + field set to a valid sender stream NormObjectHandle + to indicate when the stream is ready for writing via this function. + Note that the NORM_TX_QUEUE_VACANCY event type is + posted only after the stream's transmit buffer has been completely + filled. Thus, the application must make a call to NormStreamWrite() + that copies less than the requested numBytes + value (return value less than numBytes) + before additional NORM_TX_QUEUE_VACANCY events + are posted for the given streamHandle (i.e., + the event type is not re-posted until the application has again + filled the available stream transmit buffer space). By cueing off of + NORM_TX_QUEUE_EMPTY, the application can write + its "freshest" available data to the stream, but by cueing off of + NORM_TX_QUEUE_VACANCY, an application can keep + the NORM protocol engine busiest, to achieve the maximum possible + throughput at high data rates. + + + + Return Values + + This function returns the number of bytes of data successfully + enqueued for NORM stream transmission. If the underlying send stream + buffer is full, this function may return zero or a value less than + the requested numBytes. + + + + + NormStreamFlush() + + + Synopsis + + #include <normApi.h> + +void NormStreamFlush(NormObjectHandle streamHandle, + bool eom = false, + NormFlushMode flushMode = NORM_FLUSH_PASSIVE); + + + + Description + + This function causes an immediate "flush" of the transmit + stream specified by the streamHandle + parameter. Normally, unless NormStreamSetAutoFlush() + has been invoked, the NORM protocol engine buffers data written to a + stream until it has accumulated a sufficient quantity to generate a + NORM_DATA message with a full payload (as + designated by the segmentSize parameter of + the NormStartSender() + call). This results in most efficient operation with respect to + protocol overhead. However, for some NORM streams, the application + may not wish wait for such accumulation when critical data has been + written to a stream. The default stream "flush" operation invoked + via NormStreamFlush() + for flushMode equal to + NORM_FLUSH_PASSIVE causes NORM to immediately + transmit all enqueued data for the stream (subject to session + transmit rate limits), even if this results in + NORM_DATA messages with "small" payloads. If the + optional flushMode parameter is set to + NORM_FLUSH_ACTIVE, the application can achieve + reliable delivery of stream content up to the current write position + in an even more proactive fashion. In this case, the sender + additionally, actively transmits NORM_CMD(FLUSH) + messages after any enqueued stream content has been sent. This + immediately prompt receivers for repair requests which reduces + latency of reliable delivery, but at a cost of some additional + messaging. Note any such "active" flush activity will be terminated + upon the next subsequent write to the stream. If + flushMode is set to + NORM_FLUSH_NONE, this call has no effect other + than the optional end-of-message marking described here. + + The optional eom parameter, when set to + true, allows the sender application to mark an + end-of-message indication (see NormStreamMarkEom()) + for the stream and initiate flushing in a single function call. The + end-of-message indication causes NORM to embed the appropriate + message start byte offset in the NORM_DATA + message generated following a subsequent write to the stream with + the NORM_FLAGS_MSG_START flag. This mechanism + provide a means for automatic application message boundary recovery + when receivers join or re-sync to a sender mid-stream. + + Note that frequent flushing, particularly for + NORM_FLUSH_ACTIVE operation, may result in more + NORM protocol activity than usual, so care must be taken in + application design and deployment when scalability to large group + sizes is expected. + + + + Return Values + + This function has no return values. + + + + + NormStreamSetAutoFlush() + + + Synopsis + + #include <normApi.h> + +void NormStreamSetAutoFlush(NormObjectHandle streamHandle + NormFlushMode flushMode); + + + + Description + + This function sets "automated flushing" for the NORM transmit + stream indicated by the streamHandle + parameter. By default, a NORM transmit stream is "flushed" only when + explicitly requested by the application (see NormStreamFlush()). + However, to simplify programming, the NORM API allows that automated + flushing be enabled such that the "flush" operation occurs every + time the full requested buffer provided to a NormStreamWrite() + call is successfully enqueued. This may be appropriate for messaging + applications where the provided buffers corresponds to an + application messages requiring immediate, full transmission. This + may make the NORM protocol perhaps more "chatty" than its typical + "bulk transfer" form of operation, but can provide a useful + capability for some applications. + + Possible values for the flushMode + parameter include NORM_FLUSH_NONE, + NORM_FLUSH_PASSIVE, and + NORM_FLUSH_ACTIVE. The default setting for a NORM + stream is NORM_FLUSH_NONE where no flushing + occurs unless explicitly requested via NormStreamFlush(). + By setting the automated flushMode to + NORM_FLUSH_PASSIVE, the only action taken is to + immediately transmit any data that has been written to the stream, + even if "runt" NORM_DATA messages (with payloads + less than the NormSender + segmentSize parameter) are generated as a + result. If NORM_FLUSH_ACTIVE is specified, the + automated flushing operation is further augmented with the + additional transmission of + NORM_CMD(FLUSH) + messages to proactively excite the receiver group for repair + requests. + + + + Return Values + + This function has no return values. + + + + + NormStreamSetPushEnable() + + + Synopsis + + #include <normApi.h> + +void NormStreamSetPushEnable(NormObjectHandle streamHandle, + bool pushEnable); + + + + Description + + This function controls how the NORM API behaves when the + application attempts to enqueue new stream data for transmission + when the associated stream's transmit buffer is fully occupied with + data pending original or repair transmission. By default + (pushEnable = + false), a call to NormStreamWrite() + will return a zero value under this condition, indicating it was + unable to enqueue the new data. However, if + pushEnable is set to + true for a given + streamHandle, the NORM protocol engine will + discard the oldest buffered stream data (even if it is pending + repair transmission or has never been transmitted) as needed to + enqueue the new data. Thus a call to NormStreamWrite() + will never fail to copy data. This behavior may be desirable for + applications where it is more important to quickly delivery new data + than to reliably deliver older data written to a stream. The default + behavior for a newly opened stream corresponds to + pushEnable equals false. + This limits the rate to which an application can write new data to + the stream to the current transmission rate and status of the + reliable repair process. + + + + Return Values + + This function has no return values. + + + + + NormStreamHasVacancy() + + + Synopsis + + #include <normApi.h> + +bool NormStreamHasVacancy(NormObjectHandle streamHandle); + + + + Description + + This function can be used to query whether the transmit + stream, specified by the streamHandle + parameter, has buffer space available so that the application may + successfully make a call to NormStreamWrite(). + Normally, a call to NormStreamWrite() + itself can be used to make this determination, but this function can + be useful when "push mode" has been enabled (see the description of + the NormStreamSetPushEnable() + function) and the application wants to avoid overwriting data + previously written to the stream that has not yet been transmitted. + Note that when "push mode" is enabled, a call to NormStreamWrite() + will always succeed, overwriting previously-enqueued data if + necessary. Normally, this function will return true after a + NORM_TX_QUEUE_VACANCY notification has been + received for a given NORM stream object. + + + + Return Values + + This function returns a value of true + when there is transmit buffer space to which the application may + write and false otherwise. + + + + + NormStreamMarkEom() + + + Synopsis + + #include <normApi.h> + +void NormStreamMarkEom(NormObjectHandle streamHandle); + + + + Description + + This function allows the application to indicate to the NORM + protocol engine that the last data successfully written to the + stream indicated by streamHandle corresponded + to the end of an application-defined message boundary. The + end-of-message indication given here will cause the NORM protocol + engine to embed the appropriate message start byte offset in the + NORM_DATA message generated that contains the + data for the subsequent application call to NormStreamWrite(). Use + of this end-of-message marking enables NORM receivers to + automatically re-sync to application-defined message boundaries when + joining (or re-joining) a NORM session already in progress. + + + + Return Values + + This function has no return values. + + + + + NormSetWatermark() + + + Synopsis + + #include <normApi.h> + +bool NormSetWatermark(NormSessionHandle sessionHandle, + NormObjectHandle objectHandle, + bool overrideFlush = true); + + + + Description + + This function specifies a "watermark" transmission point at + which NORM sender protocol operation should perform a flushing + process and/or positive acknowledgment collection for a given + sessionHandle. For + NORM_OBJECT_FILE and + NORM_OBJECT_DATA transmissions, the positive + acknowledgement collection will begin when the specified object has + been completely transmitted. The objectHandle + parameter must be a valid handle to a previously-created sender + object (see NormFileEnqueue(), + NormDataEnqueue(), + or NormStreamOpen()). + For NORM_OBJECT_STREAM transmission, the positive + acknowledgment collection begins immediately, using the current + position (offset of most recent data written) of the sender stream + as a reference. + + The functions NormAddAckingNode() + and NormRemoveAckingNode() + are used to manage the list of NormNodeId values + corresponding to NORM receivers that are expected to explicitly + acknowledge the watermark flushing messages transmitted by the + sender. Note that the NormNodeId + NORM_NODE_NONE may be included in the list. + Inclusion of NORM_NODE_NONE forces the watermark + flushing process to proceed through a full + NORM_ROBUST_FACTOR number of rounds before + completing, prompting any receivers that have not completed reliable + reception to the given watermark point to NACK for any repair needs. + If NACKs occur, the flushing process is reset and repeated until + completing with no NACKs for data through the given watermark + transmission point are received. Thus, even without explicit + positive acknowledgment, the sender can use this process (by adding + NORM_NODE_NONE to the session's list of "acking + nodes") for a high level of assurance that the receiver set is + "happy" (completed reliable data reception) through the given object + (or stream transmission point). + + The event NORM_TX_WATERMARK_COMPLETED is + posted for the given session when the flushing process or positive + acknowledgment collection has completed. The process completes as + soon as all listed receivers have responded unless + NORM_NODE_NONE is included in the "acking node" + list. The sender application may use the function NormGetAckingStatus() + to determine the degree of success of the flushing process in + general or for individual NormNodeId + values. + + The flushing is conducted concurrently with ongoing data + transmission and does not impede the progress of reliable data + transfer. Thus the sender may still enqueue + NormObjects for transmission (or write to the + existing stream) and the positive acknowledgement collection and + flushing procedure will be multiplexed with the ongoing data + transmission. However, the sender application may wish to defer from + or limit itself in sending more data until a + NORM_TX_WATERMARK_COMPLETED event is received for + the given session. This provides a form of sender->receiver(s) + flow control which does not exist in NORM's default protocol + operation. If a subsequent call is made to NormSetWatermark() + before the current acknowledgement request has completed, the + pending acknowledgment request is canceled and the new one + begins. + + The optional overrideFlush parameter, + when set to true, causes the watermark + acknowledgment process that is established with this function call + to potentially fully supersede the usual NORM end-of-transmission + flushing process that occurs. If + overrideFlush is set and the "watermark" + transmission point corresponds to the last transmission that will + result from data enqueued by the sending application, then the + watermark flush completion will terminate the usual flushing + process. I.e., if positive acknowledgement of watermark is received + from the full "acking node list", then no further flushing is + conducted. Thus, the overrideFlush parameter + should only be set when the "acking node list" contains a complete + list of intended recipients. This is useful for small receiver + groups (or unicast operation) to reduce the "chattiness" of NORM's + default end-of-transmission flush process. Note that once the + watermark flush is completed and further data enqueued and + transmitted, the normal default end-of-transmission behavior will be + resumed unless another "watermark" is set with + overrideFlush enabled. Thus, as long as new + watermarks are established by successive use of this API call, this + effectively "morphs" NORM into a protocol driven by positive + acknowledgement behavior. + + + + Return Values + + The function returns true upon successful + establishment of the watermark point. The function may return + false upon failure. + + + + + NormCancelWatermark() + + + Synopsis + + #include <normApi.h> + +bool NormCancelWatermark(NormSessionHandle sessionHandle); + + + + Description + + This function cancels any "watermark" acknowledgement request + that was previously set via the NormSetWatermark() + function for the given sessionHandle. The + status of any NORM receivers that may have acknowledged prior to + cancellation can be queried using the NormGetAckingStatus() + function even after NormCancelWatermark() + is called. Typically, applications should wait until a event has + been posted, but in some special cases it may be useful to terminate + the acknowledgement collection process early. + + + + Return Values + + The function has no return values. + + + + + NormAddAckingNode() + + + Synopsis + + #include <normApi.h> + +bool NormAddAckingNode(NormSessionHandle sessionHandle, + NormNodeId nodeId); + + + + Description + + When this function is called, the specified + nodeId is added to the list of NormNodeId values + (i.e., the "acking node" list) used when NORM sender operation + performs positive acknowledgement (ACK) collection for the specified + sessionHandle. The optional NORM positive + acknowledgement collection occurs when a specified transmission + point (see NormSetWatermark()) + is reached or for specialized protocol actions such as + positively-acknowledged application-defined commands. + + Additionally the special value of + nodeId equal to + NORM_NODE_NONE may be set to force the watermark + flushing process through a full + NORM_ROBUST_FACTOR number of rounds regardless of + actual acking nodes. Otherwise the flushing process is terminated + when all of the nodes in the acking node list have responded. + Setting a "watermark" and forcing a full flush process with the + special NORM_NODE_NONE value of + nodeId enables the resultant + NORM_TX_WATERMARK_COMPLETED notification to be a + indicator with high (but not absolute) assurance that the receiver + set has completed reliable reception of content up through the + "watermark" transmission point. This provides a form of scalable + reliable multicast "flow control" for NACK-based operation without + requiring explicit positive acknowledgement from all group members. + Note that the use of the NORM_NODE_NONE value may + be mixed with other nodeId for a mix of + positive acknowledgement collection from some nodes and a measure of + assurance for the group at large. + + + + Return Values + + The function returns true upon success + and false upon failure. The only failure + condition is that insufficient memory resources were available. If a + specific nodeId is added more than once, this + has no effect. + + + + + NormRemoveAckingNode() + + + Synopsis + + #include <normApi.h> + +void NormRemoveAckingNode(NormSessionHandle sessionHandle, + NormNodeId nodeId); + + + + Description + + This function deletes the specified + nodeId from the list of NormNodeId values + used when NORM sender operation performs positive acknowledgement + (ACK) collection for the specified + sessionHandle. Note that if the special + nodeId value + "NORM_NODE_NONE" has been added to the list, it + too must be explicitly removed to change the watermark flushing + behavior if desired. + + + + Return Values + + The function has no return values. + + + + + NormGetNextAckingNode() + + + Synopsis + + #include <normApi.h> + +NormNodeId NormGetNextAckingNode(NormSessionHandle session, bool reset = false); + + + + Description + + This function iteratively retrieves the NormNodeId values in + the "acking node" list maintained by a NORM sender (see NormAddAckingNode()) + for the given sessionHandle. If the optional + reset parameter is set to a value of + true, the first NormNodeId value in + the list is returned and subsequent calls to NormGetNextAckingNode()with + the reset parameter set to its default + false value will iteratively return the + remaining NormNodeId values + contained in the list. A value of + NORM_NODE_NONE is returned when the end of the + list is reached. + + The "acking node" list is populated with application calls to + NormAddAckingNode() + or auto-populated if that optional behavior is set for a + NormSession. Note that this API does not enable the programmer to + check if the NORM_NODE_NONE value itself is + contained in the list. The programmer should keep track of that by + other means. + + The following code example illustrates how to use this call to + iterate through the set of stored NormNodeId values and + get the current "acking status" for each: + + NormNodeId nextNodeId = NormGetNextAckingNode(session, true); +while(NORM_NODE_NONE != nextNodeId) +{ + NormAckingStatus ackingStatus = NormGetAckingStatus(session, nextNodeId); + printf("ACKing node id = %lu acking status = %d\n", nextNodeId, (int)ackingStatus); +}As noted below, a good time to check the acking status of + the receiver set is after a + NORM_TX_WATERMARK_COMPLETED notification has + occurred. + + + + Return Values + + The function iteratively returns NormNodeId values + from the given session's local sender "acking node" list. A value of + NORM_NODE_NONE is returned when the end of the + list is reached. + + + + + NormGetAckingStatus() + + + Synopsis + + #include <normApi.h> + +NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, + NormNodeId nodeId = NORM_NODE_ANY); + + + + Description + + This function queries the status of the watermark flushing + process and/or positive acknowledgment collection initiated by a + prior call to NormSetWatermark() + for the given sessionHandle. In general, it + is expected that applications will invoke this function after the + corresponding NORM_TX_WATERMARK_COMPLETED event + has been posted. Setting the default parameter value + nodeId = NORM_NODE_ANY + returns a "status" indication for the overall process. Also, + individual nodeId values may be queried using + the NormNodeId + values of receivers that were included in previous calls to NormAddAckingNode() + to populate the sender session's acking node list. + + If the flushing/acknowledgment process is being used for + application flow control, the sender application may wish to reset + the watermark and flushing process (using NormSetWatermark()) + if the response indicates that some nodes have failed to respond. + However, note that the flushing/acknowledgment process itself does + elicit NACKs from receivers as needed and is interrupted and reset + by any repair response that occurs. Thus, even by the time the + flushing process has completed (and + NORM_TX_WATERMARK_COMPLETED is posted) once, this + is an indication that the NORM protocol has made a valiant attempt + to deliver the content. Resetting the watermark process can increase + robustness, but it may be in vain to repeat this process multiple + times when likely network connectivity has been lost or expected + receivers have failed (dropped out, shut down, etc). + + + + Return Values + + Possible return values include: + + + + + + NORM_ACK_INVALID + + The given sessionHandle + is invalid or the given nodeId is not + in the sender's acking list. + + + + NORM_ACK_FAILURE + + The positive acknowledgement collection process + did not receive acknowledgment from every listed receiver + (nodeId = + NORM_NODE_ANY) or the identified + nodeId did not + respond. + + + + NORM_ACK_PENDING + + The flushing process at large has not yet + completed (nodeId = + NORM_NODE_ANY) or the given individual + nodeId is still being queried for + response. + + + + NORM_ACK_SUCCESS + + All receivers (nodeId = + NORM_NODE_ANY) responded with positive + acknowledgement or the given specific + nodeId did + acknowledge. + + + + + + + + + NormSendCommand() + + + Synopsis + + #include <normApi.h> + +bool NormSendCommand(NormSessionHandle session, + const char* cmdBuffer, + unsigned int cmdLength, + bool robust = false); + + + + Description + + This function enqueues a NORM application-defined command for + transmission. The cmdBuffer parameter points + to a buffer containing the application-defined command content that + will be contained in the NORM_CMD(APPLICATION) + message payload. The cmdLength indicates the + length of this content (in bytes) and MUST be less than or equal to + the segmentLength value for the given + session (see NormStartSender()). + The NORM command transmission will be multiplexed with any NORM data + transmission. The command is NOT delivered reliably, but can be + optionally transmitted with repetition (once per GRTT) according to + the NORM transmit robust factor value (see NormSetTxRobustFactor()) + for the given session if the robust parameter + is set to true. The command transmission is + subject to any congestion control or set rate limits for the NORM + session. Once the command has been transmitted (with repetition if + robust is set to true), + a NORM_TX_CMD_SENT notification is issued. An + application can only enqueue a single command + at a time (i.e. the NORM_TX_CMD_SENT + notification must occur before another command can be sent). The + NormCancelCommand() + call is available to terminate command transmission if needed. Note + that if a rapid succession of commands are sent it is possible that + the commands may be delivered to the receivers out-of-order. Also, + when repetition is requested (i.e., if robust + is set to true) the receiver may receive + duplicate copies of the same command. It is up to the application to + provide any needed mechanism for detecting and/or filtering + duplicate command reception. + + The application-defined command feature allows NORM + applications to provide some out-of-band (with respect to reliable + data delivery) signaling to support session management or other + functions. The reception of these "atomic" commands is relatively + stateless (as compared to reliable data delivery) and thus it is + possible for many senders within a group to send commands without + extreme resource burden on receivers (i.e. other participants). + Again, this "light-weight" signaling mechanism may be used to + provide ancillary communication for the group. In the future, an + additional API mechanism will be provided to support + application-defined positive acknowledgement requests that could + conceivably be used to help guarantee command delivery if + desired. + + + + Return Values + + The function returns true upon success. + The function may fail, returning false, if the + session is not set for sender operation (see NormStartSender()), + the cmdLength exceeds the configured session + segmentLength, or a previously-enqueued + command has not yet been sent. + + + + + NormCancelCommand() + + + Synopsis + + #include <normApi.h> + +void NormCancelCommand(NormSessionHandle session); + + + + Description + + This function terminates any pending + NORM_CMD(APPLICATION) transmission that was + previously initiated with the NormSendCommand() + call. Due to the asynchrony of the NORM protocol engine thread and + the application, it is possible that the command may have been + already sent but the NormCancelCommand() + call will ensure a NORM_TX_CMD_SENT + notification is not issued for that prior + command. + + The application-defined command feature allows NORM + applications to provide some out-of-band (with respect to reliable + data delivery) signaling to support session management or other + functions. The reception of these "atomic" commands is relatively + stateless (as compared to reliable data delivery) and thus it is + possible for many senders within a group to send commands without + extreme resource burden on receivers (i.e. other participants). + Again, this "light-weight" signaling mechanism may be used to + provide ancillary communication for the group. In the future, an + additional API mechanism will be provided to support + application-defined positive acknowledgement requests that could + conceivably be used to help guarantee command delivery if + desired. + + + + Return Values + + The function has not return value. + + + + + + NORM Receiver Functions + + + NormStartReceiver() + + + Synopsis + + #include <normApi.h> + +bool NormStartReceiver(NormSessionHandle sessionHandle, + unsigned long bufferSpace); + + + + Description + + This function initiates the application's participation as a + receiver within the NormSession identified by + the sessionHandle parameter. The NORM + protocol engine will begin providing the application with + receiver-related NormEvent + notifications, and, unless NormSetSilentReceiver(true) + is invoked, respond to senders with appropriate protocol messages. + The bufferSpace parameter is used to set a + limit on the amount of bufferSpace allocated + by the receiver per active NormSender within + the session. The appropriate bufferSpace to + use is a function of expected network delay*bandwidth product and + packet loss characteristics. A discussion of trade-offs associated + with NORM transmit and receiver buffer space selection is provided + later in this document. An insufficient + bufferSpace allocation will result in + potentially inefficient protocol operation, even though reliable + operation may be maintained. In some cases of a large + delay*bandwidth product and/or severe packet loss, a small + bufferSpace allocation (coupled with the lack + of explicit flow control in NORM) may result in the receiver + "re-syncing" to the sender, resulting in "outages" in the reliable + transmissions from a sender (this is analogous to a TCP connection + timeout failure). + + + + Return Values + + A value of true is returned upon success + and false upon failure. The reasons failure may + occur include limited system resources or that the network sockets + required for session communication failed to open or properly + configure. + + + + + NormStopReceiver() + + + Synopsis + + #include <normApi.h> + +void NormStopReceiver(NormSessionHandle sessionHandle, + unsigned int gracePeriod = 0); + + + + Description + + This function ends the application's participation as a + receiver in the NormSession specified by the + session parameter. By default, all receiver-related protocol + activity is immediately halted and all receiver-related resources + are freed (except for those which have been specifically retained + (see NormNodeRetain() + and NormObjectRetain()). + However, and optional gracePeriod parameter + is provided to allow the receiver an opportunity to inform the group + of its intention. This is applicable when the local receiving + NormNode has been designated as an active + congestion control representative (i.e. current limiting receiver + (CLR) or potential limiting receiver (PLR)). In this case, a + non-zero gracePeriod value provides an + opportunity for the receiver to respond to the applicable sender(s) + so the sender will not expect further congestion control feedback + from this receiver. The gracePeriod integer + value is used as a multiplier with the largest sender GRTT to + determine the actual time period for which the receiver will linger + in the group to provide such feedback (i.e. "graceTime" = + (gracePeriod * GRTT)). During this + time, the receiver will not generate any requests for repair or + other protocol actions aside from response to applicable congestion + control probes. When the receiver is removed from the current list + of receivers in the sender congestion control probe messages (or the + gracePeriod expires, whichever comes first), + the NORM protocol engine will post a + NORM_LOCAL_RECEIVER_CLOSED event for the + applicable session, and related resources are then freed. + + + + Return Values + + This function has no return values. + + + + + NormSetRxCacheLimit() + + + Synopsis + + #include <normApi.h> + +void NormSetRxCacheLimit(NormSessionHandle sessionHandle, + unsigned short countMax); + + + + Description + + This function sets a limit on the number of outstanding + (pending) NormObjects for which a receiver will + keep state on a per-sender basis. Note that the value + countMax sets a limit on the maximum + consecutive range of objects that can be pending. The default value + (when this function is not called) of + countMax is 256. This + should be sufficient for most bulk transfer usage, but if small + object sizes (e.g. small NORM_OBJECT_DATA + messages) are being transferred, it may be useful to raise this + limit in cases of high transmission speeds or large + <delay*bandwidth, loss> network conditions. If the receiver + cache limit is set too small (i.e. for high speed or large + <delay*bandwidth> operation), the receiver may not maintain + reliable reception or impact session throughput when flow control is + enabled (see NormSetFlowControl()). + The maximum allowed value of countMax is + 16,384. + + If this value is changed after NormStartReceiver() + has been called, it will only affect newly-detected remote senders, + so this should typically be called before NORM receiver operation is + initiated. + + + + Return Values + + This function has no return value. + + + + + NormSetRxSocketBuffer() + + + Synopsis + + #include <normApi.h> + +bool NormSetRxSocketBuffer(NormSessionHandle sessionHandle, + unsigned int bufferSize); + + + + Description + + This function allows the application to set an alternative, + non-default buffer size for the UDP socket used by the specified + NORM sessionHandle for packet reception. This + may be necessary for high speed NORM sessions where the UDP receive + socket buffer becomes a bottleneck when the NORM protocol engine + (which is running as a user-space process) doesn't get to service + the receive socket quickly enough resulting in packet loss when the + socket buffer overflows. The bufferSize + parameter specifies the socket buffer size in bytes. Different + operating systems and sometimes system configurations allow + different ranges of socket buffer sizes to be set. Note that a call + to NormStartReceiver() + (or NormStartSender()) + must have been previously made for this call to succeed (i.e., the + socket must be already open). + + + + Return Values + + This function returns true upon success + and false upon failure. Possible reasons for + failure include, 1) the specified session is not valid, 2) that NORM + "receiver" (or "sender") operation has not yet been started for the + given session, or 3) an invalid bufferSize + specification was given. + + + + + NormSetSilentReceiver() + + + Synopsis + + #include <normApi.h> + +void NormSetSilentReceiver(NormSessionHandle sessionHandle, + bool silent, + INT32 maxDelay = -1); + + + + Description + + This function provides the option to configure a NORM receiver + application as a "silent receiver". This mode of receiver operation + dictates that the host does not generate any protocol messages while + operating as a receiver within the specified + sessionHandle. Setting the + silent parameter to true + enables silent receiver operation while setting it to + false results in normal protocol operation + where feedback is provided as needed for reliability and protocol + operation. Silent receivers are dependent upon proactive FEC + transmission (see NormSetAutoParity()) + or using repair information requested by other non-silent receivers + within the group to achieve reliable transfers. + + The optional maxDelay parameter is most + applicable for reception of the + NORM_OBJECT_STREAM type. The default value of + maxDelay = + -1 corresponds to normal operation where source + data segments for incompletely-received FEC coding blocks (or + transport objects) are passed to the application only when imposed + buffer constraints (either the NORM_OBJECT_STREAM + buffer size (see NormStreamOpen()) + or the FEC receive buffer limit (see NormStartReceiver()) + require. Thus, the default behavior (maxDelay + = -1), causes the receiver + to buffer received FEC code blocks for as long as possible (within + buffer constraints as newer data arrives) before allowing the + application to read the data. Hence, the receive latency (delay) can + be quite long depending upon buffer size settings, transmission + rate, etc. When the maxDelay parameter is set + to a non-negative value, the value determines the maximum number of + FEC coding blocks (according to a NORM sender's current transmit + position) the receiver will cache an incompletely-received FEC block + before giving the application the (incomplete) set of received + source segments. For example, a value of + maxDelay = + 0 will provide the receive application with any + data from the previous FEC block as soon as a subsequent FEC block + is begun reception. However, this provide no protection against the + possibility of out-of-order delivery of packets by the network. + Therefore, if lower latency operation is desired when using silent + receivers, a minimum maxDelay value of + 1 is recommended. For + NORM_OBJECT_FILE and + NORM_OBJECT_DATA, the only impact of a + non-negative maxDelay value is that previous + transport objects will be immediately aborted when subsequent object + begin reception. Thus, it is not usually recommended to apply a + non-negative maxDelay value when + NORM_OBJECT_STREAM is not being used. + + + + Return Values + + This function has no return values. + + + + + NormSetDefaultUnicastNack() + + + Synopsis + + #include <normApi.h> + +void NormSetDefaultUnicastNack(NormSessionHandle sessionHandle, + bool enable); + + + + Description + + This function controls the default behavior determining the + destination of receiver feedback messages generated while + participating in the session. If the enable + parameter is true, "unicast NACKing" is enabled for new remote + senders while it is disabled for state equal to false. The NACKing + behavior for current remote senders is not affected. When "unicast + NACKing" is disabled (default), NACK messages are sent to the + session address (usually a multicast address) and port, but when + "unicast NACKing" is enabled, receiver feedback messages are sent to + the unicast address (and port) based on the source address of sender + messages received. For unicast NORM sessions, it is recommended that + "unicast NACKing" be enabled. Note that receiver feedback messages + subject to potential "unicast NACKing" include NACK-messages as well + as some ACK messages such as congestion control feedback. Explicitly + solicited ACK messages, such as those used to satisfy sender + watermark acknowledgement requests (see NormSetWatermark()) + are always unicast to the applicable sender. (TBD - + provide API option so that all messages are multicast.) + The default session-wide behavior for unicast NACKing can be + overridden via the NormNodeSetUnicastNack() + function for individual remote senders. + + + + Return Values + + This function has no return values. + + + + + NormNodeSetUnicastNack() + + + Synopsis + + #include <normApi.h> + +void NormNodeSetUnicastNack(NormNodeHandle senderNode, + bool enable); + + + + Description + + This function controls the destination address of receiver + feedback messages generated in response to a specific remote NORM + sender corresponding to the senderNode + parameter. If enable is + true, "unicast NACKing" is enabled while it is + disabled for enable equal to + false. See the description of NormSetDefaultUnicastNack() + for details on "unicast NACKing" behavior. + + + + Return Values + + This function has no return values. + + + + + NormSetDefaultSyncPolicy() + + + Synopsis + + #include <normApi.h> + +void NormSetDefaultSyncPolicy(NormSessionHandle sessionHandle, + NormSyncPolicy syncPolicy); + + + + Description + + This function sets the default "synchronization policy" used + when beginning (or restarting) reception of objects from a remote + sender (i.e., "syncing" to the sender) for the given + sessionHandle. The "synchronization policy" + is the behavior observed by the receiver with regards to what + objects it attempts to reliably receive (via transmissions of + Negative Acknowledgements to the sender(s) or group as needed). + There are currently two synchronization policy types defined: + + + + + + NORM_SYNC_CURRENT + + Attempt reception of "current" and new objects + only. (default) + + + + NORM_SYNC_ALL + + Attempt recovery and reliable reception of all + objects held in sender transmit object cache and newer + objects. + + + + + + The behavior of a receiver using the default + NORM_SYNC_CURRENT policy is to attempt reliable + reception only for the first received "current" and newer (with + respect to the ordinal NORM object transport identifiers used by the + protocol) objects from a given NORM sender. Additionally, reliable + reception is only attempted when receiving a non-repair + NORM_DATA message (or optionally a NORM positive + acknowledgement request) from the first forward + error correction (FEC) encoding block of the given object. This + somewhat conservative synchronization behavior helps prevent + late-joining (or otherwise "flaky" with respect to group membership) + receivers from penalizing other receivers in the group by causing + the sender to "rewind" and transmit older object content to satisfy + the late joiner instead of moving forward with transmission of new + content. For large scale, loosely-organized multicast applications, + the NORM_SYNC_CURRENT policy is typically + recommended. + + The NORM_SYNC_ALL policy allows newly + joining receivers much more aggressive behavior as they will + immediately NACK for all objects from the "current" object backwards + through the entire range of objects set by the NormSetRxCacheLimit() + function. This behavior depends upon the sender to issue an + appropriate NORM_CMD(SQUELCH) response (if + applicable) to align (i.e. "synchronize") the new receiver with its + current transmit object cache (similar to a "repair window"). This + synchronization behavior may be useful for unicast uses of NORM or + other applications where the group membership is more carefully + managed and it is important that all content (including older + content) is received. Note that the sender transmit cache bounds + (see NormSetTxCacheBounds()) + and the receiver receive cache limit (see NormSetRxCacheLimit()) + settings will limit how far back onto the sender transmission + history that transmitted objects can be reliably recovered from the + "current" transmission point when the receiver begins + reception. + + When this function is not invoked, the + NORM_SYNC_CURRENT behavior is observed as the + default receiver synchronization policy. This call SHOULD be made + before NormStartReceiver() + is called. + + + + Return Values + + This function has no return values. + + + + + NormSetDefaultNackingMode() + + + Synopsis + + #include <normApi.h> + +void NormSetDefaultNackingMode(NormSessionHandle sessionHandle, + NormNackingMode nackingMode); + + + + Description + + This function sets the default "nacking mode" used when + receiving objects for the given + sessionHandle. This allows the receiver + application some control of its degree of participation in the + repair process. By limiting receivers to only request repair of + objects in which they are really interested in receiving, some + overall savings in unnecessary network loading might be realized for + some applications and users. Available nacking modes include: + + + + + + NORM_NACK_NONE + + Do not transmit any repair requests for the + newly received object. + + + + NORM_NACK_INFO_ONLY + + Transmit repair requests for + NORM_INFO content only as + needed. + + + + NORM_NACK_NORMAL + + Transmit repair requests for entire object as + needed. + + + + + + This function specifies the default behavior with respect to + any new sender or object. This default behavior may be overridden + for specific sender nodes or specific object using NormNodeSetNackingMode() + or NormObjectSetNackingMode(), + respectively. The receiver application's use of + NORM_NACK_NONE essentially disables a guarantee + of reliable reception, although the receiver may still take + advantage of sender repair transmissions in response to other + receivers' requests. When the sender provides, + NORM_INFO content for transmitted objects, the + NORM_NACK_INFO_ONLY mode may allows the receiver + to reliably receive object context information from which it may + choose to "upgrade" its nackingMode for the + specific object via the NormObjectSetNackingMode() + call. Similarly, the receiver may changes its default + nackingMode with respect to specific senders + via the NormNodeSetNackingMode() + call. The default "default nackingMode" when + this call is not made is NORM_NACK_NORMAL. + + + + Return Values + + This function has no return values. + + + + + NormNodeSetNackingMode() + + + Synopsis + + #include <normApi.h> + +void NormNodeSetNackingMode(NormNodeHandle nodeHandle, + NormNackingMode nackingMode); + + + + Description + + This function sets the default "nacking mode" used for + receiving new objects from a specific sender as identified by the + nodeHandle parameter. This overrides the + default nackingMode set for the receive + session. See NormSetDefaultNackingMode() + for a description of possible nackingMode + parameter values and other related information. + + + + Return Values + + This function has no return values. + + + + + NormObjectSetNackingMode() + + + Synopsis + + #include <normApi.h> + +void NormObjectSetNackingMode(NormObjectHandle objectHandle, + NormNackingMode nackingMode); + + + + Description + + This function sets the "nacking mode" used for receiving a + specific transport object as identified by the + objectHandle parameter. This overrides the + default nacking mode set for the applicable sender node. See NormSetDefaultNackingMode() + for a description of possible nackingMode + parameter values and other related information. + + + + Return Values + + This function has no return values. + + + + + NormSetDefaultRepairBoundary() + + + Synopsis + + #include <normApi.h> + +void NormSetDefaultRepairBoundary(NormSessionHandle sessionHandle, + NormRepairBoundary repairBoundary); + + + + Description + + This function allows the receiver application to customize, + for a given sessionHandle, at what points the + receiver initiates the NORM NACK repair process during protocol + operation. Normally, the NORM receiver initiates NACKing for repairs + at the FEC code block and transport object boundaries. For smaller + block sizes, the NACK repair process is often/quickly initiated and + the repair of an object will occur, as needed, during the + transmission of the object. This default operation corresponds to + repairBoundary equal to + NORM_BOUNDARY_BLOCK. Using this function, the + application may alternatively, setting + repairBoundary equal to + NORM_BOUNDARY_OBJECT, cause the protocol to defer + NACK process initiation until the current transport object has been + completely transmitted. + + + + Return Values + + This function has no return values. + + + + + NormNodeSetRepairBoundary() + + + Synopsis + + #include <normApi.h> + +void NormNodeSetRepairBoundary(NormNodeHandle nodeHandle, + NormRepairBoundary repairBoundary); + + + + Description + + This function allows the receiver application to customize, + for the specific remote sender referenced by the + nodeHandle parameter, at what points the + receiver initiates the NORM NACK repair process during protocol + operation. See the description of NormSetDefaultRepairBoundary() + for further details on the impact of setting the NORM receiver + repair boundary and possible values for the + repairBoundary parameter. + + + + Return Values + + This function has no return values. + + + + + NormSetDefaultRxRobustFactor() + + + Synopsis + + #include <normApi.h> + +void NormSetDefaultRxRobustFactor(NormSessionHandle sessionHandle, + int rxRobustFactor); + + + + Description + + This routine controls how persistently NORM receivers will + maintain state for sender(s) and continue to request repairs from + the sender(s) even when packet reception has ceased. The + rxRobustFactor value determines how many + times a NORM receiver will self-initiate NACKing (repair requests) + upon cessation of packet reception from a sender. The default value + is 20. Setting + rxRobustFactor to -1 + will make the NORM receiver infinitely persistent (i.e., it will + continue to NACK indefinitely as long as it is missing data + content). It is important to note that the NormSetTxRobustFactor() + also affects receiver operation in setting the time interval that is + used to gauge that sender packet transmission has ceased (i.e., the + sender inactivity timeout). This "timeout" interval is a equal of + (2 * GRTT * + txRobustFactor). Thus the overall + timeout before a NORM receiver quits NACKing is + (rxRobustFactor * + 2 * GRTT * + txRobustFactor). + + The NormNodeSetRxRobustFactor() + function can be used to control this behavior on a per-sender basis. + When a new remote sender is detected, the default + rxRobustFactor set here is used. Again, the + default value is 20. + + + + Return Values + + This function has no return values. + + + + + NormNodeSetRxRobustFactor() + + + Synopsis + + #include <normApi.h> + +void NormNodeSetRxRobustFactor(NormNodeHandle nodeHandle, + int rxRobustFactor); + + + + Description + + This routine sets the rxRobustFactor as + described in NormSetDefaultRxRobustFactor() + for an individual remote sender identified by the + nodeHandle parameter. See the description of + NormSetDefaultRxRobustFactor() + for details + + + + Return Values + + This function has no return values. + + + + + NormStreamRead() + + + Synopsis + + #include <normApi.h> + +bool NormStreamRead(NormObjectHandle streamHandle, + char* buffer + unsigned int* numBytes); + + + + Description + + This function can be used by the receiver application to read + any available data from an incoming NORM stream. NORM receiver + applications "learn" of available NORM streams via + NORM_RX_OBJECT_NEW notification events. The + streamHandle parameter here must correspond + to a valid NormObjectHandle + value provided during such a prior + NORM_RX_OBJECT_NEW notification. The + buffer parameter must be a pointer to an + array where the received data can be stored of a length as + referenced by the numBytes pointer. On + successful completion, the numBytes storage + will be modified to indicate the actual number of bytes copied into + the provided buffer. If the + numBytes storage is modified to a zero value, + this indicates that no stream data was currently available for + reading. + + Note that NormStreamRead() + is never a blocking call and only returns failure + (false) when a break in the integrity of the + received stream occurs. The NORM_RX_OBJECT_UPDATE + provides an indication to when there is stream data available for + reading. When such notification occurs, the application should + repeatedly read from the stream until the + numBytes storage is set to zero, even if a + false value is returned. Additional + NORM_RX_OBJECT_UPDATE notifications might not be + posted until the application has read all available data. + + + + Return Values + + This function normally returns a value of + true. However, if a break in the integrity of + the reliable received stream occurs (or the stream has been ended by + the sender), a value of false is returned to + indicate the break. Unless the stream has been ended (and the + receiver application will receive + NORM_RX_OBJECT_COMPLETED notification for the + stream in that case), the application may continue to read from the + stream as the NORM protocol will automatically "resync" to streams, + even if network conditions are sufficiently poor that breaks in + reliability occur. If such a "break" and "resync" occurs, the + application may be able to leverage other NORM API calls such as + NormStreamSeekMsgStart() + or NormStreamGetReadOffset() + if needed to recover its alignment with received stream content. + This depends upon the nature of the application and its stream + content. + + + + + NormStreamSeekMsgStart() + + + Synopsis + + #include <normApi.h> + +bool NormStreamSeekMsgStart(NormObjectHandle streamHandle); + + + + Description + + This function advances the read offset of the receive stream + referenced by the streamHandle parameter to + align with the next available message boundary. Message boundaries + are defined by the sender application using the NormStreamMarkEom() + call. Note that any received data prior to the next message boundary + is discarded by the NORM protocol engine and is not available to the + application (i.e., there is currently no "rewind" function for a + NORM stream). Also note this call cannot be used to skip messages. + Once a valid message boundary is found, the application must read + from the stream using NormStreamRead() + to further advance the read offset. The current offset (in bytes) + for the stream can be retrieved via NormStreamGetReadOffset(). + + + + Return Values + + This function returns a value of true + when start-of-message is found. The next call to NormStreamRead() + will retrieve data aligned with the message start. If no new message + boundary is found in the buffered receive data for the stream, the + function returns a value of false. In this + case, the application should defer repeating a call to this function + until a subsequent NORM_RX_OBJECT_UPDATE + notification is posted. + + + + + NormStreamGetReadOffset() + + + Synopsis + + #include <normApi.h> + +unsigned long NormStreamGetReadOffset(NormObjectHandle streamHandle); + + + + Description + + This function retrieves the current read offset value for the + receive stream indicated by the streamHandle + parameter. Note that for very long-lived streams, this value may + wrap. Thus, in general, applications should not be highly dependent + upon the stream offset, but this feature may be valuable for certain + applications which associate some application context with stream + position. + + + + Return Values + + This function returns the current read offset in bytes. The + return value is undefined for sender streams. There is no error + result. + + + + + + NORM Object Functions + + The functions described in this section may be used for sender or + receiver purposes to manage transmission and reception of NORM transport + objects. In most cases, the receiver will be the typical user of these + functions to retrieve additional information on newly-received objects. + All of these functions require a valid NormObjectHandle + argument which specifies the applicable object. Note that NormObjectHandle + values obtained from a NormEvent notification may + be considered valid only until a subsequent call to NormGetNextEvent(), + unless explicitly retained by the application (see NormObjectRetain()). + NormObjectHandle + values obtained as a result of NormFileEnqueue(), + NormDataEnqueue(), + or NormStreamOpen() + calls can be considered valid only until a corresponding + NORM_TX_OBJECT_PURGED notification is posted or the + object is dequeued using NormObjectCancel(), + unless, again, otherwise explicitly retained (see NormObjectRetain()). + + + NormObjectGetType() + + + Synopsis + + #include <normApi.h> + +NormObjectType NormObjectGetType(NormObjectHandle objectHandle); + + + + Description + + This function can be used to determine the object type + ((NORM_OBJECT_DAT, + NORM_OBJECT_FILE, or + NORM_OBJECT_STREAM) for the NORM transport object + identified by the objectHandle parameter. The + objectHandle must refer to a current, valid + transport object. + + + + Return Values + + This function returns the NORM object type. Valid NORM object + types include NORM_OBJECT_DATA, + NORM_OBJECT_FILE, or + NORM_OBJECT_STREAM. A type value of + NORM_OBJECT_NONE will be returned for an + objectHandle value of + NORM_OBJECT_INVALID. + + + + + NormObjectHasInfo() + + + Synopsis + + #include <normApi.h> + +bool NormObjectHasInfo(NormObjectHandle objectHandle); + + + + Description + + This function can be used to determine if the sender has + associated any NORM_INFO content with the + transport object specified by the + objectHandle parameter. This can even be used + before the NORM_INFO content is delivered to the + receiver and a NORM_RX_OBJECT_INFO notification + is posted. + + + + Return Values + + A value of true is returned if + NORM_INFO is (or will be) available for the + specified transport object. A value of false is + returned otherwise. + + + + + NormObjectGetInfoLength() + + + Synopsis + + #include <normApi.h> + +unsigned short NormObjectGetInfoLength(NormObjectHandle objectHandle); + + + + Description + + This function can be used to determine the length of currently + available NORM_INFO content (if any) associated + with the transport object referenced by the + objectHandle parameter. + + + + Return Values + + The length of the NORM_INFO content, in + bytes, of currently available for the specified transport object is + returned. A value of 0 is returned if no + NORM_INFO content is currently available or + associated with the object. + + + + + NormObjectGetInfo() + + + Synopsis + + #include <normApi.h> + +unsigned short NormObjectGetInfo(NormObjectHandle objectHandle, + char* buffer, + unsigned short bufferLen); + + + + Description + + This function copies any NORM_INFO content + associated (by the sender application) with the transport object + specified by objectHandle into the provided + memory space referenced by the buffer parameter. The + bufferLen parameter indicates the length of + the buffer space in bytes. If the provided + bufferLen is less than the actual + NORM_INFO length, a partial copy will occur. The + actual length of NORM_INFO content available for + the specified object is returned. However, note that until a + NORM_RX_OBJECT_INFO notification is posted to the + receive application, no NORM_INFO content is + available and a zero result will be returned, even if + NORM_INFO content may be subsequently available. + The NormObjectHasInfo() + call can be used to determine if any NORM_INFO + content will ever be available for a specified transport object + (i.e., determine if the sender has associated any + NORM_INFO with the object in question). + + + + Return Values + + The actual length of currently available + NORM_INFO content for the specified transport + object is returned. This function can be used to determine the + length of NORM_INFO content for the object even + if a NULL buffer value and zero bufferLen is + provided. A zero value is returned if NORM_INFO + content has not yet been received (or is non-existent) for the + specified object. + + + + + NormObjectGetSize() + + + Synopsis + + #include <normApi.h> + +NormSize NormObjectGetSize(NormObjectHandle objectHandle); + + + + Description + + This function can be used to determine the size (in bytes) of + the transport object specified by the + objectHandle parameter. NORM can support + large object sizes for the NORM_OBJECT_FILE type, + so typically the NORM library is built with any necessary, related + macros defined such that operating system large file support is + enabled (e.g., "#define + _FILE_OFFSET_BITS + 64" or equivalent). The NormSize type is + defined accordingly, so the application should be built with the + same large file support configuration. + + For objects of type NORM_OBJECT_STREAM, the + size returned here corresponds to the stream buffer size set by the + sender application when opening the referenced stream object. + + + + Return Values + + A size of the data content of the specified object, in bytes, + is returned. Note that it may be possible that some objects have + zero data content, but do have NORM_INFO content + available. + + + + + NormObjectGetBytesPending() + + + Synopsis + + #include <normApi.h> + +NormSize NormObjectGetBytesPending(NormObjectHandle objectHandle); + + + + Description + + This function can be used to determine the progress of + reception of the NORM transport object identified by the + objectHandle parameter. This function + indicates the number of bytes that are pending reception (I.e., when + the object is completely received, "bytes pending" will equal ZERO). + This function is not necessarily applicable to objects of type + NORM_OBJECT_STREAM which do not have a finite + size. Note it is possible that this function might also be useful to + query the "transmit pending" status of sender objects, but it does + not account for pending FEC repair transmissions and thus may not + produce useful results for this purpose. + + + + Return Values + + A number of object source data bytes pending reception (or + transmission) is returned. + + + + + NormObjectCancel() + + + Synopsis + + #include <normApi.h> + +void NormObjectCancel(NormObjectHandle objectHandle); + + + + Description + + This function immediately cancels the transmission of a local + sender transport object or the reception of a specified object from + a remote sender as specified by the + objectHandle parameter. The + objectHandle must refer to a currently valid + NORM transport object. Any resources used by the transport object in + question are immediately freed unless the object has been otherwise + retained by the application via the NormObjectRetain() + call. Unless the application has retained the object in such + fashion, the object in question should be considered invalid and the + application must not again reference the + objectHandle after this call is made. + + If the canceled object is a sender object not completely + received by participating receivers, the receivers will be informed + of the object's cancellation via the NORM protocol + NORM_CMD(SQUELCH) message in response to any + NACKs requesting repair or retransmission of the applicable object. + In the case of receive objects, the NORM receiver will not make + further requests for repair of the indicated object, but + furthermore, will acknowledge the object as completed with respect + to any associated positive acknowledgement requests (see NormSetWatermark()). + + + + Return Values + + This function has no return value. + + + + + NormObjectRetain() + + + Synopsis + + #include <normApi.h> + +void NormObjectRetain(NormObjectHandle objectHandle); + + + + Description + + This function "retains" the + objectHandle and any state associated with it + for further use by the application even when the NORM protocol + engine may no longer require access to the associated transport + object. Normally, the application is guaranteed that a given NormObjectHandle + is valid only while it is being actively transported by NORM (i.e., + for sender objects, from the time an object is created by the + application until it is canceled by the application or purged (see + the NORM_TX_OBJECT_PURGED notification) by the + protocol engine, or, for receiver objects, from the time of the + object's NORM_RX_OBJECT_NEW notification until + its reception is canceled by the application or a + NORM_RX_OBJECT_COMPLETED or + NORM_RX_OBJECT_ABORTED notification is posted). + Note that an application may refer to a given object after any + related notification until the application makes a subsequent call + to NormGetNextEvent(). + + When the application makes a call to NormObjectRetain() + for a given objectHandle, the application may + use that objectHandle value in any NORM API + calls until the application makes a call to NormObjectRelease() + for the given object. Note that the application MUST make a + corresponding call to NormObjectRelease() + for each call it has made to NormObjectRetain() + in order to free any system resources (i.e., memory) used by that + object. Also note that retaining a receive object also automatically + retains any state associated with the NormNodeHandle + corresponding to the remote sender of that receive object so that + the application may use NORM node API calls for the value returned + by NormObjectGetSender() + as needed. + + + + Return Values + + This function has no return value. + + + + + NormObjectRelease() + + + Synopsis + + #include <normApi.h> + +void NormObjectRelease(NormObjectHandle objectHandle); + + + + Description + + This function complements the NormObjectRetain() + call by immediately freeing any resources associated with the given + objectHandle, assuming the underlying NORM + protocol engine no longer requires access to the corresponding + transport object. Note the NORM protocol engine retains/releases + state for associated objects for its own needs and thus it is very + unsafe for an application to call NormObjectRelease() + for an objectHandle for which it has not + previously explicitly retained via NormObjectRetain(). + + + + Return Values + + This function has no return value. + + + + + NormFileGetName() + + + Synopsis + + #include <normApi.h> + +bool NormFileGetName(NormObjectHandle objectHandle, + char* nameBuffer, + unsigned int bufferLen); + + + + Description + + This function copies the name, as a + NULL-terminated string, of the file object + specified by the objectHandle parameter into + the nameBuffer of length + bufferLen bytes provided by the application. + The objectHandle parameter must refer to a + valid NormObjectHandle + for an object of type NORM_OBJECT_FILE. If the + actual name is longer than the provided + bufferLen, a partial copy will occur. Note + that the file name consists of the entire path name of the specified + file object and the application should give consideration to + operating system file path lengths when providing the + nameBuffer. + + + + Return Values + + This function returns true upon success + and false upon failure. Possible failure + conditions include the objectHandle does not + refer to an object of type + NORM_OBJECT_FILE. + + + + + NormFileRename() + + + Synopsis + + #include <normApi.h> + +bool NormFileRename(NormObjectHandle objectHandle, + const char* fileName); + + + + Description + + This function renames the file used to store content for the + NORM_OBJECT_FILE transport object specified by + the objectHandle parameter. This allows + receiver applications to rename (or move) received files as needed. + NORM uses temporary file names for received files until the + application explicitly renames the file. For example, sender + applications may choose to use the NORM_INFO + content associated with a file object to provide name and/or typing + information to receivers. The fileName + parameter must be a NULL-terminated string + which should specify the full desired path name to be used. NORM + will attempt to create sub-directories as needed to satisfy the + request. Note that existing files of the same name may be + overwritten. + + + + Return Values + + This function returns true upon success and false upon + failure. Possible failure conditions include the case where the + objectHandle does not refer to an object of + type NORM_OBJECT_FILE and where NORM was unable + to successfully create any needed directories and/or the file + itself. + + + + + NormDataAccessData() + + + Synopsis + + #include <normApi.h> + +const char* NormDataAccessData(NormObjectHandle objectHandle); + + + + Description + + This function allows the application to access the data + storage area associated with a transport object of type + NORM_OBJECT_DATA. For example, the application + may use this function to copy the received data content for its own + use. Alternatively, the application may establish "ownership" for + the allocated memory space using the NormDataDetachData() + function if it is desired to avoid the copy. + + If the object specified by the + objectHandle parameter has no data content + (or is not of type NORM_OBJECT_DATA), a NULL + value may be returned. The application MUST NOT attempt to modify + the memory space used by NORM_OBJECT_DATA objects + during the time an associated objectHandle is + valid. The length of data storage area can be determined with a call + to NormObjectGetSize() + for the same objectHandle value. + + + + Return Values + + This function returns a pointer to the data storage area for + the specified transport object. A NULL value may be returned if the + object has no associated data content or is not of type + NORM_OBJECT_DATA. + + + + + NormDataDetachData() + + + Synopsis + + #include <normApi.h> + +char* NormDataDetachData(NormObjectHandle objectHandle); + + + + Description + + This function allows the application to disassociate data + storage allocated by the NORM protocol engine for a receive object + from the NORM_OBJECT_DATA transport object + specified by the objectHandle parameter. It + is important that this function is called after the NORM protocol + engine has indicated it is finished with the data object (i.e., + after a NORM_TX_OBJECT_PURGED, + NORM_RX_OBJECT_COMPLETED, or + NORM_RX_OBJECT_ABORTED notification event). But + the application must call NormDataDetachData() + before a call is made to NormObjectCancel() + or NormObjectRelease() + for the object if it plans to access the data content afterwards. + Otherwise, the NORM protocol engine will free the applicable memory + space when the associated NORM_OBJECT_DATA + transport object is deleted and the application will be unable to + access the received data unless it has previously copied the + content. + + Once the application has used this call to "detach" the data + content, it is the application's responsibility to subsequently free + the data storage space as needed. + + + + Return Values + + This function returns a pointer to the data storage area for + the specified transport object. A NULL value + may be returned if the object has no associated data content or is + not of type NORM_OBJECT_DATA. + + + + + NormObjectGetSender() + + + Synopsis + + #include <normApi.h> + +NormNodeHandle NormObjectGetSender(NormObjectHandle objectHandle); + + + + Description + + This function retrieves the NormNodeHandle + corresponding to the remote sender of the transport object + associated with the given objectHandle + parameter. Note that the returned NormNodeHandle + value is only valid for the same period that the + objectHandle is valid. The returned NormNodeHandle + may optionally be retained for further use by the application using + the NormNodeRetain() + function call. The returned value can be used in the NORM Node + Functions described later in this document. + + + + Return Values + + This function returns the NormNodeHandle + corresponding to the remote sender of the transport object + associated with the given objectHandle + parameter. A value of NORM_NODE_INVALID is + returned if the specified objectHandle + references a locally originated, sender object. + + + + + + NORM Node Functions + + The functions described in this section may be used for NORM + sender or receiver (most typically receiver) purposes to retrieve + additional information about a remote NormNode, + given a valid NormNodeHandle. Note + that, unless specifically retained (see NormNodeRetain()), a + NormNodeHandle + provided in a NormEvent notification + should be considered valid only until a subsequent call to NormGetNextEvent() + is made. NormNodeHandle values + retrieved using NormObjectGetSender() + can be considered valid for the same period of time as the corresponding + NormObjectHandle is + valid. + + + NormNodeGetId() + + + Synopsis + + #include <normApi.h> + +NormNodeId NormNodeGetId(NormNodeHandle nodeHandle); + + + + Description + + This function retrieves the NormNodeId identifier + for the remote participant referenced by the given + nodeHandle value. The NormNodeId is a + 32-bit value used within the NORM protocol to uniquely identify + participants within a NORM session. The participants identifiers are + assigned by the application or derived (by the NORM API code) from + the host computers default IP address. + + + + Return Values + + This function returns the NormNodeId value + associated with the specified nodeHandle. In + the case nodeHandle is equal to + NORM_NODE_INVALID, the return value will be + NORM_NODE_NONE. + + + + + NormNodeGetAddress() + + + Synopsis + + #include <normApi.h> + +bool NormNodeGetAddress(NormNodeHandle nodeHandle, + char* addrBuffer, + unsigned int* bufferLen, + unsigned short* port = NULL); + + + + Description + + This function retrieves the current network source address + detected for packets received from remote NORM sender referenced by + the nodeHandle parameter. The + addrBuffer must be a pointer to storage of + bufferLen bytes in length in which the + referenced sender node's address will be returned. Optionally, the + remote sender source port number (see NormSetTxPort()) + is also returned if the optional port pointer to storage parameter + is provided in the call. Note that in the case of Network Address + Translation (NAT) or other firewall activities, the source address + detected by the NORM receiver may not be the original address of the + original NORM sender. + + + + Return Values + + A value of true is returned upon success + and false upon failure. An invalid + nodeHandle parameter value would lead to such + failure. + + + + + NormNodeGetGrtt() + + + Synopsis + + #include <normApi.h> + +double NormNodeGetId(NormNodeHandle nodeHandle); + + + + Description + + This function retrieves the advertised estimate of group + round-trip timing (GRTT) for the remote sender referenced by the + given nodeHandle value. Newly-starting + senders that have been participating as a receiver within a group + may wish to use this function to provide a more accurate startup + estimate of GRTT (see NormSetGrttEstimate()) + prior to a call to NormStartSender(). + Applications may use this information for other purpose as well. + Note that the NORM_GRTT_UPDATED event is posted + (see NormGetNextEvent()) + by the NORM protocol engine to indicate when changes in the local + sender or remote senders' GRTT estimate occurs. + + + + Return Values + + This function returns the remote sender's advertised GRTT + estimate in units of seconds. A value of -1.0 + is returned upon failure. An invalid + nodeHandle parameter value will lead to such + failure. + + + + + NormNodeGetCommand() + + + Synopsis + + #include <normApi.h> + +bool NormNodeGetCommand(NormNodeHandle nodeHandle, + char* buffer, + unsigned int* buflen); + + + + Description + + This function retrieves the content of an application-defined + command that was received from a remote sender associated with the + given nodeHandle. This call should be made in + response to the NORM_RX_CMD_NEW notification. + This notification is issued for each command received. However the + application may use this call to poll for received commands if + desired. Additionally, the received command length can be "queried" + by setting the value referenced by the buflen + parameter to ZERO. Upon return, this value + referenced by the buflen parameter is + adjusted to reflect the command length. Then a subsequent call to + NormNodeGetCommand() + can be made with an appropriately-sized buffer to retrieve the + received command content. The command size will be less than or + equal to the NORM segment size configured for the given remote + sender. + + Note that if a rapid succession of commands are sent it is + possible that the commands may be delivered to the receivers + out-of-order. Also, when repetition is requested (i.e., if + robust is set to true) + the receiver may receive duplicate copies of the same command. It is + up to the application to provide any needed mechanism for detecting + and/or filtering duplicate command reception. + + + + Return Values + + This function returns true upon + successful retrieval of command content. A return value of + false indicates that either no command was + available or the provided buffer size (buflen + parameter) was inadequate. The value referenced by the + buflen parameter is adjusted to indicate the + actual command length (in bytes) upon return. + + + + + NormNodeFreeBuffers() + + + Synopsis + + #include <normApi.h> + +void NormNodeFreeBuffers(NormNodeHandle nodeHandle); + + + + Description + + This function releases memory resources that were allocated + for a remote sender. For example, the receiver application may wish + to free memory resources when receiving a + NORM_REMOTE_SENDER_INACTIVE notification for a + given remote sender when multiple senders may be providing content. + The NORM protocol engine allocates memory for reliable transport + buffering on a per sender basis according to the limit set in the + NormStartReceiver() + call. These buffering resources comprise the majority of the state + allocated for a given remote sender. For NORM applications with + possibly multiple senders active at different times, this function + can be used to manage to amount of memory allocated for reliable + reception. If a sender becomes "active" again after a call to NormNodeFreeBuffers(), + new memory resources will be allocated. Note that state for any + pending (uncompleted) objects will be dropped when this function is + called and the receiver may request retransmission and repair of + content if the sender once again becomes "active". The application + SHOULD call NormObjectCancel() + for any pending objects before calling NormNodeFreeBuffers() + if it wishes to never receive those pending objects. Alternatively, + a call to NormNodeDelete() + will completely eliminate all state for a given remote sender and, + if that sender becomes "active" again, it will be treated as a + completely new sender. + + + + Return Values + + This function has no return value. + + + + + NormNodeDelete() + + + Synopsis + + #include <normApi.h> + +void NormNodeDelete(NormNodeHandle nodeHandle); + + + + Description + + This function can be used by a NORM receiver application to + completely remove the state associated with a remote sender for the + given nodeHandle. For example, when a + NORM_REMOTE_SENDER_INACTIVE notification occurs + for a given sender, the application may wish to completely free + all associated resources. Note this is distinct + from the NormNodeFreeBuffers() + call where only the buffering resources are freed and other state + pertaining to the sender is kept. If the deleted sender again + becomes "active", it will be treated as a brand new sender. Unless + explicitly retained with a call to NormNodeRetain(), + the nodeHandle should be considered invalid + after this call is made. Additionally, any + NormObjectHandle values for pending objects from this + sender are also invalidated (unless otherwise retained), although + NORM_RX_OBJECT_ABORTED notifications may be + issued for those pending objects. + + + + Return Values + + This function has no return value. + + + + + NormNodeRetain() + + + Synopsis + + #include <normApi.h> + +void NormNodeRetain(NormNodeHandle nodeHandle); + + + + Description + + In the same manner as the NormObjectRetain() + function, this function allows the application to retain state + associated with a given nodeHandle value even + when the underlying NORM protocol engine might normally free the + associated state and thus invalidate the NormNodeHandle. + If the application uses this function, it must make a corresponding + call to NormNodeRelease() + when finished with the node information to avoid a memory leak + condition. NormNodeHandle + values (unless retained) are valid from the time of a + NORM_REMOTE_SENDER_NEW notification until a + complimentary NORM_REMOTE_SENDER_PURGED + notification. During that interval, the application will receive + NORM_REMOTE_SENDER_ACTIVE and + NORM_REMOTE_SENDER_INACTIVE notifications + according to the sender's message transmission activity within the + session. + + It is important to note that, if the NORM protocol engine + posts a NORM_REMOTE_SENDER_PURGED notification + for a given NormNodeHandle, + the NORM protocol engine could possibly, subsequently establish a + new, different NormNodeHandle + value for the same remote sender (i.e., one of equivalent NormNodeId) if it + again becomes active in the session. A new NormNodeHandle + may likely be established even if the application has retained the + previous NormNodeHandle + value. Therefore, to the application, it might appear that two + different senders with the same NormNodeId are + participating if these notifications are not carefully monitored. + This behavior is contingent upon how the application has configured + the NORM protocol engine to manage resources when there is potential + for a large number of remote senders within a session (related APIs + are TBD). For example, the application may wish to control which + specific remote senders for which it keeps state (or limit the + memory resources used for remote sender state, etc) and the NORM API + may be extended in the future to control this behavior. + + + + Return Values + + This function has no return value. + + + + + NormNodeRelease() + + + Synopsis + + #include <normApi.h> + +void NormNodeRelease(NormNodeHandle nodeHandle); + + + + Description + + In complement to the NormNodeRetain() + function, this API call releases the specified + nodeHandle so that the NORM protocol engine + may free associated resources as needed. Once this call is made, the + application should no longer reference the specified NormNodeHandle, + unless it is still valid. + + + + Return Values + + This function has no return value. + + + + + + NORM Debugging Functions + + This section describes some additional function calls that are + available to set debugging output options and control other aspects of + the NORM implementation. + + + NormSetDebugLevel() + + + Synopsis + + #include <normApi.h> + +void NormSetDebugLevel(unsigned int level); + + + + Description + + This function controls the verbosity of NORM debugging output. + Higher values of level result in more detailed output. The highest + level of debugging is 12. The debug output consists of text written + to STDOUT by default but may be directed to a log file using the + NormOpenDebugLog() + function. + + + + Return Values + + This function has no return value. + + + + + NormOpenDebugLog() + + + Synopsis + + #include <normApi.h> + +bool NormOpenDebugLog(NormInstanceHandle instance, const char* fileName); + + + + Description + + This function allows NORM debug output to be directed to a + file instead of the default STDERR. + + + + Return Values + + The function returns true on success. If + the specified file cannot be opened a value of + false is returned. + + + + + NormCloseDebugLog() + + + Synopsis + + #include <normApi.h> + +bool NormCloseDebugLog(NormInstanceHandle instance); + + + + Description + + TBD + + + + Return Values + + TBD + + + + + NormOpenDebugPipe() + + + Synopsis + + #include <normApi.h> + +bool NormOpenDebugPipe(NormInstanceHandle instance, const char* pipeName); + + + + Description + + TBD + + + + Return Values + + TBD + + + + + NormCloseDebugPipe() + + + Synopsis + + #include <normApi.h> + +bool NormCloseDebugPipe(NormInstanceHandle instance); + + + + Description + + TBD + + + + Return Values + + TBD + + + + +
diff --git a/NormLogo.gif b/doc/NormLogo.gif similarity index 100% rename from NormLogo.gif rename to doc/NormLogo.gif diff --git a/doc/NormUserGuide.pdf b/doc/NormUserGuide.pdf new file mode 100644 index 0000000000000000000000000000000000000000..44ecb04fb0c67271b215afe748da21fc2ff14e56 GIT binary patch literal 84288 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

)sPBHh6jDBIS#lY z7Sdcu`GgSvs#&iPra;ZH#uc$C{03DZWk3U-kkVH|A$5L3=xO#nk1wQ-4Vd&a`(6N| zP7`O)IH`0)gww!}$A?bPOauHVG!g z?0evS6lzH7XOQA37*QilM8OY^f2On-!rv)#kAQ_8{y~BV!-_hmfGE+#PXN32H2Yb= z7E|VKI0z7#;>mTu^r1-)&|l!BSB-pJHbq_m7g5V{c@$cL{z9RVfX7BQTQV*@E((Cu zNTc$C0QPrL%0f0uSs)yxZ4k(>IqpFWQRiux_$mD?WKrivn0z$H0q_D-;Fk=wz?j>^$U6I13HAP2H4lk>p<)TA%SU5b7s1m%ZX z7L-Cwc+goCoP|6pErlWq@59GTsI&x`qdCT5WKeiZ1fo;3PeCoIBaKDT8K7E<90z!H z + +

+ + <inlinemediaobject> + <imageobject> + <imagedata align="center" fileref="NormLogo.gif" scale="50" /> + </imageobject> + </inlinemediaobject> <emphasis>norm</emphasis> User's Guide + + (NORM Version 1.4b4) + + norm User's Guide + + + + Background + + This document describes the usage of a demonstration application + that uses the NACK-Oriented Reliable Multicast (NORM) transport protocol + for reliable transmission of files, and byte or message stream content. +  The name of the executable binary is "norm".  It + should be noted that this "demonstration application" applies a subset of + the capability of the NORM protocol.  Additionally, the current version of + this application does not use the NORM Application Programming Interface + (API) that is described in the "NORM Developer's Guide".  The current + norm demonstration application source code preceded + the development of the NORM API.  A future version of this demonstration + application will be created that uses the NORM API and will also serve as + a reference to NORM developers. + + The norm application supports the following + uses: + + + + One time or repeated tranmission/reception of a set of files or + directories + + + + Transmission/reception of a byte stream piped into the STDIN of + the sender application instance (Unix systems only) + + + + Transmission/reception of a "message" stream piped into the + STDIN of the sender application instance (Unix systems only) + + + + The norm command-line (and run-time remote + control interface) allow configuration of a large number of NORM protocol + parameters.  Again, note that while a considerable range of NORM protocol + functionality is available in the norm application, + it does not demonstrate the full set of NORM protocol capabilities.  The + distribution also includes the raft and + npc (NORM Pre-coder) applications that can be used as + "helpers" to the norm demonstration application for + various purposes. + + The NORM protocol is described in Internet Engineering Task Force + (IETF) Request For Comments (RFC) RFC 3940 and RFC 3941.  These are + experimental RFC standards.  These documents have been revised in recent + Internet-Drafts and it should be noted that the Naval Research Laboratory + (NRL) implementation of NORM that is represented here has been updated to + reflect the revised protocol. + + In addition to this demonstration application, NRL provides a NORM + protocol library with a well-defined API that it is suitable for + application development.  Additionally, the NRL source code distribution + supports building the NORM protocol as a component into + ns-2 and OPNET network simulation environments. +  Refer to the NRL NORM website <http://cs.itd.nrl.navy.mil/work/norm> + for these other components as well as up-to-date versions of this + demonstration application and documentation. + + + + Building <emphasis>norm</emphasis> + + The norm application can be built from the NRL + NORM source code distribution.  For several Unix-based operating systems, + "Makefiles" are provided to build the NORM protocol library and example + applications including this one.  For Win32 and WinCE systems, workspace + and project configuration files are provided for the Microsoft Visual C++ + development environment. + + + Unix + + To build the norm demonstration + application: + + + + Download and unpack the NORM source code tarball. + + + + cd norm/unix + + + + make –f Makefile.<operating system> + norm + + + + + + Win32 + + To build the norm demonstration + application: + + + + Download and unpack the NORM source code tarball + + + + Make sure your VC++ environment has the Microsoft "Platform + SDK" installed and is configured to use its header, library, and + executable files. + + + + Open the "norm.sln" (VC++ .Net), + "norm.dsw" (VC++ 6.0), or + "norm.vcw" (Embedded VC++) workspace + file. + + + + The "norm" project can be selected and + built.  The "norm.exe" file will be found in + the "norm/win32/norm/Release" directory. + + + + + + + Concepts of Operation + + The norm application supports several different + uses.  The most typical use is reliable multicast of files from a sender + to a set of receivers.  However, on Unix systems, the option is available + to pipe (via STDIN) live byte or "message" streams into the + norm sender application for transmission to the + receiver(s). + + The norm address command specifies the + destination address and port to which NORM protocol messages are + transmitted.  For multicast operation, senders and receivers must use a + common address and port number.  Unicast operation is also supported, but + some care must be taken with usage.  Typically, for unicast operation, + receivers should be configured with the unicastNacks option to ensure that + feedback messages are properly directed back to the appropriate + sender. + + NORM messages are sent in User Datagram Protocol (UDP) packets.  The + user must make sure that any firewall configuration allows transmission + and reception of UDP datagrams for norm to work + properly. + + Most of the NORM protocol parameters are set at the sender and the + NORM protocol advertises parameters to the receiver(s) in the headers of + NORM messages.  This allows for somewhat loosely coordinated multicast + operation.  Typically, it is expected that receivers will join the + applicable multicast group and begin listening ahead of time.  Then, the + sender(s) will transmit content to the group for reliable transfer.  For + NORM stream operation, it is important to note that the + norm demonstration application only supports a single + sender per multicast group. + + + File Transmission + + Receiver norm instances must use the + rxcachedirectory command to specify a file system directory that is used + to store received content.  Note that the post processing (see processor + command description) option of norm allows received + content to be processed and/or removed from this cache directory to a + permanent storage location if desired. + + The sendFile command is used for the norm + sender(s) to specify the file(s) and/or directories that should be + transmitted.  By default, the files are sent once.  Directories are + recursively scanned for files and those files are transmitted once. +  Note that zero-sized files are not transmitted.  The repeatcount and + rinterval (repeat interval) commands can be used to repeat transmission + of the file/directory set on a scheduled interval after completion of + prior transmission.  Additionally, the sender updatesOnly option can be + specified so that on repeated scan of the file/directory set, only files + that have been changed or added are transmitted.  This allows the option + for a "hot outbox" to be set up that is monitored by the + norm sender for transmission of files to the group. +  A simple multicast file sharing capability can be created in this + way. + + By default, files enqueued for transmission with the sendFile + command are transmitted immediately, one after the other, but the + interval command is available to pace the transmission of files.  This + can be used to allow time for post-processing at the receivers of + subsequent files before new files are sent. + + This is a synopsis of the most typically-used commands for file + transmission.  A number of other commands are available to customize the + norm file transmission behavior.  The reader is + encouraged to read the descriptions of the available commands given + later to understand the full range of options available. + + + + Stream Transmission + + Currently, this option is available only for Unix-based operating + systems.  Instead of transmitting files from the file system, the user + may pipe (via STDIN) content directly to the norm + sender application instance using the norm input or + minput commands..  At receivers, the received content is directed to a + descriptor set using the norm receiver output or + moutput command.  Two forms of stream transmission are available: + + + + raw, unformatted "byte" streams, and + + + + "message" streams.   + + + + The distinction between these two types is the presence of + explicit message boundaries.  The NORM protocol allows receivers to + automatically recover message boundaries that have been marked by the + NORM sender.  This is useful when receivers may join the NORM session + while it is already in progress or if there is intermittent network + connectivity.   + + The input and output commands resepectively set sender and + receiver "byte-stream" operation while the minput and moutput commands + similarly set "message-stream" operation.  It is expected that the + "message-stream" operation offers the most utility for most purposes. +  "Byte-stream" operation may be used if the content is something like + human-readable text, etc where distinct message boundaries may not be + important.  Again, note that norm receivers should + begin listening before the sender begins transmitting for most effective + uses of "byte-stream" operation.   + + For "message stream" operation, the norm + application presumes that the first two bytes of messages are the + message size (in bytes) in Big Endian (network) byte order.  The NRL + mgen (see <http://cs.itd.nrl.navy.mil/work/mgen>) + and raft applications can be used to provide messages to + norm in this format.  The mgen application can be + used to measure NORM message delivery performance for testing and + experiment purposes, while raft provides the ability to capture UDP + packet flows (e.g. Real-Time Protocol (RTP) video, etc) and reliably + "tunnel" the UDP messages through NORM transport.  At the receiver(s), + raft can be correspondingly used to reconstruct UDP datagrams from the + norm "message-stream" content.  The usage of raft + is described in Appendix A (TBD) of this document. + + + + General Properties Overview + + Most norm commands are for specifically + sender or receiver operation.  There are some commands that apply to + both.  These include the instance command that establishes a "remote + control" inter-process communication facility that can be used to pass + commands to instances of the norm program that are + already running.  Also, the debug, trace, and log commands are provided + to display and/or store debugging output from the + norm application and NORM protocol code.  The + txloss and rxloss commands are provided to invoke random dropping of + sent or received NORM protocol messages for testing purposes.  Other + commands, like address, ttl, loopback, txport, and interface control the + behavior of NORM UDP packet transmission and reception. + + + + Sender Properties Overview + + The norm sender configuration controls most + aspects of NORM protocol operation.  This includes transmission rate, + packet size, Forward Error Correction (FEC) configuration, etc.  The + rate command determines the norm sender + transmission rate in units of bits/second.  The segment command sets the + maximum size of NORM message payloads.  The block, parity, and auto + commands respectively set the number of user data segment per FEC block, + number of calculated parity segments per FEC block, and number of + proactively (automatically) transmitted parity segments per FEC block. +  The backoff command sets the maximuim number of round-trip time + intervals over which timer-based feedback suppression is scaled and the + grtt command sets the sender's initial estimate of round-trip time for + the group.  A detailed understanding of these various NORM protocol + parameters can be attained by reviewing the NORM protocol specification + documents and the "NORM Developer's Guide".   + + Another significant norm sender command is + the txbuffer command.  This sets the size of the NORM sender cache for + calculated parity segments and FEC block repair state.  For + norm stream operation, this command also determines + the size of the stream buffer.  The stream buffer size limits the + "repair window" when norm stream operation is used. +  A relatively large stream buffer size may be needed for high + (bandwidth*delay, packet loss) conditions.  Some other significant + commands applicable to norm stream operation + include the push and flush commands. + + Although NORM is a NACK-based protocol, it does support optional + collection of positive acknowledgement (ACK) from a subset of the + receiver group.  The norm ackingNodes and related + ackshot commands can be used to exercise this optional protocol + behavior. + + Finally, in addition to the fixed transmission rate operation set + with the rate command, norm also supports enabling + automated congestion control with the cc command.  The bounds of + congestion control rate adjustment can be optionally set with the limit + command. + + + + Receiver Properties Overview + + As mentioned, most of the NORM protocol behavior is controlled by + the sender, but there are some options that the receiver can exercise. +  The most significant of these is the ability to put the receiver in an + emission-controlled (EMCON), or "silent receiver" mode where no NACK or + other feedback messages are generated.  The silentClient command is + available for this purpose.  This is useful when using NORM for reliable + transport over unidirectional network connectivity (In this case, it is + expected the norm sender has been configured with + some amount of auto (proactive) FEC parity in its transmission to + overcome nominal packet loss).  For optional use with the + silentClient command, the lowDelay command is + available to expedite delivery of received content (even if for a + partially-received FEC block) to the application when subsequent FEC + coding blocks are received.  The default behavior would be for the + norm receiver to buffer partially-received content + as long as possible for possible repair in response to some other + NACKing (non-silent) receiver.  The lowDelay command overrides this + default behavior. + + The processor command is available to the + norm receiver to specify a application, command, or + script that is invoked upon successful completion of reception of files. +  The specified command is invoked with the received file name as the + last argument.  Users may employ this command to move received content + to a permanent storage location, display received content, or other + purposes (One could even cleverly control norm + receiver or other system operation in this way if desired).  A related + command is the saveAborts command that causes even incomplete files + (aborted) to be passed to the receiver post processor.  An example use + of this option would be if the files transmitted were pre-encoded with + the npc (Norm Pre-Coder) utility such that original file content can be + recovered from a partial npc-encoded file (See the npc User Guide for + details). + + + + + <emphasis>norm</emphasis> usage + + The norm application is launched from a + command-line interface (e.g. Unix or DOS shell).  Many of the + norm parameters have default values, but typically + the user will wish to set at least some of these differently than their + defaults.   + + A minimal example norm sender command-line + syntax is: + + norm addr <addr/port> sendFile + <fileName> + + The corresponding minimal norm receiver + command-line syntax would be: + + norm addr <addr/port> rxcachedir + /tmp + + The sender would begin sending the specified + <filename> at a transmit rate of 64 kbps.  The + receiver would receive the file and store it in the + "/tmp" directory. + + Typically, it is expected that the user would wish to set the + norm transmit rate or enable congestion control + operation.  The norm application was designed + principally for long-term participation in an IP multicast group with the + receiver application running all of the time, post-processing received + content as it arrived, and sender(s) transmitting content to the group + (e.g., using the "hot outbox" approach mentioned) as it was available. +   + + The norm receiver command-line syntax to + support this operation would be: + + norm addr <addr/port> rxcachedir /tmp processor + <postprocessor> + + The norm sender command-line syntax would be: + + norm addr <addr/port> rate <bps> sendFile + <outboxDirectory> repeat -1 updatesOnly + + The "repeat -1" would cause norm to scan the + <outboxDirectory> indefinitely for new file + content and transmit those files to the specified group address and port. +  Note the transmit rate is specified in units of bits/second. + + + Unicast Operation + + For unicast operation, the following usage is recommended: + + Receiver: norm addr 127.0.0.1/<port> unicastNacks + rxcache <cacheDirectory> … + + Sender: norm addr <rcvrAddr/port> sendFile + <filename> … + + The norm receiver address is really a "don't + care" value since feedback is transmitted to the sender's unicast source + address and port detected during packet reception. + + + + + Command Reference + + The following tables list the available norm + command-line options.  Note that if the instance command is used, many of + these commands may be issued to instances of the norm + application that are already running.  The tables are grouped by the + categories of "General", "Sender", and "Receiver" commands. + + Note that norm commands may be abbreviated on + the command-line if desired. + + + General Commands + + + + + + + + + + address + <addr>/<port> + + Designates session address and port number.  For + multicast operation, sender(s) and receiver(s) should use common + address and port parameters.  For unicast operation, the sender + must designate the intended receiver address and port and the + receiver must specify the same session port number.  The + receiver unicastNacks command should be used for unicast + operation.  IPv4 and IPv6 addresses are + supported. + + + + txport + <port> + + Specify a specific source port number for NORM + transmission.  This can also be set to equal the session port + (ie. rxport).  Default is system-assigned transmit port + number. + + + + ttl <value>      +   + + Designates session multicast time-to-live (hop + count).  The default value is 255. + + + + interface + <ifaceName> + + Sets the network interface for multicast packet + transmission/reception.  <ifaceName> is name or IP address + of a valid network interface.  Default is system default + multicast interface. + + + + loopback {on | + off} + + Optionally enables reception of norm's own + messages.  Useful for loopback testing of sender/receiver + configuration.  Default = "off". + + + + txrobustfactor + <value> + + Set the “robust factor” that determines the number + of times NORM protocol NORM_CMD(FLUSH) messages are sent at + end-of-transmission, how robustly positive acknowledgement + collection is conducted, and how other “robustly-transmitted” + (repeat-transmitted) sender control messages are managed. +  Higher “robust factor” values makes increases the assurance of + protocol success in the face of significant packet loss.  Lower + values can be used to make norm less + “chatty” but at the cost of reduced certainty that protocol + operation will succeed under all circumstances. Note that this + parameter needs to be consistently set at both the + norm senders and receivers.  Unlike many of + the other parameters, this value is not advertised by the sender + to the receivers in the NORM protocol message headers.  The + default value is 20.  This provides about 95% likelihood of + protocol success even with 50% packet loss.  This is based on + the probability that the receiver gets the sender + NORM_CMD(FLUSH) messages and the sender gets NACKs from the + receivers needed to complete reliable transfer.  The special + value of -1 will make norm indefinitely + perform the related protocol actions (sender flush transmission, + positive acknowledgement collection until success, etc).  This + is typically not recommended.Default = 20. + + + + instance + <instanceName> + + Specifies "name" of the first running instance.  If + a norm instance is already running with the + specified <instanceName> the commands given will be issued + to that already-running instance of norm. + + + + debug + <debugLevel> + + Sets verbosity of debug output.  Higher values are + more verbose.  The range is from 0-12.  The default debug level + is zero. + + + + trace {on | off} + + Enable/disable NORM protocol message trace in debug + output.  Message trace is timestamped logging of information for + every packet sent or received.  Default = "off". + + + + log + <filename> + + Directs debug output to specified + file. + + + + txloss + <percent> + + Sets percentage of messages to be transmitted that + are randomly dropped (for testing purposes).  Default = 0.0 + percent. + + + + rxloss + <percent> + + Sets percentage of received messages that are + randomly dropped (for testing purposes).  Default = 0.0 + percent. + + + + help + + Displays command set with short + descriptions. + + + + + + + + Sender Commands + + + + + + + + + + rate <rate in + bits/sec> + + Sets the sender maximum transmission rate.  All + sender transmissions (user data, repair, protocol messages) are + subject to this rate limit.  Default = 64 kbps. + + + + cc {on|off}      +   + + Enables/disables NORM TCP-friendly congestion + control operation.  When turned on, a rate-based congestion + control scheme allows fair sharing of the network with other + network flows.  When turned off, norm will + transmit at the transmit rate set by the rate command.  Default + = "off". + + + + limit + <rateMin:rateMax> + + Sets lower/upper bounds on transmit rate adjustment + when congestion control operation is enabled.  A value of -1.0 + indicates no limit.  Default rateMin/rateMax = + -1. + + + + segment + <bytes> + + Sets norm message payload size + (segment size) in bytes.  Default = 1024 bytes. + + + + block + <segments> + + Sets number of user (source) data segments per FEC + encoding block.  Default = 64. + + + + parity + <count> + + Sets number of parity segments calculated per FEC + encoding block.  Default = 32. + + + + auto + <count> + + Sets the number of parity segments that are + proactively (automatically) transmitted with each block of user + (source) data segments.  A non-zero count can provide for + robustness/reliability with no NACKing from receivers required. +  This value must be less than or equal to the value set with the + parity command.Default = 0. + + + + extra + <count> + + Instructs the sender to respond to repair requests + (NACKs) by sending <count> extra repair segments beyond + what the receiver(s) requested.  For experimental purposes. +  Default = 0. + + + + silentClient + + This informs the norm sender + that silent receivers will be used and it should redundantly + transmit NORM_INFO content at the end of each FEC coding + block. + + + + grtt + <seconds> + + Sets the norm sender's initial + estimate of group round-trip timing.  This value affects the + latency of the NORM repair process (and thus impacts buffer size + requirements).  Default = 0.5 seconds. + + + + backoff + <factor> + + Set the factor used to scale feedback suppression + backoff timeouts.  Small groups not concerned about feedback +  implosion may use small or zero values to minimize delay of + NORM repair process.  Default = 4. + + + + txbuffer + <bytes> + + Sets the norm sender transmit + buffer and stream buffer size, if applicable.  The transmit + buffer is used to cache calculated FEC parity segments and FEC + code block repair state.  The stream buffer size limits the + "repair window" for stream transmission (and hence maximum + possible latency).Default = 1Mbyte. + + + + txcachebounds + <countMin:countMax:sizeMax> + + This sets the “transmit cache bounds” that are used + to determine how many prior transmit objects for which the + norm sender maintains state.  This + essentially limits the “repair window size” that the NORM sender + observes has for responding to repair requests (NACKs) from + receivers.  The “transmit cache bounds” also SHOULD be set to be + compatible with any use of the requeue option described below + (i.e. the safest thing to do is set <countMin> here to a + value greater or equal to the number of files in the transmit + file/directory list, including the count of files in any + directories).The <countMin> value sets a + minimum number of transmit objects (files) for which the NORM + sender will keep repair state, regardless of the file sizes + while the <countMax> value sets a maximum object count + that the NORM sender will keep in its “repair window”.  The + <sizeMax> value in units of bytes) limits the repair + window according to sum total of the file sizes in the cache, + providing that state is kept for at least <countMin> + objects.  The <countMax> limit is most useful when the + file sizes are somewhat small (i.e. <sizeMax> is not + reached) and the user wishes to limit repairs of  “older” files + sent.  Note that <sizeMax> does not directly relate to + memory allocation since NORM recovers file data directly from + the file storage system as needed.An IMPORTANT + caveat here is that the current NRL NORM implementation has a + hard-coded limit that NORM receiver will keep state for a + maximum of 256 objects per sender. Thus, the value of setting + the txcachebounds count values greater than 256 is limited. This + limitation will be  fixed in an updated to the NORM code and + will be reflected here.Default: countMin =  8, + countMax = 256, sizeMax = 20 Mbyte + + + + gsize + <count> + + Sets the estimate of receiver group size used by + NORM for scaling time-based feedback suppression. + + + + sendFile + <path> + + Adds a file or directory to the + norm transmit file/directory list. +  Directories are recursively scanned for files.  Zero-sized + files are not transmitted. + + + + repeatcount + <count> + + Repeat scan or transmission of transmit + file/directory list set with sendFile command.  A <count> + of -1 means infinite repeats.   With each “repeat” pass through + the transmit file/directory, the files are sent with new + NormTransportId values and considered separate transmit objects + by the NORM protocol.  This is different than the requeue option + which causes the file(s) to be repeat transmitted with the same + NormTransportId.  Note these two different options can be used + together and the result is a “multiplicative” effect with regard + to the amount transmission that occurs.Default = + 0. + + + + rinterval + <seconds> + + Specifies a time delay between repeated scan or + transmission of transmit file/directory list.  Default = 2 + seconds. + + + + requeue + <count> + + Specifies the number of additional repeat + transmissions of the each file using the same NormTransportId + such that the multiple transmissions can be “stitched” together + by the receiver into a successful reception even if a single + transmission is unsuccessful (useful for “silent receiver” mode + along with the “auto” parity option).  This is distinct from the + repeatcount option in that the repeatcount option specifies how + many repeated passes through the transmit file/directory list + with files getting new NormTransportIds  and thus considered + separate NORM transmit objects. Note these two different options + can be used together and the result is a “multiplicative” effect + with regard to the amount transmission that occurs. A requeue + <count> value of  0 means that each file in the transmit + file/directory list is sent once as a distinct NORM transport + object (i.e. no requeue occurs). A requeue <count> value + of -1 indicates the files are requeued indefinitely (and thus + any configured “repeatcount” or “updatesOnly” options become + irrelevant).   Note that if the number of files in the transmit + file/directory list exceeds the txcachebounds limits, then the + “requeue” option will not work.  Thus, it is important to set + the txcachebounds accordingly to use the requeue option. Default + = 0  (disabled) + + + + updatesOnly + + Upon repeat transmission of the transmit + file/directory list, NORM will only transmit files which have + been added or updated since the previous transmission.  This, + along with the "repeat" and "rinterval" options can be used to + create a sort of "hot outbox" capability. + + + + oneshot + + Causes the norm sender + application to exit after the NORM TX_FLUSH_COMPLETED event at + the end of file list transmission.  By default, the + norm sender application will run + indefinitely. + + + + ackingNodes + <node1,node2,…> + + The comma-delimited list of NORM node identifiers + is used with NORM positive acknowledgement operation. +  Acknowledgment from the specified list of nodes is collected + for each transmitted file before sending subsequent files.  The + norm application uses its host's default IP + address for a "node id".  Default is no acking + nodes. + + + + ackshot + + The sender application exits after completing + positive acknowledgement collection. + + + + input {<device> | + STDIN} + + Sets norm sender "byte-stream" + operating mode using input from specified <device> path or + STDIN.  With STDIN, the STDOUT of another process may be piped + into the norm sender. +  (Unix-only). + + + + minput {<device> | + STDIN} + + Sets norm sender + "message-stream" operating mode using input from specified + <device> path or STDIN.  With STDIN, the STDOUT of another + process may be piped into the norm sender. +  "Messages" are expected to have a 2-byte, Big Endian message + size prefix.  (Unix-only). + + + + flush + <flushMode> + + Sets norm sender flush + behavior for "message-stream" operation.  Valid options include + "none", "passive", and "active".  With "none", no flushing is + invoked; stream transmission simply pauses when no input data is + available and norm always sends full + NORM_DATA messages according to the set <segmentSize>. +  With "passive" or "active" flushing enabled, the NORM stream is + flushed with each completed message and variable-sized NORM_DATA + messages may result.  With "active" flushing, the + NORM_CMD(FLUSH) message is actively transmitted when there is no + data available to transmit.  This makes NORM more "chatty" but + provides more robust, lower-latency reliability for stream + transmission.Default <flushMode> = "none". + + + + push + + When set, new input data is always written to the + NORM stream regardless of pending repair transmissions.  This + favors new application data transmission over repair of older + stream data.  Suitable for applications that can tolerate + "quasi-reliability" and desire low latency. + + + + + + + + Receiver Commands + + To invoke norm receiver operation, one of the + rxcachedir, output, or + moutput commands MUST be given on the + command-line. + + + + + + + + + + rxcachedir <path>      +   + + Sets the directory where received file content is + stored by the norm receiver.  This is a + required command for norm file + reception. + + + + processor + <command> + + Specifies command (and any options for that + command) for post-processing of received files.  The received + <filename> is appended as the last argument to the + specified command when invoked for each received file. +   + + + + saveAborts + + Causes incomplete (aborted) files that are + partially received to be saved and/or passed to the post + processor.  The default behavior is that incomplete, partially + received files are deleted. + + + + output {<device> | + STDOUT} + + Received "byte-stream" output is directed to the + specified <device> path or STDOUT. + + + + moutput {<device> | + STDOUT} + + Received "message-stream" output is directed to the + specified <device> path or STDOUT.  Output messages will + have a 2-byte, Big-Endian prefix indicating the message + size. + + + + unicastNacks + + Feedback messages are unicast back to detected + sender source address(es) instead of being sent to the + norm session address.  Default behavior is + feedback is sent to the session (usually multicast) address. +  This receiver option is RECOMMENDED for unicast + operation. + + + + silentClient + + In this mode, the receiver sends no feedback + messages and relies solely upon sender proactive (auto parity) + FEC content for reliable reception. + + + + lowDelay + + For use with silentClient operation.  Source data + for partially-received (incomplete) FEC coding blocks is + provided to the application immediately when subsequent FEC + blocks are received.  This minimizes delay of delivery of user + data to the application.  The default behavior is to buffer + partially-received FEC blocks for as long as possible in case + repair transmissions (due to other non-silent receivers) are + provided. + + + + rxrobustfactor + <value> + + This sets a “robust factor” value at receivers that + determines how persistently the receiver keeps state for remote + senders that are not currently, actively transmitting data. +  This also corresponds to the maximum number of times the + norm receiver will “self-initiate” NACKing + to such an inactive sender before giving up.  Unless rxpersist + is specified (see below), the receiver will also free memory + resources allocated for an inactive sender at this time. A + <value> of -1 causes the receiver to be “infinitely” + persistent.  The default value is 20.   + + + + rxpersist + + If this option is given, the receiver keeps full + state on remote senders indefinitely, even when they go + “inactive” (see above).  The default behavior when this is not + specified is for norm to free buffer memory + resources allocated for senders after a timeout based on the + txrobustfactor, rxrobustfactor, and measured + GRTT. + + + + rxbuffer + <bytes> + + Specifies the size of the norm + receiver buffer that is allocated on a per-sender basis.  This + buffer is used to cache partially-received FEC coding blocks and + associated object repair state.  An operating mode or network + connectivity with significant (bandwidth*delay, packet loss) may + necessitate larger rxbuffer settings to preserve protocol + efficiency.Default = 1 Mbyte. + + + + rxsockbuffer + <bytes> + + Sets the size of the UDP receive socket buffer used + for norm sockets.  An extremely high + transmission rate may require socket buffer settings above + normal system defaults. + + + + + + + + + Parameter Considerations + + (TBD) Discuss the considerations and trade-offs of NORM parameter + selection.  (e.g. FEC parameters, buffer sizes, etc).  Note some of these + issues are described in the NORM Developer's Guide and/or NORM Protocol + specifications. + + + + Example Usage + + The example command-line usages listed below assume that NRL's MGEN + packet generator is being used as a data source.  For more information + about MGEN, to include download and installation instructions, see + <http://cs.itd.nrl.navy.mil/work/mgen>. + + + + Message stream" transmission example(with MGEN sender): + + mgen event “on 1 sink dst 0.0.0.0/1 periodic [200 + 1252]” output /dev/null sink STDOUT | norm addr 224.1.1.1/5001 rate + 3000000 segment 1252 block 40 parity 16 auto 6 backoff 0 minput + STDIN + + + + "Message stream" reception example (with MGEN receiver): + + norm addr 224.1.1.1/5001 backoff 0 moutput STDOUT | + mgen source STDIN output mgenLog.drc + + + + File transmission: + + norm addr 224.1.1.2/5002 rate 5000000 send + <fileName> + + + + File reception: + + norm addr 224.1.1.2/5002 rxcachedir + /tmp + + + + + + + "raft" Usage + + + The NRL NORM source code distribution supports building the + "Reliable Application For Tunneling" (raft) utility + that can be used as a helper application with norm to + tunnel a UDP datagram flow over a reliable NORM "message stream" tunnel. +  The raft application can be configured to listen to + a UDP port, optionally joining an IP Multicast group, and output received + UDP payloads as "messages" to its STDOUT.  This, in turn, can be piped + into the STDIN of the norm application for + message-stream tranmission.  Additionally, on the + norm receiver side, raft can be + configured to accept messages from STDIN and re-encapsulate these as UDP + datagrams transmitted to a specifed destination address and port. + + + (TBD) Finish description of raft usage and provide example of + use with norm + + +
\ No newline at end of file diff --git a/npcUsage.pdf b/doc/npcUsage.pdf similarity index 100% rename from npcUsage.pdf rename to doc/npcUsage.pdf diff --git a/examples/README.TXT b/examples/README.TXT index 833e363..6344e43 100644 --- a/examples/README.TXT +++ b/examples/README.TXT @@ -1,33 +1,65 @@ - NORM API Examples - ================= +================= NORM API Examples ================= - This directory contains some purposely simplified examples of NORM - API usage. See comments in the source code files on how to build - these on Unix platforms. Eventually, "Makefiles" (and hopefully - "configure" scripts) will be provided as well as Visual C++ - project files for Win32 builds. +This directory contains some purposely simplified examples of NORM +API usage. See comments in the source code files on how to build +these on Unix platforms. Eventually, "Makefiles" (and hopefully +"waf" configure/build scripts) will be provided as well as Visual +C++ project files for Win32 builds. - The following example programs are included: +The following example programs are included: - - normFileSend.cpp - simple file transmission program. Sends one - file and exits. +(These two programs use a hard-coded multicast address and port, +but take a file name and directory name, respectively, as a +command-line argument to determine the file sent and the directory +to which receive files are stored.) - normFileRecv.cpp - simple file reception program. Receives one - file and exits. +normFileSend.cpp - simple file transmission program. Sends one + file and exits. - (These two programs use a hard-coded multicast address and port, - but take a file name and directory name, respectively, as a - command-line argument to determine the file sent and the - directory to which receive files are stored.) +normFileRecv.cpp - simple file reception program. Receives one + file and exits. + +normDataSend.cpp - simple NORM_OBJECT_DATA transmission program. + Sends a series of data objects of random size. + In this example, the NORM_INFO for the objects + is set with some text with some "info" about + the object. + +normDataRecv.cpp - simple NORM_OBJECT_DATA reception program. This + receives the data objects that "normDataSend" + transmits and validates their correctness, in + part based on the "info" embedded in the + NORM_INFO. + +java - There is a README.TXT in the java directory. + It explains the java examples, how to build + them, and how to run them. + +NOTES: + +Althought the normDataSend/Recv example use relatively small data +object sizes, the _intended_ use of NORM_OBJECT_DATA is for bulkier +content stored in application memory space. + +For "messaging" applications that use modestly small message +sizes, the NORM_OBJECT_STREAM transport option in NORM is likely +to provide more efficient service than NORM_OBJECT_DATA for small +objects due to the packet level Forward Error Correction (FEC) +based packet recovery mechanism that NORM uses. + +In the future, a similar "simple" example pair will be provided to +illustrate the NORM_OBJECT_STREAM form of transport. Meanwhile, +the "normTest.cpp" file in the "norm/src/common" directory +provides an example of the NORM API calls related to this. - Other examples will be added later including examples using the - "NormGetDescriptor()" function to allow NORM API events to be - multiplexed with other possible application events (e.g. other I/O - or Windows messages, etc). +Other examples will be added later including examples using the +"NormGetDescriptor()" function to allow NORM API events to be +multiplexed with other possible application events (e.g. other I/O +or Windows messages, etc). + +Brian Adamson + +20 August 2010 - ------------- Brian Adamson 9 - June 2005 - diff --git a/examples/java/NormFileRecv.java b/examples/java/NormFileRecv.java new file mode 100644 index 0000000..ed0cdd7 --- /dev/null +++ b/examples/java/NormFileRecv.java @@ -0,0 +1,58 @@ +import mil.navy.nrl.norm.NormEvent; +import mil.navy.nrl.norm.NormFile; +import mil.navy.nrl.norm.NormInstance; +import mil.navy.nrl.norm.NormNode; +import mil.navy.nrl.norm.NormObject; +import mil.navy.nrl.norm.NormSession; +import mil.navy.nrl.norm.enums.NormEventType; +import mil.navy.nrl.norm.enums.NormObjectType; + +/** + * Example code to receive a file from the NormFileSend example. + * + * @author Jason Rush + */ +public class NormFileRecv { + public static void main(String[] args) throws Throwable { + if (args.length != 1) { + System.err.println("Usage: NormFileRecv "); + return; + } + + NormInstance instance = new NormInstance(); + instance.setCacheDirectory(args[0]); + + NormSession session = instance.createSession("224.1.2.3", 6003, + NormNode.NORM_NODE_ANY); + session.startReceiver(1024 * 1024); + + NormEvent event; + while ((event = instance.getNextEvent()) != null) { + NormEventType eventType = event.getType(); + NormObject normObject = event.getObject(); + + System.out.println(eventType); + + switch (eventType) { + case NORM_RX_OBJECT_INFO: + byte[] info = normObject.getInfo(); + String infoStr = new String(info, "US-ASCII"); + System.out.println("Info: " + infoStr); + break; + + case NORM_RX_OBJECT_COMPLETED: + if (normObject.getType() == NormObjectType.NORM_OBJECT_FILE) { + NormFile normFile = (NormFile)normObject; + String filename = normFile.getName(); + + System.out.println("NormFileObject: " + filename); + } + break; + } + } + + session.stopReceiver(); + session.destroySession(); + instance.destroyInstance(); + } +} diff --git a/examples/java/NormFileSend.java b/examples/java/NormFileSend.java new file mode 100644 index 0000000..c4eee2d --- /dev/null +++ b/examples/java/NormFileSend.java @@ -0,0 +1,44 @@ +import java.nio.ByteBuffer; + +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.enums.NormEventType; + +/** + * Example code to send a file to the NormFileRecv example. + * + * @author Jason Rush + */ +public class NormFileSend { + public static void main(String[] args) throws Throwable { + if (args.length != 1) { + System.err.println("Usage: NormFileSend "); + return; + } + + NormInstance instance = new NormInstance(); + + NormSession session = instance.createSession("224.1.2.3", 6003, + NormNode.NORM_NODE_ANY); + session.startSender(1, 1024 * 1024, 1400, (short)64, (short)16); + + // Enqueue some data + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); + session.dataEnqueue(byteBuffer, 0, 1024); + + // Enqueue a file + session.fileEnqueue(args[0]); + + NormEvent event; + while ((event = instance.getNextEvent()) != null) { + NormEventType eventType = event.getType(); + System.out.println(eventType); + } + + session.stopSender(); + session.destroySession(); + instance.destroyInstance(); + } +} diff --git a/examples/java/NormFileSendRecv.java b/examples/java/NormFileSendRecv.java new file mode 100644 index 0000000..38dbfd1 --- /dev/null +++ b/examples/java/NormFileSendRecv.java @@ -0,0 +1,103 @@ +import mil.navy.nrl.norm.*; +import mil.navy.nrl.norm.enums.NormEventType; +import mil.navy.nrl.norm.enums.NormObjectType; + +/** + * The ... class ... + *

+ * Created by scmijt + * Date: Feb 14, 2010 + * Time: 11:10:50 AM + */ +public class NormFileSendRecv extends Thread { + NormInstance instance; + NormSession session; + String fileToSend; + + public NormFileSendRecv(String cacheDirectory, String fileToSend) throws Throwable { + instance = new NormInstance(); + instance.setCacheDirectory(cacheDirectory); + + session = instance.createSession("224.1.2.3", 6003, + NormNode.NORM_NODE_ANY); + session.setLoopback(true); + + this.fileToSend=fileToSend; + + start(); + + sendFile(); + + session.destroySession(); + instance.destroyInstance(); + + } + + public void run() { + try { + receive(); + } catch (Throwable throwable) { + throwable.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + + public void receive() throws Throwable { + session.startReceiver(1024 * 1024); + NormEvent event; + while ((event = instance.getNextEvent()) != null) { + NormEventType eventType = event.getType(); + NormObject normObject = event.getObject(); + + System.out.println("NORM: Received something !!!!"); + + System.out.println(eventType); + + switch (eventType) { + case NORM_RX_OBJECT_INFO: + byte[] info = normObject.getInfo(); + String infoStr = new String(info, "US-ASCII"); + System.out.println("Info: " + infoStr); + break; + + case NORM_RX_OBJECT_COMPLETED: + if (normObject.getType() == NormObjectType.NORM_OBJECT_FILE) { + NormFile normFile = (NormFile)normObject; + String filename = normFile.getName(); + + System.out.println("NormFileObject: " + filename); + } + break; + } + } + + session.stopReceiver(); + + } + + public void sendFile() throws Throwable { + session.startSender(1, 1024 * 1024, 1400, (short)64, (short)16); + + // Enqueue some data + // byte buffer[] = "Hello to the other norm node!!!!!!".getBytes(); + // session.dataEnqueue(buffer, 0, buffer.length); + + // Enqueue a file + session.fileEnqueue(fileToSend); + + NormEvent event; + while ((event = instance.getNextEvent()) != null) { + NormEventType eventType = event.getType(); + System.out.println(eventType); + } + + session.stopSender(); + } + + public static void main(String[] args) throws Throwable { + if (args.length != 2) { + System.err.println("Usage: NormFileSendRecv "); + return; + } + new NormFileSendRecv(args[0], args[1]); + } +} diff --git a/examples/java/NormStreamRecv.java b/examples/java/NormStreamRecv.java new file mode 100644 index 0000000..0725e55 --- /dev/null +++ b/examples/java/NormStreamRecv.java @@ -0,0 +1,109 @@ +import java.io.IOException; +import java.net.InetAddress; +import mil.navy.nrl.norm.NormEvent; +import mil.navy.nrl.norm.NormInstance; +import mil.navy.nrl.norm.NormNode; +import mil.navy.nrl.norm.NormObject; +import mil.navy.nrl.norm.NormSession; +import mil.navy.nrl.norm.NormStream; +import mil.navy.nrl.norm.enums.NormEventType; + +public class NormStreamRecv { + static final long SESSION_BUFFER_SIZE = 1024 * 1024; + static final int SEGMENT_SIZE = 1400; + static final int BLOCK_SIZE = 64; + static final int PARITY_SEGMENTS = 16; + static final String DEST_ADDRESS = "224.1.2.3"; + static final int DEST_PORT = 6003; + + public static void main(String[] args) { + NormInstance instance = null; + NormSession session = null; + String destAddress = DEST_ADDRESS; + int destPort = DEST_PORT; + + try { + int length = 0; + int offset = 0; + byte[] buf = new byte[65536]; + boolean useUnicastNACKs = false; + + if (args.length > 0) { + // dest addr is arg 1 + InetAddress mcastAddr = InetAddress.getByName(args[0]); + useUnicastNACKs = ! mcastAddr.isMulticastAddress(); + destAddress = args[0]; + if (useUnicastNACKs) + System.err.println("Using unicast NACKs"); + } + + if (args.length > 1) { + // port is arg 2 + destPort = Integer.parseInt(args[1]); + } + + instance = new NormInstance(); + session = instance.createSession(destAddress, destPort, + NormNode.NORM_NODE_ANY); + session.setDefaultUnicastNack(useUnicastNACKs); + session.startReceiver(SESSION_BUFFER_SIZE); + boolean streamIsAlive = true; + NormEvent event; + + while ((null != (event = instance.getNextEvent())) && streamIsAlive) { + NormEventType eventType = event.getType(); + NormObject normObject = event.getObject(); + + //System.err.println(eventType); + + switch (eventType) { + case NORM_RX_OBJECT_NEW: + //System.err.println("New stream"); + break; + + case NORM_RX_OBJECT_UPDATED: // Stream updated = data to read .... + //System.err.println("An update!"); + + if (normObject instanceof NormStream) { + int numRead = 0; + NormStream normStreamobj; + normStreamobj = (NormStream)normObject; + // Read as much as possible, writing + // everything to System.out. + while (0 < (numRead = normStreamobj.read(buf, 0, buf.length))) { + if (-1 != numRead) { + System.out.write(buf, 0, numRead); + } + } + } else { + System.err.print("Expected NormStream. Got "); + System.err.println(normObject); + } + break; + + case NORM_RX_OBJECT_COMPLETED: + //System.err.println("Stream end"); + streamIsAlive = false; + break; + } + } + } + catch (IOException ex) { + System.err.println(ex); + } + catch (NumberFormatException ex) { + System.err.println("Usage: NormStreamRecv [host-name [port]]"); + System.err.println("Default host-name: " + DEST_ADDRESS); + System.err.println("Default port: " + DEST_PORT); + } + + if (null != session) { + session.stopReceiver(); + session.destroySession(); + } + + if (null != instance) { + instance.destroyInstance(); + } + } +} \ No newline at end of file diff --git a/examples/java/NormStreamSend.java b/examples/java/NormStreamSend.java new file mode 100644 index 0000000..56bc37a --- /dev/null +++ b/examples/java/NormStreamSend.java @@ -0,0 +1,110 @@ +import java.io.IOException; +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.NormStream; +import mil.navy.nrl.norm.enums.NormEventType; + +public class NormStreamSend { + static final long REPAIR_WINDOW_SIZE = 1024 * 1024; + static final long SESSION_BUFFER_SIZE = 1024 * 1024; + static final int SEGMENT_SIZE = 1400; + static final int BLOCK_SIZE = 64; + static final int PARITY_SEGMENTS = 16; + static final String DEST_ADDRESS = "224.1.2.3"; + static final int DEST_PORT = 6003; + + public static void main(String[] args) { + NormInstance instance = null; + NormSession session = null; + NormStream stream = null; + String destAddress = DEST_ADDRESS; + int destPort = DEST_PORT; + + try { + int length = 0; + int offset = 0; + byte[] buf = new byte[65536]; + + if (args.length > 0) { + destAddress = args[0]; + } + + if (args.length > 1) { + destPort = Integer.parseInt(args[1]); + } + + instance = new NormInstance(); + session = instance.createSession(destAddress, destPort, + NormNode.NORM_NODE_ANY); + + String ccStr = System.getProperty("Norm.CC", "off"); + if (ccStr.equalsIgnoreCase("on")) { + session.setCongestionControl(true, true); + System.out.println("Set Congestion Control to " + ccStr); + } + + session.startSender(1, SESSION_BUFFER_SIZE, + SEGMENT_SIZE, BLOCK_SIZE, PARITY_SEGMENTS); + stream = session.streamOpen(REPAIR_WINDOW_SIZE); + + while (-1 != (length = System.in.read(buf, 0, buf.length))) { + int numWritten = 0; + offset = 0; + + while (length != (numWritten = stream.write(buf, offset, length))) { + length -= numWritten; + offset += numWritten; + + NormEvent event = instance.getNextEvent(); + NormEventType eventType = event.getType(); + + while ((eventType != NormEventType.NORM_TX_QUEUE_EMPTY) && + (eventType != NormEventType.NORM_TX_QUEUE_VACANCY)) { + event = instance.getNextEvent(); + eventType = event.getType(); + } + } + + stream.markEom(); + + //System.err.println("Wrote " + numWritten); + //System.err.println("... Done!"); + + // TODO: Create a new buf each time I'm successful writing + // all of it? + } + + stream.flush(); + System.err.println("End of file!"); + } + catch (IOException ex) { + System.err.println(ex); + } + catch (NumberFormatException ex) { + System.err.println("Usage: NormStreamSend [host-name [port]]"); + System.err.println("Default host-name: " + DEST_ADDRESS); + System.err.println("Default port: " + DEST_PORT); + } + + if (null != stream) { + System.err.println("Closing stream"); + stream.close(true); + } + + if (null != session) { + System.err.println("Stopping sender"); + session.stopSender(); + System.err.println("Destroying session"); + session.destroySession(); + } + + if (null != instance) { + System.err.println("Destroying instance"); + instance.destroyInstance(); + } + + System.err.println("That's all folks!"); + } +} \ No newline at end of file diff --git a/examples/java/README.TXT b/examples/java/README.TXT new file mode 100644 index 0000000..dd66918 --- /dev/null +++ b/examples/java/README.TXT @@ -0,0 +1,72 @@ +================= NORM Java API Examples ================= + +This directory contains some purposely simplified examples of NORM +Java API usage. See comments below on how to build and run these on +Unix platforms. Eventually, "build.xml" will be provided. + + +The following example programs are included: + +(These two programs use a hard-coded multicast address and port, +but take a file name and directory name, respectively, as a +command-line argument to determine the file sent and the directory +to which receive files are stored.) + +NormFileSend.java - simple file transmission program. Sends one + file. The address is 224.1.2.3:6003. + +NormFileRecv.java - simple file reception program. Receives one + file. The address is 224.1.2.3:6003. + +NormFileSendRecv.java - One application that sends a file to itself. + The address is 224.1.2.3:6003. + +NormStreamSend.java - This is an example of writing to a NORM stream. + It reads from STDIN and writes to the + stream. Specify the stream address on the command + line or use the default address, 224.1.2.3:6003. + Enable congestion control by setting the Java + system property Norm.CC to "on" + (i.e. -DNorm.CC=on). + +NormStreamRecv.java - This is an example of reading from a NORM + stream. It writes to STDOUT and writes from the + stream. Specify the stream address on the command + line or use the default address, 224.1.2.3:6003. + + +COMPILING THE Java API EXAMPLES + +After you build the JNI and the Norm Java jar file, you can compile +the examples in this directory all at one go: + +javac -cp ../../lib/norm-1.0.0.jar *.java + + + +RUNNING THE Java API EXAMPLES + +You can use the shell scripts supplied to run the similarly named +example programs. + +filesend.sh - Run NormFileSend +filerecv.sh - Run NormFileRecv +streamsend.sh - Run NormStreamSend +streamrecv.sh - Run NormStreamRecv + +Alternatively, you can run any example program by invoking java. You +must define the system property java.library.path and set the +classpath so that java can find the norm jar file as well as the local +classes. For example, to run NormStreamSend: + +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormStreamSend + +NOTE ABOUT NormStreamSend + +NormStreamSend supports NORM's TCP-friendly congestion control. +Enable that by setting the system property Norm.CC to "on". You can +do that in streamsend.sh or you can do that on the command line: + +java -DNorm.CC=on -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormStreamSend + + diff --git a/examples/java/filerecv.sh b/examples/java/filerecv.sh new file mode 100755 index 0000000..e1e8cd6 --- /dev/null +++ b/examples/java/filerecv.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormFileRecv $@ diff --git a/examples/java/filesend.sh b/examples/java/filesend.sh new file mode 100755 index 0000000..808b110 --- /dev/null +++ b/examples/java/filesend.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormFileSend $@ diff --git a/examples/java/streamrecv.sh b/examples/java/streamrecv.sh new file mode 100755 index 0000000..5461083 --- /dev/null +++ b/examples/java/streamrecv.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormStreamRecv $@ diff --git a/examples/java/streamsend.sh b/examples/java/streamsend.sh new file mode 100755 index 0000000..2dd7381 --- /dev/null +++ b/examples/java/streamsend.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Enable TCP-friendly congestion control with: +# -DNorm.CC=on +java -Djava.library.path=../../lib -cp .:../../lib/norm-1.0.0.jar NormStreamSend $@ diff --git a/examples/normDataExample.cpp b/examples/normDataExample.cpp new file mode 100644 index 0000000..07e300b --- /dev/null +++ b/examples/normDataExample.cpp @@ -0,0 +1,119 @@ +// This example uses the NORM API and demonstrates the use +// of NORM_OBJECT_DATA transmission and reception. +// The "data" sent and received are simple text messages +// of randomly-varying length. + +XXXX - THIS CODE IS NOT YET COMPLETE !!!!! + +#include + +// We use some "protolib" functions for +// cross-platform portability +#include +#include + +void Usage() +{ + fprintf(stderr, "Usage: normDataExample [send][recv]\n"); +} + + + + +int main(int argc, char* argv[]) +{ + bool send = false; + bool recv = false; + // Parse any command-line arguments + int i = 1; + while (i < argc) + { + if (!strcmp("send", argv[i])) + { + send = true; + } + else if (!strcmp("recv", argv[i])) + { + recv = true; + } + else + { + fprintf(stderr, "normDataExample error: invalid command-line option!\n"); + Usage(); + return -1; + } + return 0; + } + + if (!send && !recv) + { + fprintf(stderr, "normDataExample error: not configured as sender or receiver!\n"); + Usage(); + return -1; + } + + + // 1) Create a NORM API "NormInstance" + NormInstanceHandle instance = NormCreateInstance(); + if (NORM_INSTANCE_INVALID == instance) + { + fprintf(stderr, "normDataExample error: NormCreateInstance() failure!\n"); + return -1; + } + + + // 2) Create a NormSession using default "automatic" local node id + NormSessionHandle session = NormCreateSession(instance, + "224.1.2.3", + 6003, + NORM_NODE_ANY); + if (NORM_SESSION_INVALID == instance) + { + fprintf(stderr, "normDataExample error: NormCreateSession() failure!\n"); + return -1; + } + + // Enable some debugging output here + //SetDebugLevel(2); + // Uncomment to turn on debug NORM message tracing + NormSetMessageTrace(session, true); + + // 3) Start receiver operation, if applicable + if (recv) + { + // Start receiver w/ 1MByte buffer per sender + if (!NormStartReceiver(session, 1024*1024)) + { + fprintf(stderr, "normDataExample error: NormStartReceiver() failure!\n"); + return -1; + } + } + + // 4) Start receiver operation, if applicable + if (send) + { + // a) Pick a random sender "sessionId" + // (seed the random generation so we get a new one each time) + struct timeval currentTime; + ProtoSystemTime(currentTime); + srand(currentTime.tv_sec); // seed random number generator + NormSessionId sessionId = (NormSessionId)rand(); + + // b) Set sender rate or congestion control operation + NormSetTxRate(session, 256.0e+03); // in bits/second + + } + + // Enter loop waiting for and handling NormEvents ... + // (We illustrate usage of a NormDescriptor for this + // with our WaitForNormEvent() routine.) + // When set to "send" the "interval" is used to + NormDescriptor normDescriptor = NormGetDescriptor(instance); + bool keepGoing = true; + while (keepGoing) + { + boolWaitForNormEvent(normDescriptor, timeDelay) + } // end while (keepGoing) + + +} // end main() diff --git a/examples/normDataRecv.cpp b/examples/normDataRecv.cpp new file mode 100644 index 0000000..3a3626a --- /dev/null +++ b/examples/normDataRecv.cpp @@ -0,0 +1,248 @@ +/****************************************************************************** + Simple NORM_OBJECT_DATA receiver example app using the NORM API + + USAGE: + + normDataRecv + + BUILD (Unix): + + g++ -o normDataRecv normDataRecv.cpp -D_FILE_OFFSET_BITS=64 -I../include/ \ + ../lib/libnorm.a ../protolib/lib/libProtokit.a \ + -lpthread + + (for MacOS/BSD, add "-lresolv") + (for Solaris, add "-lnsl -lsocket -lresolv") + +******************************************************************************/ + + +// Notes: +// 1) The program exits once a single file has been received ... +// 2) The received file is written to the +// 3) The program also will exit on from user +// 4) "normDataRecv" should be started before "normFileSend" + +#include "normApi.h" // for NORM API + +#include "normMessage.h" + +#include "protoAddress.h" // for ProtoAddress for easy mcast test + +#include // for printf(), etc +#include // for srand() +#include // for strrchr() +#include // for gettimeofday() + + +// Usage: normDataRecv [addr /] + + +#include "protoBitmask.h" + +void Usage() +{ + fprintf(stderr, "Usage: normDataRecv [addr /]\n"); +} + + + +int main(int argc, char* argv[]) +{ + // Initialize default parameters. + char sessionAddr[256]; + strcpy(sessionAddr, "224.1.2.3"); + UINT16 sessionPort = 6003; + + // Parse command-line for any parameters + int i = 1; + while (i < argc) + { + if (0 == strcmp("addr", argv[1])) + { + i++; + if (i == argc) + { + fprintf(stderr, "normDataRecv error: missing \"addr\" arguments\n"); + Usage(); + return -1; + } + strcpy(sessionAddr, argv[i++]); + char* ptr = strchr(sessionAddr, '/'); + if (NULL != ptr) + { + *ptr = '\0'; + sessionPort = atoi(ptr+1); + } + } + else + { + fprintf(stderr, "normDataRecv error: invalid command \"%s\"\n", argv[i]); + Usage(); + return -1; + } + } + + // 1) Create a NORM API "NormInstance" + NormInstanceHandle instance = NormCreateInstance(); + + // 2) Create a NormSession using default "automatic" local node id + NormSessionHandle session = NormCreateSession(instance, + sessionAddr, + sessionPort, + NORM_NODE_ANY); + + + // NOTE: These are debugging routines available + // (not necessary for normal app use) + NormSetDebugLevel(3); + // Uncomment to turn on debug NORM message tracing + //NormSetMessageTrace(session, true); + // Uncomment to turn on some random packet loss for testing + //NormSetRxLoss(session, 10.0); // 10% packet loss + struct timeval currentTime; + gettimeofday(¤tTime, NULL); + // Uncomment to get different packet loss patterns from run to run + srand(currentTime.tv_sec); // seed random number generator + + // Uncomment if unicast NACKing is desired + // (We "cheat" here and use the Protolib "ProtoAddress" class + // that NORM happens to use under the hood as well) + ProtoAddress theAddr; + theAddr.ResolveFromString(sessionAddr); + if (!theAddr.IsMulticast()) + { + NormSetDefaultUnicastNack(session, true); + // Set the tx port equal to session port so sender socket + // and make it "connected" to the remote sender address (sessionAddr) and port (sessionPort+1) + // (This assumes the remote sender address/port is known (sessionAddr/sessionPort+1 in this case) + NormSetTxPort(session, sessionPort); + NormSetRxPortReuse(session, true, NULL, sessionAddr, sessionPort+1); + + } + else + { + // Uncomment to allow multiple NORM processes on same session port number + //NormSetRxPortReuse(session, true, sessionAddr); + } + + // 3) Start the receiver with 1 Mbyte buffer per sender + NormStartReceiver(session, 1024*1024); + + struct timeval startTime, endTime; // to measure object transfer time + + // 4) Enter NORM event loop + bool keepGoing = true; + while (keepGoing) + { + NormEvent theEvent; + if (!NormGetNextEvent(instance, &theEvent)) continue; + switch (theEvent.type) + { + case NORM_RX_OBJECT_NEW: + gettimeofday(&startTime, NULL); + fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_NEW event ...\n"); + break; + + case NORM_RX_OBJECT_INFO: + // Assume info contains '/' delimited string + //fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_INFO event ...\n"); + if (NORM_OBJECT_DATA == NormObjectGetType(theEvent.object)) + { + char dataInfo[8192]; + unsigned int nameLen = NormObjectGetInfo(theEvent.object, dataInfo, 8191); + dataInfo[nameLen] = '\0'; + //fprintf(stderr, "normDataRecv: info = \"%s\"\n", dataInfo); + } + break; + + case NORM_RX_OBJECT_UPDATED: + { + //fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_UPDATE event ...\n"); + // Uncomment this stuff to monitor file receive progress + // (At high packet rates, you may want to be careful here and + // only calculate/post updates occasionally rather than for + // each and every RX_OBJECT_UPDATE event) + //NormSize objectSize = NormObjectGetSize(theEvent.object); + //NormSize completed = objectSize - NormObjectGetBytesPending(theEvent.object); + //double percentComplete = 100.0 * ((double)completed/(double)objectSize); + //fprintf(stderr, "normDataRecv: object>%p completion status %lu/%lu (%3.0lf%%)\n", + // theEvent.object, (unsigned long)completed, (unsigned long)objectSize, percentComplete); + break; + } + + case NORM_RX_OBJECT_COMPLETED: + { + gettimeofday(&endTime, NULL); + fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_COMPLETED event ...\n"); + unsigned int objSize = NormObjectGetSize(theEvent.object); + const char* dataPtr = NormDataAccessData(theEvent.object); + // Validate that the data is complete/accurate + // a) compare data size against the size embedded in the "INFO" + char dataInfo[8192]; + unsigned int nameLen = NormObjectGetInfo(theEvent.object, dataInfo, 8191); + dataInfo[nameLen] = '\0'; + unsigned int dataCount, dataLen; + if (2 != sscanf(dataInfo, "NORM_OBJECT_DATA count>%u size>%u", &dataCount, &dataLen)) + { + fprintf(stderr, "normDataRecv error: received NORM_OBJECT_DATA with invalid INFO?!\n"); + return -1; + } + if (objSize != dataLen) + { + fprintf(stderr, "normDataRecv error: received NORM_OBJECT_DATA with bad object size?!\n"); + return -1; + } + // b) check the data content + char data = *dataPtr; + for (unsigned int i = 0; i < dataLen; i++) + { + if (dataPtr[i] != data) + { + fprintf(stderr, "normDataRecv error: received bad NORM_OBJECT_DATA!\n"); + return -1; + } + } + double transferTime = endTime.tv_sec - startTime.tv_sec; + if (endTime.tv_usec > startTime.tv_usec) + transferTime += 1.0e-06 * (double)(endTime.tv_usec - startTime.tv_usec); + else + transferTime -= 1.0e-06 * (double)(startTime.tv_usec - endTime.tv_usec); + double transferRate = (8.0/1000.0) * (double)objSize / transferTime; + fprintf(stderr, "normDataRecv: transfer duration %lf sec at %lf kbps\n", transferTime, transferRate); + //fprintf(stderr, "normDataRecv: object>%p count>%d size>%u data>%.64s ...\n", + // theEvent.object, dataCount, objSize, dataPtr); + // NOTE: Since we did not "retain" or "detach data" from this + // received data object, it (and its data) will be deleted upon + // the next call to "NormGetNextEvent()". + //keepGoing = false; + break; + } + case NORM_RX_OBJECT_ABORTED: + fprintf(stderr, "normDataRecv: NORM_RX_OBJECT_ABORTED event ...\n"); + break; + + case NORM_REMOTE_SENDER_NEW: + fprintf(stderr, "normDataRecv: NORM_REMOTE_SENDER_NEW event ...\n"); + break; + + case NORM_REMOTE_SENDER_ACTIVE: + fprintf(stderr, "normDataRecv: NORM_REMOTE_SENDER_ACTIVE event ...\n"); + break; + + case NORM_REMOTE_SENDER_INACTIVE: + fprintf(stderr, "normDataRecv: NORM_REMOTE_SENDER_INACTIVE event ...\n"); + break; + + default: + //fprintf(stderr, "normDataRecv: Got event type: %d\n", theEvent.type); + break; + } // end switch(theEvent.type) + } + NormStopReceiver(session); + NormDestroySession(session); + NormDestroyInstance(instance); + + fprintf(stderr, "normDataRecv: Done.\n"); + return 0; +} // end main() diff --git a/examples/normDataSend.cpp b/examples/normDataSend.cpp new file mode 100644 index 0000000..270d240 --- /dev/null +++ b/examples/normDataSend.cpp @@ -0,0 +1,227 @@ +/****************************************************************************** + Simple NORM_OBJECT_DATA sender example app using the NORM API + + USAGE: + + normSendData + + BUILD (Unix): + + g++ -o normDataSend normDataSend.cpp -D_FILE_OFFSET_BITS=64 -I../common/ \ + -I../protolib/include ../lib/libnorm.a ../protolib/lib/libProtokit.a \ + -lpthread + + (for MacOS/BSD, add "-lresolv") + (for Solaris, add "-lnsl -lsocket -lresolv") + +******************************************************************************/ + + +// 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) + + +#include "normApi.h" // for NORM API + +#include "protoDefs.h" // for ProtoSystemTime() +#include "protoDebug.h" // for SetDebugLevel(), etc +#include "protoAddress.h" // for ProtoAddress for easy mcast test + +#include // for printf(), etc +#include // for srand() +#include // for strrchr() + + + +// Usage: normDataSend [addr /] + +void Usage() +{ + fprintf(stderr, "Usage: normDataSend [addr /]\n"); +} + +int main(int argc, char* argv[]) +{ + // Initialize default parameters. + char sessionAddr[256]; + strcpy(sessionAddr, "224.1.2.3"); + UINT16 sessionPort = 6003; + + // Parse command-line for any parameters + int i = 1; + while (i < argc) + { + if (0 == strcmp("addr", argv[1])) + { + i++; + if (i == argc) + { + fprintf(stderr, "normDataSend error: missing \"addr\" arguments\n"); + Usage(); + return -1; + } + strcpy(sessionAddr, argv[i++]); + char* ptr = strchr(sessionAddr, '/'); + if (NULL != ptr) + { + *ptr = '\0'; + sessionPort = atoi(ptr+1); + } + } + else + { + fprintf(stderr, "normDataSend error: invalid command \"%s\"\n", argv[i]); + Usage(); + return -1; + } + } + + // Is the session an mcast addr? + ProtoAddress theAddr; + theAddr.ResolveFromString(sessionAddr); + bool isMulticastSession = theAddr.IsMulticast(); + + + // 1) Create a NORM API "NormInstance" + NormInstanceHandle instance = NormCreateInstance(); + + // 2) Create a NormSession using default "automatic" local node id + NormSessionHandle session = NormCreateSession(instance, + sessionAddr, + sessionPort, + NORM_NODE_ANY); + + // NOTE: These are some debugging routines available + // (not necessary for normal app use) + NormSetDebugLevel(3); + // Uncomment to turn on debug NORM message tracing + //NormSetMessageTrace(session, true); + // Uncomment to turn on some random packet loss + //NormSetTxLoss(session, 10.0); // 10% packet loss + struct timeval currentTime; + ProtoSystemTime(currentTime); + // Uncomment to get different packet loss patterns from run to run + // (and a different sender sessionId) + srand(currentTime.tv_sec); // seed random number generator + + // 3) Set transmission rate + NormSetTxRate(session, 2.0e+06); // in bits/second + + // Uncomment to enable TCP-friendly congestion control + //NormSetCongestionControl(session, true); + + // Uncomment to use a _specific_ transmit port number + // (Can be the same as session port (rx port), but this + // is _not_ recommended for mcast sessions when unicast feedback may be + // possible! - must be called _before_ NormStartSender()) + if (!isMulticastSession) + { + NormSetTxPort(session, sessionPort+1, true); + + // Uncomment to set the session to only open + // a single socket for transmission. + // This also "connects" the sender to session (receiver) addr/port + // (unicast nacking _and_ "txPort == rxPort" MUST be used by receivers) + NormSetTxOnly(session, true, true); + } + + // Uncomment to allow multiple NORM processes on same session port number + // Note that port reuse only works well for mcast. + // if (isMulticastSession) NormSetRxPortReuse(session, true); + + // 4) Start the sender using a random "sessionId" + NormSessionId sessionId = (NormSessionId)rand(); + TRACE("starting NORM sender ...\n"); + NormStartSender(session, sessionId, 1024*1024, 1400, 64, 16); + + // Uncomment to set large tx socket buffer size + // (might be needed for high rate sessions) + //NormSetTxSocketBuffer(session, 512000); + + + // 5) Enqueue the first data message + // (we enqueue text strings of random length as object content) + unsigned int MAX_COUNT = 1; // number of objects to send, zero means unlimited + const int MIN_LENGTH = 460000; // min object size (in bytes) + const int MAX_LENGTH = 460000; // max object size (in bytes) + + unsigned int dataCount = 0; + int dataLen = MIN_LENGTH + rand() % (MAX_LENGTH - MIN_LENGTH + 1); + char* dataMsg = new char[dataLen]; + ASSERT(NULL != dataMsg); + char data = 'a'; + memset(dataMsg, data, dataLen); // set message content with 'dummy' data + // Provide some "info" about this message + char dataInfo[256]; + sprintf(dataInfo, "NORM_OBJECT_DATA count>%d size>%d", dataCount, dataLen); + // Enqueue the data object + NormObjectHandle dataObj = + NormDataEnqueue(session, dataMsg, dataLen, dataInfo, strlen(dataInfo)); + ASSERT(NORM_OBJECT_INVALID != dataObj); + dataCount++; + + // 6) Enter NORM event loop + bool keepGoing = true; + while (keepGoing) + { + NormEvent theEvent; + if (!NormGetNextEvent(instance, &theEvent)) continue; + switch (theEvent.type) + { + case NORM_TX_QUEUE_VACANCY: + //fprintf(stderr, "normDataSend: NORM_TX_QUEUE_VACANCY event...\n"); + break; + case NORM_TX_QUEUE_EMPTY: + { + if ((0 == MAX_COUNT) || (dataCount < MAX_COUNT)) + { + // Enqueue another data object when norm tx queue goes empty + //fprintf(stderr, "normDataSend: NORM_TX_QUEUE_EMPTY event...\n"); + dataLen = MIN_LENGTH + rand() % (MAX_LENGTH - MIN_LENGTH + 1); + dataMsg = new char[dataLen]; + ASSERT(NULL != dataMsg); + memset(dataMsg, data, dataLen); + sprintf(dataInfo, "NORM_OBJECT_DATA count>%d size>%d data>%.64s ...", dataCount, dataLen, dataMsg); + NormObjectHandle dataObj = + NormDataEnqueue(session, dataMsg, dataLen, dataInfo, strlen(dataInfo)); + // Note that flow control timer may have prevented NormDataEnqueue() + // from succeeding even though we got a NORM_TX_QUEUE_EMPTY notification + // (The underlying NORM code could be tightened up a bit here!) + if (NORM_OBJECT_INVALID != dataObj) + { + dataCount++; + if (++data > 'z') data = 'a'; + } + else + { + TRACE("normDataSend: FLOW CONTROL CONDITION?\n"); + } + } + break; + } + case NORM_TX_OBJECT_PURGED: + { + //fprintf(stderr, "normDataSend: NORM_TX_OBJECT_PURGED event ...\n"); + char* dataPtr = NormDataDetachData(theEvent.object); + delete[] dataPtr; + break; + } + case NORM_TX_FLUSH_COMPLETED: + fprintf(stderr, "normDataSend: NORM_TX_FLUSH_COMPLETED event ...\n"); + break; + + default: + //TRACE("normDataSend: Got event type: %d\n", theEvent.type); + break; + } // end switch(theEvent.type) + } // end while (NormGetNextEvent()) + + NormStopSender(session); + NormDestroySession(session); + NormDestroyInstance(instance); + + fprintf(stderr, "normDataSend: Done.\n"); + return 0; +} // end main() diff --git a/examples/normFileRecv.cpp b/examples/normFileRecv.cpp index 618f51c..e980b85 100644 --- a/examples/normFileRecv.cpp +++ b/examples/normFileRecv.cpp @@ -7,9 +7,9 @@ BUILD (Unix): -g++ -o normFileRecv normFileRecv.cpp -D_FILE_OFFSET_BITS=64 -DPROTO_DEBUG \ - -I../common/ -I../protolib/common ../unix/libnorm.a \ - ../protolib/unix/libProtokit.a -lpthread +g++ -o normFileRecv normFileRecv.cpp -D_FILE_OFFSET_BITS=64 -I../common/ \ + -I../protolib/include ../lib/libnorm.a ../protolib/lib/libProtokit.a \ + -lpthread (for MacOS/BSD, add "-lresolv") (for Solaris, add "-lnsl -lsocket -lresolv") @@ -65,7 +65,7 @@ int main(int argc, char* argv[]) // Uncomment to turn on debug NORM message tracing //NormSetMessageTrace(session, true); // Uncomment to turn on some random packet loss for testing - //NormSetRxLoss(session, 10.0); // 10% packet loss + NormSetRxLoss(session, 10.0); // 10% packet loss struct timeval currentTime; ProtoSystemTime(currentTime); // Uncomment to get different packet loss patterns from run to run @@ -83,9 +83,10 @@ int main(int argc, char* argv[]) // 5) Enter NORM event loop bool keepGoing = true; - NormEvent theEvent; - while (keepGoing && NormGetNextEvent(instance, &theEvent)) + while (keepGoing) { + NormEvent theEvent; + if (!NormGetNextEvent(instance, &theEvent)) continue; switch (theEvent.type) { case NORM_RX_OBJECT_NEW: @@ -127,7 +128,7 @@ int main(int argc, char* argv[]) // only calculate/post updates occasionally rather than for // each and every RX_OBJECT_UPDATE event) NormSize objectSize = NormObjectGetSize(theEvent.object); - fprintf(stderr, "sizeof(NormSize) = %d\n", sizeof(NormSize)); + fprintf(stderr, "sizeof(NormSize) = %d\n", (int)sizeof(NormSize)); NormSize completed = objectSize - NormObjectGetBytesPending(theEvent.object); double percentComplete = 100.0 * ((double)completed/(double)objectSize); fprintf(stderr, "normFileRecv: completion status %lu/%lu (%3.0lf%%)\n", diff --git a/examples/normFileSend.cpp b/examples/normFileSend.cpp index 101960b..a1a3cc5 100644 --- a/examples/normFileSend.cpp +++ b/examples/normFileSend.cpp @@ -8,7 +8,7 @@ BUILD (Unix): g++ -o normFileSend normFileSend.cpp -D_FILE_OFFSET_BITS=64 -I../common/ \ - -I../protolib/common ../unix/libnorm.a ../protolib/unix/libProtokit.a \ + -I../protolib/include ../lib/libnorm.a ../protolib/lib/libProtokit.a \ -lpthread (for MacOS/BSD, add "-lresolv") @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) srand(currentTime.tv_sec); // seed random number generator // 3) Set transmission rate - NormSetTransmitRate(session, 256.0e+03); // in bits/second + NormSetTxRate(session, 25600.0e+03); // in bits/second // Uncomment to use a _specific_ transmit port number // (Can be the same as session port (rx port), but this @@ -110,9 +110,10 @@ int main(int argc, char* argv[]) // 6) Enter NORM event loop bool keepGoing = true; - NormEvent theEvent; - while (keepGoing && NormGetNextEvent(instance, &theEvent)) + while (keepGoing) { + NormEvent theEvent; + if (!NormGetNextEvent(instance, &theEvent)) continue; switch (theEvent.type) { case NORM_TX_QUEUE_VACANCY: diff --git a/examples/normStreamRecv.cpp b/examples/normStreamRecv.cpp new file mode 100644 index 0000000..6995f89 --- /dev/null +++ b/examples/normStreamRecv.cpp @@ -0,0 +1,238 @@ +/****************************************************************************** + Simple NORM_OBJECT_STREAM receiver example app using the NORM API + (expects NORM message stream of text messages with 2-byte length header (network byte order)) + + USAGE: + + normStreamRecv + + BUILD (Unix): + + g++ -o normStreamRecv normStreamRecv.cpp -D_FILE_OFFSET_BITS=64 -I../include/ \ + ./lib/libnorm.a ../protolib/lib/libProtokit.a -lpthread + + (for MacOS/BSD, add "-lresolv") + (for Solaris, add "-lnsl -lsocket -lresolv") + +******************************************************************************/ + + +// Notes: +// 1) The program also will exit on from user +// 2) "normStreamRecv" should be started before "normStreamSend" but can join stream in progress. +// 3) This example is designed to receive a single stream from a single sender (could be modified +// to support multiple streams and/or senders) + +#include "normApi.h" // for NORM API + +#include // for printf(), etc +#include // for srand() +#include // for strrchr() +#include // for gettimeofday() +#include // for ntohs() + +int main(int argc, char* argv[]) +{ + // 0) Some default params + const unsigned int MSG_LENGTH_MAX = 5000; + + + // 1) Create a NORM API "NormInstance" + NormInstanceHandle instance = NormCreateInstance(); + + // 2) Create a NormSession using default "automatic" local node id + NormSessionHandle session = NormCreateSession(instance, + "127.0.0.1", + 6003, + NORM_NODE_ANY); + + // NOTE: These are debugging routines available + // (not necessary for normal app use) + // (Need to include "protolib/common/protoDebug.h" for this + NormSetDebugLevel(3); + // Uncomment to turn on debug NORM message tracing + //NormSetMessageTrace(session, true); + // Uncomment to write debug output to file "normLog.txt" + //NormOpenDebugLog(instance, "normLog.txt"); + + + // Uncomment to turn on some random packet loss for testing + //NormSetRxLoss(session, 10.0); // 10% packet loss + struct timeval currentTime; + gettimeofday(¤tTime, NULL); + // Uncomment to get different packet loss patterns from run to run + srand(currentTime.tv_sec); // seed random number generator + + + // Uncomment to enable rx port reuse (this plus unique NormNodeId's enables same-machine send/recv) + NormSetRxPortReuse(session, true); + + // 3) Start the receiver with 1 Mbyte buffer per sender + NormStartReceiver(session, 8*1024*1024); + + NormSetSilentReceiver(session, true); + + if (!NormSetRxSocketBuffer(session, 8*1024*1024)) + perror("normStreamRecv error: unable to set requested socket buffer size"); + + // We use these variables to keep track of our recv stream + // and to buffer reading from the recv stream + NormObjectHandle stream = NORM_OBJECT_INVALID; // we use this to make sure we're handling the correct (only) stream + bool msgSync = false; + char msgBuffer[MSG_LENGTH_MAX]; + UINT16 msgLen = 0; + UINT16 msgIndex = 0; + + // 4) Enter NORM event loop + bool keepGoing = true; + while (keepGoing) + { + NormEvent theEvent; + if (!NormGetNextEvent(instance, &theEvent)) continue; + switch (theEvent.type) + { + case NORM_RX_OBJECT_NEW: + fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_NEW event ...\n"); + if (NORM_OBJECT_INVALID == stream) + { + if (NORM_OBJECT_STREAM == NormObjectGetType(theEvent.object)) + { + stream = theEvent.object; + msgLen = msgIndex = 0; // init stream reading state + msgSync = false; + } + else + { + fprintf(stderr, "normStreamRecv error: received NORM_RX_OBJECT_NEW for non-stream object?!\n"); + } + } + else + { + fprintf(stderr, "normStreamRecv error: received NORM_RX_OBJECT_NEW while already receiving stream?!\n"); + } + break; + + case NORM_RX_OBJECT_INFO: + { + // Assume info contains NULL-terminated string + //fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_INFO event ...\n"); + if (theEvent.object != stream) + { + fprintf(stderr, "normStreamRecv error: received NORM_RX_OBJECT_UPDATED for unhandled object?!\n"); + break; + } + char streamInfo[8192]; + unsigned int infoLen = NormObjectGetInfo(theEvent.object, streamInfo, 8191); + streamInfo[infoLen] = '\0'; + fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_INFO event, info = \"%s\"\n", streamInfo); + break; + } + case NORM_RX_OBJECT_UPDATED: + { + + //fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_UPDATED event ...\n"); + if (theEvent.object != stream) + { + fprintf(stderr, "normStreamRecv error: received NORM_RX_OBJECT_UPDATED for unhandled object?!\n"); + break; + } + while (1) + { + // If we're not "in sync", seek message start + if (!msgSync) + { + msgSync = NormStreamSeekMsgStart(stream); + if (!msgSync) break; // wait for next NORM_RX_OBJECT_UPDATED to re-sync + } + if (msgIndex < 2) + { + // We still need to read the 2-byte message header for the next message + unsigned int numBytes = 2 - msgIndex; + if (!NormStreamRead(stream, msgBuffer+msgIndex, &numBytes)) + { + fprintf(stderr, "normStreamRecv error: broken stream detected, re-syncing ...\n"); + msgLen = msgIndex = 0; + msgSync = false; + continue; // try to re-sync and read again + } + msgIndex += numBytes; + if (msgIndex < 2) break; // wait for next NORM_RX_OBJECT_UPDATED to read more + memcpy(&msgLen, msgBuffer, 2); + msgLen = ntohs(msgLen); + if ((msgLen < 2) || (msgLen > MSG_LENGTH_MAX)) + { + fprintf(stderr, "normStreamRecv error: message received with invalid length?!\n"); + msgLen = msgIndex = 0; + msgSync = false; + continue; // try to re-sync and read again + } + } + // Read "content" portion of message (note "msgIndex" accounts for length "header" + unsigned int numBytes = msgLen - msgIndex; + if (!NormStreamRead(stream, msgBuffer+msgIndex, &numBytes)) + { + fprintf(stderr, "normStreamRecv error: broken stream detected, re-syncing ...\n"); + msgLen = msgIndex = 0; + msgSync = false; + continue; // try to re-sync and read again + } + msgIndex += numBytes; + if (msgIndex == msgLen) + { + // Complete message read + //fprintf(stderr, "normStreamRecv msg: %s\n", msgBuffer+2); + msgLen = msgIndex = 0; // reset state variables for next message + } + else + { + break; // wait for next NORM_RX_OBJECT_UPDATED to read more + } + } // end while(1) (NormStreamRead() loop) + break; + } + case NORM_RX_OBJECT_COMPLETED: + { + fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_COMPLETED event ...\n"); + if (stream == theEvent.object) + { + fprintf(stderr, "normStreamRecv: current stream completed ...\n"); + stream = NORM_OBJECT_INVALID; + } + break; + } + case NORM_RX_OBJECT_ABORTED: + fprintf(stderr, "normStreamRecv: NORM_RX_OBJECT_ABORTED event ...\n"); + if (stream == theEvent.object) + { + fprintf(stderr, "normStreamRecv error: current stream aborted ...\n"); + stream = NORM_OBJECT_INVALID; + } + break; + + case NORM_REMOTE_SENDER_NEW: + fprintf(stderr, "normStreamRecv: NORM_REMOTE_SENDER_NEW event ...\n"); + break; + + case NORM_REMOTE_SENDER_ACTIVE: + fprintf(stderr, "normStreamRecv: NORM_REMOTE_SENDER_ACTIVE event ...\n"); + break; + + case NORM_REMOTE_SENDER_INACTIVE: + fprintf(stderr, "normStreamRecv: NORM_REMOTE_SENDER_INACTIVE event ...\n"); + break; + + case NORM_GRTT_UPDATED: + fprintf(stderr, "normStreamRecv: NORM_GRTT_UPDATED event ...\n"); + break; + + default: + fprintf(stderr, "normStreamRecv: Got event type: %d\n", theEvent.type); + } // end switch(theEvent.type) + } + NormStopReceiver(session); + NormDestroySession(session); + NormDestroyInstance(instance); + + fprintf(stderr, "normStreamRecv: Done.\n"); + return 0; +} // end main() diff --git a/examples/normStreamSend.cpp b/examples/normStreamSend.cpp new file mode 100644 index 0000000..b385f15 --- /dev/null +++ b/examples/normStreamSend.cpp @@ -0,0 +1,290 @@ +/****************************************************************************** + Simple NORM_OBJECT_DATA sender example app using the NORM API + + USAGE: + + normSendStream + + BUILD (Unix): + + g++ -o normStreamSend normStreamSend.cpp -D_FILE_OFFSET_BITS=64 -I../include/ \ + ../lib/libnorm.a ../protolib/lib/libProtokit.a -lpthread + + (for MacOS/BSD, add "-lresolv") + (for Solaris, add "-lnsl -lsocket -lresolv") + +******************************************************************************/ + + +// 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) + + +#include "normApi.h" // for NORM API + +#include // for printf(), etc +#include // for srand() +#include // for strrchr() +#include // for gettimeofday() +#include // for htons() + +int main(int argc, char* argv[]) +{ + // 0) Default parameter values + const unsigned int MSG_LENGTH_MIN = 1400; + const unsigned int MSG_LENGTH_MAX = 1400; + 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 msgRate = -1.0; //1e+06; // 32 kbits/sec default message rate + + // 1) Create a NORM API "NormInstance" + NormInstanceHandle instance = NormCreateInstance(); + + // 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", + 6003, + 1);//NORM_NODE_ANY); + + // NOTE: These are some debugging routines available + // (not necessary for normal app use) + NormSetDebugLevel(3); + // Uncomment to turn on debug NORM message tracing + //NormSetMessageTrace(session, true); + // Uncomment to turn on some random packet loss + //NormSetTxLoss(session, 25.0); // 25% packet loss for testing purposes + + struct timeval currentTime; + gettimeofday(¤tTime, NULL); + // Uncomment to get different packet loss patterns from run to run + // (and a different sender sessionId) + srand(currentTime.tv_sec); // seed random number generator + + // 3) Set transmission rate + NormSetTxRate(session, normRate); // in bits/second + + //NormSetFlowControl(session, 0.0); + + // Init GRTT to low value (3 msec) + //NormSetGrttEstimate(session, 1.0e-03); + + // Disable receiver backoffs (for lower latency, high speed performance) + // (For large group sizes, the default backoff factor is RECOMMENDED) + NormSetBackoffFactor(session, 2.0); + + // Uncomment to use a _specific_ transmit port number + // (Can be the same as session port (rx port), but this + // is _not_ recommended when unicast feedback may be + // possible! - must be called _before_ NormStartSender()) + //NormSetTxPort(session, 6001); + + // Uncomment to enable TCP-friendly congestion control + //NormSetCongestionControl(session, true); + + // Uncomment to enable rx port reuse (this plus unique NormNodeId's enables same-machine send/recv) + NormSetRxPortReuse(session, true); + + // 4) Start the sender using a random "sessionId" + NormSessionId sessionId = (NormSessionId)rand(); + NormStartSender(session, sessionId, 4*1024*1024, 1300, 64, 16); + + // Uncomment to set large tx socket buffer size + // (may be needed to achieve very high packet output rates) + //NormSetTxSocketBuffer(session, 512000); + + // 5) Enqueue the NORM_OBJECT_STREAM object + // Provide some "info" about this stream (the info is OPTIONAL) + char dataInfo[256]; + sprintf(dataInfo, "NORM_OBJECT_STREAM message stream ..."); + NormObjectHandle stream = NormStreamOpen(session, streamBufferSize, dataInfo, strlen(dataInfo) + 1); + if (NORM_OBJECT_INVALID == stream) + { + fprintf(stderr, "normStreamSend NormStreamOpen() error!\n"); + return -1; + } + + // 6) Write the first stream message + // (we enqueue text strings of random length as messages) + // ( a 2-byte network byte order length "header" is in each message) + unsigned int msgCount = 0; + char data = 'a'; + char msgData[MSG_LENGTH_MAX]; + UINT16 msgLen = MSG_LENGTH_MIN + (rand() % (MSG_LENGTH_MAX - MSG_LENGTH_MIN + 1)); + // set 2 byte message header (length in network byte order) + UINT16 msgHeader = htons(msgLen); + memcpy(msgData, &msgHeader, 2); // 2-byte message length "header" + memset(msgData + 2, data, msgLen - 3); // n-byte message content + msgData[msgLen - 1] = '\0'; // 1-byte NULL-termination + + // Write the message (as much as stream buffer will accept) + unsigned int bytesWritten = NormStreamWrite(stream, msgData, msgLen); + bool vacancy = (bytesWritten == msgLen); + + // Initialize the "delayTime" used in "select()" loop below + // based on whether message was competely written to stream + // (i.e. wait according to "msgRate" (configured bytes per second)) + double delayTime; + if (vacancy) + { + // Complete message was written, wait msg interval time + NormStreamMarkEom(stream); + msgCount++; + delayTime = (msgRate > 0.0) ? ((double)msgLen / msgRate) : 0.0; + } + else + { + // wait indefinitely for NORM_TX_QUEUE_VACANCY event + // to finish writing current message to stream + delayTime = -1.0; + } + + + // 6) We keep a running "timeAccumulator" value to maintain the proper + // _average_ message transmission rate. (TBD - impose "max" accumulation limit) + double timeAccumulator = 0.0; + struct timeval lastTime; + gettimeofday(&lastTime, NULL); + + // 7) We use a "select()" call to wait for NORM events or message interval timeout + int normfd = NormGetDescriptor(instance); + fd_set fdset; + + struct timeval timeout; + + // 6) Enter NORM event loop + bool keepGoing = true; + while (keepGoing) + { + FD_SET(normfd, &fdset); + struct timeval* timeoutPtr; + if (delayTime < 0.0) + { + timeoutPtr = NULL; // wait indefinitely (i.e. for queue vacancy) + } + else + { + if (delayTime > timeAccumulator) + delayTime -= timeAccumulator; + else + delayTime = 0.0; + timeout.tv_sec = (unsigned long)delayTime; + timeout.tv_usec = (unsigned long)(1.0e+06 * (delayTime - (double)timeout.tv_sec)); + timeoutPtr = &timeout; + } + + int result = select(normfd+1, &fdset, NULL, NULL, timeoutPtr); + + if (result > 0) + { + // Get and handle NORM API event + NormEvent theEvent; + if (NormGetNextEvent(instance, &theEvent)) + { + switch (theEvent.type) + { + case NORM_TX_QUEUE_EMPTY: + case NORM_TX_QUEUE_VACANCY: + { + /* + if (NORM_TX_QUEUE_VACANCY == theEvent.type) + fprintf(stderr, "normStreamSend: NORM_TX_QUEUE_VACANCY event ...\n"); + else + fprintf(stderr, "normStreamSend: NORM_TX_QUEUE_EMPTY event ...\n"); + */ + if (!vacancy) + { + // Finish writing remaining pending message content (as much as can be written) + bytesWritten += NormStreamWrite(stream, msgData + bytesWritten, msgLen - bytesWritten); + if (bytesWritten == msgLen) + { + // Complete message was written, wait msg interval time + NormStreamMarkEom(stream); + msgCount++; + delayTime = (msgRate > 0.0) ? ((double)msgLen / msgRate) : 0.0; + vacancy = true; + } + } + break; + } + + case NORM_TX_OBJECT_PURGED: + fprintf(stderr, "normStreamSend: NORM_TX_OBJECT_PURGED event ...\n"); + break; + + case NORM_TX_FLUSH_COMPLETED: + fprintf(stderr, "normStreamSend: NORM_TX_FLUSH_COMPLETED event ...\n"); + break; + + case NORM_GRTT_UPDATED: + fprintf(stderr, "normStreamRecv: NORM_GRTT_UPDATED event ...\n"); + break; + + default: + fprintf(stderr, "normStreamSend: Got event type: %d\n", theEvent.type); + } // end switch(theEvent.type) + } // end if (NormGetNextEvent()) + } + else if (result < 0) + { + // select() error + perror("normStreamSend: select() error"); + 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; + gettimeofday(¤tTime, NULL); + double timeDelta = (double)(currentTime.tv_sec - lastTime.tv_sec); + if (currentTime.tv_usec > lastTime.tv_usec) + timeDelta += 1.0e-06 * (currentTime.tv_usec - lastTime.tv_usec); + else + timeDelta -= 1.0e-06 * (lastTime.tv_usec - currentTime.tv_usec); + timeAccumulator += timeDelta; + while (vacancy && (timeAccumulator > delayTime)) + { + timeAccumulator -= delayTime; // subtract last message tx duration from accumulator + // Fill buffer with new message "data" text character (a-z) + if (++data > 'z') data = 'a'; + msgLen = MSG_LENGTH_MIN + (rand() % (MSG_LENGTH_MAX - MSG_LENGTH_MIN + 1)); + // set 2 byte message header (length in network byte order) + msgHeader = htons(msgLen); + memcpy(msgData, &msgHeader, 2); // 2-byte message length "header" + memset(msgData+2, data, msgLen-3); // n-byte message content + msgData[msgLen - 1] = '\0'; // 1-byte NULL-termination + bytesWritten = NormStreamWrite(stream, msgData, msgLen); + if (bytesWritten < msgLen) + { + // wait indefinitely for NORM_TX_QUEUE_VACANCY event + // to finish writing current message to stream + vacancy = false; + delayTime = -1.0; + //fprintf(stderr, "norm tx stream buffer full, time accumulator = %lf\n", timeAccumulator); + } + else + { + // Complete message was written, wait msg interval time + NormStreamMarkEom(stream); + msgCount++; + delayTime = (msgRate > 0.0) ? ((double)msgLen / msgRate) : 0.0; + } + } + if (timeAccumulator <= delayTime) + { + delayTime -= timeAccumulator; + timeAccumulator = 0.0; + } + lastTime = currentTime; + + } // end while (keepGoing) + + NormStopSender(session); + NormDestroySession(session); + NormDestroyInstance(instance); + + fprintf(stderr, "normDataSend: Done.\n"); + return 0; +} // end main() diff --git a/examples/python/debugStats.py b/examples/python/debugStats.py new file mode 100755 index 0000000..17613c0 --- /dev/null +++ b/examples/python/debugStats.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +''' +Example of live plotting of pipe logging data. +This has some problems still... +''' + +import sys +import Queue +from optparse import OptionParser + +import Gnuplot + +# Include the local pynorm in the module search path +sys.path.insert(0, "../") +from pynorm.extra.pipeparser import PipeParser + +USAGE = 'usage: %s [options]' % sys.argv[0] +DEFAULT_PIPE = 'normtest' + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(pipe=DEFAULT_PIPE) + + parser.add_option('-p', '--pipe', + help='The pipe to connect to (default %s)' % DEFAULT_PIPE) + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 1: + print USAGE + return 1 + + pipep = PipeParser(opts.pipe) + pipep.start() + + g = Gnuplot.Gnuplot() + g('set data style lines') + g.title('FEC Buffer Usage') + g.xlabel('Time (s)') + g.ylabel('FEC Usage') + + ydata = [] + while True: + try: + report = pipep.reports.get(False) + except Queue.Empty: + continue + try: + ydata.append(report['remote'][0]['fec_cur']) + g.plot(list(enumerate(ydata))) + except IndexError: + continue + pipep.reports.task_done() + print "Exiting..." +# g.reset() +# g.close() +# del g + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/fecBufferStats.py b/examples/python/fecBufferStats.py new file mode 100755 index 0000000..4cbfcaf --- /dev/null +++ b/examples/python/fecBufferStats.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +''' +Example showing reading debug info from a pipe. +''' + +import sys +import Queue +from optparse import OptionParser + +from pynorm.extra.pipeparser import PipeParser + +USAGE = 'usage: %s [options]' % sys.argv[0] +DEFAULT_PIPE = 'normtest' + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(pipe=DEFAULT_PIPE) + + parser.add_option('-p', '--pipe', + help='The pipe to connect to (default %s)' % DEFAULT_PIPE) + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 1: + print USAGE + return 1 + + pipep = PipeParser(opts.pipe) + pipep.start() + + while True: + try: + report = pipep.reports.get(True, 3) + except Queue.Empty: + continue + try: + print report['time'], report['remote'][0]['fec_cur'] + except IndexError: + pass + pipep.reports.task_done() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/fileRecvManager.py b/examples/python/fileRecvManager.py new file mode 100755 index 0000000..bb2f3b8 --- /dev/null +++ b/examples/python/fileRecvManager.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +''' +Simple NORM file receiver example app using Python NORM API +Shows the usage of the event manager. +''' + +import sys +import os.path +from optparse import OptionParser + +sys.path.insert(0, '../') +import pynorm +from pynorm.extra.manager import Manager, StopManager + +USAGE = 'usage: %s [options] ' % sys.argv[0] +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 +DEFAULT_PIPE = 'normtest' + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT, debug=0, + pipe=DEFAULT_PIPE) + + parser.add_option('-a', '--address', + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + parser.add_option('-d', '--debug', type=int, help='Debug level') + parser.add_option('-e', '--pipe', help='Pipe name for logging.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 2: + print get_option_parser().get_usage() + return 1 + + path = os.path.abspath(args[1]) + + instance = pynorm.Instance() + instance.setCacheDirectory(path) + + try: + instance.openDebugPipe(opts.pipe) + except pynorm.NormError: + print 'Could not connect to pipe, disabling...' + pynorm.setDebugLevel(opts.debug) + + manager = Manager(instance) + manager.register(pynorm.NORM_RX_OBJECT_INFO, newObject, path) +# manager.register(pynorm.NORM_RX_OBJECT_UPDATED, updatedObject) + manager.register(pynorm.NORM_RX_OBJECT_COMPLETED, complete) + manager.register(pynorm.NORM_RX_OBJECT_ABORTED, abort) + manager.start() + + session = instance.createSession(opts.address, opts.port) + if opts.iface: + session.setMulticastInterface(opts.iface) + session.startReceiver(1024*1024) + + print 'Starting listener on %s:%i' % (opts.address, opts.port) + try: + while True: + manager.join(2) + except KeyboardInterrupt: + pass + print 'Exiting...' + instance.stop() + manager.join() + return 0 + +def newObject(event, path): + print 'Filename = %s' % event.object.getInfo() + event.object.filename = os.path.join(path, event.object.getInfo()) + print 'Downloading file %s' % event.object.filename + +def updatedObject(event): + print 'File %s - %i bytes left to download' % (event.object.filename, + event.object.bytesPending) + +def complete(event): + print 'File %s completed' % event.object.filename + raise StopManager() + +def abort(event): + print 'File %s aborted' % event.object.filename + raise StopManager() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/normFileFeedback.py b/examples/python/normFileFeedback.py new file mode 100755 index 0000000..32b8ef5 --- /dev/null +++ b/examples/python/normFileFeedback.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +''' +Simple NORM file sender example app using Python NORM API +''' + +import sys +import os.path +import pickle +from optparse import OptionParser +from random import randint + +import pynorm +import pynorm.constants as c + +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 + +def get_option_parser(): + parser = OptionParser() + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT) + + parser.add_option('-s', '--send', + help='The file to send.') + parser.add_option('-r', '--receive', + help='The directory to cache recieved files.') + parser.add_option('-a', '--address', + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 1: + print 'Error: Invalid arguments' + print get_option_parser().format_help() + return 1 + + if opts.send is None and opts.receive is None: + print 'No operation specified!' + print 'Must provide a --send or --receive flag!' + print get_option_parser().format_help() + return 1 + + instance = pynorm.Instance() + session = instance.createSession(opts.address, opts.port) + if opts.iface: + session.setMulticastInterface(opts.iface) + + session.setTxRate(256e10) + session.startReceiver(1024*1024) + session.startSender(randint(0, 1000), 1024**2, 1400, 64, 16) + + if opts.receive is not None: + path = os.path.abspath(opts.receive) + instance.setCacheDirectory(path) + print 'Setting cache directory to %s' % path + + if opts.send is not None: + filepath = os.path.abspath(opts.send) + filename = opts.send[opts.send.rfind('/')+1:] + print 'Sending file %s' % filename + session.fileEnqueue(filepath, filename) + + try: + for event in instance: + if event.type == c.NORM_TX_FLUSH_COMPLETED: + if event.object.type == c.NORM_OBJECT_FILE: + print 'Flush completed for file %s' % event.object.filename + + elif event.type == c.NORM_RX_OBJECT_INFO: + if event.object.type == c.NORM_OBJECT_FILE: + event.object.filename = os.path.join(path, event.object.info) + print 'Downloading file %s' % event.object.filename + + elif event.object.type == c.NORM_OBJECT_DATA: + # I put the sender node ID in the info field + # If it doesn't match ours, we dont care about it + if int(event.object.info) != session.nodeId: + event.object.cancel() + + elif event.type == c.NORM_RX_OBJECT_UPDATED: + if event.object.type == c.NORM_OBJECT_FILE: + print 'File %s - %i bytes left to download' % ( + event.object.filename, event.object.bytesPending) + + # Let the sender know how much we have done + data = pickle.dumps((event.object.filename, event.object.bytesPending), -1) + session.dataEnqueue(data, str(event.object.sender.id)) + + elif event == 'NORM_RX_OBJECT_COMPLETED': + if event.object.type == c.NORM_OBJECT_FILE: + print 'File %s completed' % event.object.filename + + elif event.object.type == c.NORM_OBJECT_DATA: + try: + # This fails sometimes, not sure why yet, so ignore errors + filename, pending = pickle.loads(event.object.accessData()) + except KeyError: + continue + print 'Node %i - File: %s - Pending: %i' % (event.object.sender.id, filename, pending) + + elif event == 'NORM_RX_OBJECT_ABORTED': + if event.object.type == c.NORM_OBJECT_FILE: + print 'File %s aborted' % event.object.filename + except KeyboardInterrupt: + pass + + print 'Exiting.' + return 0 +# end main + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/normFileRecv.py b/examples/python/normFileRecv.py new file mode 100755 index 0000000..723207e --- /dev/null +++ b/examples/python/normFileRecv.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +''' +Simple NORM file receiver example app using Python NORM API +''' + +import sys, os.path +from optparse import OptionParser + +import pynorm + +USAGE = 'usage: %s [options] ' % sys.argv[0] +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT) + + parser.add_option('-a', '--address', + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 2: + print get_option_parser().get_usage() + return 1 + + path = os.path.abspath(args[1]) + + instance = pynorm.Instance() + instance.setCacheDirectory(path) + + session = instance.createSession(opts.address, opts.port) + if opts.iface: + session.setMulticastInterface(opts.iface) + session.startReceiver(1024*1024) + + try: + for event in instance: + if event == 'NORM_RX_OBJECT_INFO': + event.object.filename = os.path.join(path, event.object.info) + print 'Downloading file %s' % event.object.filename + + elif event == 'NORM_RX_OBJECT_UPDATED': + print 'File %s - %i bytes left to download' % ( + event.object.filename, event.object.bytesPending) + + elif event == 'NORM_RX_OBJECT_COMPLETED': + print 'File %s completed' % event.object.filename + return 0 + + elif event == 'NORM_RX_OBJECT_ABORTED': + print 'File %s aborted' % event.object.filename + return 1 + + else: + print event + except KeyboardInterrupt: + pass + + print 'Exiting.' + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/normFileSend.py b/examples/python/normFileSend.py new file mode 100755 index 0000000..750d21e --- /dev/null +++ b/examples/python/normFileSend.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +''' +Simple NORM file sender example app using Python NORM API +''' + +import sys, os.path +from optparse import OptionParser +from random import randint + +import pynorm + +USAGE = 'usage: %s [options] ' % sys.argv[0] +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT) + + parser.add_option('-a', '--address', + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 2: + print get_option_parser().get_usage() + return 1 + + filepath = os.path.abspath(args[1]) + filename = args[1][args[1].rfind('/')+1:] + + instance = pynorm.Instance() + + session = instance.createSession(opts.address, opts.port) + if opts.iface: + session.setMulticastInterface(opts.iface) + session.setTxRate(256e10) + session.startSender(randint(0, 1000), 1024**2, 1400, 64, 16) + + print 'Sending file %s' % filename + session.fileEnqueue(filepath, filename) + + try: + for event in instance: + if event == 'NORM_TX_FLUSH_COMPLETED': + print 'Flush completed, exiting.' + return 0 + else: + print event + except KeyboardInterrupt: + pass + + print 'Exiting.' + return 0 +# end main + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/rawRecv.py b/examples/python/rawRecv.py new file mode 100755 index 0000000..5010c03 --- /dev/null +++ b/examples/python/rawRecv.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +''' +Example of using the NORM library directly. +You really shouldn't need to do this. Use the pretty API instead. +But its here if you need it. +''' + +import sys +import os.path +import ctypes +from optparse import OptionParser + +from pynorm.core import libnorm, NormEventStruct +import pynorm.constants as c + +USAGE = 'usage: %s [options] ' % sys.argv[0] +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT) + + parser.add_option('-a', '--address' + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 2: + print get_option_parser().get_usage() + return 1 + + path = os.path.abspath(args[1]) + + instance = libnorm.NormCreateInstance(False) + session = libnorm.NormCreateSession(instance, opts.address, opts.port, + c.NORM_NODE_ANY) + + if opts.iface: + libnorm.NormSetMulticastInterface(session, opts.iface) + + libnorm.NormSetCacheDirectory(instance, path) + libnorm.NormStartReceiver(session, 1024*1024) + theEvent = NormEventStruct() + + try: + while libnorm.NormGetNextEvent(instance, ctypes.byref(theEvent)): + if theEvent.type == c.NORM_RX_OBJECT_NEW: + print 'rawRecv.py: NORM_RX_OBJECT_NEW event ...' + + elif theEvent.type == c.NORM_RX_OBJECT_INFO: + print 'rafRecv.py: NORM_RX_OBJECT_INFO event ...' + + if c.NORM_OBJECT_FILE == libnorm.NormObjectGetType(theEvent.object): + length = libnorm.NormObjectGetInfoLength(theEvent.object) + buffer = ctypes.create_string_buffer(length) + recv = libnorm.NormObjectGetInfo(theEvent.object, buffer, length) + if recv == 0: + print 'Error' + filename = os.path.join(path, buffer.value) + print 'Filename - %s' % filename + libnorm.NormFileRename(theEvent.object, filename) + + elif theEvent.type == c.NORM_RX_OBJECT_UPDATED: + size = libnorm.NormObjectGetSize(theEvent.object) + completed = size - libnorm.NormObjectGetBytesPending(theEvent.object) + percent = 100.0 * (float(completed) / float(size)) + print '%.1f completed' % percent + + elif theEvent.type == c.NORM_RX_OBJECT_COMPLETED: + print 'Complete' + return 0 + + elif theEvent.type == c.NORM_RX_OBJECT_ABORTED: + print 'Aborted' + return 0 + + elif theEvent.type == c.NORM_REMOTE_SENDER_NEW: + print 'New sender' + + elif theEvent.type == c.NORM_REMOTE_SENDER_ACTIVE: + print 'Sender active' + + elif theEvent.type == c.NORM_REMOTE_SENDER_INACTIVE: + print 'Sender inactive' + except KeyboardInterrupt: + pass + + libnorm.NormStopReceiver(session) + libnorm.NormDestroySession(session) + libnorm.NormDestroyInstance(instance) + print 'Exiting.' + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/examples/python/streamChat.py b/examples/python/streamChat.py new file mode 100755 index 0000000..e21979d --- /dev/null +++ b/examples/python/streamChat.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +''' +Simple NORM file receiver example app using Python NORM API +Shows off streaming with a super simple chat app. +''' + +import sys +import os.path +import curses +import curses.textpad +from threading import Thread +from optparse import OptionParser +from random import randint + +import pynorm +from pynorm.extra.manager import Manager, StopManager + +USAGE = 'usage: %s [options] name' % sys.argv[0] +DEFAULT_ADDR = '224.1.2.3' +DEFAULT_PORT = 6003 + +def get_option_parser(): + parser = OptionParser(usage=USAGE) + parser.set_defaults(address=DEFAULT_ADDR, port=DEFAULT_PORT) + + parser.add_option('-a', '--address', + help='The IP address to bind to (default %s)' % DEFAULT_ADDR) + parser.add_option('-p', '--port', type=int, + help='The port number to listen on (default %i)' % DEFAULT_PORT) + parser.add_option('-i', '--iface', + help='The inteface to transmit multicast on.') + return parser + +def main(argv): + (opts, args) = get_option_parser().parse_args(argv) + + if len(args) != 2: + print get_option_parser().get_usage() + return 1 + + instance = pynorm.Instance() + session = instance.createSession(opts.address, opts.port) + + if opts.iface: + session.setMulticastInterface(opts.iface) + + session.startReceiver(1024*1024) + session.startSender(randint(0, 1000), 1024**2, 1400, 64, 16) + stream = session.streamOpen(1024*1024) + + gui = Gui(stream, args[1]) + + manager = Manager(instance) + manager.register(pynorm.NORM_RX_OBJECT_UPDATED, + lambda e: gui.showText(e.object.streamRead(1024)[1])) +# manager.register(pynorm.NORM_RX_OBJECT_INFO, +# lambda e: gui.showText('%s joined the chat' % e.object.info)) + manager.start() + + try: + curses.wrapper(gui) + except KeyboardInterrupt: + pass + + print 'Exiting...' + stream.streamClose(True) + instance.stop() + manager.join() + return 0 + +class Gui(object): + def __init__(self, stream, name): + self.stream = stream + self.name = name + self.curline = 0 + + def __call__(self, stdscr): + self.stdscr = stdscr + maxy, maxx = stdscr.getmaxyx() + + self.chatwin = curses.newwin(maxy - 2, maxx, 0, 0) + self.chatwin.scrollok(True) + + typewin = curses.newwin(1, maxx, maxy-1, 0) + + textbox = curses.textpad.Textbox(typewin) + while True: + self.send(textbox.edit()) + typewin.erase() + + def send(self, text): + msg = '%s: %s' % (self.name, text) + self.stream.streamWrite(msg) + self.stream.streamFlush(True) + self.showText(msg) + + def showText(self, msg): + maxy, maxx = self.stdscr.getmaxyx() + if self.curline >= maxy: + self.curline = maxy - 1 + + self.chatwin.addstr(self.curline, 0, msg) + self.curline += 1 + self.chatwin.refresh() + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/common/galois.h b/include/galois.h similarity index 100% rename from common/galois.h rename to include/galois.h diff --git a/common/normApi.h b/include/normApi.h similarity index 64% rename from common/normApi.h rename to include/normApi.h index fff5cec..81e79ff 100644 --- a/common/normApi.h +++ b/include/normApi.h @@ -47,6 +47,17 @@ typedef uint32_t UINT32; // C++ code continues to evolve. But, until this notice // is removed, the API shouldn't be considered final. +#ifndef __cplusplus +# include +# define DEFAULT(arg) +#else +# define DEFAULT(arg) = arg +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + /** NORM API Types */ typedef const void* NormInstanceHandle; extern NORM_API_LINKAGE @@ -78,69 +89,89 @@ typedef off_t NormSize; #endif // WIN32 NORM_API_LINKAGE -enum NormObjectType +typedef enum NormObjectType { NORM_OBJECT_NONE, NORM_OBJECT_DATA, NORM_OBJECT_FILE, NORM_OBJECT_STREAM -}; +} NormObjectType; NORM_API_LINKAGE -enum NormFlushMode +typedef enum NormFlushMode { NORM_FLUSH_NONE, NORM_FLUSH_PASSIVE, NORM_FLUSH_ACTIVE -}; +} NormFlushMode; NORM_API_LINKAGE -enum NormNackingMode +typedef enum NormNackingMode { NORM_NACK_NONE, NORM_NACK_INFO_ONLY, NORM_NACK_NORMAL -}; +} NormNackingMode; NORM_API_LINKAGE -enum NormAckingStatus +typedef enum NormAckingStatus { NORM_ACK_INVALID, NORM_ACK_FAILURE, NORM_ACK_PENDING, NORM_ACK_SUCCESS -}; +} NormAckingStatus; + +NORM_API_LINKAGE +typedef enum NormTrackingStatus +{ + NORM_TRACK_NONE, + NORM_TRACK_RECEIVERS, + NORM_TRACK_SENDERS, + NORM_TRACK_ALL +} NormTrackingStatus; + NORM_API_LINKAGE -enum NormProbingMode +typedef enum NormProbingMode { NORM_PROBE_NONE, NORM_PROBE_PASSIVE, NORM_PROBE_ACTIVE -}; +} NormProbingMode; NORM_API_LINKAGE -enum NormRepairBoundary +typedef enum NormSyncPolicy +{ + NORM_SYNC_CURRENT, // attempt to receiver current/new objects only + NORM_SYNC_ALL // attempt to receive old and new objects +} NormSyncPolicy; + +NORM_API_LINKAGE +typedef enum NormRepairBoundary { NORM_BOUNDARY_BLOCK, NORM_BOUNDARY_OBJECT -}; +} NormRepairBoundary; NORM_API_LINKAGE -enum NormEventType +typedef enum NormEventType { NORM_EVENT_INVALID = 0, NORM_TX_QUEUE_VACANCY, NORM_TX_QUEUE_EMPTY, NORM_TX_FLUSH_COMPLETED, NORM_TX_WATERMARK_COMPLETED, + NORM_TX_CMD_SENT, NORM_TX_OBJECT_SENT, NORM_TX_OBJECT_PURGED, + NORM_TX_RATE_CHANGED, NORM_LOCAL_SENDER_CLOSED, NORM_REMOTE_SENDER_NEW, NORM_REMOTE_SENDER_ACTIVE, NORM_REMOTE_SENDER_INACTIVE, NORM_REMOTE_SENDER_PURGED, + NORM_RX_CMD_NEW, NORM_RX_OBJECT_NEW, NORM_RX_OBJECT_INFO, NORM_RX_OBJECT_UPDATED, @@ -149,7 +180,7 @@ enum NormEventType NORM_GRTT_UPDATED, NORM_CC_ACTIVE, NORM_CC_INACTIVE -}; +} NormEventType; typedef struct { @@ -162,7 +193,7 @@ typedef struct /** NORM API General Initialization and Operation Functions */ NORM_API_LINKAGE -NormInstanceHandle NormCreateInstance(bool priorityBoost = false); +NormInstanceHandle NormCreateInstance(bool priorityBoost DEFAULT(false)); NORM_API_LINKAGE void NormDestroyInstance(NormInstanceHandle instanceHandle); @@ -190,7 +221,7 @@ bool NormSetCacheDirectory(NormInstanceHandle instanceHandle, // This call blocks until the next NormEvent is ready unless asynchronous // notification is used (see below) NORM_API_LINKAGE -bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent); +bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent, bool waitForEvent DEFAULT(true)); // The "NormGetDescriptor()" function returns a HANDLE (WIN32) or // a file descriptor (UNIX) which can be used for async notification @@ -215,7 +246,7 @@ NormDescriptor NormGetDescriptor(NormInstanceHandle instanceHandle); NORM_API_LINKAGE NormSessionHandle NormCreateSession(NormInstanceHandle instanceHandle, const char* sessionAddress, - UINT16 sessionPort, + UINT16 sessionPort, NormNodeId localNodeId); NORM_API_LINKAGE @@ -230,14 +261,45 @@ const void* NormGetUserData(NormSessionHandle sessionHandle); NORM_API_LINKAGE NormNodeId NormGetLocalNodeId(NormSessionHandle sessionHandle); + NORM_API_LINKAGE -void NormSetTxPort(NormSessionHandle sessionHandle, - UINT16 txPortNumber); +bool NormSetTxPort(NormSessionHandle sessionHandle, + UINT16 txPortNumber, + bool enableReuse DEFAULT(false), + const char* txBindAddress DEFAULT((const char*)0)); // if non-NULL, bind() to / + +NORM_API_LINKAGE +UINT16 NormGetTxPort(NormSessionHandle sessionHandle); + +NORM_API_LINKAGE +void NormSetTxOnly(NormSessionHandle sessionHandle, + bool txOnly, + bool connectToSessionAddress DEFAULT(false)); + + +// This does not affect the rx_socket binding if already bound +// (i.e., just affects where NORM packets are sent) +NORM_API_LINKAGE +bool NormChangeDestination(NormSessionHandle sessionHandle, + const char* sessionAddress, + UINT16 sessionPort); NORM_API_LINKAGE void NormSetRxPortReuse(NormSessionHandle sessionHandle, - bool enable, - bool bindToSessionAddress = true); + bool enableReuse, + const char* rxBindAddress DEFAULT((const char*)0), // if non-NULL, bind() to / + const char* senderAddress DEFAULT((const char*)0), // if non-NULL, connect() to / + UINT16 senderPort DEFAULT(0)); + +NORM_API_LINKAGE +UINT16 NormGetRxPort(NormSessionHandle sessionHandle); + +// TBD - We should probably have a "NormSetCCMode(NormCCMode ccMode)" function for users +NORM_API_LINKAGE +void NormSetEcnSupport(NormSessionHandle sessionHandle, + bool ecnEnable, // enables NORM ECN (congestion control) support + bool ignoreLoss DEFAULT(false), // With "ecnEnable", use ECN-only, ignoring packet loss + bool tolerateLoss DEFAULT(false)); // loss-tolerant congestion control, ecnEnable or not, ignoreLoss = false NORM_API_LINKAGE bool NormSetMulticastInterface(NormSessionHandle sessionHandle, @@ -255,21 +317,52 @@ NORM_API_LINKAGE bool NormSetLoopback(NormSessionHandle sessionHandle, bool loopback); + +NORM_API_LINKAGE +bool NormSetFragmentation(NormSessionHandle sessionHandle, + bool fragmentation); + // Special functions for debug support NORM_API_LINKAGE void NormSetMessageTrace(NormSessionHandle sessionHandle, bool state); + NORM_API_LINKAGE void NormSetTxLoss(NormSessionHandle sessionHandle, double percent); + NORM_API_LINKAGE void NormSetRxLoss(NormSessionHandle sessionHandle, double percent); +NORM_API_LINKAGE +bool NormOpenDebugLog(NormInstanceHandle instanceHandle, const char *path); + +NORM_API_LINKAGE +void NormCloseDebugLog(NormInstanceHandle instanceHandle); + +NORM_API_LINKAGE +bool NormOpenDebugPipe(NormInstanceHandle instanceHandle, const char *pipeName); + +NORM_API_LINKAGE +void NormCloseDebugPipe(NormInstanceHandle instanceHandle); + +NORM_API_LINKAGE +void NormSetDebugLevel(unsigned int level); + +NORM_API_LINKAGE +unsigned int NormGetDebugLevel(); + +NORM_API_LINKAGE +void NormSetReportInterval(NormSessionHandle sessionHandle, double interval); + +NORM_API_LINKAGE +double NormGetReportInterval(NormSessionHandle sessionHandle); + /** NORM Sender Functions */ NORM_API_LINKAGE bool NormStartSender(NormSessionHandle sessionHandle, - NormSessionId sessionId, - UINT32 bufferSpace, - UINT16 segmentSize, + NormSessionId instanceId, + UINT32 bufferSpace, + UINT16 segmentSize, unsigned char numData, unsigned char numParity); @@ -277,30 +370,34 @@ NORM_API_LINKAGE void NormStopSender(NormSessionHandle sessionHandle); NORM_API_LINKAGE -double NormGetTransmitRate(NormSessionHandle sessionHandle); - +void NormSetTxRate(NormSessionHandle sessionHandle, + double bitsPerSecond); NORM_API_LINKAGE -void NormSetTransmitRate(NormSessionHandle sessionHandle, - double bitsPerSecond); +double NormGetTxRate(NormSessionHandle sessionHandle); NORM_API_LINKAGE bool NormSetTxSocketBuffer(NormSessionHandle sessionHandle, unsigned int bufferSize); +NORM_API_LINKAGE +void NormSetFlowControl(NormSessionHandle sessionHandle, + double flowControlFactor); + NORM_API_LINKAGE void NormSetCongestionControl(NormSessionHandle sessionHandle, - bool state); + bool enable, + bool adjustRate DEFAULT(true)); NORM_API_LINKAGE -void NormSetTransmitRateBounds(NormSessionHandle sessionHandle, - double rateMin, - double rateMax); +void NormSetTxRateBounds(NormSessionHandle sessionHandle, + double rateMin, + double rateMax); NORM_API_LINKAGE -void NormSetTransmitCacheBounds(NormSessionHandle sessionHandle, - NormSize sizeMax, - UINT32 countMin, - UINT32 countMax); +void NormSetTxCacheBounds(NormSessionHandle sessionHandle, + NormSize sizeMax, + UINT32 countMin, + UINT32 countMax); NORM_API_LINKAGE void NormSetAutoParity(NormSessionHandle sessionHandle, @@ -340,26 +437,29 @@ void NormSetTxRobustFactor(NormSessionHandle sessionHandle, NORM_API_LINKAGE NormObjectHandle NormFileEnqueue(NormSessionHandle sessionHandle, - const char* fileName, - const char* infoPtr = (const char*)0, - unsigned int infoLen = 0); + const char* fileName, + const char* infoPtr DEFAULT((const char*)0), + unsigned int infoLen DEFAULT(0)); NORM_API_LINKAGE NormObjectHandle NormDataEnqueue(NormSessionHandle sessionHandle, const char* dataPtr, - UINT32 dataLen, - const char* infoPtr = (const char*)0, - unsigned int infoLen = 0); + UINT32 dataLen, + const char* infoPtr DEFAULT((const char*)0), + unsigned int infoLen DEFAULT(0)); NORM_API_LINKAGE bool NormRequeueObject(NormSessionHandle sessionHandle, NormObjectHandle objectHandle); NORM_API_LINKAGE NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle, - UINT32 bufferSize); + UINT32 bufferSize, + const char* infoPtr DEFAULT((const char*)0), + unsigned int infoLen DEFAULT(0)); +// TBD - we should add a "bool watermark" option to "graceful" stream closure??? NORM_API_LINKAGE -void NormStreamClose(NormObjectHandle streamHandle, bool graceful = false); +void NormStreamClose(NormObjectHandle streamHandle, bool graceful DEFAULT(false)); NORM_API_LINKAGE unsigned int NormStreamWrite(NormObjectHandle streamHandle, @@ -368,8 +468,8 @@ unsigned int NormStreamWrite(NormObjectHandle streamHandle, NORM_API_LINKAGE void NormStreamFlush(NormObjectHandle streamHandle, - bool eom = false, - NormFlushMode flushMode = NORM_FLUSH_PASSIVE); + bool eom DEFAULT(false), + NormFlushMode flushMode DEFAULT(NORM_FLUSH_PASSIVE)); NORM_API_LINKAGE void NormStreamSetAutoFlush(NormObjectHandle streamHandle, @@ -387,7 +487,10 @@ void NormStreamMarkEom(NormObjectHandle streamHandle); NORM_API_LINKAGE bool NormSetWatermark(NormSessionHandle sessionHandle, - NormObjectHandle objectHandle); + NormObjectHandle objectHandle, + bool overrideFlush DEFAULT(false)); +NORM_API_LINKAGE +bool NormResetWatermark(NormSessionHandle sessionHandle); NORM_API_LINKAGE void NormCancelWatermark(NormSessionHandle sessionHandle); @@ -400,19 +503,41 @@ NORM_API_LINKAGE void NormRemoveAckingNode(NormSessionHandle sessionHandle, NormNodeId nodeId); +NORM_API_LINKAGE +void NormSetAutoAckingNodes(NormSessionHandle sessionHandle, + NormTrackingStatus trackingStatus); + NORM_API_LINKAGE NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, - NormNodeId nodeId = NORM_NODE_ANY); + NormNodeId nodeId DEFAULT(NORM_NODE_ANY)); +NORM_API_LINKAGE +bool NormGetNextAckingNode(NormSessionHandle sessionHandle, + NormNodeId* nodeId, + NormAckingStatus* ackingStatus); + +NORM_API_LINKAGE +bool NormSendCommand(NormSessionHandle sessionHandle, + const char* cmdBuffer, + unsigned int cmdLength, + bool robust DEFAULT(false)); + +NORM_API_LINKAGE +void NormCancelCommand(NormSessionHandle sessionHandle); + /* NORM Receiver Functions */ NORM_API_LINKAGE bool NormStartReceiver(NormSessionHandle sessionHandle, - UINT32 bufferSpace); + UINT32 bufferSpace); NORM_API_LINKAGE void NormStopReceiver(NormSessionHandle sessionHandle); +NORM_API_LINKAGE +void NormSetRxCacheLimit(NormSessionHandle sessionHandle, + unsigned short countMax); + NORM_API_LINKAGE bool NormSetRxSocketBuffer(NormSessionHandle sessionHandle, unsigned int bufferSize); @@ -420,7 +545,7 @@ bool NormSetRxSocketBuffer(NormSessionHandle sessionHandle, NORM_API_LINKAGE void NormSetSilentReceiver(NormSessionHandle sessionHandle, bool silent, - int maxDelay = -1); + int maxDelay DEFAULT(-1)); NORM_API_LINKAGE void NormSetDefaultUnicastNack(NormSessionHandle sessionHandle, @@ -430,12 +555,16 @@ NORM_API_LINKAGE void NormNodeSetUnicastNack(NormNodeHandle remoteSender, bool unicastNacks); +NORM_API_LINKAGE +void NormSetDefaultSyncPolicy(NormSessionHandle sessionHandle, + NormSyncPolicy syncPolicy); + NORM_API_LINKAGE void NormSetDefaultNackingMode(NormSessionHandle sessionHandle, NormNackingMode nackingMode); NORM_API_LINKAGE -void NormNodeSetNackingMode(NormNodeHandle nodeHandle, +void NormNodeSetNackingMode(NormNodeHandle remoteSender, NormNackingMode nackingMode); NORM_API_LINKAGE @@ -447,7 +576,7 @@ void NormSetDefaultRepairBoundary(NormSessionHandle sessionHandle, NormRepairBoundary repairBoundary); NORM_API_LINKAGE -void NormNodeSetRepairBoundary(NormNodeHandle nodeHandle, +void NormNodeSetRepairBoundary(NormNodeHandle remoteSender, NormRepairBoundary repairBoundary); NORM_API_LINKAGE @@ -455,7 +584,7 @@ void NormSetDefaultRxRobustFactor(NormSessionHandle sessionHandle, int robustFactor); NORM_API_LINKAGE -void NormNodeSetRxRobustFactor(NormNodeHandle nodeHandle, +void NormNodeSetRxRobustFactor(NormNodeHandle remoteSender, int robustFactor); NORM_API_LINKAGE @@ -469,6 +598,8 @@ bool NormStreamSeekMsgStart(NormObjectHandle streamHandle); NORM_API_LINKAGE UINT32 NormStreamGetReadOffset(NormObjectHandle streamHandle); +NORM_API_LINKAGE +UINT32 NormStreamGetBufferUsage(NormObjectHandle streamHandle); /** NORM Object Functions */ @@ -483,8 +614,8 @@ UINT16 NormObjectGetInfoLength(NormObjectHandle objectHandle); NORM_API_LINKAGE UINT16 NormObjectGetInfo(NormObjectHandle objectHandle, - char* buffer, - UINT16 bufferLen); + char* buffer, + UINT16 bufferLen); NORM_API_LINKAGE NormSize NormObjectGetSize(NormObjectHandle objectHandle); @@ -511,7 +642,7 @@ bool NormFileRename(NormObjectHandle fileHandle, const char* fileName); NORM_API_LINKAGE -const char*volatile NormDataAccessData(NormObjectHandle objectHandle); +const char* NormDataAccessData(NormObjectHandle objectHandle); NORM_API_LINKAGE char* NormDataDetachData(NormObjectHandle objectHandle); @@ -528,23 +659,29 @@ NORM_API_LINKAGE bool NormNodeGetAddress(NormNodeHandle nodeHandle, char* addrBuffer, unsigned int* bufferLen, - UINT16* port = (UINT16*)0); + UINT16* port DEFAULT((UINT16*)0)); NORM_API_LINKAGE -double NormNodeGetGrtt(NormNodeHandle nodeHandle); +double NormNodeGetGrtt(NormNodeHandle remoteSender); + NORM_API_LINKAGE -void NormNodeFreeBuffers(NormNodeHandle nodeHandle); +bool NormNodeGetCommand(NormNodeHandle remoteSender, + char* buffer, + unsigned int* buflen); + +NORM_API_LINKAGE +void NormNodeFreeBuffers(NormNodeHandle remoteSender); + +NORM_API_LINKAGE +void NormNodeDelete(NormNodeHandle remoteSender); // The next 4 functions have not yet been implemented // (work in progress) NORM_API_LINKAGE -void NormNodeSetAutoDelete(NormNodeHandle nodeHandle, +void NormNodeSetAutoDelete(NormNodeHandle remoteSender, bool autoDelete); -NORM_API_LINKAGE -void NormNodeDeleteSender(NormNodeHandle nodeHandle); - NORM_API_LINKAGE bool NormNodeAllowSender(NormNodeId senderId); @@ -563,4 +700,8 @@ void NormNodeRelease(NormNodeHandle nodeHandle); NORM_API_LINKAGE UINT32 NormCountCompletedObjects(NormSessionHandle sessionHandle); +#ifdef __cplusplus +} // end extern "C" +#endif /* __cplusplus */ + #endif // _NORM_API diff --git a/include/normEncoder.h b/include/normEncoder.h new file mode 100644 index 0000000..3c87875 --- /dev/null +++ b/include/normEncoder.h @@ -0,0 +1,56 @@ +/********************************************************************* + * + * AUTHORIZATION TO USE AND DISTRIBUTE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: + * + * (1) source code distributions retain this paragraph in its entirety, + * + * (2) distributions including binary code include this paragraph in + * its entirety in the documentation or other materials provided + * with the distribution, and + * + * (3) all advertising materials mentioning features or use of this + * software display the following acknowledgment: + * + * "This product includes software written and developed + * by Brian Adamson and Joe Macker of the Naval Research + * Laboratory (NRL)." + * + * The name of NRL, the name(s) of NRL employee(s), or any entity + * of the United States Government may not be used to endorse or + * promote products derived from this software, nor does the + * inclusion of the NRL written and developed software directly or + * indirectly suggest NRL or United States Government endorsement + * of this product. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ********************************************************************/ + +#ifndef _NORM_ENCODER +#define _NORM_ENCODER + +#include "protokit.h" // protolib stuff + +class NormEncoder +{ + public: + virtual ~NormEncoder(); + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize) = 0; + virtual void Destroy() = 0; + virtual void Encode(unsigned int segmentId, const char *dataVector, char **parityVectorList) = 0; +}; // end class NormEncoder + +class NormDecoder +{ + public: + virtual ~NormDecoder(); + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize) = 0; + virtual void Destroy() = 0; + virtual int Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) = 0; +}; // end class NormDecoder + +#endif // _NORM_ENCODER diff --git a/common/normEncoder.h b/include/normEncoderMDP.h similarity index 70% rename from common/normEncoder.h rename to include/normEncoderMDP.h index fbf777d..37657fb 100644 --- a/common/normEncoder.h +++ b/include/normEncoderMDP.h @@ -30,43 +30,22 @@ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ********************************************************************/ -#ifndef _NORM_ENCODER -#define _NORM_ENCODER +#ifndef _NORM_ENCODER_MDP +#define _NORM_ENCODER_MDP -#include "protokit.h" // protolib stuff +#include "normEncoder.h" -class NormEncoder -{ - public: - virtual ~NormEncoder(); - virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize) = 0; - virtual void Destroy() = 0; - virtual void Encode(const char *dataVector, char **parityVectorList) = 0; -}; // end class NormEncoder - -class NormDecoder -{ - public: - virtual ~NormDecoder(); - virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize) = 0; - virtual void Destroy() = 0; - virtual int Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) = 0; -}; // end class NormDecoder - - - -class NormEncoderRS8a : public NormEncoder +class NormEncoderMDP : public NormEncoder { // Methods public: - NormEncoderRS8a(); - ~NormEncoderRS8a(); + NormEncoderMDP(); + ~NormEncoderMDP(); bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); void Destroy(); bool IsReady(){return (bool)(gen_poly != NULL);} - - // "Encode" must be called in order of source vector0, vector1, vector2, etc - void Encode(const char *dataVector, char **parityVectorList); + // "Encode" MUST be called in order of source vector0, vector1, vector2, etc + void Encode(unsigned int segmentId, const char *dataVector, char **parityVectorList); private: bool CreateGeneratorPolynomial(); @@ -77,15 +56,15 @@ class NormEncoderRS8a : public NormEncoder unsigned char* gen_poly; // Ptr to generator polynomial unsigned char* scratch; // scratch space for encoding -}; // end class NormEncoderRS8a +}; // end class NormEncoderMDP -class NormDecoderRS8a : public NormDecoder +class NormDecoderMDP : public NormDecoder { // Methods public: - NormDecoderRS8a(); - ~NormDecoderRS8a(); + NormDecoderMDP(); + ~NormDecoderMDP(); bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); int Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs); int NumParity() {return npar;} @@ -101,8 +80,7 @@ class NormDecoderRS8a : public NormDecoder unsigned char** o_vec; // Omega vectors (pointers to "npar" vectors) unsigned char* scratch; -}; // end class NormDecoderRS8a +}; // end class NormDecoderMDP - -#endif // _NORM_ENCODER +#endif // _NORM_ENCODER_MDP diff --git a/include/normEncoderRS16.h b/include/normEncoderRS16.h new file mode 100644 index 0000000..74d0df5 --- /dev/null +++ b/include/normEncoderRS16.h @@ -0,0 +1,68 @@ +#ifndef _NORM_ENCODER_RS16 +#define _NORM_ENCODER_RS16 + +#include "normEncoder.h" +#include "protoDefs.h" // for UINT16 + +class NormEncoderRS16 : public NormEncoder +{ + public: + NormEncoderRS16(); + ~NormEncoderRS16(); + + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); + virtual void Destroy(); + virtual void Encode(unsigned int segmentId, const char* dataVector, char** parityVectorList); + + unsigned int GetNumData() + {return ndata;} + unsigned int GetNumParity() + {return npar;} + unsigned int GetVectorSize() + {return vector_size;} + + private: + unsigned int ndata; // max data pkts per block (k) + unsigned int npar; // No. of parity packets (n-k) + unsigned int vector_size; // Size of biggest vector to encode + UINT8* enc_matrix; + unsigned int enc_index; + +}; // end class NormEncoderRS16 + + +class NormDecoderRS16 : public NormDecoder +{ + public: + NormDecoderRS16(); + virtual ~NormDecoderRS16(); + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); + virtual void Destroy(); + virtual int Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs); + + unsigned int GetNumParity() + {return npar;} + unsigned int GetVectorSize() + {return vector_size;} + + private: + bool InvertDecodingMatrix(); // used in Decode() method + + unsigned int ndata; // max data pkts per block (k) + unsigned int npar; // No. of parity packets (n-k) + UINT16 vector_size; // Size of biggest vector to encode + UINT8* enc_matrix; + UINT8* dec_matrix; + unsigned int* parity_loc; + + // These "inv_" members are used in InvertDecodingMatrix() + unsigned int* inv_ndxc; + unsigned int* inv_ndxr; + unsigned int* inv_pivt; + UINT8* inv_id_row; + UINT8* inv_temp_row; + +}; // end class NormDecoderRS16 + + +#endif // _NORM_ENCODER_RS16 diff --git a/include/normEncoderRS8.h b/include/normEncoderRS8.h new file mode 100644 index 0000000..aa93a05 --- /dev/null +++ b/include/normEncoderRS8.h @@ -0,0 +1,69 @@ +#ifndef _NORM_ENCODER_RS8 +#define _NORM_ENCODER_RS8 + +#include "normEncoder.h" +#include "protoDefs.h" // for UINT16 + +class NormEncoderRS8 : public NormEncoder +{ + public: + NormEncoderRS8(); + ~NormEncoderRS8(); + + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); + virtual void Destroy(); + virtual void Encode(unsigned int segmentId, const char* dataVector, char** parityVectorList); + + unsigned int GetNumData() + {return ndata;} + unsigned int GetNumParity() + {return npar;} + unsigned int GetVectorSize() + {return vector_size;} + + private: + unsigned int ndata; // max data pkts per block (k) + unsigned int npar; // No. of parity packets (n-k) + unsigned int vector_size; // Size of biggest vector to encode + UINT8* enc_matrix; + +}; // end class NormEncoder + + +class NormDecoderRS8 : public NormDecoder +{ + public: + NormDecoderRS8(); + virtual ~NormDecoderRS8(); + virtual bool Init(unsigned int numData, unsigned int numParity, UINT16 vectorSize); + virtual void Destroy(); + virtual int Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs); + + unsigned int GetNumParity() + {return npar;} + unsigned int GetVectorSize() + {return vector_size;} + + private: + bool InvertDecodingMatrix(); // used in Decode() method + + unsigned int ndata; // max data pkts per block (k) + unsigned int npar; // No. of parity packets (n-k) + UINT16 vector_size; // Size of biggest vector to encode + UINT8* enc_matrix; + UINT8* dec_matrix; + unsigned int* parity_loc; + + // These "inv_" members are used in InvertDecodingMatrix() + unsigned int* inv_ndxc; + unsigned int* inv_ndxr; + unsigned int* inv_pivt; + UINT8* inv_id_row; + UINT8* inv_temp_row; + +}; // end class NormDecoder + + + + +#endif // _NORM_ENCODER_RS8 diff --git a/common/normFile.h b/include/normFile.h similarity index 100% rename from common/normFile.h rename to include/normFile.h diff --git a/common/normMessage.h b/include/normMessage.h similarity index 59% rename from common/normMessage.h rename to include/normMessage.h index 29f1b31..67be134 100644 --- a/common/normMessage.h +++ b/include/normMessage.h @@ -16,6 +16,10 @@ #endif // if/else _WIN32_WCE #ifdef SIMULATE +// IMPORTANT! This _assumes_ that the message header of interest _will_ be +// aligned with a NormSegment (i.e. flush mode of active or passive +// is used with flushing after _each_ message written to a +// NORM_OBJECT_STREAM! #define SIM_PAYLOAD_MAX (36+8) // MGEN message size + StreamPayloadHeaderLen() #endif // SIMULATE @@ -28,9 +32,7 @@ const double NORM_TICK_MIN = 0.100; // in seconds // Pick a random number from 0..max inline double UniformRand(double max) -{ - return (max * ((double)rand() / (double)RAND_MAX)); -} + {return (max * ((double)rand() / (double)RAND_MAX));} // Pick a random number from 0..max // (truncated exponential dist. lambda = log(groupSize) + 1) @@ -43,7 +45,7 @@ inline double ExponentialRand(double max, double groupSize) // These are the GRTT estimation bounds set for the current // NORM protocol. (Note that our Grtt quantization routines -// are good for the range of 1.0e-06 <= 1000.0) +// are good for the range of 1.0e-06 <= 1000.0 seconds) const double NORM_GRTT_MIN = 0.001; // 1 msec const double NORM_GRTT_MAX = 15.0; // 15 sec @@ -51,17 +53,12 @@ const double NORM_RTT_MIN = 1.0e-06; const double NORM_RTT_MAX = 1000.0; extern const double NORM_RTT[]; inline double NormUnquantizeRtt(UINT8 qrtt) -{ - return NORM_RTT[qrtt]; -} + {return NORM_RTT[qrtt];} UINT8 NormQuantizeRtt(double rtt); - extern const double NORM_GSIZE[]; inline double NormUnquantizeGroupSize(UINT8 gsize) -{ - return NORM_GSIZE[gsize]; -} + {return NORM_GSIZE[gsize];} UINT8 NormQuantizeGroupSize(double gsize); @@ -77,6 +74,24 @@ inline double NormUnquantizeLoss(UINT16 lossQuantized) return (((double)lossQuantized) / 65535.0); } // end NormUnquantizeLossFraction() + +// Extended precision Norm loss quantize/unquantize with +// 32-bit precision (needed for low BER, high bandwidth*delay) +inline UINT32 NormQuantizeLoss32(double lossFraction) +{ + const double MAX_SCALE = (double)((unsigned int)0xffffffff); + lossFraction = MAX(lossFraction, 0.0); + lossFraction = lossFraction*MAX_SCALE + 0.5; + lossFraction = MIN(lossFraction, MAX_SCALE); + return (UINT32)lossFraction; +} // end NormQuantizeLossFraction32() +inline double NormUnquantizeLoss32(UINT32 lossQuantized) +{ + const double MAX_SCALE = (double)((unsigned int)0xffffffff); + return (((double)lossQuantized) / MAX_SCALE); +} // end NormUnquantizeLossFraction32() + + inline UINT16 NormQuantizeRate(double rate) { if (rate <= 0.0) return 0x01; // rate = 0.0 @@ -166,12 +181,21 @@ class NormObjectId NormObjectId(UINT16 id) {value = id;} NormObjectId(const NormObjectId& id) {value = id.value;} operator UINT16() const {return value;} - INT16 operator-(const NormObjectId& id) const - {return ((INT16)(value - id.value));} + //INT16 operator-(const NormObjectId& id) const + // {return ((INT16)(value - id.value));} + bool operator<(const NormObjectId& id) const - {return (((short)(value - id.value)) < 0);} + { + UINT16 diff = value - id.value; + return ((diff > 0x8000) || ((0x8000 == diff) && (value > id.value))); + } + bool operator>(const NormObjectId& id) const - {return (((INT16)(value - id.value)) > 0);} + { + UINT16 diff = id.value - value; + return ((diff > 0x8000) || ((0x8000 == diff) && (id.value > value))); + } + bool operator<=(const NormObjectId& id) const {return ((value == id.value) || (*this=(const NormObjectId& id) const @@ -180,6 +204,11 @@ class NormObjectId {return (value == id.value);} bool operator!=(const NormObjectId& id) const {return (value != id.value);} + + void operator-=(UINT16 delta) + {value -= delta;} + + NormObjectId& operator++(int) {value++; return *this;} NormObjectId& operator--(int) {value--; return *this;} @@ -198,12 +227,24 @@ class NormBlockId {return (value == (UINT32)id);} bool operator!=(const NormBlockId& id) const {return (value != (UINT32)id);} - INT32 operator-(const NormBlockId& id) const - {return ((INT32)value - id.value);} + + + //INT32 operator-(const NormBlockId& id) const + // {return ((INT32)value - id.value);} + bool operator<(const NormBlockId& id) const - {return (((INT32)(value-id.value)) < 0);} + { + UINT32 diff = value - id.value; + return ((diff > 0x80000000) || ((0x80000000 == diff) && (value > id.value))); + } + bool operator>(const NormBlockId& id) const - {return (((INT32)(value-id.value)) > 0);} + { + UINT32 diff = id.value - value; + return ((diff > 0x80000000) || ((0x80000000 == diff) && (id.value > value))); + } + + NormBlockId& operator++(int ) {value++; return *this;} private: @@ -263,12 +304,188 @@ class NormHeaderExtension }; // end class NormHeaderExtension +// This class is some we use to set/get +// FEC Payload Id content. The FEC Payload +// Id format is dependent upon the "fec_id" (FEC Type) +// and, in some cases, its field size ("m") parameter +class NormPayloadId +{ + public: + enum FecType + { + RS = 2, // fully-specified, general purpose Reed-Solomon + RS8 = 5, // fully-specified 8-bit Reed-Solmon per RFC 5510 + SB = 129 // partially-specified "small block" codes + }; + static bool IsValid(UINT8 fecId) + { + switch (fecId) + { + case 2: + case 5: + case 129: + return true; + default: + return false; + } + } + NormPayloadId(UINT8 fecId, UINT8 m, UINT32* theBuffer) + : fec_id(fecId), fec_m(m), buffer(theBuffer) {} + NormPayloadId(UINT8 fecId, UINT8 m, const UINT32* theBuffer) + : fec_id(fecId), fec_m(m), cbuffer(theBuffer) {} + + static UINT16 GetLength(UINT8 fecId) + { + switch (fecId) + { + case 2: + case 5: + return 4; + case 129: + return 8; + default: + return 0; + } + } + + static UINT32 GetFecBlockMask(UINT8 fecId, UINT8 fecM) + { + switch (fecId) + { + case 2: + if (8 == fecM) + return 0x00ffffff; // 24-bit blockId, 8-bit symbolId + else // (16 == fec_m) + return 0x0000ffff; // 16-bit blockId,, 16-bit symbolId + case 5: + return 0x00ffffff; // 24-bit blockId + case 129: + return 0xffffffff; // 32-bit blockId + default: + return 0x00000000; // invalid fecId + } + } + + void SetFecPayloadId(UINT32 blockId, UINT16 symbolId, UINT16 blockLen) + { + switch (fec_id) + { + case 2: + if (8 == fec_m) + { + blockId = (blockId << 8) | (symbolId & 0x00ff); + *buffer = htonl(blockId); // 3 + 1 bytes + } + else // (16 == fec_m) + { + UINT16* payloadId = (UINT16*)buffer; + payloadId[0] = htons(blockId); // 2 bytes + payloadId[1] = htons(symbolId); // 2 bytes + } + break; + case 5: + blockId = (blockId << 8) | (symbolId & 0x00ff); + *buffer = htonl(blockId); // 3 + 1 bytes + break; + case 129: + *buffer = htonl(blockId); // 4 bytes + UINT16* ptr = (UINT16*)(buffer + 1); + ptr[0] = htons(blockLen); // 2 bytes + ptr[1] = htons(symbolId); // 2 bytes + break; + } + } + + // Message processing methods + NormBlockId GetFecBlockId() const + { + switch (fec_id) + { + case 2: + if (8 == fec_m) + { + UINT32 blockId = ntohl(*cbuffer); + return (0x00ffffff & (blockId >> 8)); + } + else // (16 == fec_m) + { + UINT16* blockId = (UINT16*)cbuffer; + return ntohs(*blockId); + } + case 5: + { + UINT32 blockId = ntohl(*cbuffer); + return (0x00ffffff & (blockId >> 8)); + } + case 129: + return ntohl(*cbuffer); + default: + ASSERT(0); + return 0; + } + } + + UINT16 GetFecSymbolId() const + { + switch (fec_id) + { + case 2: + if (8 == fec_m) + { + UINT32 payloadId = ntohl(*cbuffer); + return (0x000000ff & payloadId); // lsb is symbolId + } + else // ( 16 == fec_m) + { + UINT16* payloadId = (UINT16*)cbuffer; + return ntohs(payloadId[1]); + } + case 5: + { + UINT32 payloadId = ntohl(*cbuffer); + return (0x000000ff & payloadId); // lsb is symbolId + } + case 129: + { + UINT16* ptr = (UINT16*)(cbuffer + 1); + return ntohs(ptr[1]); + } + default: + ASSERT(0); + return 0; + } + } + + UINT16 GetFecBlockLength() const + { + if (129 == fec_id) + { + UINT16* blockLen = (UINT16*)(cbuffer + 1); + return ntohs(*blockLen); + } + else + { + return 0; + } + } + + + + private: + UINT8 fec_id; + UINT8 fec_m; + union + { + UINT32* buffer; + const UINT32* cbuffer; + }; +}; // end class NormPayloadId + class NormMsg { friend class NormMessageQueue; public: - // (TBD) Use this "Type" enumeration instead of NormMsgType enum Type { INVALID = 0, @@ -279,7 +496,7 @@ class NormMsg ACK = 5, REPORT = 6 }; - enum {MAX_SIZE = 8192}; + enum {MAX_SIZE = 65536}; NormMsg(); @@ -312,6 +529,12 @@ class NormMsg // Message processing routines bool InitFromBuffer(UINT16 msgLength); + bool CopyFromBuffer(const char* theBuffer, unsigned int theLength) + { + if (theLength > MAX_SIZE) return false; + memcpy(buffer, theBuffer, theLength); + return InitFromBuffer(theLength); + } UINT8 GetVersion() const {return (((UINT8*)buffer)[VERSION_OFFSET] >> 4);} NormMsg::Type GetType() const @@ -404,42 +627,34 @@ class NormObjectMsg : public NormMsg //FLAG_MSG_START = 0x40 deprecated }; UINT16 GetInstanceId() const - { - return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET])); - } - UINT8 GetGrtt() const {return ((UINT8*)buffer)[GRTT_OFFSET];} - UINT8 GetBackoffFactor() const {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} - UINT8 GetGroupSize() const {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} + {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} + UINT8 GetGrtt() const + {return ((UINT8*)buffer)[GRTT_OFFSET];} + UINT8 GetBackoffFactor() const + {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} + UINT8 GetGroupSize() const + {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} bool FlagIsSet(NormObjectMsg::Flag flag) const {return (0 != (flag & ((UINT8*)buffer)[FLAGS_OFFSET]));} - bool IsStream() const {return FlagIsSet(FLAG_STREAM);} - UINT8 GetFecId() const {return ((UINT8*)buffer)[FEC_ID_OFFSET];} + bool IsStream() const + {return FlagIsSet(FLAG_STREAM);} + UINT8 GetFecId() const + {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const - { - return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]));} // Message building routines void SetInstanceId(UINT16 instanceId) - { - ((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId); - } + {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrtt(UINT8 grtt) {((UINT8*)buffer)[GRTT_OFFSET] = grtt;} void SetBackoffFactor(UINT8 backoff) - { - ((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4); - } + {((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4);} void SetGroupSize(UINT8 gsize) - { - ((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize; - } + {((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize;} void ResetFlags() {((UINT8*)buffer)[FLAGS_OFFSET] = 0;} void SetFlag(NormObjectMsg::Flag flag) {((UINT8*)buffer)[FLAGS_OFFSET] |= flag;} - void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} void SetObjectId(const NormObjectId& objectId) - { - ((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId); - } + {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} protected: enum @@ -456,8 +671,116 @@ class NormObjectMsg : public NormMsg }; // end class NormObjectMsg +// This FEC Object Transmission Information assumes "fec_id" == 2 (RFC 5510) +// (This is an m-bit Reed-Solomon codec (we use it in NORM for m == 16) +class NormFtiExtension2 : public NormHeaderExtension +{ + public: + // To build the FTI Header Extension + virtual void Init(UINT32* theBuffer) + { + AttachBuffer(theBuffer); + SetType(FTI); // HET = 64 + SetWords(4); + } + void SetObjectSize(const NormObjectSize& objectSize) + { + ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); + buffer[OBJ_SIZE_LSB_OFFSET] = htonl(objectSize.LSB()); + } + void SetFecFieldSize(UINT8 numBits) + {((UINT8*)buffer)[FEC_M_OFFSET] = numBits;} // usually 16 for this FTI + void SetFecGroupSize(UINT8 symbolsPerPkt) + {((UINT8*)buffer)[FEC_G_OFFSET] = symbolsPerPkt;} // usually one + void SetSegmentSize(UINT16 segmentSize) + {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} + void SetFecMaxBlockLen(UINT16 ndata) + {((UINT16*)buffer)[FEC_NDATA_OFFSET] = htons(ndata);} + void SetFecNumParity(UINT16 nparity) + {((UINT16*)buffer)[FEC_NPARITY_OFFSET] = htons(nparity);} + + // FTI Extension parsing methods + NormObjectSize GetObjectSize() const + { + return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), + ntohl(buffer[OBJ_SIZE_LSB_OFFSET])); + } + UINT8 GetFecFieldSize() const + {return ((UINT8*)buffer)[FEC_M_OFFSET];} // usually 16 for this FTI + UINT8 GetFecGroupSize() const + {return ((UINT8*)buffer)[FEC_G_OFFSET];} // usually 1 + UINT16 GetSegmentSize() const + {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} + UINT16 GetFecMaxBlockLen() const + {return (ntohs(((UINT16*)buffer)[FEC_NDATA_OFFSET]));} + UINT16 GetFecNumParity() const + {return (ntohs(((UINT16*)buffer)[FEC_NPARITY_OFFSET]));} + + private: + enum + { + OBJ_SIZE_MSB_OFFSET = (LENGTH_OFFSET + 1)/2, + OBJ_SIZE_LSB_OFFSET = ((OBJ_SIZE_MSB_OFFSET*2)+2)/4, + FEC_M_OFFSET = ((OBJ_SIZE_LSB_OFFSET*4)+4), + FEC_G_OFFSET = FEC_M_OFFSET + 1, + SEG_SIZE_OFFSET = (FEC_G_OFFSET+1)/2, + FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET*2)+2)/2, + FEC_NPARITY_OFFSET = ((FEC_NDATA_OFFSET*2)+2)/2 + }; +}; // end class NormFtiExtension2 + + +// This FEC Object Transmission Information assumes "fec_id" == 5 (RFC 5510) +// (this is the fully-defined 8-bit Reed-Solomon codec) +class NormFtiExtension5 : public NormHeaderExtension +{ + public: + // To build the fec_id=5 FTI Header Extension + virtual void Init(UINT32* theBuffer) + { + AttachBuffer(theBuffer); + SetType(FTI); // HET = 64 + SetWords(3); + } + void SetObjectSize(const NormObjectSize& objectSize) + { + ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); + buffer[OBJ_SIZE_LSB_OFFSET] = htonl(objectSize.LSB()); + } + void SetSegmentSize(UINT16 segmentSize) + {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} + void SetFecMaxBlockLen(UINT8 ndata) + {((UINT8*)buffer)[FEC_NDATA_OFFSET] = ndata;} + void SetFecNumParity(UINT8 nparity) + {((UINT8*)buffer)[FEC_NPARITY_OFFSET] = nparity;} + + // FTI Extension parsing methods + NormObjectSize GetObjectSize() const + { + return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), + ntohl(buffer[OBJ_SIZE_LSB_OFFSET])); + } + UINT16 GetSegmentSize() const + {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} + UINT8 GetFecMaxBlockLen() const + {return (((UINT8*)buffer)[FEC_NDATA_OFFSET]);} + UINT8 GetFecNumParity() const + {return (((UINT8*)buffer)[FEC_NPARITY_OFFSET]);} + + private: + enum + { + OBJ_SIZE_MSB_OFFSET = (LENGTH_OFFSET + 1)/2, + OBJ_SIZE_LSB_OFFSET = ((OBJ_SIZE_MSB_OFFSET*2)+2)/4, + SEG_SIZE_OFFSET = ((OBJ_SIZE_LSB_OFFSET*4)+4)/2, + FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET+1)*2), + FEC_NPARITY_OFFSET = (FEC_NDATA_OFFSET+1) + }; +}; // end class NormFtiExtension5 + + // This FEC Object Transmission Information assumes "fec_id" == 129 -class NormFtiExtension : public NormHeaderExtension +class NormFtiExtension129 : public NormHeaderExtension { public: // To build the FTI Header Extension @@ -469,21 +792,13 @@ class NormFtiExtension : public NormHeaderExtension SetWords(4); } void SetFecInstanceId(UINT16 instanceId) - { - ((UINT16*)buffer)[FEC_INSTANCE_OFFSET] = htons(instanceId); - } + { ((UINT16*)buffer)[FEC_INSTANCE_OFFSET] = htons(instanceId);} void SetFecMaxBlockLen(UINT16 ndata) - { - ((UINT16*)buffer)[FEC_NDATA_OFFSET] = htons(ndata); - } + {((UINT16*)buffer)[FEC_NDATA_OFFSET] = htons(ndata);} void SetFecNumParity(UINT16 nparity) - { - ((UINT16*)buffer)[FEC_NPARITY_OFFSET] = htons(nparity); - } + {((UINT16*)buffer)[FEC_NPARITY_OFFSET] = htons(nparity);} void SetSegmentSize(UINT16 segmentSize) - { - ((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize); - } + {((UINT16*)buffer)[SEG_SIZE_OFFSET] = htons(segmentSize);} void SetObjectSize(const NormObjectSize& objectSize) { ((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET] = htons(objectSize.MSB()); @@ -496,17 +811,11 @@ class NormFtiExtension : public NormHeaderExtension return (ntohs(((UINT16*)buffer)[FEC_INSTANCE_OFFSET])); } UINT16 GetFecMaxBlockLen() const - { - return (ntohs(((UINT16*)buffer)[FEC_NDATA_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[FEC_NDATA_OFFSET]));} UINT16 GetFecNumParity() const - { - return (ntohs(((UINT16*)buffer)[FEC_NPARITY_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[FEC_NPARITY_OFFSET]));} UINT16 GetSegmentSize() const - { - return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[SEG_SIZE_OFFSET]));} NormObjectSize GetObjectSize() const { return NormObjectSize(ntohs(((UINT16*)buffer)[OBJ_SIZE_MSB_OFFSET]), @@ -523,7 +832,7 @@ class NormFtiExtension : public NormHeaderExtension FEC_NDATA_OFFSET = ((SEG_SIZE_OFFSET*2)+2)/2, FEC_NPARITY_OFFSET = ((FEC_NDATA_OFFSET*2)+2)/2 }; -}; // end class NormFtiExtension +}; // end class NormFtiExtension129 class NormInfoMsg : public NormObjectMsg @@ -533,13 +842,17 @@ class NormInfoMsg : public NormObjectMsg { SetType(INFO); SetBaseHeaderLength(INFO_HEADER_LEN); - // Default "fec_id" = 129 - SetFecId(129); ResetFlags(); } - UINT16 GetInfoLen() const {return (length - header_length);} - const char* GetInfo() const {return (((char*)buffer) + header_length);} + UINT16 GetInfoLen() const + {return (length - header_length);} + const char* GetInfo() const + {return (((char*)buffer) + header_length);} + + // Message building methods (in addition to NormObjectMsg fields) + void SetFecId(UINT8 fecId) + {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} // Note: apply any header extensions first void SetInfo(const char* data, UINT16 size) @@ -547,6 +860,7 @@ class NormInfoMsg : public NormObjectMsg memcpy(((char*)buffer)+header_length, data, size); length = size + header_length; } + private: enum {INFO_HEADER_LEN = OBJ_MSG_OFFSET}; }; // end class NormInfoMsg @@ -557,23 +871,21 @@ class NormDataMsg : public NormObjectMsg void Init() { SetType(DATA); - // Default "fec_id" = 129 w/ "fec_payload_id" length = 8 bytes - SetBaseHeaderLength(DATA_HEADER_LEN); - SetFecId(129); ResetFlags(); + // Note: for NORM_DATA base header length depends on fec_id } + // Message building methods (in addition to NormObjectMsg fields) - void SetFecBlockId(const NormBlockId& blockId) + void SetFecId(UINT8 fecId) { - buffer[BLOCK_ID_OFFSET] = htonl((UINT32)blockId); + ((UINT8*)buffer)[FEC_ID_OFFSET] = fecId; + SetBaseHeaderLength(OBJ_MSG_OFFSET + NormPayloadId::GetLength(fecId)); } - void SetFecBlockLen(UINT16 blockLen) + + void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { - ((UINT16*)buffer)[BLOCK_LEN_OFFSET] = htons(blockLen); - } - void SetFecSymbolId(UINT16 symbolId) - { - ((UINT16*)buffer)[SYMBOL_ID_OFFSET] = htons(symbolId); + NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); + payloadId.SetFecPayloadId(blockId, symbolId, blockLen); } // Two ways to set payload content: @@ -583,9 +895,7 @@ class NormDataMsg : public NormObjectMsg char* AccessPayload() {return (((char*)buffer)+header_length);} // For NORM_STREAM_OBJECT segments, "dataLength" must include the PAYLOAD_HEADER_LENGTH void SetPayloadLength(UINT16 payloadLength) - { - length = header_length + payloadLength; - } + {length = header_length + payloadLength;} // Set "payload" directly (useful for FEC parity segments) void SetPayload(char* payload, UINT16 payloadLength) { @@ -600,21 +910,30 @@ class NormDataMsg : public NormObjectMsg } // Message processing methods - NormBlockId GetFecBlockId() const - {return (ntohl(buffer[BLOCK_ID_OFFSET]));} - UINT16 GetFecBlockLen() const - {return (ntohs(((UINT16*)buffer)[BLOCK_LEN_OFFSET]));} - UINT16 GetFecSymbolId() const - {return (ntohs(((UINT16*)buffer)[SYMBOL_ID_OFFSET]));} - bool IsData() const - {return (GetFecSymbolId() < GetFecBlockLen());} - + NormBlockId GetFecBlockId(UINT8 m) const + { + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockId(); + } + UINT16 GetFecSymbolId(UINT8 m) const + { + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecSymbolId(); + } + UINT16 GetFecBlockLength() const + { + NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockLength(); + } + // Note: For NORM_OBJECT_STREAM, "payload" includes "payload_reserved", // "payload_len", "payload_offset", and "payload_data" fields // For NORM_OBJECT_FILE and NORM_OBJECT_DATA, "payload" includes // "payload_data" only - const char* GetPayload() const {return (((char*)buffer)+header_length);} - UINT16 GetPayloadLength() const {return (length - header_length);} + const char* GetPayload() + const {return (((char*)buffer)+header_length);} + UINT16 GetPayloadLength() + const {return (length - header_length);} const char* GetPayloadData() const { @@ -627,30 +946,11 @@ class NormDataMsg : public NormObjectMsg return (length - dataIndex); } - // These routines are only applicable to messages containing NORM_OBJECT_STREAM content // Some static helper routines for reading/writing embedded payload length/offsets - static UINT16 GetStreamPayloadHeaderLength() {return (PAYLOAD_DATA_OFFSET);} + static UINT16 GetStreamPayloadHeaderLength() + {return (PAYLOAD_DATA_OFFSET);} - /* Payload flags have deprecated in the revised NORM spec - enum PayloadFlag - { - FLAG_STREAM_END = 0x01, - FLAG_MSG_START = 0x04 - }; - static bool StreamPayloadFlagIsSet(const char* payload, - PayloadFlag flag) - { - return (0 != (flag & ((UINT8*)payload)[PAYLOAD_FLAGS_OFFSET])); - } - static void ResetStreamPayloadFlags(char* payload) - { - ((UINT8*)payload)[PAYLOAD_FLAGS_OFFSET] = 0; - } - static void SetStreamPayloadFlag(char* payload, PayloadFlag flag) - { - ((UINT8*)payload)[PAYLOAD_FLAGS_OFFSET] |= flag; - }*/ static void WriteStreamPayloadLength(char* payload, UINT16 len) { UINT16 temp16 = htons(len); @@ -688,14 +988,13 @@ class NormDataMsg : public NormObjectMsg private: enum { - BLOCK_ID_OFFSET = OBJ_MSG_OFFSET/4, - BLOCK_LEN_OFFSET = ((BLOCK_ID_OFFSET*4)+4)/2, - SYMBOL_ID_OFFSET = ((BLOCK_LEN_OFFSET*2)+2)/2, - DATA_HEADER_LEN = (SYMBOL_ID_OFFSET*2)+2 + FEC_PAYLOAD_ID_OFFSET = OBJ_MSG_OFFSET/4 }; + + // IMPORTANT: These offsets are _relative_ to the NORM_DATA header + // (incl. any extensions) enum { - //PAYLOAD_FLAGS_OFFSET = 0, // deprecated PAYLOAD_LENGTH_OFFSET = 0, PAYLOAD_MSG_START_OFFSET = PAYLOAD_LENGTH_OFFSET+2, PAYLOAD_OFFSET_OFFSET = PAYLOAD_MSG_START_OFFSET+2, @@ -719,30 +1018,29 @@ class NormCmdMsg : public NormMsg APPLICATION = 7 }; - // Message building + // Message build void SetInstanceId(UINT16 instanceId) - { - ((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId); - } + {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrtt(UINT8 quantizedGrtt) {((UINT8*)buffer)[GRTT_OFFSET] = quantizedGrtt;} void SetBackoffFactor(UINT8 backoff) - { - ((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4); - } + {((UINT8*)buffer)[BACKOFF_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f) | (backoff << 4);} void SetGroupSize(UINT8 gsize) - { - ((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize; - } + {((UINT8*)buffer)[GSIZE_OFFSET] = (((UINT8*)buffer)[GSIZE_OFFSET] & 0xf0) | gsize;} void SetFlavor(NormCmdMsg::Flavor flavor) {((UINT8*)buffer)[FLAVOR_OFFSET] = (UINT8)flavor;} - // Message processing - UINT16 GetInstanceId() const {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} - UINT8 GetGrtt() const {return ((UINT8*)buffer)[GRTT_OFFSET];} - UINT8 GetBackoffFactor() const {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} - UINT8 GetGroupSize() const {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} - NormCmdMsg::Flavor GetFlavor() const {return (Flavor)((UINT8*)buffer)[FLAVOR_OFFSET];} + // Message parse + UINT16 GetInstanceId() const + {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} + UINT8 GetGrtt() const + {return ((UINT8*)buffer)[GRTT_OFFSET];} + UINT8 GetBackoffFactor() const + {return ((((UINT8*)buffer)[GSIZE_OFFSET] >> 4) & 0x0f);} + UINT8 GetGroupSize() const + {return (((UINT8*)buffer)[GSIZE_OFFSET] & 0x0f);} + NormCmdMsg::Flavor GetFlavor() const + {return (Flavor)((UINT8*)buffer)[FLAVOR_OFFSET];} protected: friend class NormMsg; @@ -765,29 +1063,24 @@ class NormCmdFlushMsg : public NormCmdMsg { SetType(CMD); SetFlavor(FLUSH); - SetBaseHeaderLength(FLUSH_HEADER_LEN); - SetFecId(129); // default "fec_id" + // base header length depends on fec payload id } - void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} + void SetFecId(UINT8 fecId) + {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} void SetObjectId(const NormObjectId& objectId) + {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} + void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { - ((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId); + SetFecId(fecId); + SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); + NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); + payloadId.SetFecPayloadId(blockId, symbolId, blockLen); + ResetAckingNodeList(); } - // "fec_payload_id" fields, assuming "fec_id" = 129 - void SetFecBlockId(const NormBlockId& blockId) - { - buffer[BLOCK_ID_OFFSET] = htonl((UINT32)blockId); - } - void SetFecBlockLen(UINT16 blockLen) - { - ((UINT16*)buffer)[BLOCK_LEN_OFFSET] = htons((UINT16)blockLen); - } - void SetFecSymbolId(UINT16 symbolId) - { - ((UINT16*)buffer)[SYMBOL_ID_OFFSET] = htons((UINT16)symbolId); - } - void ResetAckingNodeList() {length = header_length;} + + void ResetAckingNodeList() + {length = header_length;} bool AppendAckingNode(NormNodeId nodeId, UINT16 segmentSize) { if ((length-header_length+ 4) > segmentSize) return false; @@ -797,39 +1090,39 @@ class NormCmdFlushMsg : public NormCmdMsg } // Message processing - UINT8 GetFecId() {return ((UINT8*)buffer)[FEC_ID_OFFSET];} + UINT8 GetFecId() const + {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const + { return ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]);} + NormBlockId GetFecBlockId(UINT8 m) const { - return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET])); + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockId(); } - NormBlockId GetFecBlockId() const + UINT16 GetFecSymbolId(UINT8 m) const { - return (ntohl(buffer[BLOCK_ID_OFFSET])); - } - UINT16 GetFecBlockLen() const + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecSymbolId(); + } + UINT16 GetFecBlockLength() const { - return (ntohs(((UINT16*)buffer)[BLOCK_LEN_OFFSET])); - } - UINT16 GetFecSymbolId() const - { - return (ntohs(((UINT16*)buffer)[SYMBOL_ID_OFFSET])); - } - UINT16 GetAckingNodeCount() const {return ((length - header_length) >> 2);} - const UINT32* GetAckingNodeList() const {return (buffer+(header_length/4));} + NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockLength(); + } + + UINT16 GetAckingNodeCount() const + {return ((length - header_length) >> 2);} + const UINT32* GetAckingNodeList() const + {return (buffer+(header_length/4));} NormNodeId GetAckingNodeId(UINT16 index) const - { - return (ntohl(buffer[(header_length/4)+index])); - } + {return (ntohl(buffer[(header_length/4)+index]));} private: enum { - FEC_ID_OFFSET = FLAVOR_OFFSET + 1, - OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, - BLOCK_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4, - BLOCK_LEN_OFFSET = ((BLOCK_ID_OFFSET*4)+4)/2, - SYMBOL_ID_OFFSET = ((BLOCK_LEN_OFFSET*2)+2)/2, - FLUSH_HEADER_LEN = (SYMBOL_ID_OFFSET*2)+2 + FEC_ID_OFFSET = FLAVOR_OFFSET + 1, + OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, + FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; }; // end class NormCmdFlushMsg @@ -856,34 +1149,29 @@ class NormCmdSquelchMsg : public NormCmdMsg { public: // Message building - void Init() + void Init(UINT8 fecId) { SetType(CMD); SetFlavor(SQUELCH); - SetBaseHeaderLength(SQUELCH_HEADER_LEN); - SetFecId(129); // default "fec_id" + SetFecId(fecId); // default "fec_id" + SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); } void SetFecId(UINT8 fecId) {((UINT8*)buffer)[FEC_ID_OFFSET] = fecId;} void SetObjectId(const NormObjectId& objectId) + {((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId);} + void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { - ((UINT16*)buffer)[OBJ_ID_OFFSET] = htons((UINT16)objectId); - } - // "fec_payload_id" fields, assuming "fec_id" = 129 - void SetFecBlockId(const NormBlockId& blockId) - { - buffer[BLOCK_ID_OFFSET] = htonl((UINT32)blockId); - } - void SetFecBlockLen(UINT16 blockLen) - { - ((UINT16*)buffer)[BLOCK_LEN_OFFSET] = htons((UINT16)blockLen); - } - void SetFecSymbolId(UINT16 symbolId) - { - ((UINT16*)buffer)[SYMBOL_ID_OFFSET] = htons((UINT16)symbolId); + SetFecId(fecId); + SetBaseHeaderLength(4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId)); + NormPayloadId payloadId(fecId, m, buffer + FEC_PAYLOAD_ID_OFFSET); + payloadId.SetFecPayloadId(blockId, symbolId, blockLen); + ResetInvalidObjectList(); } + void ResetInvalidObjectList() + {length = header_length;} - void ResetInvalidObjectList() {length = header_length;} + // Note must apply any header extensions _before_ appending payload. bool AppendInvalidObject(NormObjectId objectId, UINT16 segmentSize) { if ((length-header_length+2) > segmentSize) return false; @@ -893,41 +1181,40 @@ class NormCmdSquelchMsg : public NormCmdMsg } // Message processing - UINT8 GetFecId() {return ((UINT8*)buffer)[FEC_ID_OFFSET];} + UINT8 GetFecId() const + {return ((UINT8*)buffer)[FEC_ID_OFFSET];} NormObjectId GetObjectId() const + {return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET]));} + NormBlockId GetFecBlockId(UINT8 m) const { - return (ntohs(((UINT16*)buffer)[OBJ_ID_OFFSET])); + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockId(); } - NormBlockId GetFecBlockId() const + UINT16 GetFecSymbolId(UINT8 m) const { - return (ntohl(buffer[BLOCK_ID_OFFSET])); - } - UINT16 GetFecBlockLen() const + NormPayloadId payloadId(GetFecId(), m, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecSymbolId(); + } + UINT16 GetFecBlockLength() const { - return (ntohs(((UINT16*)buffer)[BLOCK_LEN_OFFSET])); - } - UINT16 GetFecSymbolId() const - { - return (ntohs(((UINT16*)buffer)[SYMBOL_ID_OFFSET])); + NormPayloadId payloadId(GetFecId(), 8, buffer + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockLength(); } // Use these to parse invalid object list - UINT16 GetInvalidObjectCount() const {return ((length - header_length) >> 1);} - UINT16* GetInvalidObjectList() const {return (UINT16*)(buffer+header_length);} + UINT16 GetInvalidObjectCount() const + {return ((length - header_length) >> 1);} + UINT16* GetInvalidObjectList() const + {return (UINT16*)(buffer+header_length);} NormObjectId GetInvalidObjectId(UINT16 index) const - { - return (ntohs(((UINT16*)buffer)[(header_length/2)+index])); - } + {return (ntohs(((UINT16*)buffer)[(header_length/2)+index]));} private: enum { - FEC_ID_OFFSET = FLAVOR_OFFSET + 1, - OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, - BLOCK_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4, - BLOCK_LEN_OFFSET = ((BLOCK_ID_OFFSET*4)+4)/2, - SYMBOL_ID_OFFSET = ((BLOCK_LEN_OFFSET*2)+2)/2, - SQUELCH_HEADER_LEN = (SYMBOL_ID_OFFSET*2)+2 + FEC_ID_OFFSET = FLAVOR_OFFSET + 1, + OBJ_ID_OFFSET = (FEC_ID_OFFSET + 1)/2, + FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; }; // end class NormCmdSquelchMsg @@ -943,8 +1230,9 @@ class NormCC PLR = 0x02, RTT = 0x04, START = 0x08, - LEAVE = 0x10 - }; + LEAVE = 0x10, + LIMIT = 0x20 // experimental, non-RFC5740 + }; // (set when included rate is measured, not calculated) }; // end class NormCC class NormCmdCCMsg : public NormCmdMsg @@ -967,11 +1255,9 @@ class NormCmdCCMsg : public NormCmdMsg buffer[SEND_TIME_SEC_OFFSET] = htonl(sendTime.tv_sec); buffer[SEND_TIME_USEC_OFFSET] = htonl(sendTime.tv_usec); } - + UINT16 GetCCSequence() const - { - return (ntohs(((UINT16*)buffer)[CC_SEQUENCE_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[CC_SEQUENCE_OFFSET]));} void GetSendTime(struct timeval& sendTime) const { sendTime.tv_sec = ntohl(buffer[SEND_TIME_SEC_OFFSET]); @@ -1028,7 +1314,7 @@ class NormCmdCCMsg : public NormCmdMsg CC_ITEM_SIZE = (CC_RATE_OFFSET*2)+2 }; -}; // end NormCmdCCMsg +}; // end class NormCmdCCMsg class NormCCRateExtension : public NormHeaderExtension { @@ -1084,23 +1370,34 @@ class NormRepairRequest length = 0; } // (TBD) these could be an enumeration for optimization - static UINT16 RepairItemLength() {return 12;} - static UINT16 RepairRangeLength() {return 24;} - static UINT16 ErasureItemLength() {return 12;} + static UINT16 RepairItemLength(UINT8 fecId) + {return (4 + NormPayloadId::GetLength(fecId));} + static UINT16 RepairRangeLength(UINT8 fecId) + {return (2 * RepairItemLength(fecId));} + static UINT16 ErasureItemLength(UINT8 fecId) + {return RepairItemLength(fecId);} // Repair request building - void SetForm(NormRepairRequest::Form theForm) {form = theForm;} - void ResetFlags() {flags = 0;} - void SetFlag(NormRepairRequest::Flag theFlag) {flags |= theFlag;} - void ClearFlag(NormRepairRequest::Flag theFlag) {flags &= ~theFlag;} + void SetForm(NormRepairRequest::Form theForm) + {form = theForm;} + void ResetFlags() + {flags = 0;} + void SetFlag(NormRepairRequest::Flag theFlag) + {flags |= theFlag;} + void ClearFlag(NormRepairRequest::Flag theFlag) + {flags &= ~theFlag;} // Returns length (each repair item requires 8 bytes of space) - bool AppendRepairItem(const NormObjectId& objectId, + bool AppendRepairItem(UINT8 fecId, + UINT8 fecM, + const NormObjectId& objectId, const NormBlockId& blockId, UINT16 blockLen, UINT16 symbolId); - bool AppendRepairRange(const NormObjectId& startObjectId, + bool AppendRepairRange(UINT8 fecId, + UINT8 fecM, + const NormObjectId& startObjectId, const NormBlockId& startBlockId, UINT16 startBlockLen, UINT16 startSymbolId, @@ -1109,7 +1406,9 @@ class NormRepairRequest UINT16 endBlockLen, UINT16 endSymbolId); - bool AppendErasureCount(const NormObjectId& objectId, + bool AppendErasureCount(UINT8 fecId, + UINT8 fecM, + const NormObjectId& objectId, const NormBlockId& blockId, UINT16 blockLen, UINT16 erasureCount); @@ -1118,31 +1417,40 @@ class NormRepairRequest // Repair request processing UINT16 Unpack(const UINT32* bufferPtr, UINT16 bufferLen); - NormRepairRequest::Form GetForm() const {return form;} + NormRepairRequest::Form GetForm() const + {return form;} bool FlagIsSet(NormRepairRequest::Flag theFlag) const {return (0 != (theFlag & flags));} UINT16 GetLength() const {return length;} + // Outputs textual representation of RepairRequest content + void Log(UINT8 fecId, UINT8 fecM) const; + class Iterator { public: - Iterator(NormRepairRequest& theRequest); + // Checks for matching fecId and assumes constant 'm' ?!?! + Iterator(const NormRepairRequest& theRequest, UINT8 fecId, UINT8 fecM); void Reset() {offset = 0;} bool NextRepairItem(NormObjectId* objectId, NormBlockId* blockId, UINT16* blockLen, UINT16* symbolId); private: - NormRepairRequest& request; - UINT16 offset; + const NormRepairRequest& request; + UINT8 fec_id; + UINT8 fec_m; + UINT16 offset; }; // end class NormRepairRequest::Iterator private: - bool RetrieveRepairItem(UINT16 offset, - NormObjectId* objectId, - NormBlockId* blockId, - UINT16* blockLen, - UINT16* symbolId) const; + UINT16 RetrieveRepairItem(UINT8 fecM, + UINT16 offset, + UINT8* fecId, + NormObjectId* objectId, + NormBlockId* blockId, + UINT16* blockLen, + UINT16* symbolId) const; enum { FORM_OFFSET = 0, @@ -1157,9 +1465,7 @@ class NormRepairRequest FEC_ID_OFFSET = 0, RESERVED_OFFSET = FEC_ID_OFFSET + 1, OBJ_ID_OFFSET = (RESERVED_OFFSET + 1)/2, - BLOCK_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4, - BLOCK_LEN_OFFSET = ((BLOCK_ID_OFFSET*4)+4)/2, - SYMBOL_ID_OFFSET = ((BLOCK_LEN_OFFSET*2)+2)/2 + FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET+1)*2)/4 }; Form form; @@ -1204,12 +1510,12 @@ class NormCmdRepairAdvMsg : public NormCmdMsg // Message processing bool FlagIsSet(NormCmdRepairAdvMsg::Flag flag) const - { - return (0 != ((UINT8)flag | ((UINT8*)buffer)[FLAGS_OFFSET])); - } + {return (0 != ((UINT8)flag | ((UINT8*)buffer)[FLAGS_OFFSET]));} //char* AccessRepairContent() {return (buffer + header_length);} - const UINT32* GetRepairContent() const {return (buffer + header_length/4);} - UINT16 GetRepairContentLength() const {return (length - header_length);} + const UINT32* GetRepairContent() const + {return (buffer + header_length/4);} + UINT16 GetRepairContentLength() const + {return (length - header_length);} private: enum @@ -1221,7 +1527,8 @@ class NormCmdRepairAdvMsg : public NormCmdMsg }; // end class NormCmdRepairAdvMsg - +// TBD - define NormCCFeedbackExtension2 for larger loss encoding range +// the current 16-bit range is too limited for large RTT*rate combos class NormCCFeedbackExtension : public NormHeaderExtension { public: @@ -1231,37 +1538,50 @@ class NormCCFeedbackExtension : public NormHeaderExtension SetType(CC_FEEDBACK); SetWords(3); ((UINT8*)buffer)[CC_FLAGS_OFFSET] = 0; - ((UINT16*)buffer)[CC_RESERVED_OFFSET] = 0; + //((UINT16*)buffer)[CC_RESERVED_OFFSET] = 0; } void SetCCSequence(UINT16 ccSequence) - { - ((UINT16*)buffer)[CC_SEQUENCE_OFFSET] = htons(ccSequence); - } + {((UINT16*)buffer)[CC_SEQUENCE_OFFSET] = htons(ccSequence);} void ResetCCFlags() {((UINT8*)buffer)[CC_FLAGS_OFFSET] = 0;} void SetCCFlag(NormCC::Flag flag) {((UINT8*)buffer)[CC_FLAGS_OFFSET] |= (UINT8)flag;} void SetCCRtt(UINT8 ccRtt) {((UINT8*)buffer)[CC_RTT_OFFSET] = ccRtt;} - void SetCCLoss(UINT16 ccLoss) - {((UINT16*)buffer)[CC_LOSS_OFFSET] = htons(ccLoss);} + //void SetCCLoss(UINT16 ccLoss) + // {((UINT16*)buffer)[CC_LOSS_OFFSET] = htons(ccLoss);} void SetCCRate(UINT16 ccRate) {((UINT16*)buffer)[CC_RATE_OFFSET] = htons(ccRate);} + void SetCCLoss32(UINT32 ccLoss) + { + ccLoss = htonl(ccLoss); + UINT16* ptr = (UINT16*)&ccLoss; + ((UINT16*)buffer)[CC_LOSS_OFFSET] = ptr[0]; // msb + ((UINT16*)buffer)[CC_LOSS_EX_OFFSET] = ptr[1]; // lsb + } + UINT16 GetCCSequence() const {return (ntohs(((UINT16*)buffer)[CC_SEQUENCE_OFFSET]));} UINT8 GetCCFlags() {return ((UINT8*)buffer)[CC_FLAGS_OFFSET];} bool CCFlagIsSet(NormCC::Flag flag) const - { - return (0 != ((UINT8)flag & ((UINT8*)buffer)[CC_FLAGS_OFFSET])); - } - UINT8 GetCCRtt() + {return (0 != ((UINT8)flag & ((UINT8*)buffer)[CC_FLAGS_OFFSET]));} + UINT8 GetCCRtt() const {return ((UINT8*)buffer)[CC_RTT_OFFSET];} - UINT16 GetCCLoss() - {return (ntohs(((UINT16*)buffer)[CC_LOSS_OFFSET]));} - UINT16 GetCCRate() + //UINT16 GetCCLoss() const + // {return (ntohs(((UINT16*)buffer)[CC_LOSS_OFFSET]));} + UINT16 GetCCRate()const {return (ntohs(((UINT16*)buffer)[CC_RATE_OFFSET]));} + + UINT32 GetCCLoss32() const + { + UINT32 lossQuantized; + UINT16* ptr = (UINT16*)&lossQuantized; + ptr[0] = ((UINT16*)buffer)[CC_LOSS_OFFSET]; // msb + ptr[1] = ((UINT16*)buffer)[CC_LOSS_EX_OFFSET]; // lsb + return ntohl(lossQuantized); // return in host byte order + } private: enum @@ -1271,7 +1591,8 @@ class NormCCFeedbackExtension : public NormHeaderExtension CC_RTT_OFFSET = CC_FLAGS_OFFSET + 1, CC_LOSS_OFFSET = (CC_RTT_OFFSET + 1)/2, CC_RATE_OFFSET = ((CC_LOSS_OFFSET*2)+2)/2, - CC_RESERVED_OFFSET = ((CC_RATE_OFFSET*2)+2)/2 + //CC_RESERVED_OFFSET = ((CC_RATE_OFFSET*2)+2)/2 + CC_LOSS_EX_OFFSET = ((CC_RATE_OFFSET*2)+2)/2 // extended precision loss estimate (non-RFC5940 compliant, but compatible) }; }; // end class NormCCFeedbackExtension @@ -1303,9 +1624,12 @@ class NormCmdAckReqMsg : public NormCmdMsg } // Message building - void SetAckType(NormAck::Type ackType) {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} - void SetAckId(UINT8 ackId) {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} - void ResetAckingNodeList() {length = header_length;} + void SetAckType(NormAck::Type ackType) + {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} + void SetAckId(UINT8 ackId) + {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} + void ResetAckingNodeList() + {length = header_length;} bool AppendAckingNode(NormNodeId nodeId, UINT16 segmentSize) { if ((length - header_length + 4) > segmentSize) return false; @@ -1322,10 +1646,7 @@ class NormCmdAckReqMsg : public NormCmdMsg UINT16 GetAckingNodeCount() const {return ((length - header_length) >> 2);} NormNodeId GetAckingNodeId(UINT16 index) const - { - return (ntohl(buffer[(header_length/4)+index])); - } - + {return (ntohl(buffer[(header_length/4)+index]));} private: enum @@ -1338,7 +1659,7 @@ class NormCmdAckReqMsg : public NormCmdMsg }; // end class NormCmdAckReqMsg -class NormCmdApplicationMsg : public NormCmdMsg +class NormCmdAppMsg : public NormCmdMsg { public: void Init() @@ -1357,8 +1678,10 @@ class NormCmdApplicationMsg : public NormCmdMsg return (contentLen <= segmentSize); } - UINT16 GetContentLength() {return (length - header_length);} - const char* GetContent() {return (((char*)buffer)+header_length);} + UINT16 GetContentLength() const + {return (length - header_length);} + const char* GetContent() const + {return (((char*)buffer)+header_length);} private: enum @@ -1366,7 +1689,7 @@ class NormCmdApplicationMsg : public NormCmdMsg RESERVED_OFFSET = FLAVOR_OFFSET + 1, APPLICATION_HEADER_LEN = RESERVED_OFFSET + 3 }; -}; // end class NormCmdApplicationMsg +}; // end class NormCmdAppMsg // Receiver Messages @@ -1378,17 +1701,14 @@ class NormNackMsg : public NormMsg void Init() { SetType(NACK); + ((UINT16*)buffer)[RESERVED_OFFSET] = 0; SetBaseHeaderLength(NACK_HEADER_LEN); } // Message building - void SetServerId(NormNodeId serverId) - { - buffer[SENDER_ID_OFFSET] = htonl(serverId); - } + void SetSenderId(NormNodeId senderId) + {buffer[SENDER_ID_OFFSET] = htonl(senderId);} void SetInstanceId(UINT16 instanceId) - { - ((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId); - } + {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} void SetGrttResponse(const struct timeval& grttResponse) { buffer[GRTT_RESPONSE_SEC_OFFSET] = htonl(grttResponse.tv_sec); @@ -1409,14 +1729,10 @@ class NormNackMsg : public NormMsg } // Message processing - NormNodeId GetServerId() const - { - return (ntohl(buffer[SENDER_ID_OFFSET])); - } + NormNodeId GetSenderId() const + {return (ntohl(buffer[SENDER_ID_OFFSET]));} UINT16 GetInstanceId() const - { - return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} void GetGrttResponse(struct timeval& grttResponse) const { grttResponse.tv_sec = ntohl(buffer[GRTT_RESPONSE_SEC_OFFSET]); @@ -1458,16 +1774,14 @@ class NormAckMsg : public NormMsg SetBaseHeaderLength(ACK_HEADER_LEN); SetAckType(NormAck::INVALID); } - void SetServerId(NormNodeId serverId) - { - buffer[SENDER_ID_OFFSET] = htonl(serverId); - } + void SetSenderId(NormNodeId senderId) + {buffer[SENDER_ID_OFFSET] = htonl(senderId);} void SetInstanceId(UINT16 instanceId) - { - ((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId); - } - void SetAckType(NormAck::Type ackType) {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} - void SetAckId(UINT8 ackId) {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} + {((UINT16*)buffer)[INSTANCE_ID_OFFSET] = htons(instanceId);} + void SetAckType(NormAck::Type ackType) + {((UINT8*)buffer)[ACK_TYPE_OFFSET] = (UINT8)ackType;} + void SetAckId(UINT8 ackId) + {((UINT8*)buffer)[ACK_ID_OFFSET] = ackId;} void SetGrttResponse(const struct timeval& grttResponse) { buffer[GRTT_RESPONSE_SEC_OFFSET] = htonl(grttResponse.tv_sec); @@ -1482,23 +1796,23 @@ class NormAckMsg : public NormMsg } // Message processing - NormNodeId GetServerId() const - { - return (ntohl(buffer[SENDER_ID_OFFSET])); - } + NormNodeId GetSenderId() const + {return (ntohl(buffer[SENDER_ID_OFFSET]));} UINT16 GetInstanceId() const - { - return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET])); - } + {return (ntohs(((UINT16*)buffer)[INSTANCE_ID_OFFSET]));} void GetGrttResponse(struct timeval& grttResponse) const { grttResponse.tv_sec = ntohl(buffer[GRTT_RESPONSE_SEC_OFFSET]); grttResponse.tv_usec = ntohl(buffer[GRTT_RESPONSE_USEC_OFFSET]); } - NormAck::Type GetAckType() const {return (NormAck::Type)((UINT8*)buffer)[ACK_TYPE_OFFSET];} - UINT8 GetAckId() const {return ((UINT8*)buffer)[ACK_ID_OFFSET];} - UINT16 GetPayloadLength() const {return (length - header_length);} - const char* GetPayload() const {return (((char*)buffer) + header_length);} + NormAck::Type GetAckType() const + {return (NormAck::Type)((UINT8*)buffer)[ACK_TYPE_OFFSET];} + UINT8 GetAckId() const + {return ((UINT8*)buffer)[ACK_ID_OFFSET];} + UINT16 GetPayloadLength() const + {return (length - header_length);} + const char* GetPayload() const + {return (((char*)buffer) + header_length);} protected: enum @@ -1508,8 +1822,8 @@ class NormAckMsg : public NormMsg ACK_TYPE_OFFSET = (INSTANCE_ID_OFFSET*2)+2, ACK_ID_OFFSET = ACK_TYPE_OFFSET + 1, GRTT_RESPONSE_SEC_OFFSET = (ACK_ID_OFFSET + 1)/4, - GRTT_RESPONSE_USEC_OFFSET = ((GRTT_RESPONSE_SEC_OFFSET*4)+4)/4, - ACK_HEADER_LEN = (GRTT_RESPONSE_USEC_OFFSET*4)+4 + GRTT_RESPONSE_USEC_OFFSET = ((GRTT_RESPONSE_SEC_OFFSET+1)*4)/4, + ACK_HEADER_LEN = (GRTT_RESPONSE_USEC_OFFSET+1)*4 }; }; // end class NormAckMsg @@ -1521,62 +1835,55 @@ class NormAckFlushMsg : public NormAckMsg SetType(ACK); SetBaseHeaderLength(ACK_HEADER_LEN); SetAckType(NormAck::FLUSH); - SetFecId(129); // only one supported for the moment ((UINT8*)buffer)[RESERVED_OFFSET] = 0; - length = ACK_HEADER_LEN+PAYLOAD_LENGTH; } // Note: must apply any header exts _before_ the payload is set void SetFecId(UINT8 fecId) {((UINT8*)buffer)[header_length+FEC_ID_OFFSET] = fecId;} void SetObjectId(NormObjectId objectId) + {((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET] = htons((UINT16)objectId);} + void SetFecPayloadId(UINT8 fecId, UINT32 blockId, UINT16 symbolId, UINT16 blockLen, UINT8 m) { - ((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET] = htons((UINT16)objectId); - } - void SetFecBlockId(const NormBlockId& blockId) - { - buffer[(header_length/4)+BLOCK_ID_OFFSET] = htonl((UINT32)blockId); - } - void SetFecBlockLen(UINT16 blockLen) - { - ((UINT16*)buffer)[(header_length/2)+BLOCK_LEN_OFFSET] = htons(blockLen); - } - void SetFecSymbolId(UINT16 symbolId) - { - ((UINT16*)buffer)[(header_length/2)+SYMBOL_ID_OFFSET] = htons(symbolId); + SetFecId(fecId); + ((UINT8*)buffer)[header_length+RESERVED_OFFSET] = 0; + NormPayloadId payloadId(fecId, m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); + payloadId.SetFecPayloadId(blockId, symbolId, blockLen); + length = header_length + 4*FEC_PAYLOAD_ID_OFFSET + NormPayloadId::GetLength(fecId); } - // Message processing - UINT8 GetFecId() {return ((UINT8*)buffer)[header_length+FEC_ID_OFFSET];} + UINT8 GetFecId() const + {return ((UINT8*)buffer)[header_length+FEC_ID_OFFSET];} + NormObjectId GetObjectId() const + {return ntohs(((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET]);} + + NormBlockId GetFecBlockId(UINT8 m) const { - return (ntohs(((UINT16*)buffer)[(header_length/2)+OBJ_ID_OFFSET])); - } - NormBlockId GetFecBlockId() const + NormPayloadId payloadId(GetFecId(), m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockId(); + } + UINT16 GetFecSymbolId(UINT8 m) const { - return (ntohl(buffer[(header_length/4)+BLOCK_ID_OFFSET])); - } - UINT16 GetFecBlockLen() const + NormPayloadId payloadId(GetFecId(), m, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecSymbolId(); + } + UINT16 GetFecBlockLength() const { - return (ntohs(((UINT16*)buffer)[(header_length/2)+BLOCK_LEN_OFFSET])); - } - UINT16 GetFecSymbolId() const - { - return (ntohs(((UINT16*)buffer)[(header_length/2)+SYMBOL_ID_OFFSET])); + NormPayloadId payloadId(GetFecId(), 8, buffer + header_length/4 + FEC_PAYLOAD_ID_OFFSET); + return payloadId.GetFecBlockLength(); } private: // Note - These are the payload offsets for "fec_id" = 129 // "fec_payload_id" field + // IMPORTANT - These are _relative_ to the NORM_ACK header (incl. extensions) enum { - FEC_ID_OFFSET = 0, - RESERVED_OFFSET = FEC_ID_OFFSET + 1, - OBJ_ID_OFFSET = (RESERVED_OFFSET+1)/2, - BLOCK_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4, - BLOCK_LEN_OFFSET = ((BLOCK_ID_OFFSET*4)+4)/2, - SYMBOL_ID_OFFSET = ((BLOCK_LEN_OFFSET*2)+2)/2, - PAYLOAD_LENGTH = (SYMBOL_ID_OFFSET*2)+2 + FEC_ID_OFFSET = 0, + RESERVED_OFFSET = FEC_ID_OFFSET + 1, + OBJ_ID_OFFSET = (RESERVED_OFFSET+1)/2, + FEC_PAYLOAD_ID_OFFSET = ((OBJ_ID_OFFSET*2)+2)/4 }; }; // end class NormAckFlushMsg @@ -1610,4 +1917,7 @@ class NormMessageQueue NormMsg* tail; }; // end class NormMessageQueue +// Helper function to output report on repair content (e.g. NormNack content) to debug log +void LogRepairContent(const UINT32* buffer, UINT16 bufferLen, UINT8 fecId, UINT8 fecM); + #endif // _NORM_MESSAGE diff --git a/common/normNode.h b/include/normNode.h similarity index 73% rename from common/normNode.h rename to include/normNode.h index 6ca5e9e..7fa08f6 100644 --- a/common/normNode.h +++ b/include/normNode.h @@ -26,6 +26,27 @@ class NormNode const NormNodeId& GetId() const {return id;} void SetId(const NormNodeId& nodeId) {id = nodeId;} inline const NormNodeId& LocalNodeId() const; + + class Accumulator + { + public: + Accumulator(); + void Reset() + {msb = lsb = 0;} + void Increment(unsigned long count) + { + unsigned long lsbOld = lsb; + lsb += count; + if (lsb < lsbOld) msb++; + } + double GetValue() const + {return ((((double)0xffffffff)*msb) + lsb);} + double GetScaledValue(double factor) const + {return ((((double)0xffffffff)*(factor*msb)) + (factor*lsb));} + private: + unsigned long msb; + unsigned long lsb; + }; protected: class NormSession& session; @@ -49,7 +70,8 @@ class NormLossEstimator bool Update(const struct timeval& currentTime, unsigned short seqNumber, bool ecn = false); - void SetLossEventWindow(double lossWindow) {event_window = lossWindow;} + void SetLossEventWindow(double lossWindow) + {event_window = lossWindow;} void SetInitialLoss(double lossFraction) { memset(history, 0, (DEPTH+1)*sizeof(unsigned int)); @@ -83,16 +105,12 @@ class NormLossEstimator2 { public: NormLossEstimator2(); - void SetEventWindow(unsigned short windowDepth) - {event_window = windowDepth;} void SetLossEventWindow(double theTime) - {event_window_time = theTime;} + {event_window = theTime;} bool Update(const struct timeval& currentTime, unsigned short seqNumber, bool ecn = false); double LossFraction(); - - bool NoLoss() {return no_loss;} void SetInitialLoss(double lossFraction) { memset(history, 0, (DEPTH+1)*sizeof(unsigned int)); @@ -100,24 +118,35 @@ class NormLossEstimator2 } unsigned long CurrentLossInterval() {return history[0];} unsigned int LastLossInterval() {return history[1];} + + void SetIgnoreLoss(bool state) + {ignore_loss = state;} + void SetTolerateLoss(bool state) + {tolerate_loss = state;} private: enum {DEPTH = 8}; + + enum LossEventStatus + { + + CONFIRMED = 0, + CONFIRMING = 1, + SEEKING = 2 + }; // Members bool init; + bool ignore_loss; + bool tolerate_loss; unsigned long lag_mask; unsigned int lag_depth; unsigned long lag_test_bit; unsigned short lag_index; - unsigned short event_window; - unsigned short event_index; - double event_window_time; - double event_index_time; - bool seeking_loss_event; - - bool no_loss; - double initial_loss; + double event_window; + struct timeval event_time; + struct timeval event_time_orig; // (TBD - remove this, it's not used); + LossEventStatus seeking_loss_event; unsigned long history[9]; // loss interval history double discount[9]; @@ -128,6 +157,7 @@ class NormLossEstimator2 {init = true; Sync(theSequence);} void Sync(unsigned short theSequence) {lag_index = theSequence;} + void ChangeLagDepth(unsigned int theDepth) { theDepth = (theDepth > 20) ? 20 : theDepth; @@ -177,6 +207,8 @@ class NormCCNode : public NormNode bool HasRtt() const {return rtt_confirmed;} double GetRtt() const {return rtt;} + double GetRttSample() const {return rtt_sample;} + double GetRttSqMean() const {return rtt_sqmean;} double GetLoss() const {return loss;} double GetRate() const {return rate;} UINT16 GetCCSequence() const {return cc_sequence;} @@ -188,11 +220,17 @@ class NormCCNode : public NormNode void SetClrStatus(bool state) {is_clr = state;} void SetPlrStatus(bool state) {is_plr = state;} void SetRttStatus(bool state) {rtt_confirmed = state;} - void SetRtt(double value) {rtt = value;} + void SetRtt(double value) + { + rtt_sqmean = sqrt(value); + rtt = rtt_sample = value; + } double UpdateRtt(double value) { - rtt = 0.9*rtt + 0.1 * value; - return rtt; + rtt_sample = value; // save last rtt sample + rtt_sqmean = 0.9*rtt_sqmean + 0.1*sqrt(value); + rtt = 0.9*rtt + 0.1*value; + return rtt; } void SetLoss(double value) {loss = value;} void SetRate(double value) {rate = value;} @@ -205,20 +243,35 @@ class NormCCNode : public NormNode bool is_active; struct timeval feedback_time; // time of last received feedback double rtt; // in seconds + + + double rtt_sqmean; // ave sqrt(rtt), EWMA smoothed + double rtt_sample; // last rtt sample value + double loss; // loss fraction double rate; // in bytes per second UINT16 cc_sequence; }; // end class NormCCNode -class NormServerNode : public NormNode +class NormSenderNode : public NormNode { public: enum ObjectStatus {OBJ_INVALID, OBJ_NEW, OBJ_PENDING, OBJ_COMPLETE}; enum RepairBoundary {BLOCK_BOUNDARY, OBJECT_BOUNDARY}; + + + 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 + }; - NormServerNode(class NormSession& theSession, NormNodeId nodeId); - ~NormServerNode(); + NormSenderNode(class NormSession& theSession, NormNodeId nodeId); + ~NormSenderNode(); + + void SetInstanceId(UINT16 instanceId) + {instance_id = instanceId;} // Parameters NormObject::NackingMode GetDefaultNackingMode() const @@ -229,12 +282,17 @@ class NormServerNode : public NormNode // Should generally inherit NormSession::GetRxRobustFactor() void SetRobustFactor(int value); - NormServerNode::RepairBoundary GetRepairBoundary() const + RepairBoundary GetRepairBoundary() const {return repair_boundary;} // (TBD) force an appropriate RepairCheck on boundary change??? void SetRepairBoundary(RepairBoundary repairBoundary) {repair_boundary = repairBoundary;} + SyncPolicy GetSyncPolicy() const + {return sync_policy;} + void SetSyncPolicy(SyncPolicy syncPolicy) + {sync_policy = syncPolicy;} + bool UnicastNacks() {return unicast_nacks;} void SetUnicastNacks(bool state) {unicast_nacks = state;} @@ -247,6 +305,8 @@ class NormServerNode : public NormNode bool ecnStatus = false); double LossEstimate() {return loss_estimator.LossFraction();} + void CheckCCFeedback(); + void UpdateRecvRate(const struct timeval& currentTime, unsigned short msgSize); @@ -262,7 +322,7 @@ class NormServerNode : public NormNode UINT16 GetInstanceId() {return instance_id;} bool IsOpen() const {return is_open;} void Close(); - bool AllocateBuffers(UINT16 segmentSize, UINT16 numData, UINT16 numParity); + bool AllocateBuffers(UINT8 fecId, UINT16 fecInstanceId, UINT8 fecM, UINT16 segmentSize, UINT16 numData, UINT16 numParity); bool BuffersAllocated() {return (0 != segment_size);} void FreeBuffers(); void Activate(bool isObjectMsg); @@ -272,21 +332,25 @@ class NormServerNode : public NormNode ObjectStatus UpdateSyncStatus(const NormObjectId& objectId); ObjectStatus GetObjectStatus(const NormObjectId& objectId) const; - bool GetFirstPending(NormObjectId& objectId) + + UINT8 GetFecFieldSize() const + {return fec_m;} + + bool GetFirstPending(NormObjectId& objectId) const { UINT32 index; bool result = rx_pending_mask.GetFirstSet(index); objectId = (UINT16)index; return result; } - bool GetNextPending(NormObjectId& objectId) + bool GetNextPending(NormObjectId& objectId) const { UINT32 index = (UINT16)objectId; bool result = rx_pending_mask.GetNextSet(index); objectId = (UINT16)index; return result; } - bool GetLastPending(NormObjectId& objectId) + bool GetLastPending(NormObjectId& objectId) const { UINT32 index; bool result = rx_pending_mask.GetLastSet(index); @@ -347,18 +411,37 @@ class NormServerNode : public NormNode void CalculateGrttResponse(const struct timeval& currentTime, struct timeval& grttResponse) const; - // Statistics kept on server + // Statistics kept on sender unsigned long CurrentBufferUsage() const {return (segment_size * segment_pool.CurrentUsage());} unsigned long PeakBufferUsage() const {return (segment_size * segment_pool.PeakUsage());} unsigned long BufferOverunCount() const {return segment_pool.OverunCount() + block_pool.OverrunCount();} - unsigned long RecvTotal() const {return recv_total;} - unsigned long RecvGoodput() const {return recv_goodput;} - void IncrementRecvTotal(unsigned long count) {recv_total += count;} - void IncrementRecvGoodput(unsigned long count) {recv_goodput += count;} - void ResetRecvStats() {recv_total = recv_goodput = 0;} + + unsigned long CurrentStreamBufferUsage() const; + unsigned long PeakStreamBufferUsage() const; + unsigned long StreamBufferOverunCount() const; + + + //unsigned long RecvTotal() const {return recv_total;} + //unsigned long RecvGoodput() const {return recv_goodput;} + + // returns ave bytes/sec + double GetRecvRate(double interval) const + {return recv_total.GetScaledValue(1.0 / interval);} + double GetRecvGoodput(double interval) const + {return recv_goodput.GetScaledValue(1.0 / interval);} + + void IncrementRecvTotal(unsigned long count) + {recv_total.Increment(count);} + void IncrementRecvGoodput(unsigned long count) + {recv_goodput.Increment(count);} + void ResetRecvStats() + { + recv_total.Reset(); + recv_goodput.Reset(); + } void IncrementResyncCount() {resync_count++;} unsigned long ResyncCount() const {return resync_count;} unsigned long NackCount() const {return nack_count;} @@ -367,6 +450,41 @@ class NormServerNode : public NormNode unsigned long PendingCount() const {return rx_table.GetCount();} unsigned long FailureCount() const {return failure_count;} + class CmdBuffer + { + public: + CmdBuffer(); + ~CmdBuffer(); + + enum {CMD_SIZE_MAX = 8192}; + + void SetContent(const char* data, unsigned int numBytes) + { + ASSERT(numBytes <= CMD_SIZE_MAX); + memcpy(buffer, data, numBytes); + length = numBytes; + } + + const char* GetContent() const + {return buffer;} + unsigned int GetContentLength() const + {return length;} + + void Append(CmdBuffer* nextBuffer) + {next = nextBuffer;} + CmdBuffer* GetNext() const + {return next;} + private: + char buffer[CMD_SIZE_MAX]; + unsigned length; + CmdBuffer* next; // to support singly-linked list + }; // end class NormSenderNode::CmdBuffer + + CmdBuffer* NewCmdBuffer() const; + + bool ReadNextCmd(char* buffer, unsigned int* buflen); + + private: static const double DEFAULT_NOMINAL_INTERVAL; @@ -390,6 +508,7 @@ class NormServerNode : public NormNode UINT16 instance_id; int robust_factor; + SyncPolicy sync_policy; bool synchronized; NormObjectId sync_id; // only valid if(synchronized) NormObjectId next_id; // only valid if(synchronized) @@ -399,6 +518,8 @@ class NormServerNode : public NormNode bool is_open; UINT16 segment_size; + UINT8 fec_id; + UINT8 fec_m; unsigned int ndata; unsigned int nparity; @@ -416,7 +537,7 @@ class NormServerNode : public NormNode char** retrieval_pool; unsigned int retrieval_index; - bool server_active; + bool sender_active; ProtoTimer activity_timer; ProtoTimer repair_timer; @@ -426,7 +547,7 @@ class NormServerNode : public NormNode NormBlockId watermark_block_id; NormSegmentId watermark_segment_id; - // Remote server grtt measurement state + // Remote sender grtt measurement state double grtt_estimate; UINT8 grtt_quantized; struct timeval grtt_send_time; @@ -435,10 +556,11 @@ class NormServerNode : public NormNode UINT8 gsize_quantized; double backoff_factor; - // Remote server congestion control state + // Remote sender congestion control state NormLossEstimator2 loss_estimator; UINT16 cc_sequence; bool cc_enable; + bool cc_feedback_needed; double cc_rate; // ccRate at start of cc_timer ProtoTimer cc_timer; double rtt_estimate; @@ -449,20 +571,26 @@ class NormServerNode : public NormNode bool slow_start; double send_rate; // sender advertised rate double recv_rate; // measured recv rate + double recv_rate_prev; // for recv_rate measurement struct timeval prev_update_time; // for recv_rate measurement - unsigned long recv_accumulator; // for recv_rate measurement + Accumulator recv_accumulator; // for recv_rate measurement double nominal_packet_size; + // Buffering of app-defined commands received from Remote sender + CmdBuffer* cmd_buffer_head; // the oldest received command is here (for FIFO) + CmdBuffer* cmd_buffer_tail; // newly-received commands appended here + CmdBuffer* cmd_buffer_pool; // we "pool" allocated buffers for possible reuse here + // For statistics tracking - unsigned long recv_total; // total recvd accumulator - unsigned long recv_goodput; // goodput recvd accumulator + Accumulator recv_total; // total recvd accumulator + Accumulator recv_goodput; // goodput recvd accumulator unsigned long resync_count; unsigned long nack_count; unsigned long suppress_count; unsigned long completion_count; unsigned long failure_count; // usually due to re-syncs -}; // end class NormServerNode +}; // end class NormSenderNode // Used for binary trees of NormNodes @@ -488,8 +616,8 @@ class NormNodeTree class NormNodeTreeIterator { public: - NormNodeTreeIterator(const NormNodeTree& nodeTree); - void Reset(); + NormNodeTreeIterator(const NormNodeTree& nodeTree, NormNode* prevNode = NULL); + void Reset(NormNode* prevNode = NULL); NormNode* GetNextNode(); @@ -512,7 +640,7 @@ class NormNodeList void Remove(NormNode* theNode); void DeleteNode(NormNode* theNode) { - ASSERT(theNode); + ASSERT(NULL != theNode); Remove(theNode); theNode->Release(); } diff --git a/common/normObject.h b/include/normObject.h similarity index 86% rename from common/normObject.h rename to include/normObject.h index 4f7cb0d..40e5e79 100644 --- a/common/normObject.h +++ b/include/normObject.h @@ -61,8 +61,8 @@ class NormObject class NormSession& GetSession() const {return session;} NormNodeId LocalNodeId() const; - class NormServerNode* GetServer() const {return server;} - NormNodeId GetServerNodeId() const; + class NormSenderNode* GetSender() const {return sender;} + NormNodeId GetSenderNodeId() const; bool IsOpen() {return (0 != segment_size);} // Opens (inits) object for tx operation @@ -70,17 +70,21 @@ class NormObject const char* infoPtr, UINT16 infoLen, UINT16 segmentSize, + UINT8 fecId, + UINT8 fecM, UINT16 numData, UINT16 numParity); // Opens (inits) object for rx operation - bool Open(const NormObjectSize& objectSize, - bool hasInfo, - UINT16 segmentSize, - UINT16 numData, - UINT16 numParity) + bool RxOpen(const NormObjectSize& objectSize, + bool hasInfo, + UINT16 segmentSize, + UINT8 fecId, + UINT8 fecM, + UINT16 numData, + UINT16 numParity) { return Open(objectSize, (char*)NULL, hasInfo ? 1 : 0, - segmentSize, numData, numParity); + segmentSize, fecId, fecM, numData, numParity); } void Close(); @@ -101,7 +105,7 @@ class NormObject void SetNackingMode(NackingMode nackingMode) { nacking_mode = nackingMode; - // (TBD) initiate an appropriate NormServerNode::RepairCheck + // (TBD) initiate an appropriate NormSenderNode::RepairCheck // to prompt repair process if needed } @@ -166,9 +170,9 @@ class NormObject bool FindRepairIndex(NormBlockId& blockId, NormSegmentId& segmentId) const; - // Methods available to server for transmission - bool NextServerMsg(NormObjectMsg* msg); - NormBlock* ServerRecoverBlock(NormBlockId blockId); + // Methods available to sender for transmission + bool NextSenderMsg(NormObjectMsg* msg); + NormBlock* SenderRecoverBlock(NormBlockId blockId); bool CalculateBlockParity(NormBlock* block); /*bool IsFirstPass() {return first_pass;} @@ -189,9 +193,8 @@ class NormObject result = result ? pending_mask.Set(blockId) : false; return result; } - bool HandleInfoRequest(); + bool HandleInfoRequest(bool holdoff); bool HandleBlockRequest(NormBlockId nextId, NormBlockId lastId); - bool SetPending(NormBlockId blockId) {return pending_mask.Set(blockId);} NormBlock* FindBlock(NormBlockId blockId) {return block_buffer.Find(blockId);} bool ActivateRepairs(); bool IsRepairSet(NormBlockId blockId) {return repair_mask.Test(blockId);} @@ -205,7 +208,7 @@ class NormObject NormBlock* StealNonPendingBlock(bool excludeBlock, NormBlockId excludeId = 0); - // Methods available to client for reception + // Methods available to receiver for reception bool Accepted() {return accepted;} void HandleObjectMessage(const NormObjectMsg& msg, NormMsg::Type msgType, @@ -219,42 +222,53 @@ class NormObject bool ReclaimSourceSegments(NormSegmentPool& segmentPool); bool PassiveRepairCheck(NormBlockId blockId, NormSegmentId segmentId); - bool ClientRepairCheck(CheckLevel level, + bool ReceiverRepairCheck(CheckLevel level, NormBlockId blockId, NormSegmentId segmentId, bool timerActive, bool holdoffPhase = false); + bool ReceiverRewindCheck(NormBlockId blockId, + NormSegmentId segmentId); bool IsRepairPending(bool flush); bool AppendRepairRequest(NormNackMsg& nack, bool flush); void SetRepairInfo() {repair_info = true;} bool SetRepairs(NormBlockId first, NormBlockId last) { return (first == last) ? repair_mask.Set(first) : - repair_mask.SetBits(first, last-first+1); + repair_mask.SetBits(first, repair_mask.Delta(last,first)+1); } + void SetLastNackTime(const ProtoTime& theTime) + {last_nack_time = theTime;} + const ProtoTime& GetLastNackTime() const + {return last_nack_time;} + + double GetNackAge() const + {return ProtoTime::Delta(ProtoTime().GetCurrentTime(), last_nack_time);} protected: NormObject(Type theType, class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId); void Accept() {accepted = true;} NormObject::Type type; class NormSession& session; - class NormServerNode* server; // NULL value indicates local (tx) object + class NormSenderNode* sender; // NULL value indicates local (tx) object unsigned int reference_count; NormObjectId transport_id; NormObjectSize object_size; UINT16 segment_size; + UINT8 fec_id; + UINT8 fec_m; UINT16 ndata; UINT16 nparity; NormBlockBuffer block_buffer; bool pending_info; // set when we need to send or recv info ProtoSlidingMask pending_mask; - bool repair_info; // client: set when + bool repair_info; // receiver: set when ProtoSlidingMask repair_mask; NormBlockId current_block_id; // for suppression NormSegmentId next_segment_id; // for suppression @@ -267,6 +281,7 @@ class NormObject NormBlockId final_block_id; UINT16 final_segment_size; NackingMode nacking_mode; + ProtoTime last_nack_time; // time of last NACK received (used for flow control) char* info_ptr; UINT16 info_len; @@ -284,7 +299,7 @@ class NormFileObject : public NormObject { public: NormFileObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId); ~NormFileObject(); @@ -302,7 +317,7 @@ class NormFileObject : public NormObject return result; } bool PadToSize() - {return file.Pad(GetSize().GetOffset());} + {return file.Pad(NormObject::GetSize().GetOffset());} virtual bool WriteSegment(NormBlockId blockId, NormSegmentId segmentId, @@ -327,7 +342,7 @@ class NormDataObject : public NormObject // (TBD) allow support of greater than 4GB size data objects public: NormDataObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId); ~NormDataObject(); @@ -372,7 +387,7 @@ class NormStreamObject : public NormObject { public: NormStreamObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId); ~NormStreamObject(); @@ -409,12 +424,23 @@ class NormStreamObject : public NormObject bool HasVacancy() {return (stream_closing ? false : write_vacancy);} + NormBlock* StreamBlockLo() + {return stream_buffer.Find(stream_buffer.RangeLo());} + void SetLastNackTime(NormBlockId blockId, const ProtoTime& theTime) + { + NormBlock* block = stream_buffer.Find(blockId); + if (NULL != block) block->SetLastNackTime(theTime); + } + bool Read(char* buffer, unsigned int* buflen, bool findMsgStart = false); UINT32 Write(const char* buffer, UINT32 len, bool eom = false); UINT32 GetCurrentReadOffset() {return read_offset;} + unsigned int GetCurrentBufferUsage() const // in segments + {return segment_pool.CurrentUsage();} + bool StreamUpdateStatus(NormBlockId blockId); // Note that the "pending_mask" should be cleared and the // "block_buffer" emptied before "StreamResync()" is invoked @@ -423,7 +449,7 @@ class NormStreamObject : public NormObject stream_sync = false; StreamUpdateStatus(blockId); } - void StreamAdvance(); + bool StreamAdvance(); virtual bool WriteSegment(NormBlockId blockId, NormSegmentId segmentId, @@ -440,11 +466,11 @@ class NormStreamObject : public NormObject // For receive stream, we can rewind to earliest buffered offset void Rewind(); - bool LockBlocks(NormBlockId nextId, NormBlockId lastId); + bool LockBlocks(NormBlockId nextId, NormBlockId lastId, const ProtoTime& currentTime); void UnlockBlock(NormBlockId blockId); bool LockSegments(NormBlockId blockId, NormSegmentId firstId, NormSegmentId lastId); - NormBlockId StreamBufferLo() {return stream_buffer.RangeLo();} + NormBlockId StreamBufferLo() const {return stream_buffer.RangeLo();} void Prune(NormBlockId blockId, bool updateStatus); bool IsFlushPending() {return flush_pending;} @@ -456,17 +482,25 @@ class NormStreamObject : public NormObject (ndata-1));} NormBlockId GetNextBlockId() const - {return (server ? read_index.block : write_index.block);} + {return (sender ? read_index.block : write_index.block);} NormSegmentId GetNextSegmentId() const - {return (server ? read_index.segment : write_index.segment);} + {return (sender ? read_index.segment : write_index.segment);} UINT32 GetBlockPoolCount() {return block_pool.GetCount();} void SetBlockPoolThreshold(UINT32 value) {block_pool_threshold = value;} + + unsigned long CurrentBufferUsage() const + {return (segment_size * segment_pool.CurrentUsage());} + unsigned long PeakBufferUsage() const + {return (segment_size * segment_pool.PeakUsage());} + unsigned long BufferOverunCount() const + {return segment_pool.OverunCount() + block_pool.OverrunCount();} + bool IsReadReady() const {return read_ready;} - bool DetermineReadReadiness() + bool DetermineReadReadiness() const { NormBlock* block = stream_buffer.Find(read_index.block); return ((NULL != block) && (NULL != block->GetSegment(read_index.segment))); @@ -474,6 +508,8 @@ class NormStreamObject : public NormObject bool IsReadIndex(NormBlockId blockId, NormSegmentId segmentId) const {return ((blockId == read_index.block) && (segmentId == read_index.segment));} + + bool PassiveReadCheck(NormBlockId blockId, NormSegmentId segmentId); private: bool ReadPrivate(char* buffer, unsigned int* buflen, bool findMsgStart = false); @@ -520,7 +556,7 @@ class NormSimObject : public NormObject { public: NormSimObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId); ~NormSimObject(); diff --git a/common/normPostProcess.h b/include/normPostProcess.h similarity index 100% rename from common/normPostProcess.h rename to include/normPostProcess.h diff --git a/common/normSegment.h b/include/normSegment.h similarity index 89% rename from common/normSegment.h rename to include/normSegment.h index 5a4b3f8..35f2e48 100644 --- a/common/normSegment.h +++ b/include/normSegment.h @@ -25,7 +25,8 @@ class NormSegmentPool } bool IsEmpty() const {return (NULL == seg_list);} - unsigned long CurrentUsage() const {return (seg_total - seg_count);} + unsigned int CurrentUsage() const + {return (seg_total - seg_count);} unsigned long PeakUsage() const {return peak_usage;} unsigned long OverunCount() const {return overruns;} unsigned int GetSegmentSize() {return seg_size;} @@ -94,7 +95,7 @@ class NormBlock segment_table[sid] = segment; } - // Server routines + // Sender routines void TxInit(NormBlockId& blockId, UINT16 ndata, UINT16 autoParity) { id = blockId; @@ -106,6 +107,7 @@ class NormBlock parity_offset = autoParity; flags = 0; seg_size_max = 0; + last_nack_time.GetCurrentTime(); } void TxRecover(NormBlockId& blockId, UINT16 ndata, UINT16 nparity) { @@ -139,10 +141,12 @@ class NormBlock bool AppendRepairAdv(NormCmdRepairAdvMsg& cmd, NormObjectId objectId, bool repairInfo, + UINT8 fecId, + UINT8 fecM, UINT16 numData, UINT16 segmentSize); - // Client routines + // Receiver routines void RxInit(NormBlockId& blockId, UINT16 ndata, UINT16 nparity) { id = blockId; @@ -227,15 +231,26 @@ class NormBlock NormBlockId finalBlockId, UINT16 finalSegmentSize) const; - bool AppendRepairRequest(NormNackMsg& nack, - UINT16 numData, - UINT16 numParity, - NormObjectId objectId, - bool pendingInfo, - UINT16 segmentSize); + bool AppendRepairRequest(NormNackMsg& nack, + UINT8 fecId, + UINT8 fecM, + UINT16 numData, + UINT16 numParity, + NormObjectId objectId, + bool pendingInfo, + UINT16 segmentSize); + + + void SetLastNackTime(const ProtoTime& theTime) + {last_nack_time = theTime;} + const ProtoTime& GetLastNackTime() const + {return last_nack_time;} + double GetNackAge() const + {return ProtoTime::Delta(ProtoTime().GetCurrentTime(), last_nack_time);} + //void DisplayPendingMask(FILE* f) {pending_mask.Display(f);} - bool IsEmpty() const; + //bool IsEmpty() const; void EmptyToPool(NormSegmentPool& segmentPool); private: @@ -251,6 +266,7 @@ class NormBlock ProtoBitmask pending_mask; ProtoBitmask repair_mask; + ProtoTime last_nack_time; // for stream flow control NormBlock* next; }; // end class NormBlock @@ -274,7 +290,7 @@ class NormBlockPool } else if (!overrun_flag) { - DMSG(0, "NormBlockPool::Get() warning: operating with constrained buffering resources\n"); + PLOG(PL_DEBUG, "NormBlockPool::Get() warning: operating with constrained buffering resources\n"); overruns++; overrun_flag = true; } diff --git a/common/normSession.h b/include/normSession.h similarity index 53% rename from common/normSession.h rename to include/normSession.h index 6e6d7c5..f0d0714 100644 --- a/common/normSession.h +++ b/include/normSession.h @@ -8,6 +8,21 @@ #include "protokit.h" +#include "protoCap.h" // for ProtoCap for ECN_SUPPORT + + +// When this is defined, our experimental tweak to +// limiting suggested cc rate to 2.0* measured recv rate is +// used during steady state similar to "slow start" +// conditions. What this means is that when data transmission +// is idle, the rate will be reduced. This _may_ impact +// certain use cases. Our theory here is that preventing rate +// overshoot will be more helpful and safer than the penalty +// imposed. This uses the non-RFC5740 NORM_CC_FLAG_LIMIT in +// NORM-CC feedback header extensions + +#define LIMIT_CC_RATE 1 + class NormController { public: @@ -19,13 +34,16 @@ class NormController TX_QUEUE_EMPTY, TX_FLUSH_COMPLETED, TX_WATERMARK_COMPLETED, + TX_CMD_SENT, TX_OBJECT_SENT, TX_OBJECT_PURGED, + TX_RATE_CHANGED, LOCAL_SENDER_CLOSED, REMOTE_SENDER_NEW, REMOTE_SENDER_ACTIVE, REMOTE_SENDER_INACTIVE, REMOTE_SENDER_PURGED, + RX_CMD_NEW, RX_OBJECT_NEW, RX_OBJECT_INFO, RX_OBJECT_UPDATED, @@ -39,7 +57,7 @@ class NormController virtual void Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* sender, + class NormSenderNode* sender, class NormObject* object) = 0; }; // end class NormController @@ -49,7 +67,8 @@ class NormSessionMgr friend class NormSession; public: NormSessionMgr(ProtoTimerMgr& timerMgr, - ProtoSocket::Notifier& socketNotifier); + ProtoSocket::Notifier& socketNotifier, + ProtoChannel::Notifier* channelNotifier = NULL); ~NormSessionMgr(); void SetController(NormController* theController) {controller = theController;} @@ -62,7 +81,7 @@ class NormSessionMgr void Notify(NormController::Event event, class NormSession* session, - class NormServerNode* sender, + class NormSenderNode* sender, class NormObject* object) { if (controller) @@ -72,17 +91,20 @@ class NormSessionMgr void ActivateTimer(ProtoTimer& timer) {timer_mgr.ActivateTimer(timer);} ProtoTimerMgr& GetTimerMgr() const {return timer_mgr;} ProtoSocket::Notifier& GetSocketNotifier() const {return socket_notifier;} + ProtoChannel::Notifier* GetChannelNotifier() const {return channel_notifier;} void DoSystemTimeout() {timer_mgr.DoSystemTimeout();} NormController* GetController() const {return controller;} - private: - ProtoTimerMgr& timer_mgr; - ProtoSocket::Notifier& socket_notifier; - NormController* controller; - class NormSession* top_session; // top of NormSession list + private: + ProtoTimerMgr& timer_mgr; + ProtoSocket::Notifier& socket_notifier; + ProtoChannel::Notifier* channel_notifier; + NormController* controller; + + class NormSession* top_session; // top of NormSession list }; // end class NormSessionMgr @@ -106,8 +128,13 @@ class NormSession static const UINT16 DEFAULT_NPARITY; static const UINT16 DEFAULT_TX_CACHE_MIN; static const UINT16 DEFAULT_TX_CACHE_MAX; + static const UINT32 DEFAULT_TX_CACHE_SIZE; + static const double DEFAULT_FLOW_CONTROL_FACTOR; + static const UINT16 DEFAULT_RX_CACHE_MAX; static const int DEFAULT_ROBUST_FACTOR; + enum {IFACE_NAME_MAX = 31}; + enum ProbingMode {PROBE_NONE, PROBE_PASSIVE, PROBE_ACTIVE}; enum AckingStatus { @@ -116,10 +143,21 @@ class NormSession ACK_PENDING, ACK_SUCCESS }; + + // This is currently used to determine whether + // and how to "auto populate" the acking node + // list based on received messages + enum TrackingStatus + { + TRACK_NONE = 0x00, + TRACK_RECEIVERS = 0x01, + TRACK_SENDERS = 0x02, + TRACK_ALL = 0x03 + }; // General methods const NormNodeId& LocalNodeId() const {return local_node_id;} - bool Open(const char* interfaceName = NULL); + bool Open(); void Close(); bool IsOpen() {return (rx_socket.IsOpen() || tx_socket->IsOpen());} const ProtoAddress& Address() {return address;} @@ -145,12 +183,39 @@ class NormSession loopback = result ? state : loopback; return result; } - void SetTxPort(UINT16 txPort) {tx_port = txPort;} - void SetRxPortReuse(bool enable, bool bindToSessionAddress = true) + + bool SetFragmentation(bool state) { - rx_port_reuse = enable; // allow sessionPort reuse when true - rx_addr_bind = bindToSessionAddress; // bind rx_socket to sessionAddr when true + bool result = tx_socket->IsOpen() ? tx_socket->SetFragmentation(state) : true; + fragmentation = result ? state : fragmentation; + return result; } + + // MUST be called _after_ SetAddress() + bool SetTxPort(UINT16 txPort, bool enableReuse = false, const char* srcAddr = NULL); + + UINT16 GetTxPort() const; + + bool SetRxPortReuse(bool enableReuse, + const char* rxAddress, // bind() to / + const char* senderAddress = (const char*)0, // connect() to / + UINT16 senderPort = 0); + + UINT16 GetRxPort() const; + + // "SetEcnSupport(true)" sets up raw packet capture (pcap) so that incoming packet + // ECN status may be checked + // NOTE: only effective _before_ sndr/rcvr startup! + void SetEcnSupport(bool ecnEnable, bool ignoreLoss, bool tolerateLoss) + { + ecn_enabled = ecnEnable; + ecn_ignore_loss = ecnEnable ? ignoreLoss : false; + cc_tolerate_loss = ecn_ignore_loss ? false : tolerateLoss; + } + bool GetEcnIgnoreLoss() const + {return ecn_ignore_loss;} + bool GetCCTolerateLoss() const + {return cc_tolerate_loss;} static double CalculateRate(double size, double rtt, double loss); NormSessionMgr& GetSessionMgr() {return session_mgr;} @@ -161,38 +226,74 @@ class NormSession {return rx_socket.SetRxBufferSize(bufferSize);} // Session parameters - double GetTxRate() - { - return (tx_rate * 8.0); // convert to bits/second - } + double GetTxRate(); // returns bits/sec // (TBD) watch timer scheduling and min/max bounds void SetTxRate(double txRate) { txRate /= 8.0; // convert to bytes/sec + posted_tx_rate_changed = false; SetTxRateInternal(txRate); } - double BackoffFactor() {return backoff_factor;} - void SetBackoffFactor(double value) {backoff_factor = value;} - bool CongestionControl() {return cc_enable;} - void SetCongestionControl(bool state) + void SetTxRateBounds(double rateMin, double rateMax); + + double BackoffFactor() + {return backoff_factor;} + void SetBackoffFactor(double value) + {backoff_factor = value;} + bool CongestionControl() + {return cc_enable;} + void SetCongestionControl(bool state, bool adjustRate = true) { + if (state) SetGrttProbingMode(PROBE_ACTIVE); cc_enable = state; + cc_adjust = adjustRate; + if (state) probe_proactive = true; } + + // This method enables/disables flow control operation. + void SetFlowControl(double flowControlFactor) + {flow_control_factor = flowControlFactor;} + double GetFlowControl() const + {return flow_control_factor;} + + // This method is used by "internal" NormSession and NormObject code + // to activate the timer-based flow control when needed. + void ActivateFlowControl(double delay, NormObjectId objectId, NormController::Event event); + void DeactivateFlowControl() + {flow_control_timer.Deactivate();} + bool FlowControlIsActive() const + {return flow_control_timer.IsActive();} + NormObjectId GetFlowControlObject() const + {return flow_control_object;} + // The value returned here is the time interval used to determine + // whether there has been "recent" NACKing for a given object or block. + // A larger "flow_control_factor" stretches the time interval that + // is considered "recent" and thus imposes stronger flow control. + // A _strong_ "flow_control_factor" would be on the order of + // "tx_robust_factor", but note larger values require more + // tx/rx caching and/or buffering to sustain high throughput + // NOTE "flow_control_factor = 0.0" means _no_ timer-based + // flow control is imposed + double GetFlowControlDelay() const + { + double fdelay = (flow_control_factor * (SenderGrtt() * (backoff_factor + 1))); + return ((fdelay > 0.020) ? fdelay : 0.020); // minimum 20 msec flow control + } + // GRTT measurement management void SetGrttProbingMode(ProbingMode probingMode); void SetGrttProbingInterval(double intervalMin, double intervalMax); void SetGrttMax(double grttMax) {grtt_max = grttMax;} - void SetTxRateBounds(double rateMin, double rateMax); bool SetTxCacheBounds(NormObjectSize sizeMax, unsigned long countMin, unsigned long countMax); void Notify(NormController::Event event, - class NormServerNode* server, + class NormSenderNode* sender, class NormObject* object) { notify_pending = true; - session_mgr.Notify(event, this, server, object); + session_mgr.Notify(event, this, sender, object); notify_pending = false; } @@ -205,20 +306,21 @@ class NormSession void SetUserData(const void* userData) {user_data = userData;} const void* GetUserData() {return user_data;} - // Server methods - void ServerSetBaseObjectId(NormObjectId baseId) + // Sender methods + void SenderSetBaseObjectId(NormObjectId baseId) { - next_tx_object_id = IsServer() ? next_tx_object_id : baseId; - //instance_id = IsServer() ? instance_id : (UINT16)baseId; + next_tx_object_id = IsSender() ? next_tx_object_id : baseId; + //instance_id = IsSender() ? instance_id : (UINT16)baseId; } - bool IsServer() {return is_server;} - bool StartServer(UINT16 instanceId, + bool IsSender() {return is_sender;} + bool StartSender(UINT16 instanceId, UINT32 bufferSpace, UINT16 segmentSize, UINT16 numData, - UINT16 numParity, - const char* interfaceName = NULL); - void StopServer(); + UINT16 numParity); + void StopSender(); + void SetTxOnly(bool txOnly, bool connectToSessionAddress = false); + NormStreamObject* QueueTxStream(UINT32 bufferSize, bool doubleBuffer = false, const char* infoPtr = NULL, @@ -235,15 +337,29 @@ class NormSession void DeleteTxObject(NormObject* obj); - // postive ack mgmnt - void ServerSetWatermark(NormObjectId objectId, - NormBlockId blockId, - NormSegmentId segmentId); - void ServerCancelWatermark(); - bool ServerAddAckingNode(NormNodeId nodeId); - void ServerRemoveAckingNode(NormNodeId nodeId); - AckingStatus ServerGetAckingStatus(NormNodeId nodeId); + NormObject* SenderFindTxObject(NormObjectId objectId) + {return tx_table.Find(objectId);} + // postive ack mgmnt + void SenderSetWatermark(NormObjectId objectId, + NormBlockId blockId, + NormSegmentId segmentId, + bool overrideFlush = false); + void SenderResetWatermark(); + void SenderCancelWatermark(); + + + void SenderSetAutoAckingNodes(TrackingStatus trackingStatus) + {acking_auto_populate = trackingStatus;} + bool SenderAddAckingNode(NormNodeId nodeId); + void SenderRemoveAckingNode(NormNodeId nodeId); + AckingStatus SenderGetAckingStatus(NormNodeId nodeId); + // Set "prevNodeId = NORM_NODE_NONE" to init this iteration (returns "false" when done) + bool SenderGetNextAckingNode(NormNodeId& prevNodeId, AckingStatus* ackingStatus = NULL); + + // App-defined command support methods + bool SenderSendCmd(const char* cmdBuffer, unsigned int cmdLength, bool robust); + void SenderCancelCmd(); // robust factor void SetTxRobustFactor(int value) @@ -255,31 +371,34 @@ class NormSession int GetRxRobustFactor() const {return rx_robust_factor;} - UINT16 ServerSegmentSize() const {return segment_size;} - UINT16 ServerBlockSize() const {return ndata;} - UINT16 ServerNumParity() const {return nparity;} - UINT16 ServerAutoParity() const {return auto_parity;} - void ServerSetAutoParity(UINT16 autoParity) + UINT8 GetSenderFecId() const {return fec_id;} + UINT8 GetSenderFecFieldSize() const {return fec_m;} + UINT16 SenderSegmentSize() const {return segment_size;} + UINT16 SenderBlockSize() const {return ndata;} + UINT16 SenderNumParity() const {return nparity;} + UINT16 SenderAutoParity() const {return auto_parity;} + void SenderSetAutoParity(UINT16 autoParity) {ASSERT(autoParity <= nparity); auto_parity = autoParity;} - UINT16 ServerExtraParity() const {return extra_parity;} - void ServerSetExtraParity(UINT16 extraParity) + UINT16 SenderExtraParity() const {return extra_parity;} + void SenderSetExtraParity(UINT16 extraParity) {extra_parity = extraParity;} - // EMCON Server (useful when there are silent receivers) + + // EMCON Sender (useful when there are silent receivers) // (NORM_INFO is redundantly sent) void SndrSetEmcon(bool state) {sndr_emcon = true;} bool SndrEmcon() const {return sndr_emcon;} - bool ServerGetFirstPending(NormObjectId& objectId) + bool SenderGetFirstPending(NormObjectId& objectId) { UINT32 index; bool result = tx_pending_mask.GetFirstSet(index); objectId = (UINT16)index; return result; } - bool ServerGetFirstRepairPending(NormObjectId& objectId) + bool SenderGetFirstRepairPending(NormObjectId& objectId) { UINT32 index; bool result = tx_repair_mask.GetFirstSet(index); @@ -287,10 +406,10 @@ class NormSession return result; } - double ServerGrtt() {return grtt_advertised;} - void ServerSetGrtt(double grttValue) + double SenderGrtt() const {return grtt_advertised;} + void SenderSetGrtt(double grttValue) { - if (IsServer()) + if (IsSender()) { double grttMin = 2.0 * ((double)(44+segment_size))/tx_rate; grttValue = (grttValue < grttMin) ? grttMin : grttValue; @@ -298,59 +417,67 @@ class NormSession grtt_quantized = NormQuantizeRtt(grttValue); grtt_measured = grtt_advertised = NormUnquantizeRtt(grtt_quantized); } - double ServerGroupSize() {return gsize_measured;} - void ServerSetGroupSize(double gsize) + double SenderGroupSize() {return gsize_measured;} + void SenderSetGroupSize(double gsize) { gsize_measured = gsize; gsize_quantized = NormQuantizeGroupSize(gsize); gsize_advertised = NormUnquantizeGroupSize(gsize_quantized); } - void ServerEncode(const char* segment, char** parityVectorList) - {encoder->Encode(segment, parityVectorList);} + void SenderEncode(unsigned int segmentId, const char* segment, char** parityVectorList) + {encoder->Encode(segmentId, segment, parityVectorList);} - NormBlock* ServerGetFreeBlock(NormObjectId objectId, NormBlockId blockId); - void ServerPutFreeBlock(NormBlock* block) + NormBlock* SenderGetFreeBlock(NormObjectId objectId, NormBlockId blockId); + void SenderPutFreeBlock(NormBlock* block) { block->EmptyToPool(segment_pool); block_pool.Put(block); } - char* ServerGetFreeSegment(NormObjectId objectId, NormBlockId blockId); - void ServerPutFreeSegment(char* segment) {segment_pool.Put(segment);} + char* SenderGetFreeSegment(NormObjectId objectId, NormBlockId blockId); + void SenderPutFreeSegment(char* segment) {segment_pool.Put(segment);} - void PromptServer() {QueueMessage(NULL);} + void PromptSender() {QueueMessage(NULL);} - void TouchServer() + void TouchSender() { posted_tx_queue_empty = false; - PromptServer(); + PromptSender(); //if (!notify_pending) Serve(); } - // Client methods - bool StartClient(unsigned long bufferSpace, - const char* interfaceName = NULL); - void StopClient(); - bool IsClient() const {return is_client;} - unsigned long RemoteServerBufferSize() const - {return remote_server_buffer_size;} - void ClientSetUnicastNacks(bool state) {unicast_nacks = state;} - bool ClientGetUnicastNacks() const {return unicast_nacks;} + bool GetPostedTxQueueEmpty() const + {return posted_tx_queue_empty;} - void ClientSetSilent(bool state) - {client_silent = state;} - bool ClientIsSilent() const {return client_silent;} + // Receiver methods + bool StartReceiver(unsigned long bufferSpace); + void StopReceiver(); + bool IsReceiver() const {return is_receiver;} + unsigned long RemoteSenderBufferSize() const + {return remote_sender_buffer_size;} + + void DeleteRemoteSender(NormSenderNode& senderNode); + + // Call this to do remote sender memory allocations ahead of time + bool PreallocateRemoteSender(UINT16 segmentSize, UINT16 numData, UINT16 numParity); + + void ReceiverSetUnicastNacks(bool state) {unicast_nacks = state;} + bool ReceiverGetUnicastNacks() const {return unicast_nacks;} + + void ReceiverSetSilent(bool state) + {receiver_silent = state;} + bool ReceiverIsSilent() const {return receiver_silent;} void RcvrSetIgnoreInfo(bool state) {rcvr_ignore_info = state;} bool RcvrIgnoreInfo() const {return rcvr_ignore_info;} - // "-1" corresponds to typical operation where source data for - // partially received FEC blocks are only provided to the app - // when buffer constraints require it. + // The default "rcvr_max_delay = -1" corresponds to typical + // operation where source data forpartially received FEC blocks + // are only provided to the app when buffer constraints require it. // Otherwise, the "maxDelay" corresponds to the max number // of FEC blocks the receiver waits before passing partially // received blocks to the app. @@ -359,24 +486,47 @@ class NormSession void RcvrSetMaxDelay(INT32 maxDelay) {rcvr_max_delay = maxDelay;} bool RcvrIsLowDelay() - {return (ClientIsSilent() && (rcvr_max_delay >= 0));} + {return (ReceiverIsSilent() && (rcvr_max_delay >= 0));} INT32 RcvrGetMaxDelay() const {return rcvr_max_delay;} - NormObject::NackingMode ClientGetDefaultNackingMode() const + // When "rcvr_realtime" is set to "true", the buffer managment scheme of + // favoring newly arriving data over attempting reliable reception of + // buffered data is observed. This is the same buffer management that + // is used for silent receiver operation + // (TBD) allow the above "low delay" option to work with this, too? + void RcvrSetRealtime(bool state) + {rcvr_realtime = state;} + bool RcvrIsRealtime() const + {return rcvr_realtime;} + + NormObject::NackingMode ReceiverGetDefaultNackingMode() const {return default_nacking_mode;} - void ClientSetDefaultNackingMode(NormObject::NackingMode nackingMode) + void ReceiverSetDefaultNackingMode(NormObject::NackingMode nackingMode) {default_nacking_mode = nackingMode;} - NormServerNode::RepairBoundary ClientGetDefaultRepairBoundary() const + NormSenderNode::RepairBoundary ReceiverGetDefaultRepairBoundary() const {return default_repair_boundary;} - void ClientSetDefaultRepairBoundary(NormServerNode::RepairBoundary repairBoundary) + void ReceiverSetDefaultRepairBoundary(NormSenderNode::RepairBoundary repairBoundary) {default_repair_boundary = repairBoundary;} + NormSenderNode::SyncPolicy ReceiverGetDefaultSyncPolicy() const + {return default_sync_policy;} + void ReceiverSetDefaultSyncPolicy(NormSenderNode::SyncPolicy syncPolicy) + {default_sync_policy = syncPolicy;} + + // Set default "max_pending_range" of NormObjects for reception + void SetRxCacheMax(UINT16 maxCount) + {rx_cache_count_max = maxCount;} + UINT16 GetRxCacheMax() const + {return rx_cache_count_max;} + // Debug settings void SetTrace(bool state) {trace = state;} void SetTxLoss(double percent) {tx_loss_rate = percent;} void SetRxLoss(double percent) {rx_loss_rate = percent;} + void SetReportTimerInterval(double interval) {report_timer.SetInterval(interval);} + double GetReportTimerInterval() {return report_timer.GetInterval();} #ifdef SIMULATE // Simulation specific methods @@ -384,6 +534,8 @@ class NormSession bool SimSocketRecvHandler(char* buffer, unsigned short buflen, const ProtoAddress& src, bool unicast); #endif // SIMULATE + + void SetProbeCount(unsigned probeCount) {probe_count = probeCount;} private: // Only NormSessionMgr can create/delete sessions @@ -394,26 +546,34 @@ class NormSession bool QueueTxObject(NormObject* obj); + double GetProbeInterval(); + bool OnTxTimeout(ProtoTimer& theTimer); bool OnRepairTimeout(ProtoTimer& theTimer); bool OnFlushTimeout(ProtoTimer& theTimer); bool OnProbeTimeout(ProtoTimer& theTimer); bool OnReportTimeout(ProtoTimer& theTimer); + bool OnCmdTimeout(ProtoTimer& theTimer); + bool OnFlowControlTimeout(ProtoTimer& theTimer); void TxSocketRecvHandler(ProtoSocket& theSocket, ProtoSocket::Event theEvent); - void RxSocketRecvHandler(ProtoSocket& theSocket, ProtoSocket::Event theEve); - void HandleReceiveMessage(NormMsg& msg, bool wasUnicast); + void RxSocketRecvHandler(ProtoSocket& theSocket, ProtoSocket::Event theEvent); + void HandleReceiveMessage(NormMsg& msg, bool wasUnicast, bool ecn = false); - // Server message handling routines - void ServerHandleNackMessage(const struct timeval& currentTime, + // This is used when raw packet capture is enabled + void OnPktCapture(ProtoChannel& theChannel, + ProtoChannel::Notification notifyType); + + // Sender message handling routines + void SenderHandleNackMessage(const struct timeval& currentTime, NormNackMsg& nack); - void ServerHandleAckMessage(const struct timeval& currentTime, + void SenderHandleAckMessage(const struct timeval& currentTime, const NormAckMsg& ack, bool wasUnicast); - void ServerUpdateGrttEstimate(double clientRtt); + void SenderUpdateGrttEstimate(double rcvrRtt); double CalculateRtt(const struct timeval& currentTime, const struct timeval& grttResponse); - void ServerHandleCCFeedback(struct timeval currentTime, + void SenderHandleCCFeedback(struct timeval currentTime, NormNodeId nodeId, UINT8 ccFlags, double ccRtt, @@ -422,27 +582,39 @@ class NormSession UINT16 ccSequence); void AdjustRate(bool onResponse); void SetTxRateInternal(double txRate); // here, txRate is bytes/sec - bool ServerQueueSquelch(NormObjectId objectId); - void ServerQueueFlush(); - bool ServerQueueWatermarkFlush(); - bool ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd); - void ServerUpdateGroupSize(); + bool SenderQueueSquelch(NormObjectId objectId); + void SenderQueueFlush(); + bool SenderQueueWatermarkFlush(); + bool SenderBuildRepairAdv(NormCmdRepairAdvMsg& cmd); + void SenderUpdateGroupSize(); + bool SenderQueueAppCmd(); - // Client message handling routines - void ClientHandleObjectMessage(const struct timeval& currentTime, - const NormObjectMsg& msg); - void ClientHandleCommand(const struct timeval& currentTime, - const NormCmdMsg& msg); - void ClientHandleNackMessage(const NormNackMsg& nack); - void ClientHandleAckMessage(const NormAckMsg& ack); + // Receiver message handling routines + void ReceiverHandleObjectMessage(const struct timeval& currentTime, + const NormObjectMsg& msg, + bool ecnStatus); + void ReceiverHandleCommand(const struct timeval& currentTime, + const NormCmdMsg& msg, + bool ecnStatus); + void ReceiverHandleNackMessage(const NormNackMsg& nack); + void ReceiverHandleAckMessage(const NormAckMsg& ack); NormSessionMgr& session_mgr; bool notify_pending; ProtoTimer tx_timer; UINT16 tx_port; + bool tx_port_reuse; + ProtoAddress tx_address; // bind tx_socket to tx_address when valid ProtoSocket tx_socket_actual; ProtoSocket* tx_socket; ProtoSocket rx_socket; + ProtoCap* rx_cap; // raw packet capture alternative to "rx_socket" + bool rx_port_reuse; // enable rx_socket port (sessionPort) reuse when true + ProtoAddress rx_bind_addr; + ProtoAddress rx_connect_addr; + + + ProtoAddressList dst_addr_list; // list of local addresses NormMessageQueue message_queue; NormMessageQueue message_pool; ProtoTimer report_timer; @@ -450,20 +622,22 @@ class NormSession // General session parameters NormNodeId local_node_id; - ProtoAddress address; // session destination address & port - UINT8 ttl; // session multicast ttl - UINT8 tos; // session IPv4 TOS (or IPv6 traffic class - TBD) - bool loopback; // to receive own traffic - bool rx_port_reuse; // enable rx_socket port (sessionPort) reuse when true - bool rx_addr_bind; // bind rx_socket to sessionAddr when true - char interface_name[32]; + ProtoAddress address; // session destination address/port + UINT8 ttl; // session multicast ttl + UINT8 tos; // session IPv4 TOS (or IPv6 traffic class - TBD) + bool loopback; // receive own traffic it true + bool fragmentation; // enable UDP/IP fragmentation (i.e. clear DF bit) if true + bool ecn_enabled; // set true to get raw packets and check for ECN status + + char interface_name[IFACE_NAME_MAX+1]; double tx_rate; // bytes per second double tx_rate_min; double tx_rate_max; - double backoff_factor; - // Server parameters and state - bool is_server; + + // Sender parameters and state + double backoff_factor; + bool is_sender; int tx_robust_factor; UINT16 instance_id; UINT16 segment_size; @@ -472,6 +646,8 @@ class NormSession UINT16 auto_parity; UINT16 extra_parity; bool sndr_emcon; + bool tx_only; + bool tx_connect; NormObjectTable tx_table; ProtoSlidingMask tx_pending_mask; @@ -480,6 +656,8 @@ class NormSession NormBlockPool block_pool; NormSegmentPool segment_pool; NormEncoder* encoder; + UINT8 fec_id; + UINT8 fec_m; NormObjectId next_tx_object_id; unsigned int tx_cache_count_min; @@ -488,12 +666,15 @@ class NormSession ProtoTimer flush_timer; int flush_count; bool posted_tx_queue_empty; + bool posted_tx_rate_changed; // For postive acknowledgement collection NormNodeTree acking_node_tree; unsigned int acking_node_count; unsigned int acking_success_count; + TrackingStatus acking_auto_populate; // whether / how to "auto populate" acking node list bool watermark_pending; + bool watermark_flushes; bool watermark_active; NormObjectId watermark_object_id; NormBlockId watermark_block_id; @@ -533,31 +714,52 @@ class NormSession UINT8 gsize_quantized; // Sender congestion control parameters + unsigned int probe_count; // for experimentation (cc probes per rtt) bool cc_enable; - UINT8 cc_sequence; + bool cc_adjust; + UINT16 cc_sequence; NormNodeList cc_node_list; bool cc_slow_start; bool cc_active; - unsigned long sent_accumulator; // for sentRate measurement + NormNode::Accumulator sent_accumulator; // for sentRate measurement double nominal_packet_size; bool data_active; // true when actively sending data + double flow_control_factor; + ProtoTimer flow_control_timer; + NormObjectId flow_control_object; + NormController::Event flow_control_event; + + // Sender "app-defined" command state + unsigned int cmd_count; + char* cmd_buffer; + unsigned int cmd_length; + ProtoTimer cmd_timer; // Receiver parameters - bool is_client; + bool is_receiver; int rx_robust_factor; - NormNodeTree server_tree; - unsigned long remote_server_buffer_size; + NormSenderNode* preset_sender; + NormNodeTree sender_tree; + unsigned long remote_sender_buffer_size; bool unicast_nacks; - bool client_silent; + bool receiver_silent; bool rcvr_ignore_info; INT32 rcvr_max_delay; - NormServerNode::RepairBoundary default_repair_boundary; - NormObject::NackingMode default_nacking_mode; + bool rcvr_realtime; + NormSenderNode::RepairBoundary default_repair_boundary; + NormObject::NackingMode default_nacking_mode; + NormSenderNode::SyncPolicy default_sync_policy; + UINT16 rx_cache_count_max; + + // State for some experimental congestion control + bool ecn_ignore_loss; + bool cc_tolerate_loss; // Protocol test/debug parameters bool trace; double tx_loss_rate; // for correlated loss double rx_loss_rate; // for uncorrelated loss + double report_timer_interval; const void* user_data; @@ -565,5 +767,11 @@ class NormSession NormSession* next; }; // end class NormSession +// This function prints out NORM message info +void NormTrace(const struct timeval& currentTime, + NormNodeId localId, + const NormMsg& msg, + bool sent, + UINT8 fecM); #endif // _NORM_SESSION diff --git a/common/normSimAgent.h b/include/normSimAgent.h similarity index 76% rename from common/normSimAgent.h rename to include/normSimAgent.h index b1a585c..173b5e6 100644 --- a/common/normSimAgent.h +++ b/include/normSimAgent.h @@ -15,14 +15,22 @@ class NormSimAgent : public NormController, public ProtoMessageSink bool ProcessCommand(const char* cmd, const char* val); - // Note: don't allow client _and_ server operation at same time - bool StartServer(); // start sender - bool StartClient(); // start receiver + // Note: don't allow receiver _and_ sender operation at same time + bool StartSender(); // start sender + bool StartReceiver(); // start receiver bool IsActive() {return (NULL != session);} void Stop(); bool SendMessage(unsigned int len, const char* txBuffer); + + enum NormCC + { + NORM_FIXED, // fixed-rate (no congestion control) + NORM_CC, // "normal" TCP-friendly congestion control + NORM_CCE, // strict ECN-based congestion control + NORM_CCL // "loss-tolerant" congestion control + }; protected: NormSimAgent(ProtoTimerMgr& timerMgr, @@ -45,13 +53,15 @@ class NormSimAgent : public NormController, public ProtoMessageSink virtual void Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* server, + class NormSenderNode* sender, class NormObject* object); void ActivateTimer(ProtoTimer& theTimer) {session_mgr.ActivateTimer(theTimer);} bool OnIntervalTimeout(ProtoTimer& theTimer); + + void SetCCMode(NormCC ccMode); static const char* const cmd_list[]; @@ -63,9 +73,12 @@ class NormSimAgent : public NormController, public ProtoMessageSink UINT16 port; // session port number UINT8 ttl; double tx_rate; // bits/sec + unsigned int probe_count; bool cc_enable; + bool ecn_enable; + NormCC cc_mode; bool unicast_nacks; - bool silent_client; + bool silent_receiver; double backoff_factor; UINT16 segment_size; UINT8 ndata; @@ -75,8 +88,13 @@ class NormSimAgent : public NormController, public ProtoMessageSink double group_size; double grtt_estimate; unsigned long tx_buffer_size; // bytes + unsigned long tx_cache_min; + unsigned long tx_cache_max; + NormObjectSize tx_cache_size; + unsigned long rx_buffer_size; // bytes - + unsigned long rx_cache_max; // rx object max_pending count + // for simulated transmission (streams or files) unsigned long tx_object_size; double tx_object_interval; @@ -84,6 +102,8 @@ class NormSimAgent : public NormController, public ProtoMessageSink unsigned long tx_object_size_max; int tx_repeat_count; double tx_repeat_interval; + int tx_requeue; + int tx_requeue_count; NormStreamObject* stream; bool auto_stream; @@ -100,6 +120,7 @@ class NormSimAgent : public NormController, public ProtoMessageSink // protocol debug parameters bool tracing; + FILE* log_file_ptr; double tx_loss; double rx_loss; diff --git a/common/normVersion.h b/include/normVersion.h similarity index 98% rename from common/normVersion.h rename to include/normVersion.h index 22dfe6d..923bce8 100644 --- a/common/normVersion.h +++ b/include/normVersion.h @@ -32,6 +32,6 @@ #ifndef _NORM_VERSION #define _NORM_VERSION -#define VERSION "1.4b3a" +#define VERSION "1.5b1" #endif // _NORM_VERSION diff --git a/unix/Makefile.arm-linux b/makefiles/Makefile.arm-linux similarity index 99% rename from unix/Makefile.arm-linux rename to makefiles/Makefile.arm-linux index feb8156..442425d 100644 --- a/unix/Makefile.arm-linux +++ b/makefiles/Makefile.arm-linux @@ -50,6 +50,7 @@ SYSTEM = arm-linux CC = arm-linux-g++ SYSTEM_CFLAGS = -Wall -Wcast-align -pedantic -fPIC SYSTEM_SOFLAGS = -shared +SYSTEM_SOEXT = so RANLIB = arm-linux-ranlib AR = arm-linux-ar diff --git a/makefiles/Makefile.common b/makefiles/Makefile.common new file mode 100644 index 0000000..f9a6374 --- /dev/null +++ b/makefiles/Makefile.common @@ -0,0 +1,243 @@ +######################################################################### +# COMMON NORM MAKEFILE STUFF +# + +SHELL=/bin/sh + +.SUFFIXES: .cpp -sim.o $(.SUFFIXES) + +PROTOLIB = ../protolib +COMMON = ../src/common +UNIX = ../src/unix +EXAMPLE = ../examples +NS = ../src/sim/ns + +INCLUDES = $(SYSTEM_INCLUDES) -I$(UNIX) -I../include -I$(PROTOLIB)/include + +CFLAGS = -g -DPROTO_DEBUG -DUNIX -D_FILE_OFFSET_BITS=64 -O $(SYSTEM_CFLAGS) $(SYSTEM_HAVES) $(INCLUDES) + +LDFLAGS = $(SYSTEM_LDFLAGS) + +# Note: Even command line app needs X11 for Netscape post-processing +LIBS = $(SYSTEM_LIBS) -lm -lpthread +XLIBS = -lXmu -lXt -lX11 + + +TARGETS = norm raft + +# Rule for C++ .cpp extension +.cpp.o: + $(CC) -c $(CFLAGS) -o $*.o $*.cpp + +# NORM depends upon the NRL Protean Group's development library +LIBPROTO = $(PROTOLIB)/lib/libprotokit.a + +NORM_SRC = $(COMMON)/normMessage.cpp $(COMMON)/normSession.cpp \ + $(COMMON)/normNode.cpp $(COMMON)/normObject.cpp \ + $(COMMON)/normSegment.cpp $(COMMON)/normEncoder.cpp \ + $(COMMON)/normEncoderRS8.cpp $(COMMON)/normEncoderRS16.cpp \ + $(COMMON)/normEncoderMDP.cpp $(COMMON)/galois.cpp \ + $(COMMON)/normFile.cpp $(COMMON)/normApi.cpp $(SYSTEM_SRC) + +NORM_OBJ = $(NORM_SRC:.cpp=.o) + +LIB_SRC = $(NORM_SRC) +LIB_OBJ = $(LIB_SRC:.cpp=.o) + +all: norm raft libnorm.a + +$(PROTOLIB)/lib/libprotokit.a: + $(MAKE) -C $(PROTOLIB)/makefiles -f Makefile.$(SYSTEM) libprotokit.a + +# NORM as a static library +libnorm.a: $(LIB_OBJ) + rm -f $@ + $(AR) rcv $@ $(LIB_OBJ) + $(RANLIB) $@ + mkdir -p ../lib + cp $@ ../lib/$@ + +# NORM as a shared library (i.e. libnorm.so, libnorm.dylib, etc) +# (Note - we are linking "libprotokit" in statically +libnorm.$(SYSTEM_SOEXT): $(LIB_OBJ) $(LIBPROTO) + $(CC) $(SYSTEM_SOFLAGS) $(LIB_OBJ) $(LIBPROTO) $(LIBS) -o $@ + mkdir -p ../lib + cp $@ ../lib/$@ + +SIM_SRC = $(NORM_SRC) $(COMMON)/normSimAgent.cpp +SIM_OBJ = $(SIM_SRC:.cpp=-sim.o) + +libnormsim.a: $(SIM_OBJ) + rm -f $@ + $(AR) rcv $@ $(SIM_OBJ) + $(RANLIB) $@ + +# (norm) command-line file broadcaster/receiver +APP_SRC = $(COMMON)/normApp.cpp $(COMMON)/normPostProcess.cpp \ + $(UNIX)/unixPostProcess.cpp +APP_OBJ = $(APP_SRC:.cpp=.o) + +norm: $(APP_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(APP_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + + +# This section builds some of code "examples" that use the NORM API + +# NORM_OBJECT_FILE sender example +FSEND_SRC = $(EXAMPLE)/normFileSend.cpp +FSEND_OBJ = $(FSEND_SRC:.cpp=.o) + +normFileSend: $(FSEND_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(FSEND_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# NORM_OBJECT_FILE receiver example +FRECV_SRC = $(EXAMPLE)/normFileRecv.cpp +FRECV_OBJ = $(FRECV_SRC:.cpp=.o) + +normFileRecv: $(FRECV_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(FRECV_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + + +# NORM_OBJECT_DATA sender example +DSEND_SRC = $(EXAMPLE)/normDataSend.cpp +DSEND_OBJ = $(DSEND_SRC:.cpp=.o) + +normDataSend: $(DSEND_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(DSEND_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# NORM_OBJECT_DATA receiver example +DRECV_SRC = $(EXAMPLE)/normDataRecv.cpp +DRECV_OBJ = $(DRECV_SRC:.cpp=.o) + +normDataRecv: $(DRECV_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(DRECV_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + + +# NORM_OBJECT_STREAM sender example +SSEND_SRC = $(EXAMPLE)/normStreamSend.cpp +SSEND_OBJ = $(SSEND_SRC:.cpp=.o) + +normStreamSend: $(SSEND_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(SSEND_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + + +# NORM_OBJECT_STREAM receiver example +SRECV_SRC = $(EXAMPLE)/normStreamRecv.cpp +SRECV_OBJ = $(SRECV_SRC:.cpp=.o) + +normStreamRecv: $(SRECV_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(SRECV_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + + +# (normTest) test of NORM API +TEST_SRC = $(COMMON)/normTest.cpp +TEST_OBJ = $(TEST_SRC:.cpp=.o) + +normTest: $(TEST_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(TEST_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (normThreadTest) test of threaded use of NORM API +TTEST_SRC = $(COMMON)/normThreadTest.cpp +TTEST_OBJ = $(TTEST_SRC:.cpp=.o) +normThreadTest: $(TTEST_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(TTEST_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (normThreadTest2) alt test of threaded use of NORM API +TTEST2_SRC = $(UNIX)/normThreadTest2.cpp +TTEST2_OBJ = $(TTEST2_SRC:.cpp=.o) +normThreadTest2: $(TTEST2_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(TTEST2_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 +PCAP_OBJ = $(PCAP_SRC:.cpp=.o) + +pcap2norm: $(PCAP_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(PCAP_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (n2m) - converts NORM "trace" to MGEN log format to enable TRPR (or other) analyses +N2M_SRC = $(COMMON)/n2m.cpp +N2M_OBJ = $(N2M_SRC:.cpp=.o) + +n2m: $(N2M_OBJ) + $(CC) $(CFLAGS) -o $@ $(N2M_OBJ) $(LDFLAGS) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (npc) NORM Pre-Coder +PCODE_SRC = $(COMMON)/normPrecode.cpp +PCODE_OBJ = $(PCODE_SRC:.cpp=.o) +npc: $(PCODE_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(PCODE_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (raft) reliable app for tunnel (command-line reliable UDP tunnel) +RAFT_SRC = $(COMMON)/raft.cpp +RAFT_OBJ = $(RAFT_SRC:.cpp=.o) + +raft: $(RAFT_OBJ) $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(RAFT_OBJ) $(LDFLAGS) $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (ntun) IP/NORM tunnel app using ProtoVif +NTUN_SRC = $(COMMON)/ntun.cpp $(PROTOLIB)/common/protoVif.cpp $(PROTOLIB)/unix/unixVif.cpp +NTUN_OBJ = $(NTUN_SRC:.cpp=.o) + +ntun: $(NTUN_OBJ) $(LIBPROTO) libnorm.a + $(CC) $(CFLAGS) -o $@ $(NTUN_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (fect) fec tester code +FECT_SRC = $(COMMON)/fecTest.cpp $(COMMON)/normEncoder.cpp $(COMMON)/galois.cpp \ + $(COMMON)/normEncoderRS8.cpp $(COMMON)/normEncoderRS16.cpp +FECT_OBJ = $(FECT_SRC:.cpp=.o) +fect: $(FECT_OBJ) libnorm.a $(LIBPROTO) + $(CC) $(CFLAGS) -o $@ $(FECT_OBJ) $(LDFLAGS) $(LIBPROTO) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +# (gtf) generate test file +GTF_SRC = $(COMMON)/gtf.cpp +GTF_OBJ = $(GTF_SRC:.cpp=.o) +gtf: $(GTF_OBJ) + $(CC) $(CFLAGS) -o $@ $(GTF_OBJ) $(LDFLAGS) $(LIBS) + mkdir -p ../bin + cp $@ ../bin/$@ + +clean: + rm -f $(COMMON)/*.o $(UNIX)/*.o $(NS)/*.o \ + libnorm.a libnorm.$(SYSTEM_SOEXT) ../lib/libnorm.a ../lib/libnorm.$(SYSTEM_SOEXT) \ + norm raft normTest normTest2 normThreadTest normThreadTest2 ../bin/*; + $(MAKE) -C $(PROTOLIB)/makefiles -f Makefile.$(SYSTEM) clean +distclean: clean + +# DO NOT DELETE THIS LINE -- mkdep uses it. +# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. + diff --git a/unix/Makefile.freebsd b/makefiles/Makefile.freebsd similarity index 99% rename from unix/Makefile.freebsd rename to makefiles/Makefile.freebsd index 22c88ef..3b3f68e 100644 --- a/unix/Makefile.freebsd +++ b/makefiles/Makefile.freebsd @@ -40,7 +40,7 @@ SYSTEM_HAVES = -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK -DHAVE_DI SYSTEM_SRC = SYSTEM = freebsd -CC = gcc +CC = g++ SYSTEM_CFLAGS = -fPIC -Wall -pedantic -Wcast-align SYSTEM_SOFLAGS = -shared RANLIB = ranlib diff --git a/unix/Makefile.hpux b/makefiles/Makefile.hpux similarity index 100% rename from unix/Makefile.hpux rename to makefiles/Makefile.hpux diff --git a/makefiles/Makefile.iphone b/makefiles/Makefile.iphone new file mode 100644 index 0000000..d759333 --- /dev/null +++ b/makefiles/Makefile.iphone @@ -0,0 +1,58 @@ +# +# Protean MacOS X (Darwin) Makefile definitions +# + +SDK = /Developer/Platforms/iPhoneOS.platform/Developer +ARCH = armv6 +SYSROOT = $(SDK)/SDKs/iPhoneOS2.2.1.sdk + +# 1) System specific additional libraries, include paths, etc +# (Where to find X11 libraries, etc) +# + +SYSTEM_INCLUDES = +SYSTEM_LDFLAGS = -L$(SYSROOT)/usr/lib +SYSTEM_LIBS = -lresolv + +# 2) System specific capabilities +# Must choose appropriate for the following: +# +# A) -DHAVE_CUSERID (preferred) or -DHAVE_GETLOGIN for cuserid() or getlogin() +# functions to obtain user's login name (We may change this to getpwd() +# if that is better across different platforms and login environments) +# +# B) -DHAVE_LOCKF (preferred) or -D_LOCKF for lockf() or flock() file locking +# functions to attempt exclusive lock on writing to files +# +# C) Specify -DHAVE_DIRFD if you system provides the "dirfd()" function +# (Most don't have it defined ... but some do) +# +# D) Optionally specify -DHAVE_ASSERT if your system has a built-in ASSERT() +# routine. +# +# E) Some systems (SOLARIS/SUNOS) have a few gotchas which require +# some #ifdefs to avoid compiler warnings ... so you might need +# to specify -DSOLARIS or -DSUNOS depending on your OS. +# +# F) Uncomment this if you have the NRL IPv6+IPsec software +#DNETSEC = -DNETSEC -I/usr/inet6/include +# +# (We export these for other Makefiles as needed) +# + +SYSTEM_HAVES = -DMACOSX -DECN_SUPPORT -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK \ +-D_FILE_OFFSET_BITS=64 -DHAVE_DIRFD + +SYSTEM_SRC = ../protolib/src/unix/bpfCap.cpp + +# The "SYSTEM" keyword can be used for dependent makes +SYSTEM = iphone + +CC = $(SDK)/usr/bin/arm-apple-darwin9-g++-4.0.1 -arch $(ARCH) -isysroot $(SYSROOT) +SYSTEM_CFLAGS = -Wall -Wcast-align -pedantic -fPIC -I$(SDK)/usr/include/gcc/darwin/4.0 +SYSTEM_SOFLAGS = -dynamiclib +SYSTEM_SOEXT = dylib +RANLIB = $(SDK)/usr/bin/ranlib +AR = $(SDK)/usr/bin/ar + +include Makefile.common diff --git a/unix/Makefile.linux b/makefiles/Makefile.linux similarity index 86% rename from unix/Makefile.linux rename to makefiles/Makefile.linux index 88cae13..8aa0be8 100644 --- a/unix/Makefile.linux +++ b/makefiles/Makefile.linux @@ -35,16 +35,17 @@ SYSTEM_LIBS = -ldl # (We export these for other Makefiles as needed) # -SYSTEM_HAVES = -DLINUX -DHAVE_IPV6 -DHAVE_GETLOGIN -D_FILE_OFFSET_BITS=64 -DHAVE_LOCKF \ +SYSTEM_HAVES = -DLINUX -DECN_SUPPORT -DHAVE_IPV6 -DHAVE_GETLOGIN -D_FILE_OFFSET_BITS=64 -DHAVE_LOCKF \ -DHAVE_OLD_SIGNALHANDLER -DHAVE_DIRFD -DHAVE_ASSERT -SYSTEM_SRC = +SYSTEM_SRC = ../protolib/src/linux/linuxCap.cpp SYSTEM = linux export CC = g++ export SYSTEM_CFLAGS = -fPIC -Wall -Wcast-align -export SYSTEM_SOFLAGS = -shared +export SYSTEM_SOFLAGS = -shared -Wl,-soname,libnorm.so.1 +SYSTEM_SOEXT = so export RANLIB = ranlib export AR = ar diff --git a/unix/Makefile.macosx b/makefiles/Makefile.macosx similarity index 82% rename from unix/Makefile.macosx rename to makefiles/Makefile.macosx index c49554a..1ed670b 100644 --- a/unix/Makefile.macosx +++ b/makefiles/Makefile.macosx @@ -8,7 +8,7 @@ SYSTEM_INCLUDES = SYSTEM_LDFLAGS = -SYSTEM_LIBS = -lresolv +SYSTEM_LIBS = -lresolv -lpcap # 2) System specific capabilities # Must choose appropriate for the following: @@ -36,17 +36,18 @@ SYSTEM_LIBS = -lresolv # (We export these for other Makefiles as needed) # -SYSTEM_HAVES = -DMACOSX -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK \ +SYSTEM_HAVES = -DMACOSX -DECN_SUPPORT -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK \ -D_FILE_OFFSET_BITS=64 -DHAVE_DIRFD -SYSTEM_SRC = +SYSTEM_SRC = ../protolib/src/unix/bpfCap.cpp # The "SYSTEM" keyword can be used for dependent makes SYSTEM = macosx CC = g++ -SYSTEM_CFLAGS = -fPIC -Wall -Wcast-align -SYSTEM_SOFLAGS = -dynamiclib +SYSTEM_CFLAGS = -Wall -Wcast-align -fPIC -arch x86_64 -arch i386 +SYSTEM_SOFLAGS = -dynamiclib +SYSTEM_SOEXT = dylib RANLIB = ranlib AR = ar diff --git a/unix/Makefile.mklinux b/makefiles/Makefile.mklinux similarity index 100% rename from unix/Makefile.mklinux rename to makefiles/Makefile.mklinux diff --git a/unix/Makefile.netbsd b/makefiles/Makefile.netbsd similarity index 100% rename from unix/Makefile.netbsd rename to makefiles/Makefile.netbsd diff --git a/unix/Makefile.sgi b/makefiles/Makefile.sgi similarity index 100% rename from unix/Makefile.sgi rename to makefiles/Makefile.sgi diff --git a/unix/Makefile.solaris b/makefiles/Makefile.solaris similarity index 100% rename from unix/Makefile.solaris rename to makefiles/Makefile.solaris diff --git a/unix/Makefile.solx86 b/makefiles/Makefile.solx86 similarity index 100% rename from unix/Makefile.solx86 rename to makefiles/Makefile.solx86 diff --git a/makefiles/android/AndroidManifest.xml b/makefiles/android/AndroidManifest.xml new file mode 100644 index 0000000..d208dda --- /dev/null +++ b/makefiles/android/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/makefiles/android/README b/makefiles/android/README new file mode 100644 index 0000000..738fd34 --- /dev/null +++ b/makefiles/android/README @@ -0,0 +1,52 @@ +This directory contains an Android NDK import module for NORM, which builds +a shared library, and the JNI bindings. + +Using NORM in your pure Java Android Application +------------------------------------------------ + +1. First, update the library project to point to your Android SDK: + + android update lib-project --target --path /path/to/norm/makefiles/android + android update lib-project --target --path /path/to/protolib/makefiles/android + + Use "android list targets" to see the ids of the SDK targets you have + installed. + +2. Add a "jni/Application.mk" in the root of your project to reference NORM: + * You must at least set "APP_MODULES := norm-jni" + * You should set "APP_BUILD_SCRIPT := /path/to/norm/makefiles/android/jni/Android.mk" + * You should also set "NDK_MODULE_PATH" to point to the base norm directory + so that the NDK can find protolib. See the example. + + Example: + + LOCAL_PATH := $(call my-dir) + + APP_MODULES := mil_navy_nrl_norm + APP_ABI := all + APP_BUILD_SCRIPT := $(LOCAL_PATH)/../library/norm/makefiles/android/jni/Android.mk + + NDK_MODULE_PATH := $(LOCAL_PATH)../library/norm/ + +3. Reference the NORM Android library project in your "project.properties" + file. Add the following: + + android.library.reference.1=/path/to/norm/makefiles/android + +Now, you should be able to run "ndk-build" in your root project directory to +build the shared libraries, and run "ant debug" to build your APK. + +Using NORM in your C/C++/Java Android Application +------------------------------------------------- + +1. Add a "jni/Application.mk" with your C/C++ code specified. + +2. Reference the NORM Android project as an NDK Import Module. See + "IMPORT-MODULE.html" in the NDK docs. + + Put something like the following at the bottom of your "Android.mk" + + $(call import-module,norm/makefiles/android/jni) + + This is relative to the "NDK_MODULE_PATH" variable that must be defined in + "Application.mk". diff --git a/makefiles/android/ant.properties b/makefiles/android/ant.properties new file mode 100644 index 0000000..99c69bb --- /dev/null +++ b/makefiles/android/ant.properties @@ -0,0 +1,18 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +source.dir=../../src/java/src diff --git a/makefiles/android/build.xml b/makefiles/android/build.xml new file mode 100644 index 0000000..86812f8 --- /dev/null +++ b/makefiles/android/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/android/jni/Android.mk b/makefiles/android/jni/Android.mk new file mode 100644 index 0000000..5895d8c --- /dev/null +++ b/makefiles/android/jni/Android.mk @@ -0,0 +1,55 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := norm +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/../../../protolib/include \ + $(LOCAL_PATH)/../../../include +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) +LOCAL_STATIC_LIBRARIES := protolib + +ifeq ($(APP_OPTIM),debug) + LOCAL_CFLAGS += -DNORM_DEBUG +endif +LOCAL_EXPORT_CFLAGS := $(LOCAL_CFLAGS) + +LOCAL_SRC_FILES := \ + ../../../src/common/galois.cpp \ + ../../../src/common/normApi.cpp \ + ../../../src/common/normEncoder.cpp \ + ../../../src/common/normEncoderMDP.cpp \ + ../../../src/common/normEncoderRS16.cpp \ + ../../../src/common/normEncoderRS8.cpp \ + ../../../src/common/normFile.cpp \ + ../../../src/common/normMessage.cpp \ + ../../../src/common/normNode.cpp \ + ../../../src/common/normObject.cpp \ + ../../../src/common/normSegment.cpp \ + ../../../src/common/normSession.cpp +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := mil_navy_nrl_norm +LOCAL_STATIC_LIBRARIES := norm protolib +LOCAL_SRC_FILES := \ + ../../../src/java/jni/normDataJni.cpp \ + ../../../src/java/jni/normEventJni.cpp \ + ../../../src/java/jni/normFileJni.cpp \ + ../../../src/java/jni/normInstanceJni.cpp \ + ../../../src/java/jni/normJni.cpp \ + ../../../src/java/jni/normNodeJni.cpp \ + ../../../src/java/jni/normObjectJni.cpp \ + ../../../src/java/jni/normSessionJni.cpp \ + ../../../src/java/jni/normStreamJni.cpp +include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := norm-app +LOCAL_STATIC_LIBRARIES := norm +LOCAL_SRC_FILES := \ + ../../../src/common/normApp.cpp \ + ../../../src/common/normPostProcess.cpp \ + ../../../src/unix/unixPostProcess.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 new file mode 100644 index 0000000..b448d58 --- /dev/null +++ b/makefiles/android/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := all +APP_PLATFORM := android-9 diff --git a/makefiles/android/proguard-project.txt b/makefiles/android/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/makefiles/android/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/makefiles/android/project.properties b/makefiles/android/project.properties new file mode 100644 index 0000000..616f300 --- /dev/null +++ b/makefiles/android/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-16 diff --git a/makefiles/java/Makefile.common b/makefiles/java/Makefile.common new file mode 100644 index 0000000..be0feaf --- /dev/null +++ b/makefiles/java/Makefile.common @@ -0,0 +1,65 @@ +######################################################################### +# COMMON NORM-JNI MAKEFILE STUFF +# + +.SUFFIXES: .cpp -sim.o $(.SUFFIXES) + +NORMJNI = ../../src/java/jni + +INCLUDES = $(SYSTEM_INCLUDES) -I../../include + +CFLAGS = -g -O $(SYSTEM_CFLAGS) $(INCLUDES) + +LIBNORM = ../../lib/libnorm.a +PROTOLIB = ../../protolib +LIBPROTO = $(PROTOLIB)/lib/libprotokit.a + +LIBS = $(SYSTEM_LIBS) -lm -lpthread + +# Rule for C++ .cpp extension +.cpp.o: + $(CC) -c $(CFLAGS) -o $*.o $*.cpp + +LIB_SRC = \ + $(NORMJNI)/normJni.cpp \ + $(NORMJNI)/normInstanceJni.cpp \ + $(NORMJNI)/normSessionJni.cpp \ + $(NORMJNI)/normObjectJni.cpp \ + $(NORMJNI)/normDataJni.cpp \ + $(NORMJNI)/normFileJni.cpp \ + $(NORMJNI)/normStreamJni.cpp \ + $(NORMJNI)/normEventJni.cpp \ + $(NORMJNI)/normNodeJni.cpp + +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) + $(CC) $(SYSTEM_SOFLAGS) $(LIB_OBJ) $(LIBNORM) $(LIBPROTO) $(LIBS) -o $@ + mkdir -p ../../lib + cp $@ ../../lib/$@ + +$(LIBNORM): + $(MAKE) -C ../../makefiles -f Makefile.$(SYSTEM) libnorm.a + +$(LIBPROTO): + $(MAKE) -C $(PROTOLIB)/makefiles -f Makefile.$(SYSTEM) libprotokit.a + +-include $(LIB_DEP) + +%.d: %.cpp + $(CC) -MM -MT $(@:.d=.o) -MF $@ $(CPPFLAGS) $< + +clean: + rm -f $(NORMJNI)/*.o \ + $(NORMJNI)/*.d \ + libmil_navy_nrl_norm.$(SYSTEM_SOEXT) \ + ../../lib/libmil_navy_nrl_norm.$(SYSTEM_SOEXT) + +distclean: clean + +# DO NOT DELETE THIS LINE -- mkdep uses it. +# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. diff --git a/makefiles/java/Makefile.linux b/makefiles/java/Makefile.linux new file mode 100644 index 0000000..66f1902 --- /dev/null +++ b/makefiles/java/Makefile.linux @@ -0,0 +1,17 @@ +# +# Linux NORM-JNI Makefile definitions +# + +# System specific additional libraries, include paths, etc +SYSTEM_INCLUDES = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux +SYSTEM_LIBS = +SYSTEM_SRC = + +SYSTEM = linux + +export CC = g++ +export SYSTEM_CFLAGS = -fPIC -Wall -Wcast-align -D_FILE_OFFSET_BITS=64 +export SYSTEM_SOFLAGS = -shared -Wl,--no-undefined +SYSTEM_SOEXT = so + +include Makefile.common diff --git a/makefiles/java/build.xml b/makefiles/java/build.xml new file mode 100644 index 0000000..5263b05 --- /dev/null +++ b/makefiles/java/build.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/Norm.sln b/makefiles/win32/Norm.sln new file mode 100644 index 0000000..026f01d --- /dev/null +++ b/makefiles/win32/Norm.sln @@ -0,0 +1,63 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NormLib", "NormLib.vcproj", "{D7B0023C-8798-4918-8DA0-05C9054D70B9}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "norm", "norm\norm.vcproj", "{6E1308A6-D40F-489E-A4F1-40D859380D64}" + ProjectSection(ProjectDependencies) = postProject + {D7B0023C-8798-4918-8DA0-05C9054D70B9} = {D7B0023C-8798-4918-8DA0-05C9054D70B9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "normTest", "normTest.vcproj", "{32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + {182006F3-188F-466E-89FE-8421C0478691} = {182006F3-188F-466E-89FE-8421C0478691} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NormDll", "NormDll.vcproj", "{182006F3-188F-466E-89FE-8421C0478691}" + ProjectSection(ProjectDependencies) = postProject + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} = {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "npc", "npc.vcproj", "{7BF4525B-9185-4296-A2BF-FFE77BB66EAF}" + ProjectSection(ProjectDependencies) = postProject + {D7B0023C-8798-4918-8DA0-05C9054D70B9} = {D7B0023C-8798-4918-8DA0-05C9054D70B9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "..\..\protolib\makefiles\win32\Protokit.vcproj", "{DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Debug|Win32.ActiveCfg = Debug|Win32 + {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Debug|Win32.Build.0 = Debug|Win32 + {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Release|Win32.ActiveCfg = Release|Win32 + {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Release|Win32.Build.0 = Release|Win32 + {6E1308A6-D40F-489E-A4F1-40D859380D64}.Debug|Win32.ActiveCfg = Debug|Win32 + {6E1308A6-D40F-489E-A4F1-40D859380D64}.Debug|Win32.Build.0 = Debug|Win32 + {6E1308A6-D40F-489E-A4F1-40D859380D64}.Release|Win32.ActiveCfg = Release|Win32 + {6E1308A6-D40F-489E-A4F1-40D859380D64}.Release|Win32.Build.0 = Release|Win32 + {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug|Win32.ActiveCfg = Debug|Win32 + {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug|Win32.Build.0 = Debug|Win32 + {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release|Win32.ActiveCfg = Release|Win32 + {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release|Win32.Build.0 = Release|Win32 + {182006F3-188F-466E-89FE-8421C0478691}.Debug|Win32.ActiveCfg = Debug|Win32 + {182006F3-188F-466E-89FE-8421C0478691}.Debug|Win32.Build.0 = Debug|Win32 + {182006F3-188F-466E-89FE-8421C0478691}.Release|Win32.ActiveCfg = Release|Win32 + {182006F3-188F-466E-89FE-8421C0478691}.Release|Win32.Build.0 = Release|Win32 + {7BF4525B-9185-4296-A2BF-FFE77BB66EAF}.Debug|Win32.ActiveCfg = Debug|Win32 + {7BF4525B-9185-4296-A2BF-FFE77BB66EAF}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Debug|Win32.Build.0 = Debug|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.ActiveCfg = Release|Win32 + {DE94F096-A09B-44B6-8EFE-C7BF1F65C4C9}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/makefiles/win32/Norm.suo b/makefiles/win32/Norm.suo new file mode 100644 index 0000000000000000000000000000000000000000..2d18e9761f5a16a84f6fe2a07cec64f4711de972 GIT binary patch literal 62464 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 Yk4o2zb9ceA7yg^O)&F%||L*Sp10+I69smFU literal 0 HcmV?d00001 diff --git a/makefiles/win32/NormDll.vcproj b/makefiles/win32/NormDll.vcproj new file mode 100644 index 0000000..f30f579 --- /dev/null +++ b/makefiles/win32/NormDll.vcproj @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/NormLib.vcproj b/makefiles/win32/NormLib.vcproj new file mode 100644 index 0000000..786a994 --- /dev/null +++ b/makefiles/win32/NormLib.vcproj @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/NormLib.vcproj.vs2008 b/makefiles/win32/NormLib.vcproj.vs2008 new file mode 100644 index 0000000..b4443ee --- /dev/null +++ b/makefiles/win32/NormLib.vcproj.vs2008 @@ -0,0 +1,942 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/norm/norm.vcproj b/makefiles/win32/norm/norm.vcproj new file mode 100644 index 0000000..357e9d0 --- /dev/null +++ b/makefiles/win32/norm/norm.vcproj @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/normTest.vcproj b/makefiles/win32/normTest.vcproj new file mode 100644 index 0000000..73070b0 --- /dev/null +++ b/makefiles/win32/normTest.vcproj @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/makefiles/win32/npc.vcproj b/makefiles/win32/npc.vcproj new file mode 100644 index 0000000..c96e137 --- /dev/null +++ b/makefiles/win32/npc.vcproj @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/win32PostProcess.cpp b/makefiles/win32/win32PostProcess.cpp similarity index 100% rename from win32/win32PostProcess.cpp rename to makefiles/win32/win32PostProcess.cpp diff --git a/wince/Norm.vco b/makefiles/wince/Norm.vco similarity index 100% rename from wince/Norm.vco rename to makefiles/wince/Norm.vco diff --git a/wince/Norm.vcp b/makefiles/wince/Norm.vcp similarity index 100% rename from wince/Norm.vcp rename to makefiles/wince/Norm.vcp diff --git a/wince/Norm.vcw b/makefiles/wince/Norm.vcw similarity index 100% rename from wince/Norm.vcw rename to makefiles/wince/Norm.vcw diff --git a/wince/normApp/normApp.vcp b/makefiles/wince/normApp/normApp.vcp similarity index 100% rename from wince/normApp/normApp.vcp rename to makefiles/wince/normApp/normApp.vcp diff --git a/ns/ns-2.1b7a-Makefile.in b/ns/ns-2.1b7a-Makefile.in deleted file mode 100644 index 66124d8..0000000 --- a/ns/ns-2.1b7a-Makefile.in +++ /dev/null @@ -1,432 +0,0 @@ -# Copyright (c) 1994, 1995, 1996 -# The Regents of the University of California. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that: (1) source code distributions -# retain the above copyright notice and this paragraph in its entirety, (2) -# distributions including binary code include the above copyright notice and -# this paragraph in its entirety in the documentation or other materials -# provided with the distribution, and (3) all advertising materials mentioning -# features or use of this software display the following acknowledgement: -# ``This product includes software developed by the University of California, -# Lawrence Berkeley Laboratory and its contributors.'' Neither the name of -# the University nor the names of its contributors may be used to endorse -# or promote products derived from this software without specific prior -# written permission. -# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED -# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -# -# @(#) $Header: /cvsroot/norm/norm/ns/ns-2.1b7a-Makefile.in,v 1.1 2002/07/11 15:25:11 adamson Exp $ (LBL) - -# -# Various configurable paths (remember to edit Makefile.in, not Makefile) -# - -# Top level hierarchy -prefix = @prefix@ -# Pathname of directory to install the binary -BINDEST = @prefix@/bin -# Pathname of directory to install the man page -MANDEST = @prefix@/man - -BLANK = # make a blank space. DO NOT add anything to this line - -# The following will be redefined under Windows (see WIN32 lable below) -CC = @CC@ -CPP = @CXX@ -LINK = $(CPP) -MKDEP = ./conf/mkdep -TCLSH = @V_TCLSH@ -TCL2C = @V_TCL2CPP@ -AR = ar rc $(BLANK) - -RANLIB = @V_RANLIB@ -INSTALL = @INSTALL@ -LN = ln -TEST = test -RM = rm -f -PERL = @PERL@ - - -CCOPT = @V_CCOPT@ -STATIC = @V_STATIC@ -LDFLAGS = $(STATIC) -LDOUT = -o $(BLANK) - -DEFINE = -DTCP_DELAY_BIND_ALL -DNO_TK @V_DEFINE@ @V_DEFINES@ @DEFS@ - -PROTOLIB_INCLUDES = -Iprotolib/common -Iprotolib/ns -MDP_INCLUDES = -Imdp/common -Imdp/ns -Imdp/unix -NORM_INCLUDES = -Inorm/common -Inorm/ns - -INCLUDES = \ - -I. @V_INCLUDE_X11@ \ - @V_INCLUDES@ \ - $(PROTOLIB_INCLUDES) $(MDP_INCLUDES) $(NORM_INCLUDES) - -LIB = \ - @V_LIBS@ \ - @V_LIB_X11@ \ - @V_LIB@ \ - -lm @LIBS@ -# -L@libdir@ \ - -# These flags work on Linux -PROTOLIB_CFLAGS = -DUNIX -DNS2 -DPROTO_DEBUG -DSIMULATE -DHAVE_ASSERT -DHAVE_DIRFD - -CFLAGS = $(CCOPT) $(DEFINE) -g $(PROTOLIB_CFLAGS) - -# Explicitly define compilation rules since SunOS 4's make doesn't like gcc. -# Also, gcc does not remove the .o before forking 'as', which can be a -# problem if you don't own the file but can write to the directory. -# PROTOLIB/MDP/NORM code uses .cpp files so we added a suffix and rule -.SUFFIXES: .cc .cpp # $(.SUFFIXES) - -.cpp.o: - @rm -f $@ - $(CPP) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cpp - -.cc.o: - @rm -f $@ - $(CPP) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cc - -.c.o: - @rm -f $@ - $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $*.c - - -GEN_DIR = gen/ -LIB_DIR = lib/ -NS = ns -NSX = nsx -NSE = nse - -# To allow conf/makefile.win overwrite this macro -OBJ_STL = @V_STLOBJ@ - -# WIN32: uncomment the following line to include specific make for VC++ -# !include - -OBJ_CC = \ - random.o rng.o ranvar.o misc.o timer-handler.o \ - scheduler.o object.o \ - packet.o ip.o route.o connector.o ttl.o \ - trace.o trace-ip.o \ - classifier.o classifier-addr.o classifier-hash.o classifier-virtual.o \ - classifier-mcast.o classifier-bst.o classifier-mpath.o replicator.o \ - classifier-mac.o classifier-port.o ump.o \ - app.o telnet.o tcplib-telnet.o \ - trafgen.o traffictrace.o pareto.o expoo.o cbr_traffic.o \ - tbf.o resv.o sa.o saack.o \ - measuremod.o estimator.o adc.o ms-adc.o timewindow-est.o acto-adc.o \ - pointsample-est.o salink.o actp-adc.o hb-adc.o expavg-est.o\ - param-adc.o null-estimator.o \ - adaptive-receiver.o vatrcvr.o consrcvr.o \ - agent.o message.o udp.o session-rtp.o rtp.o rtcp.o ivs.o \ - tcp.o tcp-sink.o tcp-reno.o tcp-newreno.o \ - tcp-vegas.o tcp-rbp.o tcp-full.o \ - scoreboard.o tcp-sack1.o tcp-fack.o \ - tcp-asym.o tcp-asym-sink.o tcp-fs.o tcp-asym-fs.o \ - tcp-int.o chost.o tcp-session.o nilist.o \ - integrator.o queue-monitor.o flowmon.o loss-monitor.o \ - queue.o drop-tail.o simple-intserv-sched.o red.o \ - semantic-packetqueue.o semantic-red.o ack-recons.o \ - sfq.o fq.o drr.o cbq.o \ - hackloss.o errmodel.o \ - delay.o snoop.o \ - dynalink.o rtProtoDV.o net-interface.o \ - ctrMcast.o mcast_ctrl.o srm.o \ - sessionhelper.o delaymodel.o srm-ssm.o \ - srm-topo.o \ - mftp.o mftp_snd.o mftp_rcv.o codeword.o \ - alloc-address.o address.o \ - $(LIB_DIR)int.Vec.o $(LIB_DIR)int.RVec.o \ - $(LIB_DIR)dmalloc_support.o \ - webcache/http.o webcache/tcp-simple.o webcache/pagepool.o \ - webcache/inval-agent.o webcache/tcpapp.o webcache/http-aux.o \ - webcache/mcache.o webcache/webtraf.o \ - realaudio/realaudio.o \ - lanRouter.o filter.o pkt-counter.o \ - Decapsulator.o Encapsulator.o encap.o \ - channel.o mac.o ll.o mac-802_11.o mac-802_3.o mac-tdma.o \ - mip.o mip-reg.o gridkeeper.o \ - propagation.o tworayground.o antenna.o omni-antenna.o \ - shadowing.o bi-connector.o node.o mobilenode.o \ - arp.o god.o dem.o topography.o modulation.o priqueue.o \ - phy.o wired-phy.o wireless-phy.o \ - mac-timers.o cmu-trace.o varp.o \ - dsdv/dsdv.o dsdv/rtable.o rtqueue.o rttable.o \ - imep/imep.o imep/dest_queue.o imep/imep_api.o \ - imep/imep_rt.o imep/rxmit_queue.o imep/imep_timers.o \ - imep/imep_util.o imep/imep_io.o \ - tora/tora.o tora/tora_api.o tora/tora_dest.o tora/tora_io.o \ - tora/tora_logs.o tora/tora_neighbor.o \ - dsr/dsragent.o dsr/hdr_sr.o dsr/mobicache.o dsr/path.o \ - dsr/requesttable.o dsr/routecache.o \ - aodv/aodv_logs.o aodv/aodv.o \ - ns-process.o \ - satgeometry.o sathandoff.o satlink.o satnode.o \ - satposition.o satroute.o sattrace.o \ - rap/raplist.o rap/rap.o rap/media-app.o rap/utilities.o \ - fsm.o tcp-abs.o \ - diffusion/diffusion.o diffusion/diff_rate.o diffusion/diff_prob.o \ - diffusion/diff_sink.o diffusion/flooding.o diffusion/omni_mcast.o \ - diffusion/hash_table.o diffusion/routing_table.o diffusion/iflist.o \ - tfrc.o tfrc-sink.o energy-model.o ping.o tcp-rfc793edu.o \ - rio.o semantic-rio.o tcp-sack-rh.o scoreboard-rh.o \ - plm/loss-monitor-plm.o plm/cbr-traffic-PP.o \ - linkstate/hdr-ls.o \ - mpls/classifier-addr-mpls.o mpls/ldp.o mpls/mpls-module.o \ - rtmodule.o classifier-hier.o addr-params.o \ - $(OBJ_STL) - -# don't allow comments to follow continuation lines - -# mac-csma.o mac-multihop.o\ -# sensor-nets/landmark.o mac-simple-wireless.o \ -# sensor-nets/tags.o sensor-nets/sensor-query.o \ -# sensor-nets/flood-agent.o \ - -# what was here before is now in emulate/ -OBJ_C = - -OBJ_COMPAT = $(OBJ_GETOPT) win32.o -#XXX compat/win32x.o compat/tkConsole.o - -OBJ_EMULATE_CC = \ - emulate/net-ip.o \ - emulate/net.o \ - emulate/tap.o \ - emulate/ether.o \ - emulate/internet.o \ - emulate/ping_responder.o \ - emulate/arp.o \ - emulate/icmp.o \ - emulate/net-pcap.o \ - emulate/nat.o - -OBJ_EMULATE_C = \ - emulate/inet.o - -OBJ_PROTOLIB_CPP = \ - protolib/ns/nsProtoAgent.o protolib/common/protoSim.o \ - protolib/common/networkAddress.o protolib/common/protocolTimer.o \ - protolib/common/debug.o - -OBJ_MDP_CPP = \ - mdp/ns/nsMdpAgent.o mdp/common/mdpSimAgent.o \ - mdp/common/mdpBitMask.o mdp/common/mdpMessage.o \ - mdp/common/mdpEncoder.o mdp/common/galois.o \ - mdp/common/mdpSession.o mdp/common/mdpMsgHandler.o \ - mdp/common/mdpNode.o mdp/common/mdpObject.o \ - mdp/unix/mdpFile.o - -OBJ_NORM_CPP = \ - norm/ns/nsNormAgent.o norm/common/normSimAgent.o \ - norm/common/normMessage.o norm/common/normSession.o \ - norm/common/normNode.o norm/common/normObject.o \ - norm/common/normSegment.o norm/common/normBitmask.o \ - norm/common/normEncoder.o norm/common/galois.o \ - norm/common/normFile.o - -OBJ_GEN = $(GEN_DIR)version.o $(GEN_DIR)ns_tcl.o $(GEN_DIR)ptypes.o - -SRC = $(OBJ_C:.o=.c) $(OBJ_CC:.o=.cc) \ - $(OBJ_EMULATE_C:.o=.c) $(OBJ_EMULATE_CC:.o=.cc) \ - tclAppInit.cc tkAppInit.cc \ - $(OBJ_PROTOLIB_CPP:.o=.cpp) $(OBJ_MDP_CPP:.o=.cpp) \ - $(OBJ_NORM_CPP:.o=.cpp) - -OBJ = $(OBJ_C) $(OBJ_CC) $(OBJ_GEN) $(OBJ_COMPAT) \ - $(OBJ_PROTOLIB_CPP) $(OBJ_MDP_CPP) $(OBJ_NORM_CPP) - -CLEANFILES = ns nsx ns.dyn $(OBJ) $(OBJ_EMULATE_CC) \ - $(OBJ_EMULATE_C) tclAppInit.o \ - $(GEN_DIR)* $(NS).core core core.$(NS) core.$(NSX) core.$(NSE) \ - ptypes2tcl ptypes2tcl.o - -SUBDIRS=\ - indep-utils/cmu-scen-gen/setdest \ - indep-utils/webtrace-conv/dec \ - indep-utils/webtrace-conv/epa \ - indep-utils/webtrace-conv/nlanr \ - indep-utils/webtrace-conv/ucb - -all: $(NS) all-recursive - -all-recursive: - for i in $(SUBDIRS); do ( cd $$i; $(MAKE) all; ) done - -$(NS): $(OBJ) tclAppInit.o Makefile - $(LINK) $(LDFLAGS) $(LDOUT)$@ \ - tclAppInit.o $(OBJ) $(LIB) - -Makefile: Makefile.in - @echo "Makefile.in is newer than Makefile." - @echo "You need to re-run configure." - false - -$(NSE): $(OBJ) tclAppInit.o $(OBJ_EMULATE_CC) $(OBJ_EMULATE_C) - $(LINK) $(LDFLAGS) $(LDOUT)$@ \ - tclAppInit.o $(OBJ) \ - $(OBJ_EMULATE_CC) $(OBJ_EMULATE_C) $(LIB) -lpcap - -ns.dyn: $(OBJ) tclAppInit.o - $(LINK) $(LDFLAGS) -o $@ \ - tclAppInit.o $(OBJ) $(LIB) - -PURIFY = purify -cache-dir=/tmp -ns-pure: $(OBJ) tclAppInit.o - $(PURIFY) $(LINK) $(LDFLAGS) -o $@ \ - tclAppInit.o $(OBJ) $(LIB) - -NS_TCL_LIB = \ - tcl/lib/ns-compat.tcl \ - tcl/lib/ns-default.tcl \ - tcl/lib/ns-errmodel.tcl \ - tcl/lib/ns-lib.tcl \ - tcl/lib/ns-link.tcl \ - tcl/lib/ns-mobilenode.tcl \ - tcl/lib/ns-sat.tcl \ - tcl/lib/ns-cmutrace.tcl \ - tcl/lib/ns-node.tcl \ - tcl/lib/ns-rtmodule.tcl \ - tcl/lib/ns-hiernode.tcl \ - tcl/lib/ns-packet.tcl \ - tcl/lib/ns-queue.tcl \ - tcl/lib/ns-source.tcl \ - tcl/lib/ns-nam.tcl \ - tcl/lib/ns-trace.tcl \ - tcl/lib/ns-agent.tcl \ - tcl/lib/ns-random.tcl \ - tcl/lib/ns-namsupp.tcl \ - tcl/lib/ns-address.tcl \ - tcl/lib/ns-intserv.tcl \ - tcl/lib/ns-autoconf.tcl \ - tcl/rtp/session-rtp.tcl \ - tcl/lib/ns-mip.tcl \ - tcl/rtglib/dynamics.tcl \ - tcl/rtglib/route-proto.tcl \ - tcl/rtglib/algo-route-proto.tcl \ - tcl/rtglib/ns-rtProtoLS.tcl \ - tcl/interface/ns-iface.tcl \ - tcl/mcast/BST.tcl \ - tcl/mcast/ns-mcast.tcl \ - tcl/mcast/McastProto.tcl \ - tcl/mcast/DM.tcl \ - tcl/mcast/srm.tcl \ - tcl/mcast/srm-adaptive.tcl \ - tcl/mcast/srm-ssm.tcl \ - tcl/mcast/timer.tcl \ - tcl/mcast/McastMonitor.tcl \ - tcl/mcast/mftp_snd.tcl \ - tcl/mcast/mftp_rcv.tcl \ - tcl/mcast/mftp_rcv_stat.tcl \ - tcl/mobility/dsdv.tcl \ - tcl/mobility/dsr.tcl \ - tcl/ctr-mcast/CtrMcast.tcl \ - tcl/ctr-mcast/CtrMcastComp.tcl \ - tcl/ctr-mcast/CtrRPComp.tcl \ - tcl/rlm/rlm.tcl \ - tcl/rlm/rlm-ns.tcl \ - tcl/session/session.tcl \ - tcl/lib/ns-route.tcl \ - tcl/emulate/ns-emulate.tcl \ - tcl/lan/vlan.tcl \ - tcl/lan/ns-ll.tcl \ - tcl/lan/ns-mac.tcl \ - tcl/webcache/http-agent.tcl \ - tcl/webcache/http-server.tcl \ - tcl/webcache/http-cache.tcl \ - tcl/webcache/http-mcache.tcl \ - tcl/webcache/webtraf.tcl \ - tcl/plm/plm.tcl \ - tcl/plm/plm-ns.tcl \ - tcl/plm/plm-topo.tcl \ - tcl/mpls/ns-mpls-classifier.tcl \ - tcl/mpls/ns-mpls-ldpagent.tcl \ - tcl/mpls/ns-mpls-node.tcl \ - tcl/mpls/ns-mpls-simulator.tcl - -$(GEN_DIR)ns_tcl.cc: $(NS_TCL_LIB) - $(TCLSH) bin/tcl-expand.tcl tcl/lib/ns-lib.tcl tcl/lib/ns-stl.tcl | $(TCL2C) et_ns_lib > $@ - -$(GEN_DIR)version.c: VERSION - $(RM) $@ - $(TCLSH) bin/string2c.tcl version_string < VERSION > $@ - -$(GEN_DIR)ptypes.cc: ptypes2tcl packet.h - ./ptypes2tcl > $@ - -ptypes2tcl: ptypes2tcl.o - $(LINK) $(LDFLAGS) $(LDOUT)$@ ptypes2tcl.o - -ptypes2tcl.o: ptypes2tcl.cc packet.h - -install: force install-ns install-man install-recursive - -install-ns: force - $(INSTALL) -m 555 -o bin -g bin ns $(DESTDIR)$(BINDEST) - -install-man: force - $(INSTALL) -m 444 -o bin -g bin ns.1 $(DESTDIR)$(MANDEST)/man1 - -install-recursive: force - for i in $(SUBDIRS); do cd $$i; $(MAKE) install; done - -clean: - $(RM) $(CLEANFILES) - -AUTOCONF_GEN = tcl/lib/ns-autoconf.tcl tcl/lib/ns-stl.tcl -distclean: - $(RM) $(CLEANFILES) Makefile config.cache config.log config.status \ - gnuc.h os-proto.h $(AUTOCONF_GEN) - -tags: force - ctags -wtd *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ - dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ - ../Tcl/*.cc ../Tcl/*.h - -TAGS: force - etags *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ - dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ - ../Tcl/*.cc ../Tcl/*.h - -tcl/lib/TAGS: force - ( \ - cd tcl/lib; \ - $(TCLSH) ../../bin/tcl-expand.tcl ns-lib.tcl | grep '^### tcl-expand.tcl: begin' | awk '{print $$5}' >.tcl_files; \ - etags --lang=none -r '/^[ \t]*proc[ \t]+\([^ \t]+\)/\1/' `cat .tcl_files`; \ - etags --append --lang=none -r '/^\([A-Z][^ \t]+\)[ \t]+\(instproc\|proc\)[ \t]+\([^ \t]+\)[ \t]+/\1::\3/' `cat .tcl_files`; \ - ) - -depend: $(SRC) - $(MKDEP) $(CFLAGS) $(INCLUDES) $(SRC) - -srctar: - @cwd=`pwd` ; dir=`basename $$cwd` ; \ - name=ns-`cat VERSION | tr A-Z a-z` ; \ - tar=ns-src-`cat VERSION`.tar.gz ; \ - list="" ; \ - for i in `cat FILES` ; do list="$$list $$name/$$i" ; done; \ - echo \ - "(rm -f $$tar; cd .. ; ln -s $$dir $$name)" ; \ - (rm -f $$tar; cd .. ; ln -s $$dir $$name) ; \ - echo \ - "(cd .. ; tar cfh $$tar [lots of files])" ; \ - (cd .. ; tar cfh - $$list) | gzip -c > $$tar ; \ - echo \ - "rm ../$$name; chmod 444 $$tar" ; \ - rm ../$$name; chmod 444 $$tar - -force: - -test: force - ./validate - -# Create makefile.vc for Win32 development by replacing: -# "# !include ..." -> "!include ..." -makefile.vc: Makefile.in - $(PERL) bin/gen-vcmake.pl < Makefile.in > makefile.vc -# $(PERL) -pe 's/^# (\!include)/\!include/o' < Makefile.in > makefile.vc diff --git a/ns/ns-2.1b9-Makefile.in b/ns/ns-2.1b9-Makefile.in deleted file mode 100644 index 153dfc2..0000000 --- a/ns/ns-2.1b9-Makefile.in +++ /dev/null @@ -1,536 +0,0 @@ -# Copyright (c) 1994, 1995, 1996 -# The Regents of the University of California. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that: (1) source code distributions -# retain the above copyright notice and this paragraph in its entirety, (2) -# distributions including binary code include the above copyright notice and -# this paragraph in its entirety in the documentation or other materials -# provided with the distribution, and (3) all advertising materials mentioning -# features or use of this software display the following acknowledgement: -# ``This product includes software developed by the University of California, -# Lawrence Berkeley Laboratory and its contributors.'' Neither the name of -# the University nor the names of its contributors may be used to endorse -# or promote products derived from this software without specific prior -# written permission. -# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED -# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. -# -# @(#) $Header: /cvsroot/norm/norm/ns/ns-2.1b9-Makefile.in,v 1.1 2002/11/18 16:14:07 adamson Exp $ (LBL) - -# -# Various configurable paths (remember to edit Makefile.in, not Makefile) -# - -# Top level hierarchy -prefix = @prefix@ -# Pathname of directory to install the binary -BINDEST = @prefix@/bin -# Pathname of directory to install the man page -MANDEST = @prefix@/man - -BLANK = # make a blank space. DO NOT add anything to this line - -# The following will be redefined under Windows (see WIN32 lable below) -CC = @CC@ -CPP = @CXX@ -LINK = $(CPP) -MKDEP = ./conf/mkdep -TCLSH = @V_TCLSH@ -TCL2C = @V_TCL2CPP@ -AR = ar rc $(BLANK) - -RANLIB = @V_RANLIB@ -INSTALL = @INSTALL@ -LN = ln -TEST = test -RM = rm -f -MV = mv -PERL = @PERL@ - -# for diffusion -#DIFF_INCLUDES = "./diffusion3/main ./diffusion3/lib ./diffusion3/nr ./diffusion3/ns" - -CCOPT = @V_CCOPT@ -STATIC = @V_STATIC@ -LDFLAGS = $(STATIC) -LDOUT = -o $(BLANK) - -DEFINE = -DTCP_DELAY_BIND_ALL -DNO_TK -DNIXVECTOR @V_DEFINE@ @V_DEFINES@ @DEFS@ -DPGM -DPGM_DEBUG -DNS_DIFFUSION - - -PROTOLIB_INCLUDES = -Iprotolib/common -Iprotolib/ns -MDP_INCLUDES = -Imdp/common -Imdp/ns -Imdp/unix -NORM_INCLUDES = -Inorm/common -Inorm/ns - -INCLUDES = \ - -I. @V_INCLUDE_X11@ \ - @V_INCLUDES@ \ - -I./tcp -I./common -I./link -I./queue \ - -I./adc -I./apps -I./mac -I./mobile -I./trace \ - -I./routing -I./tools -I./classifier -I./mcast \ - -I./diffusion3/main -I./diffusion3/lib \ - -I./diffusion3/nr -I./diffusion3/ns -I./asim/ \ - $(PROTOLIB_INCLUDES) $(MDP_INCLUDES) $(NORM_INCLUDES) - - -LIB = \ - @V_LIBS@ \ - @V_LIB_X11@ \ - @V_LIB@ \ - -lm @LIBS@ -# -L@libdir@ \ - -# These flags work on Linux -PROTOLIB_CFLAGS = -g -DUNIX -DNS2 -DPROTO_DEBUG -DSIMULATE -DHAVE_ASSERT -DHAVE_DIRFD - -CFLAGS = $(CCOPT) $(DEFINE) $(PROTOLIB_CFLAGS) - -# Explicitly define compilation rules since SunOS 4's make doesn't like gcc. -# Also, gcc does not remove the .o before forking 'as', which can be a -# problem if you don't own the file but can write to the directory. -# PROTOLIB/MDP/NORM code uses .cpp files so we added a suffix and rule -.SUFFIXES: .cc .cpp # $(.SUFFIXES) - -.cc.o: - @rm -f $@ - $(CPP) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cc - -.c.o: - @rm -f $@ - $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $*.c - -.cpp.o: - @rm -f $@ - $(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $*.cpp - - -GEN_DIR = gen/ -LIB_DIR = lib/ -NS = ns -NSX = nsx -NSE = nse - -# To allow conf/makefile.win overwrite this macro -# We will set these two macros to empty in conf/makefile.win since VC6.0 -# does not seem to support the STL in gcc 2.8 and up. -OBJ_STL = linkstate/ls.o linkstate/rtProtoLS.o \ - pgm/classifier-pgm.o pgm/pgm-agent.o pgm/pgm-sender.o \ - pgm/pgm-receiver.o pgm/rcvbuf.o \ - diffusion3/nr/nr.o diffusion3/lib/dr.o \ - diffusion3/ns/diffagent.o diffusion3/ns/diffrtg.o \ - diffusion3/ns/difftimer.o diffusion3/main/diffusion.o \ - diffusion3/main/attrs.o diffusion3/main/iodev.o \ - diffusion3/main/events.o diffusion3/main/message.o \ - diffusion3/main/hashutils.o diffusion3/main/stats.o \ - diffusion3/main/tools.o diffusion3/main/drivers/rpc_stats.o \ - diffusion3/apps/sysapps/gradient.o \ - diffusion3/apps/sysapps/log.o \ - diffusion3/lib/diffapp.o \ - diffusion3/apps/agents/ping_sender.o \ - diffusion3/apps/agents/ping_receiver.o \ - diffusion3/apps/agents/ping_common.o \ - diffusion3/apps/gear/geo-attr.o \ - diffusion3/apps/gear/geo-routing.o \ - diffusion3/apps/gear/geo-tools.o - -NS_TCL_LIB_STL = tcl/rtglib/ns-rtProtoLS.tcl \ - tcl/lib/ns-diffusion.tcl - -# WIN32: uncomment the following line to include specific make for VC++ -# !include - -OBJ_CC = \ - tools/random.o tools/rng.o tools/ranvar.o common/misc.o common/timer-handler.o \ - common/scheduler.o common/object.o common/packet.o \ - common/ip.o routing/route.o common/connector.o common/ttl.o \ - trace/trace.o trace/trace-ip.o \ - classifier/classifier.o classifier/classifier-addr.o \ - classifier/classifier-hash.o \ - classifier/classifier-virtual.o \ - classifier/classifier-mcast.o \ - classifier/classifier-bst.o \ - classifier/classifier-mpath.o mcast/replicator.o \ - classifier/classifier-mac.o \ - classifier/classifier-port.o src_rtg/classifier-sr.o \ - src_rtg/sragent.o src_rtg/hdr_src.o adc/ump.o \ - apps/app.o apps/telnet.o tcp/tcplib-telnet.o \ - tools/trafgen.o trace/traffictrace.o tools/pareto.o \ - tools/expoo.o tools/cbr_traffic.o \ - adc/tbf.o adc/resv.o adc/sa.o tcp/saack.o \ - tools/measuremod.o adc/estimator.o adc/adc.o adc/ms-adc.o \ - adc/timewindow-est.o adc/acto-adc.o \ - adc/pointsample-est.o adc/salink.o adc/actp-adc.o \ - adc/hb-adc.o adc/expavg-est.o\ - adc/param-adc.o adc/null-estimator.o \ - adc/adaptive-receiver.o apps/vatrcvr.o adc/consrcvr.o \ - common/agent.o common/message.o apps/udp.o \ - common/session-rtp.o apps/rtp.o tcp/rtcp.o \ - common/ivs.o \ - tcp/tcp.o tcp/tcp-sink.o tcp/tcp-reno.o \ - tcp/tcp-newreno.o \ - tcp/tcp-vegas.o tcp/tcp-rbp.o tcp/tcp-full.o tcp/rq.o \ - baytcp/tcp-full-bay.o baytcp/ftpc.o baytcp/ftps.o \ - tcp/scoreboard.o tcp/tcp-sack1.o tcp/tcp-fack.o \ - tcp/tcp-asym.o tcp/tcp-asym-sink.o tcp/tcp-fs.o \ - tcp/tcp-asym-fs.o \ - tcp/tcp-int.o tcp/chost.o tcp/tcp-session.o \ - tcp/nilist.o \ - tools/integrator.o tools/queue-monitor.o \ - tools/flowmon.o tools/loss-monitor.o \ - queue/queue.o queue/drop-tail.o \ - adc/simple-intserv-sched.o queue/red.o \ - queue/semantic-packetqueue.o queue/semantic-red.o \ - tcp/ack-recons.o \ - queue/sfq.o queue/fq.o queue/drr.o queue/srr.o queue/cbq.o \ - link/hackloss.o queue/errmodel.o queue/fec.o\ - link/delay.o tcp/snoop.o \ - gaf/gaf.o \ - link/dynalink.o routing/rtProtoDV.o common/net-interface.o \ - mcast/ctrMcast.o mcast/mcast_ctrl.o mcast/srm.o \ - common/sessionhelper.o queue/delaymodel.o \ - mcast/srm-ssm.o mcast/srm-topo.o \ - apps/mftp.o apps/mftp_snd.o apps/mftp_rcv.o \ - apps/codeword.o \ - routing/alloc-address.o routing/address.o \ - $(LIB_DIR)int.Vec.o $(LIB_DIR)int.RVec.o \ - $(LIB_DIR)dmalloc_support.o \ - webcache/http.o webcache/tcp-simple.o webcache/pagepool.o \ - webcache/inval-agent.o webcache/tcpapp.o webcache/http-aux.o \ - webcache/mcache.o webcache/webtraf.o \ - empweb/empweb.o \ - empweb/empftp.o \ - realaudio/realaudio.o \ - mac/lanRouter.o classifier/filter.o \ - common/pkt-counter.o \ - common/Decapsulator.o common/Encapsulator.o \ - common/encap.o \ - mac/channel.o mac/mac.o mac/ll.o mac/mac-802_11.o \ - mac/mac-802_3.o mac/mac-tdma.o \ - mobile/mip.o mobile/mip-reg.o mobile/gridkeeper.o \ - mobile/propagation.o mobile/tworayground.o \ - mobile/antenna.o mobile/omni-antenna.o \ - mobile/shadowing.o mobile/shadowing-vis.o \ - common/bi-connector.o common/node.o \ - common/mobilenode.o \ - mac/arp.o mobile/god.o mobile/dem.o \ - mobile/topography.o mobile/modulation.o \ - queue/priqueue.o \ - mac/phy.o mac/wired-phy.o mac/wireless-phy.o \ - mac/mac-timers.o trace/cmu-trace.o mac/varp.o \ - dsdv/dsdv.o dsdv/rtable.o queue/rtqueue.o \ - routing/rttable.o \ - imep/imep.o imep/dest_queue.o imep/imep_api.o \ - imep/imep_rt.o imep/rxmit_queue.o imep/imep_timers.o \ - imep/imep_util.o imep/imep_io.o \ - tora/tora.o tora/tora_api.o tora/tora_dest.o \ - tora/tora_io.o tora/tora_logs.o tora/tora_neighbor.o \ - dsr/dsragent.o dsr/hdr_sr.o dsr/mobicache.o dsr/path.o \ - dsr/requesttable.o dsr/routecache.o dsr/add_sr.o \ - dsr/dsr_proto.o dsr/flowstruct.o dsr/linkcache.o \ - dsr/simplecache.o dsr/sr_forwarder.o \ - aodv/aodv_logs.o aodv/aodv.o \ - aodv/aodv_rtable.o aodv/aodv_rqueue.o \ - common/ns-process.o \ - satellite/satgeometry.o satellite/sathandoff.o \ - satellite/satlink.o satellite/satnode.o \ - satellite/satposition.o satellite/satroute.o \ - satellite/sattrace.o \ - rap/raplist.o rap/rap.o rap/media-app.o rap/utilities.o \ - common/fsm.o tcp/tcp-abs.o \ - diffusion/diffusion.o diffusion/diff_rate.o diffusion/diff_prob.o \ - diffusion/diff_sink.o diffusion/flooding.o diffusion/omni_mcast.o \ - diffusion/hash_table.o diffusion/routing_table.o diffusion/iflist.o \ - tcp/tfrc.o tcp/tfrc-sink.o mobile/energy-model.o apps/ping.o tcp/tcp-rfc793edu.o \ - queue/rio.o queue/semantic-rio.o tcp/tcp-sack-rh.o tcp/scoreboard-rh.o \ - plm/loss-monitor-plm.o plm/cbr-traffic-PP.o \ - linkstate/hdr-ls.o \ - mpls/classifier-addr-mpls.o mpls/ldp.o mpls/mpls-module.o \ - routing/rtmodule.o classifier/classifier-hier.o \ - routing/addr-params.o \ - nix/hdr_nv.o nix/classifier-nix.o \ - nix/nixnode.o \ - routealgo/rnode.o \ - routealgo/bfs.o \ - routealgo/rbitmap.o \ - routealgo/rlookup.o \ - routealgo/routealgo.o \ - nix/nixvec.o \ - nix/nixroute.o \ - diffserv/dsred.o diffserv/dsredq.o \ - diffserv/dsEdge.o diffserv/dsCore.o \ - diffserv/dsPolicy.o diffserv/ew.o\ - queue/red-pd.o queue/pi.o queue/vq.o queue/rem.o \ - queue/gk.o \ - pushback/rate-limit.o pushback/rate-limit-strategy.o \ - pushback/ident-tree.o pushback/agg-spec.o \ - pushback/logging-data-struct.o \ - pushback/rate-estimator.o \ - pushback/pushback-queue.o pushback/pushback.o \ - common/parentnode.o trace/basetrace.o \ - common/simulator.o asim/asim.o \ - @V_STLOBJ@ - - -# don't allow comments to follow continuation lines - -# mac-csma.o mac-multihop.o\ -# sensor-nets/landmark.o mac-simple-wireless.o \ -# sensor-nets/tags.o sensor-nets/sensor-query.o \ -# sensor-nets/flood-agent.o \ - -# what was here before is now in emulate/ -OBJ_C = - -OBJ_COMPAT = $(OBJ_GETOPT) common/win32.o -#XXX compat/win32x.o compat/tkConsole.o - -OBJ_EMULATE_CC = \ - emulate/net-ip.o \ - emulate/net.o \ - emulate/tap.o \ - emulate/ether.o \ - emulate/internet.o \ - emulate/ping_responder.o \ - emulate/arp.o \ - emulate/icmp.o \ - emulate/net-pcap.o \ - emulate/nat.o \ - emulate/iptap.o \ - emulate/tcptap.o - -OBJ_EMULATE_C = \ - emulate/inet.o - -OBJ_GEN = $(GEN_DIR)version.o $(GEN_DIR)ns_tcl.o $(GEN_DIR)ptypes.o - -OBJ_PROTOLIB_CPP = \ - protolib/ns/nsProtoAgent.o protolib/common/protoSim.o \ - protolib/common/networkAddress.o protolib/common/protocolTimer.o \ - protolib/common/debug.o - -OBJ_MDP_CPP = \ - mdp/ns/nsMdpAgent.o mdp/common/mdpSimAgent.o \ - mdp/common/mdpBitMask.o mdp/common/mdpMessage.o \ - mdp/common/mdpEncoder.o mdp/common/galois.o \ - mdp/common/mdpSession.o mdp/common/mdpMsgHandler.o \ - mdp/common/mdpNode.o mdp/common/mdpObject.o \ - mdp/unix/mdpFile.o - -OBJ_NORM_CPP = \ - norm/ns/nsNormAgent.o norm/common/normSimAgent.o \ - norm/common/normMessage.o norm/common/normSession.o \ - norm/common/normNode.o norm/common/normObject.o \ - norm/common/normSegment.o norm/common/normBitmask.o \ - norm/common/normEncoder.o norm/common/galois.o \ - norm/common/normFile.o - -OBJ_CPP = $(OBJ_PROTOLIB_CPP) $(OBJ_MDP_CPP) $(OBJ_NORM_CPP) - -SRC = $(OBJ_C:.o=.c) $(OBJ_CC:.o=.cc) $(OBJ_CPP:.o=.cpp) \ - $(OBJ_EMULATE_C:.o=.c) $(OBJ_EMULATE_CC:.o=.cc) \ - common/tclAppInit.cc common/tkAppInit.cc - -OBJ = $(OBJ_C) $(OBJ_CC) $(OBJ_GEN) $(OBJ_COMPAT) $(OBJ_CPP) - -CLEANFILES = ns nse nsx ns.dyn $(OBJ) $(OBJ_EMULATE_CC) \ - $(OBJ_EMULATE_C) common/tclAppInit.o \ - $(GEN_DIR)* $(NS).core core core.$(NS) core.$(NSX) core.$(NSE) \ - common/ptypes2tcl common/ptypes2tcl.o - -SUBDIRS=\ - indep-utils/cmu-scen-gen/setdest \ - indep-utils/webtrace-conv/dec \ - indep-utils/webtrace-conv/epa \ - indep-utils/webtrace-conv/nlanr \ - indep-utils/webtrace-conv/ucb - -BUILD_NSE = @build_nse@ - -all: $(NS) $(BUILD_NSE) all-recursive - - -all-recursive: - for i in $(SUBDIRS); do ( cd $$i; $(MAKE) all; ) done - -$(NS): $(OBJ) common/tclAppInit.o Makefile - $(LINK) $(LDFLAGS) $(LDOUT)$@ \ - common/tclAppInit.o $(OBJ) $(LIB) - -Makefile: Makefile.in - @echo "Makefile.in is newer than Makefile." - @echo "You need to re-run configure." - false - -$(NSE): $(OBJ) common/tclAppInit.o $(OBJ_EMULATE_CC) $(OBJ_EMULATE_C) - $(LINK) $(LDFLAGS) $(LDOUT)$@ \ - common/tclAppInit.o $(OBJ) \ - $(OBJ_EMULATE_CC) $(OBJ_EMULATE_C) $(LIB) - -ns.dyn: $(OBJ) common/tclAppInit.o - $(LINK) $(LDFLAGS) -o $@ \ - common/tclAppInit.o $(OBJ) $(LIB) - -PURIFY = purify -cache-dir=/tmp -ns-pure: $(OBJ) common/tclAppInit.o - $(PURIFY) $(LINK) $(LDFLAGS) -o $@ \ - common/tclAppInit.o $(OBJ) $(LIB) - -NS_TCL_LIB = \ - tcl/lib/ns-compat.tcl \ - tcl/lib/ns-default.tcl \ - tcl/lib/ns-errmodel.tcl \ - tcl/lib/ns-lib.tcl \ - tcl/lib/ns-link.tcl \ - tcl/lib/ns-mobilenode.tcl \ - tcl/lib/ns-sat.tcl \ - tcl/lib/ns-cmutrace.tcl \ - tcl/lib/ns-node.tcl \ - tcl/lib/ns-rtmodule.tcl \ - tcl/lib/ns-hiernode.tcl \ - tcl/lib/ns-packet.tcl \ - tcl/lib/ns-queue.tcl \ - tcl/lib/ns-source.tcl \ - tcl/lib/ns-nam.tcl \ - tcl/lib/ns-trace.tcl \ - tcl/lib/ns-agent.tcl \ - tcl/lib/ns-random.tcl \ - tcl/lib/ns-namsupp.tcl \ - tcl/lib/ns-address.tcl \ - tcl/lib/ns-intserv.tcl \ - tcl/lib/ns-autoconf.tcl \ - tcl/rtp/session-rtp.tcl \ - tcl/lib/ns-mip.tcl \ - tcl/rtglib/dynamics.tcl \ - tcl/rtglib/route-proto.tcl \ - tcl/rtglib/algo-route-proto.tcl \ - tcl/rtglib/ns-rtProtoLS.tcl \ - tcl/interface/ns-iface.tcl \ - tcl/mcast/BST.tcl \ - tcl/mcast/ns-mcast.tcl \ - tcl/mcast/McastProto.tcl \ - tcl/mcast/DM.tcl \ - tcl/mcast/srm.tcl \ - tcl/mcast/srm-adaptive.tcl \ - tcl/mcast/srm-ssm.tcl \ - tcl/mcast/timer.tcl \ - tcl/mcast/McastMonitor.tcl \ - tcl/mcast/mftp_snd.tcl \ - tcl/mcast/mftp_rcv.tcl \ - tcl/mcast/mftp_rcv_stat.tcl \ - tcl/mobility/dsdv.tcl \ - tcl/mobility/dsr.tcl \ - tcl/ctr-mcast/CtrMcast.tcl \ - tcl/ctr-mcast/CtrMcastComp.tcl \ - tcl/ctr-mcast/CtrRPComp.tcl \ - tcl/rlm/rlm.tcl \ - tcl/rlm/rlm-ns.tcl \ - tcl/session/session.tcl \ - tcl/lib/ns-route.tcl \ - tcl/emulate/ns-emulate.tcl \ - tcl/lan/vlan.tcl \ - tcl/lan/abslan.tcl \ - tcl/lan/ns-ll.tcl \ - tcl/lan/ns-mac.tcl \ - tcl/webcache/http-agent.tcl \ - tcl/webcache/http-server.tcl \ - tcl/webcache/http-cache.tcl \ - tcl/webcache/http-mcache.tcl \ - tcl/webcache/webtraf.tcl \ - tcl/webcache/empweb.tcl \ - tcl/webcache/empftp.tcl \ - tcl/plm/plm.tcl \ - tcl/plm/plm-ns.tcl \ - tcl/plm/plm-topo.tcl \ - tcl/mpls/ns-mpls-classifier.tcl \ - tcl/mpls/ns-mpls-ldpagent.tcl \ - tcl/mpls/ns-mpls-node.tcl \ - tcl/mpls/ns-mpls-simulator.tcl \ - tcl/lib/ns-pushback.tcl \ - tcl/lib/ns-srcrt.tcl \ - @V_NS_TCL_LIB_STL@ - -$(GEN_DIR)ns_tcl.cc: $(NS_TCL_LIB) - $(TCLSH) bin/tcl-expand.tcl tcl/lib/ns-lib.tcl @V_NS_TCL_LIB_STL@ | $(TCL2C) et_ns_lib > $@ - -$(GEN_DIR)version.c: VERSION - $(RM) $@ - $(TCLSH) bin/string2c.tcl version_string < VERSION > $@ - -$(GEN_DIR)ptypes.cc: common/ptypes2tcl common/packet.h - ./common/ptypes2tcl > $@ - -common/ptypes2tcl: common/ptypes2tcl.o - $(LINK) $(LDFLAGS) $(LDOUT)$@ common/ptypes2tcl.o - -common/ptypes2tcl.o: common/ptypes2tcl.cc common/packet.h - -install: force install-ns install-man install-recursive - -install-ns: force - $(INSTALL) -m 555 -o bin -g bin ns $(DESTDIR)$(BINDEST) - -install-man: force - $(INSTALL) -m 444 -o bin -g bin ns.1 $(DESTDIR)$(MANDEST)/man1 - -install-recursive: force - for i in $(SUBDIRS); do ( cd $$i; $(MAKE) install; ) done - -clean: - $(RM) $(CLEANFILES) - -AUTOCONF_GEN = tcl/lib/ns-autoconf.tcl -distclean: - $(RM) $(CLEANFILES) Makefile config.cache config.log config.status \ - gnuc.h os-proto.h $(AUTOCONF_GEN); \ - $(MV) .configure .configure- ;\ - echo "Moved .configure to .configure-" - -tags: force - ctags -wtd *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ - dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ - ../Tcl/*.cc ../Tcl/*.h - -TAGS: force - etags *.cc *.h webcache/*.cc webcache/*.h dsdv/*.cc dsdv/*.h \ - dsr/*.cc dsr/*.h webcache/*.cc webcache/*.h lib/*.cc lib/*.h \ - ../Tcl/*.cc ../Tcl/*.h - -tcl/lib/TAGS: force - ( \ - cd tcl/lib; \ - $(TCLSH) ../../bin/tcl-expand.tcl ns-lib.tcl | grep '^### tcl-expand.tcl: begin' | awk '{print $$5}' >.tcl_files; \ - etags --lang=none -r '/^[ \t]*proc[ \t]+\([^ \t]+\)/\1/' `cat .tcl_files`; \ - etags --append --lang=none -r '/^\([A-Z][^ \t]+\)[ \t]+\(instproc\|proc\)[ \t]+\([^ \t]+\)[ \t]+/\1::\3/' `cat .tcl_files`; \ - ) - -depend: $(SRC) - $(MKDEP) $(CFLAGS) $(INCLUDES) $(SRC) - -srctar: - @cwd=`pwd` ; dir=`basename $$cwd` ; \ - name=ns-`cat VERSION | tr A-Z a-z` ; \ - tar=ns-src-`cat VERSION`.tar.gz ; \ - list="" ; \ - for i in `cat FILES` ; do list="$$list $$name/$$i" ; done; \ - echo \ - "(rm -f $$tar; cd .. ; ln -s $$dir $$name)" ; \ - (rm -f $$tar; cd .. ; ln -s $$dir $$name) ; \ - echo \ - "(cd .. ; tar cfh $$tar [lots of files])" ; \ - (cd .. ; tar cfh - $$list) | gzip -c > $$tar ; \ - echo \ - "rm ../$$name; chmod 444 $$tar" ; \ - rm ../$$name; chmod 444 $$tar - -force: - -test: force - ./validate - -# Create makefile.vc for Win32 development by replacing: -# "# !include ..." -> "!include ..." -makefile.vc: Makefile.in - $(PERL) bin/gen-vcmake.pl < Makefile.in > makefile.vc -# $(PERL) -pe 's/^# (\!include)/\!include/o' < Makefile.in > makefile.vc diff --git a/src/common/fecTest.cpp b/src/common/fecTest.cpp new file mode 100644 index 0000000..4121e83 --- /dev/null +++ b/src/common/fecTest.cpp @@ -0,0 +1,135 @@ +// This code tests our NORM FEC encoder/decoder implementations + + +#include "protoTime.h" // for ProtoTime + +#include "normEncoderRS8.h" +#include "normEncoderRS16.h" + +#include // for memcpy(), etc +#include // for rand() +#include + +const unsigned int NUM_PARITY = 100; +const unsigned int NUM_DATA = 400; +const unsigned int SHORT_DATA = 400; +const unsigned int SEG_SIZE = 64; + +const unsigned int B_SIZE = (SHORT_DATA + NUM_PARITY); + +#define NORM_ENCODER NormEncoderRS16 +#define NORM_DECODER NormDecoderRS16 + +int main(int argc, char* argv[]) +{ + // Uncomment to seed random generator + ProtoTime currentTime; + currentTime.GetCurrentTime(); + int seed = (unsigned int)currentTime.usec(); + fprintf(stderr, "fect: seed = %u\n", seed); + srand(seed); + + NORM_ENCODER encoder; + encoder.Init(NUM_DATA, NUM_PARITY, SEG_SIZE); + NORM_DECODER decoder; + decoder.Init(NUM_DATA, NUM_PARITY, SEG_SIZE); + + for (int trial = 0; trial < 2; trial++) + { + + // 1) Create some "printable" source data + char txData[B_SIZE][SEG_SIZE]; + char* txDataPtr[B_SIZE]; + for (unsigned int i = 0 ; i < SHORT_DATA; i++) + { + txDataPtr[i] = txData[i]; + memset(txDataPtr[i], 'a' + (i%26), SEG_SIZE - 1); + txDataPtr[i][SEG_SIZE - 1] = '\0'; + } + + // 2) Zero-init the parity vectors of our txData + for (unsigned int i = SHORT_DATA; i < B_SIZE; i++) + { + txDataPtr[i] = txData[i]; + memset(txDataPtr[i], 0, SEG_SIZE); + } + + // 3) Run our encoder (and record CPU time) + ProtoTime startTime, stopTime; + startTime.GetCurrentTime(); + for (unsigned int i = 0; i < SHORT_DATA; i++) + { + encoder.Encode(txDataPtr[i], txDataPtr + SHORT_DATA); + } + stopTime.GetCurrentTime(); + double encodeTime = ProtoTime::Delta(stopTime, startTime); + + // 4) Copy "txData" to our "rxData" + char rxData[B_SIZE][SEG_SIZE]; + char* rxDataPtr[B_SIZE]; + for (unsigned int i = 0; i < B_SIZE; i++) + { + rxDataPtr[i] = rxData[i]; + memcpy(rxDataPtr[i], txDataPtr[i], SEG_SIZE); + } + + // 5) Randomly pick some number of erasures and their locations + unsigned int erasureCount = 2;//rand() % NUM_PARITY; + unsigned int erasureLocs[B_SIZE]; + for (unsigned int i = 0; i < B_SIZE; i++) + erasureLocs[i] = i; + for (unsigned int i = 0; i < erasureCount; i++) + { + // We do a little random shuffle here to generate + // "erasureCount" unique erasure locations + unsigned int loc = i + (rand() % (B_SIZE - i)); + unsigned int tmp = erasureLocs[i]; + erasureLocs[i] = erasureLocs[loc]; + erasureLocs[loc] = tmp; + } + // Sort the "erasureLocs" into order (important!) + for (unsigned int i = 0; i < erasureCount; i++) + { + for (unsigned int j = i+1; j < erasureCount; j++) + { + if (erasureLocs[j] < erasureLocs[i]) + { + unsigned int tmp = erasureLocs[i]; + erasureLocs[i] = erasureLocs[j]; + erasureLocs[j] = tmp; + } + } + } + fprintf(stderr, "erasureCount: %u erasureLocs: ", erasureCount); + for (unsigned int i = 0; i < erasureCount; i++) + fprintf(stderr, "%u ", erasureLocs[i]); + fprintf(stderr, "\n"); + + // 6) Clear our erasure locs + for (unsigned int i = 0; i < erasureCount; i++) + memset(rxDataPtr[erasureLocs[i]], 0, SEG_SIZE); + + // 7) Decode the rxData + + + startTime.GetCurrentTime(); + decoder.Decode(rxDataPtr, SHORT_DATA, erasureCount, erasureLocs); + stopTime.GetCurrentTime(); + double decodeTime = ProtoTime::Delta(stopTime, startTime); + + // 8) check decoding + for (unsigned int i = 0; i < SHORT_DATA; i++) + { + if (0 != memcmp(rxDataPtr[i], txDataPtr[i], SEG_SIZE)) + { + fprintf(stderr, "fect: segment:%d rxData decode error!\n", i); + fprintf(stderr, " txData: %.32s ...\n", txDataPtr[i]); + fprintf(stderr, " rxData: %.32s ...\n", rxDataPtr[i]); + } + } + + + // 9) Print results + fprintf(stderr, "fect: encodeTime:%lf usec decodeTime:%lf usec\n", 1.0e+06*encodeTime, 1.0e+06*decodeTime); + } +} // end main() diff --git a/common/galois.cpp b/src/common/galois.cpp similarity index 100% rename from common/galois.cpp rename to src/common/galois.cpp diff --git a/src/common/n2m.cpp b/src/common/n2m.cpp new file mode 100644 index 0000000..bdba993 --- /dev/null +++ b/src/common/n2m.cpp @@ -0,0 +1,414 @@ +// n2m.cpp - grab NORM packet "trace" lines from a NORM debug log +// and transform them into pseudo MGEN log format +// so our existing "trpr" program can be used for analyses + +// This currently only fully function when there is a single sender and +// a single object is in the NORM log file + +#include +#include +#include +#include + +#ifndef WIN32 +#include +#include // for gettimeofday() +#include +#endif // !WIN32 + +class FastReader +{ + public: + enum Result {OK, ERROR_, DONE, TIMEOUT}; + FastReader(); + FastReader::Result Read(FILE* filePtr, char* buffer, unsigned int* len, + double timeout = -1.0); + FastReader::Result Readline(FILE* filePtr, char* buffer, unsigned int* len, + double timeout = -1.0); + + private: + enum {BUFSIZE = 2048}; + char savebuf[BUFSIZE]; + char* saveptr; + unsigned int savecount; +}; // end class FastReader + +#ifndef MIN +#define MIN(X,Y) ((XY)?X:Y) +#endif // !MIN + +void Usage() +{ + fprintf(stderr, "Usage: n2m [data ][input \n"); +} + +int main(int argc, char* argv[]) +{ + FastReader reader; + unsigned int line = 0; + int status = 0; + + bool firstRecvEvent = true; + bool firstSendEvent = true; + unsigned int lastSendSeq = 0; + unsigned int lastRecvSeq = 0; + unsigned int sendSeqOffset = 0; + unsigned int recvSeqOffset = 0; + + FILE* infile = stdin; + + bool dataSeq = false; + int blkSize = 0; + for (int i = 1; i < argc; i++) + { + if (!strcmp("data", argv[i])) + { + + i++; + if (i < argc) + { + blkSize = atoi(argv[i]); + if (blkSize <= 0) + { + fprintf(stderr, "n2m error: invalid block size\n"); + return -1; + } + } + else + { + fprintf(stderr, "Usage: n2m [data ]\n"); + return -1; + } + dataSeq = true; + } + else if (!strcmp("input", argv[i])) + { + i++; + if (NULL == (infile = fopen(argv[i], "r"))) + { + fprintf(stderr, "Usage: n2m [data ]\n"); + return -1; + } + } + + } + + while (1) + { + unsigned int numBytes = 1024; + char buffer[1024]; + FastReader::Result result = reader.Readline(infile, buffer, &numBytes); + if (FastReader::DONE == result) + { + break; + } + else if (FastReader::ERROR_ == result) + { + perror("n2m: error reading log"); + status = -1; + break; + } + line++; + // Make sure it is a "trace" line + if (0 == numBytes) + continue; + else if (0 != strncmp(buffer, "trace>", 6)) + continue; + + // Get the event time + unsigned int hr, min; + double sec; + char* ptr = buffer + 6; + if (3 != sscanf(ptr, "%u:%u:%lf", &hr, &min, &sec)) + { + fprintf(stderr, "n2m: invalid trace \"time\" at line %u\n", line); + break; + } + + // RECV or SEND event? + bool recvEvent = false; + char* ptr2 = strstr(ptr, "src>"); + if (NULL != ptr2) + { + recvEvent = true; + + } + else if (NULL == (ptr2 = strstr(ptr, "dst>"))) + { + fprintf(stderr, "n2m: invalid trace \"src|dst\" at line %u\n", line); + break; + } + ptr = ptr2 + 4; + + // Get address string + char addr[64]; + if (1 != sscanf(ptr, "%s", addr)) + { + fprintf(stderr, "n2m: invalid trace \"%s address\" at line %u\n", + recvEvent ? "src" : "dst", line); + break; + } + + // Get sequence value + unsigned int seq = 0; + if (NULL != (ptr2 = strstr(ptr, "seq>"))) + { + ptr2 += 4; + if (1 != sscanf(ptr2, "%u", &seq)) + { + fprintf(stderr, "n2m: invalid trace \"seq\" at line %u\n", line); + break; + } + ptr = ptr2; + } + else + { + //fprintf(stderr, "n2m: no trace \"seq\" at line %u\n", line); + seq = recvEvent ? lastRecvSeq : lastSendSeq; + } + + + if (recvEvent) + { + if (firstRecvEvent) + { + firstRecvEvent = false; + recvSeqOffset = 0; + } + else + { + int delta = seq - lastRecvSeq; + if ((delta < -100) || (delta > 32000)) + recvSeqOffset += 65536; + } + lastRecvSeq = seq; + seq += recvSeqOffset; + } + else + { + if (firstSendEvent) + { + firstSendEvent = false; + sendSeqOffset = 0; + } + else + { + int delta = seq - lastSendSeq; + if ((delta < -100) || (delta > 32000)) + sendSeqOffset += 65536; + } + lastSendSeq = seq; + seq += sendSeqOffset; + } + + + if (dataSeq) + { + // Only use DATA/PRTY packet! (this is only good for a single object!) + ptr2 = strstr(ptr, "DATA"); + if (NULL == ptr2) + ptr2 = strstr(ptr, "PRTY"); + if (NULL == ptr2) continue; // skip to next line + ptr = ptr2 + 5; + // Calc seq from blk and seq + unsigned int obj, blk, seg; + if (3 != sscanf(ptr, "obj>%u blk>%u seg>%u", &obj, &blk, &seg)) + { + fprintf(stderr, "n2m: invalid trace at line %u\n", line); + break; + } + seq = blk * blkSize + seg; + + //int objDelta = (lastObj >= 0) ? (int)obj - lastObj : + + } + + // Get len value + if (NULL == (ptr2 = strstr(ptr, "len>"))) + { + fprintf(stderr, "n2m: no trace \"len\" at line %u\n", line); + break; + } + ptr = ptr2 + 4; + unsigned int length; + if (1 != sscanf(ptr, "%u", &length)) + { + fprintf(stderr, "n2m: invalid trace \"len\" at line %u\n", line); + break; + } + + + // Finally, output an MGEN log line + + + if (recvEvent) + printf("%u:%u:%lf RECV flow>0 seq>%u src>%s/0 dst>127.0.0.1/0 sent>%u:%u:%lf size>%u\n", + hr, min, sec, seq, addr, hr, min, sec, length); + else + printf("%u:%u:%lf SEND flow>0 seq>%u dst>%s/0 size>%u\n", + hr, min, sec, seq, addr, length); + fflush(stdout); + } + + if (infile != stdin) fclose(infile); + return status; +} + + +FastReader::FastReader() + : savecount(0) +{ + +} + +FastReader::Result FastReader::Read(FILE* filePtr, + char* buffer, + unsigned int* len, + double timeout) +{ + unsigned int want = *len; + if (savecount) + { + unsigned int ncopy = MIN(want, savecount); + memcpy(buffer, saveptr, ncopy); + savecount -= ncopy; + saveptr += ncopy; + buffer += ncopy; + want -= ncopy; + } + while (want) + { + unsigned int result; +#ifndef WIN32 // no real-time TRPR for WIN32 yet + if (timeout >= 0.0) + { + int fd = fileno(filePtr); + fd_set input; + FD_ZERO(&input); + struct timeval t; + t.tv_sec = (unsigned long)timeout; + t.tv_usec = (unsigned long)((1.0e+06 * (timeout - (double)t.tv_sec)) + 0.5); + FD_SET(fd, &input); + int status = select(fd+1, &input, NULL, NULL, &t); + switch(status) + { + case -1: + if (EINTR != errno) + { + perror("trpr: FastReader::Read() select() error"); + return ERROR_; + } + else + { + continue; + } + break; + + case 0: + return TIMEOUT; + + default: + result = fread(savebuf, sizeof(char), 1, filePtr); + break; + } + } + else +#endif // !WIN32 + { + // Perform buffered read when there is no "timeout" + result = fread(savebuf, sizeof(char), BUFSIZE, filePtr); + // This check skips NULLs that have been read on some + // use of trpr via tail from an NFS mounted file + if (!isprint(*savebuf) && + ('\n' != *savebuf) && + ('\r' != *savebuf)) + continue; + } + if (result) + { + unsigned int ncopy= MIN(want, result); + memcpy(buffer, savebuf, ncopy); + savecount = result - ncopy; + saveptr = savebuf + ncopy; + buffer += ncopy; + want -= ncopy; + } + else // end-of-file + { +#ifndef WIN32 + if (ferror(filePtr)) + { + if (EINTR == errno) continue; + } +#endif // !WIN32 + *len -= want; + if (*len) + return OK; // we read at least something + else + return DONE; // we read nothing + } + } // end while(want) + return OK; +} // end FastReader::Read() + +// An OK text readline() routine (reads what will fit into buffer incl. NULL termination) +// if *len is unchanged on return, it means the line is bigger than the buffer and +// requires multiple reads + +FastReader::Result FastReader::Readline(FILE* filePtr, + char* buffer, + unsigned int* len, + double timeout) +{ + unsigned int count = 0; + unsigned int length = *len; + char* ptr = buffer; + while (count < length) + { + unsigned int one = 1; + switch (Read(filePtr, ptr, &one, timeout)) + { + case OK: + if (('\n' == *ptr) || ('\r' == *ptr)) + { + *ptr = '\0'; + *len = count; + return OK; + } + count++; + ptr++; + break; + + case TIMEOUT: + // On timeout, save any partial line collected + if (count) + { + savecount = MIN(count, BUFSIZE); + if (count < BUFSIZE) + { + memcpy(savebuf, buffer, count); + savecount = count; + saveptr = savebuf; + *len = 0; + } + else + { + memcpy(savebuf, buffer+count-BUFSIZE, BUFSIZE); + savecount = BUFSIZE; + saveptr = savebuf; + *len = count - BUFSIZE; + } + } + return TIMEOUT; + + case ERROR_: + return ERROR_; + + case DONE: + return DONE; + } + } + // We've filled up the buffer provided with no end-of-line + return ERROR_; +} // end stReader::Result FastReader::Readline() diff --git a/common/normApi.cpp b/src/common/normApi.cpp similarity index 74% rename from common/normApi.cpp rename to src/common/normApi.cpp index 4a53a92..43e6b2d 100644 --- a/common/normApi.cpp +++ b/src/common/normApi.cpp @@ -40,7 +40,7 @@ class NormInstance : public NormController void Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* sender, + class NormSenderNode* sender, class NormObject* object); bool Startup(bool priorityBoost = false); @@ -59,7 +59,7 @@ class NormInstance : public NormController } else { - DMSG(0, "NormInstance::Resume() error restarting NORM thread\n"); + PLOG(PL_FATAL, "NormInstance::Resume() error restarting NORM thread\n"); return false; } } @@ -71,7 +71,9 @@ class NormInstance : public NormController bool NotifyQueueIsEmpty() const {return notify_queue.IsEmpty();} + void PurgeSessionNotifications(NormSessionHandle sessionHandle); void PurgeObjectNotifications(NormObjectHandle objectHandle); + void PurgeNotifications(NormSessionHandle sessionHandle, NormEventType eventType); UINT32 CountCompletedObjects(NormSession* theSession); @@ -94,7 +96,7 @@ class NormInstance : public NormController static NormInstance* GetInstanceFromNode(NormNodeHandle nodeHandle) { if (NORM_NODE_INVALID == nodeHandle) return ((NormInstance*)NULL); - NormSession& session = ((NormServerNode*)nodeHandle)->GetSession(); + NormSession& session = ((NormSenderNode*)nodeHandle)->GetSession(); return static_cast(session.GetSessionMgr().GetController()); } static NormInstance* GetInstanceFromObject(NormObjectHandle objectHandle) @@ -165,7 +167,7 @@ class NormInstance : public NormController #ifdef WIN32 HANDLE notify_event; #else - int notify_fd[2]; + int notify_fd[2]; // TBD - use eventfd on Linux for this, may EVT_USER on MacOS #endif // if/else WIN32/UNIX }; // end class NormInstance @@ -194,7 +196,8 @@ void NormInstance::Notification::Queue::Destroy() NormInstance::NormInstance() : priority_boost(false), session_mgr(static_cast(dispatcher), - static_cast(dispatcher)), + static_cast(dispatcher), + static_cast(&dispatcher)), previous_notification(NULL), rx_cache_path(NULL) { #ifdef WIN32 @@ -234,7 +237,7 @@ bool NormInstance::SetCacheDirectory(const char* cachePath) } else { - DMSG(0, "NormInstance::SetCacheDirectory() new pathStorage error: %s\n", + PLOG(PL_ERROR, "NormInstance::SetCacheDirectory() new pathStorage error: %s\n", GetErrorString()); } dispatcher.ResumeThread(); @@ -245,7 +248,7 @@ bool NormInstance::SetCacheDirectory(const char* cachePath) void NormInstance::Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* sender, + class NormSenderNode* sender, class NormObject* object) { // (TBD) set a limit on how many pending notifications @@ -257,7 +260,7 @@ void NormInstance::Notify(NormController::Event event, { if (!(n = new Notification)) { - DMSG(0, "NormInstance::Notify() new Notification error: %s\n", + PLOG(PL_FATAL, "NormInstance::Notify() new Notification error: %s\n", GetErrorString()); return; } @@ -273,13 +276,13 @@ void NormInstance::Notify(NormController::Event event, case NormObject::STREAM: { NormStreamObject* stream = static_cast(object); - // (TBD) implement silent_client accept differently + // (TBD) implement silent_receiver accept differently NormObjectSize size = stream->GetSize(); // We double the stream buffer to prevent unecessary data loss // for our threaded API if (!stream->Accept(size.LSB(), true)) { - DMSG(0, "NormInstance::Notify() stream accept error\n"); + PLOG(PL_FATAL, "NormInstance::Notify() stream accept error\n"); notify_pool.Append(n); return; } @@ -337,18 +340,18 @@ void NormInstance::Notify(NormController::Event event, else #endif // if/else WIN32 { - DMSG(0, "NormInstance::Notify(RX_OBJECT_NEW) Warning: mkstemp() error: %s\n", + PLOG(PL_ERROR, "NormInstance::Notify(RX_OBJECT_NEW) Warning: mkstemp() error: %s\n", GetErrorString()); } if (!static_cast(object)->Accept(fileName)) { - DMSG(0, "NormInstance::Notify(RX_OBJECT_NEW) file object accept error!\n"); + PLOG(PL_ERROR, "NormInstance::Notify(RX_OBJECT_NEW) file object accept error!\n"); } } else { // we're ignoring files - DMSG(8, "NormInstance::Notify() warning: receive file but no cache directory set, so ignoring file\n"); + PLOG(PL_DETAIL, "NormInstance::Notify() warning: receive file but no cache directory set, so ignoring file\n"); return; } break; @@ -360,7 +363,7 @@ void NormInstance::Notify(NormController::Event event, char* dataPtr = new char[dataLen]; if (NULL == dataPtr) { - DMSG(0, "NormInstance::Notify(RX_OBJECT_NEW) new dataPtr error: %s\n", + PLOG(PL_FATAL, "NormInstance::Notify(RX_OBJECT_NEW) new dataPtr error: %s\n", GetErrorString()); return; } @@ -387,7 +390,7 @@ void NormInstance::Notify(NormController::Event event, if (NORM_OBJECT_INVALID != object) ((NormObject*)object)->Retain(); else if (NORM_NODE_INVALID != sender) - ((NormServerNode*)sender)->Retain(); + ((NormSenderNode*)sender)->Retain(); bool doNotify = notify_queue.IsEmpty(); n->event.type = (NormEventType)event; @@ -401,16 +404,16 @@ void NormInstance::Notify(NormController::Event event, #ifdef WIN32 if (0 == SetEvent(notify_event)) { - DMSG(0, "NormInstance::Notify() SetEvent() error: %s\n", + PLOG(PL_ERROR, "NormInstance::Notify() SetEvent() error: %s\n", GetErrorString()); } #else - char byte; + char byte = 0; while (1 != write(notify_fd[1], &byte, 1)) { if ((EINTR != errno) && (EAGAIN != errno)) { - DMSG(0, "NormInstance::Notify() write() error: %s\n", + PLOG(PL_FATAL, "NormInstance::Notify() write() error: %s\n", GetErrorString()); break; } @@ -446,10 +449,64 @@ void NormInstance::PurgeObjectNotifications(NormObjectHandle objectHandle) next = next->GetNext(); } } - // (TBD) if we put these in the pool, should we check that the queue has been emptied - // and possible reset event/fd + // TBD - check if event queue is emptied and reset event/fd } // end NormInstance::PurgeObjectNotifications() +void NormInstance::PurgeSessionNotifications(NormSessionHandle sessionHandle) +{ + Notification* prev = NULL; + Notification* next = notify_queue.GetHead(); + while (next) + { + if (next->event.session == sessionHandle) + { + // Remove this notification from queue and return to pool + Notification* current = next; + next = next->GetNext(); + if (NULL != prev) + prev->Append(next); + else + notify_queue.RemoveHead(); + if (NULL == next) notify_queue.SetTail(prev); + notify_pool.Append(current); + } + else + { + prev = next; + next = next->GetNext(); + } + } + // TBD - check if event queue is emptied and reset event/fd +} // end NormInstance::PurgeSessionNotifications() + +void NormInstance::PurgeNotifications(NormSessionHandle sessionHandle, NormEventType eventType) +{ + Notification* prev = NULL; + Notification* next = notify_queue.GetHead(); + while (next) + { + if ((next->event.session == sessionHandle) && + (next->event.type == eventType)) + { + // Remove this notification from queue and return to pool + Notification* current = next; + next = next->GetNext(); + if (NULL != prev) + prev->Append(next); + else + notify_queue.RemoveHead(); + if (NULL == next) notify_queue.SetTail(prev); + notify_pool.Append(current); + } + else + { + prev = next; + next = next->GetNext(); + } + } + // TBD - check if event queue is emptied and reset event/fd +} // end NormInstance::PurgeNotifications() + // NormInstance::dispatcher MUST be suspended _before_ calling this bool NormInstance::GetNextEvent(NormEvent* theEvent) { @@ -460,26 +517,13 @@ bool NormInstance::GetNextEvent(NormEvent* theEvent) if (NORM_OBJECT_INVALID != previous_notification->event.object) ((NormObject*)(previous_notification->event.object))->Release(); else if (NORM_NODE_INVALID != previous_notification->event.sender) - ((NormServerNode*)(previous_notification->event.sender))->Release(); + ((NormSenderNode*)(previous_notification->event.sender))->Release(); notify_pool.Append(previous_notification); previous_notification = NULL; } Notification* n; - while ((n = notify_queue.RemoveHead())) + while (NULL != (n = notify_queue.RemoveHead())) { - if (notify_queue.IsEmpty()) - { -#ifdef WIN32 - if (0 == ResetEvent(notify_event)) - { - DMSG(0, "NormInstance::GetNextEvent() ResetEvent error: %s\n", - GetErrorString()); - } -#else - char byte[32]; - while (read(notify_fd[0], byte, 32) > 0); // TBD - error check -#endif // if/else WIN32/UNIX - } switch (n->event.type) { case NORM_EVENT_INVALID: @@ -502,10 +546,20 @@ bool NormInstance::GetNextEvent(NormEvent* theEvent) default: break; } - if (theEvent) *theEvent = n->event; + if (NULL != theEvent) *theEvent = n->event; previous_notification = n; // keep dispatched event for garbage collection return true; } + if (notify_queue.IsEmpty()) + { +#ifdef WIN32 + if (0 == ResetEvent(notify_event)) + PLOG(PL_ERROR, "NormInstance::GetNextEvent() ResetEvent error: %s\n", GetErrorString()); +#else + char byte[32]; + while (read(notify_fd[0], byte, 32) > 0); // TBD - error check +#endif // if/else WIN32/UNIX + } return false; } // end NormInstance::GetNextEvent() @@ -513,7 +567,7 @@ bool NormInstance::WaitForEvent() { if (!dispatcher.IsThreaded()) { - DMSG(0, "NormInstance::WaitForEvent() warning: NORM thread not running!\n"); + PLOG(PL_FATAL, "NormInstance::WaitForEvent() warning: NORM thread not running!\n"); return false; } #ifdef WIN32 @@ -529,7 +583,7 @@ bool NormInstance::WaitForEvent() { if (EINTR != errno) { - DMSG(0, "NormInstance::WaitForEvent() select() error: %s\n", + PLOG(PL_FATAL, "NormInstance::WaitForEvent() select() error: %s\n", GetErrorString()); return false; } @@ -552,19 +606,19 @@ bool NormInstance::Startup(bool priorityBoost) notify_event = CreateEvent(NULL, TRUE, FALSE, NULL); if (NULL == notify_event) { - DMSG(0, "NormInstance::Startup() CreateEvent() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormInstance::Startup() CreateEvent() error: %s\n", GetErrorString()); return false; } #else if (0 != pipe(notify_fd)) { - DMSG(0, "NormInstance::Startup() pipe() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormInstance::Startup() pipe() error: %s\n", GetErrorString()); return false; } // make reading non-blocking if(-1 == fcntl(notify_fd[0], F_SETFL, fcntl(notify_fd[0], F_GETFL, 0) | O_NONBLOCK)) { - DMSG(0, "NormInstance::Startup() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormInstance::Startup() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString()); close(notify_fd[0]); close(notify_fd[1]); notify_fd[0] = notify_fd[1] = -1; @@ -606,7 +660,7 @@ void NormInstance::Shutdown() if (NORM_OBJECT_INVALID != previous_notification->event.object) ((NormObject*)(previous_notification->event.object))->Release(); else if (NORM_NODE_INVALID != previous_notification->event.sender) - ((NormServerNode*)(previous_notification->event.sender))->Release(); + ((NormSenderNode*)(previous_notification->event.sender))->Release(); notify_pool.Append(previous_notification); previous_notification = NULL; } @@ -635,7 +689,7 @@ void NormInstance::Shutdown() if (NORM_OBJECT_INVALID != n->event.object) ((NormObject*)(n->event.object))->Release(); else if (NORM_NODE_INVALID != n->event.sender) - ((NormServerNode*)(n->event.sender))->Release(); + ((NormSenderNode*)(n->event.sender))->Release(); delete n; } notify_pool.Destroy(); @@ -743,23 +797,33 @@ NormDescriptor NormGetDescriptor(NormInstanceHandle instanceHandle) return NORM_DESCRIPTOR_INVALID; } // end NormGetDescriptor() + +// if "waitForEvent" is false, this is a non-blocking call // (TBD) add a timeout option to this? + NORM_API_LINKAGE -bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent) +bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent, bool waitForEvent) { bool result = false; NormInstance* instance = (NormInstance*)instanceHandle; if (instance) { - if (instance->NotifyQueueIsEmpty()) //(TBD) perform this check??? - { - if (!instance->WaitForEvent()) - { - return false; - } - } if (instance->dispatcher.SuspendThread()) { + if (waitForEvent) + { + if (instance->NotifyQueueIsEmpty()) + { + // no pending events, so resume and wait + instance->dispatcher.ResumeThread(); + if (!instance->WaitForEvent()) + { + return false; + } + // re-suspend thread after wait + if (!instance->dispatcher.SuspendThread()) return false; + } + } result = instance->GetNextEvent(theEvent); instance->dispatcher.ResumeThread(); } @@ -769,11 +833,10 @@ bool NormGetNextEvent(NormInstanceHandle instanceHandle, NormEvent* theEvent) /** NORM Session Creation and Control Functions */ - NORM_API_LINKAGE NormSessionHandle NormCreateSession(NormInstanceHandle instanceHandle, const char* sessionAddr, - UINT16 sessionPort, + UINT16 sessionPort, NormNodeId localNodeId) { // (TBD) wrap this with SuspendThread/ResumeThread ??? @@ -800,6 +863,7 @@ void NormDestroySession(NormSessionHandle sessionHandle) { session->Close(); session->GetSessionMgr().DeleteSession(session); + instance->PurgeSessionNotifications(sessionHandle); } instance->dispatcher.ResumeThread(); } @@ -850,25 +914,76 @@ NormNodeId NormGetLocalNodeId(NormSessionHandle sessionHandle) } // end NormGetLocalNodeId() NORM_API_LINKAGE -void NormSetTxPort(NormSessionHandle sessionHandle, - UINT16 txPort) +bool NormSetTxPort(NormSessionHandle sessionHandle, + UINT16 txPort, + bool enableReuse, + const char* txAddress) { + bool result = false; NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (NULL != instance) { if (instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->SetTxPort(txPort); + if (NULL != session) + result = session->SetTxPort(txPort, enableReuse, txAddress); instance->dispatcher.ResumeThread(); } } + return result; } // end NormSetTxPort() +NORM_API_LINKAGE +UINT16 NormGetTxPort(NormSessionHandle sessionHandle) +{ + NormSession* session = (NormSession*)sessionHandle; + if (NULL != session) + return session->GetTxPort(); + else + return 0; +} // end NormGetTxPort() + +NORM_API_LINKAGE +void NormSetTxOnly(NormSessionHandle sessionHandle, + bool txOnly, + bool connectToSessionAddress) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) session->SetTxOnly(txOnly, connectToSessionAddress); + instance->dispatcher.ResumeThread(); + } +} // end NormSetTxOnly() + +NORM_API_LINKAGE +bool NormChangeDestination(NormSessionHandle sessionHandle, + const char* sessionAddress, + UINT16 sessionPort) +{ + // First, see if we can make a valid ProtoAddress + ProtoAddress dest; + if (!dest.ResolveFromString(sessionAddress)) + return false; + dest.SetPort(sessionPort); + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) session->SetAddress(dest); + instance->dispatcher.ResumeThread(); + } + return true; +} // end NormChangeDestination() + NORM_API_LINKAGE void NormSetRxPortReuse(NormSessionHandle sessionHandle, - bool enable, - bool bindToSessionAddress) + bool enableReuse, + const char* rxAddress, // bind() to / + const char* senderAddress, // connect() to / + UINT16 senderPort) { NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (NULL != instance) @@ -876,11 +991,37 @@ void NormSetRxPortReuse(NormSessionHandle sessionHandle, if (instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->SetRxPortReuse(enable, bindToSessionAddress); + if (session) session->SetRxPortReuse(enableReuse, rxAddress, senderAddress, senderPort); instance->dispatcher.ResumeThread(); } } -} // end NormSetTxPort() +} // end NormSetRxPortReuse() + +NORM_API_LINKAGE +UINT16 NormGetRxPort(NormSessionHandle sessionHandle) +{ + NormSession* session = (NormSession*)sessionHandle; + if (NULL != session) + return session->GetRxPort(); + else + return 0; +} // end NormGetRxPort() + + +NORM_API_LINKAGE +void NormSetEcnSupport(NormSessionHandle sessionHandle, bool ecnEnable, bool ignoreLoss, bool tolerateLoss) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (NULL != instance) + { + if (instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) session->SetEcnSupport(ecnEnable, ignoreLoss, tolerateLoss); + instance->dispatcher.ResumeThread(); + } + } +} // end NormSetEcnSupport() NORM_API_LINKAGE bool NormSetMulticastInterface(NormSessionHandle sessionHandle, @@ -951,6 +1092,17 @@ bool NormSetLoopback(NormSessionHandle sessionHandle, bool state) return false; } // end NormSetLoopback() + +NORM_API_LINKAGE +bool NormSetFragmentation(NormSessionHandle sessionHandle, bool state) +{ + NormSession* session = (NormSession*)sessionHandle; + if (session) + return session->SetFragmentation(state); + else + return false; +} // end NormSetFragmentation() + NORM_API_LINKAGE void NormSetMessageTrace(NormSessionHandle sessionHandle, bool state) { @@ -972,6 +1124,97 @@ void NormSetRxLoss(NormSessionHandle sessionHandle, double percent) if (session) session->SetRxLoss(percent); } // end NormSetRxLoss() +NORM_API_LINKAGE +bool NormOpenDebugLog(NormInstanceHandle instanceHandle, const char *path) +{ + /* NOTE: This only locks one thread. Multiple NormInstances could + cause problems with this. */ + bool result = false; + NormInstance* instance = (NormInstance*)instanceHandle; + if (instance->dispatcher.SuspendThread()) { + result = OpenDebugLog(path); + instance->dispatcher.ResumeThread(); + } + return result; +} + +NORM_API_LINKAGE +void NormCloseDebugLog(NormInstanceHandle instanceHandle) +{ + /* NOTE: This only locks one thread. Multiple NormInstances could + cause problems with this. */ + NormInstance* instance = (NormInstance*)instanceHandle; + if (instance->dispatcher.SuspendThread()) { + CloseDebugLog(); + instance->dispatcher.ResumeThread(); + } +} + +NORM_API_LINKAGE +bool NormOpenDebugPipe(NormInstanceHandle instanceHandle, const char *pipeName) +{ + /* NOTE: This only locks one thread. Multiple NormInstances could + cause problems with this. */ + bool result = false; + NormInstance* instance = (NormInstance*)instanceHandle; + if (instance->dispatcher.SuspendThread()) { + result = OpenDebugPipe(pipeName); + instance->dispatcher.ResumeThread(); + } + return result; +} // end NormOpenDebugPipe() + +NORM_API_LINKAGE +void NormCloseDebugPipe(NormInstanceHandle instanceHandle) +{ + /* NOTE: This only locks one thread. Multiple NormInstances could + cause problems with this. */ + NormInstance* instance = (NormInstance*)instanceHandle; + if (instance->dispatcher.SuspendThread()) { + CloseDebugPipe(); + instance->dispatcher.ResumeThread(); + } +} // end NormCloseDebugPipe() + +NORM_API_LINKAGE +void NormSetDebugLevel(unsigned int level) +{ + SetDebugLevel(level); +} + +NORM_API_LINKAGE +unsigned int NormGetDebugLevel() +{ + return GetDebugLevel(); +} + +NORM_API_LINKAGE +void NormSetReportInterval(NormSessionHandle sessionHandle, double interval) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) + session->SetReportTimerInterval(interval); + instance->dispatcher.ResumeThread(); + } +} // end NormSetReportInterval() + +NORM_API_LINKAGE +double NormGetReportInterval(NormSessionHandle sessionHandle) +{ + double result = 0; + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) + result = session->GetReportTimerInterval(); + instance->dispatcher.ResumeThread(); + } + return result; +} // end NormGetReportInterval() /** NORM Sender Functions */ @@ -989,7 +1232,7 @@ bool NormStartSender(NormSessionHandle sessionHandle, { NormSession* session = (NormSession*)sessionHandle; if (session) - result = session->StartServer(sessionId, bufferSpace, segmentSize, numData, numParity); + result = session->StartSender(sessionId, bufferSpace, segmentSize, numData, numParity); else result = false; instance->dispatcher.ResumeThread(); @@ -1004,23 +1247,23 @@ void NormStopSender(NormSessionHandle sessionHandle) if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->StopServer(); + if (session) session->StopSender(); instance->dispatcher.ResumeThread(); } } // end NormStopSender() NORM_API_LINKAGE -double NormGetTransmitRate(NormSessionHandle sessionHandle) +double NormGetTxRate(NormSessionHandle sessionHandle) { if (NORM_SESSION_INVALID != sessionHandle) return (((NormSession*)sessionHandle)->GetTxRate()); else return -1.0; -} // end NormGetTransmitRate() +} // end NormGetTxRate() NORM_API_LINKAGE -void NormSetTransmitRate(NormSessionHandle sessionHandle, +void NormSetTxRate(NormSessionHandle sessionHandle, double bitsPerSecond) { NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); @@ -1030,7 +1273,7 @@ void NormSetTransmitRate(NormSessionHandle sessionHandle, if (session) session->SetTxRate(bitsPerSecond); instance->dispatcher.ResumeThread(); } -} // end NormSetTransmitRate() +} // end NormSetTxRate() NORM_API_LINKAGE bool NormSetTxSocketBuffer(NormSessionHandle sessionHandle, @@ -1049,21 +1292,33 @@ bool NormSetTxSocketBuffer(NormSessionHandle sessionHandle, } // end NormSetTxSocketBuffer() NORM_API_LINKAGE -void NormSetCongestionControl(NormSessionHandle sessionHandle, bool enable) +void NormSetFlowControl(NormSessionHandle sessionHandle, double flowControlFactor) { NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->SetCongestionControl(enable); + if (session) session->SetFlowControl(flowControlFactor); + instance->dispatcher.ResumeThread(); + } +} // end NormSetFlowControl() + +NORM_API_LINKAGE +void NormSetCongestionControl(NormSessionHandle sessionHandle, bool enable, bool adjustRate) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) session->SetCongestionControl(enable, adjustRate); instance->dispatcher.ResumeThread(); } } // end NormSetCongestionControl() NORM_API_LINKAGE -void NormSetTransmitRateBounds(NormSessionHandle sessionHandle, - double rateMin, - double rateMax) +void NormSetTxRateBounds(NormSessionHandle sessionHandle, + double rateMin, + double rateMax) { NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (instance && instance->dispatcher.SuspendThread()) @@ -1072,13 +1327,13 @@ void NormSetTransmitRateBounds(NormSessionHandle sessionHandle, if (session) session->SetTxRateBounds(rateMin, rateMax); instance->dispatcher.ResumeThread(); } -} // end NormSetTransmitRateBounds() +} // end NormSetTxRateBounds() NORM_API_LINKAGE -void NormSetTransmitCacheBounds(NormSessionHandle sessionHandle, - NormSize sizeMax, - UINT32 countMin, - UINT32 countMax) +void NormSetTxCacheBounds(NormSessionHandle sessionHandle, + NormSize sizeMax, + UINT32 countMin, + UINT32 countMax) { NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); if (instance && instance->dispatcher.SuspendThread()) @@ -1088,7 +1343,7 @@ void NormSetTransmitCacheBounds(NormSessionHandle sessionHandle, if (session) session->SetTxCacheBounds(theSize, countMin, countMax); instance->dispatcher.ResumeThread(); } -} // end NormSetTransmitCacheBounds() +} // end NormSetTxCacheBounds() NORM_API_LINKAGE void NormSetAutoParity(NormSessionHandle sessionHandle, unsigned char autoParity) @@ -1097,7 +1352,7 @@ void NormSetAutoParity(NormSessionHandle sessionHandle, unsigned char autoParity if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ServerSetAutoParity(autoParity); + if (session) session->SenderSetAutoParity(autoParity); instance->dispatcher.ResumeThread(); } } // end NormSetAutoParity() @@ -1110,7 +1365,7 @@ void NormSetGrttEstimate(NormSessionHandle sessionHandle, if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ServerSetGrtt(grttEstimate); + if (session) session->SenderSetGrtt(grttEstimate); instance->dispatcher.ResumeThread(); } } // end NormSetGrttEstimate() @@ -1122,12 +1377,12 @@ double NormGetGrttEstimate(NormSessionHandle sessionHandle) if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ServerGrtt(); + if (session) session->SenderGrtt(); instance->dispatcher.ResumeThread(); } if (NORM_SESSION_INVALID != sessionHandle) - return (((NormSession*)sessionHandle)->ServerGrtt()); + return (((NormSession*)sessionHandle)->SenderGrtt()); else return -1.0; } // end NormGetGrttEstimate() @@ -1194,7 +1449,7 @@ void NormSetGroupSize(NormSessionHandle sessionHandle, if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - session->ServerSetGroupSize((double)groupSize); + session->SenderSetGroupSize((double)groupSize); instance->dispatcher.ResumeThread(); } } // end NormSetGroupSize() @@ -1237,7 +1492,7 @@ NormObjectHandle NormFileEnqueue(NormSessionHandle sessionHandle, NORM_API_LINKAGE NormObjectHandle NormDataEnqueue(NormSessionHandle sessionHandle, const char* dataPtr, - UINT32 dataLen, + UINT32 dataLen, const char* infoPtr, unsigned int infoLen) { @@ -1278,7 +1533,9 @@ bool NormRequeueObject(NormSessionHandle sessionHandle, NormObjectHandle objectH NORM_API_LINKAGE NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle, - UINT32 bufferSize) + UINT32 bufferSize, + const char* infoPtr, + unsigned int infoLen) { NormObjectHandle objectHandle = NORM_OBJECT_INVALID; NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); @@ -1288,7 +1545,7 @@ NormObjectHandle NormStreamOpen(NormSessionHandle sessionHandle, if (session) { NormObject* obj = - static_cast(session->QueueTxStream(bufferSize, true)); + static_cast(session->QueueTxStream(bufferSize, true, infoPtr, infoLen)); if (obj) objectHandle = (NormObjectHandle)obj; } instance->dispatcher.ResumeThread(); @@ -1303,7 +1560,7 @@ void NormStreamClose(NormObjectHandle streamHandle, bool graceful) static_cast((NormObject*)streamHandle); if (NULL != stream) { - if (graceful && (NULL == stream->GetServer())) + if (graceful && (NULL == stream->GetSender())) { NormInstance* instance = NormInstance::GetInstanceFromObject(streamHandle); if (instance && instance->dispatcher.SuspendThread()) @@ -1324,6 +1581,11 @@ unsigned int NormStreamWrite(NormObjectHandle streamHandle, const char* buffer, unsigned int numBytes) { + // TBD - the thread suspend/resume here may _not_ be sufficient. + // we may need to do something to make sure the NORM thread + // wakes up right away (i.e. SignalThread() + // Also so the same for flush, send command, and data/file enqueue calls! + // (i.e. in any case where immediate data send action may be invoked) unsigned int result = 0; NormInstance* instance = NormInstance::GetInstanceFromObject(streamHandle); if (instance && instance->dispatcher.SuspendThread()) @@ -1403,7 +1665,8 @@ void NormStreamMarkEom(NormObjectHandle streamHandle) NORM_API_LINKAGE bool NormSetWatermark(NormSessionHandle sessionHandle, - NormObjectHandle objectHandle) + NormObjectHandle objectHandle, + bool overrideFlush) { bool result = false; NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); @@ -1417,17 +1680,19 @@ bool NormSetWatermark(NormSessionHandle sessionHandle, if (obj->IsStream()) { NormStreamObject* stream = static_cast(obj); - session->ServerSetWatermark(stream->GetId(), + session->SenderSetWatermark(stream->GetId(), stream->FlushBlockId(), - stream->FlushSegmentId()); + stream->FlushSegmentId(), + overrideFlush); } else { NormBlockId blockId = obj->GetFinalBlockId(); NormSegmentId segmentId = obj->GetBlockSize(blockId) - 1; - session->ServerSetWatermark(obj->GetId(), + session->SenderSetWatermark(obj->GetId(), blockId, - segmentId); + segmentId, + overrideFlush); } result = true; } @@ -1436,6 +1701,21 @@ bool NormSetWatermark(NormSessionHandle sessionHandle, return result; } // end NormSetWatermark() +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; +} // end NormResetWatermark() + NORM_API_LINKAGE void NormCancelWatermark(NormSessionHandle sessionHandle) { @@ -1443,7 +1723,7 @@ void NormCancelWatermark(NormSessionHandle sessionHandle) if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - session->ServerCancelWatermark(); + session->SenderCancelWatermark(); instance->dispatcher.ResumeThread(); } } // end NormSetWatermark() @@ -1458,7 +1738,7 @@ bool NormAddAckingNode(NormSessionHandle sessionHandle, { NormSession* session = (NormSession*)sessionHandle; if (session) - result = session->ServerAddAckingNode(nodeId); + result = session->SenderAddAckingNode(nodeId); instance->dispatcher.ResumeThread(); } return result; @@ -1472,11 +1752,35 @@ void NormRemoveAckingNode(NormSessionHandle sessionHandle, if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ServerRemoveAckingNode(nodeId); + if (session) session->SenderRemoveAckingNode(nodeId); instance->dispatcher.ResumeThread(); } } // end NormRemoveAckingNode() +NORM_API_LINKAGE +void NormSetAutoAckingNodes(NormSessionHandle sessionHandle, + NormTrackingStatus trackingStatus) +{ + NormSession* session = (NormSession*)sessionHandle; + switch (trackingStatus) + { + case NORM_TRACK_NONE: + session->SenderSetAutoAckingNodes(NormSession::TRACK_NONE); + break; + case NORM_TRACK_RECEIVERS: + session->SenderSetAutoAckingNodes(NormSession::TRACK_RECEIVERS); + break; + case NORM_TRACK_SENDERS: + session->SenderSetAutoAckingNodes(NormSession::TRACK_SENDERS); + break; + case NORM_TRACK_ALL: + session->SenderSetAutoAckingNodes(NormSession::TRACK_ALL); + break; + default: + break; + } +} // end NormSetAutoAckingNodes() + NORM_API_LINKAGE NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, NormNodeId nodeId) @@ -1486,7 +1790,7 @@ NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, { NormSession* session = (NormSession*)sessionHandle; NormAckingStatus status = - (NormAckingStatus)session->ServerGetAckingStatus(nodeId); + (NormAckingStatus)session->SenderGetAckingStatus(nodeId); instance->dispatcher.ResumeThread(); return status; } @@ -1496,6 +1800,60 @@ NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle, } } // end NormGetAckingNodeStatus() +NORM_API_LINKAGE +bool NormGetNextAckingNode(NormSessionHandle sessionHandle, + NormNodeId* nodeId, + NormAckingStatus* ackingStatus) +{ + if (NULL == nodeId) return false; + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + bool result = session->SenderGetNextAckingNode(*nodeId, (NormSession::AckingStatus*)ackingStatus); + instance->dispatcher.ResumeThread(); + return result; + } + else + { + return false; + } +} // end NormGetNextAckingNode() + +NORM_API_LINKAGE +bool NormSendCommand(NormSessionHandle sessionHandle, + const char* cmdBuffer, + unsigned int cmdLength, + bool robust) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + bool result = session->SenderSendCmd(cmdBuffer, cmdLength, robust); + instance->dispatcher.ResumeThread(); + return result; + } + else + { + return false; + } +} // end NormSendCommand() + +NORM_API_LINKAGE +void NormCancelCommand(NormSessionHandle sessionHandle) +{ + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + session->SenderCancelCmd(); + // we purge in case command was already sent notification posted + instance->PurgeNotifications(sessionHandle, NORM_TX_CMD_SENT); + instance->dispatcher.ResumeThread(); + } +} // end NormCancelCommand() + /** NORM Receiver Functions */ NORM_API_LINKAGE @@ -1507,7 +1865,7 @@ bool NormStartReceiver(NormSessionHandle sessionHandle, if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - result = session->StartClient(bufferSpace); + result = session->StartReceiver(bufferSpace); instance->dispatcher.ResumeThread(); } return result; @@ -1521,11 +1879,27 @@ void NormStopReceiver(NormSessionHandle sessionHandle) if (instance && instance->dispatcher.SuspendThread()) { NormSession* session = (NormSession*)sessionHandle; - session->StopClient(); + session->StopReceiver(); instance->dispatcher.ResumeThread(); } } // end NormStopReceiver() + +NORM_API_LINKAGE +void NormSetRxCacheLimit(NormSessionHandle sessionHandle, + unsigned short countMax) +{ + // We limit this to 1/4 of the 16-bit NormObjectId space + if (countMax > 16384) countMax = 16384; + NormInstance* instance = NormInstance::GetInstanceFromSession(sessionHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSession* session = (NormSession*)sessionHandle; + if (session) session->SetRxCacheMax(countMax); + instance->dispatcher.ResumeThread(); + } +} // end NormSetRxCacheLimit() + NORM_API_LINKAGE bool NormSetRxSocketBuffer(NormSessionHandle sessionHandle, unsigned int bufferSize) @@ -1550,7 +1924,7 @@ void NormSetSilentReceiver(NormSessionHandle sessionHandle, NormSession* session = (NormSession*)sessionHandle; if (session) { - session->ClientSetSilent(silent); + session->ReceiverSetSilent(silent); session->RcvrSetMaxDelay(maxDelay); } } // end NormSetSilentReceiver() @@ -1560,7 +1934,7 @@ void NormSetDefaultUnicastNack(NormSessionHandle sessionHandle, bool unicastNacks) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ClientSetUnicastNacks(unicastNacks); + if (session) session->ReceiverSetUnicastNacks(unicastNacks); } // end NormSetDefaultUnicastNack() NORM_API_LINKAGE @@ -1568,23 +1942,31 @@ void NormNodeSetUnicastNack(NormNodeHandle nodeHandle, bool unicastNacks) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; if (node) node->SetUnicastNacks(unicastNacks); } // end NormNodeSetUnicastNack() +NORM_API_LINKAGE +void NormSetDefaultSyncPolicy(NormSessionHandle sessionHandle, + NormSyncPolicy syncPolicy) +{ + NormSession* session = (NormSession*)sessionHandle; + if (session) session->ReceiverSetDefaultSyncPolicy((NormSenderNode::SyncPolicy)syncPolicy); +} // end NormSetDefaultSyncPolicy() + NORM_API_LINKAGE void NormSetDefaultNackingMode(NormSessionHandle sessionHandle, NormNackingMode nackingMode) { NormSession* session = (NormSession*)sessionHandle; - if (session) session->ClientSetDefaultNackingMode((NormObject::NackingMode)nackingMode); + if (session) session->ReceiverSetDefaultNackingMode((NormObject::NackingMode)nackingMode); } // end NormSetDefaultNackingMode() NORM_API_LINKAGE void NormNodeSetNackingMode(NormNodeHandle nodeHandle, NormNackingMode nackingMode) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; if (node) node->SetDefaultNackingMode((NormObject::NackingMode)nackingMode); } // end NormNodeSetNackingMode() @@ -1602,16 +1984,16 @@ void NormSetDefaultRepairBoundary(NormSessionHandle sessionHandle, { NormSession* session = (NormSession*)sessionHandle; if (session) - session->ClientSetDefaultRepairBoundary((NormServerNode::RepairBoundary)repairBoundary); + session->ReceiverSetDefaultRepairBoundary((NormSenderNode::RepairBoundary)repairBoundary); } // end NormSetDefaultRepairBoundary() NORM_API_LINKAGE void NormNodeSetRepairBoundary(NormNodeHandle nodeHandle, NormRepairBoundary repairBoundary) { - NormServerNode* node = static_cast((NormServerNode*)nodeHandle); + NormSenderNode* node = static_cast((NormSenderNode*)nodeHandle); if (node) - node->SetRepairBoundary((NormServerNode::RepairBoundary)repairBoundary); + node->SetRepairBoundary((NormSenderNode::RepairBoundary)repairBoundary); } // end NormNodeSetRepairBoundary() @@ -1637,7 +2019,7 @@ void NormNodeSetRxRobustFactor(NormNodeHandle nodeHandle, NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); if (instance && instance->dispatcher.SuspendThread()) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; node->SetRobustFactor(robustFactor); instance->dispatcher.ResumeThread(); } @@ -1688,6 +2070,21 @@ UINT32 NormStreamGetReadOffset(NormObjectHandle streamHandle) return 0; } // end NormStreamGetReadOffset() +NORM_API_LINKAGE +unsigned int NormStreamGetBufferUsage(NormObjectHandle streamHandle) +{ + unsigned int usage = 0; + NormInstance* instance = NormInstance::GetInstanceFromObject(streamHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormStreamObject* stream = + static_cast((NormObject*)streamHandle); + usage = stream->GetCurrentBufferUsage() ; + instance->dispatcher.ResumeThread(); + } + return usage; +} // end NormStreamGetBufferUsage() + /** NORM Object Functions */ @@ -1764,9 +2161,9 @@ void NormObjectCancel(NormObjectHandle objectHandle) if (instance && instance->dispatcher.SuspendThread()) { NormObject* obj = (NormObject*)objectHandle; - NormServerNode* server = obj->GetServer(); - if (server) - server->DeleteObject(obj); + NormSenderNode* sender = obj->GetSender(); + if (sender) + sender->DeleteObject(obj); else obj->GetSession().DeleteTxObject(obj); instance->PurgeObjectNotifications(objectHandle); @@ -1818,7 +2215,7 @@ bool NormFileGetName(NormObjectHandle fileHandle, static_cast((NormObject*)fileHandle); bufferLen = (bufferLen < PATH_MAX) ? bufferLen : PATH_MAX; strncpy(nameBuffer, file->GetPath(), bufferLen); - nameBuffer[bufferLen] = '\0'; + nameBuffer[bufferLen - 1] = '\0'; // (TBD) should we always do this result = true; } return result; @@ -1842,7 +2239,7 @@ bool NormFileRename(NormObjectHandle fileHandle, } // end NormFileRename() NORM_API_LINKAGE -const char*volatile NormDataAccessData(NormObjectHandle dataHandle) +const char* NormDataAccessData(NormObjectHandle dataHandle) { NormDataObject* dataObj = static_cast((NormObject*)dataHandle); @@ -1860,7 +2257,7 @@ char* NormDataDetachData(NormObjectHandle dataHandle) NORM_API_LINKAGE NormNodeHandle NormObjectGetSender(NormObjectHandle objectHandle) { - return (NormNodeHandle)(((NormObject*)objectHandle)->GetServer()); + return (NormNodeHandle)(((NormObject*)objectHandle)->GetSender()); } // end NormObjectGetSender() @@ -1869,7 +2266,7 @@ NormNodeHandle NormObjectGetSender(NormObjectHandle objectHandle) NORM_API_LINKAGE NormNodeId NormNodeGetId(NormNodeHandle nodeHandle) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; if (NULL != node) return node->GetId(); else @@ -1888,7 +2285,7 @@ bool NormNodeGetAddress(NormNodeHandle nodeHandle, NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); if (instance && instance->dispatcher.SuspendThread()) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; const ProtoAddress& nodeAddr = node->GetAddress(); unsigned int addrLen = nodeAddr.GetLength(); if (addrBuffer && bufferLen && (addrLen <= *bufferLen)) @@ -1911,13 +2308,32 @@ bool NormNodeGetAddress(NormNodeHandle nodeHandle, NORM_API_LINKAGE double NormNodeGetGrtt(NormNodeHandle nodeHandle) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; if (NULL != node) return node->GetGrttEstimate(); else return -1.0; } // end NormNodeGetGrtt() +NORM_API_LINKAGE +bool NormNodeGetCommand(NormNodeHandle nodeHandle, + char* cmdBuffer, + unsigned int* cmdLength) +{ + bool result = false; + if (NORM_NODE_INVALID != nodeHandle) + { + NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSenderNode* node = (NormSenderNode*)nodeHandle; + result = node->ReadNextCmd(cmdBuffer, cmdLength); + instance->dispatcher.ResumeThread(); + } + } + return result; +} // end NormNodeGetCommand() + NORM_API_LINKAGE void NormNodeFreeBuffers(NormNodeHandle nodeHandle) { @@ -1926,13 +2342,28 @@ void NormNodeFreeBuffers(NormNodeHandle nodeHandle) NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); if (instance && instance->dispatcher.SuspendThread()) { - NormServerNode* node = (NormServerNode*)nodeHandle; + NormSenderNode* node = (NormSenderNode*)nodeHandle; node->FreeBuffers(); instance->dispatcher.ResumeThread(); } } } // end NormNodeFreeBuffers() +NORM_API_LINKAGE +void NormNodeDelete(NormNodeHandle nodeHandle) +{ + if (NORM_NODE_INVALID != nodeHandle) + { + NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); + if (instance && instance->dispatcher.SuspendThread()) + { + NormSenderNode* node = (NormSenderNode*)nodeHandle; + node->GetSession().DeleteRemoteSender(*node); + instance->dispatcher.ResumeThread(); + } + } +} // end NormNodeDelete() + NORM_API_LINKAGE void NormNodeRetain(NormNodeHandle nodeHandle) { @@ -1941,7 +2372,7 @@ void NormNodeRetain(NormNodeHandle nodeHandle) NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); if (instance && instance->dispatcher.SuspendThread()) { - ((NormServerNode*)nodeHandle)->Retain(); + ((NormSenderNode*)nodeHandle)->Retain(); instance->dispatcher.ResumeThread(); } } @@ -1955,7 +2386,7 @@ void NormNodeRelease(NormNodeHandle nodeHandle) NormInstance* instance = NormInstance::GetInstanceFromNode(nodeHandle); if (instance && instance->dispatcher.SuspendThread()) { - ((NormServerNode*)nodeHandle)->Release(); + ((NormSenderNode*)nodeHandle)->Release(); instance->dispatcher.ResumeThread(); } } diff --git a/common/normApp.cpp b/src/common/normApp.cpp similarity index 67% rename from common/normApp.cpp rename to src/common/normApp.cpp index bae6363..72d5dab 100644 --- a/common/normApp.cpp +++ b/src/common/normApp.cpp @@ -38,6 +38,34 @@ class NormApp : public NormController, public ProtoApp const void* userData); private: + // we use this class to cache tx file paths + // and their last tx object id to support the + // "requeue" option + class FileCacheItem : public ProtoTree::Item + { + public: + FileCacheItem(const char* thePath); + ~FileCacheItem(); + + void SetObjectId(NormObjectId objectId) + {object_id = objectId;} + NormObjectId GetObjectId() const + {return object_id;} + + + private: + // ProtoTree::Item overrides + const char* GetKey() const + {return file_path;} + unsigned int GetKeysize() const + {return (PATH_MAX << 3);} + + char file_path[PATH_MAX]; + NormObjectId object_id; + }; + + + void ShowHelp(); void OnInputReady(); bool AddAckingNodes(const char* ackingNodeList); @@ -48,7 +76,7 @@ class NormApp : public NormController, public ProtoApp virtual void Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* server, + class NormSenderNode* sender, class NormObject* object); bool OnIntervalTimeout(ProtoTimer& theTimer); @@ -56,6 +84,8 @@ class NormApp : public NormController, public ProtoApp static const char* const cmd_list[]; + enum EcnMode {ECN_OFF, ECN_ON, ECN_ONLY}; + #ifdef UNIX static void SignalHandler(int sigNum); #endif // UNIX @@ -66,10 +96,12 @@ class NormApp : public NormController, public ProtoApp NormSession* session; NormStreamObject* tx_stream; NormStreamObject* rx_stream; - + // application parameters FILE* input; // input stream FILE* output; // output stream + char* output_io_buffer; + unsigned int output_io_bufsize; char input_buffer[65535]; unsigned int input_index; unsigned int input_length; @@ -79,11 +111,16 @@ class NormApp : public NormController, public ProtoApp bool input_messaging; // stream input mode UINT16 input_msg_length; UINT16 input_msg_index; + bool msg_test; + unsigned int msg_test_length; + UINT32 msg_test_seq; char output_buffer[65535]; UINT16 output_index; bool output_messaging; UINT16 output_msg_length; bool output_msg_sync; + bool precise; + bool boost; // NormSession common parameters char* address; // session address @@ -96,18 +133,27 @@ class NormApp : public NormController, public ProtoApp double tx_rate_min; double tx_rate_max; bool cc_enable; + EcnMode ecn_mode; + bool tolerate_loss; // for "lost tolerant" congestion control option + UINT32 node_id; // NormSession sender-only parameters UINT16 segment_size; - UINT8 ndata; - UINT8 nparity; - UINT8 auto_parity; - UINT8 extra_parity; + UINT16 ndata; + UINT16 nparity; + UINT16 auto_parity; + UINT16 extra_parity; double backoff_factor; double grtt_estimate; // initial grtt estimate double group_size; unsigned long tx_buffer_size; // bytes + unsigned int tx_sock_buffer_size; + unsigned long tx_cache_min; + unsigned long tx_cache_max; + NormObjectSize tx_cache_size; + NormFileList tx_file_list; + ProtoTree tx_file_cache; bool tx_one_shot; bool tx_ack_shot; bool tx_file_queued; @@ -121,8 +167,11 @@ class NormApp : public NormController, public ProtoApp int tx_repeat_count; double tx_repeat_interval; bool tx_repeat_clear; + int tx_requeue; // master requeue value + int tx_requeue_count; // current requeue counter vale ProtoTimer interval_timer; char* acking_node_list; // comma-delimited string + bool acking_flushes; bool watermark_pending; // NormSession receiver-only parameters @@ -132,11 +181,13 @@ class NormApp : public NormController, public ProtoApp char* rx_cache_path; NormPostProcessor* post_processor; bool unicast_nacks; - bool silent_client; + bool silent_receiver; bool low_delay; + bool realtime; int rx_robust_factor; bool rx_persistent; bool process_aborted_files; + bool preallocate_sender; // Debug parameters bool tracing; @@ -145,26 +196,41 @@ class NormApp : public NormController, public ProtoApp }; // end class NormApp + +NormApp::FileCacheItem::FileCacheItem(const char* thePath) +{ + strncpy(file_path, thePath, PATH_MAX); +} + +NormApp::FileCacheItem::~FileCacheItem() +{ +} + NormApp::NormApp() : control_pipe(ProtoPipe::MESSAGE), control_remote(false), - session_mgr(GetTimerMgr(), GetSocketNotifier()), + session_mgr(GetTimerMgr(), GetSocketNotifier(), &dispatcher), session(NULL), tx_stream(NULL), rx_stream(NULL), input(NULL), output(NULL), + output_io_buffer(NULL), output_io_bufsize(0), input_index(0), input_length(0), input_active(false), push_mode(false), msg_flush_mode(NormStreamObject::FLUSH_PASSIVE), input_messaging(false), input_msg_length(0), input_msg_index(0), + msg_test(false), msg_test_length(0), msg_test_seq(0), output_index(0), output_messaging(false), output_msg_length(0), output_msg_sync(false), + precise(false), boost(false), address(NULL), port(0), ttl(32), loopback(false), interface_name(NULL), - tx_rate(64000.0), tx_rate_min(-1.0), tx_rate_max(-1.0), cc_enable(false), - segment_size(1024), ndata(32), nparity(16), auto_parity(0), extra_parity(0), + tx_rate(64000.0), tx_rate_min(-1.0), tx_rate_max(-1.0), + cc_enable(false), ecn_mode(ECN_OFF), tolerate_loss(false), + node_id(NORM_NODE_ANY), segment_size(1024), ndata(32), nparity(16), auto_parity(0), extra_parity(0), backoff_factor(NormSession::DEFAULT_BACKOFF_FACTOR), grtt_estimate(NormSession::DEFAULT_GRTT_ESTIMATE), group_size(NormSession::DEFAULT_GSIZE_ESTIMATE), - tx_buffer_size(1024*1024), tx_one_shot(false), tx_ack_shot(false), tx_file_queued(false), + tx_buffer_size(1024*1024), tx_sock_buffer_size(0), tx_cache_min(8), tx_cache_max(256), tx_cache_size((UINT32)20*1024*1024), + tx_one_shot(false), tx_ack_shot(false), tx_file_queued(false), tx_robust_factor(NormSession::DEFAULT_ROBUST_FACTOR), tx_object_interval(0.0), tx_repeat_count(0), - tx_repeat_interval(2.0), tx_repeat_clear(true), acking_node_list(NULL), watermark_pending(false), - rx_buffer_size(1024*1024), rx_sock_buffer_size(0), - rx_cache_path(NULL), post_processor(NULL), unicast_nacks(false), silent_client(false), - low_delay(false), rx_robust_factor(NormSession::DEFAULT_ROBUST_FACTOR), process_aborted_files(false), - tracing(false), tx_loss(0.0), rx_loss(0.0) + tx_repeat_interval(2.0), tx_repeat_clear(true), tx_requeue(0), tx_requeue_count(0), acking_node_list(NULL), + acking_flushes(false), watermark_pending(false), rx_buffer_size(1024*1024), rx_sock_buffer_size(0), + rx_cache_path(NULL), post_processor(NULL), unicast_nacks(false), silent_receiver(false), + low_delay(false), realtime(false), rx_robust_factor(NormSession::DEFAULT_ROBUST_FACTOR), rx_persistent(true), process_aborted_files(false), + preallocate_sender(false), tracing(false), tx_loss(0.0), rx_loss(0.0) { control_pipe.SetListener(this, &NormApp::OnControlEvent); @@ -186,9 +252,17 @@ NormApp::~NormApp() { if (address) delete[] address; if (interface_name) delete[] interface_name; + + tx_file_cache.Destroy(); + if (rx_cache_path) delete[] rx_cache_path; if (post_processor) delete post_processor; if (acking_node_list) delete[] acking_node_list; + if (NULL != output_io_buffer) + { + delete[] output_io_buffer; + output_io_buffer = NULL; + } } // NOTE on message flushing mode: @@ -222,6 +296,8 @@ const char* const NormApp::cmd_list[] = "+loopback", // "off" or "on" to recv our own packets "+interface", // multicast interface name to use "+cc", // congestion control on/off + "+ecn", // explicit congestion notification 'on','off', or 'only'\n" + "-tolerant", // enables "loss-tolerant" congestion control operation "+rate", // tx date rate (bps) "+limit", // tx rate limits "-push", // push stream writes for real-time messaging @@ -234,10 +310,13 @@ const char* const NormApp::cmd_list[] = "+interval", // delay time (sec) between files (0.0 sec default) "+repeatcount", // How many times to repeat the file/directory list tx "+rinterval", // Interval (sec) between file/directory list repeats + "+requeue", // how many times files are retransmitted w/ same objId "-oneshot", // Transmit file(s), exiting upon TX_FLUSH_COMPLETED "-ackshot", // Transmit file(s), exiting upon TX_WATERMARK_COMPLETED "-updatesOnly", // only send updated files on repeat transmission "+ackingNodes", // comma-delimited list of node id's to from which to collect acks + "-ackflush", // when set, acking completion truncates end-of-transmission flushing + "+id", // set the local NormNodeId to a specific value "+rxcachedir", // recv file cache directory "+segment", // payload segment size (bytes) "+block", // User data packets per FEC coding block (blockSize) @@ -248,19 +327,29 @@ const char* const NormApp::cmd_list[] = "+grtt", // Set sender's initial GRTT estimate "+gsize", // Set sender's group size estimate "+txbuffer", // Size of sender's buffer + "+txsockbuffer", // tx socket buffer size + "+txcachebounds",// limits on sender tx object caching + "+txrobustfactor", // integer tx robust factor "+rxbuffer", // Size receiver allocates for buffering each sender "+rxsockbuffer", // Optional recv socket buffer size. "-unicastNacks", // unicast instead of multicast feedback messages - "-silentClient", // "silent" (non-nacking) receiver (EMCON mode) (must set for sender too) + "-silentReceiver", // "silent" (non-nacking) receiver (EMCON mode) (must set for sender too) + "-presetSender", // causes receiver to preallocate resources for remote sender w/ segmentSize, block, and parity params "-lowDelay", // for silent receivers only, delivers data/files to app sooner\n" + "-realtime", // for NACKing (non-silent) receivers, flips buffer mgmnt to favor low latency over reliability + "+rxpersist", // "off" or "on" to make receiver keep state on sender forever ("on" by default) "-saveAborts", // save (and possibly post-process) aborted receive files\n" "+processor", // receive file post processing command "+instance", // specify norm instance name for remote control commands - NULL + "-precise", // run the NormApp ProtoDispatcher in "precise timing" mode + "-boost", // run the NormApp with "boosted" process priority (super user only) + "+mtest", // send test messages via NORM_OBJECT_STREAM + "+stest", // send test stream of bytes via NORM_OBJECT_STREAM at + "+obuf", // non-standard buffer size for "output" or "moutput" FILE* (incl. stdout) + NULL }; // Thanks to Marinho Barcellos taking initiative with this help function -// (Someday a "norm" user's guide will be completed!" void NormApp::ShowHelp() { // TBD: this should be taken automatically from the cmd array @@ -278,6 +367,7 @@ void NormApp::ShowHelp() " +loopback, // 'on' or 'off' to recv our own packets (default = 'off')\n" " +interface, // multicast interface name to use\n" " +cc, // congestion control 'on' or 'off'\n" + " +ecn, // explicit congestion notification 'on' or 'off'\n" " +rate, // tx date rate (bps)\n" " +limit // tx rate bounds \n" " -push, // push stream writes for real-time messaging\n" @@ -290,10 +380,13 @@ void NormApp::ShowHelp() " +interval, // delay time (sec) between files (0.0 sec default)\n" " +repeatcount, // How many times to repeat the file/directory list tx\n" " +rinterval, // Interval (sec) between file/directory list repeats\n" + " +requeue, // how many times files are retransmitted w/ same objId\n" " -oneshot, // Exit upon sender TX_FLUSH_COMPLETED event (sender exits after transmission)\n" " -ackshot, // Exit upon sender TX_WATERMARK_COMPLETED event (sender exits after transmission)\n" " -updatesOnly, // only send updated files on repeat transmission\n" " +ackingNodes, // comma-delimited list of node id's to from which to collect acks\n" + " -ackflush, // when set, acking completion truncates flushing\n" + " +id, // set the local NormNodeId to a specific value\n" " +rxcachedir, // recv file cache directory\n" " +segment, // payload segment size (bytes)\n" " +block, // User data packets per FEC coding block (blockSize)\n" @@ -304,14 +397,22 @@ void NormApp::ShowHelp() " +grtt, // Set sender's initial GRTT estimate\n" " +gsize, // Set sender's group size estimate\n" " +txbuffer, // Size of sender's buffer\n" + " +txcachebounds,// limits on sender tx object caching\n" " +rxbuffer, // Size receiver allocates for buffering each sender\n" " +rxsockbuffer, // Optional recv socket buffer size.\n" " -unicastNacks, // unicast instead of multicast feedback messages\n" - " -silentClient, // silent (non-nacking) receiver (EMCON mode) (must set for sender too)\n" + " -silentReceiver, // silent (non-nacking) receiver (EMCON mode) (must set for sender too)\n" " -lowDelay, // for silent receivers only, delivers data/files to app sooner\n" + " -presetSender, // causes receiver to preallocate resources for remote sender w/ segmentSize, block, and parity params\n" + " -realtime, // for NACKing (non-silent) receivers, flips buffer mgmnt to favor low latency over reliability\n" + " +rxpersist, // 'off' or 'on' to make receiver keep state on sender forever ('on' by default)\n" " -saveAborts, // save (and possibly post-process) aborted receive files\n" " +processor, // receive file post processing command\n" " +instance, // specify norm instance name for remote control commands\n" + " -precise, // run the NormApp ProtoDispatcher in 'precise timing' mode\n" + " -boost, // run the NormApp with 'boosted' process priority (super user only)\n" + " +mtest, // send test messages via NORM_OBJECT_STREAM\n" + " +stest, // send test stream of bytes via NORM_OBJECT_STREAM\n" "\n"); } // end NormApp::ShowHelp() @@ -327,7 +428,7 @@ void NormApp::OnControlEvent(ProtoSocket& /*theSocket*/, ProtoSocket::Event theE if (control_pipe.Recv(buffer, len)) { buffer[len] = '\0'; - DMSG(0, "norm: received command \"%s\"\n", buffer); + PLOG(PL_ERROR, "norm: received command \"%s\"\n", buffer); char* cmd = buffer; char* val = NULL; for (unsigned int i = 0; i < len; i++) @@ -340,17 +441,17 @@ void NormApp::OnControlEvent(ProtoSocket& /*theSocket*/, ProtoSocket::Event theE } } if (!OnCommand(cmd, val)) - DMSG(0, "NormApp::OnControlEvent() bad command received\n"); + PLOG(PL_ERROR, "NormApp::OnControlEvent() bad command received\n"); } else { - DMSG(0, "NormApp::OnControlEvent() error receiving remote command\n"); + PLOG(PL_ERROR, "NormApp::OnControlEvent() error receiving remote command\n"); } break; } default: - DMSG(0, "NormApp::OnControlEvent() unhandled event type: %d\n", theEvent); + PLOG(PL_ERROR, "NormApp::OnControlEvent() unhandled event type: %d\n", theEvent); break; } } // end NormApp::OnControlEvent() @@ -362,7 +463,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) size_t len = strlen(cmd); if ((CMD_ARG == type) && !val) { - DMSG(0, "NormApp::OnCommand(%s) missing argument\n", cmd); + PLOG(PL_FATAL, "NormApp::OnCommand(%s) missing argument\n", cmd); return false; } @@ -391,12 +492,12 @@ bool NormApp::OnCommand(const char* cmd, const char* val) buffer[len++] = '\0'; if (!control_pipe.Send(buffer, (unsigned int&)len)) { - DMSG(0, "NormApp::OnCommand() error sending command to remote instance\n"); + PLOG(PL_FATAL, "NormApp::OnCommand() error sending command to remote instance\n"); return false; } else { - DMSG(0, "NormApp::OnCommand() sent \"%s\" to remote\n", buffer); + PLOG(PL_FATAL, "NormApp::OnCommand() sent \"%s\" to remote\n", buffer); } } @@ -406,7 +507,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) int debugLevel = atoi(val); if ((debugLevel < 0) || (debugLevel > 12)) { - DMSG(0, "NormApp::OnCommand(segment) invalid debug level!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(segment) invalid debug level!\n"); return false; } SetDebugLevel(debugLevel); @@ -423,17 +524,25 @@ bool NormApp::OnCommand(const char* cmd, const char* val) tracing = false; else { - DMSG(0, "NormApp::OnCommand(trace) invalid argument!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(trace) invalid argument!\n"); return false; } if (session) session->SetTrace(tracing); } + else if (!strncmp("precise", cmd, len)) + { + precise = true; // NormApp::dispatcher will run in "precision" mode + } + else if (!strncmp("boost", cmd, len)) + { + boost = true; // normApp will run w/ high process priority + } else if (!strncmp("txloss", cmd, len)) { double txLoss = atof(val); if (txLoss < 0) { - DMSG(0, "NormApp::OnCommand(txloss) invalid txRate!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(txloss) invalid txRate!\n"); return false; } tx_loss = txLoss; @@ -444,7 +553,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) double rxLoss = atof(val); if (rxLoss < 0) { - DMSG(0, "NormApp::OnCommand(rxloss) invalid txRate!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(rxloss) invalid txRate!\n"); return false; } rx_loss = rxLoss; @@ -456,7 +565,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) if (address) delete address; if (!(address = new char[len+1])) { - DMSG(0, "NormApp::OnCommand(address) allocation error:%s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(address) allocation error:%s\n", GetErrorString()); return false; } @@ -466,7 +575,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) { delete address; address = NULL; - DMSG(0, "NormApp::OnCommand(address) missing port number!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(address) missing port number!\n"); return false; } *ptr++ = '\0'; @@ -475,7 +584,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) { delete address; address = NULL; - DMSG(0, "NormApp::OnCommand(address) invalid port number!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(address) invalid port number!\n"); return false; } port = portNum; @@ -485,7 +594,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) int txPort = atoi(val); if ((txPort < 0) || (txPort > 65535)) { - DMSG(0, "NormApp::OnCommand(txport) invalid port number!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(txport) invalid port number!\n"); return false; } tx_port = (UINT16)txPort; @@ -496,14 +605,14 @@ bool NormApp::OnCommand(const char* cmd, const char* val) int ttlTemp = atoi(val); if ((ttlTemp < 0) || (ttlTemp > 255)) { - DMSG(0, "NormApp::OnCommand(ttl) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(ttl) invalid value!\n"); return false; } bool result = session ? session->SetTTL((UINT8)ttlTemp) : true; ttl = result ? ttlTemp : ttl; if (!result) { - DMSG(0, "NormApp::OnCommand(ttl) error setting socket ttl!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(ttl) error setting socket ttl!\n"); return false; } } @@ -516,14 +625,14 @@ bool NormApp::OnCommand(const char* cmd, const char* val) loopTemp = false; else { - DMSG(0, "NormApp::OnCommand(loopback) invalid argument!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(loopback) invalid argument!\n"); return false; } bool result = session ? session->SetLoopback(loopTemp) : true; loopback = result ? loopTemp : loopback; if (!result) { - DMSG(0, "NormApp::OnCommand(loopback) error setting socket loopback!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(loopback) error setting socket loopback!\n"); return false; } } @@ -532,7 +641,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) if (interface_name) delete[] interface_name; if (!(interface_name = new char[strlen(val)+1])) { - DMSG(0, "NormApp::OnCommand(interface) error allocating string: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(interface) error allocating string: %s\n", GetErrorString()); return false; } @@ -543,18 +652,18 @@ bool NormApp::OnCommand(const char* cmd, const char* val) double txRate = atof(val); if (txRate < 0) { - DMSG(0, "NormApp::OnCommand(rate) invalid txRate!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(rate) invalid txRate!\n"); return false; } tx_rate = txRate; if (session) session->SetTxRate(txRate); } - else if (!strncmp("rate", cmd, len)) + else if (!strncmp("limit", cmd, len)) { double rateMin, rateMax; if (2 != sscanf(val, "%lf:%lf", &rateMin, &rateMax)) { - DMSG(0, "NormApp::OnCommand(rate) invalid txRate limits!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(limit) invalid txRate limits!\n"); return false; } tx_rate_min = rateMin; @@ -573,11 +682,40 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else { - DMSG(0, "NormApp::OnCommand(cc) invalid option!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(cc) invalid option!\n"); return false; } if (session) session->SetCongestionControl(cc_enable); } + else if (!strncmp("ecn", cmd, len)) + { + // (TBD) change this to "off" "on" and "only" + if (!strcmp("on", val)) + { + ecn_mode = ECN_ON; + if (session) session->SetEcnSupport(true, false, tolerate_loss); + } + else if (!strcmp("off", val)) + { + ecn_mode = ECN_OFF; + if (session) session->SetEcnSupport(false, false, tolerate_loss); + } + else if (!strcmp("only", val)) + { + ecn_mode = ECN_ONLY; + if (session) session->SetEcnSupport(true, true, tolerate_loss); + } + else + { + PLOG(PL_FATAL, "NormApp::OnCommand(ecn) invalid option!\n"); + return false; + } + } + else if (!strncmp("tolerant", cmd, len)) + { + tolerate_loss = true; + if (session) session->SetEcnSupport(ECN_OFF != ecn_mode, ECN_ONLY == ecn_mode, true); + } else if (!strncmp("input", cmd, len)) { if (!strcmp(val, "STDIN")) @@ -586,7 +724,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else if (!(input = fopen(val, "rb"))) { - DMSG(0, "NormApp::OnCommand(input) fopen() error: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(input) fopen() error: %s\n", GetErrorString()); return false; } @@ -606,7 +744,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else if (!(output = fopen(val, "wb"))) { - DMSG(0, "NormApp::OnCommand(output) fopen() error: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(output) fopen() error: %s\n", GetErrorString()); return false; } @@ -614,6 +752,12 @@ bool NormApp::OnCommand(const char* cmd, const char* val) output_index = 0; output_msg_sync = true; } + else if (!strncmp("stest", cmd, len)) + { + msg_test = true; + msg_test_length = atoi(val); + input_messaging = false; + } else if (!strncmp("minput", cmd, len)) { if (!strcmp(val, "STDIN")) @@ -622,7 +766,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else if (!(input = fopen(val, "rb"))) { - DMSG(0, "NormApp::OnCommand(input) fopen() error: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(input) fopen() error: %s\n", GetErrorString()); return false; } @@ -642,7 +786,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else if (!(output = fopen(val, "wb"))) { - DMSG(0, "NormApp::OnCommand(output) fopen() error: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(output) fopen() error: %s\n", GetErrorString()); return false; } @@ -650,14 +794,38 @@ bool NormApp::OnCommand(const char* cmd, const char* val) output_msg_length = output_index = 0; output_msg_sync = false; } + else if (!strncmp("mtest", cmd, len)) + { + msg_test = true; + msg_test_length = atoi(val); + input_messaging = true; + } + else if (!strncmp("obuf", cmd, len)) + { + if (NULL != output_io_buffer) delete[] output_io_buffer; + output_io_bufsize = atoi(val); + if (NULL == (output_io_buffer = new char[output_io_bufsize])) + { + PLOG(PL_FATAL, "NormApp::OnCommand(obuf): error allocating buffer: %s\n", + GetErrorString()); + return false; + } + } else if (!strncmp("sendfile", cmd, len)) { if (!tx_file_list.Append(val)) { - DMSG(0, "NormApp::OnCommand(sendfile) Error appending \"%s\" " + PLOG(PL_FATAL, "NormApp::OnCommand(sendfile) Error appending \"%s\" " "to tx file list.\n", val); return false; } + // TBD - add code here to see if "session" exists (i.e. already running) + // and, if so, StartSender operation, if needed, and kick-off "OnIntervalTimeout()" + // process if needed to kick-start file transmission. To do this, we probably + // need to create a NormApp::StartSender() method that can be used here as well + // as for initial command-line sender startup, if applicable. + // (This will allow the "send" command to be invoked at run-time via the ProtoPipe + // remote control interface). } else if (!strncmp("interval", cmd, len)) { @@ -665,7 +833,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) tx_object_interval = -1.0; if (tx_object_interval < 0.0) { - DMSG(0, "NormApp::OnCommand(interval) Invalid tx object interval: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(interval) Invalid tx object interval: %s\n", val); tx_object_interval = 0.0; return false; @@ -681,12 +849,16 @@ bool NormApp::OnCommand(const char* cmd, const char* val) tx_repeat_interval = -1.0; if (tx_repeat_interval < 0.0) { - DMSG(0, "NormApp::OnCommand(rinterval) Invalid tx repeat interval: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(rinterval) Invalid tx repeat interval: %s\n", val); tx_repeat_interval = 0.0; return false; } - } + } + else if (!strncmp("requeue", cmd, len)) + { + tx_requeue = tx_requeue_count = atoi(val); + } else if (!strncmp("oneshot", cmd, len)) { tx_one_shot = true; @@ -708,7 +880,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) char* tempString = new char[length + 1]; if (NULL == tempString) { - DMSG(0, "NormApp::OnCommand(ackingNodes) error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormApp::OnCommand(ackingNodes) error: %s\n", GetErrorString()); return false; } if (NULL != acking_node_list) @@ -724,6 +896,14 @@ bool NormApp::OnCommand(const char* cmd, const char* val) acking_node_list = tempString; strcat(acking_node_list, val); if (NULL != session) return AddAckingNodes(acking_node_list); + } + else if (!strncmp("ackflush", cmd, len)) + { + acking_flushes = true; + } + else if (!strncmp("id", cmd, len)) + { + node_id = atoi(val); } else if (!strncmp("rxcachedir", cmd, len)) { @@ -735,7 +915,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) length += 1; if (!(rx_cache_path = new char[length])) { - DMSG(0, "NormApp::OnCommand(rxcachedir) alloc error: %s\n", + PLOG(PL_FATAL, "NormApp::OnCommand(rxcachedir) alloc error: %s\n", GetErrorString()); return false; } @@ -746,9 +926,9 @@ bool NormApp::OnCommand(const char* cmd, const char* val) else if (!strncmp("segment", cmd, len)) { int segmentSize = atoi(val); - if ((segmentSize < 0) || (segmentSize > 8000)) + if ((segmentSize < 0) || (segmentSize > 65535)) { - DMSG(0, "NormApp::OnCommand(segment) invalid segment size!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(segment) invalid segment size!\n"); return false; } segment_size = segmentSize; @@ -756,9 +936,9 @@ bool NormApp::OnCommand(const char* cmd, const char* val) else if (!strncmp("block", cmd, len)) { int blockSize = atoi(val); - if ((blockSize < 1) || (blockSize > 255)) + if ((blockSize < 1) || (blockSize > 65535)) { - DMSG(0, "NormApp::OnCommand(block) invalid block size!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(block) invalid block size!\n"); return false; } ndata = blockSize; @@ -766,9 +946,9 @@ bool NormApp::OnCommand(const char* cmd, const char* val) else if (!strncmp("parity", cmd, len)) { int numParity = atoi(val); - if ((numParity < 0) || (numParity > 254)) + if ((numParity < 0) || (numParity > 65534)) { - DMSG(0, "NormApp::OnCommand(parity) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(parity) invalid value!\n"); return false; } nparity = numParity; @@ -776,31 +956,31 @@ bool NormApp::OnCommand(const char* cmd, const char* val) else if (!strncmp("auto", cmd, len)) { int autoParity = atoi(val); - if ((autoParity < 0) || (autoParity > 254)) + if ((autoParity < 0) || (autoParity > 65534)) { - DMSG(0, "NormApp::OnCommand(auto) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(auto) invalid value!\n"); return false; } auto_parity = autoParity; - if (session) session->ServerSetAutoParity(autoParity); + if (session) session->SenderSetAutoParity(autoParity); } else if (!strncmp("extra", cmd, len)) { int extraParity = atoi(val); - if ((extraParity < 0) || (extraParity > 254)) + if ((extraParity < 0) || (extraParity > 65534)) { - DMSG(0, "NormApp::OnCommand(extra) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(extra) invalid value!\n"); return false; } extra_parity = extraParity; - if (session) session->ServerSetExtraParity(extraParity); + if (session) session->SenderSetExtraParity(extraParity); } else if (!strncmp("backoff", cmd, len)) { double backoffFactor = atof(val); if (backoffFactor < 0) { - DMSG(0, "NormApp::OnCommand(backoff) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(backoff) invalid value!\n"); return false; } backoff_factor = backoffFactor; @@ -811,31 +991,44 @@ bool NormApp::OnCommand(const char* cmd, const char* val) double grttEstimate = atof(val); if (grttEstimate < 0) { - DMSG(0, "NormApp::OnCommand(grtt) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(grtt) invalid value!\n"); return false; } grtt_estimate = grttEstimate; - if (session) session->ServerSetGrtt(grttEstimate); + if (session) session->SenderSetGrtt(grttEstimate); } else if (!strncmp("gsize", cmd, len)) { double groupSize = atof(val); if (groupSize < 0) { - DMSG(0, "NormApp::OnCommand(gsize) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(gsize) invalid value!\n"); return false; } group_size = groupSize; - if (session) session->ServerSetGroupSize(groupSize); + if (session) session->SenderSetGroupSize(groupSize); } else if (!strncmp("txbuffer", cmd, len)) { if (1 != sscanf(val, "%lu", &tx_buffer_size)) { - DMSG(0, "NormApp::OnCommand(txbuffer) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(txbuffer) invalid value!\n"); return false; } } + else if (!strncmp("txcachebounds", cmd, len)) + { + unsigned long countMin, countMax, sizeMax; + if (3 != sscanf(val, "%lu:%lu:%lu\n", &countMin, &countMax, &sizeMax)) + { + PLOG(PL_FATAL, "NormApp::OnCommand(txcachebounds) invalid value!\n"); + return false; + } + tx_cache_min = countMin; + tx_cache_max = countMax; + tx_cache_size = sizeMax; + if (session) session->SetTxCacheBounds(tx_cache_size, tx_cache_min, tx_cache_max); + } else if (!strncmp("txrobustfactor", cmd, len)) { tx_robust_factor = atoi(val); @@ -845,7 +1038,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) { if (1 != sscanf(val, "%lu", &rx_buffer_size)) { - DMSG(0, "NormApp::OnCommand(rxbuffer) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(rxbuffer) invalid value!\n"); return false; } } @@ -853,31 +1046,50 @@ bool NormApp::OnCommand(const char* cmd, const char* val) { if (1 != sscanf(val, "%u", &rx_sock_buffer_size)) { - DMSG(0, "NormApp::OnCommand(rxsockbuffer) invalid value!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(rxsockbuffer) invalid value!\n"); return false; } if (session && (rx_sock_buffer_size > 0)) session->SetRxSocketBuffer(rx_sock_buffer_size); } + else if (!strncmp("txsockbuffer", cmd, len)) + { + if (1 != sscanf(val, "%u", &tx_sock_buffer_size)) + { + PLOG(PL_FATAL, "NormApp::OnCommand(txsockbuffer) invalid value!\n"); + return false; + } + if (session && (tx_sock_buffer_size)) + session->SetTxSocketBuffer(tx_sock_buffer_size); + } else if (!strncmp("unicastNacks", cmd, len)) { unicast_nacks = true; - if (session) session->ClientSetUnicastNacks(true); + if (session) session->ReceiverSetUnicastNacks(true); } - else if (!strncmp("silentClient", cmd, len)) + else if (!strncmp("silentReceiver", cmd, len)) { - silent_client = true; + silent_receiver = true; if (session) { - session->ClientSetSilent(true); + session->ReceiverSetSilent(true); session->SndrSetEmcon(true); } } + else if (!strncmp("presetSender", cmd, len)) + { + preallocate_sender = true; + } else if (!strncmp("lowDelay", cmd, len)) { low_delay = true; if (session) session->RcvrSetMaxDelay(1); } + else if (!strncmp("realtime", cmd, len)) + { + realtime = true; + if (session) session->RcvrSetRealtime(true); + } else if (!strncmp("rxrobustfactor", cmd, len)) { rx_robust_factor = atoi(val); @@ -885,7 +1097,15 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else if (!strncmp("rxpersist", cmd, len)) { - rx_persistent = true; + if (!strcmp("on", val)) + rx_persistent = true; + else if (!strcmp("off", val)) + rx_persistent = false; + else + { + PLOG(PL_FATAL, "NormApp::OnCommand(rxpersist) invalid argument!\n"); + return false; + } } else if (!strncmp("saveAborts", cmd, len)) { @@ -907,7 +1127,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) msg_flush_mode = NormStreamObject::FLUSH_ACTIVE; else { - DMSG(0, "NormApp::OnCommand(flush) invalid msg flush mode!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(flush) invalid msg flush mode!\n"); return false; } if (tx_stream) tx_stream->SetFlushMode(msg_flush_mode); @@ -916,7 +1136,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) { if (!post_processor->SetCommand(val)) { - DMSG(0, "NormApp::OnCommand(processor) error!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(processor) error!\n"); return false; } } @@ -933,7 +1153,7 @@ bool NormApp::OnCommand(const char* cmd, const char* val) } else { - DMSG(0, "NormApp::OnCommand(instance) error opening control pipe\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(instance) error opening control pipe\n"); return false; } } @@ -981,13 +1201,13 @@ bool NormApp::ProcessCommands(int argc, const char*const* argv) switch (cmdType) { case CMD_INVALID: - DMSG(0, "NormApp::ProcessCommands() Invalid command:%s\n", + PLOG(PL_FATAL, "NormApp::ProcessCommands() Invalid command:%s\n", argv[i]); return false; case CMD_NOARG: if (!OnCommand(argv[i], NULL)) { - DMSG(0, "NormApp::ProcessCommands() OnCommand(%s) error\n", + PLOG(PL_FATAL, "NormApp::ProcessCommands() OnCommand(%s) error\n", argv[i]); return false; } @@ -996,7 +1216,7 @@ bool NormApp::ProcessCommands(int argc, const char*const* argv) case CMD_ARG: if (!OnCommand(argv[i], argv[i+1])) { - DMSG(0, "NormApp::ProcessCommands() OnCommand(%s, %s) error\n", + PLOG(PL_FATAL, "NormApp::ProcessCommands() OnCommand(%s, %s) error\n", argv[i], argv[i+1]); return false; } @@ -1019,7 +1239,7 @@ bool NormApp::AddAckingNodes(const char* ackingNodeList) size_t len = (NULL != end) ? (end - ptr) : strlen(ptr); if (len >= 256) { - DMSG(0, "NormApp::AddAckingNodes() error: bad acking node list\n"); + PLOG(PL_FATAL, "NormApp::AddAckingNodes() error: bad acking node list\n"); return false; } char nodeString[256]; @@ -1039,7 +1259,7 @@ bool NormApp::AddAckingNodes(const char* ackingNodeList) { if (1 != sscanf(nodeString, "%lu", &nodeId)) { - DMSG(0, "NormApp::AddAckingNodes() error: bad acking node id: %s\n", nodeString); + PLOG(PL_FATAL, "NormApp::AddAckingNodes() error: bad acking node id: %s\n", nodeString); return false; } } @@ -1049,14 +1269,14 @@ bool NormApp::AddAckingNodes(const char* ackingNodeList) ProtoAddress nodeAddr; if (!nodeAddr.ResolveFromString(nodeString)) { - DMSG(0, "NormApp::AddAckingNodes() error: bad acking node id: %s\n", nodeString); + PLOG(PL_FATAL, "NormApp::AddAckingNodes() error: bad acking node id: %s\n", nodeString); return false; } nodeId = nodeAddr.EndIdentifier(); } - if (!session->ServerAddAckingNode((UINT32)nodeId)) + if (!session->SenderAddAckingNode((UINT32)nodeId)) { - DMSG(0, "NormApp::AddAckingNodes() error: couldn't add acking node \n"); + PLOG(PL_FATAL, "NormApp::AddAckingNodes() error: couldn't add acking node \n"); return false; } ptr = (NULL != end) ? ++end : NULL; @@ -1065,7 +1285,7 @@ bool NormApp::AddAckingNodes(const char* ackingNodeList) } else { - DMSG(0, "NormApp::AddAckingNodes() error: no session instantiated\n"); + PLOG(PL_FATAL, "NormApp::AddAckingNodes() error: no session instantiated\n"); return false; } } // end NormApp::AddAckingNodes() @@ -1086,7 +1306,7 @@ void NormApp::OnInputReady() bool endOfStream = false; // write to the stream while input is available _and_ // the stream has buffer space for input - while (input) + while (input || msg_test) { bool inputStarved = false; @@ -1120,68 +1340,93 @@ void NormApp::OnInputReady() { readLength = segment_size; } - - // Read from "input" into our "input_buffer" - size_t result = fread(input_buffer+input_index, sizeof(char), readLength, input); - if (result > 0) + + if (msg_test) { + // we're just sending our test message + int hdrOffset; if (input_messaging) { - if (input_msg_length) - { - input_length = input_index + result; - input_index = 0; - } - else - { - input_length = 0; - input_index += (unsigned int)result; - } + input_msg_length = msg_test_length; + UINT16 temp16 = ntohs(msg_test_length); + memcpy(input_buffer, &temp16, 2); + hdrOffset = 2; } else { - input_length = input_index + result; - input_index = 0; - } + hdrOffset = 0; + } + int hdrLen = sprintf(input_buffer + hdrOffset, "NORM Test Msg %08u ", msg_test_seq++); + char testChar = 'a' + msg_test_seq % 26; + memset(input_buffer + hdrLen + hdrOffset, testChar, msg_test_length - (hdrLen + hdrOffset)); + input_buffer[msg_test_length - 1] = '\n'; + input_length = msg_test_length; + input_index = 0; } else { - if (feof(input)) + // Read from "input" into our "input_buffer" + size_t result = fread(input_buffer+input_index, sizeof(char), readLength, input); + if (result > 0) { - DMSG(0, "norm: input end-of-file.\n"); - if (input_active) + if (input_messaging) { -#ifdef WIN32 - ASSERT(0); // no Win32 support for stream i/o yet -#else - dispatcher.RemoveGenericInput(fileno(input)); -#endif // if/else WIN32/UNIX - input_active = false; + if (input_msg_length) + { + input_length = input_index + result; + input_index = 0; + } + else + { + input_length = 0; + input_index += (unsigned int)result; + } } - if (stdin != input) fclose(input); - input = NULL; - endOfStream = true; - tx_stream->SetFlushMode(NormStreamObject::FLUSH_ACTIVE); + else + { + input_length = input_index + result; + input_index = 0; + } } - else if (ferror(input)) + else { -#ifndef WIN32 // no Win32 stream support yet - switch (errno) + if (feof(input)) { - case EINTR: - continue; - case EAGAIN: - // Input not ready, will get notification when ready - inputStarved = true; - break; - default: - DMSG(0, "norm: input error:%s\n", GetErrorString()); - break; + PLOG(PL_FATAL, "norm: input end-of-file.\n"); + if (input_active) + { + #ifdef WIN32 + ASSERT(0); // no Win32 support for stream i/o yet + #else + dispatcher.RemoveGenericInput(fileno(input)); + #endif // if/else WIN32/UNIX + input_active = false; + } + if (stdin != input) fclose(input); + input = NULL; + endOfStream = true; + tx_stream->SetFlushMode(NormStreamObject::FLUSH_ACTIVE); } -#endif // !WIN32 - clearerr(input); - } - } + else if (ferror(input)) + { + #ifndef WIN32 // no Win32 stream support yet + switch (errno) + { + case EINTR: + continue; + case EAGAIN: + // Input not ready, will get notification when ready + inputStarved = true; + break; + default: + PLOG(PL_ERROR, "norm: input error:%s\n", GetErrorString()); + break; + } + #endif // !WIN32 + clearerr(input); + } + } + } // end if/else msg_test } // end if (0 == input_length) unsigned int writeLength = input_length;// ? input_length - input_index : 0; @@ -1238,7 +1483,7 @@ void NormApp::OnInputReady() #endif // if/else WIN32/UNIX input_active = true; else - DMSG(0, "NormApp::OnInputReady() error adding input notification!\n"); + PLOG(PL_ERROR, "NormApp::OnInputReady() error adding input notification!\n"); } break; } @@ -1249,30 +1494,30 @@ void NormApp::OnInputReady() void NormApp::Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* server, + class NormSenderNode* sender, class NormObject* object) { switch (event) { case TX_QUEUE_VACANCY: - DMSG(6, "NormApp::Notify(TX_QUEUE_VACANCY) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(TX_QUEUE_VACANCY) ...\n"); if (NULL != object) { - if (input && (object == tx_stream)) + if ((msg_test || input) && (object == tx_stream)) OnInputReady(); } break; case TX_QUEUE_EMPTY: // Write to stream as needed - DMSG(6, "NormApp::Notify(TX_QUEUE_EMPTY) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(TX_QUEUE_EMPTY) ...\n"); if (NULL != object) { - if (input && (object == tx_stream)) + if ((msg_test || input) && (object == tx_stream)) OnInputReady(); } else { - // Can queue a new object for transmission + // Can queue a new object for transmission if (interval_timer.IsActive()) interval_timer.Deactivate(); if (interval_timer.GetInterval() > 0.0) ActivateTimer(interval_timer); @@ -1282,50 +1527,50 @@ void NormApp::Notify(NormController::Event event, break; case TX_OBJECT_SENT: - DMSG(4, "NormApp::Notify(TX_OBJECT_SENT) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(TX_OBJECT_SENT) ...\n"); break; case TX_FLUSH_COMPLETED: - DMSG(4, "NormApp::Notify(TX_FLUSH_COMPLETED) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(TX_FLUSH_COMPLETED) ...\n"); if (tx_one_shot) { - DMSG(0, "norm: transmit flushing completed, exiting.\n"); + PLOG(PL_FATAL, "norm: transmit flushing completed, exiting.\n"); Stop(); } break; case TX_WATERMARK_COMPLETED: { - DMSG(4, "NormApp::Notify(TX_WATERMARK_COMPLETED) ...\n"); - //NormSession::AckingStatus status = session->ServerGetAckingStatus(NORM_NODE_ANY); + PLOG(PL_DEBUG, "NormApp::Notify(TX_WATERMARK_COMPLETED) ...\n"); + //NormSession::AckingStatus status = session->SenderGetAckingStatus(NORM_NODE_ANY); watermark_pending = false; // enable new watermark to be set if (tx_ack_shot) { - DMSG(0, "norm: transmit flushing completed, exiting.\n"); + PLOG(PL_ERROR, "norm: transmit flushing completed, exiting.\n"); Stop(); } break; } case REMOTE_SENDER_ACTIVE: - DMSG(4, "NormApp::Notify(REMOTE_SENDER_ACTIVE) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(REMOTE_SENDER_ACTIVE) ...\n"); break; case REMOTE_SENDER_INACTIVE: - DMSG(4, "NormApp::Notify(REMOTE_SENDER_INACTIVE) ...\n"); - if (!rx_persistent) server->FreeBuffers(); + PLOG(PL_DEBUG, "NormApp::Notify(REMOTE_SENDER_INACTIVE) ...\n"); + if (!rx_persistent) sender->FreeBuffers(); break; case RX_OBJECT_NEW: { - DMSG(4, "NormApp::Notify(RX_OBJECT_NEW) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(RX_OBJECT_NEW) ...\n"); struct timeval currentTime; ProtoSystemTime(currentTime); struct tm* timePtr = gmtime((time_t*)¤tTime.tv_sec); - DMSG(3, "%02d:%02d:%02d.%06lu start rx object>%hu sender>%lu\n", + PLOG(PL_INFO, "%02d:%02d:%02d.%06lu start rx object>%hu sender>%lu\n", timePtr->tm_hour, timePtr->tm_min, timePtr->tm_sec, - (unsigned long)currentTime.tv_usec, (UINT16)object->GetId(), server->GetId()); + (unsigned long)currentTime.tv_usec, (UINT16)object->GetId(), sender->GetId()); // It's up to the app to "accept" the object switch (object->GetType()) @@ -1339,7 +1584,7 @@ void NormApp::Notify(NormController::Event event, // object Size() has recommended buffering size NormObjectSize size; - if (silent_client) + if (silent_receiver) size = rx_buffer_size; else size = (UINT32)object->GetSize().LSB(); @@ -1350,7 +1595,7 @@ void NormApp::Notify(NormController::Event event, } else { - DMSG(0, "NormApp::Notify(RX_OBJECT_NEW) stream object accept error!\n"); + PLOG(PL_ERROR, "NormApp::Notify(RX_OBJECT_NEW) stream object accept error!\n"); } } break; @@ -1403,22 +1648,22 @@ void NormApp::Notify(NormController::Event event, else #endif // if/else WIN32 { - DMSG(0, "NormApp::Notify(RX_OBJECT_NEW) Warning: mkstemp() error: %s\n", + PLOG(PL_ERROR, "NormApp::Notify(RX_OBJECT_NEW) Warning: mkstemp() error: %s\n", GetErrorString()); } if (!static_cast(object)->Accept(fileName)) { - DMSG(0, "NormApp::Notify(RX_OBJECT_NEW) file object accept error!\n"); + PLOG(PL_ERROR, "NormApp::Notify(RX_OBJECT_NEW) file object accept error!\n"); } } else { - DMSG(0, "NormApp::Notify(RX_OBJECT_NEW) no rx cache for file\n"); + PLOG(PL_ERROR, "NormApp::Notify(RX_OBJECT_NEW) no rx cache for file\n"); } } break; case NormObject::DATA: - DMSG(0, "NormApp::Notify() FILE/DATA objects not _yet_ supported...\n"); + PLOG(PL_ERROR, "NormApp::Notify() FILE/DATA objects not _yet_ supported...\n"); break; case NormObject::NONE: @@ -1428,7 +1673,7 @@ void NormApp::Notify(NormController::Event event, } case RX_OBJECT_INFO: - DMSG(4, "NormApp::Notify(RX_OBJECT_INFO) ...\n"); + PLOG(PL_DEBUG, "NormApp::Notify(RX_OBJECT_INFO) ...\n"); switch(object->GetType()) { case NormObject::FILE: @@ -1456,7 +1701,7 @@ void NormApp::Notify(NormController::Event event, if (!(static_cast(object)->Rename(fileName))) { - DMSG(0, "NormApp::Notify() Error renaming rx file: %s\n", + PLOG(PL_ERROR, "NormApp::Notify() Error renaming rx file: %s\n", fileName); } break; @@ -1469,7 +1714,7 @@ void NormApp::Notify(NormController::Event event, break; case RX_OBJECT_UPDATED: - DMSG(6, "NormApp::Notify(RX_OBJECT_UPDATED) ...\n"); + PLOG(PL_TRACE, "NormApp::Notify(RX_OBJECT_UPDATED) ...\n"); switch (object->GetType()) { case NormObject::FILE: @@ -1482,11 +1727,11 @@ void NormApp::Notify(NormController::Event event, { if (object != rx_stream) { - DMSG(0, "NormApp::Notify(RX_OBJECT_UPDATED) update for invalid stream\n"); + PLOG(PL_ERROR, "NormApp::Notify(RX_OBJECT_UPDATED) update for invalid stream\n"); break; } // Read the stream when it's updated - ASSERT(output); + ASSERT(NULL != output); bool reading = true; bool findMsgSync; while (reading) @@ -1525,7 +1770,7 @@ void NormApp::Notify(NormController::Event event, if (output_messaging) { if (output_msg_sync) - DMSG(0, "NormApp::Notify() detected broken stream ...\n"); + PLOG(PL_ERROR, "NormApp::Notify() detected broken stream ...\n"); output_msg_length = output_index = 0; output_msg_sync = false; if (findMsgSync) @@ -1586,7 +1831,7 @@ void NormApp::Notify(NormController::Event event, else #endif // !WIN32 { - DMSG(0, "norm: output error:%s\n", GetErrorString()); + PLOG(PL_ERROR, "norm: output error:%s\n", GetErrorString()); clearerr(output); break; } @@ -1604,7 +1849,7 @@ void NormApp::Notify(NormController::Event event, } case NormObject::DATA: - DMSG(0, "NormApp::Notify() DATA objects not _yet_ supported...\n"); + PLOG(PL_ERROR, "NormApp::Notify() DATA objects not _yet_ supported...\n"); break; case NormObject::NONE: break; @@ -1616,13 +1861,20 @@ void NormApp::Notify(NormController::Event event, // (TBD) if we're not archiving files we should // manage our cache, deleting the cache // on shutdown ... - DMSG(4, "NormApp::Notify(RX_OBJECT_COMPLETED) ...\n"); - struct timeval currentTime; - ProtoSystemTime(currentTime); - struct tm* timePtr = gmtime((time_t*)¤tTime.tv_sec); - DMSG(3, "%02d:%02d:%02d.%06lu completed rx object>%hu sender>%lu\n", - timePtr->tm_hour, timePtr->tm_min, timePtr->tm_sec, - (unsigned long)currentTime.tv_usec, (UINT16)object->GetId(), server->GetId()); + PLOG(PL_DEBUG, "NormApp::Notify(RX_OBJECT_COMPLETED) ...\n"); + // We had to split the debug output line here into two because of some + // weirdness with the underlying PLOG() code and too many arguments? + if (GetDebugLevel() >= PL_INFO) + { + struct timeval currentTime; + ProtoSystemTime(currentTime); + struct tm* timePtr = gmtime((time_t*)¤tTime.tv_sec); + + PLOG(PL_INFO, "%02d:%02d:%02d.%06u completed rx object>%hu ", + (int)timePtr->tm_hour, (int)timePtr->tm_min, (int)timePtr->tm_sec, (unsigned int)currentTime.tv_usec, + (UINT16)object->GetId()); + TRACE("sender>%lu\n", sender->GetId()); + } switch(object->GetType()) { case NormObject::FILE: @@ -1633,7 +1885,7 @@ void NormApp::Notify(NormController::Event event, { if (!post_processor->ProcessFile(filePath)) { - DMSG(0, "norm: post processing error\n"); + PLOG(PL_ERROR, "norm: post processing error\n"); } } break; @@ -1653,13 +1905,13 @@ void NormApp::Notify(NormController::Event event, case RX_OBJECT_ABORTED: { - DMSG(0, "NormApp::Notify(RX_OBJECT_ABORTED) ...\n"); + PLOG(PL_FATAL, "NormApp::Notify(RX_OBJECT_ABORTED) ...\n"); struct timeval currentTime; ProtoSystemTime(currentTime); struct tm* timePtr = gmtime((time_t*)¤tTime.tv_sec); - DMSG(3, "%02d:%02d:%02d.%06lu aborted rx object>%hu sender>%lu\n", + PLOG(PL_INFO, "%02d:%02d:%02d.%06lu aborted rx object>%hu sender>%lu\n", timePtr->tm_hour, timePtr->tm_min, timePtr->tm_sec, - (unsigned long)currentTime.tv_usec, (UINT16)object->GetId(), server->GetId()); + (unsigned long)currentTime.tv_usec, (UINT16)object->GetId(), sender->GetId()); switch(object->GetType()) { case NormObject::FILE: @@ -1673,7 +1925,7 @@ void NormApp::Notify(NormController::Event event, { if (!post_processor->ProcessFile(filePath)) { - DMSG(0, "norm: post processing error\n"); + PLOG(PL_ERROR, "norm: post processing error\n"); } } } @@ -1691,7 +1943,7 @@ void NormApp::Notify(NormController::Event event, default: { - DMSG(4, "NormApp::Notify() unhandled event: %d\n", event); + PLOG(PL_DEBUG, "NormApp::Notify() unhandled event: %d\n", event); break; } } // end switch(event) @@ -1704,6 +1956,7 @@ bool NormApp::OnIntervalTimeout(ProtoTimer& /*theTimer*/) if (tx_file_list.GetNextFile(fileName)) { tx_repeat_clear = true; + // 1) Build up the full path name of the file char pathName[PATH_MAX]; tx_file_list.GetCurrentBasePath(pathName); size_t len = strlen(pathName); @@ -1724,24 +1977,60 @@ bool NormApp::OnIntervalTimeout(ProtoTimer& /*theTimer*/) char temp[PATH_MAX]; strncpy(temp, fileNameInfo, len); temp[len] = '\0'; - NormFileObject* obj; - if (NULL == (obj = session->QueueTxFile(fileName, fileNameInfo, (UINT16)len))) + + FileCacheItem* fileCacheItem = NULL; + NormFileObject* obj = NULL; + if (0 != tx_requeue) { - DMSG(0, "NormApp::OnIntervalTimeout() Error queuing tx file: %s\n", - fileName); - // Wait a second, then try the next file in the list - if (interval_timer.IsActive()) interval_timer.Deactivate(); - interval_timer.SetInterval(1.0); - ActivateTimer(interval_timer); - return false; + // Is this file already in our "requeue" tx_file_cache? + if (NULL != (fileCacheItem = static_cast(tx_file_cache.Find(fileName, PATH_MAX << 3)))) + { + // Try to find and requeue cached tx object + obj = static_cast(session->SenderFindTxObject(fileCacheItem->GetObjectId())); + bool requeueSuccess = false; + if (NULL != obj) requeueSuccess = session->RequeueTxObject(obj); + if (!requeueSuccess) + PLOG(PL_ERROR, "norm warning: requeue attempt exceeded configured tx cache bounds!\n"); + } } + if (NULL == obj) + { + // It's a new object (or requeue failure) + if (NULL == (obj = session->QueueTxFile(fileName, fileNameInfo, (UINT16)len))) + { + PLOG(PL_ERROR, "NormApp::OnIntervalTimeout() Error queuing tx file: %s\n", + fileName); + // Wait a second, then try the next file in the list + if (interval_timer.IsActive()) interval_timer.Deactivate(); + interval_timer.SetInterval(1.0); + ActivateTimer(interval_timer); + return false; + } + if (0 != tx_requeue) + { + if (NULL == fileCacheItem) + { + // Cache for "requeue" if applicable + // It's not in the cache, so add it ... + if (NULL == (fileCacheItem = new FileCacheItem(fileName))) + { + PLOG(PL_ERROR, "NormApp::OnIntervalTimeout(): new FileCachItem error: %s\n", + GetErrorString()); + } + tx_file_cache.Insert(*fileCacheItem); + } + if (NULL != fileCacheItem) + fileCacheItem->SetObjectId(obj->GetId()); + } + } + tx_file_queued = true; struct timeval currentTime; ProtoSystemTime(currentTime); struct tm* timePtr = gmtime((time_t*)¤tTime.tv_sec); - DMSG(3, "%02d:%02d:%02d.%06lu enqueued tx object>%hu sender>%lu\n", + PLOG(PL_INFO, "%02d:%02d:%02d.%06lu enqueued tx object>%hu sender>%lu\n", timePtr->tm_hour, timePtr->tm_min, timePtr->tm_sec, (unsigned long)currentTime.tv_usec, (UINT16)obj->GetId(), session->LocalNodeId()); @@ -1751,18 +2040,27 @@ bool NormApp::OnIntervalTimeout(ProtoTimer& /*theTimer*/) tx_last_segment_id = obj->GetBlockSize(tx_last_block_id) - 1; if (!watermark_pending && (NULL != acking_node_list) && !tx_ack_shot) { - session->ServerSetWatermark(tx_last_object_id, + session->SenderSetWatermark(tx_last_object_id, tx_last_block_id, - tx_last_segment_id); + tx_last_segment_id, + acking_flushes); watermark_pending = true; // only allow one pending watermark at a time } //DMSG(0, "norm: File \"%s\" queued for transmission.\n", fileName); interval_timer.SetInterval(tx_object_interval); } - else if (tx_repeat_count) + else if (0 != tx_requeue_count) + { + if (tx_requeue_count > 0) tx_requeue_count--; + tx_file_list.ResetIterator(); + return OnIntervalTimeout(interval_timer); + } + else if (0 != tx_repeat_count) { // (TBD) When repeating, remove previous instance from tx queue??? if (tx_repeat_count > 0) tx_repeat_count--; + tx_file_cache.Destroy(); + tx_requeue_count = tx_requeue; tx_file_list.ResetIterator(); if (tx_repeat_interval > tx_object_interval) { @@ -1791,24 +2089,21 @@ bool NormApp::OnIntervalTimeout(ProtoTimer& /*theTimer*/) else { tx_repeat_clear = true; - DMSG(0, "norm: End of tx file list reached.\n"); + PLOG(PL_FATAL, "norm: End of tx file list reached.\n"); if (tx_ack_shot && !watermark_pending && tx_file_queued) { - session->ServerSetWatermark(tx_last_object_id, + session->SenderSetWatermark(tx_last_object_id, tx_last_block_id, - tx_last_segment_id); + tx_last_segment_id, + acking_flushes); watermark_pending = true; // only allow one pending watermark at a time } } return true; } // end NormApp::OnIntervalTimeout() -#include "normSimAgent.h" - bool NormApp::OnStartup(int argc, const char*const* argv) { - TRACE("sizeof(NormSimAgent) = %d\n", sizeof(NormSimAgent)); - if (argc < 3) { ShowHelp(); @@ -1817,13 +2112,13 @@ bool NormApp::OnStartup(int argc, const char*const* argv) if (!(post_processor = NormPostProcessor::Create())) { - DMSG(0, "NormApp::OnStartup() error creating post processor\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() error creating post processor\n"); return false; } if (!ProcessCommands(argc, argv)) { - DMSG(0, "NormApp::OnStartup() error processing command-line commands\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() error processing command-line commands\n"); return false; } @@ -1838,18 +2133,26 @@ bool NormApp::OnStartup(int argc, const char*const* argv) // Validate our application settings if (!address) { - DMSG(0, "NormApp::OnStartup() Error! no session address given.\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() Error! no session address given.\n"); return false; } - if (!input && !output && tx_file_list.IsEmpty() && !rx_cache_path) + if (!input && !output && tx_file_list.IsEmpty() && !rx_cache_path && !msg_test) { - DMSG(0, "NormApp::OnStartup() Error! no \"input\", \"output\", " - "\"sendfile\", or \"rxcache\" given.\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() Error! no \"input\", \"output\", " + "\"sendfile\", \"stest\", \"mtest\", or \"rxcache\" given.\n"); return false; } + if ((NULL != output) && (NULL != output_io_buffer)) + { + if (0 != setvbuf(output, output_io_buffer, _IOFBF, output_io_bufsize)) + { + PLOG(PL_ERROR, "NormApp::OnStartup() setvbuf() error: %s\n", GetErrorString()); + } + } + // Create a new session on multicast group/port - session = session_mgr.NewSession(address, port); + session = session_mgr.NewSession(address, port, node_id); if (session) { // Common session parameters @@ -1860,74 +2163,98 @@ bool NormApp::OnStartup(int argc, const char*const* argv) session->SetTxLoss(tx_loss); session->SetRxLoss(rx_loss); session->SetTTL(ttl); - session->SetLoopback(loopback); + session->SetLoopback(loopback); + if (NULL != interface_name) + session->SetMulticastInterface(interface_name); - if (input || !tx_file_list.IsEmpty()) + session->SetEcnSupport(ECN_OFF != ecn_mode, ECN_ONLY == ecn_mode, tolerate_loss); + + if (msg_test || input || !tx_file_list.IsEmpty()) { NormObjectId baseId = (unsigned short)(rand() * (65535.0/ (double)RAND_MAX)); - session->ServerSetBaseObjectId(baseId); + session->SenderSetBaseObjectId(baseId); session->SetCongestionControl(cc_enable); session->SetBackoffFactor(backoff_factor); - session->ServerSetGrtt(grtt_estimate); - session->ServerSetGroupSize(group_size); + //session->SenderSetGrtt(grtt_estimate); + session->SenderSetGroupSize(group_size); session->SetTxRobustFactor(tx_robust_factor); + session->SetTxCacheBounds(tx_cache_size, tx_cache_min, tx_cache_max); if (!AddAckingNodes(acking_node_list)) { - DMSG(0, "NormApp::OnStartup() error: bad acking node list\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() error: bad acking node list\n"); session_mgr.Destroy(); return false; } - // redundant info transmission if we have silent clients - if (silent_client) session->SndrSetEmcon(true); - // We also use the baseId as our server's "instance id" for illustrative purposes + // redundant info transmission if we have silent receivers + if (silent_receiver) session->SndrSetEmcon(true); + // We also use the baseId as our sender's "instance id" for illustrative purposes UINT16 instanceId = baseId; - if (!session->StartServer(instanceId, tx_buffer_size, segment_size, ndata, nparity, interface_name)) + if (!session->StartSender(instanceId, tx_buffer_size, segment_size, ndata, nparity)) { - DMSG(0, "NormApp::OnStartup() start server error!\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() start sender error!\n"); session_mgr.Destroy(); return false; - } - session->ServerSetAutoParity(auto_parity); - session->ServerSetExtraParity(extra_parity); - if (input) + } + if (tx_sock_buffer_size > 0) + session->SetTxSocketBuffer(tx_sock_buffer_size); + session->SenderSetAutoParity(auto_parity); + session->SenderSetExtraParity(extra_parity); + if (input || msg_test) { // Open a stream object to write to (QueueTxStream(stream bufferSize)) tx_stream = session->QueueTxStream(tx_buffer_size); if (!tx_stream) { - DMSG(0, "NormApp::OnStartup() queue tx stream error!\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() queue tx stream error!\n"); session_mgr.Destroy(); return false; } tx_stream->SetFlushMode(msg_flush_mode); tx_stream->SetPushMode(push_mode); } + else + { + OnIntervalTimeout(interval_timer); + } } if (output || rx_cache_path) { - // StartClient(bufferMax (per-sender)) - session->ClientSetUnicastNacks(unicast_nacks); - session->ClientSetSilent(silent_client); + // StartReceiver(bufferMax (per-sender)) + session->ReceiverSetUnicastNacks(unicast_nacks); + session->ReceiverSetSilent(silent_receiver); + if (preallocate_sender) + { + if (!session->PreallocateRemoteSender(segment_size, ndata, nparity)) + { + PLOG(PL_FATAL, "NormApp::OnStartup() remote sender preallocation error!\n"); + session_mgr.Destroy(); + return false; + } + } if (low_delay) session->RcvrSetMaxDelay(1); else - session->RcvrSetMaxDelay(-1); + session->RcvrSetMaxDelay(-1); + if (realtime) + session->RcvrSetRealtime(true); session->SetRxRobustFactor(rx_robust_factor); - if (!session->StartClient(rx_buffer_size, interface_name)) + if (!session->StartReceiver(rx_buffer_size)) { - DMSG(0, "NormApp::OnStartup() start client error!\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() start receiver error!\n"); session_mgr.Destroy(); return false; } if (rx_sock_buffer_size > 0) session->SetRxSocketBuffer(rx_sock_buffer_size); } + if (precise) dispatcher.SetPreciseTiming(true); + if (boost) dispatcher.SetPriorityBoost(true); return true; } else { - DMSG(0, "NormApp::OnStartup() new session error!\n"); + PLOG(PL_FATAL, "NormApp::OnStartup() new session error!\n"); return false; } } // end NormApp::OnStartup() diff --git a/src/common/normEncoder.cpp b/src/common/normEncoder.cpp new file mode 100644 index 0000000..57495d0 --- /dev/null +++ b/src/common/normEncoder.cpp @@ -0,0 +1,54 @@ +/********************************************************************* + * + * AUTHORIZATION TO USE AND DISTRIBUTE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: + * + * (1) source code distributions retain this paragraph in its entirety, + * + * (2) distributions including binary code include this paragraph in + * its entirety in the documentation or other materials provided + * with the distribution, and + * + * (3) all advertising materials mentioning features or use of this + * software display the following acknowledgment: + * + * "This product includes software written and developed + * by Brian Adamson and Joe Macker of the Naval Research + * Laboratory (NRL)." + * + * The name of NRL, the name(s) of NRL employee(s), or any entity + * of the United States Government may not be used to endorse or + * promote products derived from this software, nor does the + * inclusion of the NRL written and developed software directly or + * indirectly suggest NRL or United States Government endorsement + * of this product. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + ********************************************************************/ + + +#include +#include + +#include "normEncoder.h" +#include "galois.h" // for Galois math routines + +#ifdef SIMULATE +#include "normMessage.h" +#endif // SIMULATE + +#include +#include "protoDefs.h" // for struct timeval +#define DIFF_T(a,b) (1+ 1000000*(a.tv_sec - b.tv_sec) + (a.tv_usec - b.tv_usec) ) + +NormEncoder::~NormEncoder() +{ +} + +NormDecoder::~NormDecoder() +{ +} diff --git a/common/normEncoder.cpp b/src/common/normEncoderMDP.cpp similarity index 78% rename from common/normEncoder.cpp rename to src/common/normEncoderMDP.cpp index 01d3b9a..9eb434a 100644 --- a/common/normEncoder.cpp +++ b/src/common/normEncoderMDP.cpp @@ -34,38 +34,26 @@ #include #include -#include "normEncoder.h" +#include "normEncoderMDP.h" #include "galois.h" // for Galois math routines #ifdef SIMULATE #include "normMessage.h" #endif // SIMULATE -#include -#include "protoDefs.h" // for struct timeval -#define DIFF_T(a,b) (1+ 1000000*(a.tv_sec - b.tv_sec) + (a.tv_usec - b.tv_usec) ) - -NormEncoder::~NormEncoder() -{ -} - -NormDecoder::~NormDecoder() -{ -} - -NormEncoderRS8a::NormEncoderRS8a() +NormEncoderMDP::NormEncoderMDP() : npar(0), vector_size(0), gen_poly(NULL), scratch(NULL) { -} // end NormEncoderRS8a::NormEncoderRS8a() +} // end NormEncoderMDP::NormEncoderMDP() -NormEncoderRS8a::~NormEncoderRS8a() +NormEncoderMDP::~NormEncoderMDP() { if (gen_poly) Destroy(); } -bool NormEncoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) +bool NormEncoderMDP::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) { // Debugging assertions ASSERT((numData + numParity) <= 255); @@ -74,29 +62,29 @@ bool NormEncoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 npar = numParity; #ifdef SIMULATE - vecSizeMax = MIN(SIM_PAYLOAD_MAX+1, vecSizeMax); -#endif // SIMUATE + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); +#endif // SIMULATE vector_size = vecSizeMax; // Create gen_poly polynomial if(!CreateGeneratorPolynomial()) { - DMSG(0, "NormEncoderRS8a: Error creating gen_poly polynomial!\n"); + PLOG(PL_FATAL, "NormEncoderMDP: Error creating gen_poly polynomial!\n"); return false; } // Allocate scratch space for encoding if(!(scratch = new unsigned char[vecSizeMax])) { - DMSG(0, "NormEncoderRS8a: Error allocating memory for encoder scratch space: %s\n", + PLOG(PL_FATAL, "NormEncoderMDP: Error allocating memory for encoder scratch space: %s\n", GetErrorString()); Destroy(); return false; } return true; -} // end NormEncoderRS8a::Init() +} // end NormEncoderMDP::Init() // Free memory allocated for encoder state (Encoder must be re-inited before use) -void NormEncoderRS8a::Destroy() +void NormEncoderMDP::Destroy() { if(NULL != scratch) { @@ -108,10 +96,10 @@ void NormEncoderRS8a::Destroy() delete[] gen_poly; gen_poly = NULL; } -} // end NormEncoderRS8a::Destroy() +} // end NormEncoderMDP::Destroy() -bool NormEncoderRS8a::CreateGeneratorPolynomial() +bool NormEncoderMDP::CreateGeneratorPolynomial() { unsigned char *tp, *tp1, *tp2; int degree = 2*npar; @@ -119,14 +107,14 @@ bool NormEncoderRS8a::CreateGeneratorPolynomial() if(!(gen_poly = new unsigned char[npar+1])) { - DMSG(0, "NormEncoderRS8a: Error allocating memory for gen_poly polynomial: %s\n", + PLOG(PL_FATAL, "NormEncoderMDP: Error allocating memory for gen_poly polynomial: %s\n", GetErrorString()); return false; } /* Allocate memory for temporary polynomial arrays */ if(!(tp = new unsigned char[2*degree])) { - DMSG(0, "NormEncoderRS8a: Error allocating memory while computing gen_poly: %s\n", + PLOG(PL_FATAL, "NormEncoderMDP: Error allocating memory while computing gen_poly: %s\n", GetErrorString()); delete[] gen_poly; return false; @@ -135,7 +123,7 @@ bool NormEncoderRS8a::CreateGeneratorPolynomial() { delete[] tp; delete[] gen_poly; - DMSG(0, "NormEncoderRS8a: Error allocating memory while computing gen_poly: %s\n", + PLOG(PL_FATAL, "NormEncoderMDP: Error allocating memory while computing gen_poly: %s\n", GetErrorString()); return false; } @@ -144,7 +132,7 @@ bool NormEncoderRS8a::CreateGeneratorPolynomial() delete[] tp1; delete[] tp; delete[] gen_poly; - DMSG(0, "NormEncoderRS8a: Error allocating memory while computing gen_poly: %s\n", + PLOG(PL_FATAL, "NormEncoderMDP: Error allocating memory while computing gen_poly: %s\n", GetErrorString()); return false; } @@ -179,29 +167,25 @@ bool NormEncoderRS8a::CreateGeneratorPolynomial() delete[] tp1; delete[] tp; return true; -} // end NormEncoderRS8a::CreateGeneratorPolynomial() +} // end NormEncoderMDP::CreateGeneratorPolynomial() // Encode data vectors one at a time. The user of this function // must keep track of when parity is ready for transmission // Parity data is written to list of parity vectors supplied by caller -void NormEncoderRS8a::Encode(const char *data, char **pVec) +// MUST be called w/ "data" vectors in-order by segmentId (caller's responsibility) +void NormEncoderMDP::Encode(unsigned int /*segmentId*/, const char* data, char** pVec) { int i, j; unsigned char *userData, *LSFR1, *LSFR2, *pVec0; int npar_minus_one = npar - 1; unsigned char* genPoly = &gen_poly[npar_minus_one]; - ASSERT(scratch); // Make sure it's been init'd first + ASSERT(NULL != scratch); // Make sure it's been init'd first // Assumes parity vectors are zero-filled at block start !!! // Copy pVec[0] for use in calculations -#ifdef SIMULATE - UINT16 vecSize = MIN(SIM_PAYLOAD_MAX+1, vector_size); -#else UINT16 vecSize = vector_size; -#endif // if/else SIMULATE - memcpy(scratch, pVec[0], vecSize); if (npar > 1) @@ -209,9 +193,9 @@ void NormEncoderRS8a::Encode(const char *data, char **pVec) for(i = 0; i < npar_minus_one; i++) { pVec0 = scratch; - userData = (unsigned char *) data; - LSFR1 = (unsigned char *) pVec[i]; - LSFR2 = (unsigned char *) pVec[i+1]; + userData = (unsigned char*) data; + LSFR1 = (unsigned char*) pVec[i]; + LSFR2 = (unsigned char*) pVec[i+1]; for(j = 0; j < vecSize; j++) *LSFR1++ = *LSFR2++ ^ gmult(*genPoly, (*userData++ ^ *pVec0++)); @@ -220,37 +204,37 @@ void NormEncoderRS8a::Encode(const char *data, char **pVec) } pVec0 = scratch; - userData = (unsigned char *) data; - LSFR1 = (unsigned char *) pVec[npar_minus_one]; + userData = (unsigned char*) data; + LSFR1 = (unsigned char*) pVec[npar_minus_one]; for(j = 0; j < vecSize; j++) *LSFR1++ = gmult(*genPoly, (*userData++ ^ *pVec0++)); -} // end NormEncoderRS8a::Encode() +} // end NormEncoderMDP::Encode() /******************************************************************************** - * NormDecoderRS8a implementation routines + * NormDecoderMDP implementation routines */ -NormDecoderRS8a::NormDecoderRS8a() +NormDecoderMDP::NormDecoderMDP() : npar(0), vector_size(0), lambda(NULL), s_vec(NULL), o_vec(NULL), scratch(NULL) { } -NormDecoderRS8a::~NormDecoderRS8a() +NormDecoderMDP::~NormDecoderMDP() { if (NULL != lambda) Destroy(); } -bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) +bool NormDecoderMDP::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) { // Debugging assertions ASSERT((numData + numParity) <= 255); if ((numData + numParity) > 255) return false; #ifdef SIMULATE - vecSizeMax = MIN(SIM_PAYLOAD_MAX+1, vecSizeMax); + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); #endif // SIMUATE if (lambda) Destroy(); // Check if already inited ... @@ -260,7 +244,7 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 if(!(lambda = new unsigned char[2*npar])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for lambda: %s\n", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for lambda: %s\n", GetErrorString()); return(false); } @@ -268,7 +252,7 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 /* Allocate memory for s_vec ptr and the syndrome vectors */ if(!(s_vec = new unsigned char*[npar])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for s_vec ptr: %s\n", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for s_vec ptr: %s\n", GetErrorString()); Destroy(); return(false); @@ -278,7 +262,7 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 { if(!(s_vec[i] = new unsigned char[vecSizeMax])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for new s_vec: %s\n", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for new s_vec: %s\n", GetErrorString()); Destroy(); return(false); @@ -288,7 +272,7 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 /* Allocate memory for the o_vec ptr and the Omega vectors */ if(!(o_vec = new unsigned char*[npar])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for new o_vec ptr: %s\n", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for new o_vec ptr: %s\n", GetErrorString()); Destroy(); return(false); @@ -298,7 +282,7 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 { if(!(o_vec[i] = new unsigned char[vecSizeMax])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for new o_vec: %s", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for new o_vec: %s", GetErrorString()); Destroy(); return(false); @@ -307,15 +291,15 @@ bool NormDecoderRS8a::Init(unsigned int numData, unsigned int numParity, UINT16 if (!(scratch = new unsigned char[vecSizeMax])) { - DMSG(0, "NormDecoderRS8a: Error allocating memory for scratch space: %s", + PLOG(PL_FATAL, "NormDecoderMDP: Error allocating memory for scratch space: %s", GetErrorString()); } memset(scratch, 0, vecSizeMax*sizeof(unsigned char)); return(true); -} // end NormDecoderRS8a::Init() +} // end NormDecoderMDP::Init() -void NormDecoderRS8a::Destroy() +void NormDecoderMDP::Destroy() { if (scratch) { @@ -342,14 +326,14 @@ void NormDecoderRS8a::Destroy() delete[] lambda; lambda = NULL; } -} // end NormDecoderRS8a::Destroy() +} // end NormDecoderMDP::Destroy() // This will crash & burn if (erasureCount > npar) -int NormDecoderRS8a::Decode(char** dVec, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) +int NormDecoderMDP::Decode(char** dVec, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) { // Debugging assertions - ASSERT(lambda); + ASSERT(NULL != lambda); ASSERT(erasureCount && (erasureCount <= npar)); // (A) Compute syndrome vectors @@ -358,11 +342,7 @@ int NormDecoderRS8a::Decode(char** dVec, unsigned int numData, unsigned int eras // Then calculate syndrome (based on zero value erasures) unsigned int nvecs = npar + numData; -#ifdef SIMULATE - UINT16 vecSize = MIN(SIM_PAYLOAD_MAX+1, vector_size); -#else UINT16 vecSize = vector_size; -#endif // if/else SIMUATE unsigned int i; for (i = 0; i < npar; i++) @@ -447,7 +427,7 @@ int NormDecoderRS8a::Decode(char** dVec, unsigned int numData, unsigned int eras } } return erasureCount; -} // end NormDecoderRS8a::Decode() +} // end NormDecoderMDP::Decode() diff --git a/src/common/normEncoderRS16.cpp b/src/common/normEncoderRS16.cpp new file mode 100644 index 0000000..d474bfa --- /dev/null +++ b/src/common/normEncoderRS16.cpp @@ -0,0 +1,888 @@ +/* + * This includes forward error correction code based on Vandermonde matrices + * 980624 + * (C) 1997-98 Luigi Rizzo (luigi@iet.unipi.it) + * + * Portions derived from code by Phil Karn (karn@ka9q.ampr.org), + * Robert Morelos-Zaragoza (robert@spectra.eng.hawaii.edu) and Hari + * Thirumoorthy (harit@spectra.eng.hawaii.edu), Aug 1995 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + + +#include "normEncoderRS16.h" +#include "protoDebug.h" +#ifdef SIMULATE +#include "normMessage.h" +#endif // SIMULATE + +/* + * The first part of the file here implements linear algebra in GF. + * + * gf is the type used to store an element of the Galois Field. + * Must constain at least GF_BITS bits. + * + * Note: unsigned char will work up to GF(256) but int seems to run + * faster on the Pentium. We use int whenever have to deal with an + * index, since they are generally faster. + */ + +#define GF_BITS 16 // 16-bit RS code +#if (GF_BITS < 2 && GF_BITS > 16) +#error "GF_BITS must be 2 .. 16" +#endif +#if (GF_BITS <= 8) +typedef UINT8 gf; +#else +typedef UINT16 gf; +#endif +#define GF_SIZE ((1 << GF_BITS) - 1) // powers of alpha + + +/* + * Primitive polynomials - see Lin & Costello, Appendix A, + * and Lee & Messerschmitt, p. 453. + */ + +static const char *allPp[] = +{ // GF_BITS Polynomial + NULL, // 0 no code + NULL, // 1 no code + "111", // 2 1+x+x^2 + "1101", // 3 1+x+x^3 + "11001", // 4 1+x+x^4 + "101001", // 5 1+x^2+x^5 + "1100001", // 6 1+x+x^6 + "10010001", // 7 1 + x^3 + x^7 + "101110001", // 8 1+x^2+x^3+x^4+x^8 + "1000100001", // 9 1+x^4+x^9 + "10010000001", // 10 1+x^3+x^10 + "101000000001", // 11 1+x^2+x^11 + "1100101000001", // 12 1+x+x^4+x^6+x^12 + "11011000000001", // 13 1+x+x^3+x^4+x^13 + "110000100010001", // 14 1+x+x^6+x^10+x^14 + "1100000000000001", // 15 1+x+x^15 + "11010000000010001" // 16 1+x+x^3+x^12+x^16 +}; + + +/* + * To speed up computations, we have tables for logarithm, exponent + * and inverse of a number. If GF_BITS <= 8, we use a table for + * multiplication as well (it takes 64K, no big deal even on a PDA, + * especially because it can be pre-initialized an put into a ROM!), + * otherwhise we use a table of logarithms. + * In any case the macro gf_mul(x,y) takes care of multiplications. + */ + +static gf gf_exp[2*GF_SIZE]; // index->poly form conversion table +static int gf_log[GF_SIZE + 1]; // Poly->index form conversion table +static gf inverse[GF_SIZE+1]; // inverse of field elem. + // inv[\alpha**i]=\alpha**(GF_SIZE-i-1) + +// modnn(x) computes x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, +// without a slow divide. +static inline gf modnn(int x) +{ + while (x >= GF_SIZE) + { + x -= GF_SIZE; + x = (x >> GF_BITS) + (x & GF_SIZE); + } + return x; +} // end modnn() + +#define SWAP(a,b,t) {t tmp; tmp=a; a=b; b=tmp;} + +/* + * gf_mul(x,y) multiplies two numbers. If GF_BITS<=8, it is much + * faster to use a multiplication table. + * + * USE_GF_MULC, GF_MULC0(c) and GF_ADDMULC(x) can be used when multiplying + * many numbers by the same constant. In this case the first + * call sets the constant, and others perform the multiplications. + * A value related to the multiplication is held in a local variable + * declared with USE_GF_MULC . See usage in addmul1(). + */ +#if (GF_BITS <= 8) + +static gf gf_mul_table[GF_SIZE + 1][GF_SIZE + 1]; + +#define gf_mul(x,y) gf_mul_table[x][y] +#define USE_GF_MULC register gf * __gf_mulc_ +#define GF_MULC0(c) __gf_mulc_ = gf_mul_table[c] +#define GF_ADDMULC(dst, x) dst ^= __gf_mulc_[x] + +static void init_mul_table() +{ + for (int i = 0; i <= GF_SIZE; i++) + { + for (int j = 0; j <= GF_SIZE; j++) + gf_mul_table[i][j] = gf_exp[modnn(gf_log[i] + gf_log[j]) ] ; + } + for (int j = 0; j <= GF_SIZE; j++) + gf_mul_table[0][j] = gf_mul_table[j][0] = 0; +} + +#else /* GF_BITS > 8 */ + +inline gf gf_mul(int x, int y) +{ + if ((0 == x) || (0 == y)) return 0; + return gf_exp[gf_log[x] + gf_log[y] ] ; +} + +#define init_mul_table() +#define USE_GF_MULC register gf * __gf_mulc_ +#define GF_MULC0(c) __gf_mulc_ = &gf_exp[ gf_log[c] ] +#define GF_ADDMULC(dst, x) { if (x) dst ^= __gf_mulc_[ gf_log[x] ] ; } + +#endif // if/else (GF_BITS <= 8) + +/* + * Generate GF(2**m) from the irreducible polynomial p(X) in p[0]..p[m] + * Lookup tables: + * index->polynomial form gf_exp[] contains j= \alpha^i; + * polynomial form -> index form gf_log[ j = \alpha^i ] = i + * \alpha=x is the primitive element of GF(2^m) + * + * For efficiency, gf_exp[] has size 2*GF_SIZE, so that a simple + * multiplication of two numbers can be resolved without calling modnn + */ + +#define NEW_GF_MATRIX(rows, cols) (new gf[rows*cols]) + +/* + * initialize the data structures used for computations in GF. + */ +static void generate_gf() +{ + const char *Pp = allPp[GF_BITS] ; + + gf mask = 1; /* x ** 0 = 1 */ + gf_exp[GF_BITS] = 0; /* will be updated at the end of the 1st loop */ + /* + * first, generate the (polynomial representation of) powers of \alpha, + * which are stored in gf_exp[i] = \alpha ** i . + * At the same time build gf_log[gf_exp[i]] = i . + * The first GF_BITS powers are simply bits shifted to the left. + */ + for (int i = 0; i < GF_BITS; i++, mask <<= 1 ) + { + gf_exp[i] = mask; + gf_log[gf_exp[i]] = i; + /* + * If Pp[i] == 1 then \alpha ** i occurs in poly-repr + * gf_exp[GF_BITS] = \alpha ** GF_BITS + */ + if ( Pp[i] == '1' ) + gf_exp[GF_BITS] ^= mask; + } + /* + * now gf_exp[GF_BITS] = \alpha ** GF_BITS is complete, so can als + * compute its inverse. + */ + gf_log[gf_exp[GF_BITS]] = GF_BITS; + /* + * Poly-repr of \alpha ** (i+1) is given by poly-repr of + * \alpha ** i shifted left one-bit and accounting for any + * \alpha ** GF_BITS term that may occur when poly-repr of + * \alpha ** i is shifted. + */ + mask = 1 << (GF_BITS - 1 ) ; + for (int i = GF_BITS + 1; i < GF_SIZE; i++) + { + if (gf_exp[i - 1] >= mask) + gf_exp[i] = gf_exp[GF_BITS] ^ ((gf_exp[i - 1] ^ mask) << 1); + else + gf_exp[i] = gf_exp[i - 1] << 1; + gf_log[gf_exp[i]] = i; + } + /* + * log(0) is not defined, so use a special value + */ + gf_log[0] = GF_SIZE ; + /* set the extended gf_exp values for fast multiply */ + for (int i = 0 ; i < GF_SIZE ; i++) + gf_exp[i + GF_SIZE] = gf_exp[i] ; + + /* + * again special cases. 0 has no inverse. This used to + * be initialized to GF_SIZE, but it should make no difference + * since noone is supposed to read from here. + */ + inverse[0] = 0 ; + inverse[1] = 1; + for (int i = 2; i <= GF_SIZE; i++) + inverse[i] = gf_exp[GF_SIZE-gf_log[i]]; +} // end generate_gf() + + +/* + * Various linear algebra operations that i use often. + */ + +/* + * addmul() computes dst[] = dst[] + c * src[] + * This is used often, so better optimize it! Currently the loop is + * unrolled 16 times, a good value for 486 and pentium-class machines. + * The case c=0 is also optimized, whereas c=1 is not. These + * calls are unfrequent in my typical apps so I did not bother. + * + * Note that gcc on + */ +#define addmul(dst, src, c, sz) \ + if (c != 0) addmul1(dst, src, c, sz) +#define UNROLL 16 /* 1, 4, 8, 16 */ + +static void addmul1(gf* dst1, gf* src1, gf c, int sz) +{ + USE_GF_MULC ; + register gf* dst = dst1; + register gf* src = src1 ; + gf* lim = &dst[sz - UNROLL + 1] ; + + GF_MULC0(c) ; + +#if (UNROLL > 1) /* unrolling by 8/16 is quite effective on the pentium */ + for (; dst < lim ; dst += UNROLL, src += UNROLL ) + { + GF_ADDMULC( dst[0] , src[0] ); + GF_ADDMULC( dst[1] , src[1] ); + GF_ADDMULC( dst[2] , src[2] ); + GF_ADDMULC( dst[3] , src[3] ); +#if (UNROLL > 4) + GF_ADDMULC( dst[4] , src[4] ); + GF_ADDMULC( dst[5] , src[5] ); + GF_ADDMULC( dst[6] , src[6] ); + GF_ADDMULC( dst[7] , src[7] ); +#endif +#if (UNROLL > 8) + GF_ADDMULC( dst[8] , src[8] ); + GF_ADDMULC( dst[9] , src[9] ); + GF_ADDMULC( dst[10] , src[10] ); + GF_ADDMULC( dst[11] , src[11] ); + GF_ADDMULC( dst[12] , src[12] ); + GF_ADDMULC( dst[13] , src[13] ); + GF_ADDMULC( dst[14] , src[14] ); + GF_ADDMULC( dst[15] , src[15] ); +#endif + } +#endif + lim += UNROLL - 1 ; + for (; dst < lim; dst++, src++ ) /* final components */ + GF_ADDMULC( *dst , *src ); +} // end addmul1() + + +// computes C = AB where A is n*k, B is k*m, C is n*m +static void matmul(gf* a, gf* b, gf* c, int n, int k, int m) +{ + int row, col, i ; + + for (row = 0; row < n ; row++) + { + for (col = 0; col < m ; col++) + { + gf* pa = &a[ row * k ]; + gf* pb = &b[ col ]; + gf acc = 0 ; + for (i = 0; i < k ; i++, pa++, pb += m) + acc ^= gf_mul( *pa, *pb ) ; + c[row * m + col] = acc ; + } + } +} // end matmul() + + +static int invert_vdm(gf* src, int k) +{ + gf t, xx; + + if (k == 1)return 0; // degenerate case, matrix must be p^0 = 1 + + /* + * c holds the coefficient of P(x) = Prod (x - p_i), i=0..k-1 + * b holds the coefficient for the matrix inversion + */ + gf* c = NEW_GF_MATRIX(1, k); + gf* b = NEW_GF_MATRIX(1, k); + gf* p = NEW_GF_MATRIX(1, k); + + int i, j; + for (j = 1, i = 0 ; i < k ; i++, j+=k ) + { + c[i] = 0 ; + p[i] = src[j] ; /* p[i] */ + } + /* + * construct coeffs. recursively. We know c[k] = 1 (implicit) + * and start P_0 = x - p_0, then at each stage multiply by + * x - p_i generating P_i = x P_{i-1} - p_i P_{i-1} + * After k steps we are done. + */ + c[k-1] = p[0] ; /* really -p(0), but x = -x in GF(2^m) */ + for (i = 1 ; i < k ; i++) + { + gf p_i = p[i] ; /* see above comment */ + for (j = k - 1 - ( i - 1 ) ; j < k-1 ; j++ ) + c[j] ^= gf_mul( p_i, c[j+1] ) ; + c[k-1] ^= p_i ; + } + for (int row = 0 ; row < k ; row++) + { + /* + * synthetic division etc. + */ + xx = p[row] ; + t = 1 ; + b[k-1] = 1 ; /* this is in fact c[k] */ + for (i = k - 2 ; i >= 0 ; i-- ) + { + b[i] = c[i+1] ^ gf_mul(xx, b[i+1]) ; + t = gf_mul(xx, t) ^ b[i] ; + } + for (int col = 0 ; col < k ; col++ ) + src[col*k + row] = gf_mul(inverse[t], b[col] ); + } + delete[] c; + delete[] b; + delete[] p; + return 0 ; +} // end invert_vdm() + + +static bool fec_initialized = false; +static void init_fec() +{ + if (!fec_initialized) + { + generate_gf(); + init_mul_table(); + fec_initialized = true; + } +} + +NormEncoderRS16::NormEncoderRS16() + : enc_matrix(NULL) +{ +} + +NormEncoderRS16::~NormEncoderRS16() +{ + Destroy(); +} + +bool NormEncoderRS16::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) +{ +#ifdef SIMULATE + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); +#endif // SIMULATE + if ((numData + numParity) > GF_SIZE) + { + PLOG(PL_FATAL, "NormEncoderRS16::Init() error: numData/numParity exceeds code limits\n"); + return false; + } + + if (NULL != enc_matrix) + { + delete[] enc_matrix; + enc_matrix = NULL; + } + init_fec(); + int n = numData + numParity; + int k = numData; + enc_matrix = (UINT8*)NEW_GF_MATRIX(n, k); + if (NULL != enc_matrix) + { + gf* tmpMatrix = NEW_GF_MATRIX(n, k); + if (NULL == tmpMatrix) + { + PLOG(PL_FATAL, "NormEncoderRS16::Init() error: new tmpMatrix error: %s\n", GetErrorString()); + delete[] enc_matrix; + enc_matrix = NULL; + return false; + } + // Fill the matrix with powers of field elements, starting from 0. + // The first row is special, cannot be computed with exp. table. + tmpMatrix[0] = 1 ; + for (int col = 1; col < k ; col++) + tmpMatrix[col] = 0 ; + for (gf* p = tmpMatrix + k, row = 0; row < n-1 ; row++, p += k) + { + for (int col = 0 ; col < k ; col ++ ) + p[col] = gf_exp[modnn(row*col)]; + } + + + // Quick code to build systematic matrix: invert the top + // k*k vandermonde matrix, multiply right the bottom n-k rows + // by the inverse, and construct the identity matrix at the top. + invert_vdm(tmpMatrix, k); /* much faster than invert_mat */ + matmul(tmpMatrix + k*k, tmpMatrix, ((gf*)enc_matrix) + k*k, n - k, k, k); + // the upper matrix is I so do not bother with a slow multiply + memset(enc_matrix, 0, k*k*sizeof(gf)); + for (gf* p = (gf*)enc_matrix, col = 0 ; col < k ; col++, p += k+1 ) + *p = 1 ; + delete[] tmpMatrix; + ndata = numData; + npar = numParity; + vector_size = vecSizeMax; + return true; + } + else + { + PLOG(PL_FATAL, "NormEncoderRS16::Init() error: new enc_matrix error: %s\n", GetErrorString()); + return false; + } +} // end NormEncoderRS16::Init() + +void NormEncoderRS16::Destroy() +{ + if (NULL != enc_matrix) + { + delete[] enc_matrix; + enc_matrix = NULL; + } +} // end NormEncoderRS16::Destroy() + +void NormEncoderRS16::Encode(unsigned int segmentId, const char* dataVector, char** parityVectorList) +{ + for (unsigned int i = 0; i < npar; i++) + { + // Update each parity vector + gf* fec = (gf*)parityVectorList[i]; + gf* p = ((gf*)enc_matrix) + ((i+ndata)*ndata); + unsigned int nelements = (GF_BITS > 8) ? vector_size / 2 : vector_size; + addmul(fec, (gf*)dataVector, p[segmentId], nelements); + } +} // end NormEncoderRS16::Encode() + + +NormDecoderRS16::NormDecoderRS16() + : enc_matrix(NULL), dec_matrix(NULL), + parity_loc(NULL), inv_ndxc(NULL), inv_ndxr(NULL), + inv_pivt(NULL), inv_id_row(NULL), inv_temp_row(NULL) +{ +} + +NormDecoderRS16::~NormDecoderRS16() +{ + Destroy(); +} + +void NormDecoderRS16::Destroy() +{ + if (NULL != enc_matrix) + { + delete[] enc_matrix; + enc_matrix = NULL; + } + if (NULL != dec_matrix) + { + delete[] dec_matrix; + dec_matrix = NULL; + } + if (NULL != parity_loc) + { + delete[] parity_loc; + parity_loc = NULL; + } + if (NULL != inv_ndxc) + { + delete[] inv_ndxc; + inv_ndxc = NULL; + } + if (NULL != inv_ndxr) + { + delete[] inv_ndxr; + inv_ndxr = NULL; + } + if (NULL != inv_pivt) + { + delete[] inv_pivt; + inv_pivt = NULL; + } + if (NULL != inv_id_row) + { + delete[] inv_id_row; + inv_id_row = NULL; + } + if (NULL != inv_temp_row) + { + delete[] inv_temp_row; + inv_temp_row = NULL; + } +} // end NormDecoderRS16::Destroy() + +bool NormDecoderRS16::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) +{ +#ifdef SIMULATE + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); +#endif // SIMULATE + if ((numData + numParity) > GF_SIZE) + { + PLOG(PL_FATAL, "NormEncoderRS16::Init() error: numData/numParity exceeds code limits\n"); + return false; + } + + init_fec(); + Destroy(); + + int n = numData + numParity; + int k = numData; + + if (NULL == (inv_ndxc = new unsigned int[k])) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() new inv_indxc error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (inv_ndxr = new unsigned int[k])) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() new inv_inv_ndxr error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (inv_pivt = new unsigned int[k])) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() new inv_pivt error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (inv_id_row = (UINT8*)NEW_GF_MATRIX(1, k))) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() new inv_id_row error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (inv_temp_row = (UINT8*)NEW_GF_MATRIX(1, k))) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() new inv_temp_row error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (parity_loc = new unsigned int[numParity])) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() error: new parity_loc error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (dec_matrix = (UINT8*)NEW_GF_MATRIX(k, k))) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() error: new dec_matrix error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + if (NULL == (enc_matrix = (UINT8*)NEW_GF_MATRIX(n, k))) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() error: new enc_matrix error: %s\n", GetErrorString()); + Destroy(); + return false; + } + + gf* tmpMatrix = NEW_GF_MATRIX(n, k); + if (NULL == tmpMatrix) + { + PLOG(PL_FATAL, "NormDecoderRS16::Init() error: new tmpMatrix error: %s\n", GetErrorString()); + delete[] enc_matrix; + enc_matrix = NULL; + return false; + } + // Fill the matrix with powers of field elements, starting from 0. + // The first row is special, cannot be computed with exp. table. + tmpMatrix[0] = 1 ; + for (int col = 1; col < k ; col++) + tmpMatrix[col] = 0 ; + for (gf* p = tmpMatrix + k, row = 0; row < n-1 ; row++, p += k) + { + for (int col = 0 ; col < k ; col ++ ) + p[col] = gf_exp[modnn(row*col)]; + } + + // Quick code to build systematic matrix: invert the top + // k*k vandermonde matrix, multiply right the bottom n-k rows + // by the inverse, and construct the identity matrix at the top. + invert_vdm(tmpMatrix, k); /* much faster than invert_mat */ + matmul(tmpMatrix + k*k, tmpMatrix, ((gf*)enc_matrix) + k*k, n - k, k, k); + // the upper matrix is I so do not bother with a slow multiply + memset(enc_matrix, 0, k*k*sizeof(gf)); + for (gf* p = (gf*)enc_matrix, col = 0 ; col < k ; col++, p += k+1 ) + *p = 1 ; + delete[] tmpMatrix; + ndata = numData; + npar = numParity; + vector_size = vecSizeMax; + return true; +} // end NormDecoderRS16::Init() + + +int NormDecoderRS16::Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) +{ + unsigned int bsz = ndata + npar; + // 1) Build decoding matrix for the given set of segments & erasures + unsigned int nextErasure = 0; + unsigned int ne = 0; + unsigned int sourceErasureCount = 0; + unsigned int parityCount = 0; + for (unsigned int i = 0; i < bsz; i++) + { + if (i < numData) + { + if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) + { + nextErasure++; + sourceErasureCount++; + } + else + { + // set identity row for segments we have + gf* p = ((gf*)dec_matrix) + ndata*i; + memset(p, 0, ndata*sizeof(gf)); + p[i] = 1; + } + } + else if (i < ndata) + { + // set identity row for assumed zero segments (shortened code) + gf* p = ((gf*)dec_matrix) + ndata*i; + memset(p, 0, ndata*sizeof(gf)); + p[i] = 1; + // Also keep track of where the non-erased parity segment are + // for the shortened code + if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) + { + nextErasure++; + } + else if (parityCount < sourceErasureCount) + { + parity_loc[parityCount++] = i; + // Copy appropriate enc_matric parity row to dec_matrix erasureRow + gf* p = ((gf*)dec_matrix) + ndata*erasureLocs[ne++]; + memcpy(p, ((gf*)enc_matrix) + (ndata-numData+i)*ndata, ndata*sizeof(gf)); + } + + } + else if (parityCount < sourceErasureCount) + { + if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) + { + nextErasure++; + } + else + { + ASSERT(parityCount < npar); + parity_loc[parityCount++] = i; + // Copy appropriate enc_matric parity row to dec_matrix erasureRow + gf* p = ((gf*)dec_matrix) + ndata*erasureLocs[ne++]; + memcpy(p, ((gf*)enc_matrix) + (ndata-numData+i)*ndata, ndata*sizeof(gf)); + } + } + else + { + break; + } + + } + ASSERT(ne == sourceErasureCount); + // 2) Invert the decoding matrix + if (!InvertDecodingMatrix()) + { + PLOG(PL_FATAL, "NormDecoderRS16::Decode() error: couldn't invert dec_matrix (numData:%d erasureCount:%d) ?!\n", numData, erasureCount); + return 0; + } + + // 3) Decode + for (unsigned int e = 0; e < erasureCount; e++) + { + // Calculate missing segments (erasures) using dec_matrix and non-erasures + unsigned int row = erasureLocs[e]; + if (row >= numData) break; // don't bother filling in parity segments + unsigned int col = 0; + unsigned int nextErasure = 0; + unsigned int nelements = (GF_BITS > 8) ? vector_size/2 : vector_size; + for (unsigned int i = 0; i < numData; i++) + { + if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) + { + // Use parity segments in place of erased vector in decoding + addmul((gf*)vectorList[row], (gf*)vectorList[parity_loc[nextErasure]], ((gf*)dec_matrix)[row*ndata + col], nelements); + col++; + nextErasure++; // point to next erasure + } + else if (i < numData) + { + addmul((gf*)vectorList[row], (gf*)vectorList[i], ((gf*)dec_matrix)[row*ndata + col], nelements); + col++; + } + else + { + ASSERT(0); + } + } + } + return erasureCount ; +} // end NormDecoderRS16::Decode() + + + +/* + * NormDecoderRS16::InvertDecodingMatrix() takes a matrix and produces its inverse + * k is the size of the matrix. (Gauss-Jordan, adapted from Numerical Recipes in C) + * Return non-zero if singular. + */ +bool NormDecoderRS16::InvertDecodingMatrix() +{ + gf* src = (gf*)dec_matrix; + int k = ndata; + + memset(inv_id_row, 0, k*sizeof(gf)); + // inv_pivt marks elements already used as pivots. + memset(inv_pivt, 0, k*sizeof(unsigned int)); + + for (int col = 0; col < k ; col++) + { + /* + * Zeroing column 'col', look for a non-zero element. + * First try on the diagonal, if it fails, look elsewhere. + */ + int irow = -1; + int icol = -1 ; + if (inv_pivt[col] != 1 && src[col*k + col] != 0) + { + irow = col ; + icol = col ; + goto found_piv ; + } + for (int row = 0 ; row < k ; row++) + { + if (inv_pivt[row] != 1) + { + for (int ix = 0 ; ix < k ; ix++) + { + if (inv_pivt[ix] == 0) + { + if (src[row*k + ix] != 0) + { + irow = row ; + icol = ix ; + goto found_piv ; + } + } + else if (inv_pivt[ix] > 1) + { + PLOG(PL_FATAL, "NormDecoderRS16::InvertDecodingMatrix() error: singular matrix!\n"); + return false; + } + } + } + } + if (icol == -1) + { + PLOG(PL_FATAL, "NormDecoderRS16::InvertDecodingMatrix() error: pivot not found!\n"); + return false; + } + found_piv: + ++(inv_pivt[icol]) ; + /* + * swap rows irow and icol, so afterwards the diagonal + * element will be correct. Rarely done, not worth + * optimizing. + */ + if (irow != icol) + { + for (int ix = 0 ; ix < k ; ix++ ) + SWAP(src[irow*k + ix], src[icol*k + ix], gf); + } + inv_ndxr[col] = irow ; + inv_ndxc[col] = icol ; + gf* pivotRow = &src[icol*k] ; + gf c = pivotRow[icol] ; + if (c == 0) + { + PLOG(PL_FATAL, "NormDecoderRS16::InvertDecodingMatrix() error: singular matrix!\n"); + return false; + } + if (c != 1 ) /* otherwhise this is a NOP */ + { + /* + * this is done often , but optimizing is not so + * fruitful, at least in the obvious ways (unrolling) + */ + c = inverse[ c ] ; + pivotRow[icol] = 1 ; + for (int ix = 0 ; ix < k ; ix++ ) + pivotRow[ix] = gf_mul(c, pivotRow[ix] ); + } + /* + * from all rows, remove multiples of the selected row + * to zero the relevant entry (in fact, the entry is not zero + * because we know it must be zero). + * (Here, if we know that the pivot_row is the identity, + * we can optimize the addmul). + */ + inv_id_row[icol] = 1; + if (0 != memcmp(pivotRow, inv_id_row, k*sizeof(gf))) + { + for (gf* p = src, ix = 0 ; ix < k ; ix++, p += k ) + { + if (ix != icol) + { + c = p[icol] ; + p[icol] = 0 ; + addmul(p, pivotRow, c, k ); + } + } + } + inv_id_row[icol] = 0; + } // end for (col = 0; col < k ; col++) + + for (int col = k - 1 ; col >= 0 ; col-- ) + { + if (inv_ndxr[col] < 0 || inv_ndxr[col] >= (unsigned int)k) + { + PLOG(PL_ERROR, "NormDecoderRS16::InvertDecodingMatrix() error: AARGH, inv_ndxr[col] %d\n", inv_ndxr[col]); + } + else if (inv_ndxc[col] <0 || inv_ndxc[col] >= (unsigned int)k) + { + PLOG(PL_ERROR, "NormDecoderRS16::InvertDecodingMatrix() error: AARGH, indxc[col] %d\n", inv_ndxc[col]); + } + else if (inv_ndxr[col] != inv_ndxc[col] ) + { + for (int row = 0 ; row < k ; row++ ) + SWAP( src[row*k + inv_ndxr[col]], src[row*k + inv_ndxc[col]], gf) ; + } + } + return true; +} // end NormDecoderRS16::InvertDecodingMatrix() + diff --git a/common/normEncoderRS8.cpp b/src/common/normEncoderRS8.cpp similarity index 77% rename from common/normEncoderRS8.cpp rename to src/common/normEncoderRS8.cpp index 4fe987e..3b50aad 100644 --- a/common/normEncoderRS8.cpp +++ b/src/common/normEncoderRS8.cpp @@ -34,6 +34,11 @@ #include "normEncoderRS8.h" +#include "protoDebug.h" + +#ifdef SIMULATE +#include "normMessage.h" +#endif // SIMULATE /* * The first part of the file here implements linear algebra in GF. @@ -46,7 +51,7 @@ * index, since they are generally faster. */ -#define GF_BITS 16 // 8-bit RS code +#define GF_BITS 8 // 8-bit RS code #if (GF_BITS < 2 && GF_BITS > 16) #error "GF_BITS must be 2 .. 16" #endif @@ -55,7 +60,7 @@ typedef UINT8 gf; #else typedef UINT16 gf; #endif -#define GF_SIZE ((1 << GF_BITS) - 1) // powers of \alpha +#define GF_SIZE ((1 << GF_BITS) - 1) // powers of alpha /* @@ -63,7 +68,7 @@ typedef UINT16 gf; * and Lee & Messerschmitt, p. 453. */ -static char *allPp[] = +static const char *allPp[] = { // GF_BITS Polynomial NULL, // 0 no code NULL, // 1 no code @@ -134,12 +139,12 @@ static gf gf_mul_table[GF_SIZE + 1][GF_SIZE + 1]; static void init_mul_table() { - int i, j; - for (i=0; i< GF_SIZE+1; i++) - for (j=0; j< GF_SIZE+1; j++) - gf_mul_table[i][j] = gf_exp[modnn(gf_log[i] + gf_log[j]) ] ; - - for (j=0; j< GF_SIZE+1; j++) + for (int i = 0; i <= GF_SIZE; i++) + { + for (int j = 0; j <= GF_SIZE; j++) + gf_mul_table[i][j] = gf_exp[modnn(gf_log[i] + gf_log[j]) ] ; + } + for (int j = 0; j <= GF_SIZE; j++) gf_mul_table[0][j] = gf_mul_table[j][0] = 0; } @@ -147,8 +152,7 @@ static void init_mul_table() inline gf gf_mul(int x, int y) { - if ( (x) == 0 || (y)==0 ) return 0; - + if ((0 == x) || (0 == y)) return 0; return gf_exp[gf_log[x] + gf_log[y] ] ; } @@ -170,9 +174,6 @@ inline gf gf_mul(int x, int y) * multiplication of two numbers can be resolved without calling modnn */ -//#define NEW_GF_MATRIX(rows, cols) \ -// (gf *)my_malloc(rows * cols * sizeof(gf), " ## __LINE__ ## " ) - #define NEW_GF_MATRIX(rows, cols) (new gf[rows*cols]) /* @@ -180,7 +181,7 @@ inline gf gf_mul(int x, int y) */ static void generate_gf() { - char *Pp = allPp[GF_BITS] ; + const char *Pp = allPp[GF_BITS] ; gf mask = 1; /* x ** 0 = 1 */ gf_exp[GF_BITS] = 0; /* will be updated at the end of the 1st loop */ @@ -236,7 +237,7 @@ static void generate_gf() */ inverse[0] = 0 ; inverse[1] = 1; - for (int i=2; i<=GF_SIZE; i++) + for (int i = 2; i <= GF_SIZE; i++) inverse[i] = gf_exp[GF_SIZE-gf_log[i]]; } // end generate_gf() @@ -258,11 +259,12 @@ static void generate_gf() if (c != 0) addmul1(dst, src, c, sz) #define UNROLL 16 /* 1, 4, 8, 16 */ -static void addmul1(gf *dst1, gf *src1, gf c, int sz) +static void addmul1(gf* dst1, gf* src1, gf c, int sz) { USE_GF_MULC ; - register gf *dst = dst1, *src = src1 ; - gf *lim = &dst[sz - UNROLL + 1] ; + register gf* dst = dst1; + register gf* src = src1 ; + gf* lim = &dst[sz - UNROLL + 1] ; GF_MULC0(c) ; @@ -298,7 +300,7 @@ static void addmul1(gf *dst1, gf *src1, gf c, int sz) // computes C = AB where A is n*k, B is k*m, C is n*m -static void matmul(gf *a, gf *b, gf *c, int n, int k, int m) +static void matmul(gf* a, gf* b, gf* c, int n, int k, int m) { int row, col, i ; @@ -306,8 +308,8 @@ static void matmul(gf *a, gf *b, gf *c, int n, int k, int m) { for (col = 0; col < m ; col++) { - gf *pa = &a[ row * k ]; - gf *pb = &b[ col ]; + gf* pa = &a[ row * k ]; + gf* pb = &b[ col ]; gf acc = 0 ; for (i = 0; i < k ; i++, pa++, pb += m) acc ^= gf_mul( *pa, *pb ) ; @@ -317,24 +319,22 @@ static void matmul(gf *a, gf *b, gf *c, int n, int k, int m) } // end matmul() -static int invert_vdm(gf *src, int k) +static int invert_vdm(gf* src, int k) { - int i, j, row, col ; - gf *b, *c, *p; - gf t, xx ; - - if (k == 1) /* degenerate case, matrix must be p^0 = 1 */ - return 0 ; + gf t, xx; + + if (k == 1)return 0; // degenerate case, matrix must be p^0 = 1 + /* * c holds the coefficient of P(x) = Prod (x - p_i), i=0..k-1 * b holds the coefficient for the matrix inversion */ - c = NEW_GF_MATRIX(1, k); - b = NEW_GF_MATRIX(1, k); - - p = NEW_GF_MATRIX(1, k); + gf* c = NEW_GF_MATRIX(1, k); + gf* b = NEW_GF_MATRIX(1, k); + gf* p = NEW_GF_MATRIX(1, k); - for ( j=1, i = 0 ; i < k ; i++, j+=k ) + int i, j; + for (j = 1, i = 0 ; i < k ; i++, j+=k ) { c[i] = 0 ; p[i] = src[j] ; /* p[i] */ @@ -349,11 +349,11 @@ static int invert_vdm(gf *src, int k) for (i = 1 ; i < k ; i++) { gf p_i = p[i] ; /* see above comment */ - for (j = k-1 - ( i - 1 ) ; j < k-1 ; j++ ) + for (j = k - 1 - ( i - 1 ) ; j < k-1 ; j++ ) c[j] ^= gf_mul( p_i, c[j+1] ) ; c[k-1] ^= p_i ; } - for (row = 0 ; row < k ; row++) + for (int row = 0 ; row < k ; row++) { /* * synthetic division etc. @@ -361,12 +361,12 @@ static int invert_vdm(gf *src, int k) xx = p[row] ; t = 1 ; b[k-1] = 1 ; /* this is in fact c[k] */ - for (i = k-2 ; i >= 0 ; i-- ) + for (i = k - 2 ; i >= 0 ; i-- ) { b[i] = c[i+1] ^ gf_mul(xx, b[i+1]) ; t = gf_mul(xx, t) ^ b[i] ; } - for (col = 0 ; col < k ; col++ ) + for (int col = 0 ; col < k ; col++ ) src[col*k + row] = gf_mul(inverse[t], b[col] ); } delete[] c; @@ -387,25 +387,24 @@ static void init_fec() } } -NormEncoder::NormEncoder() - : enc_matrix(NULL), enc_index(0) +NormEncoderRS8::NormEncoderRS8() + : enc_matrix(NULL) { } -NormEncoder::~NormEncoder() +NormEncoderRS8::~NormEncoderRS8() { - if (NULL != enc_matrix) - { - delete[] enc_matrix; - enc_matrix = NULL; - } + Destroy(); } -bool NormEncoder::Init(int numData, int numParity, int vectorSize) +bool NormEncoderRS8::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) { +#ifdef SIMULATE + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); +#endif // SIMULATE if ((numData + numParity) > GF_SIZE) { - DMSG(0, "NormEncoder::Init() error: numData/numParity exceeds code limits\n"); + PLOG(PL_FATAL, "NormEncoderRS8::Init() error: numData/numParity exceeds code limits\n"); return false; } @@ -423,7 +422,7 @@ bool NormEncoder::Init(int numData, int numParity, int vectorSize) gf* tmpMatrix = NEW_GF_MATRIX(n, k); if (NULL == tmpMatrix) { - DMSG(0, "NormEncoder::Init() error: new tmpMatrix error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormEncoderRS8::Init() error: new tmpMatrix error: %s\n", GetErrorString()); delete[] enc_matrix; enc_matrix = NULL; return false; @@ -452,19 +451,26 @@ bool NormEncoder::Init(int numData, int numParity, int vectorSize) delete[] tmpMatrix; ndata = numData; npar = numParity; - vector_size = vectorSize; - enc_index = 0; + vector_size = vecSizeMax; return true; } else { - DMSG(0, "NormEncoder::Init() error: new enc_matrix error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormEncoderRS8::Init() error: new enc_matrix error: %s\n", GetErrorString()); return false; } -} // end NormEncoder::Init() +} // end NormEncoderRS8::Init() +void NormEncoderRS8::Destroy() +{ + if (NULL != enc_matrix) + { + delete[] enc_matrix; + enc_matrix = NULL; + } +} // end NormEncoderRS8::Destroy() -void NormEncoder::Encode(unsigned char *dataVector, unsigned char** parityVectorList) +void NormEncoderRS8::Encode(unsigned int segmentId, const char* dataVector, char** parityVectorList) { for (unsigned int i = 0; i < npar; i++) { @@ -472,25 +478,24 @@ void NormEncoder::Encode(unsigned char *dataVector, unsigned char** parityVector gf* fec = (gf*)parityVectorList[i]; gf* p = ((gf*)enc_matrix) + ((i+ndata)*ndata); unsigned int nelements = (GF_BITS > 8) ? vector_size / 2 : vector_size; - addmul(fec, (gf*)dataVector, p[enc_index], nelements); + addmul(fec, (gf*)dataVector, p[segmentId], nelements); } - enc_index++; -} // end NormEncoder::Encode() +} // end NormEncoderRS8::Encode() -NormDecoder::NormDecoder() +NormDecoderRS8::NormDecoderRS8() : enc_matrix(NULL), dec_matrix(NULL), parity_loc(NULL), inv_ndxc(NULL), inv_ndxr(NULL), inv_pivt(NULL), inv_id_row(NULL), inv_temp_row(NULL) { } -NormDecoder::~NormDecoder() +NormDecoderRS8::~NormDecoderRS8() { Destroy(); } -void NormDecoder::Destroy() +void NormDecoderRS8::Destroy() { if (NULL != enc_matrix) { @@ -507,13 +512,41 @@ void NormDecoder::Destroy() delete[] parity_loc; parity_loc = NULL; } -} // end NormDecoder::Destroy() + if (NULL != inv_ndxc) + { + delete[] inv_ndxc; + inv_ndxc = NULL; + } + if (NULL != inv_ndxr) + { + delete[] inv_ndxr; + inv_ndxr = NULL; + } + if (NULL != inv_pivt) + { + delete[] inv_pivt; + inv_pivt = NULL; + } + if (NULL != inv_id_row) + { + delete[] inv_id_row; + inv_id_row = NULL; + } + if (NULL != inv_temp_row) + { + delete[] inv_temp_row; + inv_temp_row = NULL; + } +} // end NormDecoderRS8::Destroy() -bool NormDecoder::Init(int numData, int numParity, int vectorSize) +bool NormDecoderRS8::Init(unsigned int numData, unsigned int numParity, UINT16 vecSizeMax) { +#ifdef SIMULATE + vecSizeMax = MIN(SIM_PAYLOAD_MAX, vecSizeMax); +#endif // SIMULATE if ((numData + numParity) > GF_SIZE) { - DMSG(0, "NormEncoder::Init() error: numData/numParity exceeds code limits\n"); + PLOG(PL_FATAL, "NormEncoderRS8::Init() error: numData/numParity exceeds code limits\n"); return false; } @@ -523,58 +556,58 @@ bool NormDecoder::Init(int numData, int numParity, int vectorSize) int n = numData + numParity; int k = numData; - if (NULL == (inv_ndxc = new int[k])) + if (NULL == (inv_ndxc = new unsigned int[k])) { - DMSG(0, "NormDecoder::Init() new inv_indxc error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() new inv_indxc error: %s\n", GetErrorString()); Destroy(); return false; } - if (NULL == (inv_ndxr = new int[k])) + if (NULL == (inv_ndxr = new unsigned int[k])) { - DMSG(0, "NormDecoder::Init() new inv_inv_ndxr error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() new inv_inv_ndxr error: %s\n", GetErrorString()); Destroy(); return false; } - if (NULL == (inv_pivt = new int[k])) + if (NULL == (inv_pivt = new unsigned int[k])) { - DMSG(0, "NormDecoder::Init() new inv_pivt error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() new inv_pivt error: %s\n", GetErrorString()); Destroy(); return false; } if (NULL == (inv_id_row = (UINT8*)NEW_GF_MATRIX(1, k))) { - DMSG(0, "NormDecoder::Init() new inv_id_row error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() new inv_id_row error: %s\n", GetErrorString()); Destroy(); return false; } if (NULL == (inv_temp_row = (UINT8*)NEW_GF_MATRIX(1, k))) { - DMSG(0, "NormDecoder::Init() new inv_temp_row error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() new inv_temp_row error: %s\n", GetErrorString()); Destroy(); return false; } - if (NULL == (parity_loc = new int[numParity])) + if (NULL == (parity_loc = new unsigned int[numParity])) { - DMSG(0, "NormDecoder::Init() error: new parity_loc error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() error: new parity_loc error: %s\n", GetErrorString()); Destroy(); return false; } if (NULL == (dec_matrix = (UINT8*)NEW_GF_MATRIX(k, k))) { - DMSG(0, "NormDecoder::Init() error: new dec_matrix error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() error: new dec_matrix error: %s\n", GetErrorString()); Destroy(); return false; } if (NULL == (enc_matrix = (UINT8*)NEW_GF_MATRIX(n, k))) { - DMSG(0, "NormDecoder::Init() error: new enc_matrix error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormDecoderRS8::Init() error: new enc_matrix error: %s\n", GetErrorString()); Destroy(); return false; } @@ -583,7 +616,7 @@ bool NormDecoder::Init(int numData, int numParity, int vectorSize) gf* tmpMatrix = NEW_GF_MATRIX(n, k); if (NULL == tmpMatrix) { - DMSG(0, "NormEncoder::Init() error: new tmpMatrix error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormEncoderRS8::Init() error: new tmpMatrix error: %s\n", GetErrorString()); delete[] enc_matrix; enc_matrix = NULL; return false; @@ -611,23 +644,22 @@ bool NormDecoder::Init(int numData, int numParity, int vectorSize) delete[] tmpMatrix; ndata = numData; npar = numParity; - vector_size = vectorSize; + vector_size = vecSizeMax; return true; -} // end NormDecoder::Init() +} // end NormDecoderRS8::Init() -int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCount, UINT16* erasureLocs) +int NormDecoderRS8::Decode(char** vectorList, unsigned int numData, unsigned int erasureCount, unsigned int* erasureLocs) { - int bsz = ndata + npar; + unsigned int bsz = ndata + npar; // 1) Build decoding matrix for the given set of segments & erasures - int nextErasure = 0; - int ne = 0; - int sourceErasureCount = 0; - int parityCount = 0; - for (int i = 0; i < bsz; i++) - { - - if (i < sdata) + unsigned int nextErasure = 0; + unsigned int ne = 0; + unsigned int sourceErasureCount = 0; + unsigned int parityCount = 0; + for (unsigned int i = 0; i < bsz; i++) + { + if (i < numData) { if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) { @@ -659,7 +691,7 @@ int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCo parity_loc[parityCount++] = i; // Copy appropriate enc_matric parity row to dec_matrix erasureRow gf* p = ((gf*)dec_matrix) + ndata*erasureLocs[ne++]; - memcpy(p, ((gf*)enc_matrix) + (ndata-sdata+i)*ndata, ndata*sizeof(gf)); + memcpy(p, ((gf*)enc_matrix) + (ndata-numData+i)*ndata, ndata*sizeof(gf)); } } @@ -675,7 +707,7 @@ int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCo parity_loc[parityCount++] = i; // Copy appropriate enc_matric parity row to dec_matrix erasureRow gf* p = ((gf*)dec_matrix) + ndata*erasureLocs[ne++]; - memcpy(p, ((gf*)enc_matrix) + (ndata-sdata+i)*ndata, ndata*sizeof(gf)); + memcpy(p, ((gf*)enc_matrix) + (ndata-numData+i)*ndata, ndata*sizeof(gf)); } } else @@ -688,19 +720,20 @@ int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCo // 2) Invert the decoding matrix if (!InvertDecodingMatrix()) { - DMSG(0, "NormDecoder::Decode() error: couldn't invert dec_matrix ?!\n"); + PLOG(PL_FATAL, "NormDecoderRS8::Decode() error: couldn't invert dec_matrix ?!\n"); return 0; } // 3) Decode - for (int e = 0; e < erasureCount; e++) + for (unsigned int e = 0; e < erasureCount; e++) { // Calculate missing segments (erasures) using dec_matrix and non-erasures - int row = erasureLocs[e]; - int col = 0; - int nextErasure = 0; - int nelements = (GF_BITS > 8) ? vector_size/2 : vector_size; - for (int i = 0; i < sdata; i++) + unsigned int row = erasureLocs[e]; + if (row >= numData) break; // don't bother filling in parity segments + unsigned int col = 0; + unsigned int nextErasure = 0; + unsigned int nelements = (GF_BITS > 8) ? vector_size/2 : vector_size; + for (unsigned int i = 0; i < numData; i++) { if ((nextErasure < erasureCount) && (i == erasureLocs[nextErasure])) { @@ -709,7 +742,7 @@ int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCo col++; nextErasure++; // point to next erasure } - else if (i < sdata) + else if (i < numData) { addmul((gf*)vectorList[row], (gf*)vectorList[i], ((gf*)dec_matrix)[row*ndata + col], nelements); col++; @@ -721,23 +754,23 @@ int NormDecoder::Decode(unsigned char** vectorList, int sdata, UINT16 erasureCo } } return erasureCount ; -} // end NormDecoder::Decode() +} // end NormDecoderRS8::Decode() /* - * NormDecoder::InvertDecodingMatrix() takes a matrix and produces its inverse + * NormDecoderRS8::InvertDecodingMatrix() takes a matrix and produces its inverse * k is the size of the matrix. (Gauss-Jordan, adapted from Numerical Recipes in C) * Return non-zero if singular. */ -bool NormDecoder::InvertDecodingMatrix() +bool NormDecoderRS8::InvertDecodingMatrix() { gf* src = (gf*)dec_matrix; int k = ndata; memset(inv_id_row, 0, k*sizeof(gf)); // inv_pivt marks elements already used as pivots. - memset(inv_pivt, 0, k*sizeof(gf)); + memset(inv_pivt, 0, k*sizeof(unsigned int)); for (int col = 0; col < k ; col++) { @@ -770,7 +803,7 @@ bool NormDecoder::InvertDecodingMatrix() } else if (inv_pivt[ix] > 1) { - DMSG(0, "NormDecoder::InvertDecodingMatrix() error: singular matrix!\n"); + PLOG(PL_FATAL, "NormDecoderRS8::InvertDecodingMatrix() error: singular matrix!\n"); return false; } } @@ -778,10 +811,10 @@ bool NormDecoder::InvertDecodingMatrix() } if (icol == -1) { - DMSG(0, "NormDecoder::InvertDecodingMatrix() error: pivot not found!\n"); + PLOG(PL_FATAL, "NormDecoderRS8::InvertDecodingMatrix() error: pivot not found!\n"); return false; } -found_piv: + found_piv: ++(inv_pivt[icol]) ; /* * swap rows irow and icol, so afterwards the diagonal @@ -799,7 +832,7 @@ found_piv: gf c = pivotRow[icol] ; if (c == 0) { - DMSG(0, "NormDecoder::InvertDecodingMatrix() error: singular matrix!\n"); + PLOG(PL_FATAL, "NormDecoderRS8::InvertDecodingMatrix() error: singular matrix!\n"); return false; } if (c != 1 ) /* otherwhise this is a NOP */ @@ -836,15 +869,15 @@ found_piv: inv_id_row[icol] = 0; } // end for (col = 0; col < k ; col++) - for (int col = k-1 ; col >= 0 ; col-- ) + for (int col = k - 1 ; col >= 0 ; col-- ) { - if (inv_ndxr[col] <0 || inv_ndxr[col] >= k) + if (inv_ndxr[col] < 0 || inv_ndxr[col] >= (unsigned int)k) { - DMSG(0, "NormDecoder::InvertDecodingMatrix() error: AARGH, inv_ndxr[col] %d\n", inv_ndxr[col]); + PLOG(PL_ERROR, "NormDecoderRS8::InvertDecodingMatrix() error: AARGH, inv_ndxr[col] %d\n", inv_ndxr[col]); } - else if (inv_ndxc[col] <0 || inv_ndxc[col] >= k) + else if (inv_ndxc[col] <0 || inv_ndxc[col] >= (unsigned int)k) { - DMSG(0, "NormDecoder::InvertDecodingMatrix() error: AARGH, indxc[col] %d\n", inv_ndxc[col]); + PLOG(PL_ERROR, "NormDecoderRS8::InvertDecodingMatrix() error: AARGH, indxc[col] %d\n", inv_ndxc[col]); } else if (inv_ndxr[col] != inv_ndxc[col] ) { @@ -853,5 +886,5 @@ found_piv: } } return true; -} // end NormDecoder::InvertDecodingMatrix() +} // end NormDecoderRS8::InvertDecodingMatrix() diff --git a/common/normFile.cpp b/src/common/normFile.cpp similarity index 92% rename from common/normFile.cpp rename to src/common/normFile.cpp index 9d7776c..af943c9 100644 --- a/common/normFile.cpp +++ b/src/common/normFile.cpp @@ -85,13 +85,13 @@ bool NormFile::Open(const char* thePath, int theFlags) if (!CreateDirectory(tempPath, NULL)) #endif // if/else _UNICODE #else - if (mkdir(tempPath)) + if (_mkdir(tempPath)) #endif // if/else _WIN32_WCE #else if (mkdir(tempPath, 0755)) #endif // if/else WIN32/UNIX { - DMSG(0, "NormFile::Open() mkdir(%s) error: %s\n", + PLOG(PL_FATAL, "NormFile::Open() mkdir(%s) error: %s\n", tempPath, GetErrorString()); return false; } @@ -122,7 +122,7 @@ bool NormFile::Open(const char* thePath, int theFlags) } else { - DMSG(0, "Error opening file \"%s\": %s\n", thePath, GetErrorString()); + PLOG(PL_FATAL, "Error opening file \"%s\": %s\n", thePath, GetErrorString()); flags = 0; return false; } @@ -134,7 +134,7 @@ bool NormFile::Open(const char* thePath, int theFlags) } else { - DMSG(0, "norm: Error opening file \"%s\": %s\n", + PLOG(PL_FATAL, "norm: Error opening file \"%s\": %s\n", thePath, GetErrorString()); return false; } @@ -172,7 +172,7 @@ bool NormFile::Lock() else #else #ifdef HAVE_LOCKF - if (lockf(fd, F_LOCK, 0)) // assume lockf if not flock + if (0 != lockf(fd, F_LOCK, 0)) // assume lockf if not flock return false; else #endif // HAVE_LOCKF @@ -188,7 +188,8 @@ void NormFile::Unlock() flock(fd, LOCK_UN); #else #ifdef HAVE_LOCKF - lockf(fd, F_ULOCK, 0); + int ret = lockf(fd, F_ULOCK, 0); + ret = 0; // done to avoid compiler warning (should optimize out) #endif // HAVE_LOCKF #endif // if/elseHAVE_FLOCK fchmod(fd, 0640); @@ -202,7 +203,7 @@ bool NormFile::Rename(const char* oldName, const char* newName) // (This also builds sub-directories as needed) if (NormFile::IsLocked(newName)) { - DMSG(0, "NormFile::Rename() error: file is locked\n"); + PLOG(PL_FATAL, "NormFile::Rename() error: file is locked\n"); return false; } #ifdef WIN32 @@ -220,13 +221,13 @@ bool NormFile::Rename(const char* oldName, const char* newName) if (0 == DeleteFile(newName)) #endif // if/else _UNICODE { - DMSG(0, "NormFile::Rename() DeleteFile() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Rename() DeleteFile() error: %s\n", GetErrorString()); return false; } #else if (0 != _unlink(newName)) { - DMSG(0, "NormFile::Rename() _unlink() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Rename() _unlink() error: %s\n", GetErrorString()); return false; } #endif // if/else _WIN32_WCE @@ -278,13 +279,13 @@ bool NormFile::Rename(const char* oldName, const char* newName) if (!CreateDirectory(tempPath, NULL)) #endif // if/else _UNICODE #else - if (0 != mkdir(tempPath)) + if (0 != _mkdir(tempPath)) #endif // if/else _WIN32_WCE #else if (mkdir(tempPath, 0755)) #endif // if/else WIN32/UNIX { - DMSG(0, "NormFile::Rename() mkdir(%s) error: %s\n", + PLOG(PL_FATAL, "NormFile::Rename() mkdir(%s) error: %s\n", tempPath, GetErrorString()); return false; } @@ -301,11 +302,11 @@ bool NormFile::Rename(const char* oldName, const char* newName) if (!MoveFile(oldName, newName)) #endif // if/else _UNICODE { - DMSG(0, "NormFile::Rename() MoveFile() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "NormFile::Rename() MoveFile() error: %s\n", GetErrorString()); #else if (rename(oldName, newName)) { - DMSG(0, "NormFile::Rename() rename() error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "NormFile::Rename() rename() error: %s\n", GetErrorString()); #endif // if/else _WIN32_WCE #ifdef WIN32 if (oldFlags) @@ -313,7 +314,7 @@ bool NormFile::Rename(const char* oldName, const char* newName) if (Open(oldName, oldFlags)) offset = 0; else - DMSG(0, "NormFile::Rename() error re-opening file w/ old name\n"); + PLOG(PL_ERROR, "NormFile::Rename() error re-opening file w/ old name\n"); } #endif // WIN32 return false; @@ -327,7 +328,7 @@ bool NormFile::Rename(const char* oldName, const char* newName) if (Open(newName, oldFlags)) offset = 0; else - DMSG(0, "NormFile::Rename() error opening file w/ new name\n"); + PLOG(PL_ERROR, "NormFile::Rename() error opening file w/ new name\n"); } #endif // WIN32 return true; @@ -356,7 +357,7 @@ size_t NormFile::Read(char* buffer, size_t len) if (EINTR != errno) #endif // !_WIN32_WCE { - DMSG(0, "NormFile::Read() read(%d) result:%d error:%s (offset:%d)\n", len, result, GetErrorString(), offset); + PLOG(PL_FATAL, "NormFile::Read() read(%d) result:%d error:%s (offset:%d)\n", len, result, GetErrorString(), offset); return 0; } } @@ -390,7 +391,7 @@ size_t NormFile::Write(const char* buffer, size_t len) if (EINTR != errno) #endif // !_WIN32_WCE { - DMSG(0, "NormFile::Write() write(%d) result:%d error: %s\n", len, result, GetErrorString()); + PLOG(PL_FATAL, "NormFile::Write() write(%d) result:%d error: %s\n", len, result, GetErrorString()); return 0; } } @@ -418,7 +419,7 @@ bool NormFile::Seek(Offset theOffset) #endif // if/else WIN32 if (result == (Offset)-1) { - DMSG(0, "NormFile::Seek() lseek() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Seek() lseek() error: %s\n", GetErrorString()); return false; } else @@ -437,13 +438,13 @@ bool NormFile::Pad(Offset theOffset) char byte = 0; if (1 != Write(&byte, 1)) { - DMSG(0, "NormFile::Pad() write error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Pad() write error: %s\n", GetErrorString()); return false; } } else { - DMSG(0, "NormFile::Pad() seek error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Pad() seek error: %s\n", GetErrorString()); return false; } } @@ -466,7 +467,7 @@ NormFile::Offset NormFile::GetSize() const #endif // if/else WIN32 if (result) { - DMSG(0, "Error getting file size: %s\n", GetErrorString()); + PLOG(PL_FATAL, "Error getting file size: %s\n", GetErrorString()); return 0; } else @@ -506,7 +507,7 @@ bool NormDirectoryIterator::Open(const char *thePath) if (thePath && access(thePath, X_OK)) #endif // if/else WIN32 { - DMSG(0, "NormDirectoryIterator: can't access directory: %s\n", thePath); + PLOG(PL_FATAL, "NormDirectoryIterator: can't access directory: %s\n", thePath); return false; } current = new NormDirectory(thePath); @@ -518,7 +519,7 @@ bool NormDirectoryIterator::Open(const char *thePath) } else { - DMSG(0, "NormDirectoryIterator: can't open directory: %s\n", thePath); + PLOG(PL_FATAL, "NormDirectoryIterator: can't open directory: %s\n", thePath); if (current) delete current; current = NULL; return false; @@ -867,7 +868,7 @@ NormFile::Offset NormFile::GetSize(const char* path) } else { - DMSG(0, "Error getting file size: %s\n", GetErrorString()); + PLOG(PL_ERROR, "Error getting file size: %s\n", GetErrorString()); return 0; } #else @@ -919,7 +920,7 @@ time_t NormFile::GetUpdateTime(const char* path) } else { - DMSG(0, "Error getting file size: %s\n", GetErrorString()); + PLOG(PL_ERROR, "Error getting file size: %s\n", GetErrorString()); return 0; } #else @@ -993,7 +994,7 @@ bool NormFile::Unlink(const char* path) if (0 == DeleteFile(path)) #endif // if/else _UNICODE { - DMSG(0, "NormFile::Unlink() DeletFile() error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Unlink() DeletFile() error: %s\n", GetErrorString()); return false; } #else @@ -1003,7 +1004,7 @@ bool NormFile::Unlink(const char* path) if (unlink(path)) #endif // if/else WIN32 { - DMSG(0, "NormFile::Unlink() unlink error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormFile::Unlink() unlink error: %s\n", GetErrorString()); return false; } #endif // if/else _WIN32_WCE @@ -1055,7 +1056,7 @@ bool NormFileList::Append(const char* path) } else { - DMSG(0, "NormFileList::Append() Bad file/directory name: %s\n", + PLOG(PL_FATAL, "NormFileList::Append() Bad file/directory name: %s\n", path); return false; } @@ -1073,7 +1074,7 @@ bool NormFileList::Append(const char* path) } else { - DMSG(0, "NormFileList::Append() Error creating file/directory item: %s\n", + PLOG(PL_FATAL, "NormFileList::Append() Error creating file/directory item: %s\n", GetErrorString()); return false; } @@ -1252,7 +1253,7 @@ bool NormFileList::DirectoryItem::GetNextFile(char* thePath, } */ if (!diterator.Open(path)) { - DMSG(0, "NormFileList::DirectoryItem::GetNextFile() Directory iterator init error\n"); + PLOG(PL_FATAL, "NormFileList::DirectoryItem::GetNextFile() Directory iterator init error\n"); return false; } } diff --git a/common/normMessage.cpp b/src/common/normMessage.cpp similarity index 67% rename from common/normMessage.cpp rename to src/common/normMessage.cpp index 1545d24..4b37718 100644 --- a/common/normMessage.cpp +++ b/src/common/normMessage.cpp @@ -26,33 +26,33 @@ bool NormMsg::InitFromBuffer(UINT16 msgLength) header_length_base = 16; break; case DATA: - if (((UINT8*)buffer)[NormObjectMsg::FEC_ID_OFFSET] == 129) + { + UINT8 fecId = ((UINT8*)buffer)[NormObjectMsg::FEC_ID_OFFSET]; + UINT16 fecIdLen = NormPayloadId::GetLength(fecId); + if (0 == fecIdLen) { - header_length_base = 24; - } - else - { - DMSG(0, "NormMsg::InitFromBuffer(DATA) unknown fec_id value: %u\n", - ((UINT8*)buffer)[NormObjectMsg::FEC_ID_OFFSET]); + PLOG(PL_FATAL, "NormMsg::InitFromBuffer(DATA) unknown fec_id value: %u\n", fecId); return false; } + header_length_base = NormObjectMsg::OBJ_MSG_OFFSET + fecIdLen; break; + } case CMD: switch (((UINT8*)buffer)[NormCmdMsg::FLAVOR_OFFSET]) { case NormCmdMsg::FLUSH: case NormCmdMsg::SQUELCH: - if (((UINT8*)buffer)[NormCmdFlushMsg::FEC_ID_OFFSET] == 129) + { + UINT8 fecId = ((UINT8*)buffer)[NormCmdFlushMsg::FEC_ID_OFFSET]; + UINT16 fecIdLen = NormPayloadId::GetLength(fecId); + if (0 == fecIdLen) { - header_length_base = 24; - } - else - { - DMSG(0, "NormMsg::InitFromBuffer(FLUSH|SQUELCH) unknown fec_id value: %u\n", - ((UINT8*)buffer)[NormCmdFlushMsg::FEC_ID_OFFSET]); + PLOG(PL_FATAL, "NormMsg::InitFromBuffer(DATA) unknown fec_id value: %u\n", fecId); return false; } + header_length_base = 4*NormCmdFlushMsg::FEC_ID_OFFSET + fecIdLen; break; + } case NormCmdMsg::EOT: case NormCmdMsg::REPAIR_ADV: case NormCmdMsg::ACK_REQ: @@ -63,7 +63,7 @@ bool NormMsg::InitFromBuffer(UINT16 msgLength) header_length_base = 24; break; default: - DMSG(0, "NormMsg::InitFromBuffer() recv'd unkown cmd flavor:%d\n", + PLOG(PL_FATAL, "NormMsg::InitFromBuffer() recv'd unkown cmd flavor:%d\n", ((UINT8*)buffer)[NormCmdMsg::FLAVOR_OFFSET]); return false; } @@ -77,12 +77,12 @@ bool NormMsg::InitFromBuffer(UINT16 msgLength) break; default: - DMSG(0, "NormMsg::InitFromBuffer() invalid message type!\n"); + PLOG(PL_FATAL, "NormMsg::InitFromBuffer() invalid message type!\n"); return false; } if (msgLength < header_length) { - DMSG(0, "NormMsg::InitFromBuffer() invalid message or header length\n"); + PLOG(PL_FATAL, "NormMsg::InitFromBuffer() invalid message or header length\n"); return false; } else @@ -136,33 +136,36 @@ bool NormCmdCCMsg::Iterator::GetNextNode(NormNodeId& nodeId, return true; } // end NormCmdCCMsg::Iterator::GetNextNode() - NormRepairRequest::NormRepairRequest() : form(INVALID), flags(0), length(0), buffer(NULL), buffer_len(0) { } -bool NormRepairRequest::AppendRepairItem(const NormObjectId& objectId, +bool NormRepairRequest::AppendRepairItem(UINT8 fecId, + UINT8 fecM, + const NormObjectId& objectId, const NormBlockId& blockId, UINT16 blockLen, UINT16 symbolId) { if (RANGES == form) - DMSG(4, "NormRepairRequest::AppendRepairItem-Range(obj>%hu blk>%lu seg>%hu) ...\n", - (UINT16)objectId, (UINT32)blockId, (UINT32)symbolId); + PLOG(PL_TRACE, "NormRepairRequest::AppendRepairItem-Range(fecId>%d obj>%hu blk>%lu seg>%hu) ...\n", + fecId, (UINT16)objectId, (UINT32)blockId, (UINT32)symbolId); else - DMSG(4, "NormRepairRequest::AppendRepairItem(obj>%hu blk>%lu seg>%hu) ...\n", - (UINT16)objectId, (UINT32)blockId, (UINT32)symbolId); - if (buffer_len >= (length+ITEM_LIST_OFFSET+RepairItemLength())) + PLOG(PL_TRACE, "NormRepairRequest::AppendRepairItem(fecId>%d obj>%hu blk>%lu seg>%hu) ...\n", + fecId, (UINT16)objectId, (UINT32)blockId, (UINT32)symbolId); + ASSERT(NormPayloadId::IsValid(fecId)); + + UINT16 itemLength = RepairItemLength(fecId); + if (buffer_len >= (ITEM_LIST_OFFSET+length+itemLength)) { UINT32* ptr = buffer + (length + ITEM_LIST_OFFSET)/4; - ((UINT8*)ptr)[FEC_ID_OFFSET] = (char)129; - ((UINT8*)ptr)[RESERVED_OFFSET] = 0; - ((UINT16*)ptr)[OBJ_ID_OFFSET] = htons((UINT16)objectId); - ptr[BLOCK_ID_OFFSET] = htonl((UINT32)blockId); - ((UINT16*)ptr)[BLOCK_LEN_OFFSET] = htons((UINT16)blockLen); - ((UINT16*)ptr)[SYMBOL_ID_OFFSET] = htons((UINT16)symbolId); - length += RepairItemLength(); + ((UINT8*)ptr)[FEC_ID_OFFSET] = fecId; // 1 byte + ((UINT8*)ptr)[RESERVED_OFFSET] = 0; // 1 byte + ((UINT16*)ptr)[OBJ_ID_OFFSET] = htons((UINT16)objectId); // 2 bytes + NormPayloadId payloadId(fecId, fecM, ptr + 1); + payloadId.SetFecPayloadId(blockId, symbolId, blockLen); + length += itemLength; return true; } else @@ -171,7 +174,9 @@ bool NormRepairRequest::AppendRepairItem(const NormObjectId& objectId, } } // end NormRepairRequest::AppendRepairItem() -bool NormRepairRequest::AppendRepairRange(const NormObjectId& startObjectId, +bool NormRepairRequest::AppendRepairRange(UINT8 fecId, + UINT8 fecM, + const NormObjectId& startObjectId, const NormBlockId& startBlockId, UINT16 startBlockLen, UINT16 startSymbolId, @@ -180,28 +185,28 @@ bool NormRepairRequest::AppendRepairRange(const NormObjectId& startObjectId, UINT16 endBlockLen, UINT16 endSymbolId) { - DMSG(4, "NormRepairRequest::AppendRepairRange(%hu:%lu:%hu->%hu:%lu:%hu) ...\n", + PLOG(PL_TRACE, "NormRepairRequest::AppendRepairRange(%hu:%lu:%hu->%hu:%lu:%hu) ...\n", (UINT16)startObjectId, (UINT32)startBlockId, (UINT32)startSymbolId, (UINT16)endObjectId, (UINT32)endBlockId, (UINT32)endSymbolId); - if (buffer_len >= (length+ITEM_LIST_OFFSET+RepairRangeLength())) + // Note a "range" is two repair items + UINT16 rangeLength = RepairRangeLength(fecId); + if (buffer_len >= (ITEM_LIST_OFFSET+length+rangeLength)) { // range start UINT32* ptr = buffer + (length + ITEM_LIST_OFFSET)/4; - ((UINT8*)ptr)[FEC_ID_OFFSET] = (char)129; + ((UINT8*)ptr)[FEC_ID_OFFSET] = fecId; ((UINT8*)ptr)[RESERVED_OFFSET] = 0; ((UINT16*)ptr)[OBJ_ID_OFFSET] = htons((UINT16)startObjectId); - ptr[BLOCK_ID_OFFSET] = htonl((UINT32)startBlockId); - ((UINT16*)ptr)[BLOCK_LEN_OFFSET] = htons((UINT16)startBlockLen); - ((UINT16*)ptr)[SYMBOL_ID_OFFSET] = htons((UINT16)startSymbolId); - ptr += (RepairItemLength()/4); + NormPayloadId startId(fecId, fecM, ptr + 1); + startId.SetFecPayloadId(startBlockId, startSymbolId, startBlockLen); // range end - ((UINT8*)ptr)[FEC_ID_OFFSET] = (char)129; + ptr += (rangeLength/8); // advance ptr to second part of repair range + ((UINT8*)ptr)[FEC_ID_OFFSET] = fecId; ((UINT8*)ptr)[RESERVED_OFFSET] = 0; ((UINT16*)ptr)[OBJ_ID_OFFSET] = htons((UINT16)endObjectId); - ptr[BLOCK_ID_OFFSET] = htonl((UINT32)endBlockId); - ((UINT16*)ptr)[BLOCK_LEN_OFFSET] = htons((UINT16)endBlockLen); - ((UINT16*)ptr)[SYMBOL_ID_OFFSET] = htons((UINT16)endSymbolId); - length += RepairRangeLength(); + NormPayloadId endId(fecId, fecM, ptr + 1); + endId.SetFecPayloadId(endBlockId, endSymbolId, endBlockLen); + length += rangeLength; return true; } else @@ -210,21 +215,23 @@ bool NormRepairRequest::AppendRepairRange(const NormObjectId& startObjectId, } } // end NormRepairRequest::AppendRepairRange() -bool NormRepairRequest::AppendErasureCount(const NormObjectId& objectId, - const NormBlockId& blockId, - UINT16 blockLen, - UINT16 erasureCount) +bool NormRepairRequest::AppendErasureCount(UINT8 fecId, + UINT8 fecM, + const NormObjectId& objectId, + const NormBlockId& blockId, + UINT16 blockLen, + UINT16 erasureCount) { - if (buffer_len >= (ITEM_LIST_OFFSET+length+ErasureItemLength())) + UINT16 itemLength = ErasureItemLength(fecId); + if (buffer_len >= (ITEM_LIST_OFFSET+length+itemLength)) { UINT32* ptr = buffer + (length + ITEM_LIST_OFFSET)/4; - ((UINT8*)ptr)[FEC_ID_OFFSET] = (char)129; + ((UINT8*)ptr)[FEC_ID_OFFSET] = fecId; ((UINT8*)ptr)[RESERVED_OFFSET] = 0; ((UINT16*)ptr)[OBJ_ID_OFFSET] = htons((UINT16)objectId); - ptr[BLOCK_ID_OFFSET] = htonl((UINT32)blockId); - ((UINT16*)ptr)[BLOCK_LEN_OFFSET] = htons((UINT16)blockLen); - ((UINT16*)ptr)[SYMBOL_ID_OFFSET] = htons((UINT16)erasureCount); - length += ErasureItemLength(); + NormPayloadId erasureId(fecId, fecM, ptr + 1); + erasureId.SetFecPayloadId(blockId, erasureCount, blockLen); + length += itemLength; return true; } else @@ -278,29 +285,36 @@ UINT16 NormRepairRequest::Unpack(const UINT32* bufferPtr, UINT16 bufferLen) } } // end NormRepairRequest::Unpack() -bool NormRepairRequest::RetrieveRepairItem(UINT16 offset, - NormObjectId* objectId, - NormBlockId* blockId, - UINT16* blockLen, - UINT16* symbolId) const +UINT16 NormRepairRequest::RetrieveRepairItem(UINT8 fecM, + UINT16 offset, + UINT8* fecId, + NormObjectId* objectId, + NormBlockId* blockId, + UINT16* blockLen, + UINT16* symbolId) const { - if (length >= (offset + RepairItemLength())) + if (length >= (offset + FEC_ID_OFFSET + 1)) { - const UINT32* ptr = buffer+(ITEM_LIST_OFFSET+offset)/4; + const UINT32* ptr = buffer + (ITEM_LIST_OFFSET+offset)/4; + *fecId = ((UINT8*)ptr)[FEC_ID_OFFSET]; + ASSERT(NormPayloadId::IsValid(*fecId)); + UINT16 itemLength = RepairItemLength(*fecId); + if (length < (offset + RepairItemLength(*fecId))) return 0; *objectId = ntohs(((UINT16*)ptr)[OBJ_ID_OFFSET]); - *blockId = ntohl( ptr[BLOCK_ID_OFFSET]); - *blockLen = ntohs(((UINT16*)ptr)[BLOCK_LEN_OFFSET]); - *symbolId = ntohs(((UINT16*)ptr)[SYMBOL_ID_OFFSET]); - return true; + NormPayloadId payloadId(*fecId, fecM, ptr + 1); + *blockId = payloadId.GetFecBlockId(); + *symbolId = payloadId.GetFecSymbolId(); + *blockLen = payloadId.GetFecBlockLength(); + return itemLength; } else { - return false; + return 0; } } // end NormRepairRequest::RetrieveRepairItem() -NormRepairRequest::Iterator::Iterator(NormRepairRequest& theRequest) - : request(theRequest), offset(0) +NormRepairRequest::Iterator::Iterator(const NormRepairRequest& theRequest, UINT8 fecId, UINT8 fecM) + : request(theRequest), fec_id(fecId), fec_m(fecM), offset(0) { } @@ -310,9 +324,16 @@ bool NormRepairRequest::Iterator::NextRepairItem(NormObjectId* objectId, UINT16* blockLen, UINT16* symbolId) { - if (request.RetrieveRepairItem(offset, objectId, blockId, blockLen, symbolId)) + UINT8 fecId; // for check + UINT16 itemLength = request.RetrieveRepairItem(fec_m, offset, &fecId, objectId, blockId, blockLen, symbolId); + if (0 != itemLength) { - offset += NormRepairRequest::RepairItemLength(); + if (fecId != fec_id) + { + PLOG(PL_ERROR, "NormRepairRequest::Iterator::NextRepairItem() received repair request with wrong fec_id?!\n"); + return false; + } + offset += itemLength; return true; } else @@ -322,7 +343,50 @@ bool NormRepairRequest::Iterator::NextRepairItem(NormObjectId* objectId, } // end NormRepairRequest::Iterator::NextRepairItem() +// Helper function to log contents of a NormNack (or other message with repair request content) +void LogRepairContent(const UINT32* buffer, UINT16 bufferLen, UINT8 fecId, UINT8 fecM) +{ + NormRepairRequest req; + UINT16 requestLength = 0; + while ((requestLength = req.Unpack(buffer, bufferLen))) + { + // Point "buffer" to next request and adjust "bufferLen" + buffer += (requestLength/4); + bufferLen -= requestLength; + req.Log(fecId, fecM); + } +} // end void LogRepairContent() +void NormRepairRequest::Log(UINT8 fecId, UINT8 fecM) const +{ + Iterator iterator(*this, fecId, fecM); + NormObjectId objId; + NormBlockId blkId; + UINT16 blkLen, segId; + while (iterator.NextRepairItem(&objId, &blkId, &blkLen, &segId)) + { + if (FlagIsSet(SEGMENT)) + PLOG(PL_ALWAYS, "RepairItem> %hu:%lu:%hu", (UINT16)objId, (UINT32)blkId, (UINT16)segId); + else if (FlagIsSet(BLOCK)) + PLOG(PL_ALWAYS, "RepairItem> %hu:%lu", (UINT16)objId, (UINT32)blkId, (UINT16)segId); + else + PLOG(PL_ALWAYS, "RepairItem> %hu", (UINT16)objId); + if (form == RANGES) + { + iterator.NextRepairItem(&objId, &blkId, &blkLen, &segId); + if (FlagIsSet(SEGMENT)) + PLOG(PL_ALWAYS, " -> %hu:%lu:%hu", (UINT16)objId, (UINT32)blkId, (UINT16)segId); + else if (FlagIsSet(BLOCK)) + PLOG(PL_ALWAYS, " -> %hu:%lu", (UINT16)objId, (UINT32)blkId, (UINT16)segId); + else + PLOG(PL_ALWAYS, " -> %hu", (UINT16)objId); + } + if (FlagIsSet(INFO)) + PLOG(PL_ALWAYS, " INFO\n"); + else + PLOG(PL_ALWAYS, "\n"); + } +} // end NormRepairRequest::Log() NormMessageQueue::NormMessageQueue() : head(NULL), tail(NULL) diff --git a/common/normNode.cpp b/src/common/normNode.cpp similarity index 61% rename from common/normNode.cpp rename to src/common/normNode.cpp index 8e02f37..0daf7d2 100644 --- a/common/normNode.cpp +++ b/src/common/normNode.cpp @@ -1,6 +1,10 @@ #include "normNode.h" #include "normSession.h" +#include "normEncoderMDP.h" +#include "normEncoderRS8.h" // 8-bit Reed-Solomon encoder of RFC 5510 +#include "normEncoderRS16.h" // 16-bit Reed-Solomon encoder of RFC 5510 + NormNode::NormNode(class NormSession& theSession, NormNodeId nodeId) : session(theSession), id(nodeId), reference_count(1), parent(NULL), right(NULL), left(NULL) @@ -22,12 +26,19 @@ void NormNode::Release() if (reference_count) reference_count--; else - DMSG(0, "NormNode::Release() releasing non-retained node?!\n"); + PLOG(PL_ERROR, "NormNode::Release() releasing non-retained node?!\n"); if (0 == reference_count) delete this; } // end NormNode::Release() -const NormNodeId& NormNode::LocalNodeId() const {return session.LocalNodeId();} +const NormNodeId& NormNode::LocalNodeId() const + {return session.LocalNodeId();} + +NormNode::Accumulator::Accumulator() + : msb(0), lsb(0) +{ +} + NormCCNode::NormCCNode(class NormSession& theSession, NormNodeId nodeId) @@ -40,40 +51,54 @@ NormCCNode::~NormCCNode() { } -const double NormServerNode::DEFAULT_NOMINAL_INTERVAL = 2*NormSession::DEFAULT_GRTT_ESTIMATE; -const double NormServerNode::ACTIVITY_INTERVAL_MIN = 1.0; // 1 second min activity timeout + +NormSenderNode::CmdBuffer::CmdBuffer() + : length(0), next(NULL) +{ +} + +NormSenderNode::CmdBuffer::~CmdBuffer() +{ +} + +const double NormSenderNode::DEFAULT_NOMINAL_INTERVAL = 2*NormSession::DEFAULT_GRTT_ESTIMATE; +const double NormSenderNode::ACTIVITY_INTERVAL_MIN = 1.0; // 1 second min activity timeout -NormServerNode::NormServerNode(class NormSession& theSession, NormNodeId nodeId) - : NormNode(theSession, nodeId), instance_id(0), synchronized(false), sync_id(0), - max_pending_range(256), is_open(false), segment_size(0), ndata(0), nparity(0), +NormSenderNode::NormSenderNode(class NormSession& theSession, NormNodeId nodeId) + : NormNode(theSession, nodeId), instance_id(0), robust_factor(session.GetRxRobustFactor()), + synchronized(false), sync_id(0), + is_open(false), segment_size(0), fec_m(0), ndata(0), nparity(0), repair_boundary(BLOCK_BOUNDARY), decoder(NULL), erasure_loc(NULL), retrieval_loc(NULL), retrieval_pool(NULL), - cc_sequence(0), cc_enable(false), cc_rate(0.0), + cc_sequence(0), cc_enable(false), cc_feedback_needed(false), cc_rate(0.0), rtt_confirmed(false), is_clr(false), is_plr(false), - slow_start(true), send_rate(0.0), recv_rate(0.0), recv_accumulator(0), - nominal_packet_size(0), recv_total(0), recv_goodput(0), resync_count(0), + slow_start(true), send_rate(0.0), recv_rate(0.0), recv_rate_prev(0.0), + nominal_packet_size(0), cmd_buffer_head(NULL), cmd_buffer_tail(NULL), + cmd_buffer_pool(NULL), resync_count(0), nack_count(0), suppress_count(0), completion_count(0), failure_count(0) { - repair_boundary = session.ClientGetDefaultRepairBoundary(); - default_nacking_mode = session.ClientGetDefaultNackingMode(); - unicast_nacks = session.ClientGetUnicastNacks(); - // (TBD) get "max_pending_range" value from NormSession parameter + repair_boundary = session.ReceiverGetDefaultRepairBoundary(); + sync_policy = session.ReceiverGetDefaultSyncPolicy(); + default_nacking_mode = session.ReceiverGetDefaultNackingMode(); + unicast_nacks = session.ReceiverGetUnicastNacks(); + + max_pending_range = session.GetRxCacheMax(); - repair_timer.SetListener(this, &NormServerNode::OnRepairTimeout); + repair_timer.SetListener(this, &NormSenderNode::OnRepairTimeout); repair_timer.SetInterval(0.0); repair_timer.SetRepeat(1); - activity_timer.SetListener(this, &NormServerNode::OnActivityTimeout); + activity_timer.SetListener(this, &NormSenderNode::OnActivityTimeout); double activityInterval = 2*NormSession::DEFAULT_GRTT_ESTIMATE*session.GetTxRobustFactor(); if (activityInterval < ACTIVITY_INTERVAL_MIN) activityInterval = ACTIVITY_INTERVAL_MIN; activity_timer.SetInterval(activityInterval); activity_timer.SetRepeat(robust_factor); - cc_timer.SetListener(this, &NormServerNode::OnCCTimeout); + cc_timer.SetListener(this, &NormSenderNode::OnCCTimeout); cc_timer.SetInterval(0.0); cc_timer.SetRepeat(1); - ack_timer.SetListener(this, &NormServerNode::OnAckTimeout); + ack_timer.SetListener(this, &NormSenderNode::OnAckTimeout); ack_timer.SetInterval(0.0); ack_timer.SetRepeat(0); @@ -90,35 +115,37 @@ NormServerNode::NormServerNode(class NormSession& theSession, NormNodeId nodeId) rtt_estimate = NormUnquantizeRtt(rtt_quantized); loss_estimator.SetLossEventWindow(NormSession::DEFAULT_GRTT_ESTIMATE); + loss_estimator.SetIgnoreLoss(session.GetEcnIgnoreLoss()); + loss_estimator.SetTolerateLoss(session.GetCCTolerateLoss()); prev_update_time.tv_sec = 0; - prev_update_time.tv_usec = 0; - + prev_update_time.tv_usec = 0; } -NormServerNode::~NormServerNode() + +NormSenderNode::~NormSenderNode() { Close(); } -bool NormServerNode::Open(UINT16 instanceId) +bool NormSenderNode::Open(UINT16 instanceId) { instance_id = instanceId; if (!rx_table.Init(max_pending_range)) { - DMSG(0, "NormServerNode::Open() rx_table init error\n"); + PLOG(PL_FATAL, "NormSenderNode::Open() rx_table init error\n"); Close(); return false; } if (!rx_pending_mask.Init(max_pending_range, 0x0000ffff)) { - DMSG(0, "NormServerNode::Open() rx_pending_mask init error\n"); + PLOG(PL_FATAL, "NormSenderNode::Open() rx_pending_mask init error\n"); Close(); return false; } if (!rx_repair_mask.Init(max_pending_range, 0x0000ffff)) { - DMSG(0, "NormServerNode::Open() rx_repair_mask init error\n"); + PLOG(PL_FATAL, "NormSenderNode::Open() rx_repair_mask init error\n"); Close(); return false; } @@ -126,22 +153,45 @@ bool NormServerNode::Open(UINT16 instanceId) synchronized = false; resync_count = 0; // reset resync_count return true; -} // end NormServerNode::Open() +} // end NormSenderNode::Open() -void NormServerNode::Close() +void NormSenderNode::Close() { if (activity_timer.IsActive()) activity_timer.Deactivate(); if (repair_timer.IsActive()) repair_timer.Deactivate(); if (cc_timer.IsActive()) cc_timer.Deactivate(); FreeBuffers(); + + // Delete any command buffers from cmd_buffer queue + while (NULL != cmd_buffer_head) + { + CmdBuffer* buf = cmd_buffer_head; + cmd_buffer_head = buf->GetNext(); + delete buf; + } + // Delete any command buffers from cmd_buffer pool + while (NULL != cmd_buffer_pool) + { + CmdBuffer* buf = cmd_buffer_pool; + cmd_buffer_pool = buf->GetNext(); + delete buf; + } + rx_repair_mask.Destroy(); rx_pending_mask.Destroy(); rx_table.Destroy(); + if (NULL != decoder) { + delete decoder; + } synchronized = false; is_open = false; -} // end NormServerNode::Close() + +} // end NormSenderNode::Close() -bool NormServerNode::AllocateBuffers(UINT16 segmentSize, +bool NormSenderNode::AllocateBuffers(UINT8 fecId, + UINT16 fecInstanceId, + UINT8 fecM, + UINT16 segmentSize, UINT16 numData, UINT16 numParity) { @@ -151,7 +201,7 @@ bool NormServerNode::AllocateBuffers(UINT16 segmentSize, unsigned long maskSize = blockSize >> 3; if (0 != (blockSize & 0x07)) maskSize++; unsigned long blockStateSpace = sizeof(NormBlock) + blockSize * sizeof(char*) + 2*maskSize; - unsigned long bufferSpace = session.RemoteServerBufferSize(); + unsigned long bufferSpace = session.RemoteSenderBufferSize(); // The "bufferFactor" weight determines the ratio of segment buffers (blockSegmentSpace) to // allocated NormBlock (blockStateSpace). @@ -186,17 +236,16 @@ bool NormServerNode::AllocateBuffers(UINT16 segmentSize, if (!block_pool.Init(numBlocks, blockSize)) { - DMSG(0, "NormServerNode::AllocateBuffers() block_pool init error\n"); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() block_pool init error\n"); Close(); return false; } - // The extra byte of segments is used for marking segments (not any more!! TBD remove) - // which are "start segments" for messages encapsulated in - // a NormStreamObject - if (!segment_pool.Init(numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()+1)) + // The extra byte of segments is used for marking segments + // which are "start segments" for messages encapsulated in a NormStreamObject + if (!segment_pool.Init(numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength())) { - DMSG(0, "NormServerNode::AllocateBuffers() segment_pool init error\n"); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() segment_pool init error\n"); Close(); return false; } @@ -206,7 +255,7 @@ bool NormServerNode::AllocateBuffers(UINT16 segmentSize, // needed for block decoding (new rx buffer mgmt scheme) if (!(retrieval_pool = new char*[numData])) { - DMSG(0, "NormServerNode::AllocateBuffers() new retrieval_pool error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new retrieval_pool error: %s\n", GetErrorString()); Close(); return false; } @@ -214,10 +263,10 @@ bool NormServerNode::AllocateBuffers(UINT16 segmentSize, for (UINT16 i = 0; i < numData; i++) { // allocate segment with extra byte for stream flags ... - char* s = new char[segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()+1]; + char* s = new char[segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()]; if (NULL == s) { - DMSG(0, "NormServerNode::AllocateBuffers() new retrieval segment error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new retrieval segment error: %s\n", GetErrorString()); Close(); return false; } @@ -227,40 +276,110 @@ bool NormServerNode::AllocateBuffers(UINT16 segmentSize, if (!(retrieval_loc = new unsigned int[numData])) { - DMSG(0, "NormServerNode::AllocateBuffers() retrieval_loc allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() retrieval_loc allocation error: %s\n", GetErrorString()); Close(); return false; } if (NULL != decoder) delete decoder; - if (NULL == (decoder = new NormDecoderRS8a)) - { - DMSG(0, "NormServerNode::AllocateBuffers() new NormDecoderRSa error: %s\n", GetErrorString()); - Close(); - return false; - } - if (!decoder->Init(numData, numParity, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength())) + if (0 != numParity) { - DMSG(0, "NormServerNode::AllocateBuffers() decoder init error\n"); - Close(); - return false; - } - if (!(erasure_loc = new unsigned int[numParity])) + switch (fecId) + { + case 2: + if (8 == fecM) + { + if (NULL == (decoder = new NormDecoderRS8)) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString()); + Close(); + return false; + } + } + else if (16 == fecM) + { + if (NULL == (decoder = new NormDecoderRS16)) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS16 error: %s\n", GetErrorString()); + Close(); + return false; + } + } + else + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unsupported fecId=2 'm' value %d!\n", fecM); + Close(); + return false; + } + break; + case 5: + if (NULL == (decoder = new NormDecoderRS8)) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderRS8 error: %s\n", GetErrorString()); + Close(); + return false; + } + break; + case 129: +#ifdef ASSUME_MDP_FEC + if (NULL == (decoder = new NormDecoderMDP)) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderMDP error: %s\n", GetErrorString()); + Close(); + return false; + } +#else + if (0 == fecInstanceId) + { + if (NULL == (decoder = new NormDecoderRS8)) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() new NormDecoderMDP error: %s\n", GetErrorString()); + Close(); + return false; + } + } + else + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unknown fecId=129 instanceId!\n"); + Close(); + return false; + } +#endif // if/else ASSUME_MDP_FEC + break; + default: + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() error: unknown fecId>%d!\n", fecId); + Close(); + return false; + } + if (!decoder->Init(numData, numParity, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength())) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() decoder init error\n"); + Close(); + return false; + } + if (!(erasure_loc = new unsigned int[numParity])) + { + PLOG(PL_FATAL, "NormSenderNode::AllocateBuffers() erasure_loc allocation error: %s\n", GetErrorString()); + Close(); + return false; + } + } + else { - DMSG(0, "NormServerNode::AllocateBuffers() erasure_loc allocation error: %s\n", GetErrorString()); - Close(); - return false; - } + decoder = NULL; + } // end if/else (0 != numParity) segment_size = segmentSize; nominal_packet_size = (double)segmentSize; + fec_id = fecId; + fec_m = fecM; ndata = numData; nparity = numParity; IncrementResyncCount(); return true; -} // end NormServerNode::AllocateBuffers() +} // end NormSenderNode::AllocateBuffers() -void NormServerNode::FreeBuffers() +void NormSenderNode::FreeBuffers() { if (erasure_loc) { @@ -300,13 +419,88 @@ void NormServerNode::FreeBuffers() // We do the following to remember which _objects_ were pending rx_pending_mask.Set(objectId); } - segment_pool.Destroy(); block_pool.Destroy(); segment_size = ndata = nparity = 0; -} // end NormServerNode::FreeBuffers() +} // end NormSenderNode::FreeBuffers() -void NormServerNode::SetRobustFactor(int value) +unsigned long NormSenderNode::CurrentStreamBufferUsage() const +{ + unsigned long usage = 0; + NormObjectTable::Iterator it(rx_table); + NormObject* obj; + while (NULL != (obj = it.GetNextObject())) + { + if (obj->IsStream()) + usage += static_cast(obj)->CurrentBufferUsage(); + } + return usage; +} // end NormSenderNode::CurrentStreamBufferUsage() + +unsigned long NormSenderNode::PeakStreamBufferUsage() const +{ + unsigned long usage = 0; + NormObjectTable::Iterator it(rx_table); + NormObject* obj; + while (NULL != (obj = it.GetNextObject())) + { + if (obj->IsStream()) + usage += static_cast(obj)->PeakBufferUsage(); + } + return usage; +} // end NormSenderNode::PeakStreamBufferUsage() + +unsigned long NormSenderNode::StreamBufferOverunCount() const +{ + unsigned long count = 0; + NormObjectTable::Iterator it(rx_table); + NormObject* obj; + while (NULL != (obj = it.GetNextObject())) + { + if (obj->IsStream()) + count += static_cast(obj)->BufferOverunCount(); + } + return count; +} // end NormSenderNode::StreamBufferOverunCount() + + +bool NormSenderNode::ReadNextCmd(char* buffer, unsigned int* buflen) +{ + if (NULL == buflen) return false; // (TBD) indicate error type + if (NULL != cmd_buffer_head) + { + if ((NULL == buffer) || + (*buflen < cmd_buffer_head->GetContentLength())) + { + // User is just querying for content length size. + *buflen = cmd_buffer_head->GetContentLength(); + return false; + } + else + { + // a) remove cmd from cmd_buffer queue + CmdBuffer* buf = cmd_buffer_head; + cmd_buffer_head = buf->GetNext(); + if (NULL == cmd_buffer_head) + cmd_buffer_tail = NULL; + // b) copy content + *buflen = buf->GetContentLength(); + memcpy(buffer, buf->GetContent(), *buflen); + // c) put cmd into cmd_buffer pool + buf->Append(cmd_buffer_pool); + cmd_buffer_pool = buf; + return true; + } + } + else + { + // Tell user there is no cmd content to read + *buflen = 0; + return false; + } +} // end NormSenderNode::ReadNextCmd() + +void NormSenderNode::SetRobustFactor(int value) { robust_factor = value; // activity timer depends upon robust_factor @@ -316,13 +510,13 @@ void NormServerNode::SetRobustFactor(int value) activity_timer.SetInterval(activityInterval); activity_timer.SetRepeat(robust_factor); if (activity_timer.IsActive()) activity_timer.Reschedule(); -} // end NormServerNode::SetRobustFactor() +} // end NormSenderNode::SetRobustFactor() -void NormServerNode::UpdateGrttEstimate(UINT8 grttQuantized) +void NormSenderNode::UpdateGrttEstimate(UINT8 grttQuantized) { grtt_quantized = grttQuantized; grtt_estimate = NormUnquantizeRtt(grttQuantized); - DMSG(4, "NormServerNode::UpdateGrttEstimate() node>%lu server>%lu new grtt: %lf sec\n", + PLOG(PL_DEBUG, "NormSenderNode::UpdateGrttEstimate() node>%lu sender>%lu new grtt: %lf sec\n", LocalNodeId(), GetId(), grtt_estimate); // activity timer depends upon sender's grtt estimate // (TBD) do a proper rescaling here instead? @@ -332,10 +526,10 @@ void NormServerNode::UpdateGrttEstimate(UINT8 grttQuantized) if (activity_timer.IsActive()) activity_timer.Reschedule(); // (TBD) Scale/reschedule repair_timer and/or cc_timer??? session.Notify(NormController::GRTT_UPDATED, this, (NormObject*)NULL); -} // end NormServerNode::UpdateGrttEstimate() +} // end NormSenderNode::UpdateGrttEstimate() -void NormServerNode::HandleCommand(const struct timeval& currentTime, +void NormSenderNode::HandleCommand(const struct timeval& currentTime, const NormCmdMsg& cmd) { UINT8 grttQuantized = cmd.GetGrtt(); @@ -345,7 +539,7 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, { gsize_quantized = gsizeQuantized; gsize_estimate = NormUnquantizeGroupSize(gsizeQuantized); - DMSG(4, "NormServerNode::HandleCommand() node>%lu server>%lu new group size:%lf\n", + PLOG(PL_DEBUG, "NormSenderNode::HandleCommand() node>%lu sender>%lu new group size:%lf\n", LocalNodeId(), GetId(), gsize_estimate); } backoff_factor = (double)cmd.GetBackoffFactor(); @@ -356,28 +550,49 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, case NormCmdMsg::SQUELCH: { const NormCmdSquelchMsg& squelch = (const NormCmdSquelchMsg&)cmd; - // 1) Sync to squelch + if (!synchronized) + { + // Cache the remote sender's "fec_id" so we will + // build proper NACKs since we have no prior state + fec_id = squelch.GetFecId(); + if (2 == fec_id) // see comment in HandleObjectMessage() method on this + fec_m = 16; + else + fec_m = 8; + } + else + { + // TBD - should we confirm the sender's FEC config here??? + } + // 1) Sync to squelch (discards all objects prior to squelch objectId) NormObjectId objectId = squelch.GetObjectId(); - Sync(objectId); + Sync(objectId); // 2) Prune stream object if applicable NormObject* obj = rx_table.Find(objectId); - if (obj && (NormObject::STREAM == obj->GetType())) + if ((NULL != obj) && (NormObject::STREAM == obj->GetType())) { - NormBlockId blockId = squelch.GetFecBlockId(); + NormBlockId blockId = squelch.GetFecBlockId(fec_m); static_cast(obj)->Prune(blockId, true); } - // 3) (TBD) Go ahead and discard any invalidated objects - // (those listed with id's > objectId) - // (although they will eventually get discarded anyway) + // 3) Discard any invalidated objects (those listed in the squelch) + UINT16 objCount = squelch.GetInvalidObjectCount(); + for (UINT16 i = 0; i < objCount; i++) + { + NormObjectId objId = squelch.GetInvalidObjectId(i); + obj = rx_table.Find(objId); + if (NULL != obj) AbortObject(obj); + rx_pending_mask.Unset(objId); + } break; } case NormCmdMsg::ACK_REQ: - // (TBD) handle ack requests + // (TBD) handle ack requests (i.e. incl. app-defined ack requests) break; case NormCmdMsg::CC: { + // TBD - do some duplicate detection here ? const NormCmdCCMsg& cc = (const NormCmdCCMsg&)cmd; grtt_recv_time = currentTime; cc.GetSendTime(grtt_send_time); @@ -423,12 +638,13 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, is_clr = is_plr = false; } double maxBackoff; + if (is_clr || is_plr || !session.Address().IsMulticast()) { - // Respond immediately + // Respond immediately (i.e., no backoff, holdoff etc) maxBackoff = 0.0; if (cc_timer.IsActive()) cc_timer.Deactivate(); - cc_timer.ResetRepeat(); + cc_timer.ResetRepeat(); // makes sure timer phase is correct OnCCTimeout(cc_timer); break; } @@ -459,14 +675,14 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, r = MAX(r, 0.5); r = (r - 0.5) / 0.4; } - //DMSG(0, "NormServerNode::HandleCommand(CC) node>%lu bias:%lf recv_rate:%lf send_rate:%lf " + //DMSG(0, "NormSenderNode::HandleCommand(CC) node>%lu bias:%lf recv_rate:%lf send_rate:%lf " // "grtt:%lf gsize:%lf\n", // LocalNodeId(), r, 8.0e-03*recv_rate, 8.0e-03*send_rate, // backoffTime = 0.25 * r * maxBackoff + 0.75 * backoffTime; cc_timer.SetInterval(backoffTime); - DMSG(6, "NormServerNode::HandleCommand() node>%lu begin CC back-off: %lf sec)...\n", + PLOG(PL_TRACE, "NormSenderNode::HandleCommand() node>%lu begin CC back-off: %lf sec)...\n", LocalNodeId(), backoffTime); session.ActivateTimer(cc_timer); break; @@ -493,49 +709,61 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, break; } } + NormObjectId objectId = flush.GetObjectId(); + NormBlockId blockId = 0; + NormSegmentId symbolId = 0; + if (!synchronized) + { + // Cache the remote sender's "fec_id" so we will + // build proper NACKs since we have no prior state + fec_id = flush.GetFecId(); + if (2 == fec_id) // see comment in HandleObjectMessage() method on this + fec_m = 16; + else + fec_m = 8; + } + else if (flush.GetFecId() == fec_id) + { + blockId = flush.GetFecBlockId(fec_m); + symbolId = flush.GetFecSymbolId(fec_m); + } + else + { + // TBD - should we confirm the sender's FEC config here??? + } if (!synchronized) { if (doAck) { // Force sync since we're expected to ACK // and request repair for object indicated - Sync(flush.GetObjectId()); - SetPending(flush.GetObjectId()); + Sync(objectId); } else { // (TBD) optionally sync on any flush ? } } - if (0 != nodeCount) // this was a watermark flush + if (synchronized) { - if (!PassiveRepairCheck(flush.GetObjectId(), - flush.GetFecBlockId(), - flush.GetFecSymbolId())) + if (doAck) // this was a watermark flush { - if (doAck) + if (!PassiveRepairCheck(objectId, blockId, symbolId)) { - watermark_object_id = flush.GetObjectId(); - watermark_block_id = flush.GetFecBlockId(); - watermark_segment_id = flush.GetFecSymbolId(); + watermark_object_id = objectId; + watermark_block_id = blockId; + watermark_segment_id = symbolId; if (!ack_timer.IsActive()) { - double ackBackoff = UniformRand(grtt_estimate); + double ackBackoff = session.Address().IsMulticast() ? UniformRand(grtt_estimate) : 0.0; ack_timer.SetInterval(ackBackoff); session.ActivateTimer(ack_timer); } + break; // no pending repairs, skip regular "RepairCheck" } - break; // no pending repairs, skip regular "RepairCheck" } - } - if (synchronized) - { - const NormCmdFlushMsg& flush = (const NormCmdFlushMsg&)cmd; - UpdateSyncStatus(flush.GetObjectId()); - RepairCheck(NormObject::THRU_SEGMENT, - flush.GetObjectId(), - flush.GetFecBlockId(), - flush.GetFecSymbolId()); + UpdateSyncStatus(objectId); + RepairCheck(NormObject::THRU_SEGMENT, objectId, blockId, symbolId); } break; } @@ -564,15 +792,64 @@ void NormServerNode::HandleCommand(const struct timeval& currentTime, } break; } + + + case NormCmdMsg::APPLICATION: + { + PLOG(PL_TRACE, "NormSenderNode::HandleCommand(APPLICATION) node>%lu recvd app-defined cmd...\n", + LocalNodeId()); + const NormCmdAppMsg& appCmd = static_cast(cmd); + // 1) Buffer the received command either using a buffer structure + // cmd_buffer pool or allocating a new one as needed. + CmdBuffer* buf = cmd_buffer_pool; + if (NULL != buf) + cmd_buffer_pool = buf->GetNext(); + else + buf = new CmdBuffer(); + if (NULL == buf) + { + PLOG(PL_ERROR, "NormSenderNode::HandleCommand(APPLICATION) node>%lu NewCmdCBuffer() error: %s\n", + GetErrorString()); + } + else + { + unsigned int cmdLength = appCmd.GetContentLength(); + if ((cmdLength <= segment_size) || + ((0 == segment_size) && (cmdLength < 8192))) + { + // 2) Copy the app-defined command content into our buffer + buf->SetContent(appCmd.GetContent(), appCmd.GetContentLength()); + // 3) Append the buffer into our cmd_buffer FIFO queue + if (NULL != cmd_buffer_tail) + { + cmd_buffer_tail->Append(buf); + cmd_buffer_tail = buf; + } + else + { + cmd_buffer_head = cmd_buffer_tail = buf; + } + session.Notify(NormController::RX_CMD_NEW, this, NULL); + } + else + { + PLOG(PL_ERROR, "NormSenderNode::HandleCommand(APPLICATION) node>%lu error: " + "cmd content greater than sender's segment_size?!\n"); + buf->Append(cmd_buffer_pool); + cmd_buffer_pool = buf; + } + } + break; + } default: - DMSG(0, "NormServerNode::HandleCommand() recv'd unimplemented command!\n"); + PLOG(PL_ERROR, "NormSenderNode::HandleCommand() recv'd unimplemented command!\n"); break; } // end switch(flavor) -} // end NormServerNode::HandleCommand() +} // end NormSenderNode::HandleCommand() -void NormServerNode::HandleCCFeedback(UINT8 ccFlags, double ccRate) +void NormSenderNode::HandleCCFeedback(UINT8 ccFlags, double ccRate) { //ASSERT(cc_timer.IsActive() && cc_timer.GetRepeatCount()); if (0 == (ccFlags & NormCC::CLR)) @@ -628,9 +905,9 @@ void NormServerNode::HandleCCFeedback(UINT8 ccFlags, double ccRate) cc_timer.DecrementRepeatCount(); } } -} // end NormServerNode::HandleCCFeedback() +} // end NormSenderNode::HandleCCFeedback() -void NormServerNode::HandleAckMessage(const NormAckMsg& ack) +void NormSenderNode::HandleAckMessage(const NormAckMsg& ack) { // Does the CC feedback of this ACK suppress our CC feedback if (!is_clr && !is_plr && cc_timer.IsActive() && cc_timer.GetRepeatCount()) @@ -645,9 +922,9 @@ void NormServerNode::HandleAckMessage(const NormAckMsg& ack) } } } -} // end NormServerNode::HandleAckMessage() +} // end NormSenderNode::HandleAckMessage() -void NormServerNode::HandleNackMessage(const NormNackMsg& nack) +void NormSenderNode::HandleNackMessage(const NormNackMsg& nack) { // Does the CC feedback of this NACK suppress our CC feedback if (!is_clr && !is_plr && cc_timer.IsActive() && cc_timer.GetRepeatCount()) @@ -662,16 +939,16 @@ void NormServerNode::HandleNackMessage(const NormNackMsg& nack) } } } - // Clients also care about recvd NACKS for NACK suppression + // Receivers also care about recvd NACKS for NACK suppression if (repair_timer.IsActive() && repair_timer.GetRepeatCount()) HandleRepairContent(nack.GetRepairContent(), nack.GetRepairContentLength()); -} // end NormServerNode::HandleNackMessage() +} // end NormSenderNode::HandleNackMessage() -// Clients use this method to process NACK content overheard from other -// clients or via NORM_CMD(REPAIR_ADV) messages received from the server. +// Receivers use this method to process NACK content overheard from other +// receivers or via NORM_CMD(REPAIR_ADV) messages received from the sender. // Such content can "suppress" pending NACKs // (TBD) add provision to handle case when NORM_REPAIR_ADV_FLAG_LIMIT was set -void NormServerNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) +void NormSenderNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) { // Parse NACK and incorporate into repair state masks NormRepairRequest req; @@ -704,7 +981,7 @@ void NormServerNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) } bool repairInfo = req.FlagIsSet(NormRepairRequest::INFO); - NormRepairRequest::Iterator iterator(req); + NormRepairRequest::Iterator iterator(req, fec_id, fec_m); // assumes constant "m" NormObjectId nextObjectId, lastObjectId; NormBlockId nextBlockId, lastBlockId; UINT16 nextBlockLen, lastBlockLen; @@ -717,7 +994,7 @@ void NormServerNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) if (!iterator.NextRepairItem(&lastObjectId, &lastBlockId, &lastBlockLen, &lastSegmentId)) { - DMSG(0, "NormServerNode::HandleRepairContent() node>%lu recvd incomplete RANGE request!\n", + PLOG(PL_ERROR, "NormSenderNode::HandleRepairContent() node>%lu recvd incomplete RANGE request!\n", LocalNodeId()); continue; // (TBD) break/return instead??? } @@ -744,8 +1021,11 @@ void NormServerNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) break; } case OBJECT: - rx_repair_mask.SetBits(nextObjectId, lastObjectId - nextObjectId + 1); + { + UINT16 numBits = (UINT16)(lastObjectId - nextObjectId) + 1; + rx_repair_mask.SetBits(nextObjectId, numBits); break; + } case BLOCK: { if (nextObjectId != prevObjectId) freshObject = true; @@ -786,10 +1066,10 @@ void NormServerNode::HandleRepairContent(const UINT32* buffer, UINT16 bufferLen) } // end switch(requestLevel) } // end while (iterator.NextRepairItem()) } // end while (nack.UnpackRepairRequest()) -} // end NormServerNode::HandleRepairContent() +} // end NormSenderNode::HandleRepairContent() -void NormServerNode::CalculateGrttResponse(const struct timeval& currentTime, +void NormSenderNode::CalculateGrttResponse(const struct timeval& currentTime, struct timeval& grttResponse) const { grttResponse.tv_sec = grttResponse.tv_usec = 0; @@ -818,9 +1098,9 @@ void NormServerNode::CalculateGrttResponse(const struct timeval& currentTime, grttResponse.tv_sec += 1; } } -} // end NormServerNode::CalculateGrttResponse() +} // end NormSenderNode::CalculateGrttResponse() -void NormServerNode::DeleteObject(NormObject* obj) +void NormSenderNode::DeleteObject(NormObject* obj) { if (rx_table.Remove(obj)) { @@ -828,14 +1108,14 @@ void NormServerNode::DeleteObject(NormObject* obj) obj->Close(); obj->Release(); } -} // end NormServerNode::DeleteObject() +} // end NormSenderNode::DeleteObject() -NormBlock* NormServerNode::GetFreeBlock(NormObjectId objectId, NormBlockId blockId) +NormBlock* NormSenderNode::GetFreeBlock(NormObjectId objectId, NormBlockId blockId) { NormBlock* b = block_pool.Get(); if (!b) { - if (session.ClientIsSilent()) + if (session.ReceiverIsSilent() || session.RcvrIsRealtime()) { // forward iteration to find oldest older object with resources NormObjectTable::Iterator iterator(rx_table); @@ -887,9 +1167,9 @@ NormBlock* NormServerNode::GetFreeBlock(NormObjectId objectId, NormBlockId block } } return b; -} // end NormServerNode::GetFreeBlock() +} // end NormSenderNode::GetFreeBlock() -char* NormServerNode::GetFreeSegment(NormObjectId objectId, NormBlockId blockId) +char* NormSenderNode::GetFreeSegment(NormObjectId objectId, NormBlockId blockId) { if (segment_pool.IsEmpty()) { @@ -917,9 +1197,9 @@ char* NormServerNode::GetFreeSegment(NormObjectId objectId, NormBlockId blockId) } char* result = segment_pool.Get(); return result; -} // end NormServerNode::GetFreeSegment() +} // end NormSenderNode::GetFreeSegment() -void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) +void NormSenderNode::HandleObjectMessage(const NormObjectMsg& msg) { UINT8 grttQuantized = msg.GetGrtt(); if (grttQuantized != grtt_quantized) UpdateGrttEstimate(grttQuantized); @@ -928,72 +1208,145 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) { gsize_quantized = gsizeQuantized; gsize_estimate = NormUnquantizeGroupSize(gsizeQuantized); - DMSG(4, "NormServerNode::HandleObjectMessage() node>%lu server>%lu new group size: %lf\n", + PLOG(PL_DEBUG, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu new group size: %lf\n", LocalNodeId(), GetId(), gsize_estimate); } backoff_factor = (double)msg.GetBackoffFactor(); NormMsg::Type msgType = msg.GetType(); NormObjectId objectId = msg.GetObjectId(); + UINT8 fecId = msg.GetFecId(); + // The current NORM implementation assumes senders maintain a fixed, common + // set of FEC coding parameters for its transmissions. The buffers (on a + // "per-remote-sender basis") for receiver FEC processing are allocated here + // when: + // 1) A NORM_DATA message is received and the buffers have not + // been previously allocated, or + // 2) When the FEC parameters have changed (TBD) + // + bool allocateBuffers = true; + if (BuffersAllocated()) + { + if (fec_id == fecId) + allocateBuffers = false; // TBD - validate fec params? + else + FreeBuffers(); // force reallocation because fec id changed + } + NormBlockId blockId; NormSegmentId segmentId; if (NormMsg::INFO == msgType) { + fec_id = fecId; + // This assumes fec_id 2 (Reed Solomon) is only used when + // 16-bit RS code is needed (otherwise fec_id 5 should have been used) + // This is a limitation of the current RS specification where fec_m is + // only provided in FEC OTI. (Note we could dig into FEC OTI of NORM_INFO + // message, but FLUSH/SQUELCH commands can't do this anyway and because we + // defer buffer allocation until NORM_DATA is received, it doesn't really matter + // (Any NACKs generate will have zero-value FEC blockId / symbolId anyway) + if (2 == fecId) + fec_m = 16; + else + fec_m = 8; blockId = 0; segmentId = 0; } else { - const NormDataMsg& data = (const NormDataMsg&)msg; - // (TBD) verify source block length per new spec - blockId = data.GetFecBlockId(); - segmentId = data.GetFecSymbolId(); - - // The current NORM implementation assumes senders maintain a fixed, common - // set of FEC coding parameters for its transmissions. The buffers (on a - // "per-remote-server basis") for receiver FEC processing are allocated here - // when: - // 1) A NORM_DATA message is received and the buffers have not - // been previously allocated, or - // 2) When the FEC parameters have changed (TBD) - // - if (!BuffersAllocated()) + if (allocateBuffers) { - DMSG(4, "NormServerNode::HandleObjectMessage() node>%lu allocating server>%lu buffers ...\n", + PLOG(PL_DEBUG, "NormSenderNode::HandleObjectMessage() node>%lu allocating sender>%lu buffers ...\n", LocalNodeId(), GetId()); // Currently,, our implementation requires the FEC Object Transmission Information - // to properly allocate resources - NormFtiExtension fti; - while (msg.GetNextExtension(fti)) + // to properly allocate resources for FEC buffering and decoding + UINT16 fecInstanceId = 0; + UINT8 fecM = 8; + UINT16 segmentSize = 0; + UINT16 fecMaxBlockLen = 0; + UINT16 fecNumParity = 0; + bool noFTI = true; + switch (fecId) { - if (NormHeaderExtension::FTI == fti.GetType()) + case 2: { - // (TBD) pass "fec_id" to Open() method too - if (!AllocateBuffers(fti.GetSegmentSize(), - fti.GetFecMaxBlockLen(), - fti.GetFecNumParity())) + NormFtiExtension2 fti; + while (msg.GetNextExtension(fti)) { - DMSG(0, "NormServerNode::HandleObjectMessage() node>%lu server>%lu buffer allocation error\n", - LocalNodeId(), GetId()); - // (TBD) notify app of error ?? - return; - } + if (NormHeaderExtension::FTI == fti.GetType()) + { + ASSERT(1 == fti.GetFecGroupSize()); // TBD - allow for different groupings + fecM = fti.GetFecFieldSize(); + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + noFTI = false; + break; + } + } break; } - } - if (!BuffersAllocated()) + case 5: + { + NormFtiExtension5 fti; + while (msg.GetNextExtension(fti)) + { + if (NormHeaderExtension::FTI == fti.GetType()) + { + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + noFTI = false; + break; + } + } + break; + } + case 129: + { + NormFtiExtension129 fti; + while (msg.GetNextExtension(fti)) + { + if (NormHeaderExtension::FTI == fti.GetType()) + { + fecInstanceId = fti.GetFecInstanceId(); + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + noFTI = false; + break; + } + } + break; + } + default: + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu unknown fec_id type:%d\n", + LocalNodeId(), GetId(), fecId); + return; + + } // end switch (fecId) + + if (noFTI) { - DMSG(0, "NormServerNode::HandleObjectMessage() node>%lu server>%lu - no FTI provided!\n", + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu - no FTI provided!\n", LocalNodeId(), GetId()); // (TBD) notify app of error ?? return; - } - } - else - { - // (TBD) make sure FEC parameters are still the same. - } - } + } + if (!AllocateBuffers(fecId, fecInstanceId, fecM, segmentSize, fecMaxBlockLen, fecNumParity)) + { + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu buffer allocation error\n", + LocalNodeId(), GetId()); + // (TBD) notify app of error ?? + return; + } + } // end if (allocateBuffers) + + ASSERT(0 != fec_m); + const NormDataMsg& data = static_cast(msg); + blockId = data.GetFecBlockId(fec_m); + segmentId = data.GetFecSymbolId(fec_m); + } // end if/else (NormMsg::INFO == msgType) ObjectStatus status; if (synchronized) @@ -1006,7 +1359,6 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) if (SyncTest(msg)) { Sync(objectId); - SetPending(objectId); status = OBJ_NEW; } else @@ -1015,7 +1367,7 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) // printing too often while "waiting to sync" ... if (0 == sync_id) { - DMSG(0, "NormServerNode::HandleObjectMessage() waiting to sync ...\n"); + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() waiting to sync ...\n"); sync_id = 100; } else @@ -1030,7 +1382,7 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) switch (status) { case OBJ_PENDING: - if ((obj = rx_table.Find(objectId))) + if (NULL != (obj = rx_table.Find(objectId))) break; case OBJ_NEW: { @@ -1038,7 +1390,7 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) { if (!(obj = new NormStreamObject(session, this, objectId))) { - DMSG(0, "NormServerNode::HandleObjectMessage() new NORM_OBJECT_STREAM error: %s\n", + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_STREAM error: %s\n", GetErrorString()); } } @@ -1050,7 +1402,7 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) if (!(obj = new NormFileObject(session, this, objectId))) #endif { - DMSG(0, "NormServerNode::HandleObjectMessage() new NORM_OBJECT_FILE error: %s\n", + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_FILE error: %s\n", GetErrorString()); } } @@ -1058,76 +1410,125 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) { if (!(obj = new NormDataObject(session, this, objectId))) { - DMSG(0, "NormServerNode::HandleObjectMessage() new NORM_OBJECT_DATA error: %s\n", + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() new NORM_OBJECT_DATA error: %s\n", GetErrorString()); } } - + // TBD - if buffers were _just_ allocated above, we could avoid this second + // parsing of FTI header extension by promoting the FEC parameters learned + // above into stack variable that are still accessible here and adding a + // state variable to indicate they are valid if (NULL != obj) { + ASSERT(rx_table.CanInsert(objectId)); + ASSERT(rx_pending_mask.Test(objectId)); rx_table.Insert(obj); - if (129 == msg.GetFecId()) + UINT8 fecId = msg.GetFecId(); + UINT8 fecM = 8; + UINT16 segmentSize = 0; + UINT16 fecMaxBlockLen = 0; + UINT16 fecNumParity = 0; + NormObjectSize objectSize; + bool noFTI = true; + switch (fecId) { - - // Look for FEC Object Transport Information header extension - NormFtiExtension fti; - while (msg.GetNextExtension(fti)) + case 2: { - if (NormHeaderExtension::FTI == fti.GetType()) + NormFtiExtension2 fti; + while (msg.GetNextExtension(fti)) { -#ifndef ASSUME_MDP_FEC - if (0 != fti.GetFecInstanceId()) + if (NormHeaderExtension::FTI == fti.GetType()) { - DMSG(0, "NormServerNode::HandleObjectMessage() error: invalid FEC instance id\n"); - DeleteObject(obj); - obj = NULL; + ASSERT(1 == fti.GetFecGroupSize()); // TBD - allow for different groupings + fecM = fti.GetFecFieldSize(); + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + objectSize = fti.GetObjectSize(); + noFTI = false; + break; } - else -#endif // !ASSUME_MDP_FEC - if (obj->Open(fti.GetObjectSize(), - msg.FlagIsSet(NormObjectMsg::FLAG_INFO), - fti.GetSegmentSize(), - fti.GetFecMaxBlockLen(), - fti.GetFecNumParity())) - { - 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); - DMSG(8, "NormServerNode::HandleObjectMessage() node>%lu server>%lu new obj>%hu\n", - LocalNodeId(), GetId(), (UINT16)objectId); - } - else - { - DMSG(0, "NormServerNode::HandleObjectMessage() object not accepted\n"); - DeleteObject(obj); - obj = NULL; - } - } - else - { - DMSG(0, "NormServerNode::HandleObjectMessage() error opening object\n"); - DeleteObject(obj); - obj = NULL; - } - break; } + break; } - if ((NULL != obj) && !obj->IsOpen()) + case 5: { - DMSG(0, "NormServerNode::HandleObjectMessage() node>%lu server>%lu " + NormFtiExtension5 fti; + while (msg.GetNextExtension(fti)) + { + if (NormHeaderExtension::FTI == fti.GetType()) + { + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + objectSize = fti.GetObjectSize(); + noFTI = false; + break; + } + } + break; + } + case 129: + { + NormFtiExtension129 fti; + while (msg.GetNextExtension(fti)) + { + if (NormHeaderExtension::FTI == fti.GetType()) + { + segmentSize = fti.GetSegmentSize(); + fecMaxBlockLen = fti.GetFecMaxBlockLen(); + fecNumParity = fti.GetFecNumParity(); + objectSize = fti.GetObjectSize(); + noFTI = false; + break; + } + } + break; + } + default: + segmentSize = fecMaxBlockLen = fecNumParity = 0; + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu unknown fec_id type:%d\n", + LocalNodeId(), GetId(), fecId); + return; + + } // end switch (fecId) + + if (noFTI) + { + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu " "new obj>%hu - no FTI provided!\n", LocalNodeId(), GetId(), (UINT16)objectId); + DeleteObject(obj); + obj = NULL; + } + else if (obj->RxOpen(objectSize, + msg.FlagIsSet(NormObjectMsg::FLAG_INFO), + segmentSize, + fecId, + fecM, + fecMaxBlockLen, + fecNumParity)) + { + 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); + PLOG(PL_DETAIL, "NormSenderNode::HandleObjectMessage() node>%lu sender>%lu new obj>%hu\n", + LocalNodeId(), GetId(), (UINT16)objectId); + } + else + { + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() object not accepted\n"); DeleteObject(obj); - obj = NULL; + obj = NULL; } } - else + else { - DMSG(0, "NormServerNode::HandleObjectMessage() error: invalid FEC type\n"); + PLOG(PL_ERROR, "NormSenderNode::HandleObjectMessage() error opening object\n"); DeleteObject(obj); - obj = NULL; + obj = NULL; } } break; @@ -1138,7 +1539,9 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) default: ASSERT(0); break; - } // end switch(status) + } // end switch(status) + + if (obj) { @@ -1187,26 +1590,35 @@ void NormServerNode::HandleObjectMessage(const NormObjectMsg& msg) RepairCheck(NormObject::THRU_INFO, objectId, blockId, segmentId); break; } -} // end NormServerNode::HandleObjectMessage() +} // end NormSenderNode::HandleObjectMessage() -bool NormServerNode::SyncTest(const NormObjectMsg& msg) const +bool NormSenderNode::SyncTest(const NormObjectMsg& msg) const { - // Allow sync on stream at any time - bool result = msg.FlagIsSet(NormObjectMsg::FLAG_STREAM); - - // Allow sync on INFO or block zero DATA message - result = result || (NormMsg::INFO == msg.GetType()) ? - true : (0 == ((const NormDataMsg&)msg).GetFecBlockId()); - - // Never sync on repair messages - result = result && !msg.FlagIsSet(NormObjectMsg::FLAG_REPAIR); - - return result; - -} // end NormServerNode::SyncTest() + switch (sync_policy) + { + case SYNC_CURRENT: // default, more conservative "sync policy" + { + // Allow sync on stream at any time + bool result = msg.FlagIsSet(NormObjectMsg::FLAG_STREAM); + // Allow sync on INFO or block zero DATA message + result = result || (NormMsg::INFO == msg.GetType()) ? + true : (0 == ((const NormDataMsg&)msg).GetFecBlockId(fec_m)); + // Never sync on repair messages + result = result && !msg.FlagIsSet(NormObjectMsg::FLAG_REPAIR); + return result; + } + + case SYNC_ALL: // sync on anything + return true; + + default: + ASSERT(0); // should never occur + return false; + } +} // end NormSenderNode::SyncTest() // a little helper method -void NormServerNode::AbortObject(NormObject* obj) +void NormSenderNode::AbortObject(NormObject* obj) { // it it's a file, close it first, so app can do something if (NormObject::FILE == obj->GetType()) @@ -1218,7 +1630,7 @@ void NormServerNode::AbortObject(NormObject* obj) session.Notify(NormController::RX_OBJECT_ABORTED, this, obj); DeleteObject(obj); failure_count++; -} // end NormServerNode::AbortObject() +} // end NormSenderNode::AbortObject() // This method establishes the sync point "sync_id" @@ -1228,16 +1640,16 @@ void NormServerNode::AbortObject(NormObject* obj) // are ignored. // The related member variables and their purpose: // "sync_id" - sync point object id, gets rolled upward -// in NormServerNode::SetPending() to deal with wrap +// in NormSenderNode::SetPending() to deal with wrap // // "next_id" - id of next expected pending object -// (set in NormServerNode::SetPending()) +// (set in NormSenderNode::SetPending()) // // "max_pending_object" - max object id heard from sender -// (inited in NormServerNode::Sync() on -// initial sync, update in NormServerNode::RepairCheck() +// (inited in NormSenderNode::Sync() on +// initial sync, update in NormSenderNode::RepairCheck() // -void NormServerNode::Sync(NormObjectId objectId) +void NormSenderNode::Sync(NormObjectId objectId) { if (synchronized) { @@ -1254,6 +1666,7 @@ void NormServerNode::Sync(NormObjectId objectId) AbortObject(obj); } rx_pending_mask.Clear(); + resync_count++; } else if (objectId > firstPending) { @@ -1263,8 +1676,9 @@ void NormServerNode::Sync(NormObjectId objectId) { AbortObject(obj); } - unsigned long numBits = (UINT16)(objectId - firstPending) + 1; + unsigned long numBits = (UINT16)(objectId - firstPending); rx_pending_mask.UnsetBits(firstPending, numBits); + resync_count++; } } if ((next_id < objectId) || ((next_id - objectId) > max_pending_range)) @@ -1273,30 +1687,71 @@ void NormServerNode::Sync(NormObjectId objectId) } sync_id = objectId; ASSERT(OBJ_INVALID != GetObjectStatus(objectId)); + if (OBJ_NEW == GetObjectStatus(objectId)) + SetPending(objectId); } else { ASSERT(!rx_pending_mask.IsSet()); - sync_id = next_id = max_pending_object = objectId; synchronized = true; + switch (sync_policy) + { + case SYNC_CURRENT: // this is the usual default + sync_id = next_id = max_pending_object = objectId; + break; + case SYNC_ALL: // gratuitously sync for anything in our "range" + sync_id = next_id = objectId - max_pending_range + 1; + max_pending_object = objectId; + break; + } + SetPending(objectId); // inclusively sets pending mask for next_id..objectId } -} // end NormServerNode::Sync() +} // end NormSenderNode::Sync() -NormServerNode::ObjectStatus NormServerNode::UpdateSyncStatus(const NormObjectId& objectId) +NormSenderNode::ObjectStatus NormSenderNode::UpdateSyncStatus(const NormObjectId& objectId) { ASSERT(synchronized); ObjectStatus status = GetObjectStatus(objectId); switch (status) { case OBJ_INVALID: - // (TBD) We may want to control re-sync policy options + { + // (TBD) We may want to control resync policy options // or revert to fresh sync if sync is totally lost, // otherwise SQUELCH process will get things in order - DMSG(2, "NormServerNode::UpdateSyncStatus() node>%lu re-syncing to server>%lu...\n", - LocalNodeId(), GetId()); - Sync(objectId); - resync_count++; + PLOG(PL_INFO, "NormSenderNode::UpdateSyncStatus() node>%lu resync to sender>%lu obj>%hu...\n", + LocalNodeId(), GetId(), (UINT16)objectId); + + NormObjectId syncId = objectId; + /* + // This code avoids grosser resyncs (if uncommented) ... + // However, attempts at finer-grained resync + // (i.e. preserving some partially-received objects) + // has tended to exhibit an inability to ever "catch up" + // But, note newer flow control feature might help here + // so this might be worthwhile to some day uncomment + // and experiment! + if (rx_pending_mask.IsSet()) + { + NormObjectId lastPending(65535); + GetLastPending(lastPending); + TRACE("lp:%hu\n", (UINT16)lastPending); + if (syncId > lastPending) + { + UINT16 delta = syncId - lastPending; + TRACE("SYNC DELTA = %d\n", delta); + if (delta < max_pending_range) + { + syncId -= (max_pending_range - 1); + } + } + } + */ + Sync(syncId); + //resync_count++; (this is now incremented in NormNode::Sync()) status = OBJ_NEW; + break; + } case OBJ_NEW: SetPending(objectId); break; @@ -1304,9 +1759,9 @@ NormServerNode::ObjectStatus NormServerNode::UpdateSyncStatus(const NormObjectId break; } return status; -} // end NormServerNode::UpdateSyncStatus() +} // end NormSenderNode::UpdateSyncStatus() -void NormServerNode::SetPending(NormObjectId objectId) +void NormSenderNode::SetPending(NormObjectId objectId) { ASSERT(synchronized); ASSERT(OBJ_NEW == GetObjectStatus(objectId)); @@ -1316,22 +1771,30 @@ void NormServerNode::SetPending(NormObjectId objectId) } else { - rx_pending_mask.SetBits(next_id, objectId - next_id + 1); + UINT16 numBits = (UINT16)(objectId - next_id) + 1; + rx_pending_mask.SetBits(next_id, numBits); next_id = objectId + 1; // This prevents the "sync_id" from getting stale GetFirstPending(sync_id); } -} // end NormServerNode::SetPending() +} // end NormSenderNode::SetPending() -NormServerNode::ObjectStatus NormServerNode::GetObjectStatus(const NormObjectId& objectId) const +NormSenderNode::ObjectStatus NormSenderNode::GetObjectStatus(const NormObjectId& objectId) const { if (synchronized) { if (objectId < sync_id) { - if ((sync_id - objectId) > max_pending_range) + // TBD - is there a better way this should be done? + // If the object is a "little bit" old, it is probably an + // object we recently completed. If it is _very_ old, + // we are probably "out of sync" with the sender? Perhaps + // this is too aggressive a resync rule? + if ((sync_id - objectId) > 2*max_pending_range) { + PLOG(PL_WARN, "NormSenderNode::GetObjectStatus() INVALID object>%hu sync_id>%hu\n", + (UINT16)objectId, (UINT16)sync_id); return OBJ_INVALID; } else @@ -1358,10 +1821,15 @@ NormServerNode::ObjectStatus NormServerNode::GetObjectStatus(const NormObjectId& { if (rx_pending_mask.CanSet(objectId)) { + ASSERT(rx_table.CanInsert(objectId)); return OBJ_NEW; } else { + NormObjectId fp; + GetFirstPending(fp); + PLOG(PL_WARN, "NormSenderNode::GetObjectStatus() INVALID object>%hu firstPending>%hu\n", + (UINT16)objectId, (UINT16)fp); return OBJ_INVALID; } } @@ -1370,10 +1838,14 @@ NormServerNode::ObjectStatus NormServerNode::GetObjectStatus(const NormObjectId& NormObjectId delta = objectId - next_id + 1; if (delta > NormObjectId((UINT16)rx_pending_mask.GetSize())) { + PLOG(PL_WARN, "NormSenderNode::GetObjectStatus() INVALID object>%hu next_id>%hu\n", + (UINT16)objectId, (UINT16)next_id); + return OBJ_INVALID; } else { + ASSERT(rx_table.CanInsert(objectId)); return OBJ_NEW; } } @@ -1384,11 +1856,12 @@ NormServerNode::ObjectStatus NormServerNode::GetObjectStatus(const NormObjectId& { return OBJ_NEW; } -} // end NormServerNode::ObjectStatus() +} // end NormSenderNode::GetObjectStatus() // This is a "passive" THRU_SEGMENT repair check // (used to for watermark ack check) -bool NormServerNode::PassiveRepairCheck(NormObjectId objectId, +// Returns true if repairs are pending before and thru the given object:block:segment +bool NormSenderNode::PassiveRepairCheck(NormObjectId objectId, NormBlockId blockId, NormSegmentId segmentId) { @@ -1403,17 +1876,24 @@ bool NormServerNode::PassiveRepairCheck(NormObjectId objectId, else if (nextId == objectId) { NormObject* obj = rx_table.Find(nextId); - if (obj) + if (NULL != obj) return obj->PassiveRepairCheck(blockId, segmentId); else return true; // entire object pending } + else + { + return false; // it's an object already received (watermark past) + } } - return false; -} // end NormServerNode::PassiveRepairCheck() + else + { + return (OBJ_NEW == GetObjectStatus(objectId)); + } +} // end NormSenderNode::PassiveRepairCheck() // This is the "active" repair check, which may activate NACKing -void NormServerNode::RepairCheck(NormObject::CheckLevel checkLevel, +void NormSenderNode::RepairCheck(NormObject::CheckLevel checkLevel, NormObjectId objectId, NormBlockId blockId, NormSegmentId segmentId) @@ -1425,27 +1905,23 @@ void NormServerNode::RepairCheck(NormObject::CheckLevel checkLevel, { // repair timer inactive bool startTimer = false; - NormObjectId nextId; - if (GetFirstPending(nextId)) + NormObjectId firstId; + if (GetFirstPending(firstId)) { - if (rx_repair_mask.IsSet()) rx_repair_mask.Clear(); + NormObjectId nextId = firstId; do { if (nextId > objectId) break; NormObject* obj = rx_table.Find(nextId); - if (obj) + if (NULL != obj) { NormObject::CheckLevel level; if (nextId < objectId) - { level = NormObject::THRU_OBJECT; - } else - { level = checkLevel; - } - startTimer |= - obj->ClientRepairCheck(level, blockId, segmentId, false); + if (obj->ReceiverRepairCheck(level, blockId, segmentId, false)) + startTimer = true; } else { @@ -1461,9 +1937,13 @@ void NormServerNode::RepairCheck(NormObject::CheckLevel checkLevel, (session.Address().IsMulticast() && (backoff_factor > 0.0)) ? ExponentialRand(grtt_estimate*backoff_factor, gsize_estimate) : 0.0; - repair_timer.SetInterval(backoffInterval); - DMSG(4, "NormServerNode::RepairCheck() node>%lu begin NACK backoff: %lf sec)...\n", + PLOG(PL_DEBUG, "NormSenderNode::RepairCheck() node>%lu begin NACK backoff: %lf sec)...\n", LocalNodeId(), backoffInterval); + // Here, we clear NormSenderNode repair_mask + // that is used for NACK suppression. + // (object/block repair_masks are cleared as needed in NormObject::ReceiverRepairCheck + if (rx_repair_mask.IsSet()) rx_repair_mask.Clear(); + repair_timer.SetInterval(backoffInterval); session.ActivateTimer(repair_timer); } } @@ -1471,46 +1951,52 @@ void NormServerNode::RepairCheck(NormObject::CheckLevel checkLevel, else if (repair_timer.GetRepeatCount()) { // Repair timer in backoff phase - // Trim server current transmit position reference + // Trim sender current transmit position reference NormObject* obj = rx_table.Find(objectId); - if (obj) obj->ClientRepairCheck(checkLevel, blockId, segmentId, true); + if (obj) obj->ReceiverRepairCheck(checkLevel, blockId, segmentId, true); if (objectId < current_object_id) current_object_id = objectId; } else { // Repair timer in holdoff phase - bool rewindDetected = objectId < current_object_id; - if (!rewindDetected) + bool rewindDetected = false; + if (objectId < current_object_id) + { + rewindDetected = true; + } + else if (objectId == current_object_id) { NormObject* obj = rx_table.Find(objectId); if (obj) - rewindDetected = obj->ClientRepairCheck(checkLevel, blockId, segmentId, true, true); + rewindDetected = obj->ReceiverRewindCheck(blockId, segmentId); } if (rewindDetected) { repair_timer.Deactivate(); - DMSG(4, "NormServerNode::RepairCheck() node>%lu server rewind detected, ending NACK holdoff ...\n", + PLOG(PL_DEBUG, "NormSenderNode::RepairCheck() node>%lu sender rewind detected, ending NACK holdoff ...\n", LocalNodeId()); - + // Immediately do a repair check to see if rewind was sufficient + // TBD - will we get too much unnecessary NACKing with out-of-order packet delivery ??? RepairCheck(checkLevel, objectId, blockId, segmentId); } } -} // end NormServerNode::RepairCheck() +} // end NormSenderNode::RepairCheck() + // When repair timer fires, possibly build a NACK -// and queue for transmission to this server node -bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) +// and queue for transmission to this sender node +bool NormSenderNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) { switch(repair_timer.GetRepeatCount()) { case 0: // hold-off time complete - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu end NACK hold-off ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu end NACK hold-off ...\n", LocalNodeId()); break; case 1: // back-off timeout complete { - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu end NACK back-off ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu end NACK back-off ...\n", LocalNodeId()); // 1) Were we suppressed? NormObjectId nextId; @@ -1540,7 +2026,7 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) NormNackMsg* nack = (NormNackMsg*)session.GetMessageFromPool(); if (!nack) { - DMSG(3, "NormServerNode::OnRepairTimeout() node>%lu Warning! " + PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() node>%lu Warning! " "message pool empty ...\n", LocalNodeId()); repair_timer.Deactivate(); return false; @@ -1560,16 +2046,19 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) ext.SetCCFlag(NormCC::RTT); ext.SetCCRtt(rtt_quantized); double ccLoss = slow_start ? 0.0 : LossEstimate(); - UINT16 lossQuantized = NormQuantizeLoss(ccLoss); - ext.SetCCLoss(lossQuantized); - if (0.0 == ccLoss) + //UINT16 lossQuantized = NormQuantizeLoss(ccLoss); + //ext.SetCCLoss(lossQuantized); + UINT16 lossQuantized = NormQuantizeLoss32(ccLoss); + ext.SetCCLoss32(lossQuantized); + //if (0.0 == ccLoss) + if (0 == lossQuantized) { //if (slow_start) // (TBD) should we only set flag on actual slow_start? ext.SetCCFlag(NormCC::START); if (recv_rate > 0.0) ext.SetCCRate(NormQuantizeRate(2.0 * recv_rate)); else - ext.SetCCRate(NormQuantizeRate(2.0 * nominal_packet_size)); // (TBD revisit this + ext.SetCCRate(NormQuantizeRate(2.0 * nominal_packet_size)); // (TBD revisit this) } else { @@ -1577,16 +2066,28 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) double ccRate = NormSession::CalculateRate(nominalSize, rtt_estimate, ccLoss); +#ifdef LIMIT_CC_RATE + // Experimental modification to NORM-CC where congestion control rate is limited + // to MIN(2.0*measured recv rate, calculated rate). This might prevent large rate + // overshoot in conditions where the loss measurement (perhaps initial loss) is + // very low due to big network packet buffers, etc + double rxRate = 2.0*recv_rate; + if (rxRate < ccRate) + { + ext.SetCCFlag(NormCC::LIMIT); + ccRate = rxRate; + } +#endif // LIMIT_CC_RATE ext.SetCCRate(NormQuantizeRate(ccRate)); } - DMSG(6, "NormServerNode::OnRepairTimeout() node>%lu sending NACK rate:%lf kbps (rtt:%lf loss:%lf s:%hu) slow_start:%d\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu sending NACK rate:%lf kbps (rtt:%lf loss:%lf s:%hu) slow_start:%d\n", LocalNodeId(), 8.0e-03*NormUnquantizeRate(ext.GetCCRate()), rtt_estimate, ccLoss, (UINT16)nominal_packet_size, slow_start); ext.SetCCSequence(cc_sequence); - // Cancel potential pending NORM_ACK(RTT) + // Cancel potential pending NORM_ACK(CC) since we are NACKing if (cc_timer.IsActive()) { - // Set holdoff timeout to refrain from send too much cc feedback + // Set holdoff timeout to refrain from sending too much cc feedback cc_timer.SetInterval(grtt_estimate*backoff_factor); cc_timer.Reschedule(); cc_timer.DecrementRepeatCount(); @@ -1635,7 +2136,7 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) { if (0 == nack->PackRepairRequest(req)) { - DMSG(3, "NormServerNode::OnRepairTimeout() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n"); break; } nackAppended = true; @@ -1660,12 +2161,12 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) switch (nextForm) { case NormRepairRequest::ITEMS: - req.AppendRepairItem(prevId, 0, ndata, 0); + req.AppendRepairItem(fec_id, fec_m, prevId, 0, ndata, 0); if (2 == consecutiveCount) - req.AppendRepairItem(prevId+1, 0, ndata, 0); + req.AppendRepairItem(fec_id, fec_m, prevId+1, 0, ndata, 0); break; case NormRepairRequest::RANGES: - req.AppendRepairRange(prevId, 0, ndata, 0, + req.AppendRepairRange(fec_id, fec_m, prevId, 0, ndata, 0, prevId+consecutiveCount-1, 0, ndata, 0); break; default: @@ -1681,7 +2182,7 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) { if (0 == nack->PackRepairRequest(req)) { - DMSG(3, "NormServerNode::OnRepairTimeout() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n"); break; } nackAppended = true; @@ -1712,63 +2213,74 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) if (0 != nack->PackRepairRequest(req)) nackAppended = true; else - DMSG(3, "NormServerNode::OnRepairTimeout() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormSenderNode::OnRepairTimeout() warning: full NACK msg\n"); } // Queue NACK for transmission - nack->SetServerId(GetId()); + nack->SetSenderId(GetId()); nack->SetInstanceId(instance_id); // GRTT response is deferred until transmit time - if (unicast_nacks) nack->SetDestination(GetAddress()); else nack->SetDestination(session.Address()); - // Debug check to make sure NACK has content if (nackAppended) { + // Debug check to make sure NACK has content ASSERT(nack->GetRepairContentLength() > 0); - if (!session.ClientIsSilent()) + if (!session.ReceiverIsSilent()) { session.SendMessage(*nack); nack_count++; } session.ReturnMessageToPool(nack); - } else { // The nack had no repair request content, // perhaps because of our "nacking mode" // even though there were pending objects - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu zero content nack ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu zero content nack ...\n", LocalNodeId()); session.ReturnMessageToPool(nack); } } else { - if (!session.ClientIsSilent()) + if (!session.ReceiverIsSilent()) { suppress_count++; - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu NACK SUPPRESSED ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu NACK SUPPRESSED ...\n", LocalNodeId()); } } // end if/else(repairPending) // BACKOFF related code - double holdoffInterval = - session.Address().IsMulticast() ? grtt_estimate*(backoff_factor + 2.0) : - grtt_estimate; - // backoff == 0.0 is a special case + double holdoffInterval = grtt_estimate; + if (session.Address().IsMulticast()) + { + holdoffInterval *= (backoff_factor + 2.0); + } + else + { + // Allow at least a packet interval of "slop" time for holdoff + if (0.0 != recv_rate) + { + double nominalPktInterval = nominal_packet_size / recv_rate; + holdoffInterval += MIN(nominalPktInterval, grtt_estimate); + } + else + { + holdoffInterval += grtt_estimate; + } + } //holdoffInterval = (backoff_factor > 0.0) ? holdoffInterval : 1.0*grtt_estimate; - repair_timer.SetInterval(holdoffInterval); - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu begin NACK hold-off: %lf sec ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu begin NACK hold-off: %lf sec ...\n", LocalNodeId(), holdoffInterval); } else { - DMSG(4, "NormServerNode::OnRepairTimeout() node>%lu nothing pending ...\n", + PLOG(PL_DEBUG, "NormSenderNode::OnRepairTimeout() node>%lu nothing pending ...\n", LocalNodeId()); // (TBD) cancel hold-off timeout ??? } // end if/else (repair_mask.IsSet()) @@ -1780,9 +2292,9 @@ bool NormServerNode::OnRepairTimeout(ProtoTimer& /*theTimer*/) break; } return true; -} // end NormServerNode::OnRepairTimeout() +} // end NormSenderNode::OnRepairTimeout() -void NormServerNode::UpdateRecvRate(const struct timeval& currentTime, unsigned short msgSize) +void NormSenderNode::UpdateRecvRate(const struct timeval& currentTime, unsigned short msgSize) { if (prev_update_time.tv_sec || prev_update_time.tv_usec) { @@ -1796,10 +2308,10 @@ void NormServerNode::UpdateRecvRate(const struct timeval& currentTime, unsigned // recv_rate because of the typical limited granularity of our system clock // (Note this can limit our ramp up of data rate during slow start) if (measurementInterval < NORM_TICK_MIN) measurementInterval = NORM_TICK_MIN; - recv_accumulator += msgSize; + recv_accumulator.Increment(msgSize); if (interval > 0.0) { - double currentRecvRate = ((double)(recv_accumulator)) / interval; + double currentRecvRate = recv_accumulator.GetScaledValue(1.0 / interval); if ((interval >= measurementInterval) && (currentRecvRate < recv_rate)) { // Make sure we've allowed sufficient time for a measurement at low rates @@ -1809,33 +2321,47 @@ void NormServerNode::UpdateRecvRate(const struct timeval& currentTime, unsigned } if (interval >= measurementInterval) { - recv_rate = currentRecvRate; + recv_rate = recv_rate_prev = currentRecvRate; prev_update_time = currentTime; - recv_accumulator = 0; + recv_accumulator.Reset(); } else if (0.0 == recv_rate) { recv_rate = currentRecvRate; + recv_rate_prev = 0.0; + } + else if (slow_start) + { + // Go ahead and allow estimate to slew upwards on new packet arrivals + // (helps "slow start" ramp up a little more cleanly) + double rateDelta = currentRecvRate - recv_rate_prev; + if (rateDelta > 0.0) + { + double scale = interval / measurementInterval; + double partialRate = recv_rate_prev + scale*rateDelta; + if (partialRate > recv_rate) recv_rate = partialRate; + } } } else if (0.0 == recv_rate) { // Approximate initial recv_rate when initial packets arrive in a burst - recv_rate = ((double)(recv_accumulator)) / NORM_TICK_MIN; + recv_rate = recv_accumulator.GetValue() / NORM_TICK_MIN; + recv_rate_prev = 0.0; } nominal_packet_size += 0.05 * (((double)msgSize) - nominal_packet_size); } else { - recv_rate = 0.0; + recv_rate = recv_rate_prev = 0.0; prev_update_time = currentTime; - recv_accumulator = 0; + recv_accumulator.Reset(); nominal_packet_size = msgSize; } -} // end NormServerNode::UpdateRecvRate() +} // end NormSenderNode::UpdateRecvRate() -void NormServerNode::Activate(bool isObjectMsg) +void NormSenderNode::Activate(bool isObjectMsg) { if (!activity_timer.IsActive()) { @@ -1844,32 +2370,38 @@ void NormServerNode::Activate(bool isObjectMsg) activity_timer.SetInterval(activityInterval); activity_timer.SetRepeat(robust_factor); session.ActivateTimer(activity_timer); - server_active = false; + sender_active = false; + // If it is _not_ an object msg, do a comprehensive repair check + // to re-initiate NACKing for any missing data from prior sender + // activity (iff rx_pending_mask.IsSet()) + // (If it is an object message, RepairCheck() will be called accordingly + if (!isObjectMsg && rx_pending_mask.IsSet()) + RepairCheck(NormObject::THRU_OBJECT, max_pending_object, 0, 0); // (TBD) thru object??? session.Notify(NormController::REMOTE_SENDER_ACTIVE, this, NULL); } else if (isObjectMsg) { - server_active = true; + sender_active = true; } -} // end NormServerNode::Activate() +} // end NormSenderNode::Activate() -bool NormServerNode::OnActivityTimeout(ProtoTimer& /*theTimer*/) +bool NormSenderNode::OnActivityTimeout(ProtoTimer& /*theTimer*/) { - if (server_active) + if (sender_active) { activity_timer.ResetRepeat(); } else if (0 == activity_timer.GetRepeatCount()) { // Serve completely inactive? - DMSG(0, "NormServerNode::OnActivityTimeout() node>%lu server>%lu gone inactive?\n", + PLOG(PL_ERROR, "NormSenderNode::OnActivityTimeout() node>%lu sender>%lu gone inactive?\n", LocalNodeId(), GetId()); //FreeBuffers(); This now needs to be done by the app as of norm version 1.4b3 session.Notify(NormController::REMOTE_SENDER_INACTIVE, this, NULL); } else { - DMSG(2, "NormServerNode::OnActivityTimeout() node>%lu for server>%lu\n", + PLOG(PL_INFO, "NormSenderNode::OnActivityTimeout() node>%lu for sender>%lu\n", LocalNodeId(), GetId()); struct timeval currentTime; ::ProtoSystemTime(currentTime); @@ -1895,7 +2427,7 @@ bool NormServerNode::OnActivityTimeout(ProtoTimer& /*theTimer*/) max_pending_object, 0, 0); // (TBD) What should we really do here? Our current NormNode::RepairCheck() and - // NormObject::ClientRepairCheck() methods update the "max_pending" indices + // NormObject::ReceiverRepairCheck() methods update the "max_pending" indices // so we _could_ make ourselves NACK for more repair than we should // when the inactivity timeout kicks in ??? But if we don't NACK "thru object" // we may miss something at end-of-transmission by not not NACKing? I guess @@ -1904,7 +2436,7 @@ bool NormServerNode::OnActivityTimeout(ProtoTimer& /*theTimer*/) // but then _everyone_ would NACK at EOT all the time, often for nothing ... so // I guess the activity timeout NACK isn't perfect ... but could help some // so we leave it as it is for the moment ("THRU_OBJECT") ... perhaps we could - // add a parameter so NormObject::ClientRepairCheck() doesn't update its + // add a parameter so NormObject::ReceiverRepairCheck() doesn't update its // "max_pending" indices - or would this break NACK building } else @@ -1923,80 +2455,115 @@ bool NormServerNode::OnActivityTimeout(ProtoTimer& /*theTimer*/) activity_timer.Deactivate(); session.ActivateTimer(activity_timer); activity_timer.SetRepeatCount(repeatCount); - server_active = false; + sender_active = false; return false; // since we manually deactivated/reactivated the timer } - server_active = false; + sender_active = false; return true; -} // end NormServerNode::OnActivityTimeout() +} // end NormSenderNode::OnActivityTimeout() -bool NormServerNode::UpdateLossEstimate(const struct timeval& currentTime, +bool NormSenderNode::UpdateLossEstimate(const struct timeval& currentTime, unsigned short seq, - bool ecn) + bool ecnStatus) { - if (loss_estimator.Update(currentTime, seq, ecn)) + if (loss_estimator.Update(currentTime, seq, ecnStatus)) { if (slow_start) { // Calculate loss initialization based on current receive rate - // and rtt estimation + // and rtt estimation double nominalSize = (nominal_packet_size > segment_size) ? nominal_packet_size : segment_size; - double lossInit = (recv_rate * rtt_estimate) / (nominalSize*sqrt(3.0/2.0)); - lossInit = (lossInit*lossInit); - // At this point, "LastLossInterval()" will always be non-zero! + double lossInit = nominalSize / (recv_rate * rtt_estimate); + lossInit *= lossInit; + lossInit *= 3.0/2.0; double altLoss = (double)loss_estimator.LastLossInterval(); - double altInit = (altLoss > 0.0) ? (1.0 / altLoss) : 0.5; - if (altInit < lossInit) - loss_estimator.SetInitialLoss(altInit); - else - loss_estimator.SetInitialLoss(lossInit); + if (altLoss < 2.0) altLoss = 2.0; // makes sure it's no worse than 50% pkt loss + double altInit = 1.0 / altLoss; + if (altInit < lossInit) lossInit = altInit; + loss_estimator.SetInitialLoss(lossInit); slow_start = false; } + // TBD - schedule immediate CC feedback if CLR? - note duplicate feedback issue so + // need to do this with preemptively incremented cc_sequence value ... + // TBD - This can cause extra ACK for non-cc unicast sessions. + // We could reset "cc_feedback_needed" to false if NACK feedback is scheduled + // for non-clr unicast case instead??? + if (cc_enable && (is_clr || is_plr))// || !session.Address().IsMulticast())) + cc_feedback_needed = true; return true; } else { return false; } -} // end NormServerNode::UpdateLossEstimate() +} // end NormSenderNode::UpdateLossEstimate() -void NormServerNode::AttachCCFeedback(NormAckMsg& ack) +void NormSenderNode::CheckCCFeedback() +{ + // "cc_feedback_needed" is set to "true" if a loss event occurs + // and remains "true" no cc feedback was sent otherwise + // (gets reset to "false" when OnCCTimeout() is called here) + if (cc_feedback_needed) + { + cc_sequence++; // so sender won't ignore as duplicate feedback + if (cc_timer.IsActive()) cc_timer.Deactivate(); + cc_timer.ResetRepeat(); // makes sure timer phase is correct + OnCCTimeout(cc_timer); + } +} // end NormSenderNode::CheckCCFeedback() + +void NormSenderNode::AttachCCFeedback(NormAckMsg& ack) { // GRTT response is deferred until transmit time NormCCFeedbackExtension ext; ack.AttachExtension(ext); if (is_clr) - { ext.SetCCFlag(NormCC::CLR); - } else if (is_plr) ext.SetCCFlag(NormCC::PLR); if (rtt_confirmed) ext.SetCCFlag(NormCC::RTT); ext.SetCCRtt(rtt_quantized); - double ccLoss = slow_start ? 0.0 : LossEstimate(); - UINT16 lossQuantized = NormQuantizeLoss(ccLoss); - ext.SetCCLoss(lossQuantized); - if (0.0 == ccLoss) + double ccLoss = slow_start ? 0.0 : LossEstimate(); + + //UINT16 lossQuantized = NormQuantizeLoss(ccLoss); + //ext.SetCCLoss(lossQuantized); + UINT32 lossQuantized = NormQuantizeLoss32(ccLoss); + ext.SetCCLoss32(lossQuantized); + //if (0.0 == ccLoss) + if (0 == lossQuantized) { ext.SetCCFlag(NormCC::START); ext.SetCCRate(NormQuantizeRate(2.0 * recv_rate)); } else { - double nominalSize = (nominal_packet_size > segment_size) ? nominal_packet_size : segment_size; + //double nominalSize = (nominal_packet_size > segment_size) ? nominal_packet_size : segment_size; + double nominalSize = (0 != nominal_packet_size) ? nominal_packet_size : segment_size; double ccRate = NormSession::CalculateRate(nominalSize, rtt_estimate, ccLoss); +#ifdef LIMIT_CC_RATE + // Experimental modification to NORM-CC where congestion control rate is limited + // to MIN(2.0*measured recv rate, calculated rate). This might prevent large rate + // overshoot in conditions where the loss measurement (perhaps initial loss) is + // very low due to big network packet buffers, etc + double rxRate = 2.0*recv_rate; + if (rxRate < ccRate) + { + ext.SetCCFlag(NormCC::LIMIT); + ccRate = rxRate; + } +#endif // LIMIT_CC_RATE ext.SetCCRate(NormQuantizeRate(ccRate)); } - //DMSG(0, "NormServerNode::AttachCCFeedback() node>%lu sending ACK rate:%lf kbps (rtt:%lf loss:%lf s:%lf recvRate:%lf) slow_start:%d\n", - // LocalNodeId(), 8.0e-03*NormUnquantizeRate(ext.GetCCRate()) , - // rtt_estimate, ccLoss, nominal_packet_size, 8.0e-03*recv_rate, slow_start); + PLOG(PL_DEBUG, "NormSenderNode::AttachCCFeedback() node>%lu sending ACK rate:%lf kbps (rtt:%lf loss:%lf s:%lf recvRate:%lf) slow_start:%d\n", + LocalNodeId(), 8.0e-03*NormUnquantizeRate(ext.GetCCRate()) , + rtt_estimate, ccLoss, nominal_packet_size, 8.0e-03*recv_rate, slow_start); ext.SetCCSequence(cc_sequence); -} // end NormServerNode::AttachCCFeedback() +} // end NormSenderNode::AttachCCFeedback() -bool NormServerNode::OnCCTimeout(ProtoTimer& /*theTimer*/) +bool NormSenderNode::OnCCTimeout(ProtoTimer& /*theTimer*/) { - // Build and queue ACK() + // Build and send NORM_ACK(CC) switch (cc_timer.GetRepeatCount()) { case 0: @@ -2009,13 +2576,13 @@ bool NormServerNode::OnCCTimeout(ProtoTimer& /*theTimer*/) NormAckMsg* ack = (NormAckMsg*)session.GetMessageFromPool(); if (!ack) { - DMSG(3, "NormServerNode::OnCCTimeout() node>%lu warning: " + PLOG(PL_WARN, "NormSenderNode::OnCCTimeout() node>%lu warning: " "message pool empty ...\n", LocalNodeId()); if (cc_timer.IsActive()) cc_timer.Deactivate(); return false; } ack->Init(); - ack->SetServerId(GetId()); + ack->SetSenderId(GetId()); ack->SetInstanceId(instance_id); ack->SetAckType(NormAck::CC); ack->SetAckId(0); @@ -2026,19 +2593,12 @@ bool NormServerNode::OnCCTimeout(ProtoTimer& /*theTimer*/) ack->SetDestination(GetAddress()); else ack->SetDestination(session.Address()); - //if (is_clr || is_plr) - { - // Don't rate-limit feedback messages. - session.SendMessage(*ack); - session.ReturnMessageToPool(ack); - } - //else - //{ - // session.QueueMessage(ack); - //} + session.SendMessage(*ack); + session.ReturnMessageToPool(ack); + cc_feedback_needed = false; // Begin cc_timer "holdoff" phase - if (session.Address().IsMulticast()) + if (!is_clr && !is_plr && session.Address().IsMulticast()) { cc_timer.SetInterval(grtt_estimate*backoff_factor); return true; @@ -2056,20 +2616,22 @@ bool NormServerNode::OnCCTimeout(ProtoTimer& /*theTimer*/) break; } return true; -} // end NormServerNode::OnCCTimeout() +} // end NormSenderNode::OnCCTimeout() -bool NormServerNode::OnAckTimeout(ProtoTimer& /*theTimer*/) +bool NormSenderNode::OnAckTimeout(ProtoTimer& /*theTimer*/) { + // Build and send NORM_ACK(FLUSH) NormAckFlushMsg* ack = (NormAckFlushMsg*)session.GetMessageFromPool(); if (ack) { ack->Init(); - ack->SetServerId(GetId()); + ack->SetSenderId(GetId()); ack->SetInstanceId(instance_id); + ack->SetAckType(NormAck::FLUSH); ack->SetAckId(0); AttachCCFeedback(*ack); ack->SetObjectId(watermark_object_id); - ack->SetFecBlockId(watermark_block_id); + // _Attempt_ to set the fec_payload_id source block length field appropriately UINT16 blockLen; NormObject* obj = rx_table.Find(watermark_object_id); @@ -2079,8 +2641,8 @@ bool NormServerNode::OnAckTimeout(ProtoTimer& /*theTimer*/) blockLen = ndata; else blockLen = watermark_segment_id; - ack->SetFecBlockLen(blockLen); // yuk - ack->SetFecSymbolId(watermark_segment_id); + ack->SetFecPayloadId(fec_id, watermark_block_id, watermark_segment_id, blockLen, fec_m); + if (unicast_nacks) ack->SetDestination(GetAddress()); else @@ -2089,7 +2651,8 @@ bool NormServerNode::OnAckTimeout(ProtoTimer& /*theTimer*/) // Don't rate limit feedback messages session.SendMessage(*ack); session.ReturnMessageToPool(ack); - if (!is_clr && !is_plr && session.Address().IsMulticast()) + cc_feedback_needed = false; + if (cc_enable && !is_clr && !is_plr && session.Address().IsMulticast()) { // Install cc feedback holdoff cc_timer.SetInterval(grtt_estimate*backoff_factor); @@ -2097,7 +2660,7 @@ bool NormServerNode::OnAckTimeout(ProtoTimer& /*theTimer*/) cc_timer.Reschedule(); else session.ActivateTimer(cc_timer); - cc_timer.DecrementRepeatCount(); + cc_timer.DecrementRepeatCount(); // put timer into "holdoff" phase } else if (cc_timer.IsActive()) { @@ -2107,10 +2670,10 @@ bool NormServerNode::OnAckTimeout(ProtoTimer& /*theTimer*/) } else { - DMSG(3, "NormServerNode::OnAckTimeout() warning: message pool exhausted!\n"); + PLOG(PL_WARN, "NormSenderNode::OnAckTimeout() warning: message pool exhausted!\n"); } return true; -} // end NormServerNode::OnAckTimeout() +} // end NormSenderNode::OnAckTimeout() NormAckingNode::NormAckingNode(class NormSession& theSession, NormNodeId nodeId) @@ -2152,7 +2715,7 @@ NormNode *NormNodeTree::FindNodeById(NormNodeId nodeId) const void NormNodeTree::AttachNode(NormNode *node) { - ASSERT(node); + ASSERT(NULL != node); node->Retain(); node->left = NULL; node->right = NULL; @@ -2192,7 +2755,7 @@ void NormNodeTree::AttachNode(NormNode *node) void NormNodeTree::DetachNode(NormNode* node) { - ASSERT(node); + ASSERT(NULL != node); node->Release(); NormNode* x; NormNode* y; @@ -2259,28 +2822,27 @@ void NormNodeTree::Destroy() } } // end NormNodeTree::Destroy() -NormNodeTreeIterator::NormNodeTreeIterator(const NormNodeTree& t) +NormNodeTreeIterator::NormNodeTreeIterator(const NormNodeTree& t, NormNode* prevNode) : tree(t) { - NormNode* x = t.root; - if (x) - { - while (x->left) x = x->left; - next = x; - } - else - { - next = NULL; - } + Reset(prevNode); } -void NormNodeTreeIterator::Reset() +void NormNodeTreeIterator::Reset(NormNode* prevNode) { NormNode* x = tree.root; - if (x) + if (NULL != x) { - while (x->left) x = x->left; - next = x; + if (NULL == prevNode) + { + while (x->left) x = x->left; + next = x; + } + else + { + next = prevNode; + GetNextNode(); // sets "next" to return subsequent node + } } else { @@ -2315,7 +2877,7 @@ NormNode* NormNodeTreeIterator::GetNextNode() } // end NormNodeTreeIterator::GetNextNode() NormNodeList::NormNodeList() - : head(NULL), tail(NULL), count(0) + : head(NULL), tail(NULL), count(0) { } @@ -2339,7 +2901,7 @@ NormNode* NormNodeList::FindNodeById(NormNodeId nodeId) const void NormNodeList::Append(NormNode *theNode) { - ASSERT(theNode); + ASSERT(NULL != theNode); theNode->Retain(); theNode->left = tail; if (tail) @@ -2353,7 +2915,7 @@ void NormNodeList::Append(NormNode *theNode) void NormNodeList::Remove(NormNode *theNode) { - ASSERT(theNode); + ASSERT(NULL != theNode); theNode->Release(); if (theNode->right) theNode->right->left = theNode->left; @@ -2385,6 +2947,7 @@ void NormNodeList::Destroy() NormLossEstimator::NormLossEstimator() : synchronized(false), seeking_loss_event(true), event_window(0.0) { + event_time.tv_sec = event_time.tv_usec = 0; memset(history, 0, (DEPTH+1)*sizeof(unsigned int)); } @@ -2497,13 +3060,11 @@ double NormLossEstimator::LossFraction() NormLossEstimator2::NormLossEstimator2() - : init(false), lag_mask(0xffffffff), lag_depth(0), lag_test_bit(0x01), - event_window(0), event_index(0), - event_window_time(0.0), event_index_time(0.0), - seeking_loss_event(true), - no_loss(true), initial_loss(0.0), - current_discount(1.0) + : init(false), ignore_loss(false), tolerate_loss(false), + lag_mask(0xffffffff), lag_depth(0), lag_test_bit(0x01), + event_window(0.0), seeking_loss_event(SEEKING), current_discount(1.0) { + event_time.tv_sec = event_time.tv_usec = 0; memset(history, 0, 9*sizeof(unsigned long)); discount[0] = 1.0; } @@ -2527,9 +3088,10 @@ int NormLossEstimator2::SequenceDelta(unsigned short a, unsigned short b) bool NormLossEstimator2::Update(const struct timeval& currentTime, - unsigned short theSequence, - bool ecnStatus) + unsigned short theSequence, + bool ecnStatus) { + // (TBD) What if the first packet that arrives has ECN set??? if (!init) { Init(theSequence); @@ -2599,63 +3161,78 @@ bool NormLossEstimator2::Update(const struct timeval& currentTime, return false; // Duplicate packet arrived, ignore } + if (ignore_loss) outageDepth = 0; + if (ecnStatus) outageDepth += 1; bool newLossEvent = false; - if (!seeking_loss_event) + //if (!seeking_loss_event) + if (SEEKING != seeking_loss_event) { - double theTime = (((double)currentTime.tv_sec) + - (((double)currentTime.tv_usec)/1.0e06)); - if (theTime > event_index_time) seeking_loss_event = true; - - // (TBD) Should we reset our history on - // outages within the event_window??? + double deltaTime = (double)(currentTime.tv_sec - event_time.tv_sec); + if (currentTime.tv_usec > event_time.tv_usec) + deltaTime += (double)(currentTime.tv_usec - event_time.tv_usec) * 1.0e-06; + else + deltaTime -= (double)(event_time.tv_usec - currentTime.tv_usec) * 1.0e-06; + // Use a longer "loss event window" for NORM-CCE ("ignore_loss" = "true") + // since RED/ECN tends to start marking "early" and stop marking "late" + double windowScale = ignore_loss ? 2.0 : 1.0; + if (deltaTime > windowScale*event_window) + { + //if (CONFIRMING == seeking_loss_event) + // TRACE("Clearing unconfirmed loss event ...\n"); + seeking_loss_event = SEEKING; + } } - if (seeking_loss_event) + //if (seeking_loss_event) + if (CONFIRMED != seeking_loss_event) { + if ((1 == outageDepth) && !ecnStatus && tolerate_loss) // single, non-ECN loss event + { + if (SEEKING == seeking_loss_event) + { + seeking_loss_event = CONFIRMING; // wait for more loss to confirm congestion event + event_time = event_time_orig = currentTime; + outageDepth = 0; + //TRACE("Deferring loss event for confirmation ...\n"); + } + } + if (outageDepth) // non-zero outageDepth means pkt loss(es) { - if (no_loss) // first loss - { - //fprintf(stderr, "First Loss: seq:%u init:%f history:%lu adjusted:", - // theSequence, initial_loss, history[0]); - if (initial_loss != 0.0) - { - unsigned long initialHistory = (unsigned long) ((1.0 / initial_loss) + 0.5); - history[0] = MAX(initialHistory, history[0]/2); - } - //fprintf(stderr, "%lu\n", history[0]); - no_loss = false; - } - - + // call to LossFraction() here is just to make sure "current_discount" + // is updated accordlingly + LossFraction(); // New method // New loss event, shift loss interval history & discounts memmove(&history[1], &history[0], 8*sizeof(unsigned long)); history[0] = 0; - memmove(&discount[1], &discount[0], 8*sizeof(double)); + for (int i = 8; i > 0; i--) + discount[i] = discount[i-1]*current_discount; discount[0] = 1.0; current_discount = 1.0; - - event_index = theSequence; - //if (event_window) - seeking_loss_event = false; + //TRACE("Confirmed loss event ...(tolerate:%d)\n", tolerate_loss); + seeking_loss_event = CONFIRMED; + event_time = event_time_orig = currentTime; newLossEvent = true; - no_loss = false; - // (TBD) use fixed pt. math here ... - event_index_time = (((double)currentTime.tv_sec) + - (((double)currentTime.tv_usec)/1.0e06)); - event_index_time += event_window_time; + + TRACE("time>%lf new loss event fraction = %lf\n", ProtoTime(currentTime).GetValue(), LossFraction()); + } } else { - if (outageDepth) history[0] = 0; + // we commented this out not to reset history to get better loss event period measurement + // if (outageDepth > 0) + // history[0] = 0; } // end if/else (seeking_loss_event) - if (history[0] < 100000) history[0]++; + // TBD - instead of counting packets, should we calculate based on the sequence number + // of the last loss event and the current sequence number? (so dups won't fool us into increasing rate) + //if (history[0] < 65536*2) + history[0]++; return newLossEvent; } // end NormLossEstimator2::Update() @@ -2664,7 +3241,7 @@ bool NormLossEstimator2::Update(const struct timeval& currentTime, // TFRC Loss interval averaging with discounted, weighted averaging double NormLossEstimator2::LossFraction() { - if (!history[1]) return 0.0; + if (0 == history[1]) return 0.0; // Compute older weighted average s1->s8 for discount determination double average = 0.0; double scaling = 0.0; @@ -2683,32 +3260,13 @@ double NormLossEstimator2::LossFraction() } double s1 = average / scaling; - // Compute discount if applicable - if (history[0] > (2.0*s1)) + // Compute discount if applicable + if (history[0] > (2.0*s1)) { current_discount = (2.0*s1) / (double) history[0]; - current_discount = MAX (current_discount, 0.5); - } - - // Re-compute older weighted average s1->s8 with discounting - if (current_discount < 1.0) - { - average = 0.0; - scaling = 0.0; - for (i = 1; i < 9; i++) - { - if (history[i]) - { - average += current_discount * history[i] * weight[i-1] * discount[i]; - scaling += current_discount * discount[i] * weight[i-1]; - } - else - { - break; - } - } - s1 = average / scaling; + current_discount = MAX(current_discount, 0.5); } + if (history[0] > s1) return (1.0 / (double)history[0]); // Compute newer weighted average s0->s7 with discounting average = 0.0; @@ -2727,6 +3285,9 @@ double NormLossEstimator2::LossFraction() } } double s0 = (average > 0.0) ? average / scaling : 0.0; - // Use max of old/new averages - return (1.0 / MAX(s0, s1)); + + // Use max of old/new averages (i.e. only use discounting if it helps increase rate) + double result = (1.0 / MAX(s0, s1)); + + return result; } // end NormLossEstimator2::LossFraction() diff --git a/common/normObject.cpp b/src/common/normObject.cpp similarity index 76% rename from common/normObject.cpp rename to src/common/normObject.cpp index 11b1c9a..fd2d85e 100644 --- a/common/normObject.cpp +++ b/src/common/normObject.cpp @@ -9,22 +9,22 @@ NormObject::NormObject(NormObject::Type theType, class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& transportId) - : type(theType), session(theSession), server(theServer), reference_count(1), + : type(theType), session(theSession), sender(theSender), reference_count(1), 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) { - if (theServer) + if (theSender) { - nacking_mode = theServer->GetDefaultNackingMode(); - theServer->Retain(); + nacking_mode = theSender->GetDefaultNackingMode(); + theSender->Retain(); } else { - nacking_mode = NACK_NORMAL; // it doesn't really matter if !theServer + nacking_mode = NACK_NORMAL; // it doesn't really matter if !theSender } } @@ -33,7 +33,7 @@ NormObject::~NormObject() Close(); if (info_ptr) { - delete info_ptr; + delete[] info_ptr; info_ptr = NULL; } } @@ -41,19 +41,19 @@ NormObject::~NormObject() void NormObject::Retain() { reference_count++; - if (server) server->Retain(); + if (sender) sender->Retain(); } // end NormObject::Retain() void NormObject::Release() { - if (server) server->Release(); + if (sender) sender->Release(); if (reference_count) { reference_count--; } else { - DMSG(0, "NormObject::Release() releasing non-retained object?!\n"); + PLOG(PL_ERROR, "NormObject::Release() releasing non-retained object?!\n"); } if (0 == reference_count) delete this; } // end NormObject::Release() @@ -64,15 +64,17 @@ NormNodeId NormObject::LocalNodeId() const return session.LocalNodeId(); } // end NormObject::LocalNodeId() -NormNodeId NormObject::GetServerNodeId() const +NormNodeId NormObject::GetSenderNodeId() const { - return server ? server->GetId() : NORM_NODE_NONE; -} // end NormObject::GetServerNodeId() + return sender ? sender->GetId() : NORM_NODE_NONE; +} // end NormObject::GetSenderNodeId() bool NormObject::Open(const NormObjectSize& objectSize, const char* infoPtr, UINT16 infoLen, UINT16 segmentSize, + UINT8 fecId, + UINT8 fecM, UINT16 numData, UINT16 numParity) { @@ -80,8 +82,7 @@ bool NormObject::Open(const NormObjectSize& objectSize, // DATA or FILE objects, buffer size for STREAM objects // In either case, we need our sliding bit masks to be of // appropriate size. - ASSERT(!IsOpen()); - if (server) + if (sender) { if (infoLen > 0) { @@ -89,10 +90,11 @@ bool NormObject::Open(const NormObjectSize& objectSize, info_len = 0; if (!(info_ptr = new char[segmentSize])) { - DMSG(0, "NormObject::Open() info allocation error\n"); + PLOG(PL_FATAL, "NormObject::Open() info allocation error\n"); return false; } } + last_nack_time.GetCurrentTime(); // init to now } else { @@ -101,13 +103,13 @@ bool NormObject::Open(const NormObjectSize& objectSize, if (info_ptr) delete []info_ptr; if (infoLen > segmentSize) { - DMSG(0, "NormObject::Open() info too big error\n"); + PLOG(PL_FATAL, "NormObject::Open() info too big error\n"); info_len = 0; return false; } if (!(info_ptr = new char[infoLen])) { - DMSG(0, "NormObject::Open() info allocation error\n"); + PLOG(PL_FATAL, "NormObject::Open() info allocation error\n"); info_len = 0; return false; } @@ -123,31 +125,34 @@ bool NormObject::Open(const NormObjectSize& objectSize, NormObjectSize numBlocks = numSegments / NormObjectSize(numData); ASSERT(0 == numBlocks.MSB()); + if (!block_buffer.Init(numBlocks.LSB())) { - DMSG(0, "NormObject::Open() init block_buffer error\n"); + PLOG(PL_FATAL, "NormObject::Open() init block_buffer error\n"); Close(); return false; } // Init pending_mask (everything pending) - if (!pending_mask.Init(numBlocks.LSB(), (UINT32)0xffffffff)) + // Note the pending_mask and repair_mask both need to be inited with a + // "rangeMask" according to their fecId (e.g. 24-bit mask for some)!!! + UINT32 rangeMask = NormPayloadId::GetFecBlockMask(fecId, fecM); + if (!pending_mask.Init(numBlocks.LSB(), rangeMask)) { - DMSG(0, "NormObject::Open() init pending_mask (%lu) error: %s\n", + PLOG(PL_FATAL, "NormObject::Open() init pending_mask (%lu) error: %s\n", numBlocks.LSB(), GetErrorString()); Close(); return false; } // Init repair_mask - if (!repair_mask.Init(numBlocks.LSB(), (UINT32)0xffffffff)) + if (!repair_mask.Init(numBlocks.LSB(), rangeMask)) { - DMSG(0, "NormObject::Open() init pending_mask error\n"); + PLOG(PL_FATAL, "NormObject::Open() init pending_mask error\n"); Close(); return false; } repair_mask.Clear(); - if (STREAM == type) { @@ -155,7 +160,9 @@ bool NormObject::Open(const NormObjectSize& objectSize, small_block_count = large_block_count = numBlocks.LSB(); final_segment_size = segmentSize; NormStreamObject* stream = static_cast(this); - stream->StreamResync(NormBlockId(0)); + // 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)); } else { @@ -193,6 +200,8 @@ bool NormObject::Open(const NormObjectSize& objectSize, object_size = objectSize; segment_size = segmentSize; + fec_id = fecId; + fec_m = fecM; ndata = numData; nparity = numParity; current_block_id = 0; @@ -208,10 +217,10 @@ void NormObject::Close() while ((block = block_buffer.Find(block_buffer.RangeLo()))) { block_buffer.Remove(block); - if (server) - server->PutFreeBlock(block); + if (sender) + sender->PutFreeBlock(block); else - session.ServerPutFreeBlock(block); + session.SenderPutFreeBlock(block); } repair_mask.Destroy(); pending_mask.Destroy(); @@ -253,17 +262,27 @@ NormObjectSize NormObject::GetBytesPending() const } } // end NormObject::GetBytesCompleted() -// Used by server -bool NormObject::HandleInfoRequest() +// Used by sender +bool NormObject::HandleInfoRequest(bool holdoff) { bool increasedRepair = false; if (info_ptr) { if (!repair_info) { - pending_info = true; - repair_info = true; increasedRepair = true; + if (holdoff) + { + if (pending_info) + increasedRepair = false; + else + pending_info = true; + } + else + { + pending_info = true; // does this really need to be done? + repair_info = true; + } } } return increasedRepair; @@ -271,7 +290,7 @@ bool NormObject::HandleInfoRequest() bool NormObject::HandleBlockRequest(NormBlockId nextId, NormBlockId lastId) { - DMSG(6, "NormObject::HandleBlockRequest() node>%lu blk>%lu -> blk>%lu\n", + PLOG(PL_TRACE, "NormObject::HandleBlockRequest() node>%lu blk>%lu -> blk>%lu\n", LocalNodeId(), (UINT32)nextId, (UINT32)lastId); bool increasedRepair = false; lastId++; @@ -281,10 +300,10 @@ bool NormObject::HandleBlockRequest(NormBlockId nextId, NormBlockId lastId) { // (TBD) these tests can probably go away if everything else is done right if (!pending_mask.CanSet(nextId)) - DMSG(0, "NormObject::HandleBlockRequest() pending_mask.CanSet(%lu) error\n", + PLOG(PL_ERROR, "NormObject::HandleBlockRequest() pending_mask.CanSet(%lu) error\n", (UINT32)nextId); if (!repair_mask.Set(nextId)) - DMSG(0, "NormObject::HandleBlockRequest() repair_mask.Set(%lu) error\n", + PLOG(PL_ERROR, "NormObject::HandleBlockRequest() repair_mask.Set(%lu) error\n", (UINT32)nextId); increasedRepair = true; } @@ -320,7 +339,7 @@ bool NormObject::TxReset(NormBlockId firstBlock, bool requeue) { increasedRepair |= block->TxReset(GetBlockSize(blockId), nparity, - session.ServerAutoParity(), + session.SenderAutoParity(), segment_size); if (requeue) block->ClearFlag(NormBlock::IN_REPAIR); // since we're requeuing } @@ -336,7 +355,7 @@ bool NormObject::TxReset(NormBlockId firstBlock, bool requeue) bool NormObject::TxResetBlocks(NormBlockId nextId, NormBlockId lastId) { bool increasedRepair = false; - UINT16 autoParity = session.ServerAutoParity(); + UINT16 autoParity = session.SenderAutoParity(); lastId++; while (nextId != lastId) { @@ -369,10 +388,10 @@ bool NormObject::ActivateRepairs() { NormBlockId lastId; GetLastRepair(lastId); // for debug output only - DMSG(6, "NormObject::ActivateRepairs() node>%lu obj>%hu activating blk>%lu->%lu block repairs ...\n", + PLOG(PL_DEBUG, "NormObject::ActivateRepairs() node>%lu obj>%hu activating blk>%lu->%lu block repairs ...\n", LocalNodeId(), (UINT16)transport_id, (UINT32)nextId, (UINT32)lastId); //repairsActivated = true; - UINT16 autoParity = session.ServerAutoParity(); + UINT16 autoParity = session.SenderAutoParity(); do { NormBlock* block = block_buffer.Find(nextId); @@ -380,7 +399,7 @@ bool NormObject::ActivateRepairs() // (TBD) This check can be eventually eliminated if everything else is done right if (!pending_mask.Set(nextId)) { - DMSG(0, "NormObject::ActivateRepairs() pending_mask.Set(%lu) error 1!\n", (UINT32)nextId); + PLOG(PL_ERROR, "NormObject::ActivateRepairs() pending_mask.Set(%lu) error 1!\n", (UINT32)nextId); if (block) block->ClearPending(); if (IsStream()) static_cast(this)->UnlockBlock(nextId); @@ -401,11 +420,11 @@ bool NormObject::ActivateRepairs() { if (block->ActivateRepairs(nparity)) { - DMSG(6, "NormObject::ActivateRepairs() node>%lu obj>%hu activated blk>%lu segment repairs ...\n", + PLOG(PL_TRACE, "NormObject::ActivateRepairs() node>%lu obj>%hu activated blk>%lu segment repairs ...\n", LocalNodeId(), (UINT16)transport_id, (UINT32)block->GetId()); if (!pending_mask.Set(block->GetId())) { - DMSG(0, "NormObject::ActivateRepairs() pending_mask.Set(%lu) error 2!\n", (UINT32)block->GetId()); + PLOG(PL_ERROR, "NormObject::ActivateRepairs() pending_mask.Set(%lu) error 2!\n", (UINT32)block->GetId()); block->ClearPending(); if (IsStream()) static_cast(this)->UnlockBlock(block->GetId()); @@ -419,7 +438,7 @@ bool NormObject::ActivateRepairs() return repairsActivated; } // end NormObject::ActivateRepairs() -// called by servers only +// called by senders only bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) { // Determine range of blocks possibly pending repair @@ -490,7 +509,7 @@ bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormObject::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n"); return requestAppended; } requestAppended = true; @@ -505,12 +524,12 @@ bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) ASSERT(0); // can't happen break; case NormRepairRequest::ITEMS: - req.AppendRepairItem(transport_id, firstId, GetBlockSize(firstId), 0); + req.AppendRepairItem(fec_id, fec_m, transport_id, firstId, GetBlockSize(firstId), 0); if (2 == blockCount) - req.AppendRepairItem(transport_id, currentId, GetBlockSize(currentId), 0); + req.AppendRepairItem(fec_id, fec_m, transport_id, currentId, GetBlockSize(currentId), 0); break; case NormRepairRequest::RANGES: - req.AppendRepairRange(transport_id, firstId, GetBlockSize(firstId), 0, + req.AppendRepairRange(fec_id, fec_m, transport_id, firstId, GetBlockSize(firstId), 0, transport_id, currentId, GetBlockSize(currentId), 0); break; case NormRepairRequest::ERASURES: @@ -528,12 +547,12 @@ bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormObject::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n"); return requestAppended; } prevForm = NormRepairRequest::INVALID; } - block->AppendRepairAdv(cmd, transport_id, repair_info, GetBlockSize(currentId), segment_size); // (TBD) error check + block->AppendRepairAdv(cmd, transport_id, repair_info, fec_id, fec_m, GetBlockSize(currentId), segment_size); // (TBD) error check requestAppended = true; } } @@ -542,7 +561,7 @@ bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormObject::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n"); return requestAppended; } requestAppended = true; @@ -552,20 +571,20 @@ bool NormObject::AppendRepairAdv(NormCmdRepairAdvMsg& cmd) // make the "req" an INFO-only request req.ClearFlag(NormRepairRequest::BLOCK); req.SetForm(NormRepairRequest::ITEMS); - req.AppendRepairItem(transport_id, 0, 0, 0); + req.AppendRepairItem(fec_id, fec_m, transport_id, 0, 0, 0); if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormObject::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_ERROR, "NormObject::AppendRepairAdv() warning: full msg\n"); return requestAppended; } } return true; } // end NormObject::AppendRepairAdv() -// This is used by sender for watermark check +// This is used by sender (only) for watermark check bool NormObject::FindRepairIndex(NormBlockId& blockId, NormSegmentId& segmentId) const { - ASSERT(!server); + //ASSERT(NULL == sender); if (repair_info) { blockId = 0; @@ -585,12 +604,8 @@ bool NormObject::FindRepairIndex(NormBlockId& blockId, NormSegmentId& segmentId) } } if (block) - { -#ifdef PROTO_DEBUG - ASSERT(block->GetFirstRepair(segmentId)); -#else + { block->GetFirstRepair(segmentId); -#endif // if/else PROTO_DEBUG // The segmentId must < block length for watermarks if (segmentId >= GetBlockSize(block->GetId())) segmentId = GetBlockSize(block->GetId()) - 1; @@ -600,10 +615,10 @@ bool NormObject::FindRepairIndex(NormBlockId& blockId, NormSegmentId& segmentId) } // end NormObject::FindRepairIndex() -// Called by server only +// Called by sender only bool NormObject::IsRepairPending() const { - ASSERT(!server); + //ASSERT(NULL == sender); if (repair_info) return true; if (repair_mask.IsSet()) return true; NormBlockBuffer::Iterator iterator(block_buffer); @@ -625,7 +640,8 @@ bool NormObject::IsPending(bool flush) const } else { - ASSERT(NULL != server); + // This should only be invoked by NORM receiver code + //ASSERT(NULL != sender); NormBlockId firstId; if (GetFirstPending(firstId)) { @@ -644,7 +660,6 @@ bool NormObject::IsPending(bool flush) const NormBlock* block = block_buffer.Find(max_pending_block); if (block) { - ASSERT(block->IsPending()); NormSegmentId firstPending = 0; block->GetFirstPending(firstPending); if (firstPending < max_pending_segment) @@ -672,6 +687,7 @@ bool NormObject::IsPending(bool flush) const // This is a "passive" THRU_SEGMENT repair check // (used to for watermark ack check) +// return "true" if repair is pending up through blockId::segmentId bool NormObject::PassiveRepairCheck(NormBlockId blockId, NormSegmentId segmentId) { @@ -687,19 +703,17 @@ bool NormObject::PassiveRepairCheck(NormBlockId blockId, { NormBlock* block = block_buffer.Find(firstPendingBlock); - if (block) + if (NULL != block) { NormSegmentId firstPendingSegment; if (block->GetFirstPending(firstPendingSegment)) { if (firstPendingSegment <= segmentId) return true; - else - return false; } else { - ASSERT(0); + ASSERT(0); // this will never happen } } else @@ -708,23 +722,46 @@ bool NormObject::PassiveRepairCheck(NormBlockId blockId, } } } + // If it's a stream, make sure the app has read the data + if (IsStream()) + return static_cast(this)->PassiveReadCheck(blockId, segmentId); return false; } // end NormObject::PassiveRepairCheck() -bool NormObject::ClientRepairCheck(CheckLevel level, - NormBlockId blockId, - NormSegmentId segmentId, - bool timerActive, - bool holdoffPhase) +bool NormObject::ReceiverRepairCheck(CheckLevel level, + NormBlockId blockId, + NormSegmentId segmentId, + bool timerActive, + bool holdoffPhase) { // (TBD) make sure it's OK for "max_pending_segment" // and "next_segment_id" to be > blockSize + // These block ids are the range (depending on CheckLevel) + // for which repair_masks need to reset for NACK suppression use + NormBlockId nextBlockId = 0; + NormBlockId endBlockId = 0; bool thruObject = false; + bool startRepairTimer = false; switch (level) { case TO_OBJECT: return false; case THRU_INFO: + if (timerActive) + { + if (!holdoffPhase) + { + current_block_id = 0; + next_segment_id = 0; + } + } + else + { + if (pending_info) + startRepairTimer = true; + current_block_id = 0; + next_segment_id = 0; + } // (TBD) set "thruObject" if info-only obj break; case TO_BLOCK: @@ -732,7 +769,44 @@ bool NormObject::ClientRepairCheck(CheckLevel level, { max_pending_block = blockId; max_pending_segment = 0; - } + } + if (timerActive) + { + if (!holdoffPhase) + { + if (blockId < current_block_id) + { + current_block_id = blockId; + next_segment_id = 0; + } + } + } + else + { + if (pending_info) + { + startRepairTimer = true; + // Cache pending blockId range for TO_BLOCK + GetFirstPending(nextBlockId); + endBlockId = blockId; + } + else + { + NormBlockId firstPending; + if (GetFirstPending(firstPending)) + { + if (firstPending < blockId) + { + startRepairTimer = true; + // Cache pending blockId range for TO_BLOCK + nextBlockId = firstPending; + endBlockId = blockId; + } + } + } + current_block_id = blockId; + next_segment_id = 0; + } // (TBD) set "thruObject" if info-only obj break; case THRU_SEGMENT: @@ -752,6 +826,70 @@ bool NormObject::ClientRepairCheck(CheckLevel level, if (finalSegment <= segmentId) thruObject = true; } + if (timerActive) + { + if (!holdoffPhase) + { + if (blockId < current_block_id) + { + current_block_id = blockId; + next_segment_id = segmentId + 1; + } + else if (blockId == current_block_id) + { + if (segmentId < next_segment_id) + next_segment_id = segmentId + 1; + } + } + } + else + { + if (pending_info) + { + startRepairTimer = true; + // Cache pending blockId range for THRU_SEGMENT + GetFirstPending(nextBlockId); + endBlockId = blockId; + endBlockId++; + } + else + { + + NormBlockId firstPending; + if (GetFirstPending(firstPending)) + { + if (firstPending < blockId) + { + startRepairTimer = true; + } + else if (firstPending == blockId) + { + NormBlock* block = block_buffer.Find(blockId); + if (NULL != block) + { + ASSERT(block->IsPending()); + NormSymbolId firstPendingSegment; + block->GetFirstPending(firstPendingSegment); + if (firstPendingSegment <= segmentId) + startRepairTimer = true; + } + else + { + startRepairTimer = true; + } + } + if (startRepairTimer) + { + // Cache pending blockId range for THRU_SEGMENT + nextBlockId = firstPending; + endBlockId = blockId; + endBlockId++; + } + } + } + current_block_id = blockId; + next_segment_id = segmentId + 1; + } break; case THRU_BLOCK: if (blockId > max_pending_block) @@ -761,6 +899,45 @@ bool NormObject::ClientRepairCheck(CheckLevel level, } if (blockId == final_block_id) thruObject = true; + if (timerActive) + { + if (!holdoffPhase) + { + if (blockId < current_block_id) + { + current_block_id = blockId; + next_segment_id = GetBlockSize(blockId); + } + } + } + else + { + if (pending_info) + { + startRepairTimer = true; + // Cache pending blockId range for THRU_BLOCK + GetFirstPending(nextBlockId); + endBlockId = blockId; + endBlockId++; + } + else + { + NormBlockId firstPending; + if (GetFirstPending(firstPending)) + { + if (firstPending <= blockId) + { + startRepairTimer = true; + // Cache pending blockId range for THRU_BLOCK + nextBlockId = firstPending; + endBlockId = blockId; + endBlockId++; + } + } + } + current_block_id = blockId; + next_segment_id = GetBlockSize(blockId); + } break; case THRU_OBJECT: if (!IsStream()) @@ -769,141 +946,75 @@ bool NormObject::ClientRepairCheck(CheckLevel level, max_pending_block = blockId; max_pending_segment = GetBlockSize(max_pending_block); thruObject = true; - break; - default: + if (!timerActive) + { + if (pending_info || pending_mask.IsSet()) + { + startRepairTimer = true; + // Cache pending blockId range for THRU_OBJECT + GetFirstPending(nextBlockId); + GetLastPending(endBlockId); + endBlockId++; + } + if (IsStream()) + current_block_id = max_pending_block; + else + current_block_id = final_block_id; + next_segment_id = GetBlockSize(current_block_id); + } break; } // end switch (level) - if (thruObject && session.RcvrIsLowDelay()) + // This special object abort is for silent receivers configured as "low delay" + // so they immediately deliver even partially completed objects (aborted) to + // the app as soon as a new object is begun or old object transmission completed + if (thruObject && session.RcvrIsLowDelay() && !IsStream()) { - ASSERT(NULL != server); - server->AbortObject(this); + sender->AbortObject(this); return false; } - - if (timerActive) - { - if (holdoffPhase) return false; - switch (level) - { - case THRU_INFO: - current_block_id = 0; - next_segment_id = 0; - break; - case TO_BLOCK: - if (blockId < current_block_id) - { - current_block_id = blockId; - next_segment_id = 0; - } - break; - case THRU_SEGMENT: - if (blockId < current_block_id) - { - current_block_id = blockId; - next_segment_id = segmentId + 1; - } - else if (blockId == current_block_id) - { - if (segmentId < next_segment_id) - next_segment_id = segmentId + 1; - } - break; - case THRU_BLOCK: - if (blockId < current_block_id) - { - current_block_id = blockId; - next_segment_id = GetBlockSize(blockId); - } - break; - default: - break; - } - return false; // repair_timer already running - } - bool needRepair = false; - if (pending_info) - { - repair_info = false; - needRepair = true; - } - if ((level > THRU_INFO) && pending_mask.IsSet()) + if (startRepairTimer) { + // Reset repair_info, NormObject::repair_mask and NormBlock::repair_masks + // (These are used for NACK suppression state accumulation and are reset + // here at the start of a fresh NACK cycle) + repair_info = false; // info repair mask if (repair_mask.IsSet()) repair_mask.Clear(); - NormBlockId nextId; - if (GetFirstPending(nextId)) + while (nextBlockId < endBlockId) { - NormBlockId lastId; - GetLastPending(lastId); - if ((level < THRU_OBJECT) && (blockId < lastId)) lastId = blockId; - if (level > TO_BLOCK) lastId++; - while (nextId < lastId) - { - if (nextId > lastId) break; // (TBD) is this line unecessary? - NormBlock* block = block_buffer.Find(nextId); - if (NULL != block) - { - if ((nextId == blockId) && (THRU_SEGMENT == level)) - { - NormSymbolId firstPending = 0; - if (!block->GetFirstPending(firstPending)) ASSERT(0); - if (firstPending <= segmentId) - { - block->ClearRepairs(); - needRepair = true; - } - } - else - { - block->ClearRepairs(); - needRepair = true; - } - } - else - { - needRepair = true; - } - nextId++; - if (!GetNextPending(nextId)) break; - } // end while (nextId < lastId) - } + NormBlock* block = block_buffer.Find(nextBlockId); + if (NULL != block) block->ClearRepairs(); + nextBlockId++; + if (!GetNextPending(nextBlockId)) break; + } + return true; } - switch (level) + else { - case THRU_INFO: - current_block_id = 0; - next_segment_id = 0; - break; - case TO_BLOCK: - current_block_id = blockId; - next_segment_id = 0; - break; - case THRU_SEGMENT: - current_block_id = blockId; - next_segment_id = segmentId + 1; - break; - case THRU_BLOCK: - current_block_id = blockId; - next_segment_id = GetBlockSize(blockId); - break; - case THRU_OBJECT: - if (IsStream()) - current_block_id = max_pending_block; - else - current_block_id = final_block_id; - next_segment_id = GetBlockSize(current_block_id); - break; - default: - break; + return false; } - return needRepair; -} // end NormObject::ClientRepairCheck() +} // end NormObject::ReceiverRepairCheck() -// Note this clears "repair_mask" state -// (called on client repair_timer timeout) +bool NormObject::ReceiverRewindCheck(NormBlockId blockId, + NormSegmentId segmentId) +{ + if (blockId > current_block_id) + return false; + else if (blockId < current_block_id) + return true; + else // (blockId == current_block_id) + return ((segmentId+1) < next_segment_id); +} // end NormObject::ReceiverRewindCheck() + +// Note this partially clears "repair_mask" state +// Thus should only be called once at end of repair cycle +// (called on receiver repair_timer timeout) +// This uses the repair_mask that has accumulated state from +// oveheard NACKs or NORM_CMD(REPAIR_ADV) to possibly suppress NACKing +// up through the "current_block_id" bool NormObject::IsRepairPending(bool flush) { - ASSERT(server); + // Only NORM receiver code sshould call this if (pending_info && !repair_info) return true; // Calculate repair_mask = pending_mask - repair_mask repair_mask.XCopy(pending_mask); @@ -958,9 +1069,13 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, { NormBlockId lastId; GetLastPending(lastId); // for debug output only - DMSG(6, "NormObject::AppendRepairRequest() node>%lu obj>%hu, blk>%lu->%lu (maxPending:%lu)\n", - LocalNodeId(), (UINT16)transport_id, - (UINT32)nextId, (UINT32)lastId, (UINT32)max_pending_block); + // Two PLOG() statements because one doesn't work right for some reason?! + if (PL_TRACE <= GetDebugLevel()) + { + PLOG(PL_TRACE, "NormObject::AppendRepairRequest() node>%lu obj>%lu, blk>%lu->%lu ", + LocalNodeId(), (UINT32)((UINT16)GetId()), (UINT32)nextId, (UINT32)lastId); + PLOG(PL_ALWAYS, "(maxPending = %lu)\n", (UINT32)max_pending_block); + } bool appendRequest = false; NormBlock* block = iterating ? block_buffer.Find(nextId) : NULL; if (block) @@ -992,7 +1107,7 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, { if (0 == nack.PackRepairRequest(req)) { - DMSG(3, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); return requestAppended; } requestAppended = true; @@ -1010,21 +1125,22 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, prevForm = nextForm; } if (NormRepairRequest::INVALID != nextForm) - DMSG(6, "NormObject::AppendRepairRequest() BLOCK request\n"); + PLOG(PL_TRACE, "NormObject::AppendRepairRequest() BLOCK request\n"); switch (nextForm) { case NormRepairRequest::ITEMS: - req.AppendRepairItem(transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check + req.AppendRepairItem(fec_id, fec_m, transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check if (2 == consecutiveCount) { prevId++; - req.AppendRepairItem(transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check + req.AppendRepairItem(fec_id, fec_m, transport_id, prevId, GetBlockSize(prevId), 0); // (TBD) error check } break; case NormRepairRequest::RANGES: { NormBlockId lastId = prevId+consecutiveCount-1; - req.AppendRepairRange(transport_id, prevId, GetBlockSize(prevId), 0, + req.AppendRepairRange(fec_id, fec_m, + transport_id, prevId, GetBlockSize(prevId), 0, transport_id, lastId, GetBlockSize(lastId), 0); // (TBD) error check break; } @@ -1033,11 +1149,14 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, } // end switch(nextForm) if (block) { + // Note our NACK construction is limited by "max_pending_block:max_pending_segment" + // based on most recent transmissions from sender bool blockIsPending = false; if (nextId == max_pending_block) { - NormSymbolId firstPending = 0; - if (!block->GetFirstPending(firstPending)) ASSERT(0); + ASSERT(block->IsPending()); + NormSymbolId firstPending; + block->GetFirstPending(firstPending); if (firstPending < max_pending_segment) blockIsPending = true; } else @@ -1051,22 +1170,22 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, { if (0 == nack.PackRepairRequest(req)) { - DMSG(3, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); return requestAppended; } } if (flush || (nextId != max_pending_block)) { - block->AppendRepairRequest(nack, numData, nparity, transport_id, + block->AppendRepairRequest(nack, fec_id, fec_m, numData, nparity, transport_id, pending_info, segment_size); // (TBD) error check } else { if (max_pending_segment < numData) - block->AppendRepairRequest(nack, max_pending_segment, 0, transport_id, + block->AppendRepairRequest(nack, fec_id, fec_m, max_pending_segment, 0, transport_id, pending_info, segment_size); // (TBD) error check else - block->AppendRepairRequest(nack, numData, nparity, transport_id, + block->AppendRepairRequest(nack, fec_id, fec_m, numData, nparity, transport_id, pending_info, segment_size); // (TBD) error check } requestAppended = true; @@ -1091,12 +1210,11 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, // This conditional makes sure any outstanding requests constructed // are packed into the nack message. - if ((NormRepairRequest::INVALID != prevForm) && - (NACK_NONE != nacking_mode)) + if ((NormRepairRequest::INVALID != prevForm) && (NACK_NONE != nacking_mode)) { if (0 == nack.PackRepairRequest(req)) { - DMSG(3, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); return requestAppended; } requestAppended = true; @@ -1109,10 +1227,10 @@ bool NormObject::AppendRepairRequest(NormNackMsg& nack, req.SetForm(NormRepairRequest::ITEMS); req.ResetFlags(); req.SetFlag(NormRepairRequest::INFO); - req.AppendRepairItem(transport_id, 0, 0, 0); // (TBD) error check + req.AppendRepairItem(fec_id, fec_m, transport_id, 0, 0, 0); // (TBD) error check if (0 == nack.PackRepairRequest(req)) { - DMSG(3, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormObject::AppendRepairRequest() warning: full NACK msg\n"); return requestAppended; } requestAppended = true; @@ -1135,20 +1253,20 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, if (info_len > segment_size) { info_len = segment_size; - DMSG(0, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " - "Warning! info too long.\n", LocalNodeId(), server->GetId(), + PLOG(PL_WARN, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " + "Warning! info too long.\n", LocalNodeId(), sender->GetId(), (UINT16)transport_id); } memcpy(info_ptr, infoMsg.GetInfo(), info_len); pending_info = false; - session.Notify(NormController::RX_OBJECT_INFO, server, this); + session.Notify(NormController::RX_OBJECT_INFO, sender, this); } else { // (TBD) Verify info hasn't changed? - DMSG(6, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " - "received duplicate info ...\n", LocalNodeId(), - server->GetId(), (UINT16)transport_id); + PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " + "received duplicate info ...\n", + LocalNodeId(), sender->GetId(), (UINT16)transport_id); } } else // NORM_MSG_DATA @@ -1163,12 +1281,12 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, stream = static_cast(this); if (!stream->StreamUpdateStatus(blockId)) { - DMSG(4, "NormObject::HandleObjectMessage() node:%lu server:%lu obj>%hu blk>%lu " - "broken stream ...\n", LocalNodeId(), server->GetId(), (UINT16)transport_id, (UINT32)blockId); - server->IncrementResyncCount(); + PLOG(PL_WARN, "NormObject::HandleObjectMessage() node:%lu sender:%lu obj>%hu blk>%lu " + "broken stream ...\n", LocalNodeId(), sender->GetId(), (UINT16)transport_id, (UINT32)blockId); + sender->IncrementResyncCount(); while (!stream->StreamUpdateStatus(blockId)) { - // Server is too far ahead of me ... + // Sender is too far ahead of me ... NormBlockId firstId; if (GetFirstPending(firstId)) { @@ -1176,7 +1294,7 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, if (block) { block_buffer.Remove(block); - server->PutFreeBlock(block); + sender->PutFreeBlock(block); } pending_mask.Unset(firstId); } @@ -1193,10 +1311,10 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, NormBlock* block = block_buffer.Find(blockId); if (!block) { - if (!(block = server->GetFreeBlock(transport_id, blockId))) + if (!(block = sender->GetFreeBlock(transport_id, blockId))) { - //DMSG(2, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " - // "Warning! no free blocks ...\n", LocalNodeId(), server->GetId(), + //DMSG(2, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " + // "Warning! no free blocks ...\n", LocalNodeId(), sender->GetId(), // (UINT16)transport_id); return; } @@ -1208,8 +1326,8 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, UINT16 segmentLength = data.GetPayloadDataLength(); if (segmentLength > segment_size) { - DMSG(0, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " - "Error! segment too large ...\n", LocalNodeId(), server->GetId(), + PLOG(PL_ERROR, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " + "Error! segment too large ...\n", LocalNodeId(), sender->GetId(), (UINT16)transport_id); return; } @@ -1225,8 +1343,8 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, bool isSourceSymbol = (segmentId < numData); // Try to cache segment in block buffer in case it's needed for decoding - char* segment = (!isSourceSymbol || !server->SegmentPoolIsEmpty()) ? - server->GetFreeSegment(transport_id, blockId) : NULL; + char* segment = (!isSourceSymbol || !sender->SegmentPoolIsEmpty()) ? + sender->GetFreeSegment(transport_id, blockId) : NULL; if (segment) { @@ -1237,8 +1355,8 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, } else { - //DMSG(2, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " - // "Warning! no free segments ...\n", LocalNodeId(), server->GetId(), + //DMSG(2, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " + // "Warning! no free segments ...\n", LocalNodeId(), sender->GetId(), // (UINT16)transport_id); if (!isSourceSymbol) return; } @@ -1253,14 +1371,14 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, { objectUpdated = true; // For statistics only (TBD) #ifdef NORM_DEBUG - server->IncrementRecvGoodput(segmentLength); + sender->IncrementRecvGoodput(segmentLength); } else { if (IsStream()) - DMSG(4, "NormObject::HandleObjectMessage() WriteSegment() error\n"); + PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() WriteSegment() error\n"); else - DMSG(0, "NormObject::HandleObjectMessage() WriteSegment() error\n"); + PLOG(PL_ERROR, "NormObject::HandleObjectMessage() WriteSegment() error\n"); } } else @@ -1273,8 +1391,8 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, { // Decode (if pending_mask.FirstSet() < numData) // and write any decoded data segments to object - DMSG(8, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu blk>%lu " - "completed block ...\n", LocalNodeId(), server->GetId(), + PLOG(PL_DETAIL, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu blk>%lu " + "completed block ...\n", LocalNodeId(), sender->GetId(), (UINT16)transport_id, (UINT32)block->GetId()); UINT16 erasureCount = 0; UINT16 nextErasure = 0; @@ -1290,47 +1408,49 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, { if (block->IsPending(nextSegment)) { - server->SetErasureLoc(erasureCount++, nextSegment); - segment = server->GetRetrievalSegment(); - ASSERT(segment); + sender->SetErasureLoc(erasureCount++, nextSegment); + segment = sender->GetRetrievalSegment(); + ASSERT(NULL != segment); UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength(); #ifdef SIMULATE payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX); #endif // SIMULATE // Zeroize the missing segment payload in prep for decoding - memset(segment, 0, payloadMax+1); - server->SetRetrievalLoc(retrievalCount++, nextSegment); + memset(segment, 0, payloadMax); + sender->SetRetrievalLoc(retrievalCount++, nextSegment); block->SetSegment(nextSegment, segment); } else if (!block->GetSegment(nextSegment)) { if (!(segment = RetrieveSegment(blockId, nextSegment))) { + // Stream objects should be the only ones that fail + // to retrieve segments (due to stream buffer size limit) ASSERT(IsStream()); block->SetPending(nextSegment); block->IncrementErasureCount(); // Clear block of any retrieval (temp) segments for (UINT16 i = 0; i < retrievalCount; i++) - block->DetachSegment(server->GetRetrievalLoc(i)); + block->DetachSegment(sender->GetRetrievalLoc(i)); return; } - server->SetRetrievalLoc(retrievalCount++, nextSegment); + sender->SetRetrievalLoc(retrievalCount++, nextSegment); block->SetSegment(nextSegment, segment); } } nextErasure = numData; // Set erasure locs for any missing parity symbol segments, too! while (block->GetNextPending(nextErasure)) - server->SetErasureLoc(erasureCount++, nextErasure++); + sender->SetErasureLoc(erasureCount++, nextErasure++); } // end if (nextErasure < numData) } // end if (block->GetFirstPending(nextErasure)) if (erasureCount) { - server->Decode(block->SegmentList(), numData, erasureCount); + sender->Decode(block->SegmentList(), numData, erasureCount); for (UINT16 i = 0; i < erasureCount; i++) { - NormSegmentId sid = server->GetErasureLoc(i); + NormSegmentId sid = sender->GetErasureLoc(i); if (sid < numData) { if (WriteSegment(blockId, sid, block->GetSegment(sid))) @@ -1338,14 +1458,14 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, objectUpdated = true; // For statistics only (TBD) #ifdef NORM_DEBUG // "segmentLength" is not necessarily correct here (TBD - fix this) - server->IncrementRecvGoodput(segmentLength); + sender->IncrementRecvGoodput(segmentLength); } else { if (IsStream()) - DMSG(4, "NormObject::HandleObjectMessage() WriteSegment() error\n"); + PLOG(PL_DEBUG, "NormObject::HandleObjectMessage() WriteSegment() error\n"); else - DMSG(0, "NormObject::HandleObjectMessage() WriteSegment() error\n"); + PLOG(PL_ERROR, "NormObject::HandleObjectMessage() WriteSegment() error\n"); } } else @@ -1356,11 +1476,11 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, } // Clear any temporarily retrieved segments for the block for (UINT16 i = 0; i < retrievalCount; i++) - block->DetachSegment(server->GetRetrievalLoc(i)); + block->DetachSegment(sender->GetRetrievalLoc(i)); // OK, we're done with this block pending_mask.Unset(blockId); block_buffer.Remove(block); - server->PutFreeBlock(block); + sender->PutFreeBlock(block); } // Notify application of new data available // (TBD) this could be improved for stream objects @@ -1370,22 +1490,22 @@ void NormObject::HandleObjectMessage(const NormObjectMsg& msg, if ((NULL == stream) || stream->IsReadReady()) { notify_on_update = false; - session.Notify(NormController::RX_OBJECT_UPDATED, server, this); + session.Notify(NormController::RX_OBJECT_UPDATED, sender, this); } } } else { - DMSG(6, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " + PLOG(PL_TRACE, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " "received duplicate segment ...\n", LocalNodeId(), - server->GetId(), (UINT16)transport_id); + sender->GetId(), (UINT16)transport_id); } } else { - DMSG(6, "NormObject::HandleObjectMessage() node>%lu server>%lu obj>%hu " + PLOG(PL_TRACE, "NormObject::HandleObjectMessage() node>%lu sender>%lu obj>%hu " "received duplicate block message ...\n", LocalNodeId(), - server->GetId(), (UINT16)transport_id); + sender->GetId(), (UINT16)transport_id); } // end if/else pending_mask.Test(blockId) } // end if/else (NORM_MSG_INFO) } // end NormObject::HandleObjectMessage() @@ -1414,7 +1534,7 @@ bool NormObject::ReclaimSourceSegments(NormSegmentPool& segmentPool) } // end NormObject::ReclaimSourceSegments() -// Steals non-pending block (oldest first) for server resource management +// Steals non-pending block (oldest first) for _sender_ resource management // (optionally excludes block indicated by blockId) NormBlock* NormObject::StealNonPendingBlock(bool excludeBlock, NormBlockId excludeId) { @@ -1447,7 +1567,7 @@ NormBlock* NormObject::StealNonPendingBlock(bool excludeBlock, NormBlockId exclu } // end NormObject::StealNonPendingBlock() -// For client & server resource management, steals newer block resources when +// For receiver & sender resource management, steals newer block resources when // needing resources for ordinally older blocks. NormBlock* NormObject::StealNewestBlock(bool excludeBlock, NormBlockId excludeId) { @@ -1471,7 +1591,7 @@ NormBlock* NormObject::StealNewestBlock(bool excludeBlock, NormBlockId excludeId } // end NormObject::StealNewestBlock() -// For silent client resource management, steals older block resources when +// For silent receiver resource management, steals older block resources when // needing resources for ordinally newer blocks. NormBlock* NormObject::StealOldestBlock(bool excludeBlock, NormBlockId excludeId) { @@ -1496,13 +1616,21 @@ NormBlock* NormObject::StealOldestBlock(bool excludeBlock, NormBlockId excludeId -bool NormObject::NextServerMsg(NormObjectMsg* msg) +bool NormObject::NextSenderMsg(NormObjectMsg* msg) { // Init() the message if (pending_info) - ((NormInfoMsg*)msg)->Init(); + { + NormInfoMsg* infoMsg = static_cast(msg); + infoMsg->Init(); + infoMsg->SetFecId(fec_id); + } else - ((NormDataMsg*)msg)->Init(); + { + NormDataMsg* dataMsg = static_cast(msg); + dataMsg->Init(); + dataMsg->SetFecId(fec_id); // we do this here so the base header length is properly set + } // General object message flags msg->ResetFlags(); @@ -1517,24 +1645,56 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) default: break; } - if (info_ptr) msg->SetFlag(NormObjectMsg::FLAG_INFO); - msg->SetFecId(129); + if (info_ptr) + msg->SetFlag(NormObjectMsg::FLAG_INFO); + msg->SetObjectId(transport_id); // We currently always apply the FTI extension - NormFtiExtension fti; - msg->AttachExtension(fti); + switch (fec_id) + { + case 2: + { + NormFtiExtension2 fti; + msg->AttachExtension(fti); + fti.SetObjectSize(object_size); + fti.SetFecFieldSize(fec_m); + fti.SetFecGroupSize(1); + fti.SetSegmentSize(segment_size); + fti.SetFecMaxBlockLen(ndata); + fti.SetFecNumParity(nparity); + break; + } + case 5: + { + NormFtiExtension5 fti; + msg->AttachExtension(fti); + fti.SetObjectSize(object_size); + fti.SetSegmentSize(segment_size); + fti.SetFecMaxBlockLen((UINT8)ndata); + fti.SetFecNumParity((UINT8)nparity); + break; + } + case 129: + { + NormFtiExtension129 fti; + msg->AttachExtension(fti); + fti.SetObjectSize(object_size); + fti.SetFecInstanceId(0); // ZERO is for legacy MDP/NORM FEC encoder (TBD - use appropriate instanceId) + fti.SetSegmentSize(segment_size); + fti.SetFecMaxBlockLen(ndata); + fti.SetFecNumParity(nparity); + break; + } + default: + ASSERT(0); + return false; + } - fti.SetSegmentSize(segment_size); - fti.SetObjectSize(object_size); - fti.SetFecInstanceId(0); // ZERO is for legacy MDP/NORM FEC encoder (TBD - use appropriate instanceId) - fti.SetFecMaxBlockLen(ndata); - fti.SetFecNumParity(nparity); - if (pending_info) { // (TBD) set REPAIR_FLAG for retransmitted info - NormInfoMsg* infoMsg = (NormInfoMsg*)msg; + NormInfoMsg* infoMsg = static_cast(msg); infoMsg->SetInfo(info_ptr, info_len); pending_info = false; return true; @@ -1545,20 +1705,19 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) // Attempt to advance stream (probably had been repair-delayed) if (IsStream()) { - static_cast(this)->StreamAdvance(); - if (pending_mask.IsSet()) + if (static_cast(this)->StreamAdvance()) { - return NextServerMsg(msg); + return NextSenderMsg(msg); } else { - ASSERT(IsRepairPending()); + //ASSERT(IsRepairPending()); return false; } } else { - DMSG(0, "NormObject::NextServerMsg() pending object w/ no pending blocks?!\n"); + PLOG(PL_FATAL, "NormObject::NextSenderMsg() pending object w/ no pending blocks?!\n"); return false; } } @@ -1568,52 +1727,49 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) NormBlock* block = block_buffer.Find(blockId); if (!block) { - if (NULL == (block = session.ServerGetFreeBlock(transport_id, blockId))) + if (NULL == (block = session.SenderGetFreeBlock(transport_id, blockId))) { - DMSG(2, "NormObject::NextServerMsg() node>%lu warning: server resource " - "constrained (no free blocks).\n", LocalNodeId()); + PLOG(PL_INFO, "NormObject::NextSenderMsg() node>%lu warning: sender resource " + "constrained (no free blocks).\n", LocalNodeId()); return false; } // Load block with zero initialized parity segments UINT16 totalBlockLen = numData + nparity; for (UINT16 i = numData; i < totalBlockLen; i++) { - char* s = session.ServerGetFreeSegment(transport_id, blockId); + char* s = session.SenderGetFreeSegment(transport_id, blockId); if (s) { UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength(); #ifdef SIMULATE payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX); #endif // SIMULATE - memset(s, 0, payloadMax+1); // extra byte for msg flags + memset(s, 0, payloadMax); // extra byte for msg flags block->AttachSegment(i, s); } else { - DMSG(2, "NormObject::NextServerMsg() node>%lu warning: server resource " - "constrained (no free segments).\n", LocalNodeId()); - session.ServerPutFreeBlock(block); + PLOG(PL_INFO, "NormObject::NextSenderMsg() node>%lu warning: sender resource " + "constrained (no free segments).\n", LocalNodeId()); + session.SenderPutFreeBlock(block); return false; } - } - - block->TxInit(blockId, numData, session.ServerAutoParity()); + } + block->TxInit(blockId, numData, session.SenderAutoParity()); if (blockId < max_pending_block) block->SetFlag(NormBlock::IN_REPAIR); while (!block_buffer.Insert(block)) { - ASSERT(STREAM == type); + //ASSERT(STREAM == type); if (blockId > block_buffer.RangeLo()) { NormBlock* lowBlock = block_buffer.Find(block_buffer.RangeLo()); - ASSERT(NULL != lowBlock); - ASSERT(!lowBlock->IsPending()); NormBlockId lowBlockId = lowBlock->GetId(); bool push = static_cast(this)->GetPushMode(); if (!push && (lowBlock->IsRepairPending() || IsRepairSet(lowBlockId))) { // Pending repairs delaying stream advance - DMSG(4, "NormObject::NextServerMsg() node>%lu pending repairs delaying stream progress\n", LocalNodeId()); - session.ServerPutFreeBlock(block); + PLOG(PL_DEBUG, "NormObject::NextSenderMsg() node>%lu pending repairs delaying stream progress\n", LocalNodeId()); + session.SenderPutFreeBlock(block); return false; } else @@ -1624,23 +1780,23 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) pending_mask.Unset(lowBlockId); if (IsStream()) // always true static_cast(this)->UnlockBlock(lowBlockId); - session.ServerPutFreeBlock(lowBlock); + session.SenderPutFreeBlock(lowBlock); continue; } } else if (IsStream()) { - DMSG(3, "NormObject::NextServerMsg() node>%lu Warning! can't repair old stream block\n", LocalNodeId()); - session.ServerPutFreeBlock(block); + PLOG(PL_WARN, "NormObject::NextSenderMsg() node>%lu Warning! can't repair old stream block\n", LocalNodeId()); + session.SenderPutFreeBlock(block); repair_mask.Unset(blockId); // just in case pending_mask.Unset(blockId); // Unlock (set to non-pending status) the corresponding stream_buffer block static_cast(this)->UnlockBlock(blockId); - return NextServerMsg(msg); + return NextSenderMsg(msg); } else { - DMSG(0, "NormObject::NextServerMsg() invalid non-stream state!\n"); + PLOG(PL_FATAL, "NormObject::NextSenderMsg() invalid non-stream state!\n"); ASSERT(0); return false; } @@ -1649,7 +1805,7 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) NormSegmentId segmentId = 0; if (!block->GetFirstPending(segmentId)) { - DMSG(0, "NormObject::NextServerMsg() nothing pending!?\n"); + PLOG(PL_ERROR, "NormObject::NextSenderMsg() nothing pending!?\n"); ASSERT(0); } @@ -1665,12 +1821,12 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) //(for streams, it currently means the stream is pending, but app hasn't yet written data) if (!IsStream()) { - DMSG(0, "NormObject::NextServerMsg() ReadSegment() error\n"); + PLOG(PL_FATAL, "NormObject::NextSenderMsg() ReadSegment() error\n"); return false; } else if (static_cast(this)->IsOldBlock(blockId)) { - DMSG(3, "NormObject::NextServerMsg() node>%lu Warning! can't repair old stream segment\n", LocalNodeId()); + PLOG(PL_ERROR, "NormObject::NextSenderMsg() node>%lu Warning! can't repair old stream segment\n", LocalNodeId()); block->UnsetPending(segmentId); if (!block->IsPending()) { @@ -1681,7 +1837,7 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) if (session.SndrEmcon() && HaveInfo()) pending_info = true; } - return NextServerMsg(msg); + return NextSenderMsg(msg); } else { @@ -1705,30 +1861,34 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) memset(buffer+payloadLength, 0, payloadMax-payloadLength); // (TBD) the encode routine could update the block's parity readiness block->UpdateSegSizeMax(payloadLength); - session.ServerEncode(data->AccessPayload(), block->SegmentList(numData)); + session.SenderEncode(segmentId, data->AccessPayload(), block->SegmentList(numData)); block->IncreaseParityReadiness(); } } else - { + { if (!block->ParityReady(numData)) { ASSERT(0 == block->ParityReadiness()); CalculateBlockParity(block); } - ASSERT(block->ParityReady(numData)); char* segment = block->GetSegment(segmentId); - ASSERT(segment); + ASSERT(NULL != segment); // We only need to send FEC content to cover the biggest segment // sent for the block. - data->SetPayload(segment, block->GetSegSizeMax()); +#ifdef SIMULATE + UINT16 payloadMax = MIN(block->GetSegSizeMax(), SIM_PAYLOAD_MAX); + data->SetPayload(segment, payloadMax); + data->SetPayloadLength(block->GetSegSizeMax()); // correct the msg length +#else + data->SetPayload(segment, block->GetSegSizeMax()); +#endif // if/else SIMULATE } block->UnsetPending(segmentId); - if (block->InRepair()) - data->SetFlag(NormObjectMsg::FLAG_REPAIR); - data->SetFecBlockId(blockId); - data->SetFecBlockLen(numData); - data->SetFecSymbolId(segmentId); + //if (block->InRepair()) + // data->SetFlag(NormObjectMsg::FLAG_REPAIR); + + data->SetFecPayloadId(fec_id, blockId, segmentId, numData, fec_m); if (!block->IsPending()) { // End of block reached @@ -1742,6 +1902,17 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) if (blockId == max_pending_block) max_pending_block++; } + + // We update the object/block flow control timestamp to + // mark the "recent" activity for this object or block + if (session.GetFlowControl() > 0.0) + { + ProtoTime currentTime; + currentTime.GetCurrentTime(); + SetLastNackTime(currentTime); + if (IsStream()) + static_cast(this)->SetLastNackTime(blockId, currentTime); + } if (!pending_mask.IsSet()) { @@ -1760,10 +1931,10 @@ bool NormObject::NextServerMsg(NormObjectMsg* msg) } } return true; -} // end NormObject::NextServerMsg() +} // end NormObject::NextSenderMsg() -void NormStreamObject::StreamAdvance() +bool NormStreamObject::StreamAdvance() { // (TBD) should we make sure !pending_mask.IsSet()??? NormBlockId nextBlockId = stream_next_id; @@ -1773,37 +1944,48 @@ void NormStreamObject::StreamAdvance() if (block_buffer.CanInsert(nextBlockId)) { if (pending_mask.Set(nextBlockId)) + { stream_next_id++; + return true; + } else - DMSG(0, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set set stream pending mask (1)\n", + { + PLOG(PL_ERROR, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set set stream pending mask (1)\n", LocalNodeId()); + } } else { NormBlock* block = block_buffer.Find(block_buffer.RangeLo()); - ASSERT(block); + ASSERT(NULL != block); if (!block->IsTransmitPending()) { // ??? (TBD) Should this block be returned to the pool right now??? // especially for "push" enabled streams if (pending_mask.Set(nextBlockId)) + { stream_next_id++; + return true; + } else - DMSG(0, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set stream pending mask (2)\n", + { + PLOG(PL_ERROR, "NormStreamObject::StreamAdvance() error: node>%lu couldn't set stream pending mask (2)\n", LocalNodeId()); + } } else { - DMSG(3, "NormStreamObject::StreamAdvance() warning: node>%lu pending segment repairs (blk>%lu) " + PLOG(PL_WARN, "NormStreamObject::StreamAdvance() warning: node>%lu pending segment repairs (blk>%lu) " "delaying stream advance ...\n", LocalNodeId(), (UINT32)block->GetId()); } } } else { - DMSG(3, "NormStreamObject::StreamAdvance() warning: node>%lu pending block repair delaying stream advance ...\n", + PLOG(PL_WARN, "NormStreamObject::StreamAdvance() warning: node>%lu pending block repair delaying stream advance ...\n", LocalNodeId()); } + return false; } // end NormStreamObject::StreamAdvance() bool NormObject::CalculateBlockParity(NormBlock* block) @@ -1823,7 +2005,7 @@ bool NormObject::CalculateBlockParity(NormBlock* block) if (payloadLength < payloadMax) memset(buffer+payloadLength, 0, payloadMax-payloadLength+1); block->UpdateSegSizeMax(payloadLength); - session.ServerEncode(buffer, block->SegmentList(numData)); + session.SenderEncode(i, buffer, block->SegmentList(numData)); } else { @@ -1834,9 +2016,9 @@ bool NormObject::CalculateBlockParity(NormBlock* block) return true; } // end NormObject::CalculateBlockParity() -NormBlock* NormObject::ServerRecoverBlock(NormBlockId blockId) +NormBlock* NormObject::SenderRecoverBlock(NormBlockId blockId) { - NormBlock* block = session.ServerGetFreeBlock(transport_id, blockId); + NormBlock* block = session.SenderGetFreeBlock(transport_id, blockId); if (block) { UINT16 numData = GetBlockSize(blockId); @@ -1846,21 +2028,21 @@ NormBlock* NormObject::ServerRecoverBlock(NormBlockId blockId) UINT16 totalBlockLen = numData + nparity; for (UINT16 i = numData; i < totalBlockLen; i++) { - char* s = session.ServerGetFreeSegment(transport_id, blockId); + char* s = session.SenderGetFreeSegment(transport_id, blockId); if (s) { UINT16 payloadMax = segment_size + NormDataMsg::GetStreamPayloadHeaderLength(); #ifdef SIMULATE payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX); #endif // SIMULATE - memset(s, 0, payloadMax+1); // extra byte for msg flags + memset(s, 0, payloadMax); // extra byte for msg flags block->AttachSegment(i, s); } else { - //DMSG(2, "NormObject::ServerRecoverBlock() node>%lu Warning! server resource " + //DMSG(2, "NormObject::SenderRecoverBlock() node>%lu Warning! sender resource " // "constrained (no free segments).\n", LocalNodeId()); - session.ServerPutFreeBlock(block); + session.SenderPutFreeBlock(block); return (NormBlock*)NULL; } } @@ -1869,34 +2051,34 @@ NormBlock* NormObject::ServerRecoverBlock(NormBlockId blockId) { if (!block_buffer.Insert(block)) { - session.ServerPutFreeBlock(block); - DMSG(4, "NormObject::ServerRecoverBlock() node>%lu couldn't buffer recovered block\n"); + session.SenderPutFreeBlock(block); + PLOG(PL_DEBUG, "NormObject::SenderRecoverBlock() node>%lu couldn't buffer recovered block\n"); return NULL; } return block; } else { - session.ServerPutFreeBlock(block); + session.SenderPutFreeBlock(block); return (NormBlock*)NULL; } } else { - //DMSG(2, "NormObject::ServerRecoverBlock() node>%lu Warning! server resource " + //DMSG(2, "NormObject::SenderRecoverBlock() node>%lu Warning! sender resource " // "constrained (no free blocks).\n", LocalNodeId()); return (NormBlock*)NULL; } -} // end NormObject::ServerRecoverBlock() +} // end NormObject::SenderRecoverBlock() ///////////////////////////////////////////////////////////////// // // NormFileObject Implementation // NormFileObject::NormFileObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId) - : NormObject(FILE, theSession, theServer, objectId), + : NormObject(FILE, theSession, theSender, objectId), large_block_length(0), small_block_length(0) { path[0] = '\0'; @@ -1912,12 +2094,12 @@ bool NormFileObject::Open(const char* thePath, const char* infoPtr, UINT16 infoLen) { - if (server) + if (sender) { // We're receiving this file if (NormFile::IsLocked(thePath)) { - DMSG(0, "NormFileObject::Open() Error trying to open locked file for recv!\n"); + PLOG(PL_FATAL, "NormFileObject::Open() Error trying to open locked file for recv!\n"); return false; } else @@ -1928,7 +2110,7 @@ bool NormFileObject::Open(const char* thePath, } else { - DMSG(0, "NormFileObject::Open() recv file.Open() error!\n"); + PLOG(PL_FATAL, "NormFileObject::Open() recv file.Open() error!\n"); return false; } } @@ -1938,7 +2120,7 @@ bool NormFileObject::Open(const char* thePath, // Verify that it _is_ a file if (NormFile::NORMAL != NormFile::GetType(thePath)) { - DMSG(0, "NormFileObject::Open() send file \"%s\" is not a file " + PLOG(PL_FATAL, "NormFileObject::Open() send file \"%s\" is not a file " "(a directory perhaps?)\n", thePath); return false; } @@ -1952,25 +2134,27 @@ bool NormFileObject::Open(const char* thePath, if (!NormObject::Open(NormObjectSize(size), infoPtr, infoLen, - session.ServerSegmentSize(), - session.ServerBlockSize(), - session.ServerNumParity())) + session.SenderSegmentSize(), + session.GetSenderFecId(), + session.GetSenderFecFieldSize(), + session.SenderBlockSize(), + session.SenderNumParity())) { - DMSG(0, "NormFileObject::Open() send object open error\n"); + PLOG(PL_FATAL, "NormFileObject::Open() send object open error\n"); Close(); return false; } } else { - DMSG(0, "NormFileObject::Open() send file.GetSize() error!\n"); + PLOG(PL_FATAL, "NormFileObject::Open() send file.GetSize() error!\n"); file.Close(); return false; } } else { - DMSG(0, "NormFileObject::Open() send file.Open() error!\n"); + PLOG(PL_FATAL, "NormFileObject::Open() send file.Open() error!\n"); return false; } } @@ -1999,7 +2183,7 @@ bool NormFileObject::Accept(const char* thePath) void NormFileObject::Close() { NormObject::Close(); - if (NULL != server) // we've been receiving this file + if (NULL != sender) // we've been receiving this file file.Unlock(); file.Close(); } // end NormFileObject::Close() @@ -2048,6 +2232,9 @@ UINT16 NormFileObject::ReadSegment(NormBlockId blockId, NormSegmentId segmentId, char* buffer) { + + + // Determine segment length from blockId::segmentId size_t len; if (blockId == final_block_id) @@ -2081,11 +2268,13 @@ UINT16 NormFileObject::ReadSegment(NormBlockId blockId, { if (!file.Seek(offset)) { - DMSG(0, "NormFileObject::ReadSegment() error seeking to file offset\n"); + PLOG(PL_FATAL, "NormFileObject::ReadSegment() error seeking to file offset\n"); return 0; } } + size_t nbytes = file.Read(buffer, len); + if (len == nbytes) return (UINT16)len; else @@ -2095,9 +2284,9 @@ UINT16 NormFileObject::ReadSegment(NormBlockId blockId, char* NormFileObject::RetrieveSegment(NormBlockId blockId, NormSegmentId segmentId) { - if (server) + if (sender) { - char* segment = server->GetRetrievalSegment(); + char* segment = sender->GetRetrievalSegment(); UINT16 len = ReadSegment(blockId, segmentId, segment); if (len) { @@ -2108,13 +2297,13 @@ char* NormFileObject::RetrieveSegment(NormBlockId blockId, } else { - DMSG(0, "NormFileObject::RetrieveSegment() error reading segment\n"); + PLOG(PL_FATAL, "NormFileObject::RetrieveSegment() error reading segment\n"); return NULL; } } else { - DMSG(0, "NormFileObject::RetrieveSegment() error: NULL server!\n"); + PLOG(PL_FATAL, "NormFileObject::RetrieveSegment() error: NULL sender!\n"); return NULL; } } // end NormFileObject::RetrieveSegment() @@ -2124,9 +2313,9 @@ char* NormFileObject::RetrieveSegment(NormBlockId blockId, // NormDataObject Implementation // NormDataObject::NormDataObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId) - : NormObject(DATA, theSession, theServer, objectId), + : NormObject(DATA, theSession, theSender, objectId), large_block_length(0), small_block_length(0), data_ptr(NULL), data_max(0), data_released(false) { @@ -2138,7 +2327,7 @@ NormDataObject::~NormDataObject() Close(); if (data_released) { - if (data_ptr) + if (NULL != data_ptr) { delete[] data_ptr; data_ptr = NULL; @@ -2160,26 +2349,28 @@ bool NormDataObject::Open(char* dataPtr, data_ptr = NULL; data_released = false; } - if (server) - { - // We're receiving this data object - ASSERT(NULL == infoPtr); - } - else + if (NULL == sender) { // We're sending this data object if (!NormObject::Open(dataLen, infoPtr, infoLen, - session.ServerSegmentSize(), - session.ServerBlockSize(), - session.ServerNumParity())) + session.SenderSegmentSize(), + session.GetSenderFecId(), + session.GetSenderFecFieldSize(), + session.SenderBlockSize(), + session.SenderNumParity())) { - DMSG(0, "NormDataObject::Open() send object open error\n"); + PLOG(PL_FATAL, "NormDataObject::Open() send object open error\n"); Close(); return false; } } + else + { + // We're receiving this data object + //ASSERT(NULL == infoPtr); + } data_ptr = dataPtr; data_max = dataLen; data_released = dataRelease; @@ -2212,7 +2403,7 @@ bool NormDataObject::WriteSegment(NormBlockId blockId, { if (NULL == data_ptr) { - DMSG(0, "NormDataObject::WriteSegment() error: NULL data_ptr\n"); + PLOG(PL_FATAL, "NormDataObject::WriteSegment() error: NULL data_ptr\n"); return false; } UINT16 len; @@ -2241,7 +2432,7 @@ bool NormDataObject::WriteSegment(NormBlockId blockId, segmentOffset = segmentOffset + small_block_length*smallBlockIndex + segmentSize*segmentId; } - ASSERT(0 == segmentOffset.MSB()); + ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects if (data_max <= segmentOffset.LSB()) return true; else if (data_max <= (segmentOffset.LSB() + len)) @@ -2257,7 +2448,7 @@ UINT16 NormDataObject::ReadSegment(NormBlockId blockId, { if (NULL == data_ptr) { - DMSG(0, "NormDataObject::ReadSegment() error: NULL data_ptr\n"); + PLOG(PL_FATAL, "NormDataObject::ReadSegment() error: NULL data_ptr\n"); return 0; } // Determine segment length from blockId::segmentId @@ -2288,7 +2479,7 @@ UINT16 NormDataObject::ReadSegment(NormBlockId blockId, segmentOffset = segmentOffset + small_block_length*smallBlockIndex + segmentSize*segmentId; } - ASSERT(0 == segmentOffset.MSB()); + ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects if (data_max <= segmentOffset.LSB()) return 0; else if (data_max <= (segmentOffset.LSB() + len)) @@ -2303,7 +2494,7 @@ char* NormDataObject::RetrieveSegment(NormBlockId blockId, { if (NULL == data_ptr) { - DMSG(0, "NormDataObject::RetrieveSegment() error: NULL data_ptr\n"); + PLOG(PL_FATAL, "NormDataObject::RetrieveSegment() error: NULL data_ptr\n"); return NULL; } // Determine segment length from blockId::segmentId @@ -2333,19 +2524,19 @@ char* NormDataObject::RetrieveSegment(NormBlockId blockId, segmentOffset = segmentOffset + small_block_length*smallBlockIndex + segmentSize*segmentId; } - ASSERT(0 == segmentOffset.MSB()); + ASSERT(0 == segmentOffset.MSB()); // we don't yet support super-sized "data" objects if ((len < segment_size) || (data_max < (segmentOffset.LSB() + len))) { - if (server) + if (sender) { - char* segment = server->GetRetrievalSegment(); + char* segment = sender->GetRetrievalSegment(); len = ReadSegment(blockId, segmentId, segment); memset(segment+len, 0, segment_size-len); return segment; } else { - DMSG(0, "NormDataObject::RetrieveSegment() error: NULL server!\n"); + PLOG(PL_FATAL, "NormDataObject::RetrieveSegment() error: NULL sender!\n"); return NULL; } } @@ -2361,9 +2552,9 @@ char* NormDataObject::RetrieveSegment(NormBlockId blockId, // NormStreamObject::NormStreamObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId) - : NormObject(STREAM, theSession, theServer, objectId), + : NormObject(STREAM, theSession, theSender, objectId), stream_sync(false), write_vacancy(false), read_init(true), read_ready(false), flush_pending(false), msg_start(true), @@ -2396,12 +2587,12 @@ bool NormStreamObject::Open(UINT32 bufferSize, { if (!bufferSize) { - DMSG(0, "NormStreamObject::Open() zero bufferSize error\n"); + PLOG(PL_FATAL, "NormStreamObject::Open() zero bufferSize error\n"); return false; } UINT16 segmentSize, numData; - if (server) + if (sender) { // receive streams have already be pre-opened segmentSize = segment_size; @@ -2409,8 +2600,8 @@ bool NormStreamObject::Open(UINT32 bufferSize, } else { - segmentSize = session.ServerSegmentSize(); - numData = session.ServerBlockSize(); + segmentSize = session.SenderSegmentSize(); + numData = session.SenderBlockSize(); } UINT32 blockSize = segmentSize * numData; @@ -2422,21 +2613,21 @@ bool NormStreamObject::Open(UINT32 bufferSize, if (!block_pool.Init(numBlocks, numData)) { - DMSG(0, "NormStreamObject::Open() block_pool init error\n"); + PLOG(PL_FATAL, "NormStreamObject::Open() block_pool init error\n"); Close(); return false; } - if (!segment_pool.Init(numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength()+1)) + if (!segment_pool.Init(numSegments, segmentSize+NormDataMsg::GetStreamPayloadHeaderLength())) { - DMSG(0, "NormStreamObject::Open() segment_pool init error\n"); + PLOG(PL_FATAL, "NormStreamObject::Open() segment_pool init error\n"); Close(); return false; } if (!stream_buffer.Init(numBlocks)) { - DMSG(0, "NormStreamObject::Open() stream_buffer init error\n"); + PLOG(PL_FATAL, "NormStreamObject::Open() stream_buffer init error\n"); Close(); return false; } @@ -2449,16 +2640,18 @@ bool NormStreamObject::Open(UINT32 bufferSize, tx_offset = write_offset = read_offset = 0; write_vacancy = true; - if (!server) + if (!sender) { if (!NormObject::Open(NormObjectSize((UINT32)bufferSize), infoPtr, infoLen, - session.ServerSegmentSize(), - session.ServerBlockSize(), - session.ServerNumParity())) + session.SenderSegmentSize(), + session.GetSenderFecId(), + session.GetSenderFecFieldSize(), + session.SenderBlockSize(), + session.SenderNumParity())) { - DMSG(0, "NormStreamObject::Open() object open error\n"); + PLOG(PL_FATAL, "NormStreamObject::Open() object open error\n"); Close(); return false; } @@ -2487,7 +2680,7 @@ bool NormStreamObject::Accept(UINT32 bufferSize, bool doubleBuffer) void NormStreamObject::Close(bool graceful) { - if (graceful && (NULL == server)) + if (graceful && (NULL == sender)) { stream_closing = true; Terminate(); @@ -2501,10 +2694,10 @@ void NormStreamObject::Close(bool graceful) } } // end NormStreamObject::Close() -bool NormStreamObject::LockBlocks(NormBlockId firstId, NormBlockId lastId) +bool NormStreamObject::LockBlocks(NormBlockId firstId, NormBlockId lastId, const ProtoTime& currentTime) { NormBlockId nextId = firstId; - // First check that we _can_ lock them all + // First check that we _can_ lock/reset them all while (nextId <= lastId) { NormBlock* block = stream_buffer.Find(nextId); @@ -2515,16 +2708,12 @@ bool NormStreamObject::LockBlocks(NormBlockId firstId, NormBlockId lastId) while (nextId <= lastId) { NormBlock* block = stream_buffer.Find(nextId); - if (block) + if (NULL != block) { UINT16 numData = GetBlockSize(nextId); - block->SetPending(0, numData); + block->SetPending(0, numData); // TBD - should this reset auto-parity, too? + block->SetLastNackTime(currentTime); } - else - { - ASSERT(0); - return false; - } nextId++; } return true; @@ -2575,7 +2764,8 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) { if (pending_mask.CanSet(blockId)) { - pending_mask.SetBits(stream_next_id, blockId - stream_next_id + 1); + UINT32 numBits = pending_mask.Delta(blockId, stream_next_id) + 1; + pending_mask.SetBits(stream_next_id, numBits); stream_next_id = blockId + 1; // Handle potential stream_sync_id wrap NormBlockId delta = stream_next_id - stream_sync_id; @@ -2586,7 +2776,8 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) else { // Stream broken - DMSG(4, "NormObject::StreamUpdateStatus() broken 1 ...\n"); + NormBlockId firstPending; + GetFirstPending(firstPending); return false; } } @@ -2596,7 +2787,6 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) if (delta > NormBlockId(pending_mask.GetSize())) { // Stream broken - DMSG(4, "NormObject::StreamUpdateStatus() broken 2 ...\n"); return false; } else @@ -2606,7 +2796,7 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) // Handle potential stream_sync_id wrap NormBlockId delta = stream_next_id - stream_sync_id; if (delta > NormBlockId(2*pending_mask.GetSize())) - stream_sync_id = blockId; + GetFirstPending(stream_sync_id); return true; } } @@ -2621,7 +2811,7 @@ bool NormStreamObject::StreamUpdateStatus(NormBlockId blockId) while (NULL != (block = block_buffer.Find(block_buffer.RangeLo()))) { block_buffer.Remove(block); - server->PutFreeBlock(block); + sender->PutFreeBlock(block); } pending_mask.Clear(); pending_mask.SetBits(blockId, pending_mask.GetSize()); @@ -2649,12 +2839,12 @@ char* NormStreamObject::RetrieveSegment(NormBlockId blockId, NormBlock* block = stream_buffer.Find(blockId); if (!block) { - DMSG(0, "NormStreamObject::RetrieveSegment() segment block unavailable\n"); + PLOG(PL_FATAL, "NormStreamObject::RetrieveSegment() segment block unavailable\n"); return NULL; } char* segment = block->GetSegment(segmentId); if (NULL == segment) - DMSG(0, "NormStreamObject::RetrieveSegment() segment unavailable\n"); + PLOG(PL_FATAL, "NormStreamObject::RetrieveSegment() segment unavailable\n"); return segment; } // end NormStreamObject::RetrieveSegment() @@ -2668,16 +2858,15 @@ UINT16 NormStreamObject::ReadSegment(NormBlockId blockId, { //DMSG(0, "NormStreamObject::ReadSegment() stream starved (1)\n"); if (!stream_buffer.IsEmpty() && (blockId < stream_buffer.RangeLo())) - DMSG(0, "NormStreamObject::ReadSegment() error: attempted to read old block> %lu\n", (UINT32)blockId); - - return false; + PLOG(PL_ERROR, "NormStreamObject::ReadSegment() error: attempted to read old block> %lu\n", (UINT32)blockId); + return 0; } // (TBD) should we check to see if "blockId > write_index.block" ? if ((blockId == write_index.block) && (segmentId >= write_index.segment)) { //DMSG(0, "NormStreamObject::ReadSegment(blk>%lu seg>%hu) stream starved (2) (write_index>%lu:%hu)\n", // (UINT32)blockId, (UINT16)segmentId, (UINT32)write_index.block, (UINT16)write_index.segment); - return false; + return 0; } block->UnsetPending(segmentId); @@ -2711,11 +2900,36 @@ UINT16 NormStreamObject::ReadSegment(NormBlockId blockId, if (blockDelta <= (block_pool.GetTotal() >> 1)) { NormBlock* b = stream_buffer.Find(stream_buffer.RangeLo()); - if (b && !b->IsPending()) + if (NULL != b) + { + if (!b->IsPending()) + { + // make sure no recent nacking + double delay = session.GetFlowControlDelay() - b->GetNackAge(); + if (delay < 1.0e-06) + + { + if (session.FlowControlIsActive() && (session.GetFlowControlObject() == GetId())) + session.DeactivateFlowControl(); + write_vacancy = true; + } + else + { + if (!session.FlowControlIsActive()) + { + session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_VACANCY); + PLOG(PL_DEBUG, "NormStreamObject::ReadSegment() asserting flow control for stream (postedEmpty:%d)\n", + session.GetPostedTxQueueEmpty()); + } + } + } + } + else { write_vacancy = true; - session.Notify(NormController::TX_QUEUE_VACANCY, NULL, this); } + if (write_vacancy) + session.Notify(NormController::TX_QUEUE_VACANCY, NULL, this); } } @@ -2751,7 +2965,7 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, ((blockId == read_index.block) && (segmentId < read_index.segment))) { - DMSG(4, "NormStreamObject::WriteSegment() block/segment < read_index!?\n"); + PLOG(PL_DEBUG, "NormStreamObject::WriteSegment() block/segment < read_index!?\n"); return false; } @@ -2759,7 +2973,7 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, UINT32 diff = segmentOffset - read_offset; if ((diff > 0x80000000) || ((0x80000000 == diff) && (segmentOffset > read_offset))) { - DMSG(0, "NormStreamObject::WriteSegment() diff:%lu segmentOffset:%lu < read_offset:%lu \n", + PLOG(PL_ERROR, "NormStreamObject::WriteSegment() diff:%lu segmentOffset:%lu < read_offset:%lu \n", diff, segmentOffset, read_offset); //ASSERT(0); return false; @@ -2773,10 +2987,10 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, while (block_pool.IsEmpty() || !stream_buffer.CanInsert(blockId)) { block = stream_buffer.Find(stream_buffer.RangeLo()); - ASSERT(block); + ASSERT(NULL != block); if (blockId < block->GetId()) { - DMSG(4, "NormStreamObject::WriteSegment() blockId too old!?\n"); + PLOG(PL_DEBUG, "NormStreamObject::WriteSegment() blockId too old!?\n"); return false; } while (block->IsPending()) @@ -2793,7 +3007,7 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, if (notify_on_update) { notify_on_update = false; - session.Notify(NormController::RX_OBJECT_UPDATED, server, this); + session.Notify(NormController::RX_OBJECT_UPDATED, sender, this); } block = stream_buffer.Find(stream_buffer.RangeLo()); if (tempBlock == block) @@ -2803,7 +3017,6 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, (tempIndex.segment == read_index.segment)) { // App didn't want any data here, purge segment - //ASSERT(0); dataLost = true; block->UnsetPending(read_index.segment++); if (read_index.segment >= ndata) @@ -2841,10 +3054,10 @@ bool NormStreamObject::WriteSegment(NormBlockId blockId, } // end while (block_pool.IsEmpty() || !stream_buffer.CanInsert(blockId)) if (broken) { - DMSG(4, "NormStreamObject::WriteSegment() node>%lu obj>%hu blk>%lu seg>%hu broken stream ...\n", + PLOG(PL_WARN, "NormStreamObject::WriteSegment() node>%lu obj>%hu blk>%lu seg>%hu broken stream ...\n", LocalNodeId(), (UINT16)transport_id, (UINT32)blockId, (UINT16)segmentId); if (dataLost) - DMSG(0, "NormStreamObject::WriteSegment() broken stream data not read by app!\n"); + PLOG(PL_ERROR, "NormStreamObject::WriteSegment() broken stream data not read by app!\n"); } block = block_pool.Get(); block->SetId(blockId); @@ -2908,7 +3121,7 @@ void NormStreamObject::Prune(NormBlockId blockId, bool updateStatus) resync = true; pending_mask.Unset(block->GetId()); block_buffer.Remove(block); - server->PutFreeBlock(block); + sender->PutFreeBlock(block); } else { @@ -2921,13 +3134,13 @@ void NormStreamObject::Prune(NormBlockId blockId, bool updateStatus) if (firstId < blockId) { resync = true; - UINT32 count = blockId - firstId; + UINT32 count = pending_mask.Delta(blockId, firstId); pending_mask.UnsetBits(firstId, count); } } while (!StreamUpdateStatus(blockId)) { - // Server is too far ahead of me ... + // Sender is too far ahead of me ... resync = true; NormBlockId firstId; if (GetFirstPending(firstId)) @@ -2936,7 +3149,7 @@ void NormStreamObject::Prune(NormBlockId blockId, bool updateStatus) if (block) { block_buffer.Remove(block); - server->PutFreeBlock(block); + sender->PutFreeBlock(block); } pending_mask.Unset(firstId); } @@ -2946,10 +3159,31 @@ void NormStreamObject::Prune(NormBlockId blockId, bool updateStatus) break; } } - if (resync) server->IncrementResyncCount(); + if (resync) sender->IncrementResyncCount(); } } // end NormStreamObject::Prune() +// returns "true" if given blockId::segmentId has _not_ been read yet +// i.e., "true" if still pending reading by application +bool NormStreamObject::PassiveReadCheck(NormBlockId blockId, NormSegmentId segmentId) +{ + bool result; + if (read_index.block < blockId) + result = true; + else if (read_index.block > blockId) + result = false; + else // read_index.block == blockId + result = (read_index.segment <= segmentId); + /*if (result) + { + TRACE("NormStreamObject::PassiveReadCheck() %s\n", result ? "data not yet read by app" : "OK"); + TRACE(" (read_index>%lu:%hu check>%lu:%hu usage:%u)\n", + (UINT32)read_index.block, read_index.segment, + (UINT32)blockId, segmentId, GetCurrentBufferUsage()); + } + */ + return result; +} // end NormStreamObject::PassiveReadCheck() bool NormStreamObject::Read(char* buffer, unsigned int* buflen, bool seekMsgStart) { @@ -2966,7 +3200,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek if (stream_closing || read_init) { if (stream_closing) - DMSG(4, "NormStreamObject::Read() attempted to read from closed stream\n"); + PLOG(PL_DEBUG, "NormStreamObject::Read() attempted to read from closed stream\n"); *buflen = 0; return seekMsgStart ? false : true; } @@ -2976,7 +3210,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek do { NormBlock* block = stream_buffer.Find(read_index.block); - if (!block) + if (NULL == block) { //DMSG(0, "NormStreamObject::Read() stream buffer empty (1) (sbEmpty:%d)\n", stream_buffer.IsEmpty()); read_ready = false; @@ -3022,12 +3256,12 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek } char* segment = block->GetSegment(read_index.segment); - ASSERT(!segment || block->IsPending(read_index.segment)); + ASSERT((NULL == segment) || block->IsPending(read_index.segment)); - if (!segment) + if (NULL == segment) { - //DMSG(0, "NormStreamObject::Read(%lu:%hu) stream buffer empty (2)\n", - // (UINT32)read_index.block, read_index.segment); + //DMSG(0, "NormStreamObject::Read(%lu:%hu) stream buffer empty (read_offset>%lu) (2)\n", + // (UINT32)read_index.block, read_index.segment, read_offset); read_ready = false; *buflen = bytesRead; if (bytesRead > 0) @@ -3071,7 +3305,8 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek return seekMsgStart ? false : true; } } - } // end if (!segment) + } // end if (NULL = segment) + ASSERT(NULL != segment); read_ready = true; UINT16 length = NormDataMsg::ReadStreamPayloadLength(segment); if (0 == length) @@ -3084,7 +3319,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek break; default: // Invalid stream control code, skip this invalid segment - DMSG(0, "NormStreamObject::Read() invalid stream control message\n"); + PLOG(PL_ERROR, "NormStreamObject::Read() invalid stream control message\n"); if (++read_index.segment >= ndata) { stream_buffer.Remove(block); @@ -3111,7 +3346,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek else { // Skip this invalid segment - DMSG(0, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu invalid stream segment! (read_offset>%lu length>%hu)\n", + PLOG(PL_ERROR, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu invalid stream segment!\n", LocalNodeId(), (UINT16)transport_id, (UINT32)read_index.block, read_index.segment, read_offset, length); if (++read_index.segment >= ndata) { @@ -3127,7 +3362,6 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek return false; } } - UINT32 segmentOffset = NormDataMsg::ReadStreamPayloadOffset(segment); // if (read_offset < segmentOffset) UINT32 diff = read_offset - segmentOffset; @@ -3142,8 +3376,8 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek } else { - DMSG(4, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu broken stream! (read_offset>%lu segmentOffset>%lu)\n", - LocalNodeId(), (UINT16)transport_id, (UINT32)read_index.block, read_index.segment, read_offset, segmentOffset); + PLOG(PL_WARN, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu broken stream!\n", + (UINT32)LocalNodeId(), (UINT16)transport_id, (UINT32)read_index.block, (UINT16)read_index.segment); read_offset = segmentOffset; *buflen = 0; read_ready = false; //DetermineReadReadiness(); @@ -3164,7 +3398,7 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek } else { - DMSG(0, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu mangled stream! index:%hu length:%hu " + PLOG(PL_ERROR, "NormStreamObject::Read() node>%lu obj>%hu blk>%lu seg>%hu mangled stream! index:%hu length:%hu " "read_offset:%lu segmentOffset:%lu\n", LocalNodeId(), (UINT16)transport_id, (UINT32)read_index.block, read_index.segment, index, length, read_offset, segmentOffset); // Reset our read_offset ... @@ -3180,14 +3414,13 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek UINT16 msgStart = NormDataMsg::ReadStreamPayloadMsgStart(segment); if (0 == msgStart) { - // make sure msg start searches don't miss stream end ... - //if (NormDataMsg::StreamPayloadFlagIsSet(segment, NormDataMsg::FLAG_STREAM_END)) + // make sure msg start searches don't miss the stream end ... if (0 == NormDataMsg::ReadStreamPayloadLength(segment)) { - DMSG(4, "NormStreamObject::Read() stream ended by sender 1\n"); - session.Notify(NormController::RX_OBJECT_COMPLETED, server, this); + PLOG(PL_DEBUG, "NormStreamObject::Read() stream ended by sender 1\n"); + session.Notify(NormController::RX_OBJECT_COMPLETED, sender, this); stream_closing = true; - server->DeleteObject(this); + sender->DeleteObject(this); } // Don't bother managing individual segments since // stream buffers are exact multiples of block size! @@ -3254,10 +3487,10 @@ bool NormStreamObject::ReadPrivate(char* buffer, unsigned int* buflen, bool seek if (streamEnded) { - DMSG(4, "NormStreamObject::Read() stream ended by sender 2\n"); - session.Notify(NormController::RX_OBJECT_COMPLETED, server, this); + PLOG(PL_DEBUG, "NormStreamObject::Read() stream ended by sender 2\n"); + session.Notify(NormController::RX_OBJECT_COMPLETED, sender, this); stream_closing = true; - server->DeleteObject(this); + sender->DeleteObject(this); } } } while ((bytesToRead > 0) || seekMsgStart); // end while (len > 0) @@ -3277,7 +3510,7 @@ void NormStreamObject::Terminate() if (NULL == (block = block_pool.Get())) { block = stream_buffer.Find(stream_buffer.RangeLo()); - ASSERT(block); + ASSERT(NULL != block); if (block->IsPending()) { NormBlockId blockId = block->GetId(); @@ -3287,7 +3520,7 @@ void NormStreamObject::Terminate() if (b) { block_buffer.Remove(b); - session.ServerPutFreeBlock(b); + session.SenderPutFreeBlock(b); } if (!pending_mask.IsSet()) { @@ -3319,7 +3552,7 @@ void NormStreamObject::Terminate() if (c) { block_buffer.Remove(c); - session.ServerPutFreeBlock(c); + session.SenderPutFreeBlock(c); } if (!pending_mask.IsSet()) { @@ -3331,7 +3564,7 @@ void NormStreamObject::Terminate() b->EmptyToPool(segment_pool); block_pool.Put(b); segment = segment_pool.Get(); - ASSERT(segment); + ASSERT(NULL != segment); } block->AttachSegment(write_index.segment, segment); NormDataMsg::WriteStreamPayloadMsgStart(segment, 0); @@ -3339,6 +3572,7 @@ void NormStreamObject::Terminate() } else { + // Make sure the segment is not still referenced ASSERT(0 == NormDataMsg::ReadStreamPayloadMsgStart(segment)); ASSERT(0 == NormDataMsg::ReadStreamPayloadLength(segment)); } @@ -3351,7 +3585,7 @@ void NormStreamObject::Terminate() write_index.segment = 0; } flush_pending = true; - session.TouchServer(); + session.TouchSender(); } // end NormStreamObject::Terminate() UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) @@ -3363,7 +3597,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) { if (0 != len) { - DMSG(0, "NormStreamObject::Write() error: stream is closing (len:%lu eom:%d)\n", len, eom); + PLOG(PL_ERROR, "NormStreamObject::Write() error: stream is closing (len:%lu eom:%d)\n", len, eom); len = 0; } break; @@ -3379,17 +3613,19 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) if (deltaBlock > (block_pool.GetTotal() >> 1)) { write_vacancy = false; - DMSG(4, "NormStreamObject::Write() stream buffer full (1)\n"); + PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (1)\n"); if (!push_mode) break; } NormBlock* block = stream_buffer.Find(write_index.block); - if (!block) - { - if (!(block = block_pool.Get())) + if (NULL == block) + { + block = block_pool.Get(); + if (NULL == block) { block = stream_buffer.Find(stream_buffer.RangeLo()); - ASSERT(block); - if (block->IsPending()) + ASSERT(NULL != block); + double delay = session.GetFlowControlDelay() - block->GetNackAge(); + if (block->IsPending() || (delay >= 1.0e-06)) { write_vacancy = false; if (push_mode) @@ -3401,7 +3637,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) if (b) { block_buffer.Remove(b); - session.ServerPutFreeBlock(b); + session.SenderPutFreeBlock(b); } if (!pending_mask.IsSet()) { @@ -3411,7 +3647,18 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) } else { - DMSG(4, "NormStreamObject::Write() stream buffer full (2) len:%d eom:%d\n", len, eom); + // The timer activated here makes sure a deferred TX_QUEUE_VACANCY is posted + // when flow control has been asserted. + if (!block->IsPending()) + { + PLOG(PL_DEBUG, "NormStreamObject::Write() asserting flow control for stream (postedEmpty:%d)\n", + session.GetPostedTxQueueEmpty()); + if (session.GetPostedTxQueueEmpty()) + session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_EMPTY); + else + session.ActivateFlowControl(delay, GetId(), NormController::TX_QUEUE_VACANCY); + } + PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (2) len:%d eom:%d\n", len, eom); break; } } @@ -3424,9 +3671,9 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) ASSERT(success); } // end if (!block) char* segment = block->GetSegment(write_index.segment); - if (!segment) + if (NULL == segment) { - if (!(segment = segment_pool.Get())) + if (NULL == (segment = segment_pool.Get())) { NormBlock* b = stream_buffer.Find(stream_buffer.RangeLo()); ASSERT(b != block); @@ -3442,7 +3689,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) if (c) { block_buffer.Remove(c); - session.ServerPutFreeBlock(c); + session.SenderPutFreeBlock(c); } if (!pending_mask.IsSet()) { @@ -3452,7 +3699,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) } else { - DMSG(4, "NormStreamObject::Write() stream buffer full (3)\n"); + PLOG(PL_DEBUG, "NormStreamObject::Write() stream buffer full (3)\n"); break; } } @@ -3460,7 +3707,7 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) b->EmptyToPool(segment_pool); block_pool.Put(b); segment = segment_pool.Get(); - ASSERT(segment); + ASSERT(NULL != segment); } NormDataMsg::WriteStreamPayloadMsgStart(segment, 0); NormDataMsg::WriteStreamPayloadLength(segment, 0); @@ -3500,6 +3747,9 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) block->SetPending(write_index.segment); if (++write_index.segment >= ndata) { + ProtoTime currentTime; + currentTime.GetCurrentTime(); + block->SetLastNackTime(currentTime); write_index.block++; write_index.segment = 0; } @@ -3516,11 +3766,11 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) else if (!stream_closing) flush_pending = false; if ((0 != nBytes) || (FLUSH_NONE != flush_mode)) - session.TouchServer(); + session.TouchSender(); } else { - session.TouchServer(); + session.TouchSender(); } return nBytes; } // end NormStreamObject::Write() @@ -3532,9 +3782,9 @@ UINT32 NormStreamObject::Write(const char* buffer, UINT32 len, bool eom) // NormSimObject::NormSimObject(class NormSession& theSession, - class NormServerNode* theServer, + class NormSenderNode* theSender, const NormObjectId& objectId) - : NormObject(FILE, theSession, theServer, objectId) + : NormObject(FILE, theSession, theSender, objectId) { } @@ -3548,12 +3798,15 @@ bool NormSimObject::Open(UINT32 objectSize, const char* infoPtr , UINT16 infoLen) { - return (server ? + return (sender ? true : - NormObject::Open(objectSize, infoPtr, infoLen, - session.ServerSegmentSize(), - session.ServerBlockSize(), - session.ServerNumParity())); + NormObject::Open(NormObjectSize(objectSize), + infoPtr, infoLen, + session.SenderSegmentSize(), + session.GetSenderFecId(), + session.GetSenderFecFieldSize(), + session.SenderBlockSize(), + session.SenderNumParity())); } // end NormSimObject::Open() UINT16 NormSimObject::ReadSegment(NormBlockId blockId, @@ -3579,7 +3832,7 @@ UINT16 NormSimObject::ReadSegment(NormBlockId blockId, char* NormSimObject::RetrieveSegment(NormBlockId blockId, NormSegmentId segmentId) { - return server ? server->GetRetrievalSegment() : NULL; + return sender ? sender->GetRetrievalSegment() : NULL; } // end NormSimObject::RetrieveSegment() #endif // SIMULATE @@ -3603,12 +3856,12 @@ NormObjectTable::~NormObjectTable() bool NormObjectTable::Init(UINT16 rangeMax, UINT16 tableSize) { if (table) Destroy(); - // Make sure tableSize is greater than 0 and 2^n + // Make sure (rangeMax > 0) and tableSize is greater than 0 and 2^n if (!rangeMax || !tableSize) return false; if (0 != (tableSize & 0x07)) tableSize = (tableSize >> 3) + 1; if (!(table = new NormObject*[tableSize])) { - DMSG(0, "NormObjectTable::Init() table allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormObjectTable::Init() table allocation error: %s\n", GetErrorString()); return false; } memset(table, 0, tableSize*sizeof(char*)); @@ -3626,17 +3879,17 @@ void NormObjectTable::SetRangeMax(UINT16 rangeMax) while (range > rangeMax) { NormObject* obj = Find(range_lo); - ASSERT(obj); - NormServerNode* sender = obj->GetServer(); + ASSERT(NULL != obj); + NormSenderNode* sender = obj->GetSender(); NormSession& session = obj->GetSession(); if (NULL == sender) { - session.Notify(NormController::TX_OBJECT_PURGED, (NormServerNode*)NULL, obj); + session.Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, obj); session.DeleteTxObject(obj); } else { - if (!session.ClientIsSilent()) obj = Find(range_hi); + if (!session.ReceiverIsSilent()) obj = Find(range_hi); session.Notify(NormController::RX_OBJECT_ABORTED, sender, obj); sender->DeleteObject(obj); } @@ -3652,6 +3905,8 @@ void NormObjectTable::Destroy() NormObject* obj; while((obj = Find(range_lo))) { + // TBD - should we issue PURGED/ABORTED notifications here??? + // (We haven't since this is destroyed only when session is terminated) Remove(obj); obj->Release(); } @@ -3663,7 +3918,7 @@ void NormObjectTable::Destroy() NormObject* NormObjectTable::Find(const NormObjectId& objectId) const { - if (range) + if (0 != range) { if ((objectId < range_lo) || (objectId > range_hi)) return (NormObject*)NULL; NormObject* theObject = table[((UINT16)objectId) & hash_mask]; @@ -3710,7 +3965,7 @@ bool NormObjectTable::CanInsert(NormObjectId objectId) const bool NormObjectTable::Insert(NormObject* theObject) { const NormObjectId& objectId = theObject->GetId(); - if (!range) + if (0 == range) { range_lo = range_hi = objectId; range = 1; @@ -3741,7 +3996,7 @@ bool NormObjectTable::Insert(NormObject* theObject) prev->next = theObject; else table[index] = theObject; - ASSERT((entry ? (objectId != entry->GetId()) : true)); + ASSERT(((NULL != entry) ? (objectId != entry->GetId()) : true)); theObject->next = entry; count++; size = size + theObject->GetSize(); @@ -3751,7 +4006,7 @@ bool NormObjectTable::Insert(NormObject* theObject) bool NormObjectTable::Remove(NormObject* theObject) { - ASSERT(theObject); + ASSERT(NULL != theObject); const NormObjectId& objectId = theObject->GetId(); if (range) { diff --git a/common/normPostProcess.cpp b/src/common/normPostProcess.cpp similarity index 94% rename from common/normPostProcess.cpp rename to src/common/normPostProcess.cpp index 1d567ac..3c07724 100644 --- a/common/normPostProcess.cpp +++ b/src/common/normPostProcess.cpp @@ -87,7 +87,7 @@ bool NormPostProcessor::SetCommand(const char* cmd) // and one for terminating NULL pointer. if (!(process_argv = new char*[argCount+2])) { - DMSG(0, "NormPostProcessor::SetCommand new(process_argv) error: %s\n", + PLOG(PL_FATAL, "NormPostProcessor::SetCommand new(process_argv) error: %s\n", GetErrorString()); return false; } @@ -107,7 +107,7 @@ bool NormPostProcessor::SetCommand(const char* cmd) if (!(arg = new char[argLength+1])) { SetCommand(NULL); - DMSG(0, "NormPostProcessor::SetCommand new(process_arg) error: %s\n", + PLOG(PL_FATAL, "NormPostProcessor::SetCommand new(process_arg) error: %s\n", GetErrorString()); return false; } diff --git a/common/normPrecode.cpp b/src/common/normPrecode.cpp similarity index 90% rename from common/normPrecode.cpp rename to src/common/normPrecode.cpp index 3fbba9f..5864a09 100644 --- a/common/normPrecode.cpp +++ b/src/common/normPrecode.cpp @@ -1,7 +1,7 @@ #include "protoApp.h" #include "normFile.h" -#include "normEncoder.h" +#include "normEncoderRS8.h" #include // for BYTE_ORDER macro #include // for atoi() @@ -140,7 +140,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) size_t len = strlen(cmd); if ((CMD_ARG == type) && !val) { - DMSG(0, "NormApp::OnCommand(%s) missing argument\n", cmd); + PLOG(PL_FATAL, "NormApp::OnCommand(%s) missing argument\n", cmd); return false; } @@ -155,7 +155,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) int debugLevel = atoi(val); if ((debugLevel < 0) || (debugLevel > 12)) { - DMSG(0, "NormApp::OnCommand(segment) invalid debug level!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(segment) invalid debug level!\n"); return false; } SetDebugLevel(debugLevel); @@ -172,7 +172,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) { if (!in_file.Open(val, O_RDONLY)) { - DMSG(0, "npc: error opening input file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error opening input file: %s\n", GetErrorString()); Usage(); return false; } @@ -182,7 +182,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) { if (!out_file.Open(val, O_WRONLY | O_CREAT | O_TRUNC)) { - DMSG(0, "npc: error opening input file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error opening input file: %s\n", GetErrorString()); Usage(); return false; } @@ -192,7 +192,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) int segmentSize = atoi(val); if ((segmentSize < SEGMENT_MIN) || (segmentSize > SEGMENT_MAX)) { - DMSG(0, "npc: error: out of range\n"); + PLOG(PL_FATAL, "npc: error: out of range\n"); return false; } segment_size = segmentSize; @@ -202,7 +202,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) int numData = atoi(val); if ((numData < 1) || (numData > 127)) { - DMSG(0, "npc: error: block out of range\n"); + PLOG(PL_FATAL, "npc: error: block out of range\n"); return false; } num_data = numData; @@ -212,7 +212,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) int numParity = atoi(val); if ((numParity < 0) || (numParity > 127)) { - DMSG(0, "npc: error: parity out of range\n"); + PLOG(PL_FATAL, "npc: error: parity out of range\n"); return false; } num_parity = numParity; @@ -228,7 +228,7 @@ bool NormPrecodeApp::OnCommand(const char* cmd, const char* val) int iBufferMax = atoi(val); if (iBufferMax < 0) { - DMSG(0, "npc: error: \"ibuffer\" cannot be less than zero\n"); + PLOG(PL_FATAL, "npc: error: \"ibuffer\" cannot be less than zero\n"); return false; } i_buffer_max = iBufferMax; @@ -249,7 +249,7 @@ bool NormPrecodeApp::ProcessCommands(int argc, const char*const* argv) switch (cmdType) { case CMD_INVALID: - DMSG(0, "npc: error: Invalid command:%s\n", argv[i]); + PLOG(PL_FATAL, "npc: error: Invalid command:%s\n", argv[i]); return false; case CMD_NOARG: if (!OnCommand(argv[i], NULL)) return false; @@ -304,7 +304,7 @@ bool NormPrecodeApp::OnStartup(int argc, const char*const* argv) if (!in_file.IsOpen()) { - DMSG(0, "npc: error: no input file given\n"); + PLOG(PL_FATAL, "npc: error: no input file given\n"); Usage(); return false; } @@ -321,7 +321,7 @@ void NormPrecodeApp::OnShutdown() // (TBD) do better cleanup of allocated buffers, etc!! if (in_file.IsOpen()) in_file.Close(); if (out_file.IsOpen()) out_file.Close(); - DMSG(0, "npc: Done.\n"); + PLOG(PL_INFO, "npc: Done.\n"); } // end NormPrecodeApp::OnShutdown() #define DIFF_T(a,b) (1+ 1000000*(a.tv_sec - b.tv_sec) + (a.tv_usec - b.tv_usec) ) @@ -335,7 +335,7 @@ void NormPrecodeApp::InitInterleaver(NormFile::Offset numSegments) if ((i_max > 0) && ((interleaver_width > i_max) || (interleaver_height > i_max))) interleaver_height = interleaver_width = i_max; interleaver_size = interleaver_height * interleaver_width; - DMSG(2, "npc interleaver width:%lu height:%lu segments (numSeg:%lld)\n", + PLOG(PL_INFO, "npc interleaver width:%lu height:%lu segments (numSeg:%lld)\n", (unsigned long)interleaver_width, (unsigned long)interleaver_height, numSegments); } // end NormPrecodeApp::InitInterleaver() @@ -527,7 +527,7 @@ bool NormPrecodeApp::Encode() strcpy(outFileName + PATH_MAX - 4, ".npc"); if (!out_file.Open(outFileName, O_WRONLY | O_CREAT | O_TRUNC)) { - DMSG(0, "npc: error opening output file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error opening output file: %s\n", GetErrorString()); return false; } } @@ -559,27 +559,27 @@ bool NormPrecodeApp::Encode() InitInterleaver(numOutputSegments); // 1) Init our FEC encoder - NormEncoderRS8a encoder; + NormEncoderRS8 encoder; if (!encoder.Init(num_data, num_parity, dataSegmentSize)) // 4 CRC bytes are _not_ encoded { - DMSG(0, "npc: error initializing FEC encoder\n"); + PLOG(PL_FATAL, "npc: error initializing FEC encoder\n"); return false; } // Determine number of segments to allocate for FEC encoding and // interleaver buffering if applicable NormFile::Offset interleaverBytes = interleaver_size * segment_size; - char* iBuffer = NULL; + char* iBuffer = NULL; bool useBuffering = false; if (interleaverBytes <= i_buffer_max) { // Allocate buffering for full interleaver block and parity - DMSG(0, "npc: allocating interleaver buffer ...\n"); + PLOG(PL_INFO, "npc: allocating interleaver buffer ...\n"); iBuffer = new char[interleaverBytes + (num_parity * segment_size)]; if (NULL != iBuffer) useBuffering = true; else - DMSG(0, "npc: warning: couldn't allocate full interleaver buffer: %s\n", GetErrorString()); + PLOG(PL_WARN, "npc: warning: couldn't allocate full interleaver buffer: %s\n", GetErrorString()); } if (NULL == iBuffer) { @@ -587,7 +587,7 @@ bool NormPrecodeApp::Encode() iBuffer = new char[(1 + num_parity) * segment_size]; if (NULL == iBuffer) { - DMSG(0, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); return false; } } @@ -596,7 +596,7 @@ bool NormPrecodeApp::Encode() char** parityVec = new char*[num_parity]; if (NULL == parityVec) { - DMSG(0, "npc: new parity array error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: new parity array error: %s\n", GetErrorString()); return false; } // Keep parity vecs after first _or_ "interleaver_size" segments of "iBuffer" @@ -625,7 +625,7 @@ bool NormPrecodeApp::Encode() } else { - DMSG(0, "npc: error: unsupported file offset size (%d bytes)\n", sizeof(NormFile::Offset)); + PLOG(PL_FATAL, "npc: error: unsupported file offset size (%d bytes)\n", sizeof(NormFile::Offset)); return false; } // put in_file_path file name portion into middle section of "metaData" @@ -638,7 +638,7 @@ bool NormPrecodeApp::Encode() strncpy(metaData+8, ptr, segment_size - 12); // 2) Read "in_file" segments, encode, and output to "out_file" - DMSG(0, "npc: encoding file ... (progress: 0%%)"); + PLOG(PL_FATAL, "npc: encoding file ... (progress: 0%%)"); // State to track/display encoding progress NormFile::Offset progressThreshold = numOutputSegments / 100; double progressIncrement = 100.0; @@ -686,12 +686,12 @@ bool NormPrecodeApp::Encode() } if (in_file.Read(segment, bytesToRead) != bytesToRead) { - DMSG(0, "\nnpc: unexpected error reading input file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected error reading input file: %s\n", GetErrorString()); return false; } } // C) Encode and check for parity readiness - encoder.Encode(segment, parityVec); + encoder.Encode(outputSegmentId, segment, parityVec); unsigned int numData = (blockId != lastBlockId) ? fecBlockSize : lastBlockSize; if (numData == ++parityCount) { @@ -734,7 +734,7 @@ bool NormPrecodeApp::Encode() bytesToWrite *= segment_size; if (out_file.Write(iBuffer, bytesToWrite) != bytesToWrite) { - DMSG(0, "\nnpc: unexpected error writing to output file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected error writing to output file: %s\n", GetErrorString()); return false; } } @@ -745,13 +745,13 @@ bool NormPrecodeApp::Encode() // "Seek" to interleaver offset if (!out_file.Seek(interleaverOffset)) { - DMSG(0, "\nnpc: unexpected output file seek error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected output file seek error: %s\n", GetErrorString()); return false; } // And write segment to output file if (out_file.Write(segment, segment_size) != segment_size) { - DMSG(0, "npc: unexpected error writing to output file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: unexpected error writing to output file: %s\n", GetErrorString()); return false; } outputSegmentId++; @@ -760,17 +760,17 @@ bool NormPrecodeApp::Encode() if (++progressCounter >= progressThreshold) { if (progressPercent < 9) - DMSG(0, "\b\b\b%d%%)", progressPercent + 1); + PLOG(PL_FATAL, "\b\b\b%d%%)", progressPercent + 1); else if (progressPercent < 99) - DMSG(0, "\b\b\b\b%d%%)", progressPercent + 1); + PLOG(PL_FATAL, "\b\b\b\b%d%%)", progressPercent + 1); if (progressPercent < 99) progressPercent++; progressCounter = 0; } } if (progressPercent < 10) - DMSG(0, "\b\b\b100%%)\n"); + PLOG(PL_FATAL, "\b\b\b100%%)\n"); else - DMSG(0, "\b\b\b\b100%%)\n"); + PLOG(PL_FATAL, "\b\b\b\b100%%)\n"); in_file.Close(); out_file.Close(); @@ -783,7 +783,7 @@ bool NormPrecodeApp::Encode() delete[] parityVec; parityVec = NULL; - DMSG(2, "NormPrecodeApp::Encode() encoding time: %ld usec\n", DIFF_T(t2, t1)); + PLOG(PL_INFO, "NormPrecodeApp::Encode() encoding time: %ld usec\n", DIFF_T(t2, t1)); return true; @@ -796,7 +796,7 @@ bool NormPrecodeApp::Decode() NormFile::Offset numInputSegments = inputFileSize / segment_size; if (0 != (inputFileSize % segment_size)) { - DMSG(0, "npc: error: input file size not integral number of given \n"); + PLOG(PL_FATAL, "npc: error: input file size not integral number of given \n"); return false; } // Reverse calculate the FEC blocking @@ -815,11 +815,11 @@ bool NormPrecodeApp::Decode() InitInterleaver(numInputSegments); // 2) init FEC decoder - NormDecoderRS8a decoder; + NormDecoderRS8 decoder; unsigned int dataSegmentSize = segment_size - 4; // leaves space for our CRC if (!decoder.Init(num_data, num_parity, dataSegmentSize)) { - DMSG(0, "npc: error initializing decoder\n"); + PLOG(PL_FATAL, "npc: error initializing decoder\n"); return false; } @@ -836,12 +836,12 @@ bool NormPrecodeApp::Decode() //((num_data + num_parity) <= numInputSegments)) { // Allocate buffering for full interleaver block and parity - DMSG(0, "npc: allocating interleaver buffer ...\n"); + PLOG(PL_INFO, "npc: allocating interleaver buffer ...\n"); iBuffer = new char[interleaverBytes + ((num_data + num_parity) * segment_size)]; if (NULL != iBuffer) useBuffering = true; else - DMSG(0, "npc: warning: couldn't allocate full interleaver buffer: %s\n", GetErrorString()); + PLOG(PL_WARN, "npc: warning: couldn't allocate full interleaver buffer: %s\n", GetErrorString()); } if (NULL == iBuffer) { @@ -849,7 +849,7 @@ bool NormPrecodeApp::Decode() iBuffer = new char[(num_data + num_parity) * segment_size]; if (NULL == iBuffer) { - DMSG(0, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); return false; } } @@ -857,7 +857,7 @@ bool NormPrecodeApp::Decode() char** fecVec = new char*[num_data + num_parity]; if (NULL == fecVec) { - DMSG(0, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: error: couldn't allocate parity buffer: %s\n", GetErrorString()); return false; } if (!useBuffering) @@ -875,12 +875,12 @@ bool NormPrecodeApp::Decode() unsigned int* erasureLocs = new unsigned int[num_parity]; if (NULL == erasureLocs) { - DMSG(0, "npc: new erasure location array error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "npc: new erasure location array error: %s\n", GetErrorString()); return false; } - DMSG(0, "npc: decoding file ... (progress: 0%%)"); + PLOG(PL_FATAL, "npc: decoding file ... (progress: 0%%)"); // State to track/display decoding progress NormFile::Offset progressThreshold = numInputSegments / 100; double progressIncrement = 100.0; @@ -921,7 +921,7 @@ bool NormPrecodeApp::Decode() bytesToRead = lastInterleaverBytes; if (in_file.Read(iBuffer, bytesToRead) != bytesToRead) { - DMSG(0, "\nnpc: error reading input file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: error reading input file: %s\n", GetErrorString()); return false; } interleaverBlockId++; @@ -938,13 +938,13 @@ bool NormPrecodeApp::Decode() // seek to interleaver offset if (!in_file.Seek(interleaverOffset)) { - DMSG(0, "\nnpc: unexpected input file seek error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected input file seek error: %s\n", GetErrorString()); return false; } // Read segment if (in_file.Read(fecVec[i], segment_size) != segment_size) { - DMSG(0, "\nnpc: unexpected error reading input file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected error reading input file: %s\n", GetErrorString()); return false; } inputSegmentId++; @@ -954,11 +954,11 @@ bool NormPrecodeApp::Decode() checksum = htonl(checksum); if (0 != memcmp(&checksum, fecVec[i] + dataSegmentSize, 4)) { - DMSG(6, "\nnpc: bad checksum! (found erasure)\n"); + PLOG(PL_TRACE, "\nnpc: bad checksum! (found erasure)\n"); erasureLocs[erasureCount++] = i; if (erasureCount > num_parity) { - DMSG(0, "\nnpc: decoding encountered block with too many errors!\n"); + PLOG(PL_FATAL, "\nnpc: decoding encountered block with too many errors!\n"); return false; } memset(fecVec[i], 0, dataSegmentSize); @@ -983,11 +983,11 @@ bool NormPrecodeApp::Decode() checksum = htonl(checksum); if (0 != memcmp(&checksum, fecVec[segmentCount] + dataSegmentSize, 4)) { - DMSG(6, "\nnpc: bad checksum! (found erasure)\n"); + PLOG(PL_TRACE, "\nnpc: bad checksum! (found erasure)\n"); erasureLocs[erasureCount++] = segmentCount; if (erasureCount > num_parity) { - DMSG(0, "\nnpc: decoding encountered block with too many errors!\n"); + PLOG(PL_FATAL, "\nnpc: decoding encountered block with too many errors!\n"); return false; } memset(fecVec[segmentCount], 0, dataSegmentSize); @@ -1043,7 +1043,7 @@ bool NormPrecodeApp::Decode() outFileSize = ntoho(outFileSize); break; default: - DMSG(0, "\nnpc: error: unsupported file offset size\n"); + PLOG(PL_FATAL, "\nnpc: error: unsupported file offset size\n"); return false; } if (!out_file.IsOpen()) @@ -1055,7 +1055,7 @@ bool NormPrecodeApp::Decode() strncpy(outFileName, fecVec[0]+8, maxLen); if (!out_file.Open(outFileName, O_WRONLY | O_CREAT | O_TRUNC)) { - DMSG(0, "\nnpc: error opening output file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: error opening output file: %s\n", GetErrorString()); return false; } } @@ -1068,7 +1068,7 @@ bool NormPrecodeApp::Decode() } if (out_file.Write(fecVec[i], segmentSize) != segmentSize) { - DMSG(0, "\nnpc: unexpected error writing to output file: %s\n", GetErrorString()); + PLOG(PL_FATAL, "\nnpc: unexpected error writing to output file: %s\n", GetErrorString()); return false; } } @@ -1084,9 +1084,9 @@ bool NormPrecodeApp::Decode() if (++progressCounter >= progressThreshold) { if (progressPercent < 9) - DMSG(0, "\b\b\b%d%%)", progressPercent + 1); + PLOG(PL_FATAL, "\b\b\b%d%%)", progressPercent + 1); else if (progressPercent < 99) - DMSG(0, "\b\b\b\b%d%%)", progressPercent + 1); + PLOG(PL_FATAL, "\b\b\b\b%d%%)", progressPercent + 1); if (progressPercent < 99) progressPercent++; progressCounter = 0; } @@ -1094,9 +1094,9 @@ bool NormPrecodeApp::Decode() } // end while (inputSegmentId < numInputSegments) if (progressPercent < 10) - DMSG(0, "\b\b\b100%%)\n"); + PLOG(PL_FATAL, "\b\b\b100%%)\n"); else - DMSG(0, "\b\b\b\b100%%)\n"); + PLOG(PL_FATAL, "\b\b\b\b100%%)\n"); // Cleanup, cleanup delete[] iBuffer; diff --git a/common/normSegment.cpp b/src/common/normSegment.cpp similarity index 89% rename from common/normSegment.cpp rename to src/common/normSegment.cpp index 9829e78..41b0a6b 100644 --- a/common/normSegment.cpp +++ b/src/common/normSegment.cpp @@ -20,7 +20,7 @@ bool NormSegmentPool::Init(unsigned int count, unsigned int size) #ifdef SIMULATE // In simulations, don't really need big vectors for data // since we don't actually read/write real data (for the most part) - size = MIN(size, (SIM_PAYLOAD_MAX+1)); + size = MIN(size, SIM_PAYLOAD_MAX); #endif // SIMULATE // This makes sure we get appropriate alignment unsigned int allocSize = size / sizeof(char*); @@ -29,7 +29,7 @@ bool NormSegmentPool::Init(unsigned int count, unsigned int size) seg_pool = new char*[allocSize * count]; if (seg_pool) { - char **ptr = seg_pool; + char** ptr = seg_pool; for (unsigned int i = 0; i < count; i++) { *ptr = seg_list; @@ -39,12 +39,11 @@ bool NormSegmentPool::Init(unsigned int count, unsigned int size) } else { - DMSG(0, "NormSegmentPool::Init() memory allocation error: %s\n", + PLOG(PL_FATAL, "NormSegmentPool::Init() memory allocation error: %s\n", GetErrorString()); Destroy(); return false; } - seg_total = seg_count = count; return true; } // end NormSegmentPool::Init() @@ -52,7 +51,8 @@ bool NormSegmentPool::Init(unsigned int count, unsigned int size) void NormSegmentPool::Destroy() { ASSERT(seg_count == seg_total); - delete [] seg_pool; + if (NULL != seg_pool) + delete[] seg_pool; seg_pool = NULL; seg_list = NULL; seg_count = 0; @@ -77,7 +77,7 @@ char* NormSegmentPool::Get() { if (!overrun_flag) { - DMSG(0, "NormSegmentPool::Get() warning: operating with constrained buffering resources\n"); + PLOG(PL_WARN, "NormSegmentPool::Get() warning: operating with constrained buffering resources\n"); overruns++; overrun_flag = true; } @@ -91,7 +91,7 @@ char* NormSegmentPool::Get() // NormBlock Implementation NormBlock::NormBlock() - : size(0), segment_table(NULL), erasure_count(0), parity_count(0) + : size(0), segment_table(NULL), erasure_count(0), parity_count(0), next(NULL) { } @@ -105,19 +105,19 @@ bool NormBlock::Init(UINT16 totalSize) if (segment_table) Destroy(); if (!(segment_table = new char*[totalSize])) { - DMSG(0, "NormBlock::Init() segment_table allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormBlock::Init() segment_table allocation error: %s\n", GetErrorString()); return false; } memset(segment_table, 0, totalSize*sizeof(char*)); if (!pending_mask.Init(totalSize)) { - DMSG(0, "NormBlock::Init() pending_mask allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormBlock::Init() pending_mask allocation error: %s\n", GetErrorString()); Destroy(); return false; } if (!repair_mask.Init(totalSize)) { - DMSG(0, "NormBlock::Init() repair_mask allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormBlock::Init() repair_mask allocation error: %s\n", GetErrorString()); Destroy(); return false; } @@ -149,7 +149,7 @@ void NormBlock::Destroy() void NormBlock::EmptyToPool(NormSegmentPool& segmentPool) { - ASSERT(segment_table); + ASSERT(NULL != segment_table); for (unsigned int i = 0; i < size; i++) { if (NULL != segment_table[i]) @@ -160,21 +160,31 @@ void NormBlock::EmptyToPool(NormSegmentPool& segmentPool) } } // end NormBlock::EmptyToPool() +/* bool NormBlock::IsEmpty() const { ASSERT(segment_table); for (unsigned int i = 0; i < size; i++) - if (segment_table[i]) return false; + if (NULL != segment_table[i]) return false; return true; -} // end NormBlock::EmptyToPool() - -// Used by client side to determine if NACK should be sent +} // end NormBlock::IsEmpty() +*/ + +// Used by receiver side to determine if NACK should be sent // Note: This invalidates the block's "repair_mask" state bool NormBlock::IsRepairPending(UINT16 numData, UINT16 numParity) { - // Clients ask for a block of parity to fulfill their + // Receivers ask for a block of parity to fulfill their // repair needs (erasure_count), but if there isn't // enough parity, they ask for some data segments, too + + // This first section of code presets bits in the repair_mask + // for those segments we don't care about. We care about the + // parity we need (erasure_count) and any explicit segments + // required when our erasure_count exceeds numParity + // The XCopy() below then determines if there is any residual + // repair need (remember repair_mask has overheard repair + // state already set) if (erasure_count > numParity) { if (numParity) @@ -206,7 +216,7 @@ bool NormBlock::IsRepairPending(UINT16 numData, UINT16 numParity) return (repair_mask.IsSet()); } // end NormBlock::IsRepairPending() -// Called by server +// Called by sender bool NormBlock::TxReset(UINT16 numData, UINT16 numParity, UINT16 autoParity, @@ -238,7 +248,7 @@ bool NormBlock::TxReset(UINT16 numData, #ifdef SIMULATE payloadMax = MIN(payloadMax, SIM_PAYLOAD_MAX); #endif // SIMULATE - memset(*ptr, 0, payloadMax+1); + memset(*ptr, 0, payloadMax); } ptr++; } @@ -255,7 +265,8 @@ bool NormBlock::ActivateRepairs(UINT16 numParity) { pending_mask.Add(repair_mask); ASSERT(pending_mask.IsSet()); - repair_mask.Clear(); + repair_mask.Clear(); + SetFlag(IN_REPAIR); return true; } else @@ -264,7 +275,7 @@ bool NormBlock::ActivateRepairs(UINT16 numParity) } } // end NormBlock::ActivateRepairs() -// For NACKs arriving during server repair_timer "holdoff" time +// For NACKs arriving during sender repair_timer "holdoff" time // (we directly update the "pending_mask" for blocks/segments // greater than our current transmit index) bool NormBlock::TxUpdate(NormSegmentId nextId, NormSegmentId lastId, @@ -330,7 +341,7 @@ bool NormBlock::TxUpdate(NormSegmentId nextId, NormSegmentId lastId, bool NormBlock::HandleSegmentRequest(NormSegmentId nextId, NormSegmentId lastId, UINT16 numData, UINT16 numParity, UINT16 erasureCount) { - DMSG(6, "NormBlock::HandleSegmentRequest() blk>%lu seg>%hu:%hu erasures:%hu\n", + PLOG(PL_TRACE, "NormBlock::HandleSegmentRequest() blk>%lu seg>%hu:%hu erasures:%hu\n", (UINT32)id, (UINT16)nextId, (UINT16)lastId, erasureCount); bool increasedRepair = false; if (nextId < numData) @@ -392,6 +403,8 @@ bool NormBlock::HandleSegmentRequest(NormSegmentId nextId, NormSegmentId lastId, bool NormBlock::AppendRepairAdv(NormCmdRepairAdvMsg& cmd, NormObjectId objectId, bool repairInfo, + UINT8 fecId, + UINT8 fecM, UINT16 numData, UINT16 segmentSize) { @@ -434,7 +447,7 @@ bool NormBlock::AppendRepairAdv(NormCmdRepairAdvMsg& cmd, { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormBlock::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_WARN, "NormBlock::AppendRepairAdv() warning: full msg\n"); break; } } @@ -448,12 +461,12 @@ bool NormBlock::AppendRepairAdv(NormCmdRepairAdvMsg& cmd, ASSERT(0); // can't happen break; case NormRepairRequest::ITEMS: - req.AppendRepairItem(objectId, id, numData, firstId); + req.AppendRepairItem(fecId, fecM, objectId, id, numData, firstId); if (2 == segmentCount) - req.AppendRepairItem(objectId, id, numData, currentId); + req.AppendRepairItem(fecId, fecM, objectId, id, numData, currentId); break; case NormRepairRequest::RANGES: - req.AppendRepairRange(objectId, id, numData, firstId, + req.AppendRepairRange(fecId, fecM, objectId, id, numData, firstId, objectId, id, numData, currentId); break; case NormRepairRequest::ERASURES: @@ -466,7 +479,7 @@ bool NormBlock::AppendRepairAdv(NormCmdRepairAdvMsg& cmd, if (NormRepairRequest::INVALID != prevForm) { if (0 == cmd.PackRepairRequest(req)) - DMSG(0, "NormBlock::AppendRepairAdv() warning: full msg\n"); + PLOG(PL_WARN, "NormBlock::AppendRepairAdv() warning: full msg\n"); } } return true; @@ -499,9 +512,11 @@ NormObjectSize NormBlock::GetBytesPending(UINT16 numData, return pendingBytes; } // end NormBlock::GetBytesPending() -// Called by client +// Called by receiver // (TBD) this should return true iff something appended, false otherwise bool NormBlock::AppendRepairRequest(NormNackMsg& nack, + UINT8 fecId, + UINT8 fecM, UINT16 numData, UINT16 numParity, NormObjectId objectId, @@ -565,7 +580,7 @@ bool NormBlock::AppendRepairRequest(NormNackMsg& nack, { if (0 == nack.PackRepairRequest(req)) { - DMSG(3, "NormBlock::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormBlock::AppendRepairRequest() warning: full NACK msg\n"); break; } } @@ -579,12 +594,12 @@ bool NormBlock::AppendRepairRequest(NormNackMsg& nack, ASSERT(0); break; case NormRepairRequest::ITEMS: - req.AppendRepairItem(objectId, id, numData, firstId); // (TBD) error check + req.AppendRepairItem(fecId, fecM, objectId, id, numData, firstId); // (TBD) error check if (2 == segmentCount) - req.AppendRepairItem(objectId, id, numData, currentId); // (TBD) error check + req.AppendRepairItem(fecId, fecM, objectId, id, numData, currentId); // (TBD) error check break; case NormRepairRequest::RANGES: - req.AppendRepairRange(objectId, id, numData, firstId, // (TBD) error check + req.AppendRepairRange(fecId, fecM, objectId, id, numData, firstId, // (TBD) error check objectId, id, numData, currentId); // (TBD) error check break; case NormRepairRequest::ERASURES: @@ -597,7 +612,7 @@ bool NormBlock::AppendRepairRequest(NormNackMsg& nack, if (NormRepairRequest::INVALID != prevForm) { if (0 == nack.PackRepairRequest(req)) - DMSG(3, "NormBlock::AppendRepairRequest() warning: full NACK msg\n"); + PLOG(PL_WARN, "NormBlock::AppendRepairRequest() warning: full NACK msg\n"); } return true; } // end NormBlock::AppendRepairRequest() @@ -622,7 +637,7 @@ bool NormBlockPool::Init(UINT32 numBlocks, UINT16 segsPerBlock) { if (!b->Init(segsPerBlock)) { - DMSG(0, "NormBlockPool::Init() block init error\n"); + PLOG(PL_FATAL, "NormBlockPool::Init() block init error\n"); delete b; Destroy(); return false; @@ -634,7 +649,7 @@ bool NormBlockPool::Init(UINT32 numBlocks, UINT16 segsPerBlock) } else { - DMSG(0, "NormBlockPool::Init() new block error\n"); + PLOG(PL_FATAL, "NormBlockPool::Init() new block error\n"); Destroy(); return false; } @@ -670,14 +685,14 @@ bool NormBlockBuffer::Init(unsigned long rangeMax, unsigned long tableSize) // Make sure tableSize is greater than 0 and 2^n if (!rangeMax || !tableSize) { - DMSG(0, "NormBlockBuffer::Init() bad range(%lu) or tableSize(%lu)\n", + PLOG(PL_FATAL, "NormBlockBuffer::Init() bad range(%lu) or tableSize(%lu)\n", rangeMax, tableSize); return false; } if (0 != (tableSize & 0x07)) tableSize = (tableSize >> 3) + 1; if (!(table = new NormBlock*[tableSize])) { - DMSG(0, "NormBlockBuffer::Init() buffer allocation error: %s\n", GetErrorString()); + PLOG(PL_FATAL, "NormBlockBuffer::Init() buffer allocation error: %s\n", GetErrorString()); return false; } memset(table, 0, tableSize*sizeof(char*)); @@ -695,7 +710,7 @@ void NormBlockBuffer::Destroy() NormBlock* block; while((block = Find(range_lo))) { - DMSG(0, "NormBlockBuffer::Destroy() buffer not empty!?\n"); + PLOG(PL_ERROR, "NormBlockBuffer::Destroy() buffer not empty!?\n"); Remove(block); delete block; } @@ -796,7 +811,7 @@ bool NormBlockBuffer::Insert(NormBlock* theBlock) bool NormBlockBuffer::Remove(const NormBlock* theBlock) { - ASSERT(theBlock); + ASSERT(NULL != theBlock); if (range) { const NormBlockId& blockId = theBlock->GetId(); diff --git a/common/normSession.cpp b/src/common/normSession.cpp similarity index 58% rename from common/normSession.cpp rename to src/common/normSession.cpp index 3fac087..9ddff5a 100644 --- a/common/normSession.cpp +++ b/src/common/normSession.cpp @@ -1,8 +1,15 @@ #include "normSession.h" + +#include "normEncoderMDP.h" // "legacy" MDP Reed-Solomon encoder +#include "normEncoderRS8.h" // 8-bit Reed-Solomon encoder of RFC 5510 +#include "normEncoderRS16.h" // 16-bit Reed-Solomon encoder of RFC 5510 + #include // for gmtime() in NormTrace() +#include "protoPktETH.h" +#include "protoPktIP.h" -const UINT8 NormSession::DEFAULT_TTL = 255; // bits/sec +const UINT8 NormSession::DEFAULT_TTL = 255; const double NormSession::DEFAULT_TRANSMIT_RATE = 64000.0; // bits/sec const double NormSession::DEFAULT_GRTT_INTERVAL_MIN = 1.0; // sec const double NormSession::DEFAULT_GRTT_INTERVAL_MAX = 30.0; // sec @@ -12,28 +19,32 @@ const unsigned int NormSession::DEFAULT_GRTT_DECREASE_DELAY = 3; const double NormSession::DEFAULT_BACKOFF_FACTOR = 4.0; const double NormSession::DEFAULT_GSIZE_ESTIMATE = 1000.0; const UINT16 NormSession::DEFAULT_NDATA = 64; -const UINT16 NormSession::DEFAULT_NPARITY = 32; +const UINT16 NormSession::DEFAULT_NPARITY = 8; const UINT16 NormSession::DEFAULT_TX_CACHE_MIN = 8; const UINT16 NormSession::DEFAULT_TX_CACHE_MAX = 256; - +const UINT32 NormSession::DEFAULT_TX_CACHE_SIZE = (UINT32)20*1024*1024; +const double NormSession::DEFAULT_FLOW_CONTROL_FACTOR = 2.0; +const UINT16 NormSession::DEFAULT_RX_CACHE_MAX = 256; + const int NormSession::DEFAULT_ROBUST_FACTOR = 20; // default robust factor NormSession::NormSession(NormSessionMgr& sessionMgr, NormNodeId localNodeId) - : session_mgr(sessionMgr), notify_pending(false), tx_port(0), + : session_mgr(sessionMgr), notify_pending(false), tx_port(0), tx_port_reuse(false), tx_socket_actual(ProtoSocket::UDP), tx_socket(&tx_socket_actual), - rx_socket(ProtoSocket::UDP), local_node_id(localNodeId), - ttl(DEFAULT_TTL), tos(0), loopback(false), rx_port_reuse(false), rx_addr_bind(false), + rx_socket(ProtoSocket::UDP), rx_cap(NULL), rx_port_reuse(false), local_node_id(localNodeId), + ttl(DEFAULT_TTL), tos(0), loopback(false), fragmentation(false), ecn_enabled(false), tx_rate(DEFAULT_TRANSMIT_RATE/8.0), tx_rate_min(-1.0), tx_rate_max(-1.0), - backoff_factor(DEFAULT_BACKOFF_FACTOR), is_server(false), + backoff_factor(DEFAULT_BACKOFF_FACTOR), is_sender(false), tx_robust_factor(DEFAULT_ROBUST_FACTOR), instance_id(0), ndata(DEFAULT_NDATA), nparity(DEFAULT_NPARITY), auto_parity(0), extra_parity(0), - sndr_emcon(false), encoder(NULL), + sndr_emcon(false), tx_only(false), tx_connect(false), encoder(NULL), next_tx_object_id(0), tx_cache_count_min(DEFAULT_TX_CACHE_MIN), tx_cache_count_max(DEFAULT_TX_CACHE_MAX), - tx_cache_size_max((UINT32)20*1024*1024), - posted_tx_queue_empty(false), - acking_node_count(0), watermark_pending(false), tx_repair_pending(false), advertise_repairs(false), + tx_cache_size_max(DEFAULT_TX_CACHE_SIZE), + posted_tx_queue_empty(false), posted_tx_rate_changed(false), + acking_node_count(0), acking_auto_populate(TRACK_NONE), watermark_pending(false), watermark_flushes(false), + tx_repair_pending(false), advertise_repairs(false), suppress_nonconfirmed(false), suppress_rate(-1.0), suppress_rtt(-1.0), probe_proactive(true), probe_pending(false), probe_reset(true), probe_data_check(false), grtt_interval(0.5), @@ -41,18 +52,23 @@ NormSession::NormSession(NormSessionMgr& sessionMgr, NormNodeId localNodeId) grtt_interval_max(DEFAULT_GRTT_INTERVAL_MAX), grtt_max(DEFAULT_GRTT_MAX), grtt_decrease_delay_count(DEFAULT_GRTT_DECREASE_DELAY), - grtt_response(false), grtt_current_peak(0.0), grtt_age(0.0), - cc_enable(false), cc_sequence(0), cc_slow_start(true), cc_active(false), - is_client(false), rx_robust_factor(DEFAULT_ROBUST_FACTOR), unicast_nacks(false), - client_silent(false), rcvr_ignore_info(false), rcvr_max_delay(-1), - default_repair_boundary(NormServerNode::BLOCK_BOUNDARY), - default_nacking_mode(NormObject::NACK_NORMAL), + grtt_response(false), grtt_current_peak(0.0), grtt_age(0.0), probe_count(1), + cc_enable(false), cc_adjust(true), cc_sequence(0), cc_slow_start(true), cc_active(false), + flow_control_factor(DEFAULT_FLOW_CONTROL_FACTOR), + cmd_count(0), cmd_buffer(NULL), cmd_length(0), + is_receiver(false), rx_robust_factor(DEFAULT_ROBUST_FACTOR), preset_sender(NULL), unicast_nacks(false), + receiver_silent(false), rcvr_ignore_info(false), rcvr_max_delay(-1), rcvr_realtime(false), + default_repair_boundary(NormSenderNode::BLOCK_BOUNDARY), + default_nacking_mode(NormObject::NACK_NORMAL), default_sync_policy(NormSenderNode::SYNC_CURRENT), + rx_cache_count_max(DEFAULT_RX_CACHE_MAX), + ecn_ignore_loss(false), trace(false), tx_loss_rate(0.0), rx_loss_rate(0.0), user_data(NULL), next(NULL) { interface_name[0] = '\0'; tx_socket->SetNotifier(&sessionMgr.GetSocketNotifier()); tx_socket->SetListener(this, &NormSession::TxSocketRecvHandler); + tx_address.Invalidate(); rx_socket.SetNotifier(&sessionMgr.GetSocketNotifier()); rx_socket.SetListener(this, &NormSession::RxSocketRecvHandler); @@ -69,6 +85,14 @@ NormSession::NormSession(NormSessionMgr& sessionMgr, NormNodeId localNodeId) flush_timer.SetInterval(0.0); flush_timer.SetRepeat(0); + flow_control_timer.SetListener(this, &NormSession::OnFlowControlTimeout); + flow_control_timer.SetInterval(0.0); + flow_control_timer.SetRepeat(0); + + cmd_timer.SetListener(this, &NormSession::OnCmdTimeout); + cmd_timer.SetInterval(0.0); + cmd_timer.SetRepeat(0); + probe_timer.SetListener(this, &NormSession::OnProbeTimeout); probe_timer.SetInterval(0.0); probe_timer.SetRepeat(-1); @@ -90,126 +114,244 @@ NormSession::NormSession(NormSessionMgr& sessionMgr, NormNodeId localNodeId) report_timer.SetRepeat(-1); } - NormSession::~NormSession() { + if (NULL != preset_sender) + { + delete preset_sender; + preset_sender = NULL; + } Close(); } - -bool NormSession::Open(const char* interfaceName) +bool NormSession::Open() { ASSERT(address.IsValid()); if (!tx_socket->IsOpen()) { - if (address.GetPort() != tx_port) + // Make sure user wants a separate tx_socket + if ((address.GetPort() != tx_port) || + (tx_address.IsValid() && !address.HostIsEqual(tx_address))) { - if (!tx_socket->Open(tx_port, address.GetType())) + if (!tx_socket->Open(tx_port, address.GetType(), false)) { - DMSG(0, "NormSession::Open() tx_socket open error\n"); + PLOG(PL_FATAL, "NormSession::Open() tx_socket::Open() error\n"); return false; } + if (tx_port_reuse) + { + if (!tx_socket->SetReuse(true)) + { + PLOG(PL_FATAL, "NormSession::Open() tx_socket::SetReuse() error\n"); + Close(); + return false; + } + } + ProtoAddress* txBindAddress = tx_address.IsValid() ? &tx_address : NULL; + if (!tx_socket->Bind(tx_port, txBindAddress)) + { + PLOG(PL_FATAL, "NormSession::Open() tx_socket::Bind() error\n"); + Close(); + return false; + } + // Connecting unicast sockets can help us get unicast NACKs + // when tx_port_reuse and tx_only is requested + if (tx_connect && !address.IsMulticast()) + { + if (!tx_socket->Connect(address)) + { + PLOG(PL_FATAL, "NormSession::Open() tx_socket::Connect() error\n"); + Close(); + return false; + } + } } else { tx_socket = &rx_socket; } + } - if (!rx_socket.IsOpen()) + if (!rx_socket.IsOpen() && (!tx_only || (&rx_socket == tx_socket))) { if (!rx_socket.Open(0, address.GetType(), false)) { - DMSG(0, "NormSession::Open() rx_socket open error\n"); + PLOG(PL_FATAL, "NormSession::Open() rx_socket.Open() error\n"); Close(); return false; } - if (rx_port_reuse) { // Enable port/addr reuse and bind socket to destination address if (!rx_socket.SetReuse(true)) { - DMSG(0, "NormSession::Open() rx_socket reuse error\n"); + PLOG(PL_FATAL, "NormSession::Open() rx_socket::SetReuse() error\n"); Close(); return false; } } const ProtoAddress* bindAddr = NULL; - if (rx_addr_bind) + if (rx_bind_addr.IsValid()) { -#ifndef WIN32 - if (address.IsMulticast()) // Win32 doesn't like to bind to multicast addr ??? - bindAddr = &address; -#endif // !WIN32 +#ifdef WIN32 + if (!rx_bind_addr.IsMulticast()) // Win32 doesn't like to bind() mcast addr?? +#endif + bindAddr = &rx_bind_addr; } if(!rx_socket.Bind(address.GetPort(), bindAddr)) { - DMSG(0, "NormSession::Open() rx_socket bind error\n"); + PLOG(PL_FATAL, "NormSession::Open() error: rx_socket.Bind() error\n"); Close(); return false; } + if (rx_connect_addr.IsValid()) + { + // For unicast, we use the "connect()" call to effectively + // uniquely "bind" our rx_socket to the remote addr. + // (it _may_ be the case that "tx_port" == "address.GetPort()" + // for this to work?) + if (!rx_socket.Connect(rx_connect_addr)) + { + PLOG(PL_FATAL, "NormSession::Open() rx_socket.Connect() error\n"); + Close(); + return false; + } + } } + if (ecn_enabled) + { + if (!tx_socket->SetEcnCapable(true)) + { + PLOG(PL_WARN, "NormSession::Open() warning: tx_socket.SetEcnEnable() error\n"); + } + } + if (0 != tos) { if (!tx_socket->SetTOS(tos)) - { - DMSG(0, "NormSession::Open() warning: tx_socket set tos error\n"); - } + PLOG(PL_WARN, "NormSession::Open() warning: tx_socket.SetTOS() error\n"); } + if (!tx_socket->SetFragmentation(fragmentation)) + PLOG(PL_WARN, "NormSession::Open() warning: tx_socket.SetFragmentation() error\n"); + if (address.IsMulticast()) { if (!tx_socket->SetTTL(ttl)) { - DMSG(0, "NormSession::Open() tx_socket set ttl error\n"); + PLOG(PL_FATAL, "NormSession::Open() tx_socket.SetTTL() error\n"); Close(); return false; } if (!tx_socket->SetLoopback(loopback)) { - DMSG(0, "NormSession::Open() tx_socket set loopback error\n"); + // TBD - Should this be set on the rx_socket instead??? + PLOG(PL_FATAL, "NormSession::Open() tx_socket.SetLoopback() error\n"); Close(); return false; } - if (interfaceName) - { - strncpy(interface_name, interfaceName, 31); - interface_name[31] = '\0'; - } + const char* interfaceName = NULL; if ('\0' != interface_name[0]) { - bool result = rx_socket.SetMulticastInterface(interface_name); + bool result = tx_only ? true : rx_socket.SetMulticastInterface(interface_name); result &= tx_socket->SetMulticastInterface(interface_name); if (!result) { - DMSG(0, "NormSession::Open() error setting multicast interface\n"); + PLOG(PL_FATAL, "NormSession::Open() t_socket::SetMulticastInterface() error\n"); Close(); return false; } interfaceName = interface_name; } - if (!rx_socket.JoinGroup(address, interfaceName)) + if (!tx_only) { - DMSG(0, "NormSession::Open() rx_socket join group error\n"); - Close(); - return false; - } - } - for (unsigned int i = 0; i < DEFAULT_MESSAGE_POOL_DEPTH; i++) - { - NormMsg* msg = new NormMsg(); - if (msg) - { - message_pool.Append(msg); + if (!rx_socket.JoinGroup(address, interfaceName)) + { + PLOG(PL_FATAL, "NormSession::Open() rx_socket.JoinGroup error\n"); + Close(); + return false; + } } - else - { - DMSG(0, "NormSession::Open() new message error: %s\n", GetErrorString()); - Close(); - return false; - } } - ActivateTimer(report_timer); + +#ifdef ECN_SUPPORT + // TBD - do this via UDP socket recvmsg() instead of raw packet capture + // If raw packet capture is enabled, create/open ProtoCap device to do it + if (ecn_enabled && !tx_only) + { + if (NULL == rx_cap) + { + if (NULL == (rx_cap = ProtoCap::Create())) + { + PLOG(PL_FATAL, "NormSession::Open() error: unable to create ProtoCap device!\n"); + Close(); + return false; + } + rx_cap->SetListener(this, &NormSession::OnPktCapture); + rx_cap->SetNotifier(session_mgr.GetChannelNotifier()); + if (!rx_cap->Open(('\0' != interface_name[0]) ? interface_name : NULL)) + { + PLOG(PL_FATAL, "NormSession::Open() error: unable to open ProtoCap device '%s'!\n", (('\0' != interface_name[0]) ? interface_name : "(null)")); + Close(); + return false; + } + rx_cap->StartInputNotification(); + // Populate "dst_addr_list" with potential valid dst addrs for this host + dst_addr_list.Destroy(); + if (rx_bind_addr.IsValid()) + { + if (!dst_addr_list.Insert(rx_bind_addr)) + { + PLOG(PL_FATAL, "NormSession::Open() error: unable to add rx_bind_addr to dst_addr_list!!\n"); + Close(); + return false; + } + } + else + { + // Put all local unicast addrs in list + if (!ProtoSocket::GetHostAddressList(ProtoAddress::IPv4, dst_addr_list)) + PLOG(PL_WARN, "NormSession::Open() warning: incomplete IPv4 host address list\n"); + if (!ProtoSocket::GetHostAddressList(ProtoAddress::IPv6, dst_addr_list)) + PLOG(PL_WARN, "NormSession::Open() warning: incomplete IPv6 host address list\n"); + } + if (address.IsMulticast() && !address.HostIsEqual(rx_bind_addr)) + { + if (!dst_addr_list.Insert(address)) + { + PLOG(PL_FATAL, "NormSession::Open() error: unable to add session addr to dst_addr_list!!\n"); + Close(); + return false; + } + } + if (dst_addr_list.IsEmpty()) + { + PLOG(PL_FATAL, "NormSession::Open() error: unable to add any addresses to dst_addr_list!!\n"); + Close(); + return false; + } + } + rx_socket.StopInputNotification(); // Disable rx_socket (keep open so mcast JOIN holds) + } +#endif // ECN_SUPPORT + if (message_pool.IsEmpty()) + { + for (unsigned int i = 0; i < DEFAULT_MESSAGE_POOL_DEPTH; i++) + { + NormMsg* msg = new NormMsg(); + if (msg) + { + message_pool.Append(msg); + } + else + { + PLOG(PL_FATAL, "NormSession::Open() new message error: %s\n", GetErrorString()); + Close(); + return false; + } + } + } + if (!report_timer.IsActive()) ActivateTimer(report_timer); return true; } // end NormSession::Open() @@ -217,8 +359,8 @@ bool NormSession::Open(const char* interfaceName) void NormSession::Close() { if (report_timer.IsActive()) report_timer.Deactivate(); - if (is_server) StopServer(); - if (is_client) StopClient(); + if (is_sender) StopSender(); + if (is_receiver) StopReceiver(); if (tx_timer.IsActive()) tx_timer.Deactivate(); message_queue.Destroy(); message_pool.Destroy(); @@ -233,6 +375,14 @@ void NormSession::Close() } rx_socket.Close(); } +#ifdef ECN_SUPPORT + if (NULL != rx_cap) + { + rx_cap->Close(); + delete rx_cap; + rx_cap = NULL; + } +#endif // ECN_SUPPORT } // end NormSession::Close() @@ -245,7 +395,8 @@ bool NormSession::SetMulticastInterface(const char* interfaceName) result &= rx_socket.SetMulticastInterface(interfaceName); if (tx_socket->IsOpen()) result &= tx_socket->SetMulticastInterface(interfaceName); - strncpy(interface_name, interfaceName, 32); + strncpy(interface_name, interfaceName, IFACE_NAME_MAX); + interface_name[IFACE_NAME_MAX] = '\0'; return result; } else @@ -255,16 +406,157 @@ bool NormSession::SetMulticastInterface(const char* interfaceName) } } // end NormSession::SetMulticastInterface() +bool NormSession::SetRxPortReuse(bool enableReuse, + const char* rxBindAddress, // bind() to / + const char* senderAddress, // connect() to / + UINT16 senderPort) +{ + rx_port_reuse = enableReuse; // allow sessionPort reuse when true + bool result; + if (NULL != rxBindAddress) + { + result = rx_bind_addr.ResolveFromString(rxBindAddress); + } + else + { + rx_bind_addr.Invalidate(); + result = true; + } + if (NULL != senderAddress) + { + if (rx_connect_addr.ResolveFromString(senderAddress)) + { + rx_connect_addr.SetPort(senderPort); + result &= true; + } + else + { + result = false; + } + } + else + { + rx_connect_addr.Invalidate(); + } + // TBD - if rx_socket.IsOpen(), should we do a Close()/Open() to rebind socket??? + return result; +} // end NormSession::SetRxPortReuse() + +bool NormSession::SetTxPort(UINT16 txPort, bool enableReuse, const char* txAddress) +{ + tx_port = txPort; + tx_port_reuse = enableReuse; + bool result; + if (NULL != txAddress) + { + result = tx_address.ResolveFromString(txAddress); + // Automatically set port reuse if same tx/rx port nums but diff addr bindings + if (result) + { + if ((tx_port == GetRxPort()) && !tx_address.HostIsEqual(address)) + tx_port_reuse = rx_port_reuse = true; + } + } + else + { + tx_address.Invalidate(); + result = true; + } + return result; +} // end NormSession::SetTxPort() + +UINT16 NormSession::GetTxPort() const +{ + if (0 != tx_port) + return tx_port; + else if (tx_socket->IsOpen()) + return tx_socket->GetPort(); + else + return 0; +} // end NormSession::GetTxPort() + + +UINT16 NormSession::GetRxPort() const +{ + // Note that rx port _can_ be different than session port + // if the session destination address was changed via + // NormSession::SetAddress() + if (rx_socket.IsOpen()) + return rx_socket.GetPort(); + else + return address.GetPort(); +} // end NormSession::GetRxPort() + +void NormSession::SetTxOnly(bool txOnly, bool connectToSessionAddress) +{ + tx_only = txOnly; + tx_connect = connectToSessionAddress; + if (txOnly && IsOpen()) + { + if (IsReceiver()) StopReceiver(); + if (rx_socket.IsOpen()) rx_socket.Close(); +#ifdef ECN_SUPPORT + if (NULL != rx_cap) + { + rx_cap->Close(); + delete rx_cap; + rx_cap = NULL; + } +#endif // ECN_SUPPORT + // We connect tx_only session sockets when tx port + // reuse is set _and_ it is a unicast session + // (This makes sure unicast NACKs get back to the right tx_socket!) + if (connectToSessionAddress && !address.IsMulticast()) + { + if (!tx_socket->Connect(address)) + PLOG(PL_WARN, "NormSession::SetTxOnly() tx_socket connect() error: %s\n", ProtoSocket::GetErrorString()); + } + } +} // end NormSession::SetTxOnly() + +double NormSession::GetTxRate() +{ + posted_tx_rate_changed = false; + if (cc_enable && !cc_adjust) + { + // Return rate of CLR + const NormCCNode* clr = static_cast(cc_node_list.Head()); + return ((NULL != clr) ? 8.0 * clr->GetRate() : 0.0); + } + else + { + return (8.0*tx_rate); + } +} // 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) +{ + double interval = (double)msgSize / txRate; + + //double jitterMax = 0.05*interval; + //interval += UniformRand(jitterMax) - jitterMax/2.0; + return interval; // PERIODIC interval based on rate + //return PoissonRand(interval); +} + void NormSession::SetTxRateInternal(double txRate) { - if (!is_server) + if (!is_sender) { tx_rate = txRate; return; } if (txRate < 0.0) { - DMSG(0, "NormSession::SetTxRateInternal() invalid transmit rate!\n"); + PLOG(PL_FATAL, "NormSession::SetTxRateInternal() invalid transmit rate!\n"); return; } if (tx_timer.IsActive()) @@ -272,6 +564,7 @@ void NormSession::SetTxRateInternal(double txRate) if (txRate > 0.0) { double adjustInterval = (tx_rate/txRate) * tx_timer.GetTimeRemaining(); + //adjustInterval = PoissonRand(adjustInterval); if (adjustInterval > NORM_TICK_MIN) { tx_timer.SetInterval(adjustInterval); @@ -308,17 +601,26 @@ void NormSession::SetTxRateInternal(double txRate) } if (grttQuantizedOld != grtt_quantized) { - DMSG(4, "NormSession::SetTxRateInternal() node>%lu %s to new grtt to: %lf sec\n", + PLOG(PL_DEBUG, "NormSession::SetTxRateInternal() node>%lu %s to new grtt to: %lf sec\n", LocalNodeId(), (grttQuantizedOld < grtt_quantized) ? "increased" : "decreased", grtt_advertised); - Notify(NormController::GRTT_UPDATED, (NormServerNode*)NULL, (NormObject*)NULL); + Notify(NormController::GRTT_UPDATED, (NormSenderNode*)NULL, (NormObject*)NULL); + } + // wakeup grtt/cc probing if necessary + if (probe_reset) + { + probe_reset = false; + OnProbeTimeout(probe_timer); + if (!probe_timer.IsActive()) + ActivateTimer(probe_timer); } } } // end NormSession::SetTxRateInternal() void NormSession::SetTxRateBounds(double rateMin, double rateMax) { + posted_tx_rate_changed = false; // Make sure min <= max if ((rateMin >= 0.0) && (rateMax >= 0.0)) { @@ -351,34 +653,32 @@ void NormSession::SetTxRateBounds(double rateMin, double rateMax) } // end NormSession::SetTxRateBounds() -bool NormSession::StartServer(UINT16 instanceId, +bool NormSession::StartSender(UINT16 instanceId, UINT32 bufferSpace, UINT16 segmentSize, UINT16 numData, - UINT16 numParity, - const char* interfaceName) + UINT16 numParity) { if (!IsOpen()) { - if (!Open(interfaceName)) return false; + if (!Open()) return false; } - // (TBD) parameterize the object history depth if (!tx_table.Init(tx_cache_count_max)) { - DMSG(0, "NormSession::StartServer() tx_table.Init() error!\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() tx_table.Init() error!\n"); + StopSender(); return false; } if (!tx_pending_mask.Init(tx_cache_count_max, 0x0000ffff)) { - DMSG(0, "NormSession::StartServer() tx_pending_mask.Init() error!\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() tx_pending_mask.Init() error!\n"); + StopSender(); return false; } if (!tx_repair_mask.Init(tx_cache_count_max, 0x0000ffff)) { - DMSG(0, "NormSession::StartServer() tx_repair_mask.Init() error!\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() tx_repair_mask.Init() error!\n"); + StopSender(); return false; } @@ -398,48 +698,97 @@ bool NormSession::StartServer(UINT16 instanceId, if (!block_pool.Init(numBlocks, blockSize)) { - DMSG(0, "NormSession::StartServer() block_pool init error\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() block_pool init error\n"); + StopSender(); return false; } - if (!segment_pool.Init(numSegments, segmentSize + NormDataMsg::GetStreamPayloadHeaderLength() + 1)) + if (!segment_pool.Init(numSegments, segmentSize + NormDataMsg::GetStreamPayloadHeaderLength())) { - DMSG(0, "NormSession::StartServer() segment_pool init error\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() segment_pool init error\n"); + StopSender(); return false; } if (numParity) { if (NULL != encoder) delete encoder; - if (NULL == (encoder = new NormEncoderRS8a)) + + if (blockSize <= 255) { - DMSG(0, "NormSession::StartServer() new NormEncoderRS8a error: %s\n", GetErrorString()); - StopServer(); - return false; +#ifdef ASSUME_MDP_FEC + if (NULL == (encoder = new NormEncoderMDP)) + { + PLOG(PL_FATAL, "NormSession::StartSender() new NormEncoderMDP error: %s\n", GetErrorString()); + StopSender(); + return false; + } + fec_id = 129; + fec_m = 8; +#else + if (NULL == (encoder = new NormEncoderRS8)) + { + PLOG(PL_FATAL, "NormSession::StartSender() new NormEncoderRS8 error: %s\n", GetErrorString()); + StopSender(); + return false; + } + fec_id = 5; + fec_m = 8; +#endif } + else //if (blockSize <= 65535) + { + if (NULL == (encoder = new NormEncoderRS16)) + { + PLOG(PL_FATAL, "NormSession::StartSender() new NormEncoderRS16 error: %s\n", GetErrorString()); + StopSender(); + return false; + } + fec_id = 2; + fec_m = 16; + } + /*else + { + PLOG(PL_FATAL, "NormSession::StartSender() error: invalid FEC block size\n"); + StopSender(); + return false; + }*/ if (!encoder->Init(numData, numParity, segmentSize + NormDataMsg::GetStreamPayloadHeaderLength())) { - DMSG(0, "NormSession::StartServer() encoder init error\n"); - StopServer(); + PLOG(PL_FATAL, "NormSession::StartSender() encoder init error\n"); + StopSender(); return false; - } + } + } + else + { + // for now use RS8 fec_id with no parity (TBD - support "compact" null FEC type) + fec_id = 5; + fec_m = 8; + } + + // Initialize optional app-defined command state + cmd_count = cmd_length = 0; + if (NULL == (cmd_buffer = new char[segmentSize])) + { + PLOG(PL_FATAL, "NormSession::StartSender() error: unable to allocate cmd_buffer: %s\n", GetErrorString()); + StopSender(); + return false; } instance_id = instanceId; segment_size = segmentSize; - sent_accumulator = 0; + sent_accumulator.Reset(); nominal_packet_size = (double)segmentSize; data_active = false; ndata = numData; nparity = numParity; - is_server = true; + is_sender = true; flush_count = (GetTxRobustFactor() < 0) ? 0 : (GetTxRobustFactor() + 1); - if (cc_enable) + if (cc_enable && cc_adjust) { double txRate; if(tx_rate_min > 0.0) @@ -455,12 +804,11 @@ bool NormSession::StartServer(UINT16 instanceId, } if ((tx_rate_max >= 0.0) && (tx_rate > tx_rate_max)) txRate = tx_rate_max; - //tx_rate = txRate; // keep grtt at initial SetTxRateInternal(txRate); // adjusts grtt_advertised as needed } else { - SetTxRateInternal(tx_rate); // takes segment size into account, etc on server start + SetTxRateInternal(tx_rate); // takes segment size into account, etc on sender start } cc_slow_start = true; cc_active = false; @@ -472,14 +820,14 @@ bool NormSession::StartServer(UINT16 instanceId, { probe_reset = false; OnProbeTimeout(probe_timer); - ActivateTimer(probe_timer); + if (!probe_timer.IsActive()) + ActivateTimer(probe_timer); } - return true; -} // end NormSession::StartServer() +} // end NormSession::StartSender() -void NormSession::StopServer() +void NormSession::StopSender() { if (probe_timer.IsActive()) { @@ -491,6 +839,20 @@ void NormSession::StopServer() repair_timer.Deactivate(); tx_repair_pending = false; } + if (flush_timer.IsActive()) + flush_timer.Deactivate(); + if (cmd_timer.IsActive()) + cmd_timer.Deactivate(); + if (flow_control_timer.IsActive()) + flow_control_timer.Deactivate(); + + if (NULL != cmd_buffer) + { + delete[] cmd_buffer; + cmd_buffer = NULL; + cmd_length = 0; + } + if (NULL != encoder) { encoder->Destroy(); @@ -503,7 +865,7 @@ void NormSession::StopServer() while (!tx_table.IsEmpty()) { NormObject* obj = tx_table.Find(tx_table.RangeLo()); - ASSERT(obj); + ASSERT(NULL != obj); tx_table.Remove(obj); obj->Close(); obj->Release(); @@ -514,61 +876,104 @@ void NormSession::StopServer() segment_pool.Destroy(); tx_repair_mask.Destroy(); tx_pending_mask.Destroy(); - is_server = false; - if (!IsClient()) Close(); -} // end NormSession::StopServer() + is_sender = false; + if (!IsReceiver()) Close(); +} // end NormSession::StopSender() -bool NormSession::StartClient(unsigned long bufferSize, const char* interfaceName) +bool NormSession::StartReceiver(unsigned long bufferSize) { - if (!IsOpen()) + //if (tx_only) return false; + tx_only = false; + if (!rx_socket.IsOpen()) { - if (!Open(interfaceName)) return false; + if (!Open()) return false; } - is_client = true; - remote_server_buffer_size = bufferSize; + is_receiver = true; + remote_sender_buffer_size = bufferSize; return true; -} +} // end NormSession::StartReceiver() -void NormSession::StopClient() +void NormSession::StopReceiver() { - // Iterate server_tree and close/release server nodes - NormServerNode* serverNode = - static_cast(server_tree.GetRoot()); - while (serverNode) + // Iterate sender_tree and close/release sender nodes + NormSenderNode* senderNode = + static_cast(sender_tree.GetRoot()); + while (senderNode) { - server_tree.DetachNode(serverNode); - serverNode->Close(); - serverNode->Release(); - serverNode = - static_cast(server_tree.GetRoot()); + sender_tree.DetachNode(senderNode); + senderNode->Close(); + senderNode->Release(); + senderNode = + static_cast(sender_tree.GetRoot()); } - is_client = false; - if (!is_server) Close(); -} + is_receiver = false; + if (!is_sender) Close(); +} // end NormSession::StopReceiver() + +void NormSession::DeleteRemoteSender(NormSenderNode& senderNode) +{ + // TBD - confirm that "senderNode" is valid??? + sender_tree.DetachNode(&senderNode); + senderNode.Close(); + senderNode.Release(); +} // end NormSession::DeleteSender() + +bool NormSession::PreallocateRemoteSender(UINT16 segmentSize, + UINT16 numData, + UINT16 numParity) +{ + if (NULL != preset_sender) delete preset_sender; + preset_sender = new NormSenderNode(*this, NORM_NODE_ANY); + if (!preset_sender->Open(0)) + { + PLOG(PL_ERROR, "NormSession::PreallocateRemoteSender() error: NormSenderNode::Open() failure!\n"); + delete preset_sender; + preset_sender = NULL; + return false; + } + UINT16 blockSize = numData + numParity; + UINT8 fecId; + UINT8 fecM = 8; + if (blockSize > 255) + { + fecId = 2; + fecM = 16; + } + else + { + fecId = 5; + } + + if (!preset_sender->AllocateBuffers(fecId, 0, fecM, segmentSize, numData, numParity)) + { + PLOG(PL_ERROR, "NormSession::PreallocateRemoteSender() error: buffer allocation failure!\n"); + delete preset_sender; + preset_sender = NULL; + return false; + } + return true; +} // end NormSession::PreallocateRemoteSender() void NormSession::Serve() { // Only send new data when no other messages are queued for transmission if (!message_queue.IsEmpty()) return; - // Queue next server message + // Queue next sender message NormObjectId objectId; NormObject* obj = NULL; - if (ServerGetFirstPending(objectId)) + if (SenderGetFirstPending(objectId)) { obj = tx_table.Find(objectId); - ASSERT(obj); + ASSERT(NULL != obj); } - // (TBD) code to support app-defined commands will go here - /* - if (command_pending && !command_timer.IsActive()) + // If any app-defined command is pending, enqueue it for transmission + if ((0 != cmd_count) && !(cmd_timer.IsActive())) { - SenderQueueAppCommand() + // If command is enqueued + if (SenderQueueAppCmd()) return; } - - */ - if (watermark_pending && !flush_timer.IsActive()) { @@ -625,8 +1030,7 @@ void NormSession::Serve() nextObjectId = tx_repair_object_min; nextBlockId = tx_repair_block_min; nextSegmentId = tx_repair_segment_min; - - DMSG(8, "watermark>%hu:%lu:%hu check against repair index>%hu:%lu:%hu\n", + PLOG(PL_DETAIL, "watermark>%hu:%lu:%hu check against repair index>%hu:%lu:%hu\n", (UINT16)watermark_object_id, (UINT32)watermark_block_id, (UINT16)watermark_segment_id, (UINT16)nextObjectId, (UINT32)nextBlockId, (UINT16)nextSegmentId); } @@ -639,7 +1043,7 @@ void NormSession::Serve() (nextSegmentId > watermark_segment_id))))) { // The sender tx position is > watermark - if (ServerQueueWatermarkFlush()) + if (SenderQueueWatermarkFlush()) { watermark_active = true; return; @@ -649,21 +1053,38 @@ void NormSession::Serve() // (TBD) optionally return here to have ack collection temporarily // suspend forward progress of data transmission //return; + + // If the app has set the property to "truncated_flushing" because + // there is explicit positive acknowledge from everyone in the group + // (or unicast destination), we can safely provide an _early_ + // termination of flushing at this point iff: + // + // "(false == watermark_pending) && + // (NULL == obj) && + // (watermark_object_id == last_tx_object_id) + // + // These conditions indicate that the watermark ack has completed, + // there is no more data to send, _and_ the watermark was the + // ordinally highest object that has been enqueued .... + if (!watermark_pending) + { + // watermark flush just completed + if (watermark_flushes) + flush_count = GetTxRobustFactor(); + } } } else { // The sender tx position is < watermark - // Reset non-acked acking nodes since server has rewound + // Reset non-acked acking nodes since sender has rewound if (watermark_active) { watermark_active = false; NormNodeTreeIterator iterator(acking_node_tree); NormAckingNode* next; while ((next = static_cast(iterator.GetNextNode()))) - { next->ResetReqCount(GetTxRobustFactor()); - } } } } // end if (watermark_pending) @@ -673,7 +1094,7 @@ void NormSession::Serve() NormObjectMsg* msg = (NormObjectMsg*)GetMessageFromPool(); if (msg) { - if (obj->NextServerMsg(msg)) + if (obj->NextSenderMsg(msg)) { if (cc_enable && !data_active) { @@ -681,10 +1102,7 @@ void NormSession::Serve() if (probe_timer.IsActive()) { double elapsed = probe_timer.GetInterval() - probe_timer.GetTimeRemaining(); - const NormCCNode* clr = static_cast(cc_node_list.Head()); - double probeInterval = (clr && clr->IsActive()) ? - MIN(grtt_advertised, clr->GetRtt()) : - grtt_advertised; + double probeInterval = GetProbeInterval(); if (elapsed > probeInterval) probe_timer.SetInterval(0.0); else @@ -706,7 +1124,16 @@ void NormSession::Serve() // what has been written and conversely set some pending // state as calls to NormStreamObject::Write() are made. if (!obj->IsPending() && !obj->IsStream()) + { tx_pending_mask.Unset(obj->GetId()); + if (!tx_pending_mask.IsSet() && !posted_tx_queue_empty) + { + // Tell the app we would like to send more data ... + posted_tx_queue_empty = true; + Notify(NormController::TX_QUEUE_EMPTY, (NormSenderNode*)NULL, (NormObject*)NULL); + // (TBD) Was session deleted? + } + } } else { @@ -721,83 +1148,79 @@ void NormSession::Serve() { if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) { - ServerQueueFlush(); + SenderQueueFlush(); } else if (GetTxRobustFactor() == flush_count) { - DMSG(6, "NormSession::Serve() node>%lu server flush complete ...\n", + PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender flush complete ...\n", LocalNodeId()); flush_count++; + data_active = false; if (stream->IsClosing()) { stream->Close(); - Notify(NormController::TX_OBJECT_PURGED, (NormServerNode*)NULL, stream); + Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, stream); DeleteTxObject(stream); obj = NULL; } } } } - ASSERT(stream->IsPending() || stream->IsRepairPending() || stream->IsClosing()); + //ASSERT(stream->IsPending() || stream->IsRepairPending() || stream->IsClosing()); if (!posted_tx_queue_empty && !stream->IsClosing() && stream->IsPending()) // post if pending || !repair_timer.IsActive() || (repair_timer.GetRepeatCount() == 0) ??? { //data_active = false; posted_tx_queue_empty = true; - Notify(NormController::TX_QUEUE_EMPTY, (NormServerNode*)NULL, obj); + Notify(NormController::TX_QUEUE_EMPTY, (NormSenderNode*)NULL, obj); // (TBD) Was session deleted? return; } } else { - DMSG(0, "NormSession::Serve() pending non-stream obj, no message?.\n"); + PLOG(PL_ERROR, "NormSession::Serve() pending non-stream obj, no message?.\n"); ASSERT(repair_timer.IsActive()); } } } else { - DMSG(0, "NormSession::Serve() node>%lu Warning! message_pool empty.\n", + PLOG(PL_ERROR, "NormSession::Serve() node>%lu Warning! message_pool empty.\n", LocalNodeId()); } } else { // No pending objects or positive acknowledgement request - if (!posted_tx_queue_empty) - { - data_active = false; // (TBD) should we wait until the flush process completes before setting false??? - posted_tx_queue_empty = true; - Notify(NormController::TX_QUEUE_EMPTY, (NormServerNode*)NULL, (NormObject*)NULL); - // (TBD) Was session deleted? - return; - } if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) { // Queue flush message if (!tx_repair_pending) // don't queue flush if repair pending - ServerQueueFlush(); + SenderQueueFlush(); else - DMSG(8, "NormSession::Serve() node>%lu NORM_CMD(FLUSH) deferred by pending repairs ...\n", + PLOG(PL_DETAIL, "NormSession::Serve() node>%lu NORM_CMD(FLUSH) deferred by pending repairs ...\n", LocalNodeId()); } else if (GetTxRobustFactor() == flush_count) { - DMSG(6, "NormSession::Serve() node>%lu server flush complete ...\n", + PLOG(PL_TRACE, "NormSession::Serve() node>%lu sender flush complete ...\n", LocalNodeId()); Notify(NormController::TX_FLUSH_COMPLETED, - (NormServerNode*)NULL, + (NormSenderNode*)NULL, (NormObject*)NULL); flush_count++; + data_active = false; } } } // end NormSession::Serve() -void NormSession::ServerSetWatermark(NormObjectId objectId, +void NormSession::SenderSetWatermark(NormObjectId objectId, NormBlockId blockId, - NormSegmentId segmentId) + NormSegmentId segmentId, + bool overrideFlush) { + watermark_flushes = overrideFlush; watermark_pending = true; watermark_active = false; watermark_object_id = objectId; @@ -810,15 +1233,33 @@ void NormSession::ServerSetWatermark(NormObjectId objectId, int robustFactor = GetTxRobustFactor(); while ((next = iterator.GetNextNode())) static_cast(next)->Reset(robustFactor); - PromptServer(); -} // end Norm::ServerSetWatermark() + PromptSender(); +} // end NormSession::SenderSetWatermark() -void NormSession::ServerCancelWatermark() +void NormSession::SenderResetWatermark() +{ + NormNodeTreeIterator iterator(acking_node_tree); + NormNode* next; + int robustFactor = GetTxRobustFactor(); + while ((next = iterator.GetNextNode())) + { + NormAckingNode* node = static_cast(next); + if ((NORM_NODE_NONE == node->GetId()) || (!node->AckReceived())) + { + node->Reset(robustFactor); + watermark_pending = true; + watermark_active = false; + } + } + PromptSender(); +} // end NormSession::SenderResetWatermark() + +void NormSession::SenderCancelWatermark() { watermark_pending = false; -} // end NormSession::ServerCancelWatermark() +} // end NormSession::SenderCancelWatermark() -bool NormSession::ServerAddAckingNode(NormNodeId nodeId) +bool NormSession::SenderAddAckingNode(NormNodeId nodeId) { NormAckingNode* theNode = static_cast(acking_node_tree.FindNodeById(nodeId)); if (NULL == theNode) @@ -833,17 +1274,17 @@ bool NormSession::ServerAddAckingNode(NormNodeId nodeId) } else { - DMSG(0, "NormSession::AddAckingNode() new NormAckingNode error: %s\n", GetErrorString()); + PLOG(PL_ERROR, "NormSession::SenderAddAckingNode() new NormAckingNode error: %s\n", GetErrorString()); } } else { - DMSG(0, "NormSession::AddAckingNode() warning: node already in list!?\n"); + PLOG(PL_ERROR, "NormSession::SenderAddAckingNode() warning: node already in list!?\n"); } return true; } // end NormSession::AddAckingNode(NormNodeId nodeId) -void NormSession::ServerRemoveAckingNode(NormNodeId nodeId) +void NormSession::SenderRemoveAckingNode(NormNodeId nodeId) { NormAckingNode* theNode = static_cast(acking_node_tree.FindNodeById(nodeId)); @@ -854,7 +1295,7 @@ void NormSession::ServerRemoveAckingNode(NormNodeId nodeId) } } // end NormSession::RemoveAckingNode() -NormSession::AckingStatus NormSession::ServerGetAckingStatus(NormNodeId nodeId) +NormSession::AckingStatus NormSession::SenderGetAckingStatus(NormNodeId nodeId) { if (NORM_NODE_ANY == nodeId) { @@ -875,7 +1316,7 @@ NormSession::AckingStatus NormSession::ServerGetAckingStatus(NormNodeId nodeId) { NormAckingNode* theNode = static_cast(acking_node_tree.FindNodeById(nodeId)); - if (theNode) + if (NULL != theNode) { if (theNode->IsPending()) return ACK_PENDING; @@ -891,9 +1332,47 @@ NormSession::AckingStatus NormSession::ServerGetAckingStatus(NormNodeId nodeId) return ACK_INVALID; } } -} // end NormSession::ServerGetAckingStatus() +} // end NormSession::SenderGetAckingStatus() -bool NormSession::ServerQueueWatermarkFlush() + +bool NormSession::SenderGetNextAckingNode(NormNodeId& prevNodeId, AckingStatus* ackingStatus) +{ + NormNode* prevNode = NULL; + if (NORM_NODE_NONE != prevNodeId) + prevNode = acking_node_tree.FindNodeById(prevNodeId); + NormNodeTreeIterator iterator(acking_node_tree, prevNode); + NormAckingNode* nextNode = static_cast(iterator.GetNextNode()); + // Note we skip NORM_NODE_NONE even though it may be in the tree + // (This method only returns the id / status of _actual_ nodes) + // TBD - we could return NORM_NODE_ANY as a proxy id for a NORM_NODE_NONE entry + if ((NULL != nextNode) && (NORM_NODE_NONE == nextNode->GetId())) + nextNode = static_cast(iterator.GetNextNode()); + if (NULL != nextNode) + { + prevNodeId = nextNode->GetId(); + if (NULL != ackingStatus) + { + if (nextNode->IsPending()) + *ackingStatus = ACK_PENDING; + else if (NORM_NODE_NONE == nextNode->GetId()) + *ackingStatus = ACK_SUCCESS; + else if (nextNode->AckReceived()) + *ackingStatus = ACK_SUCCESS; + else + *ackingStatus = ACK_FAILURE; + } + return true; + } + else + { + prevNodeId = NORM_NODE_NONE; + if (NULL != ackingStatus) + *ackingStatus = ACK_INVALID; + return false; + } +} // end NormSession::SenderGetNextAckingNode() + +bool NormSession::SenderQueueWatermarkFlush() { if (flush_timer.IsActive()) return false; NormCmdFlushMsg* flush = static_cast(GetMessageFromPool()); @@ -905,7 +1384,6 @@ bool NormSession::ServerQueueWatermarkFlush() flush->SetBackoffFactor((unsigned char)backoff_factor); flush->SetGroupSize(gsize_quantized); flush->SetObjectId(watermark_object_id); - flush->SetFecBlockId(watermark_block_id); // _Attempt_ to set the fec_payload_id source block length field appropriately UINT16 blockLen; NormObject* obj = tx_table.Find(watermark_object_id); @@ -915,8 +1393,9 @@ bool NormSession::ServerQueueWatermarkFlush() blockLen = ndata; else blockLen = watermark_segment_id; - flush->SetFecBlockLen(blockLen); - flush->SetFecSymbolId(watermark_segment_id); + + flush->SetFecPayloadId(fec_id, watermark_block_id, watermark_segment_id, blockLen, fec_m); + NormNodeTreeIterator iterator(acking_node_tree); NormAckingNode* next; watermark_pending = false; @@ -947,7 +1426,7 @@ bool NormSession::ServerQueueWatermarkFlush() } else { - DMSG(8, "NormSession::ServeQueueWatermarkFlush() full cmd ...\n"); + PLOG(PL_FATAL, "NormSession::ServeQueueWatermarkFlush() full cmd ...\n"); nodeNone = NULL; break; } @@ -962,42 +1441,45 @@ bool NormSession::ServerQueueWatermarkFlush() } else { - DMSG(8, "NormSession::ServeQueueWatermarkFlush() full cmd ...\n"); + PLOG(PL_DETAIL, "NormSession::ServeQueueWatermarkFlush() full cmd ...\n"); } } if (watermark_pending) { - if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) - flush_count++; + + // (TBD) we should increment the "flush_count" here only iff the watemark + // corresponds to our "last_tx_object_id", etc + //if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) + // flush_count++; QueueMessage(flush); - DMSG(8, "NormSession::ServeQueueWatermarkFlush() node>%lu cmd queued ...\n", + PLOG(PL_DETAIL, "NormSession::ServeQueueWatermarkFlush() node>%lu cmd queued ...\n", LocalNodeId()); } else if (NULL != acking_node_tree.GetRoot()) { ReturnMessageToPool(flush); - DMSG(4, "NormSession::ServeQueueWatermarkFlush() node>%lu watermark ack finished.\n"); - Notify(NormController::TX_WATERMARK_COMPLETED, (NormServerNode*)NULL, (NormObject*)NULL); + PLOG(PL_DEBUG, "NormSession::ServeQueueWatermarkFlush() node>%lu watermark ack finished.\n"); + Notify(NormController::TX_WATERMARK_COMPLETED, (NormSenderNode*)NULL, (NormObject*)NULL); return false; } else { ReturnMessageToPool(flush); - DMSG(2, "NormSession::ServeQueueWatermarkFlush() node>%lu no acking nodes specified?!\n"); + PLOG(PL_INFO, "NormSession::ServeQueueWatermarkFlush() node>%lu no acking nodes specified?!\n"); return false; } } else { - DMSG(0, "NormSession::ServerQueueWatermarkRequest() node>%lu message_pool exhausted! (couldn't req)\n", + PLOG(PL_ERROR, "NormSession::SenderQueueWatermarkRequest() node>%lu message_pool exhausted! (couldn't req)\n", LocalNodeId()); } flush_timer.SetInterval(2*grtt_advertised); ActivateTimer(flush_timer); return true; -} // end NormSession::ServerQueueWatermarkFlush() +} // end NormSession::SenderQueueWatermarkFlush() -void NormSession::ServerQueueFlush() +void NormSession::SenderQueueFlush() { // (TBD) Don't enqueue a new flush if there is already one in our tx_queue! if (flush_timer.IsActive()) return; @@ -1029,18 +1511,18 @@ void NormSession::ServerQueueFlush() flush->SetBackoffFactor((unsigned char)backoff_factor); flush->SetGroupSize(gsize_quantized); flush->SetObjectId(objectId); - flush->SetFecBlockId(blockId); - flush->SetFecBlockLen(obj->GetBlockSize(blockId)); - flush->SetFecSymbolId(segmentId); + + flush->SetFecPayloadId(fec_id, blockId, segmentId, obj->GetBlockSize(blockId), fec_m); + QueueMessage(flush); if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) flush_count++; - DMSG(4, "NormSession::ServerQueueFlush() node>%lu, flush queued (flush_count:%u)...\n", + PLOG(PL_DEBUG, "NormSession::SenderQueueFlush() node>%lu, flush queued (flush_count:%u)...\n", LocalNodeId(), flush_count); } else { - DMSG(0, "NormSession::ServerQueueFlush() node>%lu message_pool exhausted! (couldn't flush)\n", + PLOG(PL_ERROR, "NormSession::SenderQueueFlush() node>%lu message_pool exhausted! (couldn't flush)\n", LocalNodeId()); } @@ -1054,27 +1536,27 @@ void NormSession::ServerQueueFlush() // unless the app does bad things like "cancel" all of its tx objects ... // Maybe we shouldn't send anything if we have no pending tx objects? No need to flush, etc // if all tx object state is gone ... - if (ServerQueueSquelch(next_tx_object_id)) + if (SenderQueueSquelch(next_tx_object_id)) { if ((GetTxRobustFactor() < 0) || (flush_count < GetTxRobustFactor())) flush_count++; - DMSG(4, "NormSession::ServerQueueFlush() node>%lu squelch queued (flush_count:%u)...\n", + PLOG(PL_DEBUG, "NormSession::SenderQueueFlush() node>%lu squelch queued (flush_count:%u)...\n", LocalNodeId(), flush_count); } else { - DMSG(0, "NormSession::ServerQueueFlush() warning: node>%lu unable to queue squelch\n", + PLOG(PL_ERROR, "NormSession::SenderQueueFlush() warning: node>%lu unable to queue squelch\n", LocalNodeId()); } } flush_timer.SetInterval(2*grtt_advertised); ActivateTimer(flush_timer); -} // end NormSession::ServerQueueFlush() +} // end NormSession::SenderQueueFlush() bool NormSession::OnFlushTimeout(ProtoTimer& /*theTimer*/) { flush_timer.Deactivate(); - PromptServer(); + PromptSender(); return false; } // NormSession::OnFlushTimeout() @@ -1111,21 +1593,21 @@ NormFileObject* NormSession::QueueTxFile(const char* path, const char* infoPtr, UINT16 infoLen) { - if (!IsServer()) + if (!IsSender()) { - DMSG(0, "NormSession::QueueTxFile() Error: server is closed\n"); + PLOG(PL_FATAL, "NormSession::QueueTxFile() Error: sender is closed\n"); return NULL; } - NormFileObject* file = new NormFileObject(*this, (NormServerNode*)NULL, next_tx_object_id); + NormFileObject* file = new NormFileObject(*this, (NormSenderNode*)NULL, next_tx_object_id); if (!file) { - DMSG(0, "NormSession::QueueTxFile() new file object error: %s\n", + PLOG(PL_FATAL, "NormSession::QueueTxFile() new file object error: %s\n", GetErrorString()); return NULL; } if (!file->Open(path, infoPtr, infoLen)) { - DMSG(0, "NormSession::QueueTxFile() file open error\n"); + PLOG(PL_FATAL, "NormSession::QueueTxFile() file open error\n"); file->Release(); return NULL; } @@ -1146,21 +1628,21 @@ NormDataObject* NormSession::QueueTxData(const char* dataPtr, const char* infoPtr, UINT16 infoLen) { - if (!IsServer()) + if (!IsSender()) { - DMSG(0, "NormSession::QueueTxData() Error: server is closed\n"); + PLOG(PL_FATAL, "NormSession::QueueTxData() Error: sender is closed\n"); return NULL; } - NormDataObject* obj = new NormDataObject(*this, (NormServerNode*)NULL, next_tx_object_id); + NormDataObject* obj = new NormDataObject(*this, (NormSenderNode*)NULL, next_tx_object_id); if (!obj) { - DMSG(0, "NormSession::QueueTxData() new data object error: %s\n", + PLOG(PL_FATAL, "NormSession::QueueTxData() new data object error: %s\n", GetErrorString()); return NULL; } if (!obj->Open((char*)dataPtr, dataLen, false, infoPtr, infoLen)) { - DMSG(0, "NormSession::QueueTxData() object open error\n"); + PLOG(PL_FATAL, "NormSession::QueueTxData() object open error\n"); obj->Release(); return NULL; } @@ -1182,21 +1664,21 @@ NormStreamObject* NormSession::QueueTxStream(UINT32 bufferSize, const char* infoPtr, UINT16 infoLen) { - if (!IsServer()) + if (!IsSender()) { - DMSG(0, "NormSession::QueueTxStream() Error: server is closed\n"); + PLOG(PL_FATAL, "NormSession::QueueTxStream() Error: sender is closed\n"); return NULL; } - NormStreamObject* stream = new NormStreamObject(*this, (NormServerNode*)NULL, next_tx_object_id); + NormStreamObject* stream = new NormStreamObject(*this, (NormSenderNode*)NULL, next_tx_object_id); if (!stream) { - DMSG(0, "NormSession::QueueTxStream() new stream object error: %s\n", + PLOG(PL_FATAL, "NormSession::QueueTxStream() new stream object error: %s\n", GetErrorString()); return NULL; } if (!stream->Open(bufferSize, doubleBuffer, infoPtr, infoLen)) { - DMSG(0, "NormSession::QueueTxStream() stream open error\n"); + PLOG(PL_FATAL, "NormSession::QueueTxStream() stream open error\n"); stream->Release(); return NULL; } @@ -1218,22 +1700,22 @@ NormStreamObject* NormSession::QueueTxStream(UINT32 bufferSize, #ifdef SIMULATE NormSimObject* NormSession::QueueTxSim(unsigned long objectSize) { - if (!IsServer()) + if (!IsSender()) { - DMSG(0, "NormSession::QueueTxSim() Error: server is closed\n"); + PLOG(PL_FATAL, "NormSession::QueueTxSim() Error: sender is closed\n"); return NULL; } NormSimObject* simObject = new NormSimObject(*this, NULL, next_tx_object_id); if (!simObject) { - DMSG(0, "NormSession::QueueTxSim() new sim object error: %s\n", + PLOG(PL_FATAL, "NormSession::QueueTxSim() new sim object error: %s\n", GetErrorString()); return NULL; } if (!simObject->Open(objectSize)) { - DMSG(0, "NormSession::QueueTxSim() open error\n"); + PLOG(PL_FATAL, "NormSession::QueueTxSim() open error\n"); simObject->Release(); return NULL; } @@ -1251,9 +1733,9 @@ NormSimObject* NormSession::QueueTxSim(unsigned long objectSize) bool NormSession::QueueTxObject(NormObject* obj) { - if (!IsServer()) + if (!IsSender()) { - DMSG(0, "NormSession::QueueTxObject() non-sender session error!?\n"); + PLOG(PL_FATAL, "NormSession::QueueTxObject() non-sender session error!?\n"); return false; } @@ -1277,38 +1759,51 @@ bool NormSession::QueueTxObject(NormObject* obj) NormObject* oldest = tx_table.Find(tx_table.RangeLo()); if (oldest->IsRepairPending() || oldest->IsPending()) { - DMSG(0, "NormSession::QueueTxObject() all held objects repair pending\n"); + PLOG(PL_DEBUG, "NormSession::QueueTxObject() all held objects repair pending:%d (repair active:%d) pending:%d\n", + oldest->IsRepairPending(), repair_timer.IsActive(), oldest->IsPending()); posted_tx_queue_empty = false; return false; } - else + else { - Notify(NormController::TX_OBJECT_PURGED, (NormServerNode*)NULL, oldest); - DeleteTxObject(oldest); + double delay = GetFlowControlDelay() - oldest->GetNackAge(); + if (delay < 1.0e-06) + { + Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, oldest); + DeleteTxObject(oldest); + } + else + { + PLOG(PL_DEBUG, "NormSession::QueueTxObject() asserting flow control for object delay:%lf sec\n", delay); + // TBD - flow control as should allow for TX_QUEUE_VACANCY posting for session + ActivateFlowControl(delay, oldest->GetId(), NormController::TX_QUEUE_EMPTY); + posted_tx_queue_empty = false; + return false; + } } newCount = tx_table.GetCount() + 1; } // Attempt to queue the object (note it gets "retained" by the tx_table) if (!tx_table.Insert(obj)) { - DMSG(0, "NormSession::QueueTxObject() tx_table insert error\n"); + PLOG(PL_FATAL, "NormSession::QueueTxObject() tx_table insert error\n"); ASSERT(0); return false; } tx_pending_mask.Set(obj->GetId()); ASSERT(tx_pending_mask.Test(obj->GetId())); next_tx_object_id++; - TouchServer(); + TouchSender(); return true; } // end NormSession::QueueTxObject() bool NormSession::RequeueTxObject(NormObject* obj) { - ASSERT(obj); + ASSERT(NULL != obj); if (obj->IsStream()) { // (TBD) allow buffered stream to be reset? - DMSG(0, "NormSession::RequeueTxObject() error: can't requeue NORM_OBJECT_STREAM\n"); + PLOG(PL_FATAL, "NormSession::RequeueTxObject() error: can't requeue NORM_OBJECT_STREAM\n"); return false; } NormObjectId objectId = obj->GetId(); @@ -1317,32 +1812,33 @@ bool NormSession::RequeueTxObject(NormObject* obj) if (tx_pending_mask.Set(objectId)) { obj->TxReset(0, true); + TouchSender(); return true; } else { - DMSG(0, "NormSession::RequeueTxObject() error: couldn't set object as pending\n"); + PLOG(PL_FATAL, "NormSession::RequeueTxObject() error: couldn't set object as pending\n"); return false; } } else { - DMSG(0, "NormSession::RequeueTxObject() error: couldn't find object\n"); + PLOG(PL_FATAL, "NormSession::RequeueTxObject() error: couldn't find object\n"); return false; } } // end NormSession::RequeueTxObject() void NormSession::DeleteTxObject(NormObject* obj) { - ASSERT(obj); + ASSERT(NULL != obj); if (tx_table.Remove(obj)) { NormObjectId objectId = obj->GetId(); tx_pending_mask.Unset(objectId); tx_repair_mask.Unset(objectId); + obj->Close(); + obj->Release(); } - obj->Close(); - obj->Release(); } // end NormSession::DeleteTxObject() bool NormSession::SetTxCacheBounds(NormObjectSize sizeMax, @@ -1356,7 +1852,7 @@ bool NormSession::SetTxCacheBounds(NormObjectSize sizeMax, tx_cache_count_max = (countMax > countMin) ? countMax : countMin; if (tx_cache_count_max < 1) tx_cache_count_max = 1; - if (IsServer()) + if (IsSender()) { // Trim/resize the tx_table and tx masks as needed unsigned long count = tx_table.GetCount(); @@ -1366,8 +1862,8 @@ bool NormSession::SetTxCacheBounds(NormObjectSize sizeMax, { // Remove oldest (hopefully non-pending ) object NormObject* oldest = tx_table.Find(tx_table.RangeLo()); - ASSERT(oldest); - Notify(NormController::TX_OBJECT_PURGED, (NormServerNode*)NULL, oldest); + ASSERT(NULL != oldest); + Notify(NormController::TX_OBJECT_PURGED, (NormSenderNode*)NULL, oldest); DeleteTxObject(oldest); count = tx_table.GetCount(); } @@ -1395,7 +1891,7 @@ bool NormSession::SetTxCacheBounds(NormObjectSize sizeMax, return result; } // end NormSession::SetTxCacheBounds() -NormBlock* NormSession::ServerGetFreeBlock(NormObjectId objectId, +NormBlock* NormSession::SenderGetFreeBlock(NormObjectId objectId, NormBlockId blockId) { // First, try to get one from our block pool @@ -1445,65 +1941,136 @@ NormBlock* NormSession::ServerGetFreeBlock(NormObjectId objectId, } } return b; -} // end NormSession::ServerGetFreeBlock() +} // end NormSession::SenderGetFreeBlock() -char* NormSession::ServerGetFreeSegment(NormObjectId objectId, +char* NormSession::SenderGetFreeSegment(NormObjectId objectId, NormBlockId blockId) { while (segment_pool.IsEmpty()) { - NormBlock* b = ServerGetFreeBlock(objectId, blockId); + NormBlock* b = SenderGetFreeBlock(objectId, blockId); if (b) block_pool.Put(b); else return NULL; } return segment_pool.Get(); -} // end NormSession::ServerGetFreeSegment() +} // end NormSession::SenderGetFreeSegment() void NormSession::TxSocketRecvHandler(ProtoSocket& /*theSocket*/, - ProtoSocket::Event /*theEvent*/) + ProtoSocket::Event theEvent) { - NormMsg msg; - unsigned int msgLength = NormMsg::MAX_SIZE; - while (tx_socket->RecvFrom(msg.AccessBuffer(), - msgLength, - msg.AccessAddress())) + if (ProtoSocket::RECV == theEvent) { - if (msg.InitFromBuffer(msgLength)) + NormMsg msg; + unsigned int msgLength = NormMsg::MAX_SIZE; + while (tx_socket->RecvFrom(msg.AccessBuffer(), + msgLength, + msg.AccessAddress())) { - HandleReceiveMessage(msg, true); - msgLength = NormMsg::MAX_SIZE; + if (0 == msgLength) break; // no more data to read + if (msg.InitFromBuffer(msgLength)) + { + HandleReceiveMessage(msg, true); + msgLength = NormMsg::MAX_SIZE; + } + else + { + PLOG(PL_ERROR, "NormSession::TxSocketRecvHandler() warning: received bad message\n"); + } } - else + } + else if (ProtoSocket::SEND == theEvent) + { + // This is a little cheesy, but ... + tx_socket->StopOutputNotification(); + if (tx_timer.IsActive()) tx_timer.Deactivate(); + if (OnTxTimeout(tx_timer)) { - DMSG(0, "NormSession::TxSocketRecvHandler() warning: received bad message\n"); + if (!tx_timer.IsActive()) ActivateTimer(tx_timer); } } } // end NormSession::TxSocketRecvHandler() +//#define RX_MEASURE_ONLY +#ifdef RX_MEASURE_ONLY +static bool rxMeasureInit = true; +struct timeval rxMeasureRefTime; +unsigned int rxMeasurePktCount = 0; +unsigned int rxMeasurePktTotal = 0; +unsigned int rxMeasureByteTotal = 0; +UINT16 rxMeasureSeqPrev = 0; +int rxMeasureGapMax = 0; +#endif // RX_MEASURE_ONLY -void NormSession::RxSocketRecvHandler(ProtoSocket& /*theSocket*/, +void NormSession::RxSocketRecvHandler(ProtoSocket& theSocket, ProtoSocket::Event /*theEvent*/) { unsigned int recvCount = 0; NormMsg msg; unsigned int msgLength = NormMsg::MAX_SIZE; - while (rx_socket.RecvFrom(msg.AccessBuffer(), + while (theSocket.RecvFrom(msg.AccessBuffer(), msgLength, msg.AccessAddress())) { + if (0 == msgLength) break; if (msg.InitFromBuffer(msgLength)) { - HandleReceiveMessage(msg, false); +#ifdef RX_MEASURE_ONLY + // Measure rx rate / loss stats only + struct timeval currentTime; + ProtoSystemTime(currentTime); + UINT16 seq = msg.GetSequence(); + if (rxMeasureInit) + { + rxMeasureRefTime = currentTime; + rxMeasureSeqPrev = seq; + rxMeasurePktCount = rxMeasurePktTotal = 1; + rxMeasureByteTotal = msgLength; + rxMeasureInit = false; + return; + } + int seqDelta = (int)seq - (int)rxMeasureSeqPrev; + ASSERT(seqDelta > 0); + + rxMeasurePktTotal += seqDelta; // total should have received. + rxMeasurePktCount++; // total actually received + rxMeasureByteTotal += msgLength; + + if (seqDelta > rxMeasureGapMax) + rxMeasureGapMax = seqDelta; + + int deltaSec = currentTime.tv_sec - rxMeasureRefTime.tv_sec; + if (deltaSec >= 10) + { + if (currentTime.tv_usec > rxMeasureRefTime.tv_usec) + deltaSec += 1.0e-06 * (double)(currentTime.tv_usec - rxMeasureRefTime.tv_usec); + else + deltaSec -= 1.0e-06 * (double)(rxMeasureRefTime.tv_usec - currentTime.tv_usec); + double rxRate = (8.0/1000.0) * (double)rxMeasureByteTotal / (double)deltaSec; + double rxLoss = 100.0 * (1.0 - (double)rxMeasurePktCount / (double)rxMeasurePktTotal); + + rxMeasureRefTime = currentTime; + rxMeasureByteTotal = rxMeasurePktCount = rxMeasurePktTotal = rxMeasureGapMax = 0; + } + rxMeasureSeqPrev = seq; + return; +#endif // RX_MEASURE_ONLY + bool ecnStatus = false; +#ifdef SIMULATE + ecnStatus = theSocket.GetEcnStatus(); + //if (ecnStatus) TRACE("NORM RECEIVED PACKET W/ ECN BIT SET!!!!!\n"); +#endif // SIMULATE + HandleReceiveMessage(msg, false, ecnStatus); msgLength = NormMsg::MAX_SIZE; } else { - DMSG(0, "NormSession::RxSocketRecvHandler() warning: received bad message\n"); + PLOG(PL_ERROR, "NormSession::RxSocketRecvHandler() warning: received bad message\n"); } // If our system gets very busy reading sockets, we should occasionally - // execute any timeouts to keep protocol operation smooth + // execute any timeouts to keep protocol operation smooth (i.e., sending feedback) + // TBD - perhaps this should be time based if (++recvCount >= 100) { break; @@ -1513,10 +2080,126 @@ void NormSession::RxSocketRecvHandler(ProtoSocket& /*theSocket*/, } } // end NormSession::RxSocketRecvHandler() + +#ifndef SIMULATE +void NormSession::OnPktCapture(ProtoChannel& theChannel, + ProtoChannel::Notification notifyType) +{ + // We only care about NOTIFY_INPUT events (all we should get anyway) + if (ProtoChannel::NOTIFY_INPUT != notifyType) return; + while(1) + { + ProtoCap::Direction direction; + // Note: We offset the buffer by 2 bytes since Ethernet header is 14 bytes + // (i.e. not a multiple of 4 (sizeof(UINT32)) + // This gives us a properly aligned buffer for 32-bit aligned IP packets + // (The 256*sizeof(UINT32) bytes are for potential "smfPkt" message header use) + const int BUFFER_MAX = 4096; + UINT32 alignedBuffer[BUFFER_MAX/sizeof(UINT32)]; + UINT16* ethBuffer = ((UINT16*)alignedBuffer) + 1; // offset by 2-bytes so IP content is 32-bit aligned + UINT32* ipBuffer = alignedBuffer + 4; // offset by ETHER header size + 2 bytes + unsigned int numBytes = (sizeof(UINT32) * (BUFFER_MAX/sizeof(UINT32))) - 2; + + ProtoCap& cap = static_cast(theChannel); + + if (!cap.Recv((char*)ethBuffer, numBytes, &direction)) + { + PLOG(PL_ERROR, "NormSession::OnPktCapture() ProtoCap::Recv() error\n"); + break; + } + 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); + if (!ethPkt.InitFromBuffer(numBytes)) + { + PLOG(PL_ERROR, "NormSession::OnPktCapture() error: bad Ether frame\n"); + continue; + } + // Only process IP packets (skip others) + UINT16 ethType = ethPkt.GetType(); + if ((ethType != 0x0800) && (ethType != 0x86dd)) + continue; // go read next packet + // Map ProtoPktIP instance into buffer and init for processing. + ProtoPktIP ipPkt(ipBuffer, BUFFER_MAX - 16); + if (!ipPkt.InitFromBuffer(ethPkt.GetPayloadLength())) + { + PLOG(PL_ERROR, "NormSession::OnPktCapture() error: bad IP packet\n"); + continue; + } + + // Does this packet match any of our valid destination addrs? + ProtoAddress dstIp; + ProtoAddress srcIp; + ProtoSocket::EcnStatus ecnStatus = ProtoSocket::ECN_NONE; + switch (ipPkt.GetVersion()) + { + case 4: + { + ProtoPktIPv4 ip4Pkt(ipPkt); + ip4Pkt.GetDstAddr(dstIp); + ip4Pkt.GetSrcAddr(srcIp); + ecnStatus = (ProtoSocket::EcnStatus)(ip4Pkt.GetTOS() & ProtoSocket::ECN_CE); + break; + } + case 6: + { + ProtoPktIPv6 ip6Pkt(ipPkt); + ip6Pkt.GetDstAddr(dstIp); + ip6Pkt.GetSrcAddr(srcIp); + ecnStatus = (ProtoSocket::EcnStatus)(ip6Pkt.GetTrafficClass() & ProtoSocket::ECN_CE); + break; + } + default: + PLOG(PL_ERROR, "NormSession::OnPktCapture() error: recvd IP packet w/ bad version number\n"); + continue; // go read next packet + } + if (!dst_addr_list.Contains(dstIp)) + continue; // not a matching dst addr, go read next packet + // Is this a UDP packet for our session dst port? + int dstPort = -1; + ProtoPktUDP udpPkt; + if (udpPkt.InitFromPacket(ipPkt)) + dstPort = udpPkt.GetDstPort(); + //if (dstPort != address.GetPort()) + if (dstPort != rx_socket.GetPort()) + continue; // not a UDP packet for our session, go read next packet + // If our rx_socket is "connected", make sure source addr/port matches + srcIp.SetPort(udpPkt.GetSrcPort()); + if (rx_connect_addr.IsValid() && !rx_connect_addr.IsEqual(srcIp)) + continue; // not from the remote tx_socket to which we are "connected" + + // IMPORTANT NOTE: We ignore the checksum for OUTBOUND packets since these + // are often computed by the Ethernet hardware these days + if ((ProtoCap::INBOUND == direction) && !udpPkt.ChecksumIsValid(ipPkt)) + { + PLOG(PL_WARN, "NormSession::OnPktCapture() error: recvd UDP packet w/ bad checksum: %04x (computed: %04x)\n", + (UINT16)udpPkt.GetChecksum(), udpPkt.ComputeChecksum(ipPkt)); + continue; // go read next packet + } + + // TBD - we can avoid this copy + NormMsg msg; + if (msg.CopyFromBuffer((const char*)udpPkt.GetPayload(), udpPkt.GetPayloadLength())) + { + + msg.AccessAddress() = srcIp; + HandleReceiveMessage(msg, !dstIp.IsMulticast(), (ProtoSocket::ECN_CE == ecnStatus)); + } + else + { + PLOG(PL_WARN, "NormSession::OnPktCapture() error: recvd bad NORM packet?!\n"); + } + } // end while(1) +} // end NormSession::OnPktCapture() +#endif // !SIMULATE + +// TBD - move this to its own cpp file??? void NormTrace(const struct timeval& currentTime, NormNodeId localId, const NormMsg& msg, - bool sent) + bool sent, + UINT8 fecM) { static const char* MSG_NAME[] = { @@ -1563,90 +2246,103 @@ void NormTrace(const struct timeval& currentTime, timeStruct.tm_hour = timeStruct.tm_hour % 24; struct tm* ct = &timeStruct; #else - struct tm* ct = gmtime((time_t*)¤tTime.tv_sec); + time_t secs = (time_t)currentTime.tv_sec; + struct tm* ct = gmtime(&secs); #endif // if/else _WIN32_WCE - DMSG(0, "trace>%02d:%02d:%02d.%06lu node>%lu %s>%s ", - ct->tm_hour, ct->tm_min, ct->tm_sec, currentTime.tv_usec, - (UINT32)localId, status, addr.GetHostString()); + PLOG(PL_ALWAYS, "trace>%02d:%02d:%02d.%06lu ", + (int)ct->tm_hour, (int)ct->tm_min, (int)ct->tm_sec, (unsigned int)currentTime.tv_usec); + PLOG(PL_ALWAYS, "node>%lu %s>%s ", (UINT32)localId, status, addr.GetHostString()); + bool clrFlag = false; switch (msgType) { case NormMsg::INFO: { const NormInfoMsg& info = (const NormInfoMsg&)msg; - DMSG(0, "inst>%hu seq>%hu INFO obj>%hu ", + PLOG(PL_ALWAYS, "inst>%hu seq>%hu INFO obj>%hu ", info.GetInstanceId(), seq, (UINT16)info.GetObjectId()); break; } case NormMsg::DATA: { const NormDataMsg& data = (const NormDataMsg&)msg; - DMSG(0, "inst>%hu seq>%hu %s obj>%hu blk>%lu seg>%hu ", + PLOG(PL_ALWAYS, "inst>%hu seq>%hu DATA obj>%hu blk>%lu seg>%hu ", data.GetInstanceId(), seq, - data.IsData() ? "DATA" : "PRTY", + //data.IsData() ? "DATA" : "PRTY", (UINT16)data.GetObjectId(), - (UINT32)data.GetFecBlockId(), - (UINT16)data.GetFecSymbolId()); + (UINT32)data.GetFecBlockId(fecM), + (UINT16)data.GetFecSymbolId(fecM)); + + if (data.IsStream()) + { + UINT32 offset = NormDataMsg::ReadStreamPayloadOffset(data.GetPayload()); + PLOG(PL_ALWAYS, "offset>%lu ", offset); + } + /* if (data.IsData() && data.IsStream()) { //if (NormDataMsg::StreamPayloadFlagIsSet(data.GetPayload(), NormDataMsg::FLAG_MSG_START)) UINT16 msgStartOffset = NormDataMsg::ReadStreamPayloadMsgStart(data.GetPayload()); if (0 != msgStartOffset) { - DMSG(0, "start word>%hu ", msgStartOffset - 1); + PLOG(PL_ALWAYS, "start word>%hu ", msgStartOffset - 1); } //if (NormDataMsg::StreamPayloadFlagIsSet(data.GetPayload(), NormDataMsg::FLAG_STREAM_END)) if (0 == NormDataMsg::ReadStreamPayloadLength(data.GetPayload())) - DMSG(0, "(stream end) "); + PLOG(PL_ALWAYS, "(stream end) "); } + */ break; } case NormMsg::CMD: { const NormCmdMsg& cmd = static_cast(msg); NormCmdMsg::Flavor flavor = cmd.GetFlavor(); - DMSG(0, "inst>%hu seq>%hu %s ", cmd.GetInstanceId(), seq, CMD_NAME[flavor]); + PLOG(PL_ALWAYS, "inst>%hu seq>%hu %s ", cmd.GetInstanceId(), seq, CMD_NAME[flavor]); switch (flavor) { case NormCmdMsg::ACK_REQ: { int index = ((const NormCmdAckReqMsg&)msg).GetAckType(); index = MIN(index, 3); - DMSG(0, "(%s) ", REQ_NAME[index]); + PLOG(PL_ALWAYS, "(%s) ", REQ_NAME[index]); break; } case NormCmdMsg::SQUELCH: { const NormCmdSquelchMsg& squelch = static_cast(msg); - DMSG(0, " obj>%hu blk>%lu seg>%hu ", + PLOG(PL_ALWAYS, " obj>%hu blk>%lu seg>%hu ", (UINT16)squelch.GetObjectId(), - (UINT32)squelch.GetFecBlockId(), - (UINT16)squelch.GetFecSymbolId()); + (UINT32)squelch.GetFecBlockId(fecM), + (UINT16)squelch.GetFecSymbolId(fecM)); break; } case NormCmdMsg::FLUSH: { const NormCmdFlushMsg& flush = static_cast(msg); - DMSG(0, " obj>%hu blk>%lu seg>%hu ", + PLOG(PL_ALWAYS, " obj>%hu blk>%lu seg>%hu ", (UINT16)flush.GetObjectId(), - (UINT32)flush.GetFecBlockId(), - (UINT16)flush.GetFecSymbolId()); + (UINT32)flush.GetFecBlockId(fecM), + (UINT16)flush.GetFecSymbolId(fecM)); + + if (0 != flush.GetAckingNodeCount()) + PLOG(PL_ALWAYS, "(WATERMARK) "); // ACK requested break; } case NormCmdMsg::CC: { const NormCmdCCMsg& cc = static_cast(msg); - DMSG(0, " seq>%u ", cc.GetCCSequence()); + PLOG(PL_ALWAYS, " seq>%u ", cc.GetCCSequence()); NormHeaderExtension ext; while (cc.GetNextExtension(ext)) { if (NormHeaderExtension::CC_RATE == ext.GetType()) { UINT16 sendRate = ((NormCCRateExtension&)ext).GetSendRate(); - DMSG(0, " rate>%f ", 8.0e-03 * NormUnquantizeRate(sendRate)); + PLOG(PL_ALWAYS, " rate>%f ", 8.0e-03 * NormUnquantizeRate(sendRate)); break; } } @@ -1671,51 +2367,105 @@ void NormTrace(const struct timeval& currentTime, break; } } - DMSG(0, "%s ", MSG_NAME[msgType]); + if (NormMsg::ACK == msgType) + { + const NormAckMsg& ack = static_cast(msg); + if (NormAck::FLUSH == ack.GetAckType()) + { + const NormAckFlushMsg& flushAck = static_cast(ack); + PLOG(PL_ALWAYS, "ACK(FLUSH) obj>%hu blk>%lu seg>%hu ", + (UINT16)flushAck.GetObjectId(), + (UINT32)flushAck.GetFecBlockId(fecM), + (UINT16)flushAck.GetFecSymbolId(fecM)); + } + else if (NormAck::CC == ack.GetAckType()) + { + PLOG(PL_ALWAYS, "ACK(CC) "); + } + else + { + PLOG(PL_ALWAYS, "ACK(XXX) "); + } + } + else + { + PLOG(PL_ALWAYS, "NACK "); + // TBD - provide deeper NACK inspection? + } break; } default: - DMSG(0, "%s ", MSG_NAME[msgType]); + PLOG(PL_ALWAYS, "%s ", MSG_NAME[msgType]); break; - } - DMSG(0, "len>%hu %s\n", length, clrFlag ? "(CLR)" : ""); + } // end switch (msgType) + PLOG(PL_ALWAYS, "len>%hu %s\n", length, clrFlag ? "(CLR)" : ""); } // end NormTrace(); -void NormSession::HandleReceiveMessage(NormMsg& msg, bool wasUnicast) +void NormSession::HandleReceiveMessage(NormMsg& msg, bool wasUnicast, bool ecnStatus) { // Ignore messages from ourself unless "loopback" is enabled if ((msg.GetSourceId() == LocalNodeId()) && !loopback) return; // Drop some rx messages for testing - if (UniformRand(100.0) < rx_loss_rate) + if ((rx_loss_rate > 0) && (UniformRand(100.0) < rx_loss_rate)) return; struct timeval currentTime; ::ProtoSystemTime(currentTime); - if (trace) NormTrace(currentTime, LocalNodeId(), msg, false); + if (trace) NormTrace(currentTime, LocalNodeId(), msg, false, 16); // TBD don't assume m == 16 (i.e. for fec_id == 2) + + NormMsg::Type msgType = msg.GetType(); + + // Add newly detected nodes to acking list _before_ processing message + if (IsSender() && (TRACK_NONE != acking_auto_populate)) + { + bool addNode = false; + switch (acking_auto_populate) + { + case TRACK_ALL: + addNode = true; + break; + case TRACK_RECEIVERS: + addNode = (NormMsg::NACK == msgType) || (NormMsg::ACK == msgType); + break; + case TRACK_SENDERS: + addNode = (NormMsg::NACK != msgType) && (NormMsg::ACK != msgType); + break; + default: + break; + } + if (addNode) + { + NormNodeId sourceId = msg.GetSourceId(); + if (NULL == acking_node_tree.FindNodeById(sourceId)) + { + if (!SenderAddAckingNode(msg.GetSourceId())) + PLOG(PL_ERROR, "NormSession::HandleReceiveMessage() error: unable to add acking node!\n"); + } + } + } switch (msg.GetType()) { case NormMsg::INFO: //DMSG(0, "NormSession::HandleReceiveMessage(NormMsg::INFO)\n"); - if (IsClient()) ClientHandleObjectMessage(currentTime, (NormObjectMsg&)msg); + if (IsReceiver()) ReceiverHandleObjectMessage(currentTime, (NormObjectMsg&)msg, ecnStatus); break; case NormMsg::DATA: //DMSG(0, "NormSession::HandleReceiveMessage(NormMsg::DATA) ...\n"); - if (IsClient()) ClientHandleObjectMessage(currentTime, (NormObjectMsg&)msg); + if (IsReceiver()) ReceiverHandleObjectMessage(currentTime, (NormObjectMsg&)msg, ecnStatus); break; case NormMsg::CMD: //DMSG(0, "NormSession::HandleReceiveMessage(NormMsg::CMD) ...\n"); - if (IsClient()) ClientHandleCommand(currentTime, (NormCmdMsg&)msg); + if (IsReceiver()) ReceiverHandleCommand(currentTime, (NormCmdMsg&)msg, ecnStatus); break; case NormMsg::NACK: - DMSG(4, "NormSession::HandleReceiveMessage(NormMsg::NACK) node>%lu ...\n", LocalNodeId()); - if (IsServer() && (((NormNackMsg&)msg).GetServerId() == LocalNodeId())) + if (IsSender() && (((NormNackMsg&)msg).GetSenderId() == LocalNodeId())) { - ServerHandleNackMessage(currentTime, (NormNackMsg&)msg); + SenderHandleNackMessage(currentTime, (NormNackMsg&)msg); if (wasUnicast && (backoff_factor > 0.5) && Address().IsMulticast()) { // for suppression of unicast nack feedback @@ -1723,39 +2473,39 @@ void NormSession::HandleReceiveMessage(NormMsg& msg, bool wasUnicast) QueueMessage(NULL); // to prompt transmit timeout } } - if (IsClient()) ClientHandleNackMessage((NormNackMsg&)msg); + if (IsReceiver()) ReceiverHandleNackMessage((NormNackMsg&)msg); break; case NormMsg::ACK: - if (IsServer() && (((NormAckMsg&)msg).GetServerId() == LocalNodeId())) - ServerHandleAckMessage(currentTime, (NormAckMsg&)msg, wasUnicast); - if (IsClient()) ClientHandleAckMessage((NormAckMsg&)msg); + if (IsSender() && (((NormAckMsg&)msg).GetSenderId() == LocalNodeId())) + SenderHandleAckMessage(currentTime, (NormAckMsg&)msg, wasUnicast); + if (IsReceiver()) ReceiverHandleAckMessage((NormAckMsg&)msg); break; case NormMsg::REPORT: case NormMsg::INVALID: - DMSG(0, "NormSession::HandleReceiveMessage(NormMsg::INVALID)\n"); + PLOG(PL_FATAL, "NormSession::HandleReceiveMessage(NormMsg::INVALID)\n"); break; } } // end NormSession::HandleReceiveMessage() - -void NormSession::ClientHandleObjectMessage(const struct timeval& currentTime, - const NormObjectMsg& msg) +void NormSession::ReceiverHandleObjectMessage(const struct timeval& currentTime, + const NormObjectMsg& msg, + bool ecnStatus) { - // Do common updates for servers we already know. + // Do common updates for senders we already know. NormNodeId sourceId = msg.GetSourceId(); - NormServerNode* theServer = (NormServerNode*)server_tree.FindNodeById(sourceId); - if (theServer) + NormSenderNode* theSender = (NormSenderNode*)sender_tree.FindNodeById(sourceId); + if (theSender) { - if (msg.GetInstanceId() != theServer->GetInstanceId()) + if (msg.GetInstanceId() != theSender->GetInstanceId()) { - DMSG(2, "NormSession::ClientHandleObjectMessage() node>%lu server>%lu instanceId change - resyncing.\n", - LocalNodeId(), theServer->GetId()); - theServer->Close(); - if (!theServer->Open(msg.GetInstanceId())) + PLOG(PL_INFO, "NormSession::ReceiverHandleObjectMessage() node>%lu sender>%lu instanceId change - resyncing.\n", + LocalNodeId(), theSender->GetId()); + theSender->Close(); + if (!theSender->Open(msg.GetInstanceId())) { - DMSG(0, "NormSession::ClientHandleObjectMessage() node>%lu error re-opening NormServerNode\n"); + PLOG(PL_ERROR, "NormSession::ReceiverHandleObjectMessage() node>%lu error re-opening NormSenderNode\n"); // (TBD) notify application of error return; } @@ -1763,55 +2513,69 @@ void NormSession::ClientHandleObjectMessage(const struct timeval& currentTime, } else { - if ((theServer = new NormServerNode(*this, msg.GetSourceId()))) + if (NULL != preset_sender) { - Notify(NormController::REMOTE_SENDER_NEW, theServer, NULL); - if (theServer->Open(msg.GetInstanceId())) + theSender = preset_sender; + preset_sender = NULL; + theSender->SetId(msg.GetSourceId()); + theSender->SetInstanceId(msg.GetInstanceId()); + sender_tree.AttachNode(theSender); + PLOG(PL_DEBUG, "NormSession::ReceiverHandleObjectMessage() node>%lu new remote sender:%lu ...\n", + LocalNodeId(), msg.GetSourceId()); + } + else if (NULL != (theSender = new NormSenderNode(*this, msg.GetSourceId()))) + { + Notify(NormController::REMOTE_SENDER_NEW, theSender, NULL); + if (theSender->Open(msg.GetInstanceId())) { - server_tree.AttachNode(theServer); - DMSG(4, "NormSession::ClientHandleObjectMessage() node>%lu new remote server:%lu ...\n", + sender_tree.AttachNode(theSender); + PLOG(PL_DEBUG, "NormSession::ReceiverHandleObjectMessage() node>%lu new remote sender:%lu ...\n", LocalNodeId(), msg.GetSourceId()); } else { - DMSG(0, "NormSession::ClientHandleObjectMessage() node>%lu error opening NormServerNode\n"); + PLOG(PL_FATAL, "NormSession::ReceiverHandleObjectMessage() node>%lu error opening NormSenderNode\n"); // (TBD) notify application of error return; } } else { - DMSG(0, "NormSession::ClientHandleObjectMessage() new NormServerNode error: %s\n", + PLOG(PL_ERROR, "NormSession::ReceiverHandleObjectMessage() new NormSenderNode error: %s\n", GetErrorString()); // (TBD) notify application of error return; } } - theServer->Activate(true); - theServer->SetAddress(msg.GetSource()); - theServer->UpdateRecvRate(currentTime, msg.GetLength()); - theServer->UpdateLossEstimate(currentTime, msg.GetSequence()); - theServer->IncrementRecvTotal(msg.GetLength()); // for statistics only (TBD) #ifdef NORM_DEBUG - theServer->HandleObjectMessage(msg); + theSender->Activate(true); + theSender->SetAddress(msg.GetSource()); + theSender->UpdateRecvRate(currentTime, msg.GetLength()); + //if (ecnStatus) TRACE("UPDATING LOSS EST w/ ECN pkt\n"); + theSender->UpdateLossEstimate(currentTime, msg.GetSequence(), ecnStatus); + theSender->IncrementRecvTotal(msg.GetLength()); // for statistics only (TBD) #ifdef NORM_DEBUG + theSender->HandleObjectMessage(msg); + theSender->CheckCCFeedback(); // this cues immediate CLR cc feedback if loss was detected + // and cc feedback was not provided in response otherwise -} // end NormSession::ClientHandleObjectMessage() +} // end NormSession::ReceiverHandleObjectMessage() -void NormSession::ClientHandleCommand(const struct timeval& currentTime, - const NormCmdMsg& cmd) +void NormSession::ReceiverHandleCommand(const struct timeval& currentTime, + const NormCmdMsg& cmd, + bool ecnStatus) { - // Do common updates for servers we already know. + // Do common updates for senders we already know. NormNodeId sourceId = cmd.GetSourceId(); - NormServerNode* theServer = (NormServerNode*)server_tree.FindNodeById(sourceId); - if (theServer) + NormSenderNode* theSender = (NormSenderNode*)sender_tree.FindNodeById(sourceId); + if (theSender) { - if (cmd.GetInstanceId() != theServer->GetInstanceId()) + if (cmd.GetInstanceId() != theSender->GetInstanceId()) { - DMSG(2, "NormSession::ClientHandleCommand() node>%lu server>%lu instanceId change - resyncing.\n", - LocalNodeId(), theServer->GetId()); - theServer->Close(); - if (!theServer->Open(cmd.GetInstanceId())) + PLOG(PL_INFO, "NormSession::ReceiverHandleCommand() node>%lu sender>%lu instanceId change - resyncing.\n", + LocalNodeId(), theSender->GetId()); + theSender->Close(); + if (!theSender->Open(cmd.GetInstanceId())) { - DMSG(0, "NormSession::ClientHandleCommand() node>%lu error re-opening NormServerNode\n"); + PLOG(PL_ERROR, "NormSession::ReceiverHandleCommand() node>%lu error re-opening NormSenderNode\n"); // (TBD) notify application of error return; } @@ -1819,39 +2583,55 @@ void NormSession::ClientHandleCommand(const struct timeval& currentTime, } else { - //DMSG(0, "NormSession::ClientHandleCommand() node>%lu recvd command from unknown server ...\n", + //DMSG(0, "NormSession::ReceiverHandleCommand() node>%lu recvd command from unknown sender ...\n", // LocalNodeId()); - if ((theServer = new NormServerNode(*this, cmd.GetSourceId()))) + if (NULL != preset_sender) { - Notify(NormController::REMOTE_SENDER_NEW, theServer, NULL); - if (theServer->Open(cmd.GetInstanceId())) + theSender = preset_sender; + preset_sender = NULL; + theSender->SetId(cmd.GetSourceId()); + theSender->SetInstanceId(cmd.GetInstanceId()); + sender_tree.AttachNode(theSender); + PLOG(PL_DEBUG, "NormSession::ReceiverHandleObjectMessage() node>%lu new remote sender:%lu ...\n", + LocalNodeId(), cmd.GetSourceId()); + } + else if ((theSender = new NormSenderNode(*this, cmd.GetSourceId()))) + { + Notify(NormController::REMOTE_SENDER_NEW, theSender, NULL); + if (theSender->Open(cmd.GetInstanceId())) { - server_tree.AttachNode(theServer); - DMSG(4, "NormSession::ClientHandleCommand() node>%lu new remote server:%lu ...\n", + sender_tree.AttachNode(theSender); + PLOG(PL_DEBUG, "NormSession::ReceiverHandleCommand() node>%lu new remote sender:%lu ...\n", LocalNodeId(), cmd.GetSourceId()); } else { - DMSG(0, "NormSession::ClientHandleCommand() node>%lu error opening NormServerNode\n"); + PLOG(PL_ERROR, "NormSession::ReceiverHandleCommand() node>%lu error opening NormSenderNode\n"); // (TBD) notify application of error return; } } else { - DMSG(0, "NormSession::ClientHandleCommand() new NormServerNode error: %s\n", + PLOG(PL_ERROR, "NormSession::ReceiverHandleCommand() new NormSenderNode error: %s\n", GetErrorString()); // (TBD) notify application of error return; } } - theServer->Activate(false); - theServer->SetAddress(cmd.GetSource()); - theServer->UpdateRecvRate(currentTime, cmd.GetLength()); - theServer->UpdateLossEstimate(currentTime, cmd.GetSequence()); - theServer->IncrementRecvTotal(cmd.GetLength()); // for statistics only (TBD) #ifdef NORM_DEBUG - theServer->HandleCommand(currentTime, cmd); -} // end NormSession::ClientHandleCommand() + // We should "re-activate" senders on NORM_CMD(FLUSH) + if (NormCmdMsg::FLUSH == cmd.GetFlavor()) + theSender->Activate(true); + else + theSender->Activate(false); + theSender->SetAddress(cmd.GetSource()); + theSender->UpdateRecvRate(currentTime, cmd.GetLength()); + theSender->UpdateLossEstimate(currentTime, cmd.GetSequence(), ecnStatus); + theSender->IncrementRecvTotal(cmd.GetLength()); // for statistics only (TBD) #ifdef NORM_DEBUG + theSender->HandleCommand(currentTime, cmd); + theSender->CheckCCFeedback(); // this cues immediate CLR cc feedback if loss was detected + // and cc feedback was not provided in response otherwise +} // end NormSession::ReceiverHandleCommand() double NormSession::CalculateRtt(const struct timeval& currentTime, @@ -1886,15 +2666,15 @@ double NormSession::CalculateRtt(const struct timeval& currentTime, } } // end NormSession::CalculateRtt() -void NormSession::ServerUpdateGrttEstimate(double clientRtt) +void NormSession::SenderUpdateGrttEstimate(double receiverRtt) { grtt_response = true; - if ((clientRtt > grtt_measured) || !address.IsMulticast()) + if ((receiverRtt > grtt_measured) || !address.IsMulticast()) { // Immediately incorporate bigger RTT's grtt_decrease_delay_count = DEFAULT_GRTT_DECREASE_DELAY; - grtt_measured = 0.25 * grtt_measured + 0.75 * clientRtt; - //grtt_measured = 0.9 * grtt_measured + 0.1 * clientRtt; + grtt_measured = 0.25 * grtt_measured + 0.75 * receiverRtt; + //grtt_measured = 0.9 * grtt_measured + 0.1 * receiverRtt; if (grtt_measured > grtt_max) grtt_measured = grtt_max; UINT8 grttQuantizedOld = grtt_quantized; double pktInterval = ((double)(44+segment_size))/tx_rate; @@ -1911,24 +2691,35 @@ void NormSession::ServerUpdateGrttEstimate(double clientRtt) } grtt_current_peak = grtt_measured; if (grttQuantizedOld != grtt_quantized) - DMSG(4, "NormSession::ServerUpdateGrttEstimate() node>%lu increased to new grtt>%lf sec\n", + { + Notify(NormController::GRTT_UPDATED, (NormSenderNode*)NULL, (NormObject*)NULL); + PLOG(PL_DEBUG, "NormSession::SenderUpdateGrttEstimate() node>%lu increased to new grtt>%lf sec\n", LocalNodeId(), grtt_advertised); + } } - else if (clientRtt > grtt_current_peak) + else if (receiverRtt > grtt_current_peak) { - grtt_current_peak = clientRtt; + grtt_current_peak = receiverRtt; } -} // end NormSession::ServerUpdateGrttEstimate() +} // end NormSession::SenderUpdateGrttEstimate() + + double NormSession::CalculateRate(double size, double rtt, double loss) { + // size + // rate = ------------------------------------------------------------- + // rtt * (sqrt(2*loss/3) + 12*loss*(1 + 32*loss*loss)*sqrt(3*loss/8)) + // + // notes: "b" = 1 and "t_RTO" = 4*rtt where "b" is number of TCP pkts/ACK + double denom = rtt * (sqrt((2.0/3.0)*loss) + (12.0 * sqrt((3.0/8.0)*loss) * loss * (1.0 + 32.0*loss*loss))); return (size / denom); } // end NormSession::CalculateRate() -void NormSession::ServerHandleCCFeedback(struct timeval currentTime, +void NormSession::SenderHandleCCFeedback(struct timeval currentTime, NormNodeId nodeId, UINT8 ccFlags, double ccRtt, @@ -1936,6 +2727,10 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, double ccRate, UINT16 ccSequence) { + + PLOG(PL_DEBUG, "NormSession::SenderHandleCCFeedback() cc feedback recvd at time %lu.%lf ccRate:%9.3lf ccRtt:%lf ccLoss:%lf ccFlags:%02x\n", + (unsigned long)currentTime.tv_sec, ((double)currentTime.tv_usec)*1.0e-06, + ccRate*8.0/1000.0, ccRtt, ccLoss, ccFlags); // Keep track of current suppressing feedback // (non-CLR, lowest rate, unconfirmed RTT) if (0 == (ccFlags & NormCC::CLR)) @@ -1962,10 +2757,31 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, bool ccSlowStart = (0 != (ccFlags & NormCC::START)); if (!ccSlowStart) - ccRate = CalculateRate(nominal_packet_size, ccRtt, ccLoss); + { + double calcRate = CalculateRate(nominal_packet_size, ccRtt, ccLoss); +#ifdef LIMIT_CC_RATE + // Experimental modification to NORM-CC where congestion control rate is limited + // to MIN(2.0*measured recv rate, calculated rate). This might prevent large rate + // overshoot in conditions where the loss measurement (perhaps initial loss) is + // very low due to big network packet buffers, etc + // Note that when the NORM_CC_FLAG_LIMIT is set, this indicates the receiver + // has set the rate field to 2.0 * measured recv rate instead of calculated rate. + if (0 != (ccFlags & NormCC::LIMIT)) + { + // receiver set a limited rate instead of calculated rate + // so let's confirm which is the lower rate + if (calcRate < ccRate) + ccRate = calcRate; + } + else +#endif // LIMIT_CC_RATE + { + ccRate = calcRate; + } + } - //DMSG(0, "NormSession::ServerHandleCCFeedback() node>%lu rate>%lf (rtt>%lf loss>%lf slow_start>%d)\n", - // nodeId, ccRate * 8.0 / 1000.0, ccRtt, ccLoss, (0 != (ccFlags & NormCC::START))); + PLOG(PL_DEBUG, "NormSession::SenderHandleCCFeedback() node>%lu rate>%lf (rtt>%lf loss>%lf slow_start>%d limit>%d)\n", + nodeId, ccRate * 8.0 / 1000.0, ccRtt, ccLoss, (0 != (ccFlags & NormCC::START)), (0 != (ccFlags & NormCC::LIMIT))); // Keep the active CLR (if there is one) at the head of the list NormNodeListIterator iterator(cc_node_list); @@ -1973,9 +2789,17 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, // 1) Does this response replace the active CLR? if (next && next->IsActive()) { + // First, make sure this is _new_ non-duplicative + // feedback for the given "nodeId" + if (next->GetId() == nodeId) + { + INT16 ccDelta = ccSequence - next->GetCCSequence(); + if (ccDelta <= 0) return; + } if ((nodeId == next->GetId()) || (ccRate < next->GetRate()) || - ((ccRate < (next->GetRate() * 1.1)) && (ccRtt > next->GetRtt()))) // use Rtt as tie-breaker if close + ((ccRate < (next->GetRate() * 1.1)) && + (ccRtt > next->GetRtt()))) // use Rtt as tie-breaker if close { NormNodeId savedId = next->GetId(); bool savedRttStatus = next->HasRtt(); @@ -1988,6 +2812,7 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, next->SetId(nodeId); next->SetClrStatus(true); next->SetRttStatus(0 != (ccFlags & NormCC::RTT)); + next->SetLoss(ccLoss); next->SetRate(ccRate); next->SetCCSequence(ccSequence); @@ -2027,7 +2852,7 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, } else { - DMSG(0, "NormSession::ServerHandleCCFeedback() memory allocation error: %s\n", + PLOG(PL_FATAL, "NormSession::SenderHandleCCFeedback() memory allocation error: %s\n", GetErrorString()); return; } @@ -2056,7 +2881,7 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, } else { - DMSG(0, "NormSession::ServerHandleCCFeedback() memory allocation error: %s\n", + PLOG(PL_FATAL, "NormSession::SenderHandleCCFeedback() memory allocation error: %s\n", GetErrorString()); } } @@ -2121,16 +2946,16 @@ void NormSession::ServerHandleCCFeedback(struct timeval currentTime, candidate->SetActive(true); } } -} // end NormSession::ServerHandleCCFeedback() +} // end NormSession::SenderHandleCCFeedback() -void NormSession::ServerHandleAckMessage(const struct timeval& currentTime, const NormAckMsg& ack, bool wasUnicast) +void NormSession::SenderHandleAckMessage(const struct timeval& currentTime, const NormAckMsg& ack, bool wasUnicast) { // Update GRTT estimate struct timeval grttResponse; ack.GetGrttResponse(grttResponse); - double clientRtt = CalculateRtt(currentTime, grttResponse); - if (clientRtt >= 0.0) ServerUpdateGrttEstimate(clientRtt); + double receiverRtt = CalculateRtt(currentTime, grttResponse); + if (receiverRtt >= 0.0) SenderUpdateGrttEstimate(receiverRtt); // Look for NORM-CC Feedback header extension NormCCFeedbackExtension ext; @@ -2138,12 +2963,12 @@ void NormSession::ServerHandleAckMessage(const struct timeval& currentTime, cons { if (NormHeaderExtension::CC_FEEDBACK == ext.GetType()) { - ServerHandleCCFeedback(currentTime, + SenderHandleCCFeedback(currentTime, ack.GetSourceId(), ext.GetCCFlags(), - clientRtt >= 0.0 ? - clientRtt : NormUnquantizeRtt(ext.GetCCRtt()), - NormUnquantizeLoss(ext.GetCCLoss()), + receiverRtt >= 0.0 ? + receiverRtt : NormUnquantizeRtt(ext.GetCCRtt()), + NormUnquantizeLoss32(ext.GetCCLoss32()), NormUnquantizeRate(ext.GetCCRate()), ext.GetCCSequence()); if (wasUnicast && probe_proactive && Address().IsMulticast()) @@ -2167,56 +2992,70 @@ void NormSession::ServerHandleAckMessage(const struct timeval& currentTime, cons { NormAckingNode* acker = static_cast(acking_node_tree.FindNodeById(ack.GetSourceId())); - if (acker) + if (NULL != acker) { if (!acker->AckReceived()) { const NormAckFlushMsg& flushAck = static_cast(ack); - if ((watermark_object_id == flushAck.GetObjectId()) && - (watermark_block_id == flushAck.GetFecBlockId()) && - (watermark_segment_id == flushAck.GetFecSymbolId())) + if (flushAck.GetFecId() != fec_id) + { + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received watermark ACK with wrong fec_id?!\n"); + } + else if ((watermark_object_id == flushAck.GetObjectId()) && + (watermark_block_id == flushAck.GetFecBlockId(fec_m)) && + (watermark_segment_id == flushAck.GetFecSymbolId(fec_m))) { acker->MarkAckReceived(); } else { // This can happen when new watermarks are set when an old watermark is still - // pending. - DMSG(0, "NormSession::ServerHandleAckMessage() received wrong watermark ACK?!\n"); + // pending (i.e. receivers may still be in the process of replying) + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received wrong watermark ACK?!\n"); } } else { - DMSG(0, "NormSession::ServerHandleAckMessage() received redundant watermark ACK?!\n"); + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received redundant watermark ACK?!\n"); } } else { - DMSG(0, "NormSession::ServerHandleAckMessage() received watermark ACK from unknown acker?!\n"); + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received watermark ACK from unknown acker?!\n"); } } else { - DMSG(0, "NormSession::ServerHandleAckMessage() received unsolicited watermark ACK?!\n"); + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() received unsolicited watermark ACK?!\n"); } break; // (TBD) Handle other acknowledgement types default: - DMSG(0, "NormSession::ServerHandleAckMessage() node>%lu received " + PLOG(PL_ERROR, "NormSession::SenderHandleAckMessage() node>%lu received " "unsupported ack type:%d\n", LocalNodeId(), ack.GetAckType()); + break; } -} // end ServerHandleAckMessage() +} // end SenderHandleAckMessage() -void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, NormNackMsg& nack) +void NormSession::SenderHandleNackMessage(const struct timeval& currentTime, NormNackMsg& nack) { + + if (GetDebugLevel() >= PL_DEBUG) + { + PLOG(PL_ALWAYS, "node>%lu sender (tactive:%d)received NACK message with content:\n", + LocalNodeId(), repair_timer.IsActive()); + LogRepairContent(nack.GetRepairContent(), nack.GetRepairContentLength(), fec_id, fec_m); + PLOG(PL_ALWAYS, "\n"); + } + // (TBD) maintain average of "numErasures" for SEGMENT repair requests // to use as input to a an automatic "auto parity" adjustor // Update GRTT estimate struct timeval grttResponse; nack.GetGrttResponse(grttResponse); - double clientRtt = CalculateRtt(currentTime, grttResponse); - if (clientRtt >= 0.0) ServerUpdateGrttEstimate(clientRtt); + double receiverRtt = CalculateRtt(currentTime, grttResponse); + if (receiverRtt >= 0.0) SenderUpdateGrttEstimate(receiverRtt); // Look for NORM-CC Feedback header extension NormCCFeedbackExtension ext; @@ -2224,12 +3063,12 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor { if (NormHeaderExtension::CC_FEEDBACK == ext.GetType()) { - ServerHandleCCFeedback(currentTime, + SenderHandleCCFeedback(currentTime, nack.GetSourceId(), ext.GetCCFlags(), - clientRtt >= 0.0 ? - clientRtt : NormUnquantizeRtt(ext.GetCCRtt()), - NormUnquantizeLoss(ext.GetCCLoss()), + receiverRtt >= 0.0 ? + receiverRtt : NormUnquantizeRtt(ext.GetCCRtt()), + NormUnquantizeLoss32(ext.GetCCLoss32()), // note using extended precision loss value here NormUnquantizeRate(ext.GetCCRate()), ext.GetCCSequence()); } @@ -2255,10 +3094,10 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor // Get the index of our next pending NORM_DATA transmission NormObjectId txObjectIndex; NormBlockId txBlockIndex; - if (ServerGetFirstPending(txObjectIndex)) + if (SenderGetFirstPending(txObjectIndex)) { NormObject* obj = tx_table.Find(txObjectIndex); - ASSERT(obj); + ASSERT(NULL != obj); if (obj->IsPendingInfo()) { txBlockIndex = 0; @@ -2304,12 +3143,12 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor } else { - DMSG(0, "NormSession::ServerHandleNackMessage() node>%lu recvd repair request w/ invalid repair level\n", + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() node>%lu recvd repair request w/ invalid repair level\n", LocalNodeId()); continue; } - NormRepairRequest::Iterator iterator(req); + NormRepairRequest::Iterator iterator(req, fec_id, fec_m); NormObjectId nextObjectId, lastObjectId; NormBlockId nextBlockId, lastBlockId; UINT16 nextBlockLen, lastBlockLen; @@ -2322,7 +3161,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (!iterator.NextRepairItem(&lastObjectId, &lastBlockId, &lastBlockLen, &lastSegmentId)) { - DMSG(0, "NormSession::ServerHandleNackMessage() node>%lu recvd incomplete RANGE request!\n", + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() node>%lu recvd incomplete RANGE request!\n", LocalNodeId()); continue; // (TBD) break/return instead??? } @@ -2345,11 +3184,11 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor freshBlock = true; if (!(object = tx_table.Find(nextObjectId))) { - DMSG(4, "NormSession::ServerHandleNackMessage() node>%lu recvd repair request " + PLOG(PL_DEBUG, "NormSession::SenderHandleNackMessage() node>%lu recvd repair request " "for unknown object ...\n", LocalNodeId()); if (!squelchQueued) { - ServerQueueSquelch(nextObjectId); + SenderQueueSquelch(nextObjectId); squelchQueued = true; } if ((OBJECT == requestLevel) || (INFO == requestLevel)) @@ -2372,7 +3211,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (holdoff) { if (nextObjectId > txObjectIndex) - object->HandleInfoRequest(); + object->HandleInfoRequest(true); } else { @@ -2393,17 +3232,18 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor tx_repair_block_min = 0; tx_repair_segment_min = 0; } - object->HandleInfoRequest(); + object->HandleInfoRequest(false); startTimer = true; } } } // end if (freshObject) - ASSERT(object); + ASSERT(NULL != object); + object->SetLastNackTime(ProtoTime(currentTime)); switch (requestLevel) { case OBJECT: - DMSG(8, "NormSession::ServerHandleNackMessage(OBJECT) objs>%hu:%hu\n", + PLOG(PL_DETAIL, "NormSession::SenderHandleNackMessage(OBJECT) objs>%hu:%hu\n", (UINT16)nextObjectId, (UINT16)lastObjectId); if (holdoff) { @@ -2414,7 +3254,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor else object->TxReset(); if (!tx_pending_mask.Set(nextObjectId)) - DMSG(0, "NormSession::ServerHandleNackMessage() tx_pending_mask.Set(%hu) error (1)\n", + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() tx_pending_mask.Set(%hu) error (1)\n", (UINT16)nextObjectId); } } @@ -2444,12 +3284,14 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (nextObjectId > lastObjectId) inRange = false; break; case BLOCK: - DMSG(8, "NormSession::ServerHandleNackMessage(BLOCK) obj>%hu blks>%lu:%lu\n", + PLOG(PL_DETAIL, "NormSession::SenderHandleNackMessage(BLOCK) obj>%hu blks>%lu:%lu\n", (UINT16)nextObjectId, (UINT32)nextBlockId, (UINT32)lastBlockId); inRange = false; // BLOCK requests are processed in one pass // (TBD) if entire object is TxReset(), continue if (object->IsStream()) { + // mark nack time for potential flow control + static_cast(object)->SetLastNackTime(nextBlockId, ProtoTime(currentTime)); bool attemptLock = true; NormBlockId firstLockId = nextBlockId; if (holdoff) @@ -2474,13 +3316,13 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor // Lock stream_buffer pending for block data retransmissions if (attemptLock) { - if (!((NormStreamObject*)object)->LockBlocks(firstLockId, lastBlockId)) + if (!((NormStreamObject*)object)->LockBlocks(firstLockId, lastBlockId, currentTime)) { - DMSG(4, "NormSession::ServerHandleNackMessage() node>%lu LockBlocks() failure\n", + PLOG(PL_DEBUG, "NormSession::SenderHandleNackMessage() node>%lu LockBlocks() failure\n", LocalNodeId()); if (!squelchQueued) { - ServerQueueSquelch(nextObjectId); + SenderQueueSquelch(nextObjectId); squelchQueued = true; } break; @@ -2505,7 +3347,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (object->TxResetBlocks(nextBlockId, lastBlockId)) { if (!tx_pending_mask.Set(nextObjectId)) - DMSG(0, "NormSession::ServerHandleNackMessage() tx_pending_mask.Set(%hu) error (2)\n", + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() tx_pending_mask.Set(%hu) error (2)\n", (UINT16)nextObjectId); } } @@ -2542,7 +3384,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor } break; case SEGMENT: - DMSG(8, "NormSession::ServerHandleNackMessage(SEGMENT) obj>%hu blk>%lu segs>%hu:%hu\n", + PLOG(PL_DETAIL, "NormSession::SenderHandleNackMessage(SEGMENT) obj>%hu blk>%lu segs>%hu:%hu\n", (UINT16)nextObjectId, (UINT32)nextBlockId, (UINT32)nextSegmentId, (UINT32)lastSegmentId); inRange = false; // SEGMENT repairs are also handled in one pass @@ -2558,31 +3400,31 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (object->IsPendingSet(nextBlockId)) { // Entire block already tx pending, don't worry about individual segments - DMSG(4, "NormSession::ServerHandleNackMessage() node>%lu " + PLOG(PL_DEBUG, "NormSession::SenderHandleNackMessage() node>%lu " "recvd SEGMENT repair request for pending block.\n"); continue; } else { // Try to recover block including parity calculation - if (NULL == (block = object->ServerRecoverBlock(nextBlockId))) + if (NULL == (block = object->SenderRecoverBlock(nextBlockId))) { if (NormObject::STREAM == object->GetType()) { - DMSG(4, "NormSession::ServerHandleNackMessage() node>%lu " + PLOG(PL_DEBUG, "NormSession::SenderHandleNackMessage() node>%lu " "recvd repair request for old stream block(%lu) ...\n", LocalNodeId(), (UINT32)nextBlockId); if (!squelchQueued) { - ServerQueueSquelch(nextObjectId); + SenderQueueSquelch(nextObjectId); squelchQueued = true; } } else { // Resource constrained, move on to next repair request - DMSG(2, "NormSession::ServerHandleNackMessage() node>%lu " - "Warning - server is resource constrained ...\n"); + PLOG(PL_INFO, "NormSession::SenderHandleNackMessage() node>%lu " + "Warning - sender is resource constrained ...\n"); } continue; } @@ -2593,67 +3435,73 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor prevBlockId = nextBlockId; } // end if (freshBlock) ASSERT(NULL != block); + // If stream && explicit data repair, lock the data for retransmission // (TBD) this use of "ndata" needs to be replaced for dynamically shortened blocks - if (object->IsStream() && (nextSegmentId < ndata)) + if (object->IsStream()) { - bool attemptLock = true; - NormSegmentId firstLockId = nextSegmentId; - NormSegmentId lastLockId = ndata - 1; - lastLockId = MIN(lastLockId, lastSegmentId); - if (holdoff) + // mark nack time for potential flow control + static_cast(object)->SetLastNackTime(nextBlockId, ProtoTime(currentTime)); + if (nextSegmentId < ndata) { - if (nextObjectId == txObjectIndex) + bool attemptLock = true; + NormSegmentId firstLockId = nextSegmentId; + NormSegmentId lastLockId = ndata - 1; + lastLockId = MIN(lastLockId, lastSegmentId); + if (holdoff) { - if (nextBlockId < txBlockIndex) + if (nextObjectId == txObjectIndex) { - if (1 == (txBlockIndex - nextBlockId)) + if (nextBlockId < txBlockIndex) { - // We're currently sending this block - if (block->IsPending()) + if (1 == (txBlockIndex - nextBlockId)) { - NormSegmentId firstPending = 0; - block->GetFirstPending(firstPending); - if (lastLockId <= firstPending) - attemptLock = false; - else if (nextSegmentId < firstPending) - firstLockId = firstPending; + // We're currently sending this block + if (block->IsPending()) + { + NormSegmentId firstPending = 0; + block->GetFirstPending(firstPending); + if (lastLockId <= firstPending) + attemptLock = false; + else if (nextSegmentId < firstPending) + firstLockId = firstPending; + } + else + { + // block was just recovered + } } else { - // block was just recovered - } + attemptLock = false; // NACK arrived way too late + } } - else - { - attemptLock = false; // NACK arrived way too late - } } - } - else if (nextObjectId < txObjectIndex) - { - attemptLock = false; // NACK arrived too late - } - } - if (attemptLock) - { - if (!((NormStreamObject*)object)->LockSegments(nextBlockId, firstLockId, lastLockId)) - { - DMSG(0, "NormSession::ServerHandleNackMessage() node>%lu " - "LockSegments() failure\n", LocalNodeId()); - if (!squelchQueued) + else if (nextObjectId < txObjectIndex) { - ServerQueueSquelch(nextObjectId); - squelchQueued = true; + attemptLock = false; // NACK arrived too late } - break; + } + if (attemptLock) + { + if (!((NormStreamObject*)object)->LockSegments(nextBlockId, firstLockId, lastLockId)) + { + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() node>%lu " + "LockSegments() failure\n", LocalNodeId()); + if (!squelchQueued) + { + SenderQueueSquelch(nextObjectId); + squelchQueued = true; + } + break; + } } - } - else - { - break; // ignore late arriving NACK - } - } // end if (object->IsStream() && (nextSegmentId < ndata)) + else + { + break; // ignore late arriving NACK + } + } // end if (nextSegmentId < ndata) + } // end if (object->IsStream()) // With a series of SEGMENT repair requests for a block, "numErasures" will // eventually total the number of missing segments in the block. @@ -2665,7 +3513,7 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor if (object->TxUpdateBlock(block, nextSegmentId, lastSegmentId, numErasures)) { if (!tx_pending_mask.Set(nextObjectId)) - DMSG(0, "NormSession::ServerHandleNackMessage() tx_pending_mask.Set(%hu) error (3)\n", + PLOG(PL_ERROR, "NormSession::SenderHandleNackMessage() tx_pending_mask.Set(%hu) error (3)\n", (UINT16)nextObjectId); } } @@ -2754,57 +3602,64 @@ void NormSession::ServerHandleNackMessage(const struct timeval& currentTime, Nor // backoff == 0.0 is a special case //aggregateInterval = (backoff_factor > 0.0) ? aggregateInterval : 0.0; - if (tx_timer.IsActive()) + // TBD - why did we do this thing here to limit the min aggregateInterval??? + // (I think to allow "11th hour NACKs to be incorporated .. so this should be + // for mcast only) + if (tx_timer.IsActive() && address.IsMulticast()) { double txTimeout = tx_timer.GetTimeRemaining() - 1.0e-06; aggregateInterval = MAX(txTimeout, aggregateInterval); - } - + } repair_timer.SetInterval(aggregateInterval); - DMSG(4, "NormSession::ServerHandleNackMessage() node>%lu starting server " - "NACK aggregation timer (%lf sec)...\n", LocalNodeId(), aggregateInterval); + PLOG(PL_DEBUG, "NormSession::SenderHandleNackMessage() node>%lu starting sender " + "NACK aggregation timer (%lf sec)...\n", LocalNodeId(), aggregateInterval); ActivateTimer(repair_timer); } -} // end NormSession::ServerHandleNackMessage() +} // end NormSession::SenderHandleNackMessage() -void NormSession::ClientHandleAckMessage(const NormAckMsg& ack) +void NormSession::ReceiverHandleAckMessage(const NormAckMsg& ack) { - NormServerNode* theServer = (NormServerNode*)server_tree.FindNodeById(ack.GetServerId()); - if (theServer) + NormSenderNode* theSender = (NormSenderNode*)sender_tree.FindNodeById(ack.GetSenderId()); + if (theSender) { - theServer->HandleAckMessage(ack); + theSender->HandleAckMessage(ack); } else { - DMSG(4, "NormSession::ClientHandleAckMessage() node>%lu heard ACK for unknown server.\n", + PLOG(PL_DEBUG, "NormSession::ReceiverHandleAckMessage() node>%lu heard ACK for unknown sender.\n", LocalNodeId()); } -} // end NormSession::ClientHandleAckMessage() +} // end NormSession::ReceiverHandleAckMessage() -void NormSession::ClientHandleNackMessage(const NormNackMsg& nack) +void NormSession::ReceiverHandleNackMessage(const NormNackMsg& nack) { - NormServerNode* theServer = (NormServerNode*)server_tree.FindNodeById(nack.GetServerId()); - if (theServer) + NormSenderNode* theSender = (NormSenderNode*)sender_tree.FindNodeById(nack.GetSenderId()); + if (theSender) { - theServer->HandleNackMessage(nack); + theSender->HandleNackMessage(nack); } else { - DMSG(4, "NormSession::ClientHandleNackMessage() node>%lu heard NACK for unknown server\n", + PLOG(PL_DEBUG, "NormSession::ReceiverHandleNackMessage() node>%lu heard NACK for unknown sender\n", LocalNodeId()); } -} // end NormSession::ClientHandleNackMessage() +} // end NormSession::ReceiverHandleNackMessage() -bool NormSession::ServerQueueSquelch(NormObjectId objectId) +bool NormSession::SenderQueueSquelch(NormObjectId objectId) { // If a squelch is already queued, update it if (objectId < squelch->objectId) bool doEnqueue = true; NormCmdSquelchMsg* squelch = NULL; - NormMsg* msg = message_pool.GetHead(); + NormMsg* msg = message_queue.GetHead(); while (NULL != msg) { + // (TBD) we need to depreceate the whole "message_pool" idea and + // have messages be built on demand in NormSession::Serve() according + // to some state variables (i.e. that dictate when to send a command + // instead of data, etc). This will simplify alot of stuff and + // probably improve performance some too. if (NormMsg::CMD == msg->GetType()) { if (NormCmdMsg::SQUELCH == static_cast(msg)->GetFlavor()) @@ -2827,7 +3682,7 @@ bool NormSession::ServerQueueSquelch(NormObjectId objectId) } if (squelch) { - squelch->Init(); + squelch->Init(fec_id); squelch->SetDestination(address); squelch->SetGrtt(grtt_quantized); squelch->SetBackoffFactor((unsigned char)backoff_factor); @@ -2840,10 +3695,7 @@ bool NormSession::ServerQueueSquelch(NormObjectId objectId) ASSERT(NormObject::STREAM == obj->GetType()); squelch->SetObjectId(objectId); NormBlockId blockId = static_cast(obj)->StreamBufferLo(); - squelch->SetFecBlockId(blockId); - squelch->SetFecBlockLen(obj->GetBlockSize(blockId)); - squelch->SetFecSymbolId(0); - squelch->ResetInvalidObjectList(); + squelch->SetFecPayloadId(fec_id, blockId, 0, obj->GetBlockSize(blockId), fec_m); while ((obj = iterator.GetNextObject())) if (objectId == obj->GetId()) break; nextId = objectId + 1; @@ -2859,18 +3711,15 @@ bool NormSession::ServerQueueSquelch(NormObjectId objectId) blockId =static_cast(obj)->StreamBufferLo(); else blockId = NormBlockId(0); - squelch->SetFecBlockId(blockId); - squelch->SetFecBlockLen(obj->GetBlockSize(blockId)); - squelch->SetFecSymbolId(0); + squelch->SetFecPayloadId(fec_id, blockId, 0, obj->GetBlockSize(blockId), fec_m); nextId = obj->GetId() + 1; } else { // Squelch to point to future object squelch->SetObjectId(next_tx_object_id); - squelch->SetFecBlockId(0); - squelch->SetFecBlockLen(0); // (TBD) should this be "ndata" instead? but we can't be sure - squelch->SetFecSymbolId(0); + // (TBD) should the "blockLen" here be "ndata" instead? but we can't be sure + squelch->SetFecPayloadId(fec_id, 0, 0, 0, fec_m); nextId = next_tx_object_id; } } @@ -2890,26 +3739,176 @@ bool NormSession::ServerQueueSquelch(NormObjectId objectId) if (doEnqueue) { QueueMessage(squelch); - DMSG(4, "NormSession::ServerQueueSquelch() node>%lu server queued squelch ...\n", + PLOG(PL_DEBUG, "NormSession::SenderQueueSquelch() node>%lu sender queued squelch ...\n", LocalNodeId()); } else { - DMSG(4, "NormSession::ServerQueueSquelch() node>%lu server updated squelch ...\n", + PLOG(PL_DEBUG, "NormSession::SenderQueueSquelch() node>%lu sender updated squelch ...\n", LocalNodeId()); } return true; } else { - DMSG(0, " NormSession::ServerQueueSquelch() node>%lu message_pool exhausted! (couldn't squelch)\n", + PLOG(PL_FATAL, " NormSession::SenderQueueSquelch() node>%lu message_pool exhausted! (couldn't squelch)\n", LocalNodeId()); return false; } -} // end NormSession::ServerQueueSquelch() +} // end NormSession::SenderQueueSquelch() + +bool NormSession::SenderSendCmd(const char* cmdBuffer, unsigned int cmdLength, bool robust) +{ + if (!is_sender) + { + PLOG(PL_ERROR, "NormSession::SenderSendCmd() error: non-sender session!\n"); + return false; + } + if (0 != cmd_count) + { + PLOG(PL_INFO, "NormSession::SenderSendCmd() error: command already pending!\n"); + return false; + } + else if (cmdLength > segment_size) + { + PLOG(PL_INFO, "NormSession::SenderSendCmd() error: command length greater than segment_size!\n"); + return false; + } + memcpy(cmd_buffer, cmdBuffer, cmdLength); + cmd_length = cmdLength; + cmd_count = robust ? tx_robust_factor : 1; + if (!tx_timer.IsActive()) PromptSender(); + return true; +} // end NormSession::SenderSendCmd() + +void NormSession::SenderCancelCmd() +{ + if (0 != cmd_count) + { + if (cmd_timer.IsActive()) cmd_timer.Deactivate(); + cmd_count = 0; + cmd_length = 0; + } +} // end NormSession::SenderCancelCmd() + +bool NormSession::SenderQueueAppCmd() +{ + if (0 == cmd_count) return false; + ASSERT(!cmd_timer.IsActive()); + // 1) Build a NORM_CMD(APPLICATION) message + NormCmdAppMsg* appMsg = static_cast(GetMessageFromPool()); + if (NULL != appMsg) + { + appMsg->Init(); + appMsg->SetDestination(address); + appMsg->SetGrtt(grtt_quantized); + appMsg->SetBackoffFactor((unsigned char)backoff_factor); + appMsg->SetGroupSize(gsize_quantized); + appMsg->SetContent(cmd_buffer, cmd_length, segment_size); + QueueMessage(appMsg); + PLOG(PL_DEBUG, "NormSession::SenderQueueAppCmd() node>%lu sender queued app-defined cmd ...\n", + LocalNodeId()); + cmd_count--; + if (0 != cmd_count) + { + cmd_timer.SetInterval(grtt_advertised * 2); + ActivateTimer(cmd_timer); + } + else + { + PLOG(PL_DEBUG, "NormSession::SenderQueueAppCmd() node>%lu cmd transmission completed ...\n", + LocalNodeId()); + Notify(NormController::TX_CMD_SENT, NULL, NULL); + } + return true; + } + else + { + PLOG(PL_FATAL, " NormSession::SenderQueueAppCmd() node>%lu message_pool exhausted!\n", + LocalNodeId()); + return false; + } +} // end NormSession::SenderQueueAppCmd() + +bool NormSession::OnCmdTimeout(ProtoTimer& theTimer) +{ + if (!tx_timer.IsActive()) PromptSender(); + return true; +} // end NormSession::OnCmdTimeout() + +void NormSession::ActivateFlowControl(double delay, NormObjectId objectId, NormController::Event event) +{ + flow_control_object = objectId; + flow_control_event = event; + flow_control_timer.SetInterval(delay); + if (flow_control_timer.IsActive()) + flow_control_timer.Reschedule(); + else + ActivateTimer(flow_control_timer); +} // end NormSession::ActivateFlowControl() + +bool NormSession::OnFlowControlTimeout(ProtoTimer& theTimer) +{ + NormObject* object = tx_table.Find(flow_control_object); + if (NULL == object) + { + PLOG(PL_WARN, "NormSession::OnFlowControlTimeout() flow_control_object removed?!\n"); + // Throw a TX_QUEUE_EMPTY just in case ??? + //Notify(NormController::TX_QUEUE_EMPTY, (NormSenderNode*)NULL, (NormObject*)NULL); + return true; + } + double deltaTime; + if (object->IsStream()) + { + // A stream was flow-controlled, so check its stream nack age, etc + NormBlock* block = static_cast(object)->StreamBlockLo(); + if (NULL == block) + { + // No blocks in stream buffer, thus it is actually empty + // Notify(NormController::TX_QUEUE_VACANCY, (NormSenderNode*)NULL, object); + posted_tx_queue_empty = true; + Notify(NormController::TX_QUEUE_EMPTY, (NormSenderNode*)NULL, object);//(NormObject*)NULL); + return true; + } + deltaTime = GetFlowControlDelay() - block->GetNackAge(); + if (deltaTime < 1.0e-06) + { + // no recent NACKing for "oldest block", so post EMPTY/VACANCY if non-pending + // TBD - retest this with mixed stream/object sender sessions + if (!block->IsPending()) + { + posted_tx_queue_empty = (NormController::TX_QUEUE_EMPTY == flow_control_event); + Notify(flow_control_event, (NormSenderNode*)NULL, object); + } + return true; + } + } + else + { + // The tx cache (queue) is being flow controlled ... + deltaTime = GetFlowControlDelay() - object->GetNackAge(); + if (deltaTime < 1.0e-06) + { + // no recent NACKing, so if non-pending, dispatch queue EMPTY or VACANCY + if (!object->IsRepairPending() && !object->IsPending()) + { + posted_tx_queue_empty = (NormController::TX_QUEUE_EMPTY == flow_control_event); + Notify(flow_control_event, (NormSenderNode*)NULL, (NormObject*)NULL); + } + return true; + } + } + // Extend flow control timeout due to recent activity + // NOTE that above we limited the minimum "deltaTime" to 1.0-06 ... otherwise + // ProtoTime 1usec precision limitation put us into an infinite loop in + // _simulation_ environments where perfect scheduling occurs w/ zero processing time + theTimer.SetInterval(deltaTime); + theTimer.Reschedule(); + return false; +} // end NormSession::OnFlowControlTimeout() -bool NormSession::ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd) +bool NormSession::SenderBuildRepairAdv(NormCmdRepairAdvMsg& cmd) { // Build a NORM_CMD(REPAIR_ADV) message with current pending repair state. NormRepairRequest req; @@ -2954,7 +3953,7 @@ bool NormSession::ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormSession::ServerBuildRepairAdv() warning: full msg\n"); + PLOG(PL_FATAL, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case prevForm = NormRepairRequest::INVALID; break; @@ -2972,13 +3971,13 @@ bool NormSession::ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd) case 1: case 2: req.SetForm(NormRepairRequest::ITEMS); - req.AppendRepairItem(firstId, 0, ndata, 0); // (TBD) error check + req.AppendRepairItem(fec_id, fec_m, firstId, 0, ndata, 0); // (TBD) error check if (2 == objectCount) - req.AppendRepairItem(currentId, 0, ndata, 0); // (TBD) error check + req.AppendRepairItem(fec_id, fec_m, currentId, 0, ndata, 0); // (TBD) error check break; default: req.SetForm(NormRepairRequest::RANGES); - req.AppendRepairRange(firstId, 0, ndata, 0, // (TBD) error check + req.AppendRepairRange(fec_id, fec_m, firstId, 0, ndata, 0, // (TBD) error check currentId, 0, ndata, 0); break; } @@ -2991,7 +3990,7 @@ bool NormSession::ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd) { if (0 == cmd.PackRepairRequest(req)) { - DMSG(0, "NormSession::ServerBuildRepairAdv() warning: full msg\n"); + PLOG(PL_FATAL, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case prevForm = NormRepairRequest::INVALID; break; @@ -3009,11 +4008,11 @@ bool NormSession::ServerBuildRepairAdv(NormCmdRepairAdvMsg& cmd) if (NormRepairRequest::INVALID != prevForm) { if (0 == cmd.PackRepairRequest(req)) - DMSG(0, "NormSession::ServerBuildRepairAdv() warning: full msg\n"); + PLOG(PL_ERROR, "NormSession::SenderBuildRepairAdv() warning: full msg\n"); // (TBD) set NORM_REPAIR_ADV_LIMIT flag in this case } return true; -} // end NormSession::ServerBuildRepairAdv() +} // end NormSession::SenderBuildRepairAdv() bool NormSession::OnRepairTimeout(ProtoTimer& /*theTimer*/) { @@ -3021,9 +4020,8 @@ bool NormSession::OnRepairTimeout(ProtoTimer& /*theTimer*/) if (0 != repair_timer.GetRepeatCount()) { // NACK aggregation period has ended. (incorporate accumulated repair requests) - DMSG(4, "NormSession::OnRepairTimeout() node>%lu server NACK aggregation time ended.\n", + PLOG(PL_DEBUG, "NormSession::OnRepairTimeout() node>%lu sender NACK aggregation time ended.\n", LocalNodeId()); - NormObjectTable::Iterator iterator(tx_table); NormObject* obj; while ((obj = iterator.GetNextObject())) @@ -3031,7 +4029,7 @@ bool NormSession::OnRepairTimeout(ProtoTimer& /*theTimer*/) NormObjectId objectId = obj->GetId(); if (tx_repair_mask.Test(objectId)) { - DMSG(6, "NormSession::OnRepairTimeout() node>%lu tx reset obj>%hu ...\n", + PLOG(PL_TRACE, "NormSession::OnRepairTimeout() node>%lu tx reset obj>%hu ...\n", LocalNodeId(), (UINT16)objectId); if (obj->IsStream()) obj->TxReset(((NormStreamObject*)obj)->StreamBufferLo()); @@ -3039,42 +4037,43 @@ bool NormSession::OnRepairTimeout(ProtoTimer& /*theTimer*/) obj->TxReset(); tx_repair_mask.Unset(objectId); if (!tx_pending_mask.Set(objectId)) - DMSG(0, "NormSession::OnRepairTimeout() tx_pending_mask.Set(%hu) error (1)\n", + PLOG(PL_ERROR, "NormSession::OnRepairTimeout() tx_pending_mask.Set(%hu) error (1)\n", (UINT16)objectId); } else { - DMSG(6, "NormSession::OnRepairTimeout() node>%lu activating obj>%hu repairs ...\n", + PLOG(PL_DEBUG, "NormSession::OnRepairTimeout() node>%lu activating obj>%hu repairs ...\n", LocalNodeId(), (UINT16)objectId); if (obj->ActivateRepairs()) { - DMSG(6, "NormSession::OnRepairTimeout() node>%lu activated obj>%hu repairs ...\n", + PLOG(PL_TRACE, "NormSession::OnRepairTimeout() node>%lu activated obj>%hu repairs ...\n", LocalNodeId(), (UINT16)objectId); if (!tx_pending_mask.Set(objectId)) - DMSG(0, "NormSession::OnRepairTimeout() tx_pending_mask.Set(%hu) error (2)\n", + PLOG(PL_ERROR, "NormSession::OnRepairTimeout() tx_pending_mask.Set(%hu) error (2)\n", (UINT16)objectId); } } } // end while (iterator.GetNextObject()) - PromptServer(); + PromptSender(); // BACKOFF related code // Holdoff initiation of new repair cycle for one GRTT // (TBD) for unicast sessions, use CLR RTT ??? //double holdoffInterval = backoff_factor > 0.0 ? grtt_advertised : 0.0; double holdoffInterval = grtt_advertised; repair_timer.SetInterval(holdoffInterval); // repair holdoff interval = 1*GRTT - DMSG(4, "NormSession::OnRepairTimeout() node>%lu starting server " + PLOG(PL_DEBUG, "NormSession::OnRepairTimeout() node>%lu starting sender " "NACK holdoff timer (%lf sec)...\n", LocalNodeId(), holdoffInterval); } else { // REPAIR holdoff interval has now ended. - DMSG(4, "NormSession::OnRepairTimeout() node>%lu server holdoff time ended.\n", + PLOG(PL_DEBUG, "NormSession::OnRepairTimeout() node>%lu sender holdoff time ended.\n", LocalNodeId()); } return true; } // end NormSession::OnRepairTimeout() + // (TBD) Should pass current system time to ProtoTimer timeout handlers // for more efficiency ... bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) @@ -3111,7 +4110,7 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) ext.SetCCRate(NormQuantizeRate(suppress_rate)); } - ServerBuildRepairAdv(adv); + SenderBuildRepairAdv(adv); msg = (NormMsg*)&adv; } @@ -3128,13 +4127,19 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) if (SendMessage(*msg)) { if (tx_rate > 0.0) - tx_timer.SetInterval(msg->GetLength() / tx_rate); + //tx_timer.SetInterval(msg->GetLength() / tx_rate); + tx_timer.SetInterval(GetTxInterval(msg->GetLength(), tx_rate)); if (advertise_repairs) advertise_repairs = false; else ReturnMessageToPool(msg); // Pre-serve to allow pre-prompt for empty tx queue - if (message_queue.IsEmpty() && IsServer()) Serve(); + // (TBD) do this in a better way ??? There is a slight chance + // that with this approach some new data may get pre-queued + // when an interim repair request should be serviced first + // instead ??? + //if (message_queue.IsEmpty() && IsSender()) Serve(); + return true; // reinstall tx_timer } else { @@ -3146,25 +4151,37 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) // Make sure the tx_timer interval is non-ZERO // (this avoids a sort of infinite loop that can occur // under certain conditions) + + // old code - time out and try again + /* if (tx_rate > 0.0) - tx_timer.SetInterval(msg->GetLength() / tx_rate); + //tx_timer.SetInterval(msg->GetLength() / tx_rate); + tx_timer.SetInterval(GetTxInterval(msg->GetLength(), tx_rate)); else if (0.0 == tx_timer.GetInterval()) - tx_timer.SetInterval(0.1); + tx_timer.SetInterval(0.01); + return true; + */ + // new code - use async i/o to cue retry + //ASSERT(tx_timer.IsActive()); + if (tx_timer.IsActive()) + tx_timer.Deactivate(); + tx_socket->StartOutputNotification(); + return false; + } - return true; // reinstall tx_timer } else { - // 1) Prompt for next server message - if (IsServer()) Serve(); + // 1) Prompt for next sender message + if (IsSender()) Serve(); if (message_queue.IsEmpty()) { tx_timer.Deactivate(); // Check that any possible notifications posted in // the previous call to Serve() may have caused a - // change in server state making it ready to send - //if (IsServer()) Serve(); + // change in sender state making it ready to send + //if (IsSender()) Serve(); return false; } else @@ -3175,14 +4192,15 @@ bool NormSession::OnTxTimeout(ProtoTimer& /*theTimer*/) } } // end NormSession::OnTxTimeout() - bool NormSession::SendMessage(NormMsg& msg) { - bool isClientMsg = false; + bool isReceiverMsg = false; bool isProbe = false; // Fill in any last minute timestamps // (TBD) fill in InstanceId fields on all messages as needed + // We need "fec_m" for the message for NormTrace() purposes + UINT8 fecM = fec_m; // assume it's a sender message (will be overridden otherwise) switch (msg.GetType()) { case NormMsg::INFO: @@ -3191,6 +4209,7 @@ bool NormSession::SendMessage(NormMsg& msg) msg.SetSequence(tx_sequence++); // (TBD) set for session dst msgs break; case NormMsg::CMD: + { ((NormCmdMsg&)msg).SetInstanceId(instance_id); switch (((NormCmdMsg&)msg).GetFlavor()) { @@ -3209,55 +4228,70 @@ bool NormSession::SendMessage(NormMsg& msg) } msg.SetSequence(tx_sequence++); // (TBD) set for session dst msgs break; - + } case NormMsg::NACK: { - isClientMsg = true; + msg.SetSequence(0); // TBD - set per destination + isReceiverMsg = true; NormNackMsg& nack = (NormNackMsg&)msg; - NormServerNode* theServer = - (NormServerNode*)server_tree.FindNodeById(nack.GetServerId()); - ASSERT(theServer); + NormSenderNode* theSender = + (NormSenderNode*)sender_tree.FindNodeById(nack.GetSenderId()); + ASSERT(NULL != theSender); + fecM = theSender->GetFecFieldSize(); struct timeval currentTime; ProtoSystemTime(currentTime); struct timeval grttResponse; - theServer->CalculateGrttResponse(currentTime, grttResponse); + theSender->CalculateGrttResponse(currentTime, grttResponse); nack.SetGrttResponse(grttResponse); break; } - case NormMsg::ACK: { - isClientMsg = true; + msg.SetSequence(0); // TBD - set per destination + isReceiverMsg = true; NormAckMsg& ack = (NormAckMsg&)msg; - NormServerNode* theServer = - (NormServerNode*)server_tree.FindNodeById(ack.GetServerId()); - ASSERT(theServer); + NormSenderNode* theSender = + (NormSenderNode*)sender_tree.FindNodeById(ack.GetSenderId()); + ASSERT(NULL != theSender); + fecM = theSender->GetFecFieldSize(); struct timeval grttResponse; struct timeval currentTime; ProtoSystemTime(currentTime); - theServer->CalculateGrttResponse(currentTime, grttResponse); + theSender->CalculateGrttResponse(currentTime, grttResponse); ack.SetGrttResponse(grttResponse); break; } - default: break; } // Fill in common message fields - msg.SetSourceId(local_node_id); UINT16 msgSize = msg.GetLength(); // Possibly drop some tx messages for testing purposes bool drop = (UniformRand(100.0) < tx_loss_rate); - if (drop || (isClientMsg && client_silent)) + + if (isReceiverMsg && receiver_silent) + { + // don't send receiver messages if "silent receiver" + // TBD - perhaps we should make sure silent receivers + // never enqueue any receiver messages. But we + // did this to make sure all integrity of timer + // state interdependencies wasn't messed up + return true; // wasn't sent but wasn't supposed to + } + else if (drop) { //DMSG(0, "TX MESSAGE DROPPED! (tx_loss_rate:%lf\n", tx_loss_rate); - if (!(isClientMsg && client_silent)) + // "Pretend" like dropped message was sent for trace and timing purposes + if (trace) { - // Update sent rate tracker even if dropped (for testing/debugging) - sent_accumulator += msgSize; - nominal_packet_size += 0.05 * (((double)msgSize) - nominal_packet_size); + struct timeval currentTime; + ProtoSystemTime(currentTime); + NormTrace(currentTime, LocalNodeId(), msg, true, fecM); } + // Update sent rate tracker even if dropped (for testing/debugging) + sent_accumulator.Increment(msgSize); + nominal_packet_size += 0.01 * (((double)msgSize) - nominal_packet_size); } else { @@ -3268,20 +4302,21 @@ bool NormSession::SendMessage(NormMsg& msg) { struct timeval currentTime; ProtoSystemTime(currentTime); - NormTrace(currentTime, LocalNodeId(), msg, true); + NormTrace(currentTime, LocalNodeId(), msg, true, fecM); } // To keep track of _actual_ sent rate - sent_accumulator += msgSize; + sent_accumulator.Increment(msgSize); // Update nominal packet size - nominal_packet_size += 0.05 * (((double)msgSize) - nominal_packet_size); + nominal_packet_size += 0.01 * (((double)msgSize) - nominal_packet_size); } else { - DMSG(8, "NormSession::SendMessage() sendto() error: %s\n", GetErrorString()); + PLOG(PL_DETAIL, "NormSession::SendMessage() sendto() error: %s\n", GetErrorString()); tx_sequence--; return false; } } + if (isProbe) { probe_pending = false; @@ -3293,7 +4328,7 @@ bool NormSession::SendMessage(NormMsg& msg) ActivateTimer(probe_timer); } } - else if (!isClientMsg) + else if (!isReceiverMsg) { probe_data_check = false; if (!probe_pending && probe_reset) @@ -3351,7 +4386,7 @@ void NormSession::SetGrttProbingMode(ProbingMode probingMode) break; case PROBE_PASSIVE: probe_proactive = false; - if (IsServer()) + if (IsSender()) { if (!probe_timer.IsActive()) { @@ -3366,7 +4401,7 @@ void NormSession::SetGrttProbingMode(ProbingMode probingMode) break; case PROBE_ACTIVE: probe_proactive = true; - if (IsServer()) + if (IsSender()) { if (!probe_timer.IsActive()) { @@ -3387,18 +4422,13 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) { // 1) Temporarily kill probe_timer if CMD(CC) not yet tx'd // (or if data has not been sent since last probe) - if (probe_pending || (data_active && probe_data_check)) + if (probe_pending || (data_active && probe_data_check) || (0.0 == tx_rate)) { probe_reset = true; if (probe_timer.IsActive()) probe_timer.Deactivate(); return false; } - else if (0.0 == tx_rate) - { - // Sender paused, so just skip probing until transmission is resumed - return true; - } // 2) Update grtt_estimate _if_ sufficient time elapsed. // This new code allows more liberal downward adjustment of @@ -3477,8 +4507,11 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) grtt_advertised = NormUnquantizeRtt(grtt_quantized); } if (grttQuantizedOld != grtt_quantized) - DMSG(4, "NormSession::OnProbeTimeout() node>%lu decreased to new grtt to: %lf sec\n", + { + Notify(NormController::GRTT_UPDATED, (NormSenderNode*)NULL, (NormObject*)NULL); + PLOG(PL_DEBUG, "NormSession::OnProbeTimeout() node>%lu decreased to new grtt to: %lf sec\n", LocalNodeId(), grtt_advertised); + } grtt_response = false; // reset } grtt_age = 0.0; @@ -3495,8 +4528,9 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) NormCmdCCMsg* cmd = (NormCmdCCMsg*)GetMessageFromPool(); if (!cmd) { - DMSG(0, "NormSession::OnProbeTimeout() node>%lu message_pool empty! can't probe\n", + PLOG(PL_FATAL, "NormSession::OnProbeTimeout() node>%lu message_pool empty! can't probe\n", LocalNodeId()); + ASSERT(0); return true; } cmd->Init(); @@ -3507,17 +4541,16 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) // defer SetSendTime() to when message is being sent (in OnTxTimeout()) cmd->SetCCSequence(cc_sequence++); + // Insert NORM-CC header extension, if applicable + // (Note we set the extension "rate" _after_ AdjustRate() done below) + NormCCRateExtension ext; if (probe_proactive) - { - NormCCRateExtension ext; cmd->AttachExtension(ext); - ext.SetSendRate(NormQuantizeRate(tx_rate)); - } - double probeInterval; if (cc_enable) { // Iterate over cc_node_list and append cc_nodes ... + // (we also check cc_node "activity status here) NormNodeListIterator iterator(cc_node_list); NormCCNode* next; while ((next = (NormCCNode*)iterator.GetNextNode())) @@ -3553,40 +4586,29 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) feedbackAge += 1.0e-06*((double)(currentTime.tv_usec - feedbackTime.tv_usec)); else feedbackAge -= 1.0e-06*((double)(feedbackTime.tv_usec - currentTime.tv_usec));*/ - double maxFeedbackAge = 5 * MAX(grtt_advertised, next->GetRtt()); + double maxFeedbackAge = 20 * MAX(grtt_advertised, next->GetRtt()); // Safety bound to compensate for computer clock coarseness // and possible sluggish feedback from slower machines // at higher norm data rates (keeps rate from being // prematurely reduced) if (maxFeedbackAge <(10*NORM_TICK_MIN)) maxFeedbackAge = (10*NORM_TICK_MIN); - unsigned int ccSeqDelta = cc_sequence - next->GetCCSequence() - 2; - if ((feedbackAge > maxFeedbackAge) && (ccSeqDelta > 5)) + INT16 ccSeqDelta = cc_sequence - next->GetCCSequence(); + if ((feedbackAge > maxFeedbackAge) && (ccSeqDelta > (INT16)(20 * probe_count))) { - DMSG(4, "Deactivating cc node feedbackAge:%lf sec maxAge:%lf sec ccSeqDelta:%u\n", + PLOG(PL_DEBUG, "Deactivating cc node feedbackAge:%lf sec maxAge:%lf sec ccSeqDelta:%u\n", feedbackAge, maxFeedbackAge, ccSeqDelta); next->SetActive(false); } } } - AdjustRate(false); - - // Determine next probe_interval - if (data_active) - { - const NormCCNode* clr = static_cast(cc_node_list.Head()); - probeInterval = (clr && clr->IsActive()) ? MIN(grtt_advertised, clr->GetRtt()) : grtt_advertised; - } - else - { - probeInterval = grtt_interval; - } - } - else - { - // Determine next probe_interval - probeInterval = grtt_interval; - } + } // end if (cc_enable) + + if (probe_proactive) + ext.SetSendRate(NormQuantizeRate(tx_rate)); + + + double probeInterval = GetProbeInterval(); /*// perhaps this instead of the commented out probe_reset case??? double nominalInterval = ((double)segment_size)/((double)tx_rate); if (nominalInterval > grtt_max) nominalInterval = grtt_max; @@ -3602,6 +4624,50 @@ bool NormSession::OnProbeTimeout(ProtoTimer& /*theTimer*/) } // end NormSession::OnProbeTimeout() +double NormSession::GetProbeInterval() +{ + if (cc_enable && data_active) + { + const NormCCNode* clr = static_cast(cc_node_list.Head()); + if (NULL != clr) + { + double probeInterval = (clr->IsActive() ? MIN(grtt_advertised, clr->GetRtt()) : grtt_advertised); + // For "large" RTT (100 msec or bigger), we need to possibly probe more + // often depending on transmit rate, to make NORM-CC perform a little better + // The "probeCount" calculated here is based on sending no more probes per RTT + // than 0.25 * the number of data packets that would be sent per RTT. + // (although we floor the probeCount at 1 (i.e., the usual 1 probe per RTT) + // Note that no more than a few (e.g. 3) probes per RTT provides performance benefit + unsigned int probeCount = (unsigned int)(0.25 * tx_rate * probeInterval / (double)segment_size); + if (probeCount < 1) probeCount = 1; + if (clr->GetRtt() > 0.200) + { + if (probeCount > 3) probeCount = 3; + } + else if (clr->GetRtt() > 0.100) + { + if (probeCount > 2) probeCount = 2; + } + else + { + probeCount = 1; + } + if (1 != probe_count) probeCount = probe_count; + + // Don't send more than one CLR probe per RTT during slow_start + return (cc_slow_start ? probeInterval : (probeInterval / (double)probeCount)); + } + else + { + return grtt_advertised; + } + } + else + { + return grtt_interval; + } +} // end NormSession::GetProbeInterval() + void NormSession::AdjustRate(bool onResponse) { const NormCCNode* clr = (const NormCCNode*)cc_node_list.Head(); @@ -3619,16 +4685,18 @@ void NormSession::AdjustRate(bool onResponse) { // Adjust rate based on CLR feedback and // adjust probe schedule - ASSERT(clr); + ASSERT(NULL != clr); // (TBD) check feedback age if (cc_slow_start) { txRate = clr->GetRate(); if (GetDebugLevel() >= 6) { - double sentRate = 8.0e-03*((double)sent_accumulator) / (report_timer.GetInterval() - report_timer.GetTimeRemaining()); - DMSG(6, "NormSession::AdjustRate(slow start) clr>%lu newRate>%lf (oldRate>%lf sentRate>%lf clrRate>%lf\n", + double sentRate = 8.0e-03*sent_accumulator.GetScaledValue(1.0 / (report_timer.GetInterval() - report_timer.GetTimeRemaining())); + PLOG(PL_DETAIL, "NormSession::AdjustRate(slow start) clr>%lu newRate>%lf (oldRate>%lf sentRate>%lf clrRate>%lf\n", clr->GetId(), 8.0e-03*txRate, 8.0e-03*tx_rate, sentRate, 8.0e-03*clr->GetRate()); + //TRACE("NormSession::AdjustRate(slow start) clr>%lu newRate>%lf (oldRate>%lf sentRate>%lf clrRate>%lf\n", + // clr->GetId(), 8.0e-03*txRate, 8.0e-03*tx_rate, sentRate, 8.0e-03*clr->GetRate()); } } else @@ -3636,31 +4704,49 @@ void NormSession::AdjustRate(bool onResponse) double clrRate = clr->GetRate(); if (clrRate > txRate) { - double linRate = txRate + segment_size; - txRate = MIN(clrRate, linRate); + double maxRate = txRate*2; + txRate = MIN(clrRate, maxRate); } else { txRate = clrRate; - } - DMSG(6, "NormSession::AdjustRate(stdy state) clr>%lu newRate>%lf (rtt>%lf loss>%lf)\n", - clr->GetId(), 8.0e-03*txRate, clr->GetRtt(), clr->GetLoss()); + } + + // Here, we use the most recent CLR rtt sample to "damp" oscillation + double damper = clr->GetRttSqMean() / sqrt(clr->GetRttSample()); + if (damper < 0.5) + damper = 0.5; + else if (damper > 2.0) + damper = 2.0; + txRate *= damper; + PLOG(PL_DETAIL, "NormSession::AdjustRate(stdy state) clr>%lu newRate>%lf (rtt>%lf loss>%lf)\n", + clr->GetId(), 8.0e-03*txRate, clr->GetRtt(), clr->GetLoss()); + //TRACE("NormSession::AdjustRate(stdy state) clr>%lu newRate>%lf (rtt>%lf loss>%lf clrRate>%9.3lf damper>%lf)\n", + // clr->GetId(), 8.0e-03*txRate, clr->GetRtt(), clr->GetLoss(), 8.0e-03*clr->GetRate(), damper); } - // Adjust the probe timeout right away - /* double probeInterval = probe_timer.GetInterval(); - if (probeInterval > ccRtt) + } + if (!address.IsMulticast()) + { + // For unicast, adjust the probe timeout right away + double probeInterval = GetProbeInterval(); // based on CLR RTT, etc + if (probe_timer.GetInterval() > probeInterval) { - double elapsed = probeInterval - probe_timer.GetTimeRemaining(); - probeInterval = (ccRtt > elapsed) ? (ccRtt - elapsed) : 0.0; + // reduce to speed up rate increase + double elapsed = probe_timer.GetInterval() - probe_timer.GetTimeRemaining(); + if (probeInterval > elapsed) + probeInterval -= elapsed; + else + probeInterval = 0.0; probe_timer.SetInterval(probeInterval); if (probe_timer.IsActive()) probe_timer.Reschedule(); - } */ + } } } else if (!data_active) { // reduce rate if no active data transmission // (TBD) Perhaps we want to be less aggressive here someday + //TRACE("DATA INACTIVITY!\n"); txRate *= 0.5; } else if (clr && clr->IsActive()) @@ -3684,7 +4770,7 @@ void NormSession::AdjustRate(bool onResponse) } else { - // reduce rate if no active clr + // reduce rate by half if no active clr txRate *= 0.5; } @@ -3698,8 +4784,9 @@ void NormSession::AdjustRate(bool onResponse) else { // Don't let txRate below MIN(one segment per grtt, one segment per second) - minRate = ((double)segment_size) / grtt_measured; - if (minRate > ((double)(segment_size))) + if (grtt_measured > 1.0) + minRate = ((double)segment_size) / grtt_measured; + else minRate = (double)(segment_size); } if (txRate <= minRate) @@ -3717,17 +4804,28 @@ void NormSession::AdjustRate(bool onResponse) } if ((tx_rate_max >= 0.0) && (txRate > tx_rate_max)) txRate = tx_rate_max; - if (txRate != tx_rate) SetTxRateInternal(txRate); - + if (txRate != tx_rate) + { + if (cc_adjust) SetTxRateInternal(txRate); + posted_tx_rate_changed = true; + Notify(NormController::TX_RATE_CHANGED, (NormSenderNode*)NULL, (NormObject*)NULL); + } + struct timeval currentTime; ::ProtoSystemTime(currentTime); double theTime = (double)currentTime.tv_sec + 1.0e-06 * ((double)currentTime.tv_usec); - DMSG(8, "ServerRateTracking time>%lf rate>%lf rtt>%lf loss>%lf\n\n", theTime, 8.0e-03*tx_rate, ccRtt, ccLoss); + PLOG(PL_DEBUG, "SenderRateTracking time>%lf rate>%lf rtt>%lf loss>%lf\n", theTime, 8.0e-03*txRate, ccRtt, ccLoss); + //TRACE("SenderRateTracking time>%lf rate>%lf rtt>%lf loss>%lf\n", theTime, 8.0e-03*txRate, ccRtt, ccLoss); + //double calcRate = NormSession::CalculateRate(nominal_packet_size, ccRtt, ccLoss); + //TRACE("SenderRateTracking time>%lf rate>%lf clrRate>%lf rtt>%lf loss>%lf calcRate>%lf size>%lf%s\n\n", + // theTime, 8.0e-03*txRate, clr ? 8.0e-03*clr->GetRate() : 0.0, ccRtt, ccLoss, calcRate*8.0e-03, + // nominal_packet_size, cc_slow_start ? " (slow start)" : ""); + //ASSERT((NULL == clr) || (txRate <= clr->GetRate())); } // end NormSession::AdjustRate() bool NormSession::OnReportTimeout(ProtoTimer& /*theTimer*/) { - // Client reporting (just print out for now) + // Receiver reporting (just print out for now) struct timeval currentTime; ProtoSystemTime(currentTime); #ifdef _WIN32_WCE @@ -3739,55 +4837,60 @@ bool NormSession::OnReportTimeout(ProtoTimer& /*theTimer*/) timeStruct.tm_hour = timeStruct.tm_hour % 24; struct tm* ct = &timeStruct; #else - struct tm* ct = gmtime((time_t*)¤tTime.tv_sec); + time_t secs = (time_t)currentTime.tv_sec; + struct tm* ct = gmtime(&secs); #endif // if/else _WIN32_WCE - DMSG(2, "REPORT time>%02d:%02d:%02d.%06lu node>%lu ***************************************\n", + PLOG(PL_INFO, "REPORT time>%02d:%02d:%02d.%06lu node>%lu ***************************************\n", ct->tm_hour, ct->tm_min, ct->tm_sec, currentTime.tv_usec, LocalNodeId()); - if (IsServer()) + if (IsSender()) { - DMSG(2, "Local status:\n"); - double sentRate = 8.0e-03*((double)sent_accumulator) / report_timer.GetInterval(); // kbps - sent_accumulator = 0; - DMSG(2, " txRate>%9.3lf kbps sentRate>%9.3lf grtt>%lf\n", + PLOG(PL_INFO, "Local status:\n"); + double sentRate = 8.0e-03*sent_accumulator.GetScaledValue(1.0 / report_timer.GetInterval()); // kbps + sent_accumulator.Reset(); + PLOG(PL_INFO, " txRate>%9.3lf kbps sentRate>%9.3lf grtt>%lf\n", 8.0e-03*tx_rate, sentRate, grtt_advertised); if (cc_enable) { const NormCCNode* clr = (const NormCCNode*)cc_node_list.Head(); if (clr) - DMSG(2, " clr>%lu rate>%9.3lf rtt>%lf loss>%lf %s\n", clr->GetId(), + PLOG(PL_INFO, " clr>%lu rate>%9.3lf rtt>%lf loss>%lf %s\n", clr->GetId(), 8.0e-03*clr->GetRate(), clr->GetRtt(), clr->GetLoss(), cc_slow_start ? "(slow_start)" : ""); } } - if (IsClient()) + if (IsReceiver()) { - NormNodeTreeIterator iterator(server_tree); - NormServerNode* next; - while ((next = (NormServerNode*)iterator.GetNextNode())) + NormNodeTreeIterator iterator(sender_tree); + NormSenderNode* next; + while ((next = (NormSenderNode*)iterator.GetNextNode())) { - DMSG(2, "Remote sender>%lu\n", next->GetId()); - double rxRate = 8.0e-03*((double)next->RecvTotal()) / report_timer.GetInterval(); // kbps - double rxGoodput = 8.0e-03*((double)next->RecvGoodput()) / report_timer.GetInterval(); // kbps + PLOG(PL_INFO, "Remote sender>%lu\n", next->GetId()); + double rxRate = 8.0e-03*next->GetRecvRate(report_timer.GetInterval()); // kbps + double rxGoodput = 8.0e-03*next->GetRecvGoodput(report_timer.GetInterval()); // kbps next->ResetRecvStats(); - DMSG(2, " rxRate>%9.3lf kbps rx_goodput>%9.3lf kbps\n", rxRate, rxGoodput); - DMSG(2, " rxObjects> completed>%lu pending>%lu failed:%lu\n", + PLOG(PL_INFO, " rxRate>%9.3lf kbps rx_goodput>%9.3lf kbps\n", rxRate, rxGoodput); + PLOG(PL_INFO, " rxObjects> completed>%lu pending>%lu failed>%lu\n", next->CompletionCount(), next->PendingCount(), next->FailureCount()); - DMSG(2, " fecBufferUsage> current>%lu peak>%lu (overuns>%lu)\n", next->CurrentBufferUsage(), + PLOG(PL_INFO, " fecBufferUsage> current>%lu peak>%lu overuns>%lu\n", next->CurrentBufferUsage(), next->PeakBufferUsage(), next->BufferOverunCount()); - DMSG(2, " resyncs>%lu nacks>%lu suppressed>%lu\n", + PLOG(PL_INFO, " strBufferUsage> current>%lu peak>%lu overuns>%lu\n", next->CurrentStreamBufferUsage(), + next->PeakStreamBufferUsage(), + next->StreamBufferOverunCount()); + PLOG(PL_INFO, " resyncs>%lu nacks>%lu suppressed>%lu\n", next->ResyncCount() ? next->ResyncCount() - 1 : 0, // "ResyncCount()" is reall "SyncCount()" next->NackCount(), next->SuppressCount()); } - } // end if (IsClient()) - DMSG(2, "***************************************************************************\n"); + } // end if (IsReceiver()) + PLOG(PL_INFO, "***************************************************************************\n"); return true; } // end NormSession::OnReportTimeout() NormSessionMgr::NormSessionMgr(ProtoTimerMgr& timerMgr, - ProtoSocket::Notifier& socketNotifier) - : timer_mgr(timerMgr), socket_notifier(socketNotifier), + ProtoSocket::Notifier& socketNotifier, + ProtoChannel::Notifier* channelNotifier) + : timer_mgr(timerMgr), socket_notifier(socketNotifier), channel_notifier(channelNotifier), controller(NULL), top_session(NULL) { } @@ -3813,37 +4916,40 @@ NormSession* NormSessionMgr::NewSession(const char* sessionAddress, { if ((NORM_NODE_ANY == localNodeId) || (NORM_NODE_NONE == localNodeId)) { +#ifndef SIMULATE // Use local ip address to assign default localNodeId ProtoAddress localAddr; if (!localAddr.ResolveLocalAddress()) { - DMSG(0, "NormSessionMgr::NewSession() local address lookup error\n"); + PLOG(PL_ERROR, "NormSessionMgr::NewSession() local address lookup error\n"); return ((NormSession*)NULL); } // (TBD) test IPv6 "EndIdentifier" ??? localNodeId = localAddr.EndIdentifier(); +#else + localNodeId = NORM_NODE_ANY - 1; +#endif } ProtoAddress theAddress; if (!theAddress.ResolveFromString(sessionAddress)) { - DMSG(0, "NormSessionMgr::NewSession() session address lookup error!\n"); + PLOG(PL_ERROR, "NormSessionMgr::NewSession() session address \"%s\" lookup error!\n", sessionAddress); return ((NormSession*)NULL); } theAddress.SetPort(sessionPort); NormSession* theSession = new NormSession(*this, localNodeId); if (!theSession) { - DMSG(0, "NormSessionMgr::NewSession() new session error: %s\n", + PLOG(PL_ERROR, "NormSessionMgr::NewSession() new session error: %s\n", GetErrorString()); return ((NormSession*)NULL); } theSession->SetAddress(theAddress); - theSession->SetTxPort(sessionPort); /* JPH 4/24/06 */ // Add new session to our session list theSession->next = top_session; top_session = theSession; return theSession; -} // end NormSessionMgr::NewSession(); +} // end NormSessionMgr::NewSession() void NormSessionMgr::DeleteSession(class NormSession* theSession) { diff --git a/common/normSimAgent.cpp b/src/common/normSimAgent.cpp similarity index 50% rename from common/normSimAgent.cpp rename to src/common/normSimAgent.cpp index 37d0929..a726407 100644 --- a/common/normSimAgent.cpp +++ b/src/common/normSimAgent.cpp @@ -9,20 +9,26 @@ NormSimAgent::NormSimAgent(ProtoTimerMgr& timerMgr, ProtoSocket::Notifier& socketNotifier) : msg_sink(NULL), session_mgr(timerMgr, socketNotifier), session(NULL), address(NULL), port(0), ttl(3), - tx_rate(NormSession::DEFAULT_TRANSMIT_RATE), - cc_enable(false), unicast_nacks(false), silent_client(false), + tx_rate(NormSession::DEFAULT_TRANSMIT_RATE), probe_count(1), + cc_enable(false), ecn_enable(false), cc_mode(NORM_FIXED), + unicast_nacks(false), silent_receiver(false), backoff_factor(NormSession::DEFAULT_BACKOFF_FACTOR), segment_size(1024), ndata(32), nparity(16), auto_parity(0), extra_parity(0), group_size(NormSession::DEFAULT_GSIZE_ESTIMATE), grtt_estimate(NormSession::DEFAULT_GRTT_ESTIMATE), - tx_buffer_size(1024*1024), rx_buffer_size(1024*1024), + tx_buffer_size(1024*1024), + tx_cache_min(NormSession::DEFAULT_TX_CACHE_MIN), + tx_cache_max(NormSession::DEFAULT_TX_CACHE_MAX), + tx_cache_size(NormSession::DEFAULT_TX_CACHE_SIZE), + rx_buffer_size(1024*1024), rx_cache_max(NormSession::DEFAULT_RX_CACHE_MAX), tx_object_size(0), tx_object_interval(0.0), 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), msg_sync(false), mgen_bytes(0), mgen_pending_bytes(0), - tracing(false), tx_loss(0.0), rx_loss(0.0) + tracing(false), log_file_ptr(NULL), tx_loss(0.0), rx_loss(0.0) { // Bind NormSessionMgr to this agent and simulation environment session_mgr.SetController(static_cast(this)); @@ -45,38 +51,47 @@ const char* const NormSimAgent::cmd_list[] = "+debug", // debug level "+log", // log file name "-trace", // message tracing + "+flog", // set "file" send/recv log file name "+txloss", // tx packet loss percent "+rxloss", // rx packet loss percent "+address", // session dest address "+ttl", // multicast ttl "+rate", // tx rate - "+cc", // congestion control on/off + "+cc", // congestion control on/off (sets NORM-CC operation, or fixed rate if off) + "+ecn", // ecn support on/off + "-cce", // sets NORM-CCE operation + "-ccl", // sets NORM-CCL operation + "+probe", // set number of cc probes per rtt + "+flowControl", // flow control on/off "+backoff", // backoff factor 'k' (maxBackoff = k * GRTT) "+input", // stream input "+output", // stream output "+interval", // delay between tx objects "+repeat", // number of times to repeat tx object set "+rinterval", // repeat interval - "+segment", // server segment size - "+block", // server blocking size - "+parity", // server parity segments calculated per block - "+auto", // server auto parity count - "+extra", // server extra parity count + "+segment", // sender segment size + "+block", // sender blocking size + "+parity", // sender parity segments calculated per block + "+auto", // sender auto parity count + "+extra", // sender extra parity count "+gsize", // group size estimate "+grtt", // grtt estimate "+txbuffer", // tx buffer size (bytes) + "+txcachebounds",// limits on sender tx object caching "+rxbuffer", // rx buffer size (bytes) + "+rxcachemax", // sets rcvr "max_pending_range" for NormObjectIds "+start", // open session and begin activity "-stop", // cease activity and close session "+sendFile", // queue a "sim" file of bytes for transmission "+sendRandomFile", // queue random-size file size range : + "+requeue", // requeue (i.e. retransmit) each file sent times "+sendStream", // send a simulated NORM stream "+openStream", // open a stream object for messaging (sending) "+push", // "on" means real-time push stream advancement (non-blocking) "+flush", // stream flush mode "-doFlush", // invoke flushing of stream - "+unicastNacks", // clients will unicast feedback - "+silentClient", // clients will not transmit + "+unicastNacks", // receivers will unicast feedback + "+silentReceiver", // receivers will not transmit NULL }; @@ -87,7 +102,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) unsigned int len = strlen(cmd); if ((CMD_ARG == type) && !val) { - DMSG(0, "NormSimAgent::ProcessCommand(%s) missing argument\n", cmd); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(%s) missing argument\n", cmd); return false; } @@ -97,7 +112,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) int debugLevel = atoi(val); if ((debugLevel < 0) || (debugLevel > 12)) { - DMSG(0, "NormSimAgent::ProcessCommand(segment) invalid debug level!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(segment) invalid debug level!\n"); return false; } SetDebugLevel(debugLevel); @@ -106,6 +121,15 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { OpenDebugLog(val); } + else if (!strncmp("flog", cmd, len)) + { + // open a file to log "file" enqueue/completion events in ".pdrc" format + if (NULL == (log_file_ptr = fopen(val, "w"))) + { + perror("NormSimAgent::ProcessCommand(flog) fopen() error:"); + return false; + } + } else if (!strncmp("trace", cmd, len)) { tracing = true; @@ -116,7 +140,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) double txLoss = atof(val); if (txLoss < 0) { - DMSG(0, "NormSimAgent::ProcessCommand(txloss) invalid txRate!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(txloss) invalid txRate!\n"); return false; } tx_loss = txLoss; @@ -127,7 +151,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) double rxLoss = atof(val); if (rxLoss < 0) { - DMSG(0, "NormSimAgent::ProcessCommand(rxloss) invalid txRate!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(rxloss) invalid txRate!\n"); return false; } rx_loss = rxLoss; @@ -139,7 +163,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) if (address) delete address; if (!(address = new char[len+1])) { - DMSG(0, "NormSimAgent::ProcessCommand(address) allocation error:%s\n", + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(address) allocation error:%s\n", strerror(errno)); return false; } @@ -149,7 +173,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { delete address; address = NULL; - DMSG(0, "NormSimAgent::ProcessCommand(address) missing port number!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(address) missing port number!\n"); return false; } *ptr++ = '\0'; @@ -158,7 +182,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { delete address; address = NULL; - DMSG(0, "NormSimAgent::ProcessCommand(address) invalid port number!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(address) invalid port number!\n"); return false; } port = portNum; @@ -168,7 +192,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) int ttlTemp = atoi(val); if ((ttlTemp < 1) || (ttlTemp > 255)) { - DMSG(0, "NormSimAgent::ProcessCommand(ttl) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(ttl) invalid value!\n"); return false; } ttl = ttlTemp; @@ -178,26 +202,80 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) double txRate = atof(val); if (txRate < 0) { - DMSG(0, "NormSimAgent::ProcessCommand(rate) invalid txRate!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(rate) invalid txRate!\n"); return false; } tx_rate = txRate; if (session) session->SetTxRate(txRate); } + else if (!strncmp("probe", cmd, len)) + { + unsigned int probeCount = (unsigned int)atoi(val); + if (0 == probeCount) + { + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(probe) invalid probeCount!\n"); + return false; + } + probe_count = probeCount; + if (session) session->SetProbeCount(probeCount); + } else if (!strncmp("cc", cmd, len)) { bool ccEnable; if (!strcmp(val, "on")) + { ccEnable = true; + } else if (!strcmp(val, "off")) + { ccEnable = false; + } else { - DMSG(0, "NormSimAgent::ProcessCommand(cc) invalid argument!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(cc) invalid argument!\n"); + return false; + } + if (ccEnable) + SetCCMode(NORM_CC); + else + SetCCMode(NORM_FIXED); + return true; + } + else if (!strncmp("ecn", cmd, len)) + { + if (!strcmp(val, "on")) + { + ecn_enable = true; + } + else if (!strcmp(val, "off")) + { + ecn_enable = false; + } + else + { + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(ecn) invalid argument!\n"); return false; } - cc_enable = ccEnable; - if (session) session->SetCongestionControl(ccEnable); + if (session) + { + bool ignoreLoss = (NORM_CCE == cc_mode); + bool tolerateLoss = (NORM_CCL == cc_mode); + session->SetEcnSupport(ecn_enable, ignoreLoss, tolerateLoss); + } + return true; + } + else if (!strncmp("cce", cmd, len)) + { + // This sets up NORM-CCE congestion control + // (i.e. ECN-enabled and "ignoreLoss) + SetCCMode(NORM_CCE); + return true; + } + else if (!strncmp("ccl", cmd, len)) + { + // This sets up NORM-CCE congestion control + // (i.e. ECN-enabled and "ignoreLoss) + SetCCMode(NORM_CCL); return true; } else if (!strncmp("backoff", cmd, len)) @@ -205,7 +283,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) double backoffFactor = atof(val); if (backoffFactor < 0) { - DMSG(0, "NormSimAgent::ProcessCommand(backoff) invalid txRate!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(backoff) invalid txRate!\n"); return false; } backoff_factor = backoffFactor; @@ -217,7 +295,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) tx_object_interval = -1.0; if (tx_object_interval < 0.0) { - DMSG(0, "NormSimAgent::ProcessCommand(interval) Invalid tx object interval: %s\n", + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(interval) Invalid tx object interval: %s\n", val); tx_object_interval = 0.0; return false; @@ -230,9 +308,9 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) else if (!strncmp("segment", cmd, len)) { int segmentSize = atoi(val); - if ((segmentSize < 0) || (segmentSize > 8000)) + if ((segmentSize < 0) || (segmentSize > 8300)) { - DMSG(0, "NormSimAgent::ProcessCommand(segment) invalid segment size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(segment) invalid segment size!\n"); return false; } segment_size = segmentSize; @@ -242,7 +320,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) int blockSize = atoi(val); if ((blockSize < 1) || (blockSize > 255)) { - DMSG(0, "NormSimAgent::ProcessCommand(block) invalid block size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(block) invalid block size!\n"); return false; } ndata = blockSize; @@ -252,7 +330,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) int numParity = atoi(val); if ((numParity < 0) || (numParity > 254)) { - DMSG(0, "NormSimAgent::ProcessCommand(parity) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(parity) invalid value!\n"); return false; } nparity = numParity; @@ -262,70 +340,92 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) int autoParity = atoi(val); if ((autoParity < 0) || (autoParity > 254)) { - DMSG(0, "NormSimAgent::ProcessCommand(auto) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(auto) invalid value!\n"); return false; } auto_parity = autoParity; - if (session) session->ServerSetAutoParity(autoParity); + if (session) session->SenderSetAutoParity(autoParity); } else if (!strncmp("extra", cmd, len)) { int extraParity = atoi(val); if ((extraParity < 0) || (extraParity > 254)) { - DMSG(0, "NormSimAgent::ProcessCommand(extra) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(extra) invalid value!\n"); return false; } extra_parity = extraParity; - if (session) session->ServerSetExtraParity(extraParity); + if (session) session->SenderSetExtraParity(extraParity); } else if (!strncmp("gsize", cmd, len)) { if (1 != sscanf(val, "%lf", &group_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(gize) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(gize) invalid value!\n"); return false; } - if (session) session->ServerSetGroupSize(group_size); + if (session) session->SenderSetGroupSize(group_size); } else if (!strncmp("grtt", cmd, len)) { if (1 != sscanf(val, "%lf", &grtt_estimate)) { - DMSG(0, "NormSimAgent::ProcessCommand(gize) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(gize) invalid value!\n"); return false; } - if (session) session->ServerSetGroupSize(grtt_estimate); + if (session) session->SenderSetGroupSize(grtt_estimate); } else if (!strncmp("txbuffer", cmd, len)) { if (1 != sscanf(val, "%lu", &tx_buffer_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(txbuffer) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(txbuffer) invalid value!\n"); return false; } } + else if (!strncmp("txcachebounds", cmd, len)) + { + unsigned long countMin, countMax, sizeMax; + if (3 != sscanf(val, "%lu:%lu:%lu\n", &countMin, &countMax, &sizeMax)) + { + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(txcachebounds) invalid value!\n"); + return false; + } + tx_cache_min = countMin; + tx_cache_max = countMax; + tx_cache_size = sizeMax; + if (session) session->SetTxCacheBounds(tx_cache_size, tx_cache_min, tx_cache_max); + } else if (!strncmp("rxbuffer", cmd, len)) { if (1 != sscanf(val, "%lu", &rx_buffer_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(rxbuffer) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(rxbuffer) invalid value!\n"); return false; } } + else if (!strncmp("rxcachemax", cmd, len)) + { + if (1 != sscanf(val, "%lu", &rx_cache_max)) + { + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(rxcachemax) invalid value!\n"); + return false; + } + if (session) session->SetRxCacheMax(rx_cache_max); + } else if (!strncmp("start", cmd, len)) { - if (!strcmp("server", val)) + if (!strcmp("sender", val)) { - return StartServer(); + return StartSender(); } - else if (!strcmp("client", val)) + else if (!strcmp("receiver", val)) { - return StartClient(); + return StartReceiver(); } else { - DMSG(0, "NormSimAgent::ProcessCommand(start) invalid value!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(start) invalid value!\n"); return false; } } @@ -337,16 +437,17 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { if (1 != sscanf(val, "%lu", &tx_object_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(sendFile) invalid size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendFile) invalid size!\n"); return false; } if (session) { - return (NULL != session->QueueTxSim(tx_object_size)); + if (tx_repeat_count >= 0) tx_repeat_count++; + OnIntervalTimeout(interval_timer); } else { - DMSG(0, "NormSimAgent::ProcessCommand(sendFile) no session started!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendFile) no session started!\n"); return false; } } @@ -354,7 +455,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { if (2 != sscanf(val, "%lu:%lu", &tx_object_size_min, &tx_object_size_max)) { - DMSG(0, "NormSimAgent::ProcessCommand(sendRandomFile) invalid size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendRandomFile) invalid size!\n"); return false; } if (tx_object_size_min > tx_object_size_max) @@ -368,32 +469,41 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) tx_object_size += tx_object_size_min; if (session) { - DMSG(2, "NormSimAgent::ProcessCommand(sendRandomFile) Queued file size: %lu bytes\n", tx_object_size); - return (NULL != session->QueueTxSim(tx_object_size)); + if (tx_repeat_count >= 0) tx_repeat_count++; + OnIntervalTimeout(interval_timer); } else { - DMSG(0, "NormSimAgent::ProcessCommand(sendRandomFile) no session started!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendRandomFile) no session started!\n"); return false; } - } + } + else if (!strncmp("requeue", cmd, len)) + { + if (1 != sscanf(val, "%d", &tx_requeue)) + { + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(requeue) invalid count!\n"); + return false; + } + tx_requeue_count = tx_requeue; + } else if (!strncmp("sendStream", cmd, len)) { if (session) { if (stream) { - DMSG(0, "NormSimAgent::ProcessCommand(sendStream) stream already open!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendStream) stream already open!\n"); return false; } if (1 != sscanf(val, "%lu", &tx_object_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(sendStream) invalid buffer size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendStream) invalid buffer size!\n"); return false; } if (!(stream = session->QueueTxStream(tx_object_size))) { - DMSG(0, "NormSimAgent::ProcessCommand(sendStream) error opening stream!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendStream) error opening stream!\n"); return false; } stream->SetFlushMode(flush_mode); @@ -402,7 +512,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) } else { - DMSG(0, "NormSimAgent::ProcessCommand(sendStream) session not started!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(sendStream) session not started!\n"); return false; } } @@ -412,23 +522,23 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) { if (stream) { - DMSG(0, "NormSimAgent::ProcessCommand(openStream) stream already open!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) stream already open!\n"); return false; } if (1 != sscanf(val, "%lu", &tx_object_size)) { - DMSG(0, "NormSimAgent::ProcessCommand(openStream) invalid buffer size!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) invalid buffer size!\n"); return false; } if(!(tx_msg_buffer = new char[65536])) { - DMSG(0, "NormSimAgent::ProcessCommand(openStream) error allocating tx_msg_buffer: %s\n", + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) error allocating tx_msg_buffer: %s\n", strerror(errno)); return false; } if (!(stream = session->QueueTxStream(tx_object_size))) { - DMSG(0, "NormSimAgent::ProcessCommand(openStream) error opening stream!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) error opening stream!\n"); return false; } stream->SetFlushMode(flush_mode); @@ -438,7 +548,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) } else { - DMSG(0, "NormSimAgent::ProcessCommand(openStream) session not started!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(openStream) session not started!\n"); return false; } } @@ -453,7 +563,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) flush_mode = NormStreamObject::FLUSH_ACTIVE; else { - DMSG(0, "NormApp::OnCommand(flush) invalid msg flush mode!\n"); + PLOG(PL_FATAL, "NormApp::OnCommand(flush) invalid msg flush mode!\n"); return false; } } @@ -465,7 +575,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) } else { - DMSG(0, "NormSimAgent::ProcessCommand(doFlush) session not started!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(doFlush) session not started!\n"); return false; } } @@ -477,7 +587,7 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) push_mode = false; else { - DMSG(0, "NormSimAgent::ProcessCommand(push) invalid argument!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(push) invalid argument!\n"); return false; } return true; @@ -490,58 +600,93 @@ bool NormSimAgent::ProcessCommand(const char* cmd, const char* val) unicast_nacks = false; else { - DMSG(0, "NormSimAgent::ProcessCommand(unicastNacks) invalid argument!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(unicastNacks) invalid argument!\n"); return false; } - if (session) session->ClientSetUnicastNacks(unicast_nacks); + if (session) session->ReceiverSetUnicastNacks(unicast_nacks); return true; } - else if (!strncmp("silentClient", cmd, len)) + else if (!strncmp("silentReceiver", cmd, len)) { if (!strcmp(val, "on")) - silent_client = true; + silent_receiver = true; else if (!strcmp(val, "off")) - silent_client = false; + silent_receiver = false; else { - DMSG(0, "NormSimAgent::ProcessCommand(silentClient) invalid argument!\n"); + PLOG(PL_FATAL, "NormSimAgent::ProcessCommand(silentReceiver) invalid argument!\n"); return false; } - if (session) session->ClientSetSilent(silent_client); + if (session) session->ReceiverSetSilent(silent_receiver); return true; } return true; } // end NormSimAgent::ProcessCommand() +void NormSimAgent::SetCCMode(NormCC ccMode) +{ + cc_mode = ccMode; + if (NULL != session) + { + switch (ccMode) + { + case NORM_FIXED: + cc_enable = false; + session->SetCongestionControl(false); + session->SetEcnSupport(ecn_enable, false, false); + break; + case NORM_CC: + cc_enable = true; + session->SetCongestionControl(true); + session->SetEcnSupport(ecn_enable, false, false); + break; + case NORM_CCE: + cc_enable = true; + session->SetCongestionControl(true); + session->SetEcnSupport(true, true, false); + break; + case NORM_CCL: + cc_enable = true; + session->SetCongestionControl(true); + session->SetEcnSupport(ecn_enable, false, true); + break; + } + } +} // end NormSimAgent::SetCCMode() + NormSimAgent::CmdType NormSimAgent::CommandType(const char* cmd) { if (!cmd) return CMD_INVALID; unsigned int len = strlen(cmd); - bool matched = false; CmdType type = CMD_INVALID; const char* const* nextCmd = cmd_list; - while (*nextCmd) + int matchCount = 0; + while (NULL != *nextCmd) { if (!strncmp(cmd, *nextCmd+1, len)) { - if (matched) + if (len == strlen(*nextCmd+1)) { - // ambiguous command (command should match only once) - return CMD_INVALID; + // exact match + return (('+' == *nextCmd[0]) ? CMD_ARG : CMD_NOARG); } + matchCount++; + if ('+' == *nextCmd[0]) + type = CMD_ARG; else - { - matched = true; - if ('+' == *nextCmd[0]) - type = CMD_ARG; - else - type = CMD_NOARG; - } + type = CMD_NOARG; } nextCmd++; } - return type; + switch (matchCount) + { + case 0: + case 1: + return type; + default: + return CMD_INVALID; // it was ambiguous + } } // end NormSimAgent::CommandType() bool NormSimAgent::SendMessage(unsigned int len, const char* txBuffer) @@ -552,13 +697,13 @@ bool NormSimAgent::SendMessage(unsigned int len, const char* txBuffer) { if(!(tx_msg_buffer = new char[65536])) { - DMSG(0, "NormSimAgent::SendMessage() error allocating tx_msg_buffer: %s\n", + PLOG(PL_FATAL, "NormSimAgent::SendMessage() error allocating tx_msg_buffer: %s\n", strerror(errno)); return false; } if (!(stream = session->QueueTxStream(tx_object_size))) { - DMSG(0, "NormSimAgent::SendMessage() error opening stream!\n"); + PLOG(PL_FATAL, "NormSimAgent::SendMessage() error opening stream!\n"); return false; } auto_stream = false; @@ -575,14 +720,14 @@ bool NormSimAgent::SendMessage(unsigned int len, const char* txBuffer) else { // Message still pending, can't send yet - DMSG(0, "NormSimAgent::SendMessage() warning: input overflow!\n"); + PLOG(PL_DEBUG, "NormSimAgent::SendMessage() warning: input overflow!\n"); //ASSERT(0); return false; } } else { - DMSG(0, "NormSimAgent::SendMessage() session not started!\n"); + PLOG(PL_FATAL, "NormSimAgent::SendMessage() session not started!\n"); return false; } @@ -609,14 +754,14 @@ void NormSimAgent::OnInputReady() // Do an _active_ flush of the stream (for use at end-of-transmission) bool NormSimAgent::FlushStream() { - if (stream && session && session->IsServer()) + if (stream && session && session->IsSender()) { stream->Flush(true); return true; } else { - DMSG(0, "NormSimAgent::FlushStream() no output stream to flush\n"); + PLOG(PL_FATAL, "NormSimAgent::FlushStream() no output stream to flush\n"); return false; } } // end NormSimAgent::FlushStream() @@ -625,79 +770,119 @@ bool NormSimAgent::FlushStream() void NormSimAgent::Notify(NormController::Event event, class NormSessionMgr* sessionMgr, class NormSession* session, - class NormServerNode* server, + class NormSenderNode* sender, class NormObject* object) { switch (event) { - //case TX_QUEUE_VACANCY: - case TX_QUEUE_EMPTY: - // Can queue a new object or write to stream for transmission - if (object && (object == stream)) - { - if (auto_stream) + case TX_QUEUE_VACANCY: + case TX_QUEUE_EMPTY: + // Can queue a new object or write to stream for transmission + if ((NULL != object) && (object == stream)) { - // sending a dummy byte stream - char buffer[NormMsg::MAX_SIZE]; - stream->Write(buffer, segment_size, false); + 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(); + } } else - { - // Stream starved, ask for input from "source" ? - OnInputReady(); - } - } - else - { - // Schedule or queue next "sim file" transmission - if (interval_timer.GetInterval() > 0.0) - { - ActivateTimer(interval_timer); - } - else - { - OnIntervalTimeout(interval_timer); - } - } - break; + { + // Schedule or queue next "sim object" transmission + if (interval_timer.GetInterval() > 0.0) + ActivateTimer(interval_timer); + else + OnIntervalTimeout(interval_timer); + } + break; + + case TX_OBJECT_SENT: + if ((0 != tx_requeue) && (object != stream)) + { + if (0 != tx_requeue_count) + { + if (session->RequeueTxObject(object)) + { + if (tx_requeue_count > 0) tx_requeue_count--; + } + else + { + // unable to requeue + PLOG(PL_ERROR, "NormSimAgent::Notify(TX_OBJECT_SENT) warning!: requeue attempt exceeded configured tx cache bounds!\n"); + // reset "tx_requeue_count" for next tx object + tx_requeue_count = tx_requeue; + } + } + else + { + // reset "tx_requeue_count" for next tx object + tx_requeue_count = tx_requeue; + } + } + break; case RX_OBJECT_NEW: { // It's up to the app to "accept" the object switch (object->GetType()) { - case NormObject::STREAM: - { - NormObjectSize size; - if (silent_client) - size = NormObjectSize(rx_buffer_size); - else - size = object->GetSize(); - if (!((NormStreamObject*)object)->Accept(size.LSB())) + case NormObject::STREAM: { - DMSG(0, "NormSimAgent::Notify(RX_OBJECT_NEW) stream object accept error!\n"); + NormObjectSize size; + if (silent_receiver) + size = NormObjectSize(rx_buffer_size); + else + size = object->GetSize(); + if (!((NormStreamObject*)object)->Accept(size.LSB())) + { + PLOG(PL_ERROR, "NormSimAgent::Notify(RX_OBJECT_NEW) stream object accept error!\n"); + } + if (!stream) + stream = (NormStreamObject*)object; + else + PLOG(PL_ERROR, "NormSimAgent::Notify(RX_OBJECT_NEW) warning! one stream already accepted.\n"); } - if (!stream) - stream = (NormStreamObject*)object; - else - DMSG(0, "NormSimAgent::Notify(RX_OBJECT_NEW) warning! one stream already accepted.\n"); - } - break; - case NormObject::FILE: - { - if (!((NormSimObject*)object)->Accept()) + break; + case NormObject::FILE: { - DMSG(0, "NormSimAgent::Notify(RX_OBJECT_NEW) sim object accept error!\n"); + if (!((NormSimObject*)object)->Accept()) + { + PLOG(PL_ERROR, "NormSimAgent::Notify(RX_OBJECT_NEW) sim object accept error!\n"); + } + if (NULL != log_file_ptr) + { + // Log start of NEW receive object + unsigned long objSize = object->GetSize().LSB(); + ProtoTime theTime; + theTime.GetCurrentTime(); + double rxTime = theTime.GetValue(); + double sec = fmod(rxTime, 60.0); + int min = (int)fmod(rxTime/60.0,60.0); + //int hr = (int)fmod(rxTime/3600.0, 24.0); + int hr = (int)(rxTime/3600.0); + fprintf(log_file_ptr, "%02d:%02d:%lf NEW size>%lu seq>%hu\n", + hr, min, sec, objSize, (UINT16)object->GetId()); + } } - } - break; - case NormObject::DATA: - DMSG(0, "NormSimAgent::Notify() DATA objects not _yet_ supported...\n"); - break; - default: - DMSG(0, "NormSimAgent::Notify() INVALID object type!\n"); - ASSERT(0); - break; + break; + case NormObject::DATA: + PLOG(PL_ERROR, "NormSimAgent::Notify() DATA objects not _yet_ supported...\n"); + break; + default: + PLOG(PL_ERROR, "NormSimAgent::Notify() INVALID object type!\n"); + ASSERT(0); + break; } break; } @@ -727,10 +912,10 @@ void NormSimAgent::Notify(NormController::Event event, { bool dataReady = true; while (dataReady) - { - if (!mgen_pending_bytes) + { + if (0 == mgen_pending_bytes) { - // Read 2 byte MGEN msg len header + // 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; @@ -743,10 +928,18 @@ void NormSimAgent::Notify(NormController::Event event, } else { - DMSG(0, "NormSimAgent::Notify(1) detected stream break\n"); - mgen_bytes = mgen_pending_bytes = 0; - msg_sync = false; - continue; + 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) { @@ -771,7 +964,7 @@ void NormSimAgent::Notify(NormController::Event event, } else { - DMSG(0, "NormSimAgent::Notify(2) detected stream break\n"); + PLOG(PL_WARN, "NormSimAgent::Notify(2) detected stream break\n"); mgen_bytes = mgen_pending_bytes = 0; msg_sync = false; continue; @@ -790,18 +983,17 @@ void NormSimAgent::Notify(NormController::Event event, } else { - DMSG(0, "NormSimAgent::Notify(3) detected stream break\n"); + PLOG(PL_WARN, "NormSimAgent::Notify(3) detected stream break\n"); mgen_bytes = mgen_pending_bytes = 0; msg_sync = false; - - break; + break; } } if (msg_sync && (0 == mgen_pending_bytes)) { ProtoAddress srcAddr; - srcAddr.ResolveFromString(server->GetAddress().GetHostString()); - srcAddr.SetPort(server->GetAddress().GetPort()); + srcAddr.ResolveFromString(sender->GetAddress().GetHostString()); + srcAddr.SetPort(sender->GetAddress().GetPort()); msg_sink->HandleMessage(mgen_buffer,mgen_bytes,srcAddr); mgen_bytes = 0; } @@ -810,6 +1002,7 @@ void NormSimAgent::Notify(NormController::Event event, } else { + // Simply read and discard the byte stream for sim purposes char buffer[1024]; unsigned int want = 1024; unsigned int got = want; @@ -822,7 +1015,7 @@ void NormSimAgent::Notify(NormController::Event event, } else { - DMSG(0, "NormSimAgent::Notify() detected stream break\n"); + PLOG(PL_WARN, "NormSimAgent::Notify() detected stream break\n"); } got = want = 1024; } @@ -831,7 +1024,8 @@ void NormSimAgent::Notify(NormController::Event event, } case NormObject::DATA: - DMSG(0, "NormSimAgent::Notify() FILE/DATA objects not _yet_ supported...\n"); + PLOG(PL_FATAL, "NormSimAgent::Notify() DATA objects not supported...\n"); + ASSERT(0); break; default: @@ -843,22 +1037,65 @@ void NormSimAgent::Notify(NormController::Event event, { switch(object->GetType()) { - case NormObject::FILE: - //DMSG(0, "norm: Completed rx file: %s\n", ((NormFileObject*)object)->Path()); - break; - case NormObject::STREAM: - //DMSG(0, "norm: Completed rx stream ...\n"); - break; - case NormObject::DATA: - ASSERT(0); - break; - default: - break; + case NormObject::FILE: + //DMSG(0, "norm: Completed rx file: %s\n", ((NormFileObject*)object)->Path()); + if (NULL != log_file_ptr) + { + unsigned long objSize = object->GetSize().LSB(); + ProtoTime theTime; + theTime.GetCurrentTime(); + double rxTime = theTime.GetValue(); + double sec = fmod(rxTime, 60.0); + int min = (int)fmod(rxTime/60.0,60.0); + int hr = (int)fmod(rxTime/3600.0, 24.0); + fprintf(log_file_ptr, "%02d:%02d:%lf RECV size>%lu seq>%hu\n", + hr, min, sec, objSize, (UINT16)object->GetId()); + } + break; + case NormObject::STREAM: + //DMSG(0, "norm: Completed rx stream ...\n"); + break; + case NormObject::DATA: + ASSERT(0); + break; + default: + break; } break; } + case RX_OBJECT_ABORTED: + { + switch(object->GetType()) + { + case NormObject::FILE: + //DMSG(0, "norm: Aborted rx file: %s\n", ((NormFileObject*)object)->Path()); + if (NULL != log_file_ptr) + { + unsigned long objSize = object->GetSize().LSB(); + ProtoTime theTime; + theTime.GetCurrentTime(); + double rxTime = theTime.GetValue(); + double sec = fmod(rxTime, 60.0); + int min = (int)fmod(rxTime/60.0,60.0); + int hr = (int)fmod(rxTime/3600.0, 24.0); + fprintf(log_file_ptr, "%02d:%02d:%lf ABORT size>%lu seq>%hu\n", + hr, min, sec, objSize, (UINT16)object->GetId()); + } + break; + case NormObject::STREAM: + //DMSG(0, "norm: Aborted rx stream ...\n"); + break; + case NormObject::DATA: + ASSERT(0); + break; + default: + break; + } + break; + } + default: - DMSG(0, "NormSimAgent::Notify() unhandled NormEvent type\n"); + PLOG(PL_DEBUG, "NormSimAgent::Notify() unhandled NormEvent type\n"); } // end switch(event) } // end NormSimAgent::Notify() @@ -881,11 +1118,27 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) tx_object_size = (unsigned long)(tx_object_size * UniformRand(1.0)); tx_object_size += tx_object_size_min; } - if (!session->QueueTxSim(tx_object_size)) + NormObject* object = session->QueueTxSim(tx_object_size); + if (NULL != object) { - DMSG(0, "NormSimAgent::OnIntervalTimeout() Error queueing tx object.\n"); - } - DMSG(2, "NormSimAgent::OnIntervalTimeout(() Queued file size: %lu bytes\n", tx_object_size); + PLOG(PL_DEBUG, "NormSimAgent::OnIntervalTimeout(() Queued file size: %lu bytes\n", tx_object_size); + if (NULL != log_file_ptr) + { + ProtoTime theTime; + theTime.GetCurrentTime(); + double rxTime = theTime.GetValue(); + double sec = fmod(rxTime, 60.0); + int min = (int)fmod(rxTime/60.0,60.0); + int hr = (int)fmod(rxTime/3600.0, 24.0); + fprintf(log_file_ptr, "%02d:%02d:%lf SEND size>%lu seq>%hu\n", + hr, min, sec, tx_object_size, (UINT16)object->GetId()); + } + } + else + { + TRACE("NormSimAgent::OnIntervalTimeout() Error queueing tx object.\n"); + PLOG(PL_WARN, "NormSimAgent::OnIntervalTimeout() Error queueing tx object.\n"); + } } interval_timer.SetInterval(tx_object_interval); return true; @@ -899,17 +1152,17 @@ bool NormSimAgent::OnIntervalTimeout(ProtoTimer& theTimer) } // end NormSimAgent::OnIntervalTimeout() -bool NormSimAgent::StartServer() +bool NormSimAgent::StartSender() { if (session) { - DMSG(0, "NormSimAgent::StartServer() Error! server or client already started!\n"); + PLOG(PL_FATAL, "NormSimAgent::StartSender() Error! sender or receiver already started!\n"); return false; } // Validate our session settings if (!address) { - DMSG(0, "NormSimAgent::StartServer() Error! no session address given."); + PLOG(PL_FATAL, "NormSimAgent::StartSender() Error! no session address given."); return false; } @@ -919,45 +1172,46 @@ bool NormSimAgent::StartServer() { // Common session parameters session->SetTxRate(tx_rate); - session->SetCongestionControl(cc_enable); + SetCCMode(cc_mode); // calls NormSession::SetCongestionControl() and NormSession::SetEcnSupport() as needed + session->SetProbeCount(probe_count); session->SetBackoffFactor(backoff_factor); session->SetTrace(tracing); session->SetTxLoss(tx_loss); session->SetRxLoss(rx_loss); - session->ServerSetGroupSize(group_size); - session->ServerSetGrtt(grtt_estimate); - // StartServer(bufferMax, segmentSize, fec_ndata, fec_nparity) + session->SenderSetGroupSize(group_size); + session->SenderSetGrtt(grtt_estimate); + session->SetTxCacheBounds(tx_cache_size, tx_cache_min, tx_cache_max); UINT16 instanceId = (rand() * 65535) / RAND_MAX; - if (!session->StartServer(instanceId, tx_buffer_size, segment_size, ndata, nparity, NULL)) + if (!session->StartSender(instanceId, tx_buffer_size, segment_size, ndata, nparity)) { - DMSG(0, "NormSimAgent::OnStartup() start server error!\n"); + PLOG(PL_FATAL, "NormSimAgent::OnStartup() start sender error!\n"); session_mgr.Destroy(); return false; } - session->ServerSetAutoParity(auto_parity); - session->ServerSetExtraParity(extra_parity); + session->SenderSetAutoParity(auto_parity); + session->SenderSetExtraParity(extra_parity); return true; } else { - DMSG(0, "NormSimAgent::StartServer() new session error!\n"); + PLOG(PL_FATAL, "NormSimAgent::StartSender() new session error!\n"); return false; } -} // end NormSimAgent::StartServer() +} // end NormSimAgent::StartSender() -bool NormSimAgent::StartClient() +bool NormSimAgent::StartReceiver() { if (session) { - DMSG(0, "NormSimAgent::StartClient() Error! server or client already started!\n"); + PLOG(PL_FATAL, "NormSimAgent::StartReceiver() Error! sender or receiver already started!\n"); return false; } // Validate our session settings if (!address) { - DMSG(0, "NormSimAgent::StartClient() Error! no session address given."); + PLOG(PL_FATAL, "NormSimAgent::StartReceiver() Error! no session address given."); return false; } @@ -971,14 +1225,14 @@ bool NormSimAgent::StartClient() session->SetTrace(tracing); session->SetTxLoss(tx_loss); session->SetRxLoss(rx_loss); - - session->ClientSetUnicastNacks(unicast_nacks); - session->ClientSetSilent(silent_client); - - // StartClient(bufferSize) - if (!session->StartClient(rx_buffer_size)) + SetCCMode(cc_mode); // calls NormSession::SetCongestionControl() and NormSession::SetEcnSupport() as needed + session->ReceiverSetUnicastNacks(unicast_nacks); + session->ReceiverSetSilent(silent_receiver); + session->SetRxCacheMax(rx_cache_max); + // StartReceiver(bufferSize) + if (!session->StartReceiver(rx_buffer_size)) { - DMSG(0, "NormSimAgent::StartClient() start client error!\n"); + PLOG(PL_FATAL, "NormSimAgent::StartReceiver() start receiver error!\n"); session_mgr.Destroy(); return false; } @@ -986,22 +1240,27 @@ bool NormSimAgent::StartClient() } else { - DMSG(0, "NormSimAgent::StartClient() new session error!\n"); + PLOG(PL_FATAL, "NormSimAgent::StartReceiver() new session error!\n"); return false; } -} // end NormSimAgent::StartServer() +} // end NormSimAgent::StartSender() void NormSimAgent::Stop() { if (session) { - if (session->IsServer()) session->StopServer(); - if (session->IsClient()) session->StopClient(); + if (session->IsSender()) session->StopSender(); + if (session->IsReceiver()) session->StopReceiver(); session_mgr.DeleteSession(session); session = NULL; stream = NULL; } -} // end NormSimAgent::StopServer() + if (NULL != log_file_ptr) + { + fclose(log_file_ptr); + log_file_ptr = NULL; + } +} // end NormSimAgent::StopSender() diff --git a/common/normTest.cpp b/src/common/normTest.cpp similarity index 85% rename from common/normTest.cpp rename to src/common/normTest.cpp index 97cec6a..d479bac 100644 --- a/common/normTest.cpp +++ b/src/common/normTest.cpp @@ -22,6 +22,9 @@ int main(int argc, char* argv[]) bool trace = false; int debugLevel = 2; double loss = 0.0; + int sendMax = 3000;//-1; // -1 means unlimited + const char* cmd = NULL; + // 1) Parse command-line options for (int i = 1; i < argc; i++) @@ -46,13 +49,24 @@ int main(int argc, char* argv[]) { debugLevel = atoi(argv[++i]); } - else if (!strcmp(argv[i], "loss")) { loss = (double)atoi(argv[++i]); } + else if (!strcmp(argv[i], "smax")) + { + sendMax = atoi(argv[++i]); + } + else if (!strcmp(argv[i], "cmd")) + { + cmd = argv[++i]; + } } + TRACE("loss = %lf\n", loss); + + int sendMid = sendMax / 2; + if (!send && !recv) { TRACE("normTest: neither 'send' or 'recv' operation was specified?!\n"); @@ -128,13 +142,13 @@ int main(int argc, char* argv[]) // (and a different sender sessionId) srand(currentTime.tv_sec); // seed random number generator - NormSetGrttEstimate(session, 0.001); // 1 msec initial grtt + NormSetGrttEstimate(session, 0.25); // 1 msec initial grtt - NormSetTransmitRate(session, 1.0e+04); // in bits/second + NormSetTxRate(session, 5.0e+06); // in bits/second - // Option to enable TCP-friendly congestion control (overrides NormSetTransmitRate()) + // Option to enable TCP-friendly congestion control (overrides NormSetTxRate()) if (cc) NormSetCongestionControl(session, true); - NormSetTransmitRateBounds(session, 5.0e+06, 10.0e+06); + NormSetTxRateBounds(session, 5.0e+06, 6.0e+06); //NormSetDefaultRepairBoundary(session, NORM_BOUNDARY_BLOCK); @@ -164,7 +178,7 @@ int main(int argc, char* argv[]) // We use a random "sessionId" NormSessionId sessionId = (NormSessionId)rand(); - if (send) NormStartSender(session, sessionId, 1024*1024, 1400, 64, 8); + if (send) NormStartSender(session, sessionId, 2097152, 1420, 16, 4); // Uncomment to set large rx socket buffer size // (might be needed for high rate sessions) @@ -190,12 +204,14 @@ int main(int argc, char* argv[]) // Uncomment this line to send a stream instead of the file - stream = NormStreamOpen(session, 4*1024*1024); + stream = NormStreamOpen(session, 2*1024*1024); // NORM_FLUSH_PASSIVE automatically flushes full writes to // the stream. NormStreamSetAutoFlush(stream, NORM_FLUSH_PASSIVE); + //NormStreamWrite(stream, 0, 0);//txBuffer+txIndex, want); + // Some variable for stream input/output char txBuffer[8192], rxBuffer[8192]; int txIndex = 0; @@ -210,7 +226,7 @@ int main(int argc, char* argv[]) int msgCount = 0; int recvCount = -1; // used to monitor reliable stream reception int sendCount = 0; - int sendMax = 3000;//-1; // -1 means unlimited + int fileMax = 1; NormObjectHandle txFile = NORM_OBJECT_INVALID; @@ -219,12 +235,16 @@ int main(int argc, char* argv[]) bool rxActive = false; + if (NULL != cmd) + NormSendCommand(session, cmd, strlen(cmd) + 1, false); + + while (NormGetNextEvent(instance, &theEvent)) { switch (theEvent.type) { case NORM_CC_ACTIVE: - DMSG(2, "normTest: NORM_CC_ACTIVE event ...\n"); + PLOG(PL_INFO, "normTest: NORM_CC_ACTIVE event ...\n"); if (rxActive) { break; @@ -237,14 +257,23 @@ int main(int argc, char* argv[]) case NORM_TX_QUEUE_VACANCY: case NORM_TX_QUEUE_EMPTY: - if (!rxActive) break; + //if (!rxActive) break; + /* if (NORM_TX_QUEUE_VACANCY == theEvent.type) TRACE("NORM_TX_QUEUE_VACANCY ...\n"); else if (NORM_TX_QUEUE_EMPTY == theEvent.type) TRACE("NORM_TX_QUEUE_EMPTY ...\n"); else TRACE("writing to stream after CC_ACTIVE\n"); - if ((sendMax > 0) && (sendCount >= sendMax)) break; + */ + + if ((sendMax > 0) && (sendCount >= sendMax)) + { + // send "cmd" again at end + if (NULL != cmd) + NormSendCommand(session, cmd, strlen(cmd) + 1, false); + break; + } //if (NORM_OBJECT_INVALID != theEvent.object) if (true) { @@ -274,6 +303,12 @@ int main(int argc, char* argv[]) NormStreamMarkEom(stream); txLen = txIndex = 0; sendCount++; + + if ((NULL != cmd) && (sendCount == sendMid)) + NormSendCommand(session, cmd, strlen(cmd) + 1, true); + + + if (0 == (sendCount % 1000)) TRACE("normTest: sender sent %d messages ...\n", sendCount); if (sendCount == 15) @@ -304,7 +339,7 @@ int main(int argc, char* argv[]) // Repeatedly queue our file for sending if (NORM_OBJECT_INVALID == txFile) { - DMSG(0, "normTest: error queuing file: %s\n", filePath); + PLOG(PL_ERROR, "normTest: error queuing file: %s\n", filePath); break; } else @@ -318,26 +353,30 @@ int main(int argc, char* argv[]) break; case NORM_TX_FLUSH_COMPLETED: - DMSG(2, "normTest: NORM_TX_FLUSH_COMPLETED event ...\n"); + PLOG(PL_INFO, "normTest: NORM_TX_FLUSH_COMPLETED event ...\n"); // This line of code "requeues" the last file sent // (Good for use with silent receivers ... can repeat transmit object this way) NormRequeueObject(session, txFile); break; case NORM_TX_WATERMARK_COMPLETED: - DMSG(2, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); + PLOG(PL_INFO, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); + break; + + case NORM_TX_CMD_SENT: + PLOG(PL_INFO, "normTest: NORM_TX_CMD_SENT event ...\n"); break; case NORM_TX_OBJECT_SENT: - DMSG(2, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); + PLOG(PL_INFO, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); break; case NORM_TX_OBJECT_PURGED: - DMSG(2, "normTest: NORM_TX_OBJECT_PURGED event ...\n"); + PLOG(PL_INFO, "normTest: NORM_TX_OBJECT_PURGED event ...\n"); break; case NORM_CC_INACTIVE: - DMSG(2, "normTest: NORM_CC_INACTIVE event ...\n"); + PLOG(PL_INFO, "normTest: NORM_CC_INACTIVE event ...\n"); rxActive = false; // (TBD) add APIs to delete remote sender's state entirely // (TBD) add APIs to automate buffer freeing, remote sender managment somewhat @@ -349,10 +388,32 @@ int main(int argc, char* argv[]) case NORM_REMOTE_SENDER_INACTIVE: NormNodeFreeBuffers(theEvent.sender); // frees up some memory allocated for this remote sender's state break; - + + case NORM_RX_CMD_NEW: + { + char cmdBuffer[8192]; + unsigned int cmdLength = 8192; + if (NormNodeGetCommand(theEvent.sender, cmdBuffer, &cmdLength)) + { + char addrBuffer[16]; + unsigned int addrLength = 16; + if (!NormNodeGetAddress(theEvent.sender, addrBuffer, &addrLength)) + PLOG(PL_ERROR, "normTest: error getting sender addr!\n"); + ProtoAddress senderAddr; + if (4 == addrLength) + senderAddr.SetRawHostAddress(ProtoAddress::IPv4, addrBuffer, 4); + else if (16 == addrLength) + senderAddr.SetRawHostAddress(ProtoAddress::IPv6, addrBuffer, 16); + cmdBuffer[cmdLength] = '\0'; + PLOG(PL_ERROR, "normTest: recvd cmd \"%s\" from sender %s\n", + cmdBuffer, senderAddr.GetHostString()); + } + PLOG(PL_INFO, "normTest: NORM_RX_CMD_NEW event ...\n"); + break; + } case NORM_RX_OBJECT_NEW: - DMSG(2, "normTest: NORM_RX_OBJECT_NEW event ...\n"); + PLOG(PL_INFO, "normTest: NORM_RX_OBJECT_NEW event ...\n"); break; case NORM_RX_OBJECT_INFO: @@ -373,7 +434,7 @@ int main(int argc, char* argv[]) } if (!NormFileRename(theEvent.object, fileName)) TRACE("normTest: NormSetFileName(%s) error\n", fileName); - DMSG(3, "normTest: recv'd info for file: %s\n", fileName); + PLOG(PL_DEBUG, "normTest: recv'd info for file: %s\n", fileName); } break; diff --git a/common/normThreadTest.cpp b/src/common/normThreadTest.cpp similarity index 94% rename from common/normThreadTest.cpp rename to src/common/normThreadTest.cpp index 6011d02..2d588a4 100644 --- a/common/normThreadTest.cpp +++ b/src/common/normThreadTest.cpp @@ -41,7 +41,7 @@ class NormThreadApp : public ProtoApp void OnNormEvent(); - static void DoWorkerEvent(const void* clientData); + static void DoWorkerEvent(const void* receiverData); void WorkerReadStream(); enum {MSG_SIZE_MAX = 1024}; @@ -112,7 +112,7 @@ bool NormThreadApp::OnStartup(int argc, const char*const* argv) // Set a callback that will call NormGetNextEvent() if (!dispatcher.InstallGenericInput(NormGetDescriptor(norm_instance), DoNormEvent, this)) { - DMSG(0, "NormThreadApp::OnStartup() InstallGenericInput() error\n"); + PLOG(PL_FATAL, "NormThreadApp::OnStartup() InstallGenericInput() error\n"); NormDestroyInstance(norm_instance); return false; } @@ -165,10 +165,10 @@ bool NormThreadApp::OnStartup(int argc, const char*const* argv) NormSetGrttEstimate(norm_session, 0.250); // 1 msec initial grtt - NormSetTransmitRate(norm_session, 80.0e+06); // in bits/second + NormSetTxRate(norm_session, 80.0e+06); // in bits/second - NormSetTransmitRateBounds(norm_session, 10.0e+06, 10.0e+06); + NormSetTxRateBounds(norm_session, 10.0e+06, 10.0e+06); NormSetCongestionControl(norm_session, true); @@ -331,13 +331,13 @@ void NormThreadApp::OnNormEvent() } else { - DMSG(0, "NormThreadApp::OnNormEvent() NormGetNextEvent() error?\n"); + PLOG(PL_ERROR, "NormThreadApp::OnNormEvent() NormGetNextEvent() error?\n"); } } // end NormThreadApp::OnNormEvent() -void NormThreadApp::DoWorkerEvent(const void* clientData) +void NormThreadApp::DoWorkerEvent(const void* receiverData) { - NormThreadApp* theApp = (NormThreadApp*)clientData; + NormThreadApp* theApp = (NormThreadApp*)receiverData; theApp->WorkerReadStream(); } // end NormThreadApp::DoWorkerEvent() @@ -394,7 +394,7 @@ bool NormThreadApp::OnCommand(const char* cmd, const char* val) } else if (!strncmp("debug", cmd, cmdlen)) { - DMSG(0, "NormThreadApp::OnCommand(%s) unknown command\n", cmd); + PLOG(PL_FATAL, "NormThreadApp::OnCommand(%s) unknown command\n", cmd); return false; } return true; @@ -440,13 +440,13 @@ bool NormThreadApp::ProcessCommands(int argc, const char*const* argv) switch (cmdType) { case CMD_INVALID: - DMSG(0, "NormThreadApp::ProcessCommands() Invalid command:%s\n", + PLOG(PL_FATAL, "NormThreadApp::ProcessCommands() Invalid command:%s\n", argv[i]); return false; case CMD_NOARG: if (!OnCommand(argv[i], NULL)) { - DMSG(0, "NormThreadApp::ProcessCommands() OnCommand(%s) error\n", + PLOG(PL_FATAL, "NormThreadApp::ProcessCommands() OnCommand(%s) error\n", argv[i]); return false; } @@ -455,7 +455,7 @@ bool NormThreadApp::ProcessCommands(int argc, const char*const* argv) case CMD_ARG: if (!OnCommand(argv[i], argv[i+1])) { - DMSG(0, "NormThreadApp::ProcessCommands() OnCommand(%s, %s) error\n", + PLOG(PL_FATAL, "NormThreadApp::ProcessCommands() OnCommand(%s, %s) error\n", argv[i], argv[i+1]); return false; } diff --git a/src/common/pcap2norm.cpp b/src/common/pcap2norm.cpp new file mode 100644 index 0000000..538096b --- /dev/null +++ b/src/common/pcap2norm.cpp @@ -0,0 +1,359 @@ + +// Assumes UDP packets in tcpdump trace file (pcap file) are +// MGEN packets and parses to build an MGEN log file + + +#include +#include +#include "protoPktETH.h" // for Ethernet frame parsing +#include "protoPktIP.h" // for IP packet parsing +#include "protoPktARP.h" + +#include "normSession.h" + +void NormTrace2(const struct timeval& currentTime, + const NormMsg& msg, + const ProtoAddress& srcAddr, + const ProtoAddress& dstAddr); +void Usage() +{ + fprintf(stderr, "pcap2norm [pcapInputFile [outputFile]]\n"); +} + +int main(int argc, char* argv[]) +{ + // Use stdin/stdout by default + FILE* infile = stdin; + FILE* outfile = stdout; + switch(argc) + { + case 1: + // using default stdin/stdout + break; + case 2: + // using named input pcap file and stdout + if (NULL == (infile = fopen(argv[1], "r"))) + { + perror("pcap2norm: error opening input file"); + return -1; + } + break; + case 3: + // use name input and output files + if (NULL == (infile = fopen(argv[1], "r"))) + { + perror("pcap2norm: error opening input file"); + return -1; + } + if (NULL == (outfile = fopen(argv[2], "w+"))) + { + perror("pcap2norm: error opening output file"); + return -1; + } + break; + default: + fprintf(stderr, "pcap2norm: error: too many arguments!\n"); + Usage(); + return -1; + } // end switch(argc) + + char pcapErrBuf[PCAP_ERRBUF_SIZE+1]; + pcapErrBuf[PCAP_ERRBUF_SIZE] = '\0'; + pcap_t* pcapDevice = pcap_fopen_offline(infile, pcapErrBuf); + if (NULL == pcapDevice) + { + fprintf(stderr, "pcap2norm: pcap_fopen_offline() error: %s\n", pcapErrBuf); + if (stdin != infile) fclose(infile); + if (stdout != outfile) fclose(outfile); + return -1; + } + + + UINT32 alignedBuffer[4096/4]; // 128 buffer for packet parsing + UINT16* ethBuffer = ((UINT16*)alignedBuffer) + 1; + unsigned int maxBytes = 4096 - 2; // due to offset, can only use 4094 bytes of buffer + + pcap_pkthdr hdr; + const u_char* pktData; + while(NULL != (pktData = pcap_next(pcapDevice, &hdr))) + { + unsigned int numBytes = maxBytes; + if (hdr.caplen < numBytes) numBytes = hdr.caplen; + memcpy(ethBuffer, pktData, numBytes); + ProtoPktETH ethPkt((UINT32*)ethBuffer, maxBytes); + if (!ethPkt.InitFromBuffer(hdr.len)) + { + fprintf(stderr, "pcap2norm error: invalid Ether frame in pcap file\n"); + continue; + } + ProtoPktIP ipPkt; + ProtoAddress srcAddr, dstAddr; + ProtoPktETH::Type ethType = ethPkt.GetType(); + if ((ProtoPktETH::IP == ethType) || + (ProtoPktETH::IPv6 == ethType)) + { + unsigned int payloadLength = ethPkt.GetPayloadLength(); + if (!ipPkt.InitFromBuffer(payloadLength, (UINT32*)ethPkt.AccessPayload(), payloadLength)) + { + fprintf(stderr, "pcap2norm error: bad IP packet\n"); + continue; + } + switch (ipPkt.GetVersion()) + { + case 4: + { + ProtoPktIPv4 ip4Pkt(ipPkt); + ip4Pkt.GetDstAddr(dstAddr); + ip4Pkt.GetSrcAddr(srcAddr); + break; + } + case 6: + { + ProtoPktIPv6 ip6Pkt(ipPkt); + ip6Pkt.GetDstAddr(dstAddr); + ip6Pkt.GetSrcAddr(srcAddr); + break; + } + default: + { + PLOG(PL_ERROR,"pcap2norm Error: Invalid IP pkt version.\n"); + break; + } + } + //PLOG(PL_ALWAYS, "pcap2norm IP packet dst>%s ", dstAddr.GetHostString()); + //PLOG(PL_ALWAYS," src>%s length>%d\n", srcAddr.GetHostString(), ipPkt.GetLength()); + } + if (!srcAddr.IsValid()) continue; // wasn't an IP packet + + ProtoPktUDP udpPkt; + if (!udpPkt.InitFromPacket(ipPkt)) continue; // not a UDP packet + + NormMsg msg; + if (msg.CopyFromBuffer((const char*)udpPkt.GetPayload(), udpPkt.GetPayloadLength())) + { + srcAddr.SetPort(udpPkt.GetSrcPort()); + msg.AccessAddress() = srcAddr; + dstAddr.SetPort(udpPkt.GetDstPort()); + NormTrace2(hdr.ts, msg, srcAddr, dstAddr); + } + else + { + fprintf(stderr, "pcap2norm warning: UDP packet not an MGEN packet?\n"); + } + } // end while (pcap_next()) + +} // end main() + + +static UINT8 lastFecId = 0; + +void NormTrace2(const struct timeval& currentTime, + const NormMsg& msg, + const ProtoAddress& srcAddr, + const ProtoAddress& dstAddr) +{ + + UINT8 fecM = 16; // NOTE - this assumes 16-bit RS code for fec_id == 2 + + static const char* MSG_NAME[] = + { + "INVALID", + "INFO", + "DATA", + "CMD", + "NACK", + "ACK", + "REPORT" + }; + static const char* CMD_NAME[] = + { + "CMD(INVALID)", + "CMD(FLUSH)", + "CMD(EOT)", + "CMD(SQUELCH)", + "CMD(CC)", + "CMD(REPAIR_ADV)", + "CMD(ACK_REQ)", + "CMD(APP)" + }; + static const char* REQ_NAME[] = + { + "INVALID", + "WATERMARK", + "RTT", + "APP" + }; + + NormMsg::Type msgType = msg.GetType(); + UINT16 length = msg.GetLength(); + UINT16 seq = msg.GetSequence(); + char src[64], dst[64]; + src[63] = dst[63] = '\0'; + srcAddr.GetHostString(src, 63); + dstAddr.GetHostString(dst, 63); + + + +#ifdef _WIN32_WCE + struct tm timeStruct; + timeStruct.tm_hour = currentTime.tv_sec / 3600; + unsigned long hourSecs = 3600 * timeStruct.tm_hour; + timeStruct.tm_min = (currentTime.tv_sec - (hourSecs)) / 60; + timeStruct.tm_sec = currentTime.tv_sec - (hourSecs) - (60*timeStruct.tm_min); + timeStruct.tm_hour = timeStruct.tm_hour % 24; + struct tm* ct = &timeStruct; +#else + time_t secs = (time_t)currentTime.tv_sec; + struct tm* ct = gmtime(&secs); +#endif // if/else _WIN32_WCE + + + PLOG(PL_ALWAYS, "trace>%02d:%02d:%02d.%06lu ", + (int)ct->tm_hour, (int)ct->tm_min, (int)ct->tm_sec, (unsigned int)currentTime.tv_usec); + PLOG(PL_ALWAYS, "src>%s/%hu dst>%s/%hu id>0x%08x ", src, srcAddr.GetPort(), dst, dstAddr.GetPort(), (UINT32)msg.GetSourceId()); + + bool clrFlag = false; + switch (msgType) + { + case NormMsg::INFO: + { + const NormInfoMsg& info = (const NormInfoMsg&)msg; + lastFecId = info.GetFecId(); + PLOG(PL_ALWAYS, "inst>%hu seq>%hu INFO obj>%hu ", + info.GetInstanceId(), seq, (UINT16)info.GetObjectId()); + break; + } + case NormMsg::DATA: + { + const NormDataMsg& data = (const NormDataMsg&)msg; + lastFecId = data.GetFecId(); + PLOG(PL_ALWAYS, "inst>%hu seq>%hu DATA obj>%hu blk>%lu seg>%hu ", + data.GetInstanceId(), + seq, + //data.IsData() ? "DATA" : "PRTY", + (UINT16)data.GetObjectId(), + (UINT32)data.GetFecBlockId(fecM), + (UINT16)data.GetFecSymbolId(fecM)); + + if (data.IsStream()) + { + UINT32 offset = NormDataMsg::ReadStreamPayloadOffset(data.GetPayload()); + PLOG(PL_ALWAYS, "offset>%lu ", offset); + } + /* + if (data.IsData() && data.IsStream()) + { + //if (NormDataMsg::StreamPayloadFlagIsSet(data.GetPayload(), NormDataMsg::FLAG_MSG_START)) + UINT16 msgStartOffset = NormDataMsg::ReadStreamPayloadMsgStart(data.GetPayload()); + if (0 != msgStartOffset) + { + PLOG(PL_ALWAYS, "start word>%hu ", msgStartOffset - 1); + } + //if (NormDataMsg::StreamPayloadFlagIsSet(data.GetPayload(), NormDataMsg::FLAG_STREAM_END)) + if (0 == NormDataMsg::ReadStreamPayloadLength(data.GetPayload())) + PLOG(PL_ALWAYS, "(stream end) "); + } + */ + break; + } + case NormMsg::CMD: + { + const NormCmdMsg& cmd = static_cast(msg); + NormCmdMsg::Flavor flavor = cmd.GetFlavor(); + PLOG(PL_ALWAYS, "inst>%hu seq>%hu %s ", cmd.GetInstanceId(), seq, CMD_NAME[flavor]); + switch (flavor) + { + case NormCmdMsg::ACK_REQ: + { + int index = ((const NormCmdAckReqMsg&)msg).GetAckType(); + index = MIN(index, 3); + PLOG(PL_ALWAYS, "(%s) ", REQ_NAME[index]); + break; + } + case NormCmdMsg::SQUELCH: + { + const NormCmdSquelchMsg& squelch = + static_cast(msg); + PLOG(PL_ALWAYS, " obj>%hu blk>%lu seg>%hu ", + (UINT16)squelch.GetObjectId(), + (UINT32)squelch.GetFecBlockId(fecM), + (UINT16)squelch.GetFecSymbolId(fecM)); + break; + } + case NormCmdMsg::FLUSH: + { + const NormCmdFlushMsg& flush = + static_cast(msg); + PLOG(PL_ALWAYS, " obj>%hu blk>%lu seg>%hu ", + (UINT16)flush.GetObjectId(), + (UINT32)flush.GetFecBlockId(fecM), + (UINT16)flush.GetFecSymbolId(fecM)); + + // Print acking node list (if any) + UINT16 nodeCount = flush.GetAckingNodeCount(); + if (nodeCount > 0) + { + PLOG(PL_ALWAYS, "ackers>"); + for (UINT16 i = 0; i < nodeCount; i++) + { + if (i > 0) PLOG(PL_ALWAYS, ","); + PLOG(PL_ALWAYS,"0x%08x", (UINT32)flush.GetAckingNodeId(i)); + } + PLOG(PL_ALWAYS, " "); + } + break; + } + case NormCmdMsg::CC: + { + const NormCmdCCMsg& cc = static_cast(msg); + PLOG(PL_ALWAYS, " seq>%u ", cc.GetCCSequence()); + NormHeaderExtension ext; + while (cc.GetNextExtension(ext)) + { + if (NormHeaderExtension::CC_RATE == ext.GetType()) + { + UINT16 sendRate = ((NormCCRateExtension&)ext).GetSendRate(); + PLOG(PL_ALWAYS, " rate>%f ", 8.0e-03 * NormUnquantizeRate(sendRate)); + break; + } + } + break; + } + default: + break; + } + break; + } + + case NormMsg::ACK: + case NormMsg::NACK: + { + // look for NormCCFeedback extension + NormHeaderExtension ext; + while (msg.GetNextExtension(ext)) + { + if (NormHeaderExtension::CC_FEEDBACK == ext.GetType()) + { + clrFlag = ((NormCCFeedbackExtension&)ext).CCFlagIsSet(NormCC::CLR); + break; + } + } + PLOG(PL_ALWAYS, "%s ", MSG_NAME[msgType]); + PLOG(PL_ALWAYS, "len>%hu %s\n", length, clrFlag ? "(CLR)" : ""); + if (NormMsg::NACK == msgType) + { + const NormNackMsg& nack = static_cast(msg); + PLOG(PL_ALWAYS, "repair content for sender id 0x%08x)\n", nack.GetSenderId()); + LogRepairContent(nack.GetRepairContent(), nack.GetRepairContentLength(), lastFecId, fecM); + } + return; + break; + } + + default: + PLOG(PL_ALWAYS, "%s ", MSG_NAME[msgType]); + break; + } + PLOG(PL_ALWAYS, "len>%hu %s\n", length, clrFlag ? "(CLR)" : ""); +} // end NormTrace2(); + diff --git a/common/raft.cpp b/src/common/raft.cpp similarity index 78% rename from common/raft.cpp rename to src/common/raft.cpp index 27b419c..67bb57a 100644 --- a/common/raft.cpp +++ b/src/common/raft.cpp @@ -24,7 +24,7 @@ class RaftApp : public ProtoApp private: static void Usage() { - fprintf(stderr, "Usage: raft [listen [/]][dest /]\n"); + fprintf(stderr, "Usage: raft [listen [/]][dest /][ipv4|ipv6]\n"); } enum CmdType {CMD_INVALID, CMD_NOARG, CMD_ARG}; static CmdType GetCommandType(const char* cmd); @@ -56,6 +56,10 @@ class RaftApp : public ProtoApp ProtoAddress rtsp_server_address; ProtoSocket rtsp_client_socket; + enum IpvType { IPV4, IPV6 }; + IpvType rcv_socket_type; + ProtoAddress rcv_group_addr; + UINT16 rcv_port; }; // end class RaftApp const char* const RaftApp::CMD_LIST[] = @@ -64,6 +68,8 @@ const char* const RaftApp::CMD_LIST[] = "+listen", // recv [/] "+dest", // send / "+rtspProxy", // rtsp + "-ipv4", // rcv socket is IPv4 (default) + "-ipv6", // rcv socket is IPv6 NULL }; @@ -71,7 +77,8 @@ RaftApp::RaftApp() : rx_socket(ProtoSocket::UDP), tx_socket(ProtoSocket::UDP), tx_msg_length(0), tx_msg_index(0), rtsp_proxy_socket(ProtoSocket::TCP), rtsp_url(NULL), - rtsp_client_socket(ProtoSocket::TCP) + rtsp_client_socket(ProtoSocket::TCP), rcv_socket_type(IPV4), + rcv_port(0) { rx_socket.SetNotifier(&GetSocketNotifier()); rx_socket.SetListener(this, &RaftApp::OnRxSocketEvent); @@ -98,6 +105,35 @@ RaftApp::~RaftApp() bool RaftApp::OnStartup(int argc, const char*const* argv) { bool result = ProcessCommands(argc, argv); + if (rcv_port > 0) // need to open rcv port after PC() + { + if (rcv_socket_type == IPV4) + { + if (!rx_socket.Open(rcv_port, ProtoAddress::IPv4)) + { + PLOG(PL_FATAL, "Raft::OnCommand() rx_socket.Open() error\n"); + return false; + } + } + else // IPV6 + { + if (!rx_socket.Open(rcv_port, ProtoAddress::IPv6)) + { + PLOG(PL_FATAL, "Raft::OnCommand() rx_socket.Open() error\n"); + return false; + } + } + + if (rcv_group_addr.IsValid()) // need to join group after PC() + { + if (!rx_socket.JoinGroup(rcv_group_addr)) + { + PLOG(PL_FATAL, "Raft::OnStartup() rx_socket.JoinGroup() error\n"); + return false; + } + } + } + if (result && !dispatcher.IsPending()) { Usage(); @@ -125,7 +161,7 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) if (!strncmp(cmd, "dest", strlen(cmd))) { char host[256]; - char* ptr = strchr(arg, '/'); + char* ptr = (char*) strchr(arg, '/'); if (ptr) { unsigned int len = ptr - arg; @@ -135,25 +171,25 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) } else { - DMSG(0, "Raft::OnCommand() invalid \"dest\" command\n"); + PLOG(PL_FATAL, "Raft::OnCommand() invalid \"dest\" command\n"); return false; } if (!tx_address.ResolveFromString(host)) { - DMSG(0, "Raft::OnCommand() invalid dest address\n"); + PLOG(PL_FATAL, "Raft::OnCommand() invalid dest address\n"); return false; } UINT16 port; if (1 != sscanf(ptr, "%hu", &port)) { - DMSG(0, "Raft::OnCommand() invalid dest port\n"); + PLOG(PL_FATAL, "Raft::OnCommand() invalid dest port\n"); return false; } tx_address.SetPort(port); int fd = fileno(stdin); if(-1 == fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)) { - DMSG(0, "Raft::OnCommand() warning: fcntl(stdout, F_SETFL(O_NONBLOCK)) error: %s", + PLOG(PL_FATAL, "Raft::OnCommand() warning: fcntl(stdout, F_SETFL(O_NONBLOCK)) error: %s", strerror(errno)); } tx_msg_length = tx_msg_index = 0; @@ -161,57 +197,42 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) } else if (!strncmp(cmd, "listen", strlen(cmd))) { - ProtoAddress groupAddr; const char* ptr = strchr(arg, '/'); - if (ptr) + if (!ptr) + ptr = arg; // no group address, just a port # + else // extract the group address { char group[256]; unsigned int len = ptr - arg; + ptr++; // point to port # strncpy(group, arg, len); group[len] = '\0'; - if (!groupAddr.ResolveFromString(group) || - !groupAddr.IsMulticast()) + if (!rcv_group_addr.ResolveFromString(group) || + !rcv_group_addr.IsMulticast() ) { - DMSG(0, "Raft::OnCommand() invalid recv multicast address\n"); + PLOG(PL_FATAL, "Raft::OnCommand() invalid recv multicast address\n"); return false; } - ptr++; } - else + + if (1 != sscanf(ptr, "%hu", &rcv_port)) { - ptr = arg; - } - UINT16 port; - if (1 != sscanf(ptr, "%hu", &port)) - { - DMSG(0, "Raft::OnCommand() invalid recv port\n"); + PLOG(PL_FATAL, "Raft::OnCommand() invalid recv port\n"); return false; } - if (!rx_socket.Open(port)) - { - DMSG(0, "Raft::OnCommand() rx_socket.Open() error\n"); - return false; - } - if (groupAddr.IsValid()) - { - if (!rx_socket.JoinGroup(groupAddr)) - { - DMSG(0, "Raft::OnCommand() rx_socket.JoinGroup() error\n"); - return false; - } - } + } else if (!strncmp(cmd, "rtspProxy", strlen(cmd))) { if (!rtsp_proxy_socket.Listen(554)) { - DMSG(0, "RaftApp::OnCommand() error: rtsp_proxy_socket.Listen() failed\n"); + PLOG(PL_FATAL, "RaftApp::OnCommand() error: rtsp_proxy_socket.Listen() failed\n"); return false; } if (rtsp_url) delete rtsp_url; if (!(rtsp_url = new char[strlen(arg) + 1])) { - DMSG(0, "RaftApp::OnCommand() new rtsp_url error: %s\n", + PLOG(PL_FATAL, "RaftApp::OnCommand() new rtsp_url error: %s\n", strerror(errno)); rtsp_proxy_socket.Close(); return false; @@ -220,7 +241,7 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) char* ptr = strstr(rtsp_url, "rtsp://"); if (!ptr) { - DMSG(0, "RaftApp::OnCommand() error: invalid rtsp_url\n"); + PLOG(PL_FATAL, "RaftApp::OnCommand() error: invalid rtsp_url\n"); rtsp_proxy_socket.Close(); return false; } @@ -228,7 +249,7 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) char hostName[256]; if (1 != sscanf(ptr, "%255s", hostName)) { - DMSG(0, "RaftApp::OnCommand() error: no rtsp_url hostname\n"); + PLOG(PL_FATAL, "RaftApp::OnCommand() error: no rtsp_url hostname\n"); rtsp_proxy_socket.Close(); return false; } @@ -236,16 +257,20 @@ bool RaftApp::OnCommand(const char* cmd, const char* arg) if (sptr) *sptr = '\0'; if (!rtsp_server_address.ResolveFromString(hostName)) { - DMSG(0, "RaftApp::OnCommand() error: invalid rtsp_url hostname:%s\n", + PLOG(PL_FATAL, "RaftApp::OnCommand() error: invalid rtsp_url hostname:%s\n", hostName); rtsp_proxy_socket.Close(); return false; } rtsp_server_address.SetPort(554); } + else if (!strncmp(cmd, "ipv6", strlen(cmd))) + rcv_socket_type = IPV6; + else if (!strncmp(cmd, "ipv4", strlen(cmd))) + rcv_socket_type = IPV4; // the default else { - DMSG(0, "RaftApp::OnCommand() error: invalid command!\n"); + PLOG(PL_FATAL, "RaftApp::OnCommand() error: invalid command!\n"); return false; } return true; @@ -261,7 +286,7 @@ bool RaftApp::ProcessCommands(int argc, const char*const* argv) case CMD_ARG: if (!OnCommand(argv[i], argv[i+1])) { - DMSG(0, "RaftApp::ProcessCommands() %s command error\n", argv[i]); + PLOG(PL_FATAL, "RaftApp::ProcessCommands() %s command error\n", argv[i]); return false; } i += 2; @@ -269,13 +294,13 @@ bool RaftApp::ProcessCommands(int argc, const char*const* argv) case CMD_NOARG: if (!OnCommand(argv[i])) { - DMSG(0, "RaftApp::ProcessCommands() %s command error\n", argv[i]); + PLOG(PL_FATAL, "RaftApp::ProcessCommands() %s command error\n", argv[i]); return false; } i++; break; default: - DMSG(0, "RaftApp::ProcessCommands() invalid command\n"); + PLOG(PL_FATAL, "RaftApp::ProcessCommands() invalid command\n"); return false; } } @@ -308,14 +333,14 @@ void RaftApp::OnInputReady() case EAGAIN: break; default: - DMSG(0, "raft: input error:%s\n", strerror(errno)); + PLOG(PL_ERROR, "raft: input error:%s\n", strerror(errno)); break; } clearerr(stdin); } else if (feof(stdin)) { - DMSG(0, "raft: input end-of-file\n"); + PLOG(PL_ERROR, "raft: input end-of-file\n"); dispatcher.RemoveGenericInput(fileno(stdin)); return; } @@ -328,7 +353,7 @@ void RaftApp::OnInputReady() tx_msg_length = ntohs(tx_msg_length); if ((tx_msg_length < 2) || (tx_msg_length > 8194)) { - DMSG(0, "raft: input error: invalid tx_msg_length: %u\n", tx_msg_length); + PLOG(PL_ERROR, "raft: input error: invalid tx_msg_length: %u\n", tx_msg_length); dispatcher.RemoveGenericInput(fileno(stdin)); return; } @@ -339,7 +364,7 @@ void RaftApp::OnInputReady() { if (!tx_socket.SendTo(tx_msg_buffer+2, tx_msg_length-2, tx_address)) { - DMSG(0, "raft: tx_socket.SendTo() error\n"); + PLOG(PL_ERROR, "raft: tx_socket.SendTo() error\n"); return; } tx_msg_index = tx_msg_length = 0; @@ -355,7 +380,7 @@ void RaftApp::OnRxSocketEvent(ProtoSocket& /*theSocket*/, ProtoAddress srcAddr; if (!rx_socket.RecvFrom(buffer+2, numBytes, srcAddr)) { - DMSG(0, "RaftApp::OnRxSocketEvent() rx_socket.RecvFrom() error\n"); + PLOG(PL_ERROR, "RaftApp::OnRxSocketEvent() rx_socket.RecvFrom() error\n"); return; } numBytes += 2; @@ -371,7 +396,7 @@ void RaftApp::OnRxSocketEvent(ProtoSocket& /*theSocket*/, } else if (EINTR != errno) { - DMSG(0, "RaftApp::OnRxSocketEvent() fwrite() error: %s\n", + PLOG(PL_ERROR, "RaftApp::OnRxSocketEvent() fwrite() error: %s\n", strerror(errno)); } } @@ -388,15 +413,15 @@ void RaftApp::OnProxySocketEvent(ProtoSocket& /*theSocket*/, { if (!rtsp_proxy_socket.Accept()) { - DMSG(0, "RaftApp::OnProxySocketEvent() rtsp_proxy_socket.Accept() error\n"); + PLOG(PL_ERROR, "RaftApp::OnProxySocketEvent() rtsp_proxy_socket.Accept() error\n"); rtsp_proxy_socket.Close(); if (!rtsp_proxy_socket.Listen(554)) - DMSG(0, "raft: rtsp_proxy_socket.Listen() error\n"); + PLOG(PL_ERROR, "raft: rtsp_proxy_socket.Listen() error\n"); } TRACE("calling rtsp_client_socket.Connect() ...\n"); if (!rtsp_client_socket.Connect(rtsp_server_address)) { - DMSG(0, "RaftApp::OnProxySocketEvent() rtsp_client_socket.Connect() error\n"); + PLOG(PL_ERROR, "RaftApp::OnProxySocketEvent() rtsp_client_socket.Connect() error\n"); rtsp_proxy_socket.Close(); } TRACE(" rtsp_client_socket.Connect() complete.\n"); @@ -418,11 +443,11 @@ void RaftApp::OnProxySocketEvent(ProtoSocket& /*theSocket*/, unsigned int numBytes = buflen - put; if (!rtsp_client_socket.Send(buffer+put, numBytes)) { - DMSG(0, "rtsp_client_socket.Send() error\n"); + PLOG(PL_ERROR, "rtsp_client_socket.Send() error\n"); rtsp_client_socket.Close(); rtsp_proxy_socket.Close(); if (!rtsp_proxy_socket.Listen(554)) - DMSG(0, "raft: rtsp_proxy_socket.Listen() error\n"); + PLOG(PL_ERROR, "raft: rtsp_proxy_socket.Listen() error\n"); } put += numBytes; } @@ -439,7 +464,7 @@ void RaftApp::OnProxySocketEvent(ProtoSocket& /*theSocket*/, rtsp_proxy_socket.Close(); rtsp_client_socket.Close(); if (!rtsp_proxy_socket.Listen(554)) - DMSG(0, "raft: rtsp_proxy_socket.Listen() error\n"); + PLOG(PL_ERROR, "raft: rtsp_proxy_socket.Listen() error\n"); break; } default: @@ -478,7 +503,7 @@ void RaftApp::OnClientSocketEvent(ProtoSocket& /*theSocket*/, rtsp_client_socket.Close(); rtsp_proxy_socket.Close(); if (!rtsp_proxy_socket.Listen(554)) - DMSG(0, "raft: rtsp_proxy_socket.Listen() error\n"); + PLOG(PL_ERROR, "raft: rtsp_proxy_socket.Listen() error\n"); break; } default: diff --git a/common/rules.txt b/src/common/rules.txt similarity index 100% rename from common/rules.txt rename to src/common/rules.txt diff --git a/src/java/.classpath b/src/java/.classpath new file mode 100644 index 0000000..90b4838 --- /dev/null +++ b/src/java/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/java/.project b/src/java/.project new file mode 100644 index 0000000..ccf0ba0 --- /dev/null +++ b/src/java/.project @@ -0,0 +1,17 @@ + + + norm-jni + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/java/jni/normDataJni.cpp b/src/java/jni/normDataJni.cpp new file mode 100644 index 0000000..b4aa033 --- /dev/null +++ b/src/java/jni/normDataJni.cpp @@ -0,0 +1,31 @@ +#include "normJni.h" +#include "normDataJni.h" + +JNIEXPORT jbyteArray JNICALL PKGNAME(NormData_getData) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + jbyteArray data; + jbyte *ptr; + const char *buffer; + int length; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + // Get the data information from NORM + buffer = NormDataAccessData(objectHandle); + length = NormObjectGetSize(objectHandle); + + // Create a new java array to hold the data + data = env->NewByteArray(length); + + // Get the element array and copy the data into it + ptr = env->GetByteArrayElements(data, NULL); + memcpy(ptr, buffer, length); + + // Release the array, commiting the changes back to the java array + env->ReleaseByteArrayElements(data, ptr, 0); + + return data; +} + diff --git a/src/java/jni/normDataJni.h b/src/java/jni/normDataJni.h new file mode 100644 index 0000000..3b114a3 --- /dev/null +++ b/src/java/jni/normDataJni.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormData */ + +#ifndef _Included_mil_navy_nrl_norm_NormData +#define _Included_mil_navy_nrl_norm_NormData +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormData + * Method: getData + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_mil_navy_nrl_norm_NormData_getData + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normEventJni.cpp b/src/java/jni/normEventJni.cpp new file mode 100644 index 0000000..75e7922 --- /dev/null +++ b/src/java/jni/normEventJni.cpp @@ -0,0 +1,35 @@ +#include "normJni.h" +#include "normEventJni.h" + +JNIEXPORT jobject JNICALL PKGNAME(NormEvent_getObject) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + jobject object = NULL; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormEvent_objectHandle); + + switch (NormObjectGetType(objectHandle)) { + case NORM_OBJECT_DATA: + object = env->NewObject((jclass)env->NewLocalRef(jw_NormData), mid_NormData_init, + (jlong)objectHandle); + break; + + case NORM_OBJECT_FILE: + object = env->NewObject((jclass)env->NewLocalRef(jw_NormFile), mid_NormFile_init, + (jlong)objectHandle); + break; + + case NORM_OBJECT_STREAM: + object = env->NewObject((jclass)env->NewLocalRef(jw_NormStream), mid_NormStream_init, + (jlong)objectHandle); + break; + + default: + case NORM_OBJECT_NONE: + break; + } + + return object; +} + diff --git a/src/java/jni/normEventJni.h b/src/java/jni/normEventJni.h new file mode 100644 index 0000000..8993f41 --- /dev/null +++ b/src/java/jni/normEventJni.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormEvent */ + +#ifndef _Included_mil_navy_nrl_norm_NormEvent +#define _Included_mil_navy_nrl_norm_NormEvent +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormEvent + * Method: getObject + * Signature: ()Lmil/navy/nrl/norm/NormObject; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormEvent_getObject + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normFileJni.cpp b/src/java/jni/normFileJni.cpp new file mode 100644 index 0000000..c9720a8 --- /dev/null +++ b/src/java/jni/normFileJni.cpp @@ -0,0 +1,39 @@ +#include "normJni.h" +#include "normFileJni.h" + +JNIEXPORT jstring JNICALL PKGNAME(NormFile_getName) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + char path[FILENAME_MAX]; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, fid_NormObject_handle); + + // Get the path information from NORM + if (!NormFileGetName(objectHandle, path, FILENAME_MAX)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to get file name"); + return NULL; + } + + // Create a new Java string from the path + jstring str = env->NewStringUTF(path); + + return str; +} + +JNIEXPORT void JNICALL PKGNAME(NormFile_rename) + (JNIEnv *env, jobject obj, jstring filename) { + NormObjectHandle objectHandle; + const char *str; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, fid_NormObject_handle); + str = env->GetStringUTFChars(filename, NULL); + + if (!NormFileRename(objectHandle, str)) { + env->ReleaseStringUTFChars(filename, str); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to rename file"); + return; + } + + env->ReleaseStringUTFChars(filename, str); +} + diff --git a/src/java/jni/normFileJni.h b/src/java/jni/normFileJni.h new file mode 100644 index 0000000..ca6676d --- /dev/null +++ b/src/java/jni/normFileJni.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormFile */ + +#ifndef _Included_mil_navy_nrl_norm_NormFile +#define _Included_mil_navy_nrl_norm_NormFile +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormFile + * Method: getName + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_mil_navy_nrl_norm_NormFile_getName + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormFile + * Method: rename + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormFile_rename + (JNIEnv *, jobject, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normInstanceJni.cpp b/src/java/jni/normInstanceJni.cpp new file mode 100644 index 0000000..8fcd6c3 --- /dev/null +++ b/src/java/jni/normInstanceJni.cpp @@ -0,0 +1,210 @@ +#include "normJni.h" +#include "normInstanceJni.h" + +#ifndef WIN32 +#include +#endif + +JNIEXPORT void JNICALL PKGNAME(NormInstance_createInstance) + (JNIEnv *env, jobject obj, jboolean priorityBoost) { + NormInstanceHandle handle; + + handle = NormCreateInstance(priorityBoost); + if (handle == NORM_INSTANCE_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to create NORM instance"); + return; + } + + env->SetLongField(obj, fid_NormInstance_handle, (jlong)handle); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_destroyInstance) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + NormDestroyInstance(handle); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_stopInstance) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + NormStopInstance(handle); +} + +JNIEXPORT jboolean JNICALL PKGNAME(NormInstance_restartInstance) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + return NormRestartInstance(handle); +} + +JNIEXPORT jboolean JNICALL PKGNAME(NormInstance_suspendInstance) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + return NormSuspendInstance(handle); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_resumeInstance) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + NormResumeInstance(handle); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_setCacheDirectory) + (JNIEnv *env, jobject obj, jstring cachePath) { + NormInstanceHandle handle; + const char *str; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + str = env->GetStringUTFChars(cachePath, NULL); + + if (!NormSetCacheDirectory(handle, str)) { + env->ReleaseStringUTFChars(cachePath, str); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set the cache directory"); + return; + } + + env->ReleaseStringUTFChars(cachePath, str); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_openDebugLog) + (JNIEnv *env, jobject obj, jstring filename) { + NormInstanceHandle handle; + const char *str; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + str = env->GetStringUTFChars(filename, NULL); + + if (!NormOpenDebugLog(handle, str)) { + env->ReleaseStringUTFChars(filename, str); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to open debug log"); + return; + } + + env->ReleaseStringUTFChars(filename, str); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_closeDebugLog) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + NormCloseDebugLog(handle); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_openDebugPipe) + (JNIEnv *env, jobject obj, jstring pipename) { + NormInstanceHandle handle; + const char *str; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + str = env->GetStringUTFChars(pipename, NULL); + + if (!NormOpenDebugLog(handle, str)) { + env->ReleaseStringUTFChars(pipename, str); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to open debug log"); + return; + } + + env->ReleaseStringUTFChars(pipename, str); +} + +JNIEXPORT void JNICALL PKGNAME(NormInstance_setDebugLevel) + (JNIEnv *env, jobject obj, jint level) { + NormSetDebugLevel(level); +} + +JNIEXPORT jint JNICALL PKGNAME(NormInstance_getDebugLevel) + (JNIEnv *env, jobject obj) { + return NormGetDebugLevel(); +} + +JNIEXPORT jboolean JNICALL PKGNAME(NormInstance_hasNextEvent) + (JNIEnv *env, jobject obj, jint sec, jint usec) { + NormInstanceHandle handle; + NormDescriptor nd; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + nd = NormGetDescriptor(handle); +#ifndef WIN32 + struct timeval tv; + fd_set fds; + int rv; + + FD_ZERO(&fds); + FD_SET(nd, &fds); + + tv.tv_sec = sec; + tv.tv_usec = usec; + + rv = select(nd + 1, &fds, NULL, NULL, &tv); + + if (rv == -1) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to perform select"); + return false; + } + + return rv != 0 ? true : false; +#else + return WaitForSingleObject((HANDLE)nd, sec * 1000 + usec / 1000) != WAIT_FAILED; +#endif +} + +JNIEXPORT jobject JNICALL PKGNAME(NormInstance_getNextEvent) + (JNIEnv *env, jobject obj) { + NormInstanceHandle handle; + NormEvent event; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + + if (!NormGetNextEvent(handle, &event)) { + //env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to get next event"); + return NULL; + } + + // Get the event type + jobjectArray array = (jobjectArray)env->CallStaticObjectMethod( + (jclass)env->NewLocalRef(jw_NormEventType), mid_NormEventType_values); + jobject type = env->GetObjectArrayElement(array, event.type); + + // Create the event + return env->NewObject((jclass)env->NewLocalRef(jw_NormEvent), mid_NormEvent_init, type, + (jlong)event.session, (jlong)event.sender, (jlong)event.object); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormInstance_createSession) + (JNIEnv *env, jobject obj, jstring address, jint port, jlong localNodeId) { + NormInstanceHandle handle; + NormSessionHandle session; + const char *str; + + handle = (NormInstanceHandle)env->GetLongField(obj, fid_NormInstance_handle); + str = env->GetStringUTFChars(address, NULL); + + session = NormCreateSession(handle, str, port, (NormNodeId)localNodeId); + + // Release the string + env->ReleaseStringUTFChars(address, str); + + if (session == NORM_SESSION_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to create session"); + return NULL; + } + + return env->NewObject((jclass)env->NewLocalRef(jw_NormSession), mid_NormSession_init, (jlong)session); +} + diff --git a/src/java/jni/normInstanceJni.h b/src/java/jni/normInstanceJni.h new file mode 100644 index 0000000..5ab291e --- /dev/null +++ b/src/java/jni/normInstanceJni.h @@ -0,0 +1,133 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormInstance */ + +#ifndef _Included_mil_navy_nrl_norm_NormInstance +#define _Included_mil_navy_nrl_norm_NormInstance +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: createInstance + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_createInstance + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: destroyInstance + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_destroyInstance + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: stopInstance + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_stopInstance + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: restartInstance + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_mil_navy_nrl_norm_NormInstance_restartInstance + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: suspendInstance + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_mil_navy_nrl_norm_NormInstance_suspendInstance + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: resumeInstance + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_resumeInstance + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: setCacheDirectory + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_setCacheDirectory + (JNIEnv *, jobject, jstring); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: openDebugLog + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_openDebugLog + (JNIEnv *, jobject, jstring); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: closeDebugLog + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_closeDebugLog + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: openDebugPipe + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_openDebugPipe + (JNIEnv *, jobject, jstring); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: setDebugLevel + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormInstance_setDebugLevel + (JNIEnv *, jobject, jint); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: getDebugLevel + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_mil_navy_nrl_norm_NormInstance_getDebugLevel + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: hasNextEvent + * Signature: (II)Z + */ +JNIEXPORT jboolean JNICALL Java_mil_navy_nrl_norm_NormInstance_hasNextEvent + (JNIEnv *, jobject, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: getNextEvent + * Signature: ()Lmil/navy/nrl/norm/NormEvent; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormInstance_getNextEvent + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormInstance + * Method: createSession + * Signature: (Ljava/lang/String;IJ)Lmil/navy/nrl/norm/NormSession; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormInstance_createSession + (JNIEnv *, jobject, jstring, jint, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normJni.cpp b/src/java/jni/normJni.cpp new file mode 100644 index 0000000..610a6e2 --- /dev/null +++ b/src/java/jni/normJni.cpp @@ -0,0 +1,226 @@ +#include "normJni.h" + +#ifdef WIN32 +#define snprintf _snprintf +#endif + +jweak jw_NormInstance; +jfieldID fid_NormInstance_handle; + +jweak jw_NormAckingStatus; +jmethodID mid_NormAckingStatus_values; + +jweak jw_NormData; +jmethodID mid_NormData_init; + +jweak jw_NormEvent; +jmethodID mid_NormEvent_init; +jfieldID fid_NormEvent_objectHandle; + +jweak jw_NormEventType; +jmethodID mid_NormEventType_values; + +jweak jw_NormFile; +jmethodID mid_NormFile_init; + +jweak jw_NormFlushMode; +jmethodID mid_NormFlushMode_ordinal; + +jweak jw_NormNackingMode; +jmethodID mid_NormNackingMode_ordinal; + +jweak jw_NormNode; +jfieldID fid_NormNode_handle; +jmethodID mid_NormNode_init; + +jweak jw_NormObject; +jfieldID fid_NormObject_handle; + +jweak jw_NormObjectType; +jmethodID mid_NormObjectType_values; + +jweak jw_NormProbingMode; +jmethodID mid_NormProbingMode_ordinal; + +jweak jw_NormRepairBoundary; +jmethodID mid_NormRepairBoundary_ordinal; + +jweak jw_NormSession; +jfieldID fid_NormSession_handle; +jmethodID mid_NormSession_init; + +jweak jw_NormStream; +jmethodID mid_NormStream_init; + +jweak jw_NormSyncPolicy; +jmethodID mid_NormSyncPolicy_ordinal; + +jweak jw_InetAddress; +jmethodID mid_InetAddress_getByAddress; + +jweak jw_InetSocketAddress; +jmethodID mid_InetSocketAddress_init; + +jweak jw_IOException; + +void check_version(JNIEnv *env); + +/* Called by the JVM when the library is loaded */ +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + + if (vm->GetEnv((void **)&env, JNI_VERSION_1_4)) { + return JNI_ERR; /* JNI version not supported */ + } + + /* Load the NormInstance first so we can compare the versions */ + jclass NormInstanceClass = env->FindClass("mil/navy/nrl/norm/NormInstance"); + jw_NormInstance = env->NewWeakGlobalRef(NormInstanceClass); + fid_NormInstance_handle = env->GetFieldID(NormInstanceClass, "handle", "J"); + + /* Check the version */ + check_version(env); + + jclass NormAckingStatusClass = env->FindClass("mil/navy/nrl/norm/enums/NormAckingStatus"); + jw_NormAckingStatus = env->NewWeakGlobalRef(NormAckingStatusClass); + mid_NormAckingStatus_values = env->GetStaticMethodID(NormAckingStatusClass, + "values", "()[Lmil/navy/nrl/norm/enums/NormAckingStatus;"); + + jclass NormDataClass = env->FindClass("mil/navy/nrl/norm/NormData"); + jw_NormData = env->NewWeakGlobalRef(NormDataClass); + mid_NormData_init = env->GetMethodID(NormDataClass, "", "(J)V"); + + jclass NormEventClass = env->FindClass("mil/navy/nrl/norm/NormEvent"); + jw_NormEvent = env->NewWeakGlobalRef(NormEventClass); + fid_NormEvent_objectHandle = env->GetFieldID(NormEventClass,"objectHandle", "J"); + mid_NormEvent_init = env->GetMethodID(NormEventClass, "", + "(Lmil/navy/nrl/norm/enums/NormEventType;JJJ)V"); + + jclass NormEventTypeClass = env->FindClass("mil/navy/nrl/norm/enums/NormEventType"); + jw_NormEventType = env->NewWeakGlobalRef(NormEventTypeClass); + mid_NormEventType_values = env->GetStaticMethodID(NormEventTypeClass, + "values", "()[Lmil/navy/nrl/norm/enums/NormEventType;"); + + jclass NormFileClass = env->FindClass("mil/navy/nrl/norm/NormFile"); + jw_NormFile = env->NewWeakGlobalRef(NormFileClass); + mid_NormFile_init = env->GetMethodID(NormFileClass, "", "(J)V"); + + jclass NormFlushModeClass = env->FindClass("mil/navy/nrl/norm/enums/NormFlushMode"); + jw_NormFlushMode = env->NewWeakGlobalRef(NormFlushModeClass); + mid_NormFlushMode_ordinal = env->GetMethodID(NormFlushModeClass, + "ordinal", "()I"); + + jclass NormNackingModeClass = env->FindClass("mil/navy/nrl/norm/enums/NormNackingMode"); + jw_NormNackingMode = env->NewWeakGlobalRef(NormNackingModeClass); + mid_NormNackingMode_ordinal = env->GetMethodID(NormNackingModeClass, + "ordinal", "()I"); + + jclass NormNodeClass = env->FindClass("mil/navy/nrl/norm/NormNode"); + jw_NormNode = env->NewWeakGlobalRef(NormNodeClass); + fid_NormNode_handle = env->GetFieldID(NormNodeClass, "handle", "J"); + mid_NormNode_init = env->GetMethodID(NormNodeClass, "", "(J)V"); + + jclass NormObjectClass = env->FindClass("mil/navy/nrl/norm/NormObject"); + jw_NormObject = env->NewWeakGlobalRef(NormObjectClass); + fid_NormObject_handle = env->GetFieldID(NormObjectClass, "handle", "J"); + + jclass NormObjectTypeClass = env->FindClass("mil/navy/nrl/norm/enums/NormObjectType"); + jw_NormObjectType = env->NewWeakGlobalRef(NormObjectTypeClass); + mid_NormObjectType_values = env->GetStaticMethodID(NormObjectTypeClass, + "values", "()[Lmil/navy/nrl/norm/enums/NormObjectType;"); + + jclass NormProbingModeClass = env->FindClass("mil/navy/nrl/norm/enums/NormProbingMode"); + jw_NormProbingMode = env->NewWeakGlobalRef(NormProbingModeClass); + mid_NormProbingMode_ordinal = env->GetMethodID(NormProbingModeClass, + "ordinal", "()I"); + + jclass NormRepairBoundaryClass = env->FindClass("mil/navy/nrl/norm/enums/NormRepairBoundary"); + jw_NormRepairBoundary = env->NewWeakGlobalRef(NormRepairBoundaryClass); + mid_NormRepairBoundary_ordinal = env->GetMethodID(NormRepairBoundaryClass, + "ordinal", "()I"); + + jclass NormSessionClass = env->FindClass("mil/navy/nrl/norm/NormSession"); + jw_NormSession = env->NewWeakGlobalRef(NormSessionClass); + fid_NormSession_handle = env->GetFieldID(NormSessionClass, "handle", "J"); + mid_NormSession_init = env->GetMethodID(NormSessionClass, "", "(J)V"); + + jclass NormStreamClass = env->FindClass("mil/navy/nrl/norm/NormStream"); + jw_NormStream = env->NewWeakGlobalRef(NormStreamClass); + mid_NormStream_init = env->GetMethodID(NormStreamClass, "", "(J)V"); + + jclass NormSyncPolicyClass = env->FindClass("mil/navy/nrl/norm/enums/NormSyncPolicy"); + jw_NormSyncPolicy = env->NewWeakGlobalRef(NormSyncPolicyClass); + mid_NormSyncPolicy_ordinal = env->GetMethodID(NormSyncPolicyClass, + "ordinal", "()I"); + + jclass InetAddressClass = env->FindClass("java/net/InetAddress"); + jw_InetAddress = env->NewWeakGlobalRef(InetAddressClass); + mid_InetAddress_getByAddress = env->GetStaticMethodID(InetAddressClass, + "getByAddress", "([B)Ljava/net/InetAddress;"); + + jclass InetSocketAddressClass = env->FindClass("java/net/InetSocketAddress"); + jw_InetSocketAddress = env->NewWeakGlobalRef(InetSocketAddressClass); + mid_InetSocketAddress_init = env->GetMethodID(InetSocketAddressClass, + "", "(Ljava/net/InetAddress;I)V"); + + jw_IOException = (jclass)env->NewWeakGlobalRef( + env->FindClass("java/io/IOException")); + + return JNI_VERSION_1_4; +} + +void check_version(JNIEnv *env) { + jfieldID fid; + jstring versionString; + const char *version; + + jclass LocalNormInstance = (jclass)env->NewLocalRef(jw_NormInstance); + fid = env->GetStaticFieldID(LocalNormInstance, "VERSION", "Ljava/lang/String;"); + if (fid == NULL) { + env->FatalError("Could not obtain version from NormInstance class." + " The native library and jar file are not compatible.\n"); + } + + versionString = (jstring)env->GetStaticObjectField(LocalNormInstance, fid); + version = env->GetStringUTFChars(versionString, NULL); + + if (strcmp(VERSION, version) != 0) { + char str[1024]; + snprintf(str, 1024, "Warning: NORM JNI versions do not match %s != %s." + " The native library and jar file are not compatible.\n", + VERSION, version); + env->FatalError(str); + } + + env->ReleaseStringUTFChars(versionString, version); +} + +/* Called by the JVM when the library is unloaded */ +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv *env; + + if (vm->GetEnv((void **)&env, JNI_VERSION_1_2)) { + return; + } + + env->DeleteWeakGlobalRef(jw_InetAddress); + env->DeleteWeakGlobalRef(jw_InetSocketAddress); + env->DeleteWeakGlobalRef(jw_IOException); + env->DeleteWeakGlobalRef(jw_NormAckingStatus); + env->DeleteWeakGlobalRef(jw_NormData); + env->DeleteWeakGlobalRef(jw_NormEvent); + env->DeleteWeakGlobalRef(jw_NormEventType); + env->DeleteWeakGlobalRef(jw_NormFile); + env->DeleteWeakGlobalRef(jw_NormFlushMode); + env->DeleteWeakGlobalRef(jw_NormInstance); + env->DeleteWeakGlobalRef(jw_NormNackingMode); + env->DeleteWeakGlobalRef(jw_NormNode); + env->DeleteWeakGlobalRef(jw_NormObject); + env->DeleteWeakGlobalRef(jw_NormObjectType); + env->DeleteWeakGlobalRef(jw_NormProbingMode); + env->DeleteWeakGlobalRef(jw_NormRepairBoundary); + env->DeleteWeakGlobalRef(jw_NormSession); + env->DeleteWeakGlobalRef(jw_NormStream); + env->DeleteWeakGlobalRef(jw_NormSyncPolicy); +} + diff --git a/src/java/jni/normJni.h b/src/java/jni/normJni.h new file mode 100644 index 0000000..14db63e --- /dev/null +++ b/src/java/jni/normJni.h @@ -0,0 +1,85 @@ +#ifndef __NORMJNI_H__ +#define __NORMJNI_H__ + +#include +#include +#include +#include + +/* + * This version string is used to validate the compatibility between the Java + * and C native libraries. Update this string along with it's counterpart in + * the NormInstance.java file whenever the native API changes. + */ +#define VERSION "20130415-0927" + +#define PKGNAME(str) Java_mil_navy_nrl_norm_##str + +#ifdef __cplusplus +extern "C" { +#endif + +extern jweak jw_InetAddress; +extern jmethodID mid_InetAddress_getByAddress; + +extern jweak jw_InetSocketAddress; +extern jmethodID mid_InetSocketAddress_init; + +extern jweak jw_IOException; + +extern jweak jw_NormAckingStatus; +extern jmethodID mid_NormAckingStatus_values; + +extern jweak jw_NormData; +extern jmethodID mid_NormData_init; + +extern jweak jw_NormEvent; +extern jmethodID mid_NormEvent_init; +extern jfieldID fid_NormEvent_objectHandle; + +extern jweak jw_NormEventType; +extern jmethodID mid_NormEventType_values; + +extern jweak jw_NormFile; +extern jmethodID mid_NormFile_init; + +extern jweak jw_NormFlushMode; +extern jmethodID mid_NormFlushMode_ordinal; + +extern jweak jw_NormInstance; +extern jfieldID fid_NormInstance_handle; + +extern jweak jw_NormNackingMode; +extern jmethodID mid_NormNackingMode_ordinal; + +extern jweak jw_NormNode; +extern jfieldID fid_NormNode_handle; +extern jmethodID mid_NormNode_init; + +extern jweak jw_NormObject; +extern jfieldID fid_NormObject_handle; + +extern jweak jw_NormObjectType; +extern jmethodID mid_NormObjectType_values; + +extern jweak jw_NormProbingMode; +extern jmethodID mid_NormProbingMode_ordinal; + +extern jweak jw_NormRepairBoundary; +extern jmethodID mid_NormRepairBoundary_ordinal; + +extern jweak jw_NormSession; +extern jfieldID fid_NormSession_handle; +extern jmethodID mid_NormSession_init; + +extern jweak jw_NormStream; +extern jmethodID mid_NormStream_init; + +extern jweak jw_NormSyncPolicy; +extern jmethodID mid_NormSyncPolicy_ordinal; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/java/jni/normNodeJni.cpp b/src/java/jni/normNodeJni.cpp new file mode 100644 index 0000000..8678308 --- /dev/null +++ b/src/java/jni/normNodeJni.cpp @@ -0,0 +1,144 @@ +#include "normJni.h" +#include "normNodeJni.h" + +JNIEXPORT void JNICALL PKGNAME(NormNode_setUnicastNack) + (JNIEnv *env, jobject obj, jboolean state) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + NormNodeSetUnicastNack(nodeHandle, state); +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_setNackingMode) + (JNIEnv *env, jobject obj, jobject nackingMode) { + NormNodeHandle nodeHandle; + NormNackingMode mode; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + mode = (NormNackingMode)env->CallIntMethod(nackingMode, + mid_NormNackingMode_ordinal); + + NormNodeSetNackingMode(nodeHandle, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_setRepairBoundary) + (JNIEnv *env, jobject obj, jobject repairBoundary) { + NormNodeHandle nodeHandle; + NormRepairBoundary mode; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + mode = (NormRepairBoundary)env->CallIntMethod(repairBoundary, + mid_NormRepairBoundary_ordinal); + + NormNodeSetRepairBoundary(nodeHandle, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_setRxRobustFactor) + (JNIEnv *env, jobject obj, jint robustFactor) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + NormNodeSetRxRobustFactor(nodeHandle, robustFactor); +} + +JNIEXPORT jlong JNICALL PKGNAME(NormNode_getId) + (JNIEnv *env, jobject obj) { + NormNodeHandle handle; + + handle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + return (jlong)NormNodeGetId(handle); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormNode_getAddress) + (JNIEnv *env, jobject obj) { + NormNodeHandle handle; + char buffer[256]; + unsigned int bufferLen; + unsigned short port; + jobject address; + jbyteArray array; + jbyte *ptr; + + handle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + // Get the node address and port + bufferLen = sizeof(buffer); + if (!NormNodeGetAddress(handle, buffer, &bufferLen, &port)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to get node address"); + return NULL; + } + + // Copy the address into a jbyte* + array = env->NewByteArray(bufferLen); + ptr = env->GetByteArrayElements(array, 0); + memcpy(ptr, buffer, bufferLen); + env->ReleaseByteArrayElements(array, ptr, 0); + + // Create a new InetAddress + address = env->CallStaticObjectMethod((jclass)env->NewLocalRef(jw_InetAddress), + mid_InetAddress_getByAddress, array); + + // Create a new InetSocketAddress + return env->NewObject((jclass)env->NewLocalRef(jw_InetSocketAddress), mid_InetSocketAddress_init, + address, (jint)port); +} + +JNIEXPORT jdouble JNICALL PKGNAME(NormNode_getGrtt) + (JNIEnv *env, jobject obj) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + return NormNodeGetGrtt(nodeHandle); +} + +JNIEXPORT jint JNICALL PKGNAME(NormNode_getCommand) + (JNIEnv *env, jobject obj, jbyteArray buffer, jint offset, jint length) { + NormNodeHandle nodeHandle; + jbyte *bytes; + unsigned int n = length; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + bytes = env->GetByteArrayElements(buffer, 0); + + if (!NormNodeGetCommand(nodeHandle, (char*)(bytes + offset), &n)) { + env->ReleaseByteArrayElements(buffer, bytes, 0); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to get command"); + return -1; + } + + env->ReleaseByteArrayElements(buffer, bytes, 0); + + return (jint)n; +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_freeBuffers) + (JNIEnv *env, jobject obj) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + NormNodeFreeBuffers(nodeHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_retain) + (JNIEnv *env, jobject obj) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + NormNodeRetain(nodeHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormNode_release) + (JNIEnv *env, jobject obj) { + NormNodeHandle nodeHandle; + + nodeHandle = (NormNodeHandle)env->GetLongField(obj, fid_NormNode_handle); + + NormNodeRelease(nodeHandle); +} + diff --git a/src/java/jni/normNodeJni.h b/src/java/jni/normNodeJni.h new file mode 100644 index 0000000..58119b7 --- /dev/null +++ b/src/java/jni/normNodeJni.h @@ -0,0 +1,103 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormNode */ + +#ifndef _Included_mil_navy_nrl_norm_NormNode +#define _Included_mil_navy_nrl_norm_NormNode +#ifdef __cplusplus +extern "C" { +#endif +#undef mil_navy_nrl_norm_NormNode_NORM_NODE_ANY +#define mil_navy_nrl_norm_NormNode_NORM_NODE_ANY -1LL +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: setUnicastNack + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_setUnicastNack + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: setNackingMode + * Signature: (Lmil/navy/nrl/norm/enums/NormNackingMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_setNackingMode + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: setRepairBoundary + * Signature: (Lmil/navy/nrl/norm/enums/NormRepairBoundary;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_setRepairBoundary + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: setRxRobustFactor + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_setRxRobustFactor + (JNIEnv *, jobject, jint); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: getId + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_mil_navy_nrl_norm_NormNode_getId + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: getAddress + * Signature: ()Ljava/net/InetSocketAddress; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormNode_getAddress + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: getGrtt + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_mil_navy_nrl_norm_NormNode_getGrtt + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: getCommand + * Signature: ([BII)I + */ +JNIEXPORT jint JNICALL Java_mil_navy_nrl_norm_NormNode_getCommand + (JNIEnv *, jobject, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: freeBuffers + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_freeBuffers + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: retain + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_retain + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormNode + * Method: release + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormNode_release + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normObjectJni.cpp b/src/java/jni/normObjectJni.cpp new file mode 100644 index 0000000..1ef2eed --- /dev/null +++ b/src/java/jni/normObjectJni.cpp @@ -0,0 +1,127 @@ +#include "normJni.h" +#include "normObjectJni.h" + +JNIEXPORT void JNICALL PKGNAME(NormObject_setNackingMode) + (JNIEnv *env, jobject obj, jobject nackingMode) { + NormObjectHandle objectHandle; + NormNackingMode mode; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + mode = (NormNackingMode)env->CallIntMethod(nackingMode, + mid_NormNackingMode_ordinal); + + NormObjectSetNackingMode(objectHandle, mode); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormObject_getType) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + NormObjectType type; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, fid_NormObject_handle); + + type = NormObjectGetType(objectHandle); + + // Get the enum value from the native value + jobjectArray array = (jobjectArray)env->CallStaticObjectMethod( + (jclass)env->NewLocalRef(jw_NormObjectType), mid_NormObjectType_values); + return env->GetObjectArrayElement(array, type); +} + +JNIEXPORT jbyteArray JNICALL PKGNAME(NormObject_getInfo) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + // Check if there is any info attached + if (!NormObjectHasInfo(objectHandle)) { + return NULL; + } + + int length = NormObjectGetInfoLength(objectHandle); + + // Create a new java array to hold the info + jbyteArray info = env->NewByteArray(length); + jbyte *ptr = env->GetByteArrayElements(info, NULL); + + // Get the info from the NormObject + NormObjectGetInfo(objectHandle, (char*)ptr, length); + + // Release the jbyte* to copy them back to the jbyteArray + env->ReleaseByteArrayElements(info, ptr, 0); + + return info; +} + +JNIEXPORT jlong JNICALL PKGNAME(NormObject_getSize) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + return (jlong)NormObjectGetSize(objectHandle); +} + +JNIEXPORT jlong JNICALL PKGNAME(NormObject_getBytesPending) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + return (jlong)NormObjectGetBytesPending(objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormObject_cancel) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormObjectCancel(objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormObject_retain) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormObjectRetain(objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormObject_release) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormObjectRelease(objectHandle); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormObject_getSender) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + NormNodeHandle nodeHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + nodeHandle = NormObjectGetSender(objectHandle); + if (nodeHandle == NORM_NODE_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Locally originated sender object"); + return NULL; + } + + // Create the NormNode + return env->NewObject((jclass)env->NewLocalRef(jw_NormNode), mid_NormNode_init, + (jlong)nodeHandle); +} + diff --git a/src/java/jni/normObjectJni.h b/src/java/jni/normObjectJni.h new file mode 100644 index 0000000..fcf2578 --- /dev/null +++ b/src/java/jni/normObjectJni.h @@ -0,0 +1,85 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormObject */ + +#ifndef _Included_mil_navy_nrl_norm_NormObject +#define _Included_mil_navy_nrl_norm_NormObject +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: setNackingMode + * Signature: (Lmil/navy/nrl/norm/enums/NormNackingMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormObject_setNackingMode + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: getType + * Signature: ()Lmil/navy/nrl/norm/enums/NormObjectType; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormObject_getType + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: getInfo + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_mil_navy_nrl_norm_NormObject_getInfo + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: getSize + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_mil_navy_nrl_norm_NormObject_getSize + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: getBytesPending + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_mil_navy_nrl_norm_NormObject_getBytesPending + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: cancel + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormObject_cancel + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: retain + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormObject_retain + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: release + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormObject_release + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormObject + * Method: getSender + * Signature: ()Lmil/navy/nrl/norm/NormNode; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormObject_getSender + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normSessionJni.cpp b/src/java/jni/normSessionJni.cpp new file mode 100644 index 0000000..fc02bb8 --- /dev/null +++ b/src/java/jni/normSessionJni.cpp @@ -0,0 +1,680 @@ +#include "normJni.h" +#include "normSessionJni.h" + +JNIEXPORT void JNICALL PKGNAME(NormSession_destroySessionNative) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormDestroySession(session); +} + +JNIEXPORT jlong JNICALL PKGNAME(NormSession_getLocalNodeId) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + return NormGetLocalNodeId(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxPort) + (JNIEnv *env, jobject obj, jint port, jboolean enableReuse, jstring txBindAddress) { + NormSessionHandle session; + const char *str = NULL; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (txBindAddress != NULL) { + str = env->GetStringUTFChars(txBindAddress, NULL); + } + + if (!NormSetTxPort(session, port, enableReuse, str)) { + if (txBindAddress != NULL) { + env->ReleaseStringUTFChars(txBindAddress, str); + } + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Invalid tx bind address"); + return; + } + + if (txBindAddress != NULL) { + env->ReleaseStringUTFChars(txBindAddress, str); + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setRxPortReuse) + (JNIEnv *env, jobject obj, jboolean enable, jstring rxBindAddress, jstring senderAddress, jint senderPort) { + NormSessionHandle session; + const char *str1 = NULL; + const char *str2 = NULL; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (rxBindAddress != NULL) { + str1 = env->GetStringUTFChars(rxBindAddress, NULL); + } + if (senderAddress != NULL) { + str2 = env->GetStringUTFChars(senderAddress, NULL); + } + + NormSetRxPortReuse(session, enable, str1, str2, senderPort); + + if (rxBindAddress != NULL) { + env->ReleaseStringUTFChars(rxBindAddress, str1); + } + if (senderAddress != NULL) { + env->ReleaseStringUTFChars(senderAddress, str2); + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setEcnSupport) + (JNIEnv *env, jobject obj, jboolean ecnEnable, jboolean ignoreLoss) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetEcnSupport(session, ecnEnable, ignoreLoss); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setMulticastInterface) + (JNIEnv *env, jobject obj, jstring interfaceName) { + NormSessionHandle session; + const char *str; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + str = env->GetStringUTFChars(interfaceName, NULL); + + if (!NormSetMulticastInterface(session, str)) { + env->ReleaseStringUTFChars(interfaceName, str); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set multicast interface"); + return; + } + + env->ReleaseStringUTFChars(interfaceName, str); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTTL) + (JNIEnv *env, jobject obj, jbyte ttl) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormSetTTL(session, ttl)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set TTL"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTOS) + (JNIEnv *env, jobject obj, jbyte tos) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormSetTOS(session, tos)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set TOS"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setLoopback) + (JNIEnv *env, jobject obj, jboolean loopbackEnable) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormSetLoopback(session, loopbackEnable)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set loopback"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setMessageTrace) + (JNIEnv *env, jobject obj, jboolean flag) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetMessageTrace(session, flag); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxLoss) + (JNIEnv *env, jobject obj, jdouble percent) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxLoss(session, percent); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setRxLoss) + (JNIEnv *env, jobject obj, jdouble percent) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetRxLoss(session, percent); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setReportInterval) + (JNIEnv *env, jobject obj, jdouble interval) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetReportInterval(session, interval); +} + +JNIEXPORT jdouble JNICALL PKGNAME(NormSession_getReportInterval) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + return NormGetReportInterval(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_startSender) + (JNIEnv *env, jobject obj, jint sessionId, jlong bufferSpace, + jint segmentSize, jshort blockSize, jshort numParity) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormStartSender(session, sessionId, bufferSpace, segmentSize, + blockSize, numParity)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to start sender"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_stopSender) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormStopSender(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxOnly) + (JNIEnv *env, jobject obj, jboolean txOnly) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxOnly(session, txOnly); +} + +JNIEXPORT jdouble JNICALL PKGNAME(NormSession_getTxRate) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + return NormGetTxRate(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxRate) + (JNIEnv *env, jobject obj, jdouble rate) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxRate(session, rate); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setFlowControl) + (JNIEnv *env, jobject obj, jdouble flowControlFactor) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetFlowControl(session, flowControlFactor); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxSocketBuffer) + (JNIEnv *env, jobject obj, jlong bufferSize) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormSetTxSocketBuffer(session, bufferSize)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set tx socket buffer"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setCongestionControl) + (JNIEnv *env, jobject obj, jboolean enable, jboolean adjustRate) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetCongestionControl(session, enable, adjustRate); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxRateBounds) + (JNIEnv *env, jobject obj, jdouble rateMin, jdouble rateMax) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxRateBounds(session, rateMin, rateMax); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxCacheBounds) + (JNIEnv *env, jobject obj, jlong sizeMax, jlong countMin, jlong countMax) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxCacheBounds(session, (NormSize)sizeMax, countMin, countMax); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setAutoParity) + (JNIEnv *env, jobject obj, jshort autoParity) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetAutoParity(session, autoParity); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setGrttEstimate) + (JNIEnv *env, jobject obj, jdouble grtt) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetGrttEstimate(session, grtt); +} + +JNIEXPORT jdouble JNICALL PKGNAME(NormSession_getGrttEstimate) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + return NormGetGrttEstimate(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setGrttMax) + (JNIEnv *env, jobject obj, jdouble grttMax) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetGrttMax(session, grttMax); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setGrttProbingMode) + (JNIEnv *env, jobject obj, jobject probingMode) { + NormSessionHandle session; + NormProbingMode mode; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + mode = (NormProbingMode)env->CallIntMethod(probingMode, + mid_NormProbingMode_ordinal); + + NormSetGrttProbingMode(session, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setGrttProbingInterval) + (JNIEnv *env, jobject obj, jdouble intervalMin, jdouble intervalMax) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetGrttProbingInterval(session, intervalMin, intervalMax); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setBackoffFactor) + (JNIEnv *env, jobject obj, jdouble backoffFactor) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetBackoffFactor(session, backoffFactor); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setGroupSize) + (JNIEnv *env, jobject obj, jlong groupSize) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetGroupSize(session, groupSize); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setTxRobustFactor) + (JNIEnv *env, jobject obj, jint robustFactor) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetTxRobustFactor(session, robustFactor); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormSession_fileEnqueue) + (JNIEnv *env, jobject obj, jstring filename, jbyteArray info, + jint infoOffset, jint infoLength) { + NormSessionHandle session; + NormObjectHandle objectHandle; + const char *str; + jbyte *infoBytes = NULL; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + str = env->GetStringUTFChars(filename, NULL); + if (info != NULL) { + infoBytes = env->GetByteArrayElements(info, 0); + } + + objectHandle = NormFileEnqueue(session, str, + (const char*)(infoBytes + infoOffset), infoLength); + + env->ReleaseStringUTFChars(filename, str); + if (info != NULL) { + env->ReleaseByteArrayElements(info, infoBytes, JNI_ABORT); + } + + if (objectHandle == NORM_OBJECT_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to enqueue file"); + return NULL; + } + + // Create the NormObject + return env->NewObject((jclass)env->NewLocalRef(jw_NormFile), mid_NormFile_init, + (jlong)objectHandle); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormSession_dataEnqueue) +(JNIEnv *env, jobject obj, jobject dataBuffer, jint dataOffset, + jint dataLength, jbyteArray info, jint infoOffset, jint infoLength) { + NormSessionHandle session; + NormObjectHandle objectHandle; + char *dataPtr; + jbyte *infoBytes; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + // Get the data ByteBuffer's address + dataPtr = (char*)env->GetDirectBufferAddress(dataBuffer); + if (dataPtr == NULL) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Cannot access direct ByteBuffer address"); + return NULL; + } + dataPtr = dataPtr + dataOffset; + + // Get the info byte array + if (info != NULL) { + infoBytes = env->GetByteArrayElements(info, 0); + } else { + infoBytes = NULL; + infoOffset = 0; + infoLength = 0; + } + + objectHandle = NormDataEnqueue(session, dataPtr, dataLength, + (char*)infoBytes + infoOffset, infoLength); + + // Release the info byte array + if (info != NULL) { + env->ReleaseByteArrayElements(info, infoBytes, JNI_ABORT); + } + + if (objectHandle == NORM_OBJECT_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to enqueue data"); + return NULL; + } + + // Create the NormObject + return env->NewObject((jclass)env->NewLocalRef(jw_NormData), mid_NormData_init, + (jlong)objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_requeueObject) + (JNIEnv *env, jobject obj, jobject object) { + NormSessionHandle session; + NormObjectHandle objectHandle; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + objectHandle = (NormObjectHandle)env->GetLongField(object, fid_NormObject_handle); + + if (!NormRequeueObject(session, objectHandle)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to requeue object"); + return; + } +} + +JNIEXPORT jobject JNICALL PKGNAME(NormSession_streamOpen) + (JNIEnv *env, jobject obj, jlong bufferSize, + jbyteArray info, jint infoOffset, jint infoLength) { + NormSessionHandle session; + NormObjectHandle objectHandle; + jbyte *infoBytes; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + // Get the info byte array + if (info != NULL) { + infoBytes = env->GetByteArrayElements(info, 0); + } else { + infoBytes = NULL; + infoOffset = 0; + infoLength = 0; + } + + objectHandle = NormStreamOpen(session, bufferSize, + (char*)infoBytes + infoOffset, infoLength); + + // Release the info byte array + if (info != NULL) { + env->ReleaseByteArrayElements(info, infoBytes, JNI_ABORT); + } + + if (objectHandle == NORM_OBJECT_INVALID) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to open stream"); + return NULL; + } + + // Create the NormObject + return env->NewObject((jclass)env->NewLocalRef(jw_NormStream), mid_NormStream_init, + (jlong)objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setWatermark) + (JNIEnv *env, jobject obj, jobject object, jboolean overrideFlush) { + NormSessionHandle session; + NormObjectHandle objectHandle; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + objectHandle = (NormObjectHandle)env->GetLongField(object, + fid_NormObject_handle); + + if (!NormSetWatermark(session, objectHandle, overrideFlush)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set watermark"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_cancelWatermark) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormCancelWatermark(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_addAckingNode) + (JNIEnv *env, jobject obj, jlong nodeId) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormAddAckingNode(session, nodeId)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to add acking node"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_removeAckingNode) + (JNIEnv *env, jobject obj, jlong nodeId) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormRemoveAckingNode(session, nodeId); +} + +JNIEXPORT jobject JNICALL PKGNAME(NormSession_getAckingStatus) + (JNIEnv *env, jobject obj, jlong nodeId) { + NormSessionHandle session; + NormAckingStatus ackingStatus; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + ackingStatus = NormGetAckingStatus(session, nodeId); + + // get the enum value from the native value + jclass LocalNormAckingStatus = (jclass)env->NewLocalRef(jw_NormAckingStatus); + jobjectArray array = (jobjectArray)env->CallStaticObjectMethod( + LocalNormAckingStatus, mid_NormAckingStatus_values); + return env->GetObjectArrayElement(array, ackingStatus); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_sendCommand) + (JNIEnv *env, jobject obj, jbyteArray cmdBuffer, jint cmdOffset, + jint cmdLength, jboolean robust) { + NormSessionHandle session; + jbyte *cmdBytes; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + cmdBytes = env->GetByteArrayElements(cmdBuffer, 0); + + if (!NormSendCommand(session, (const char*)(cmdBytes + cmdOffset), cmdLength, robust)) { + env->ReleaseByteArrayElements(cmdBuffer, cmdBytes, JNI_ABORT); + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to send command"); + return; + } + + env->ReleaseByteArrayElements(cmdBuffer, cmdBytes, JNI_ABORT); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_cancelCommand) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormCancelCommand(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_startReceiver) + (JNIEnv *env, jobject obj, jlong bufferSpace) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormStartReceiver(session, bufferSpace)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to start receiver"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_stopReceiver) + (JNIEnv *env, jobject obj) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormStopReceiver(session); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setRxCacheLimit) + (JNIEnv *env, jobject obj, jint countMax) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetRxCacheLimit(session, countMax); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setRxSocketBuffer) + (JNIEnv *env, jobject obj, jlong bufferSize) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + if (!NormSetRxSocketBuffer(session, bufferSize)) { + env->ThrowNew((jclass)env->NewLocalRef(jw_IOException), "Failed to set rx socket buffer"); + return; + } +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setSilentReceiver) + (JNIEnv *env, jobject obj, jboolean silent, jint maxDelay) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetSilentReceiver(session, silent, maxDelay); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setDefaultUnicastNack) + (JNIEnv *env, jobject obj, jboolean enable) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetDefaultUnicastNack(session, enable); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setDefaultSyncPolicy) + (JNIEnv *env, jobject obj, jobject syncPolicy) { + NormSessionHandle session; + NormSyncPolicy policy; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + policy = (NormSyncPolicy)env->CallIntMethod(syncPolicy, + mid_NormSyncPolicy_ordinal); + + NormSetDefaultSyncPolicy(session, policy); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setDefaultNackingMode) + (JNIEnv *env, jobject obj, jobject nackingMode) { + NormSessionHandle session; + NormNackingMode mode; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + mode = (NormNackingMode)env->CallIntMethod(nackingMode, + mid_NormNackingMode_ordinal); + + NormSetDefaultNackingMode(session, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setDefaultRepairBoundary) + (JNIEnv *env, jobject obj, jobject repairBoundary) { + NormSessionHandle session; + NormRepairBoundary mode; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + mode = (NormRepairBoundary)env->CallIntMethod(repairBoundary, + mid_NormRepairBoundary_ordinal); + + NormSetDefaultRepairBoundary(session, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormSession_setDefaultRxRobustFactor) + (JNIEnv *env, jobject obj, jint robustFactor) { + NormSessionHandle session; + + session = (NormSessionHandle)env->GetLongField(obj, fid_NormSession_handle); + + NormSetDefaultRxRobustFactor(session, robustFactor); +} + diff --git a/src/java/jni/normSessionJni.h b/src/java/jni/normSessionJni.h new file mode 100644 index 0000000..0fe1610 --- /dev/null +++ b/src/java/jni/normSessionJni.h @@ -0,0 +1,445 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormSession */ + +#ifndef _Included_mil_navy_nrl_norm_NormSession +#define _Included_mil_navy_nrl_norm_NormSession +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: destroySessionNative + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_destroySessionNative + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: getLocalNodeId + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_mil_navy_nrl_norm_NormSession_getLocalNodeId + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxPort + * Signature: (IZLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxPort + (JNIEnv *, jobject, jint, jboolean, jstring); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setRxPortReuse + * Signature: (ZLjava/lang/String;Ljava/lang/String;I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setRxPortReuse + (JNIEnv *, jobject, jboolean, jstring, jstring, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setEcnSupport + * Signature: (ZZ)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setEcnSupport + (JNIEnv *, jobject, jboolean, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setMulticastInterface + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setMulticastInterface + (JNIEnv *, jobject, jstring); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTTL + * Signature: (B)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTTL + (JNIEnv *, jobject, jbyte); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTOS + * Signature: (B)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTOS + (JNIEnv *, jobject, jbyte); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setLoopback + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setLoopback + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setMessageTrace + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setMessageTrace + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxLoss + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxLoss + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setRxLoss + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setRxLoss + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setReportInterval + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setReportInterval + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: getReportInterval + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_mil_navy_nrl_norm_NormSession_getReportInterval + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: startSender + * Signature: (IJISS)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_startSender + (JNIEnv *, jobject, jint, jlong, jint, jshort, jshort); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: stopSender + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_stopSender + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxOnly + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxOnly + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: getTxRate + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_mil_navy_nrl_norm_NormSession_getTxRate + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxRate + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxRate + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setFlowControl + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setFlowControl + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxSocketBuffer + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxSocketBuffer + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setCongestionControl + * Signature: (ZZ)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setCongestionControl + (JNIEnv *, jobject, jboolean, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxRateBounds + * Signature: (DD)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxRateBounds + (JNIEnv *, jobject, jdouble, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxCacheBounds + * Signature: (JJJ)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxCacheBounds + (JNIEnv *, jobject, jlong, jlong, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setAutoParity + * Signature: (S)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setAutoParity + (JNIEnv *, jobject, jshort); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setGrttEstimate + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setGrttEstimate + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: getGrttEstimate + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_mil_navy_nrl_norm_NormSession_getGrttEstimate + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setGrttMax + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setGrttMax + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setGrttProbingMode + * Signature: (Lmil/navy/nrl/norm/enums/NormProbingMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setGrttProbingMode + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setGrttProbingInterval + * Signature: (DD)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setGrttProbingInterval + (JNIEnv *, jobject, jdouble, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setBackoffFactor + * Signature: (D)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setBackoffFactor + (JNIEnv *, jobject, jdouble); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setGroupSize + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setGroupSize + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setTxRobustFactor + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setTxRobustFactor + (JNIEnv *, jobject, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: fileEnqueue + * Signature: (Ljava/lang/String;[BII)Lmil/navy/nrl/norm/NormFile; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormSession_fileEnqueue + (JNIEnv *, jobject, jstring, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: dataEnqueue + * Signature: (Ljava/nio/ByteBuffer;II[BII)Lmil/navy/nrl/norm/NormData; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormSession_dataEnqueue + (JNIEnv *, jobject, jobject, jint, jint, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: requeueObject + * Signature: (Lmil/navy/nrl/norm/NormObject;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_requeueObject + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: streamOpen + * Signature: (J[BII)Lmil/navy/nrl/norm/NormStream; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormSession_streamOpen + (JNIEnv *, jobject, jlong, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setWatermark + * Signature: (Lmil/navy/nrl/norm/NormObject;Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setWatermark + (JNIEnv *, jobject, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: cancelWatermark + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_cancelWatermark + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: addAckingNode + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_addAckingNode + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: removeAckingNode + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_removeAckingNode + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: getAckingStatus + * Signature: (J)Lmil/navy/nrl/norm/enums/NormAckingStatus; + */ +JNIEXPORT jobject JNICALL Java_mil_navy_nrl_norm_NormSession_getAckingStatus + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: sendCommand + * Signature: ([BIIZ)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_sendCommand + (JNIEnv *, jobject, jbyteArray, jint, jint, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: cancelCommand + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_cancelCommand + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: startReceiver + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_startReceiver + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: stopReceiver + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_stopReceiver + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setRxCacheLimit + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setRxCacheLimit + (JNIEnv *, jobject, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setRxSocketBuffer + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setRxSocketBuffer + (JNIEnv *, jobject, jlong); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setSilentReceiver + * Signature: (ZI)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setSilentReceiver + (JNIEnv *, jobject, jboolean, jint); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setDefaultUnicastNack + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setDefaultUnicastNack + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setDefaultSyncPolicy + * Signature: (Lmil/navy/nrl/norm/enums/NormSyncPolicy;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setDefaultSyncPolicy + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setDefaultNackingMode + * Signature: (Lmil/navy/nrl/norm/enums/NormNackingMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setDefaultNackingMode + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setDefaultRepairBoundary + * Signature: (Lmil/navy/nrl/norm/enums/NormRepairBoundary;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setDefaultRepairBoundary + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormSession + * Method: setDefaultRxRobustFactor + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormSession_setDefaultRxRobustFactor + (JNIEnv *, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/jni/normStreamJni.cpp b/src/java/jni/normStreamJni.cpp new file mode 100644 index 0000000..977f0ff --- /dev/null +++ b/src/java/jni/normStreamJni.cpp @@ -0,0 +1,126 @@ +#include "normJni.h" +#include "normStreamJni.h" + +JNIEXPORT void JNICALL PKGNAME(NormStream_close) + (JNIEnv *env, jobject obj, jboolean graceful) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormStreamClose(objectHandle, graceful); +} + +JNIEXPORT jint JNICALL PKGNAME(NormStream_write) + (JNIEnv *env, jobject obj, jbyteArray buffer, + jint offset, jint length) { + NormObjectHandle objectHandle; + jbyte *bytes; + unsigned int n; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + bytes = env->GetByteArrayElements(buffer, 0); + + n = NormStreamWrite(objectHandle, (const char*)(bytes + offset), length); + + env->ReleaseByteArrayElements(buffer, bytes, JNI_ABORT); + + return (jint)n; +} + +JNIEXPORT void JNICALL PKGNAME(NormStream_flush) + (JNIEnv *env, jobject obj, jboolean eom, jobject flushMode) { + NormObjectHandle objectHandle; + NormFlushMode mode; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + mode = (NormFlushMode)env->CallIntMethod(flushMode, + mid_NormFlushMode_ordinal); + + NormStreamFlush(objectHandle, eom, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormStream_setAutoFlush) + (JNIEnv *env, jobject obj, jobject flushMode) { + NormObjectHandle objectHandle; + NormFlushMode mode; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + mode = (NormFlushMode)env->CallIntMethod(flushMode, + mid_NormFlushMode_ordinal); + + NormStreamSetAutoFlush(objectHandle, mode); +} + +JNIEXPORT void JNICALL PKGNAME(NormStream_setPushEnable) + (JNIEnv *env, jobject obj, jboolean pushEnable) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormStreamSetPushEnable(objectHandle, pushEnable); +} + +JNIEXPORT jboolean JNICALL PKGNAME(NormStream_hasVacancy) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + return NormStreamHasVacancy(objectHandle); +} + +JNIEXPORT void JNICALL PKGNAME(NormStream_markEom) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + NormStreamMarkEom(objectHandle); +} + +JNIEXPORT jint JNICALL PKGNAME(NormStream_read) + (JNIEnv *env, jobject obj, jbyteArray buffer, jint offset, jint length) { + NormObjectHandle objectHandle; + jbyte *bytes; + unsigned int n = length; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + bytes = env->GetByteArrayElements(buffer, 0); + + if (!NormStreamRead(objectHandle, (char*)(bytes + offset), &n)) { + return -1; + } + + env->ReleaseByteArrayElements(buffer, bytes, 0); + + return (jint)n; +} + +JNIEXPORT jboolean JNICALL PKGNAME(NormStream_seekMsgStart) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + return NormStreamSeekMsgStart(objectHandle); +} + +JNIEXPORT jlong JNICALL PKGNAME(NormStream_getReadOffset) + (JNIEnv *env, jobject obj) { + NormObjectHandle objectHandle; + + objectHandle = (NormObjectHandle)env->GetLongField(obj, + fid_NormObject_handle); + + return NormStreamGetReadOffset(objectHandle); +} + diff --git a/src/java/jni/normStreamJni.h b/src/java/jni/normStreamJni.h new file mode 100644 index 0000000..48696f4 --- /dev/null +++ b/src/java/jni/normStreamJni.h @@ -0,0 +1,93 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class mil_navy_nrl_norm_NormStream */ + +#ifndef _Included_mil_navy_nrl_norm_NormStream +#define _Included_mil_navy_nrl_norm_NormStream +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: close + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormStream_close + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: write + * Signature: ([BII)I + */ +JNIEXPORT jint JNICALL Java_mil_navy_nrl_norm_NormStream_write + (JNIEnv *, jobject, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: flush + * Signature: (ZLmil/navy/nrl/norm/enums/NormFlushMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormStream_flush + (JNIEnv *, jobject, jboolean, jobject); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: setAutoFlush + * Signature: (Lmil/navy/nrl/norm/enums/NormFlushMode;)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormStream_setAutoFlush + (JNIEnv *, jobject, jobject); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: setPushEnable + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormStream_setPushEnable + (JNIEnv *, jobject, jboolean); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: hasVacancy + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_mil_navy_nrl_norm_NormStream_hasVacancy + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: markEom + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_mil_navy_nrl_norm_NormStream_markEom + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: read + * Signature: ([BII)I + */ +JNIEXPORT jint JNICALL Java_mil_navy_nrl_norm_NormStream_read + (JNIEnv *, jobject, jbyteArray, jint, jint); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: seekMsgStart + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_mil_navy_nrl_norm_NormStream_seekMsgStart + (JNIEnv *, jobject); + +/* + * Class: mil_navy_nrl_norm_NormStream + * Method: getReadOffset + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_mil_navy_nrl_norm_NormStream_getReadOffset + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/java/src/mil/navy/nrl/norm/NormData.java b/src/java/src/mil/navy/nrl/norm/NormData.java new file mode 100644 index 0000000..5be652a --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormData.java @@ -0,0 +1,15 @@ +package mil.navy.nrl.norm; + +/** + * This class contains information about a NORM Data Object. + * + * @author Jason Rush + */ +public class NormData extends NormObject { + + NormData(long handle) { + super(handle); + } + + public native byte[] getData(); +} diff --git a/src/java/src/mil/navy/nrl/norm/NormEvent.java b/src/java/src/mil/navy/nrl/norm/NormEvent.java new file mode 100644 index 0000000..f0a069c --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormEvent.java @@ -0,0 +1,62 @@ +package mil.navy.nrl.norm; + +import mil.navy.nrl.norm.enums.NormEventType; + +/** + * The NormEvent encapsulates the type of event that occured along with the + * session, node, and possibly object information. + * + * @author Jason Rush + */ +public class NormEvent { + private NormEventType type; + private long sessionHandle; + private long nodeHandle; + @SuppressWarnings("unused") + private long objectHandle; + + public NormEvent(NormEventType type, long sessionHandle, long nodeHandle, + long objectHandle) { + this.type = type; + this.sessionHandle = sessionHandle; + this.nodeHandle = nodeHandle; + this.objectHandle = objectHandle; + } + + /** + * @return Returns the type. + */ + public NormEventType getType() { + return type; + } + + /** + * @return Returns the node. + */ + public NormSession getSession() { + if (sessionHandle == 0) { + return null; + } + + return NormSession.getSession(sessionHandle); + } + + /** + * @return Returns the node. + */ + public NormNode getNode() { + if (nodeHandle == 0) { + return null; + } + return new NormNode(nodeHandle); + } + + public native NormObject getObject(); + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return String.format("NormEvent [type=%s]", type); + } +} diff --git a/src/java/src/mil/navy/nrl/norm/NormFile.java b/src/java/src/mil/navy/nrl/norm/NormFile.java new file mode 100644 index 0000000..e03e586 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormFile.java @@ -0,0 +1,19 @@ +package mil.navy.nrl.norm; + +import java.io.IOException; + +/** + * This class contains information about a NORM File Object. + * + * @author Jason Rush + */ +public class NormFile extends NormObject { + + NormFile(long handle) { + super(handle); + } + + public native String getName() throws IOException; + + public native void rename(String filename) throws IOException; +} diff --git a/src/java/src/mil/navy/nrl/norm/NormInstance.java b/src/java/src/mil/navy/nrl/norm/NormInstance.java new file mode 100644 index 0000000..b35fd9a --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormInstance.java @@ -0,0 +1,63 @@ +package mil.navy.nrl.norm; + +import java.io.IOException; + +/** + * This class maps to a native instance of a NORM protocol engine. + * + * @author Jason Rush + */ +public class NormInstance { + /* + * This version string is used to validate the compatibility between the Java + * and C native libraries. Update this string along with it's counterpart in + * the normJni.h file whenever the native API changes. + */ + private static final String VERSION = "20130415-0927"; + + static { + System.loadLibrary("mil_navy_nrl_norm"); + } + + @SuppressWarnings("unused") + private long handle; // The pointer to the native NormInstanceHandle + + public NormInstance() throws IOException { + createInstance(false); + } + + public NormInstance(boolean priorityBoost) throws IOException { + createInstance(priorityBoost); + } + + private native void createInstance(boolean priorityBoost) throws IOException; + + public native void destroyInstance(); + + public native void stopInstance(); + + public native boolean restartInstance(); + + public native boolean suspendInstance(); + + public native void resumeInstance(); + + public native void setCacheDirectory(String cachePath) throws IOException; + + public native void openDebugLog(String filename) throws IOException; + + public native void closeDebugLog(); + + public native void openDebugPipe(String pipename) throws IOException; + + public native void setDebugLevel(int level); + + public native int getDebugLevel(); + + public native boolean hasNextEvent(int sec, int usec) throws IOException; + + public native NormEvent getNextEvent() throws IOException; + + public native NormSession createSession(String address, int port, + long localNodeId) throws IOException; +} diff --git a/src/java/src/mil/navy/nrl/norm/NormNode.java b/src/java/src/mil/navy/nrl/norm/NormNode.java new file mode 100644 index 0000000..d193984 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormNode.java @@ -0,0 +1,46 @@ +package mil.navy.nrl.norm; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import mil.navy.nrl.norm.enums.NormNackingMode; +import mil.navy.nrl.norm.enums.NormRepairBoundary; + +/** + * This class contains the information about a NORM node. + * + * @author Jason Rush + */ +public class NormNode { + public static final long NORM_NODE_ANY = 0xffffffff; + + @SuppressWarnings("unused") + private long handle; + + NormNode(long handle) { + this.handle = handle; + } + + public native void setUnicastNack(boolean state); + + public native void setNackingMode(NormNackingMode nackingMode); + + public native void setRepairBoundary(NormRepairBoundary repairBoundary); + + public native void setRxRobustFactor(int robustFactor); + + public native long getId(); + + public native InetSocketAddress getAddress() throws IOException; + + public native double getGrtt(); + + public native int getCommand(byte buffer[], int offset, int length) + throws IOException; + + public native void freeBuffers(); + + public native void retain(); + + public native void release(); +} diff --git a/src/java/src/mil/navy/nrl/norm/NormObject.java b/src/java/src/mil/navy/nrl/norm/NormObject.java new file mode 100644 index 0000000..0bd9da8 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormObject.java @@ -0,0 +1,57 @@ +package mil.navy.nrl.norm; + +import java.io.IOException; + +import mil.navy.nrl.norm.enums.NormNackingMode; +import mil.navy.nrl.norm.enums.NormObjectType; + +/** + * Interface to tag all NORM objects (data, file, etc). + * + * @author Jason Rush + */ +public class NormObject { + private long handle; // The pointer to the native NormInstanceHandle + + NormObject(long handle) { + this.handle = handle; + } + + public native void setNackingMode(NormNackingMode nackingMode); + + public native NormObjectType getType(); + + // TODO add hasInfo(), getInfoLength() methods? + + public native byte[] getInfo(); + + public native long getSize(); + + public native long getBytesPending(); + + public native void cancel(); + + public native void retain(); + + public native void release(); + + public native NormNode getSender() throws IOException; + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return (int)handle; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj instanceof NormObject) { + 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 new file mode 100644 index 0000000..0ac5f87 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormSession.java @@ -0,0 +1,192 @@ +package mil.navy.nrl.norm; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import mil.navy.nrl.norm.enums.NormAckingStatus; +import mil.navy.nrl.norm.enums.NormNackingMode; +import mil.navy.nrl.norm.enums.NormProbingMode; +import mil.navy.nrl.norm.enums.NormRepairBoundary; +import mil.navy.nrl.norm.enums.NormSyncPolicy; + +/** + * This class maps to a native instance of a NORM protocol session. + * + * @author Jason Rush + */ +public class NormSession { + private static Map normSessions = new HashMap(); + + private long handle; // The pointer to the native NormInstanceHandle + + /** + * Package protected constructor is invoked by NormInstance + */ + NormSession(long handle) { + this.handle = handle; + + normSessions.put(handle, this); + } + + static synchronized NormSession getSession(long handle) { + return normSessions.get(handle); + } + + /* NORM Session Creation and Control Functions */ + + public void destroySession() { + normSessions.remove(handle); + + destroySessionNative(); + } + + private native void destroySessionNative(); + + public native long getLocalNodeId(); + + 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 native void setMulticastInterface(String interfaceName) + throws IOException; + + public native void setTTL(byte ttl) throws IOException; + + public native void setTOS(byte tos) throws IOException; + + public native void setLoopback(boolean loopbackEnable) throws IOException; + + /* Special functions for debug support */ + public native void setMessageTrace(boolean flag); + + public native void setTxLoss(double percent); + + public native void setRxLoss(double percent); + + public native void setReportInterval(double interval); + + public native double getReportInterval(); + + /* NORM Sender Functions */ + + public native void startSender(int sessionId, long bufferSpace, + int segmentSize, short blockSize, short numParity) throws IOException; + + public native void stopSender(); + + public native void setTxOnly(boolean txOnly); + + public native double getTxRate(); + + public native void setTxRate(double rate); + + public native void setFlowControl(double flowControlFactor); + + public native void setTxSocketBuffer(long bufferSize) throws IOException; + + public native void setCongestionControl(boolean enable, boolean adjustRate); + + public native void setTxRateBounds(double rateMin, double rateMax); + + public native void setTxCacheBounds(long sizeMax, long countMin, long countMax); + + public native void setAutoParity(short autoParity); + + public native void setGrttEstimate(double grtt); + + public native double getGrttEstimate(); + + public native void setGrttMax(double grttMax); + + public native void setGrttProbingMode(NormProbingMode probingMode); + + public native void setGrttProbingInterval(double intervalMin, + double intervalMax); + + public native void setBackoffFactor(double backoffFactor); + + public native void setGroupSize(long groupSize); + + public native void setTxRobustFactor(int robustFactor); + + public NormFile fileEnqueue(String filename) throws IOException { + byte[] info = filename.getBytes("US-ASCII"); + return fileEnqueue(filename, info, 0, info.length); + } + + public native NormFile fileEnqueue(String filename, byte info[], + int infoOffset, int infoLength) throws IOException; + + public NormData dataEnqueue(ByteBuffer dataBuffer, int dataOffset, + int dataLength) throws IOException { + return dataEnqueue(dataBuffer, dataOffset, dataLength, null, 0, 0); + } + + public native NormData dataEnqueue(ByteBuffer dataBuffer, int dataOffset, + int dataLength, byte info[], int infoOffset, int infoLength) + throws IOException; + + public native void requeueObject(NormObject object) throws IOException; + + public NormStream streamOpen(long bufferSize) throws IOException { + return streamOpen(bufferSize, null, 0, 0); + } + + public native NormStream streamOpen(long bufferSize, byte info[], + int infoOffset, int infoLength) throws IOException; + + public void setWatermark(NormObject object) throws IOException { + setWatermark(object, false); + } + + public native void setWatermark(NormObject object, boolean overrideFlush) + throws IOException; + + public native void cancelWatermark(); + + public native void addAckingNode(long nodeId) throws IOException; + + public native void removeAckingNode(long nodeId); + + public native NormAckingStatus getAckingStatus(long nodeId); + + public native void sendCommand(byte cmdBuffer[], int cmdOffset, + int cmdLength, boolean robust) throws IOException; + + public native void cancelCommand(); + + /* NORM Receiver Functions */ + + public native void startReceiver(long bufferSpace) throws IOException; + + public native void stopReceiver(); + + public native void setRxCacheLimit(int countMax); + + public native void setRxSocketBuffer(long bufferSize) throws IOException; + + public native void setSilentReceiver(boolean silent, int maxDelay); + + public native void setDefaultUnicastNack(boolean enabled); + + public native void setDefaultSyncPolicy(NormSyncPolicy syncPolicy); + + public native void setDefaultNackingMode(NormNackingMode nackingMode); + + public native void setDefaultRepairBoundary(NormRepairBoundary repairBoundary); + + public native void setDefaultRxRobustFactor(int robustFactor); +} diff --git a/src/java/src/mil/navy/nrl/norm/NormStream.java b/src/java/src/mil/navy/nrl/norm/NormStream.java new file mode 100644 index 0000000..241d881 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/NormStream.java @@ -0,0 +1,43 @@ +package mil.navy.nrl.norm; + +import mil.navy.nrl.norm.enums.NormFlushMode; + +/** + * This class contains information about a NORM Stream Object. + * + * @author Jason Rush + */ +public class NormStream extends NormObject { + + NormStream(long handle) { + super(handle); + } + + public void close() { + close(false); + } + + public native void close(boolean graceful); + + public native int write(byte buffer[], int offset, int length); + + public void flush() { + flush(false, NormFlushMode.NORM_FLUSH_PASSIVE); + } + + public native void flush(boolean eom, NormFlushMode flushMode); + + public native void setAutoFlush(NormFlushMode flushMode); + + public native void setPushEnable(boolean pushEnable); + + public native boolean hasVacancy(); + + public native void markEom(); + + public native int read(byte buffer[], int offset, int length); + + public native boolean seekMsgStart(); + + public native long getReadOffset(); +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormAckingStatus.java b/src/java/src/mil/navy/nrl/norm/enums/NormAckingStatus.java new file mode 100644 index 0000000..184edb9 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormAckingStatus.java @@ -0,0 +1,15 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM acking statuses. This enumeration + * maps to the native implementation and therefore is tied to the specific + * version of NORM. + * + * @author Jason Rush + */ +public enum NormAckingStatus { + NORM_ACK_INVALID, + NORM_ACK_FAILURE, + NORM_ACK_PENDING, + NORM_ACK_SUCCESS; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormEventType.java b/src/java/src/mil/navy/nrl/norm/enums/NormEventType.java new file mode 100644 index 0000000..5a023fa --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormEventType.java @@ -0,0 +1,34 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM events. This enumeration maps to + * the native implementation and therefore is tied to the specific version of + * NORM. + * + * @author Jason Rush + */ +public enum NormEventType { + NORM_EVENT_INVALID, + NORM_TX_QUEUE_VACANCY, + NORM_TX_QUEUE_EMPTY, + NORM_TX_FLUSH_COMPLETED, + NORM_TX_WATERMARK_COMPLETED, + NORM_TX_CMD_SENT, + NORM_TX_OBJECT_SENT, + NORM_TX_OBJECT_PURGED, + NORM_TX_RATE_CHANGED, + NORM_LOCAL_SENDER_CLOSED, + NORM_REMOTE_SENDER_NEW, + NORM_REMOTE_SENDER_ACTIVE, + NORM_REMOTE_SENDER_INACTIVE, + NORM_REMOTE_SENDER_PURGED, + NORM_RX_CMD_NEW, + NORM_RX_OBJECT_NEW, + NORM_RX_OBJECT_INFO, + NORM_RX_OBJECT_UPDATED, + NORM_RX_OBJECT_COMPLETED, + NORM_RX_OBJECT_ABORTED, + NORM_GRTT_UPDATED, + NORM_CC_ACTIVE, + NORM_CC_INACTIVE; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormFlushMode.java b/src/java/src/mil/navy/nrl/norm/enums/NormFlushMode.java new file mode 100644 index 0000000..653fbe4 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormFlushMode.java @@ -0,0 +1,14 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM flush modes. This enumeration maps + * to the native implementation and therefore is tied to the specific version of + * NORM. + * + * @author Jason Rush + */ +public enum NormFlushMode { + NORM_FLUSH_NONE, + NORM_FLUSH_PASSIVE, + NORM_FLUSH_ACTIVE; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormNackingMode.java b/src/java/src/mil/navy/nrl/norm/enums/NormNackingMode.java new file mode 100644 index 0000000..d04bf4a --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormNackingMode.java @@ -0,0 +1,14 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM nacking modes. This enumeration + * maps to the native implementation and therefore is tied to the specific + * version of NORM. + * + * @author Jason Rush + */ +public enum NormNackingMode { + NORM_NACK_NONE, + NORM_NACK_INFO_ONLY, + NORM_NACK_NORMAL; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormObjectType.java b/src/java/src/mil/navy/nrl/norm/enums/NormObjectType.java new file mode 100644 index 0000000..37be34c --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormObjectType.java @@ -0,0 +1,15 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM objects. This enumeration maps to + * the native implementation and therefore is tied to the specific version of + * NORM. + * + * @author Jason Rush + */ +public enum NormObjectType { + NORM_OBJECT_NONE, + NORM_OBJECT_DATA, + NORM_OBJECT_FILE, + NORM_OBJECT_STREAM; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormProbingMode.java b/src/java/src/mil/navy/nrl/norm/enums/NormProbingMode.java new file mode 100644 index 0000000..da98b69 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormProbingMode.java @@ -0,0 +1,14 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM GRTT probing modes. This + * enumeration maps to the native implementation and therefore is tied to the + * specific version of NORM. + * + * @author Jason Rush + */ +public enum NormProbingMode { + NORM_PROBE_NONE, + NORM_PROBE_PASSIVE, + NORM_PROBE_ACTIVE; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormRepairBoundary.java b/src/java/src/mil/navy/nrl/norm/enums/NormRepairBoundary.java new file mode 100644 index 0000000..d32f4e3 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormRepairBoundary.java @@ -0,0 +1,13 @@ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM repair boundaries. This + * enumeration maps to the native implementation and therefore is tied to the + * specific version of NORM. + * + * @author Jason Rush + */ +public enum NormRepairBoundary { + NORM_BOUNDARY_BLOCK, + NORM_BOUNDARY_OBJECT; +} diff --git a/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java b/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java new file mode 100644 index 0000000..62bf19c --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/enums/NormSyncPolicy.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2010 by General Dynamics Advanced Information Systems + * Classification: UNCLASSIFIED + */ +package mil.navy.nrl.norm.enums; + +/** + * Enumeration of the different types on NORM sync policies. This enumeration + * maps to the native implementation and therefore is tied to the specific + * version of NORM. + * + * @author Jason Rush + */ +public enum NormSyncPolicy { + NORM_SYNC_CURRENT, + NORM_SYNC_ALL; +} diff --git a/src/java/src/mil/navy/nrl/norm/io/INormEventListener.java b/src/java/src/mil/navy/nrl/norm/io/INormEventListener.java new file mode 100644 index 0000000..d98e3c0 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/io/INormEventListener.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 by General Dynamics Advanced Information Systems + * Classification: UNCLASSIFIED + */ +package mil.navy.nrl.norm.io; + +import mil.navy.nrl.norm.NormEvent; + +/** + * @author Jason Rush + */ +public interface INormEventListener { + public void normEventOccurred(NormEvent normEvent); +} diff --git a/src/java/src/mil/navy/nrl/norm/io/NormInputStream.java b/src/java/src/mil/navy/nrl/norm/io/NormInputStream.java new file mode 100644 index 0000000..526ad8b --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/io/NormInputStream.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2010 by General Dynamics Advanced Information Systems + * Classification: UNCLASSIFIED + */ +package mil.navy.nrl.norm.io; + +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.List; + +import mil.navy.nrl.norm.NormEvent; +import mil.navy.nrl.norm.NormInstance; +import mil.navy.nrl.norm.NormNode; +import mil.navy.nrl.norm.NormObject; +import mil.navy.nrl.norm.NormSession; +import mil.navy.nrl.norm.NormStream; +import mil.navy.nrl.norm.enums.NormEventType; +import mil.navy.nrl.norm.enums.NormObjectType; + +/** + * @author Jason Rush + */ +public class NormInputStream extends InputStream { + private NormInstance normInstance; + private NormSession normSession; + private NormStream normStream; + + private List normEventListeners; + + private boolean closed; + private Object closeLock; + + private boolean bufferIsEmpty; + + public NormInputStream(String address, int port) throws IOException { + // Create the NORM instance + normInstance = new NormInstance(); + + // Create the NORM session + normSession = normInstance.createSession(address, port, + NormNode.NORM_NODE_ANY); + + normStream = null; + + normEventListeners = new LinkedList(); + + closed = true; + closeLock = new Object(); + + bufferIsEmpty = true; + } + + public synchronized void openDebugLog(String filename) throws IOException { + normInstance.openDebugLog(filename); + } + + public synchronized void closeDebugLog() { + normInstance.closeDebugLog(); + } + + public synchronized void setDebugLevel(int level) { + normInstance.setDebugLevel(level); + } + + public synchronized void setMessageTrace(boolean messageTrace) { + normSession.setMessageTrace(messageTrace); + } + + public synchronized void setMulticastInterface(String multicastInterface) + throws IOException { + normSession.setMulticastInterface(multicastInterface); + } + + public synchronized void setEcnSupport(boolean ecnEnable, boolean ignoreLoss) + throws IOException { + normSession.setEcnSupport(ecnEnable, ignoreLoss); + } + + public synchronized void setTtl(byte ttl) throws IOException { + normSession.setTTL(ttl); + } + + public synchronized void setTos(byte tos) throws IOException { + normSession.setTOS(tos); + } + + public synchronized void setSilentReceiver(boolean silent, int maxDelay) { + normSession.setSilentReceiver(silent, maxDelay); + } + + public synchronized void setDefaultUnicastNack(boolean defaultUnicastNack) { + normSession.setDefaultUnicastNack(defaultUnicastNack); + } + + public synchronized void seekMsgStart() { + if (normStream == null) { + throw new IllegalStateException( + "Can only seek msg start after the stream is connected"); + } + + normStream.seekMsgStart(); + } + + /** + * @param normEventListener The INormEventListener to add. + */ + public void addNormEventListener(INormEventListener normEventListener) { + synchronized (normEventListeners) { + normEventListeners.add(normEventListener); + } + } + + /** + * @param normEventListener The INormEventListener to remove. + */ + public void removeNormEventListener(INormEventListener normEventListener) { + synchronized (normEventListeners) { + normEventListeners.remove(normEventListener); + } + } + + private void fireNormEventOccured(NormEvent normEvent) { + synchronized (normEventListeners) { + for (INormEventListener normEventListener : normEventListeners) { + normEventListener.normEventOccurred(normEvent); + } + } + } + + public void open(long bufferSpace) throws IOException { + synchronized (closeLock) { + if (!isClosed()) { + throw new SocketException("Stream is already open"); + } + + normSession.startReceiver(bufferSpace); + + closed = false; + } + } + + /** + * @see java.io.InputStream#close() + */ + @Override + public void close() throws IOException { + synchronized (closeLock) { + if (isClosed()) { + return; + } + + normSession.stopReceiver(); + normInstance.stopInstance(); + + closed = true; + } + } + + /** + * @return Returns the closed. + */ + public boolean isClosed() { + synchronized (closeLock) { + return closed; + } + } + + /** + * @see java.io.InputStream#read() + */ + @Override + public synchronized int read() throws IOException { + byte buffer[] = new byte[1]; + + if (isClosed()) { + throw new IOException("Stream is closed"); + } + + if (read(buffer) < 0) { + return -1; + } + + return buffer[0]; + } + + /** + * @see java.io.InputStream#read(byte[], int, int) + */ + @Override + public synchronized int read(byte[] buffer, int offset, int length) + throws IOException { + int n; + + if (isClosed()) { + throw new IOException("Stream is closed"); + } + + do { + while (normInstance.hasNextEvent(0, 0)) { + processEvent(); + } + + while (bufferIsEmpty) { + processEvent(); + } + + if (normStream == null) { + return -1; + } + + // Read from the stream + if ((n = normStream.read(buffer, offset, length)) < 0) { + throw new StreamBreakException("Break in stream integrity"); + } + + bufferIsEmpty = (n == 0); + } while (bufferIsEmpty); + + return n; + } + + private void processEvent() throws IOException { + // Retrieve the next event + NormEvent normEvent = normInstance.getNextEvent(); + + // Check if the stream was closed + if (isClosed()) { + throw new IOException("Stream closed"); + } + + if (normEvent != null) { + // Process the event + NormEventType eventType = normEvent.getType(); + switch (eventType) { + case NORM_RX_OBJECT_NEW: + NormObject normObject = normEvent.getObject(); + if (normObject.getType() == NormObjectType.NORM_OBJECT_STREAM) { + normStream = (NormStream)normObject; + } + break; + + case NORM_RX_OBJECT_UPDATED: + NormObject object = normEvent.getObject(); + if (!object.equals(normStream)) { + break; + } + + // Signal that the buffer is not empty + bufferIsEmpty = false; + break; + + case NORM_RX_OBJECT_ABORTED: + case NORM_RX_OBJECT_COMPLETED: + normStream = null; + break; + + default: + break; + } + + // Notify listeners of the norm event + fireNormEventOccured(normEvent); + } + } +} diff --git a/src/java/src/mil/navy/nrl/norm/io/NormOutputStream.java b/src/java/src/mil/navy/nrl/norm/io/NormOutputStream.java new file mode 100644 index 0000000..c23c737 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/io/NormOutputStream.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2010 by General Dynamics Advanced Information Systems + * Classification: UNCLASSIFIED + */ +package mil.navy.nrl.norm.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.List; + +import mil.navy.nrl.norm.NormEvent; +import mil.navy.nrl.norm.NormInstance; +import mil.navy.nrl.norm.NormNode; +import mil.navy.nrl.norm.NormObject; +import mil.navy.nrl.norm.NormSession; +import mil.navy.nrl.norm.NormStream; +import mil.navy.nrl.norm.enums.NormEventType; +import mil.navy.nrl.norm.enums.NormFlushMode; + +/** + * @author Jason Rush + */ +public class NormOutputStream extends OutputStream { + private NormInstance normInstance; + private NormSession normSession; + private NormStream normStream; + + private List normEventListeners; + + private boolean closed; + private Object closeLock; + + private boolean bufferIsFull; + + public NormOutputStream(String address, int port) throws IOException { + // Create the NORM instance + normInstance = new NormInstance(); + + // Create the NORM session + normSession = normInstance.createSession(address, port, + NormNode.NORM_NODE_ANY); + + normStream = null; + + normEventListeners = new LinkedList(); + + closed = true; + closeLock = new Object(); + + bufferIsFull = false; + } + + public synchronized void openDebugLog(String filename) throws IOException { + normInstance.openDebugLog(filename); + } + + public synchronized void closeDebugLog() { + normInstance.closeDebugLog(); + } + + public synchronized void setDebugLevel(int level) { + normInstance.setDebugLevel(level); + } + + public synchronized void setMessageTrace(boolean messageTrace) { + normSession.setMessageTrace(messageTrace); + } + + public synchronized void setMulticastInterface(String multicastInterface) + throws IOException { + normSession.setMulticastInterface(multicastInterface); + } + + public synchronized void setEcnSupport(boolean ecnEnable, boolean ignoreLoss) + throws IOException { + normSession.setEcnSupport(ecnEnable, ignoreLoss); + } + + public synchronized void setTtl(byte ttl) throws IOException { + normSession.setTTL(ttl); + } + + public synchronized void setTos(byte tos) throws IOException { + normSession.setTOS(tos); + } + + public synchronized void setCongestionControl(boolean ccEnabled, + boolean ccAdjustRate) { + normSession.setCongestionControl(ccEnabled, ccAdjustRate); + } + + public synchronized void setTxRateBounds(double minTxRate, double maxTxRate) { + normSession.setTxRateBounds(minTxRate, maxTxRate); + } + + public synchronized void setTxRate(double txRate) { + normSession.setTxRate(txRate); + } + + public synchronized void setGrttEstimate(double initialGrttEstimate) { + normSession.setGrttEstimate(initialGrttEstimate); + } + + public synchronized void setGroupSize(long groupSize) { + normSession.setGroupSize(groupSize); + } + + public synchronized void setAutoParity(short autoParity) { + normSession.setAutoParity(autoParity); + } + + public synchronized void setBackoffFactor(double backoffFactor) { + normSession.setBackoffFactor(backoffFactor); + } + + public synchronized double getTxRate() { + return normSession.getTxRate(); + } + + public synchronized double getGrttEstimate() { + return normSession.getGrttEstimate(); + } + + public synchronized void setAutoFlush(NormFlushMode flushMode) { + if (normStream == null) { + throw new IllegalStateException( + "Can only set auto flush after the stream is open"); + } + + normStream.setAutoFlush(flushMode); + } + + public synchronized void setPushEnable(boolean pushEnable) { + if (normStream == null) { + throw new IllegalStateException( + "Can only set push enabled after the stream is open"); + } + + normStream.setPushEnable(pushEnable); + } + + public synchronized void markEom() { + if (normStream == null) { + throw new IllegalStateException( + "Can only mark EOM after the stream is open"); + } + + normStream.markEom(); + } + + /** + * @param normEventListener The INormEventListener to add. + */ + public void addNormEventListener(INormEventListener normEventListener) { + synchronized (normEventListeners) { + normEventListeners.add(normEventListener); + } + } + + /** + * @param normEventListener The INormEventListener to add. + */ + public void removeNormEventListener(INormEventListener normEventListener) { + synchronized (normEventListeners) { + normEventListeners.add(normEventListener); + } + } + + private void fireNormEventOccured(NormEvent normEvent) { + synchronized (normEventListeners) { + for (INormEventListener normEventListener : normEventListeners) { + normEventListener.normEventOccurred(normEvent); + } + } + } + + public void open(int sessionId, long bufferSpace, int segmentSize, + short blockSize, short numParity, long repairWindow) throws IOException { + synchronized (closeLock) { + if (!isClosed()) { + throw new SocketException("Stream is already open"); + } + + normSession.startSender(sessionId, bufferSpace, segmentSize, blockSize, + numParity); + + // Open the stream + normStream = normSession.streamOpen(repairWindow); + + closed = false; + } + } + + /** + * @see java.io.OutputStream#close() + */ + @Override + public void close() throws IOException { + synchronized (closeLock) { + if (isClosed()) { + return; + } + + normStream.close(false); + normSession.stopSender(); + normInstance.stopInstance(); + + closed = true; + } + } + + /** + * @return Returns the closed. + */ + public boolean isClosed() { + synchronized (closeLock) { + return closed; + } + } + + /** + * @see java.io.OutputStream#write(int) + */ + @Override + public synchronized void write(int b) throws IOException { + if (isClosed()) { + throw new IOException("Stream is closed"); + } + + byte buffer[] = new byte[1]; + buffer[0] = (byte)b; + + write(buffer); + } + + /** + * @see java.io.OutputStream#write(byte[], int, int) + */ + @Override + public synchronized void write(byte[] buffer, int offset, int length) + throws IOException { + int n; + + if (isClosed()) { + throw new IOException("Stream is closed"); + } + + while (length > 0) { + while (normInstance.hasNextEvent(0, 0)) { + processEvent(); + } + + // Wait while the buffer is full + while (bufferIsFull) { + processEvent(); + } + + // Write some data + if ((n = normStream.write(buffer, offset, length)) < 0) { + throw new IOException("Failed to write to stream"); + } + + bufferIsFull = (n == 0); + + length -= n; + offset += n; + } + } + + private void processEvent() throws IOException { + // Retrieve the next event + NormEvent normEvent = normInstance.getNextEvent(); + + // Check if the stream was closed + if (isClosed()) { + throw new IOException("Stream closed"); + } + + if (normEvent != null) { + // Process the event + NormEventType eventType = normEvent.getType(); + switch (eventType) { + case NORM_TX_QUEUE_VACANCY: + case NORM_TX_QUEUE_EMPTY: + NormObject object = normEvent.getObject(); + if (!object.equals(normStream)) { + break; + } + + // Signal that the buffer is not full + bufferIsFull = false; + break; + + case NORM_TX_OBJECT_SENT: + case NORM_TX_OBJECT_PURGED: + normStream = null; + break; + + default: + break; + } + + // Notify listeners of the norm event + fireNormEventOccured(normEvent); + } + } +} diff --git a/src/java/src/mil/navy/nrl/norm/io/StreamBreakException.java b/src/java/src/mil/navy/nrl/norm/io/StreamBreakException.java new file mode 100644 index 0000000..51e1122 --- /dev/null +++ b/src/java/src/mil/navy/nrl/norm/io/StreamBreakException.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2010 by General Dynamics Advanced Information Systems + * Classification: UNCLASSIFIED + */ +package mil.navy.nrl.norm.io; + +import java.io.IOException; + +/** + * @author Jason Rush + */ +public class StreamBreakException extends IOException { + public StreamBreakException(String message) { + super(message); + } +} diff --git a/src/pynorm/__init__.py b/src/pynorm/__init__.py new file mode 100644 index 0000000..046e585 --- /dev/null +++ b/src/pynorm/__init__.py @@ -0,0 +1,14 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from pynorm.instance import Instance +from pynorm.core import libnorm, NormError +from pynorm.constants import * + +def setDebugLevel(level): + libnorm.NormSetDebugLevel(level) + +def getDebugLevel(): + return libnorm.NormGetDebugLevel() diff --git a/src/pynorm/constants.py b/src/pynorm/constants.py new file mode 100644 index 0000000..9308cca --- /dev/null +++ b/src/pynorm/constants.py @@ -0,0 +1,73 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes + +from pynorm.core import libnorm + +# Constants +# enum NormObjectType +NORM_OBJECT_NONE = 0 +NORM_OBJECT_DATA = 1 +NORM_OBJECT_FILE = 2 +NORM_OBJECT_STREAM = 3 + +# enum NormFlushMode +NORM_FLUSH_NONE = 0 +NORM_FLUSH_PASSIVE = 1 +NORM_FLUSH_ACTIVE = 2 + +# enum NormNackingMode +NORM_NACK_NONE = 0 +NORM_NACK_INFO_ONLY = 1 +NORM_NACK_NORMAL = 2 + +# enum NormAckingStatus +NORM_ACK_INVALID = 0 +NORM_ACK_FAILURE = 1 +NORM_ACK_PENDING = 2 +NORM_ACK_SUCCESS = 3 + +# enum NormProbingMode +NORM_PROBE_NONE = 0 +NORM_PROBE_PASSIVE = 1 +NORM_PROBE_ACTIVE = 2 + +# enum NormRepairBoundary +NORM_BOUNDARY_BLOCK = 0 +NORM_BOUNDARY_OBJECT = 1 + +# enum NormEventType +NORM_EVENT_INVALID = 0 +NORM_TX_QUEUE_VACANCY = 1 +NORM_TX_QUEUE_EMPTY = 2 +NORM_TX_FLUSH_COMPLETED = 3 +NORM_TX_WATERMARK_COMPLETED = 4 +NORM_TX_CMD_SENT = 5 +NORM_TX_OBJECT_SENT = 6 +NORM_TX_OBJECT_PURGED = 7 +NORM_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_INSTANCE_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_INSTANCE_INVALID").value +NORM_SESSION_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_SESSION_INVALID").value +NORM_NODE_INVALID = ctypes.c_void_p.in_dll(libnorm, "NORM_NODE_INVALID").value +NORM_NODE_NONE = ctypes.c_uint32.in_dll(libnorm, "NORM_NODE_NONE").value +NORM_NODE_ANY = ctypes.c_uint32.in_dll(libnorm, "NORM_NODE_ANY").value +NORM_OBJECT_INVALID = ctypes.c_uint32.in_dll(libnorm, "NORM_OBJECT_INVALID").value diff --git a/src/pynorm/core.py b/src/pynorm/core.py new file mode 100644 index 0000000..cf2f62f --- /dev/null +++ b/src/pynorm/core.py @@ -0,0 +1,505 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes +from ctypes.util import find_library +from platform import system + +# Exception classes +class NormError(Exception): pass + +# ctypes libnorm structures +class NormEventStruct(ctypes.Structure): + _fields_ = [ + ("type", ctypes.c_int), + ("session", ctypes.c_void_p), + ("sender", ctypes.c_void_p), + ("object", ctypes.c_void_p)] + +# ctypes error checkers +def errcheck_bool(result, func, args): + """Checks the return value of functions that return bools. Raises an + exception if they return false.""" + if result == False: + raise NormError("Error calling %s" % func.__name__) + return bool(result) + +def errcheck_instance(result, func, args): + """Checks the return value of NormCreateInstance""" + if result == ctypes.c_void_p.in_dll(libnorm, "NORM_INSTANCE_INVALID"): + raise NormError("Error creating instance.") + return result + +def errcheck_descriptor(result, func, args): + """Checks the return value of NormGetDescriptor""" + if result == ctypes.c_int.in_dll(libnorm, "NORM_DESCRIPTOR_INVALID").value: + raise NormError("Error getting descriptor.") + return result + +def errcheck_session(result, func, args): + """Checks the return value of NormGetDescriptor""" + if result == ctypes.c_void_p.in_dll(libnorm, "NORM_SESSION_INVALID"): + raise NormError("Error starting session.") + return result + +def errcheck_object(result, func, args): + """Checks the return value of NormFileEnqueue and NormDataEnqueue""" + if result == ctypes.c_void_p.in_dll(libnorm, "NORM_OBJECT_INVALID"): + raise NormError( + "Error creating object from function '%s'" % func.__name__) + return result + +def return_bool(result, func, args): + """Converts the return type of a C function to a Python bool""" + return bool(result) + +def get_libnorm(): + """Sets up the return and argument types for the functions in the NORM + library""" + + system_name = system().lower() + if system_name == "windows": + libnorm = ctypes.windll.LoadLibrary("libnorm.dll") + else: + if system_name == "darwin": + libname = "libnorm.dylib" + else: + libname = "libnorm.so" + libnorm = ctypes.cdll.LoadLibrary(libname) + + ## libnorm initializations + # Instance functions + libnorm.NormCreateInstance.restype = ctypes.c_void_p + libnorm.NormCreateInstance.argtypes = [ctypes.c_bool] + libnorm.NormCreateInstance.errcheck = errcheck_instance + + libnorm.NormDestroyInstance.restype = None + libnorm.NormDestroyInstance.argtypes = [ctypes.c_void_p] + + libnorm.NormStopInstance.restype = None + libnorm.NormStopInstance.argtypes = [ctypes.c_void_p] + + libnorm.NormRestartInstance.restype = ctypes.c_void_p + libnorm.NormRestartInstance.argtypes = [ctypes.c_void_p] + libnorm.NormRestartInstance.errcheck = errcheck_bool + + libnorm.NormSuspendInstance.restype = ctypes.c_void_p + libnorm.NormSuspendInstance.argtypes = [ctypes.c_void_p] + libnorm.NormSuspendInstance.errcheck = errcheck_bool + + libnorm.NormResumeInstance.restype = None + libnorm.NormResumeInstance.argtypes = [ctypes.c_void_p] + + libnorm.NormSetCacheDirectory.restype = ctypes.c_bool + libnorm.NormSetCacheDirectory.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + libnorm.NormSetCacheDirectory.errcheck = errcheck_bool + + libnorm.NormGetNextEvent.restype = ctypes.c_bool + libnorm.NormGetNextEvent.argtypes = [ctypes.c_void_p, + ctypes.POINTER(NormEventStruct), ctypes.c_bool] + libnorm.NormGetNextEvent.errcheck = errcheck_bool + + libnorm.NormGetDescriptor.restype = ctypes.c_void_p + libnorm.NormGetDescriptor.argtypes = [ctypes.c_void_p] + libnorm.NormGetDescriptor.errcheck = errcheck_descriptor + + # Session Functions + libnorm.NormCreateSession.restype = ctypes.c_void_p + libnorm.NormCreateSession.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint16, ctypes.c_uint32] + libnorm.NormCreateSession.errcheck = errcheck_session + + libnorm.NormDestroySession.restype = None + libnorm.NormDestroySession.argtypes = [ctypes.c_void_p] + + libnorm.NormSetUserData.restype = None + libnorm.NormSetUserData.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + + libnorm.NormGetUserData.restype = ctypes.c_char_p + libnorm.NormGetUserData.argtypes = [ctypes.c_void_p] + + libnorm.NormGetLocalNodeId.restype = ctypes.c_uint32 + libnorm.NormGetLocalNodeId.argtypes = [ctypes.c_void_p] + + libnorm.NormSetTxPort.restype = ctypes.c_bool + libnorm.NormSetTxPort.argtypes = [ctypes.c_void_p, ctypes.c_uint16, + ctypes.c_bool, ctypes.c_char_p] + libnorm.NormSetTxPort.errcheck = errcheck_bool + + libnorm.NormGetTxPort.restype = ctypes.c_uint16 + libnorm.NormGetTxPort.argtypes = [ctypes.c_void_p] + + libnorm.NormSetTxOnly.restype = None + libnorm.NormSetTxOnly.argtypes = [ctypes.c_void_p, ctypes.c_bool, + ctypes.c_bool] + + libnorm.NormChangeDestination.restype = ctypes.c_bool + libnorm.NormChangeDestination.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint16] + libnorm.NormChangeDestination.errcheck = errcheck_bool + + libnorm.NormSetRxPortReuse.restype = None + libnorm.NormSetRxPortReuse.argtypes = [ctypes.c_void_p, ctypes.c_bool, + ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint16] + + libnorm.NormGetRxPort.restype = ctypes.c_uint16 + libnorm.NormGetRxPort.argtypes = [ctypes.c_void_p] + + libnorm.NormSetEcnSupport.restype = None + libnorm.NormSetEcnSupport.argtypes = [ctypes.c_void_p, ctypes.c_bool, + ctypes.c_bool, ctypes.c_bool] + + libnorm.NormSetMulticastInterface.restype = ctypes.c_bool + libnorm.NormSetMulticastInterface.argtypes = [ctypes.c_void_p, + ctypes.c_char_p] + libnorm.NormSetMulticastInterface.errcheck = errcheck_bool + + libnorm.NormSetTTL.restype = ctypes.c_bool + libnorm.NormSetTTL.argtypes = [ctypes.c_void_p, ctypes.c_uint8] + libnorm.NormSetTTL.errcheck = errcheck_bool + + libnorm.NormSetTOS.restype = ctypes.c_bool + libnorm.NormSetTOS.argtypes = [ctypes.c_void_p, ctypes.c_uint8] + libnorm.NormSetTOS.errcheck = errcheck_bool + + libnorm.NormSetLoopback.restype = ctypes.c_bool + libnorm.NormSetLoopback.argtypes = [ctypes.c_void_p, ctypes.c_byte] + libnorm.NormSetLoopback.errcheck = errcheck_bool + + libnorm.NormSetFragmentation.restype = ctypes.c_bool + libnorm.NormSetFragmentation.argtypes = [ctypes.c_void_p, ctypes.c_bool] + + # Debug functions + libnorm.NormSetMessageTrace.restype = None + libnorm.NormSetMessageTrace.argtypes = [ctypes.c_void_p, ctypes.c_bool] + + libnorm.NormSetTxLoss.restype = None + libnorm.NormSetTxLoss.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormSetRxLoss.restype = None + libnorm.NormSetRxLoss.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormOpenDebugLog.restype = ctypes.c_bool + libnorm.NormOpenDebugLog.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + libnorm.NormOpenDebugLog.errcheck = errcheck_bool + + libnorm.NormCloseDebugLog.restype = None + libnorm.NormCloseDebugLog.argtypes = [ctypes.c_void_p] + + libnorm.NormOpenDebugPipe.restype = ctypes.c_bool + libnorm.NormOpenDebugPipe.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + libnorm.NormOpenDebugPipe.errcheck = errcheck_bool + + libnorm.NormCloseDebugPipe.restype = None + libnorm.NormCloseDebugPipe.argtypes = [ctypes.c_void_p] + + libnorm.NormSetDebugLevel.restype = None + libnorm.NormSetDebugLevel.argtypes = [ctypes.c_uint] + + libnorm.NormGetDebugLevel.restype = ctypes.c_uint + libnorm.NormGetDebugLevel.argtypes = [] + + libnorm.NormSetReportInterval.restype = None + libnorm.NormSetReportInterval.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormGetReportInterval.restype = ctypes.c_double + libnorm.NormGetReportInterval.argtypes = [] + + # Sender functions + libnorm.NormStartSender.restype = ctypes.c_bool + libnorm.NormStartSender.argtypes = [ctypes.c_void_p, ctypes.c_uint16, + ctypes.c_uint32, ctypes.c_uint16, ctypes.c_ubyte, ctypes.c_ubyte] + libnorm.NormStartSender.errcheck = errcheck_bool + + libnorm.NormStopSender.restype = None + libnorm.NormStopSender.argtypes = [ctypes.c_void_p] + + libnorm.NormSetTxRate.restype = None + libnorm.NormSetTxRate.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormGetTxRate.restype = ctypes.c_double + libnorm.NormGetTxRate.argtypes = [ctypes.c_void_p] + + libnorm.NormSetTxSocketBuffer.restype = ctypes.c_bool + libnorm.NormSetTxSocketBuffer.argtypes = [ctypes.c_void_p, ctypes.c_uint] + libnorm.NormSetTxSocketBuffer.errcheck = errcheck_bool + + libnorm.NormSetFlowControl.restype = None + 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.NormSetTxRateBounds.restype = None + libnorm.NormSetTxRateBounds.argtypes = [ + ctypes.c_void_p, ctypes.c_double, ctypes.c_double] + + libnorm.NormSetTxCacheBounds.restype = None + libnorm.NormSetTxCacheBounds.argtypes = [ctypes.c_void_p, + ctypes.c_int64, ctypes.c_uint32, ctypes.c_uint32] + + libnorm.NormSetAutoParity.restype = None + libnorm.NormSetAutoParity.argtypes = [ctypes.c_void_p, ctypes.c_uint8] + + libnorm.NormSetGrttEstimate.restype = None + libnorm.NormSetGrttEstimate.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormGetGrttEstimate.restype = ctypes.c_double + libnorm.NormGetGrttEstimate.argtypes = [ctypes.c_void_p] + + libnorm.NormSetGrttMax.restype = None + libnorm.NormSetGrttMax.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormSetGrttProbingMode.restype = None + libnorm.NormSetGrttProbingMode.argtypes = [ctypes.c_void_p, ctypes.c_int] + + libnorm.NormSetGrttProbingInterval.restype = None + libnorm.NormSetGrttProbingInterval.argtypes = [ctypes.c_void_p, + ctypes.c_double, ctypes.c_double] + + libnorm.NormSetBackoffFactor.restype = None + libnorm.NormSetBackoffFactor.argtypes = [ctypes.c_void_p, ctypes.c_double] + + libnorm.NormSetGroupSize.restype = None + libnorm.NormSetGroupSize.argtypes = [ctypes.c_void_p, ctypes.c_uint] + + libnorm.NormSetTxRobustFactor.restype = None + libnorm.NormSetTxRobustFactor.argtypes = [ctypes.c_void_p, ctypes.c_int] + + libnorm.NormFileEnqueue.restype = ctypes.c_void_p + libnorm.NormFileEnqueue.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_char_p, ctypes.c_uint] + libnorm.NormFileEnqueue.errcheck = errcheck_object + + libnorm.NormDataEnqueue.restype = ctypes.c_void_p + libnorm.NormDataEnqueue.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint] + libnorm.NormDataEnqueue.errcheck = errcheck_object + + libnorm.NormRequeueObject.restype = ctypes.c_bool + libnorm.NormRequeueObject.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + libnorm.NormRequeueObject.errcheck = errcheck_bool + + libnorm.NormStreamOpen.restype = ctypes.c_void_p + libnorm.NormStreamOpen.argtypes = [ctypes.c_void_p, ctypes.c_uint32, + ctypes.c_char_p, ctypes.c_uint] + libnorm.NormStreamOpen.errcheck = errcheck_object + + libnorm.NormStreamClose.restype = None + libnorm.NormStreamClose.argtypes = [ctypes.c_void_p, ctypes.c_bool] + + libnorm.NormStreamWrite.restype = ctypes.c_uint + libnorm.NormStreamWrite.argtypes = [ + ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint] + + libnorm.NormStreamFlush.restype = None + libnorm.NormStreamFlush.argtypes = [ + ctypes.c_void_p, ctypes.c_bool, ctypes.c_int] + + libnorm.NormStreamSetAutoFlush.restype = None + libnorm.NormStreamSetAutoFlush.argtypes = [ctypes.c_void_p, ctypes.c_int] + + libnorm.NormStreamSetPushEnable.restype = None + libnorm.NormStreamSetPushEnable.argtypes = [ctypes.c_void_p, ctypes.c_bool] + + libnorm.NormStreamHasVacancy.restype = ctypes.c_bool + libnorm.NormStreamHasVacancy.argtypes = [ctypes.c_void_p] + + 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.errcheck = errcheck_bool + + libnorm.NormResetWatermark.restype = ctypes.c_bool + libnorm.NormResetWatermark.argtypes = [ctypes.c_void_p] + libnorm.NormResetWatermark.errcheck = errcheck_bool + + libnorm.NormCancelWatermark.restype = None + libnorm.NormCancelWatermark.argtypes = [ctypes.c_void_p] + + libnorm.NormAddAckingNode.restype = ctypes.c_bool + libnorm.NormAddAckingNode.argtypes = [ctypes.c_void_p, ctypes.c_uint32] + + libnorm.NormRemoveAckingNode.restype = None + libnorm.NormRemoveAckingNode.argtypes = [ctypes.c_void_p, ctypes.c_uint32] + + libnorm.NormGetAckingStatus.restype = ctypes.c_int + libnorm.NormGetAckingStatus.argtypes = [ctypes.c_void_p, ctypes.c_uint32] + + libnorm.NormSendCommand.restype = ctypes.c_bool + libnorm.NormSendCommand.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint, ctypes.c_bool] + libnorm.NormSendCommand.errcheck = errcheck_bool + + libnorm.NormCancelCommand.restype = None + libnorm.NormCancelCommand.argtypes = [ctypes.c_void_p] + + # Receiver functions + libnorm.NormStartReceiver.restype = ctypes.c_bool + libnorm.NormStartReceiver.argtypes = [ctypes.c_void_p, ctypes.c_uint32] + libnorm.NormStartReceiver.errcheck = errcheck_bool + + libnorm.NormStopReceiver.restype = None + libnorm.NormStopReceiver.argtypes = [ctypes.c_void_p] + + libnorm.NormSetRxCacheLimit.restype = None + libnorm.NormSetRxCacheLimit.argtypes = [ctypes.c_void_p, ctypes.c_ushort] + + libnorm.NormSetRxSocketBuffer.restype = ctypes.c_bool + libnorm.NormSetRxSocketBuffer.argtypes = [ctypes.c_void_p, ctypes.c_uint] + libnorm.NormSetRxSocketBuffer.errcheck = errcheck_bool + + libnorm.NormSetSilentReceiver.restype = None + libnorm.NormSetSilentReceiver.argtypes = [ctypes.c_void_p, ctypes.c_bool, + ctypes.c_int] + + libnorm.NormSetDefaultUnicastNack.restype = None + 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.NormNodeSetNackingMode.restype = None + libnorm.NormNodeSetNackingMode.argtypes = [ctypes.c_void_p, ctypes.c_int] + + libnorm.NormObjectSetNackingMode.restype = None + 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.NormNodeSetRepairBoundary.restype = None + 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.NormNodeSetRxRobustFactor.restype = None + 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.errcheck = return_bool + + libnorm.NormStreamSeekMsgStart.restype = ctypes.c_bool + libnorm.NormStreamSeekMsgStart.argtypes = [ctypes.c_void_p] + libnorm.NormStreamSeekMsgStart.errcheck = return_bool + + libnorm.NormStreamGetReadOffset.restype = ctypes.c_uint32 + libnorm.NormStreamGetReadOffset.argtypes = [ctypes.c_void_p] + + libnorm.NormStreamGetBufferUsage.restype = ctypes.c_uint32 + libnorm.NormStreamGetBufferUsage.argtypes = [ctypes.c_void_p] + + # Object functions + libnorm.NormObjectGetType.restype = ctypes.c_int + libnorm.NormObjectGetType.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectHasInfo.restype = ctypes.c_bool + libnorm.NormObjectHasInfo.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectGetInfoLength.restype = ctypes.c_uint16 + libnorm.NormObjectGetInfoLength.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectGetInfo.restype = ctypes.c_uint16 + libnorm.NormObjectGetInfo.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint16] + + libnorm.NormObjectGetSize.restype = ctypes.c_int64 + libnorm.NormObjectGetSize.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectGetBytesPending.restype = ctypes.c_int64 + libnorm.NormObjectGetBytesPending.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectCancel.restype = None + libnorm.NormObjectCancel.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectRetain.restype = None + libnorm.NormObjectRetain.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectRelease.restype = None + libnorm.NormObjectRelease.argtypes = [ctypes.c_void_p] + + libnorm.NormFileGetName.restype = ctypes.c_bool + libnorm.NormFileGetName.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.c_uint] + libnorm.NormFileGetName.errcheck = errcheck_bool + + libnorm.NormFileRename.restype = ctypes.c_bool + 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.argtypes = [ctypes.c_void_p] + + libnorm.NormDataDetachData.restype = ctypes.c_char_p + libnorm.NormDataDetachData.argtypes = [ctypes.c_void_p] + + libnorm.NormObjectGetSender.restype = ctypes.c_void_p + libnorm.NormObjectGetSender.argtypes = [ctypes.c_void_p] + + # Node functions + libnorm.NormNodeGetId.restype = ctypes.c_uint32 + libnorm.NormNodeGetId.argtypes = [ctypes.c_void_p] + + libnorm.NormNodeGetAddress.restype = ctypes.c_bool + libnorm.NormNodeGetAddress.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_uint), ctypes.POINTER(ctypes.c_uint16)] + libnorm.NormNodeGetAddress.errcheck = errcheck_bool + + libnorm.NormNodeGetGrtt.restype = ctypes.c_double + libnorm.NormNodeGetGrtt.argtypes = [ctypes.c_void_p] + + libnorm.NormNodeGetCommand.restype = ctypes.c_bool + libnorm.NormNodeGetCommand.argtypes = [ctypes.c_void_p, ctypes.c_char_p, + ctypes.POINTER(ctypes.c_uint)] + libnorm.NormNodeGetCommand.errcheck = errcheck_bool + + libnorm.NormNodeFreeBuffers.restype = None + libnorm.NormNodeFreeBuffers.argtypes = [ctypes.c_void_p] + + libnorm.NormNodeDelete.restype = None + libnorm.NormNodeDelete.argtypes = [ctypes.c_void_p] + + #libnorm.NormNodeSetAutoDelete.restype = None + #libnorm.NormNodeSetAutoDelete.argtypes = [ctypes.c_void_p, ctypes.c_bool] + + #libnorm.NormNodeAllowSender.restype = ctypes.c_bool + #libnorm.NormNodeAllowSender.argtypes = [ctypes.c_uint32] + #libnorm.NormNodeAllowSender.errcheck = errcheck_bool + + #libnorm.NormNodeDenySender.restype = ctypes.c_bool + #libnorm.NormNodeDenySender.argtypes = [ctypes.c_uint32] + #libnorm.NormNodeDenySender.errcheck = errcheck_bool + + libnorm.NormNodeRetain.restype = None + libnorm.NormNodeRetain.argtypes = [ctypes.c_void_p] + + libnorm.NormNodeRelease.restype = None + libnorm.NormNodeRelease.argtypes = [ctypes.c_void_p] + + # Experimental functions + libnorm.NormCountCompletedObjects.restype = ctypes.c_uint32 + libnorm.NormCountCompletedObjects.argtypes = [ctypes.c_void_p] + + return libnorm + +# Global libnorm refrence that everyone should use +libnorm = get_libnorm() diff --git a/src/pynorm/event.py b/src/pynorm/event.py new file mode 100644 index 0000000..0def624 --- /dev/null +++ b/src/pynorm/event.py @@ -0,0 +1,89 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes + +import pynorm.constants as c +from pynorm.session import Session +from pynorm.node import Node +from pynorm.object import Object + +class Event(object): + def __init__(self, type, session, sender, object): + self._type = type + self._session = session + self._sender = sender + self._object = object + + # Properties + type = property(lambda self: self._type) + session = property(lambda self: self._session) + sender = property(lambda self: self._sender) + object = property(lambda self: self._object) + + ## Private functions + @property + def _as_parameter_(self): + """For Ctypes""" + return self._event + + def __cmp__(self, type): + """Does comparison by string event name, or by constant""" + if not str(type).isdigit(): + try: + type = getattr(c, type) + except AttributeError: + return -1 + return cmp(self.type, type) + + def __str__(self): + if self.type == c.NORM_EVENT_INVALID: + return "NORM_EVENT_INVALID" + elif self.type == c.NORM_TX_QUEUE_VACANCY: + return "NORM_TX_QUEUE_VACANCY" + elif self.type == c.NORM_TX_QUEUE_EMPTY: + return "NORM_TX_QUEUE_EMPTY" + elif self.type == c.NORM_TX_FLUSH_COMPLETED: + return "NORM_TX_FLUSH_COMPLETED" + elif self.type == c.NORM_TX_WATERMARK_COMPLETED: + return "NORM_TX_WATERMARK_COMPLETED" + elif self.type == c.NORM_TX_CMD_SENT: + return "NORM_TX_CMD_SENT" + elif self.type == c.NORM_TX_OBJECT_SENT: + return "NORM_TX_OBJECT_SENT" + elif self.type == c.NORM_TX_OBJECT_PURGED: + return "NORM_TX_OBJECT_PURGED" + elif self.type == c.NORM_LOCAL_SENDER_CLOSED: + return "NORM_LOCAL_SENDER_CLOSED" + elif self.type == c.NORM_REMOTE_SENDER_NEW: + return "NORM_REMOTE_SENDER_NEW" + elif self.type == c.NORM_REMOTE_SENDER_ACTIVE: + return "NORM_REMOTE_SENDER_ACTIVE" + elif self.type == c.NORM_REMOTE_SENDER_INACTIVE: + return "NORM_REMOTE_SENDER_INACTIVE" + elif self.type == c.NORM_REMOTE_SENDER_PURGED: + return "NORM_REMOTE_SENDER_PURGED" + elif self.type == c.NORM_RX_CMD_NEW: + return "NORM_RX_CMD_NEW" + elif self.type == c.NORM_RX_OBJECT_NEW: + return "NORM_RX_OBJECT_NEW" + elif self.type == c.NORM_RX_OBJECT_INFO: + return "NORM_RX_OBJECT_INFO" + elif self.type == c.NORM_RX_OBJECT_UPDATED: + return "NORM_RX_OBJECT_UPDATED" + elif self.type == c.NORM_RX_OBJECT_COMPLETED: + return "NORM_RX_OBJECT_COMPLETED" + elif self.type == c.NORM_RX_OBJECT_ABORTED: + return "NORM_RX_OBJECT_ABORTED" + elif self.type == c.NORM_GRTT_UPDATED: + return "NORM_GRTT_UPDATED" + elif self.type == c.NORM_CC_ACTIVE: + return "NORM_CC_ACTIVE" + elif self.type == c.NORM_CC_INACTIVE: + return "NORM_CC_INACTIVE" + else: + return "Unknown event type" diff --git a/src/pynorm/extra/__init__.py b/src/pynorm/extra/__init__.py new file mode 100644 index 0000000..2acefbe --- /dev/null +++ b/src/pynorm/extra/__init__.py @@ -0,0 +1,4 @@ +''' +nft - Norm File Transfer Tool +By: Tom Wambold +''' diff --git a/src/pynorm/extra/logparser.py b/src/pynorm/extra/logparser.py new file mode 100644 index 0000000..a421c90 --- /dev/null +++ b/src/pynorm/extra/logparser.py @@ -0,0 +1,114 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +import re +from datetime import time + +REPORT_RE = re.compile(r'REPORT time>(\d+):(\d+):(\d+)\.(\d+) node>(\d+)') +LOCAL_RATE_RE = re.compile(r'txRate> *(\d+\.\d+) kbps sentRate> *(\d+\.\d+) grtt> *(\d+\.\d+)') +LOCAL_CC_RE = re.compile(r'clr> *(\d+) rate> *(\d+\.\d+) rtt> *(\d+\.\d+) loss> *(\d+\.\d+)') +REMOTE_RE = re.compile(r'Remote sender> *(\d+)') +REMOTE_RATE_RE = re.compile(r'rxRate> *(\d+\.\d+) kbps rx_goodput> *(\d+\.\d+) kbps') +REMOTE_OBJ_RE = re.compile(r'rxObjects> completed> *(\d+) pending> *(\d+) failed> *(\d+)') +REMOTE_FEC_RE = re.compile(r'fecBufferUsage> current> *(\d+) peak> *(\d+) overuns> *(\d+)') +REMOTE_STR_RE = re.compile(r'strBufferUsage> current> *(\d+) peak> *(\d+) overuns> *(\d+)') +REMOTE_RESYNCS_RE = re.compile(r'resyncs> *(\d+) nacks> *(\d+) suppressed> *(\d+)') + +END_STR = '***************************************************************************' + +class Report(object): + def __init__(self): + self.time = None + self.node = None + self.local = None + self.remote = [] + +class LocalReport(object): + def __init__(self): + self.txrate = None + self.sent_rate = None + self.grtt = None + self.clr = None + self.rate = None + self.rtt = None + self.loss = None + self.slow_start = False + +class RemoteReport(object): + def __init__(self): + self.node = None + self.rate = None + self.goodput = None + self.completed = None + self.pending = None + self.failed = None + self.fec_cur = None + self.fec_peak = None + self.fec_over = None + self.str_cur = None + self.str_peak = None + self.str_over = None + self.resyncs = None + self.nacks = None + self.supressed = None + +def parse_log(file): + rep = None + sender = None + + for line in file: + # Get rid of "Proto Info:" header + line = line[line.find(':')+1:].strip() + + if not line: + continue + + if line.startswith('REPORT'): + rep = Report() + hour, min, sec, usec, rep.node = map(int, + REPORT_RE.match(line).groups()) + rep.time = time(hour, min, sec, usec) + + elif line.startswith('Local status'): + rep.local = LocalReport() + + elif line.startswith('txRate'): + rep.local.txrate, rep.local.sent_rate, rep.local.grtt = map(float, + LOCAL_RATE_RE.match(line).groups()) + + elif line.startswith('clr'): + match = LOCAL_CC_RE.match(line).groups() + rep.local.clr = int(match[0]) + rep.local.rate, rep.local.rtt, rep.local.loss = map(float, + match[1:]) + rep.local.slow_start = True if 'slow_start' in line else False + + elif line.startswith('Remote sender'): + rep.remote.append(RemoteReport()) + sender = rep.remote[-1] + sender.node = int(REMOTE_RE.match(line).group(1)) + + elif line.startswith('rxRate'): + sender.rate, sender.goodput = map(float, + REMOTE_RATE_RE.match(line).groups()) + + elif line.startswith('rxObjects'): + sender.completed, sender.pending, sender.failed = map(int, + REMOTE_OBJ_RE.match(line).groups()) + + elif line.startswith('fecBufferUsage'): + sender.fec_cur, sender.fec_peak, sender.fec_over = map(int, + REMOTE_FEC_RE.match(line).groups()) + + elif line.startswith('strBufferUsage'): + sender.str_cur, sender.str_peak, sender.str_over = map(int, + REMOTE_STR_RE.match(line).groups()) + + elif line.startswith('resyncs'): + sender.resyncs, sender.nacks, sender.supressed = map(int, + REMOTE_RESYNCS_RE.match(line).groups()) + + elif line == END_STR: + yield rep diff --git a/src/pynorm/extra/manager.py b/src/pynorm/extra/manager.py new file mode 100644 index 0000000..6d67f81 --- /dev/null +++ b/src/pynorm/extra/manager.py @@ -0,0 +1,158 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +# For Python 2.5 +from __future__ import with_statement + +from threading import Thread, Lock +from random import randint + +#from .models import get_session, SessionProfile, SendProfile, ReceiveProfile +from .. import constants as c +from ..instance import Instance + +class StopManager(Exception): pass + +class Manager(Thread): + """Provides a callback driven method of handling NORM events.""" + + def __init__(self, instance=None, *args, **kwargs): + super(Manager, self).__init__(*args, **kwargs) + self.setDaemon(True) + + self.instance = instance if instance is not None else Instance() + + self.callbacks = {} + self.sessions = {} + self.lock = Lock() + self.doQuit = False + + def run(self): + while not self.doQuit: + event = self.instance.getNextEvent(2) + if event is None: + continue + + with self.lock: + try: + cbs = self.callbacks[event.type] + except KeyError: + continue + + try: + for func, args, kwargs in cbs: + func(event, *args, **kwargs) + except StopManager: + self.doQuit = True + + def register(self, event, func, *args, **kwargs): + with self.lock: + self.callbacks.setdefault(event, list()).append( + (func, args, kwargs)) + + def unregister(self, event, func, *args, **kwargs): + with self.lock: + self.callbacks[event].remove((func, args, kwargs)) + +# def start_session(self, name): +# sqlsession = get_session() +# profile = sqlsession.query(SessionProfile).filter_by(name=name).first() +# self.instance.setCacheDirectory(profile.cache_directory) +# session = self.instance.createSession(profile.address, profile.port, profile.node_id) +# session.userData = name +# +# if profile.interface is not None: +# session.setMulticastInterface(profile.interface) +# +# if profile.ttl is not None: +# session.setTTL(profile.ttl) +# +# if profile.tos is not None: +# session.setTOS(profile.tos) +# +# if profile.loopback is not None: +# session.setLoopback(profile.loopback) +# self.sessions[name] = session +# sqlsession.close() +# +# def start_sender(self, session_name, sender_name): +# session = self.sessions[session_name] +# sqlsession = get_session() +# profile = sqlsession.query(SendProfile).filter_by( +# name=sender_name).first() +# session.startSender(randint(0, 10000), profile.buffer_space, +# profile.segment_size, profile.block_size, profile.num_parity) +# +# if profile.graceful_stop is not None: +# session.sendGracefulStop = profile.graceful_stop +# +# if profile.rate is not None: +# session.setTxRate(profile.rate) +# +# if profile.socket_buffer is not None: +# session.setTxSocketBuffer(profile.socket_buffer) +# +# if profile.congestion_control is not None: +# session.setCongestionControl(profile.congestion_control) +# +# if None not in (profile.rate_min, profile.rate_max): +# session.setTxRateBounds(profile.rate_min, profile.rate_max) +# +# if None not in (profile.size_max, profile.cache_min, +# profile.cache_max): +# session.setTxCacheBounds(profile.size_max, profile.cache_min, +# profile.cache_max) +# +# if profile.auto_parity is not None: +# session.setAutoParity(profile.auto_parity) +# +# if profile.grtt is not None: +# session.setGrttEstimate(profile.grtt) +# +# if profile.grtt_max is not None: +# session.setGrttMax(profile.grtt_max) +# +# if profile.grtt_probing_mode is not None: +# session.setGrttProbingMode(getattr(c, profile.grtt_probing_mode)) +# +# if None not in (profile.grtt_probing_interval_min, +# profile.grtt_probing_interval_max): +# session.setGrttProbingInterval(profile.grtt_probing_interval_min, +# profile.grtt_probing_interval_max) +# +# if profile.backoff_factor is not None: +# session.setBackoffFactor(profile.backoff_factor) +# +# if profile.group_size is not None: +# session.setGroupSize(profile.group_size) +# sqlsession.close() +# return session +# +# def start_receiver(self, session_name, receiver_name): +# sqlsession = get_session() +# session = self.sessions[session_name] +# profile = sqlsession.query(ReceiveProfile).filter_by( +# name=receiver_name).first() +# session.startReceiver(profile.buffer_space) +# +# if profile.grace_period is not None: +# session.gracePeriod = profile.grace_period +# +# if profile.socket_buffer is not None: +# session.setRxSocketBuffer(profile.socket_buffer) +# +# if profile.is_silent is not None: +# session.setSilentReceiver(profile.is_silent, profile.silent_max_delay) +# +# if profile.unicast_nack is not None: +# session.setDefaultUnicastNack(profile.unicast_nack) +# +# if profile.nacking_mode is not None: +# session.setDefaultNackingMode(getattr(c, profile.nacking_mode)) +# +# if profile.repair_boundary is not None: +# session.setDefaultRepairBoundary(getattr(c, profile.repair_boundary)) +# sqlsession.close() +# return session diff --git a/src/pynorm/instance.py b/src/pynorm/instance.py new file mode 100644 index 0000000..86499ed --- /dev/null +++ b/src/pynorm/instance.py @@ -0,0 +1,137 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes +from select import select +from weakref import WeakValueDictionary +from platform import system + +if system() == 'Windows': + import win32event + +import pynorm.constants as c +from pynorm.core import libnorm, NormError, NormEventStruct +from pynorm.session import Session +from pynorm.event import Event +from pynorm.node import Node +from pynorm.object import Object + +class Instance(object): + """Represents an instance of the NORM protocal engine""" + + def __init__(self, priorityBoost=False): + """Creates a new instance of the NORM protocol engine""" + self._instance = libnorm.NormCreateInstance(priorityBoost) + self._sessions = dict() + self._senders = WeakValueDictionary() + self._objects = WeakValueDictionary() + self._estruct = NormEventStruct() + + if system() == 'Windows': + self._select = self._select_windows + else: + self._select = self._select_everythingelse + + ## Public functions + def destroy(self): + libnorm.NormDestroyInstance(self) + + def stop(self): + libnorm.NormStopInstance(self) + + def restart(self): + libnorm.NormRestartInstance(self) + + def setCacheDirectory(self, path): + libnorm.NormSetCacheDirectory(self, path) + + def openDebugLog(self, path): + libnorm.NormOpenDebugLog(self, path) + + def closeDebugLog(self): + libnorm.NormCloseDebugLog(self) + + def openDebugPipe(self, pipeName): + libnorm.NormOpenDebugPipe(self, pipeName) + + def getNextEvent(self, timeout=None): + # Use python's select because letting the C NormGetNextEvent block + # seems to stop signals (CTRL+C) from killing the process + if not self._select(timeout): + return None + + libnorm.NormGetNextEvent(self, ctypes.byref(self._estruct), True) + + if self._estruct.session == c.NORM_SESSION_INVALID: + raise NormError("No new event") + + try: + sender = self._senders[self._estruct.sender] + except KeyError: + sender = self._senders[self._estruct.sender] =\ + Node(self._estruct.sender) + try: + 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) + + def getDescriptor(self): + return libnorm.NormGetDescriptor(self) + + def fileno(self): + return libnorm.NormGetDescriptor(self) + + def createSession(self, address, port, localId=None): + if localId == None: + localId = c.NORM_NODE_ANY + session = Session(self, address, port, localId) + self._sessions[session] = session + return session + + ## Properties + descriptor = property(getDescriptor) + + ## Private functions + def __del__(self): + del self._sessions + self.destroy() + + def _select_windows(self, timeout): + if timeout is None: + timeout = win32event.INFINITE + else: + # Windows wants milliseconds... + timeout *= 1000 + rv = win32event.WaitForSingleObject(self.getDescriptor(), timeout) + return True if rv == win32event.WAIT_OBJECT_0 else False + + def _select_everythingelse(self, timeout): + return True if select([self], [], [], timeout)[0] else False + + @property + def _as_parameter_(self): + """This is for ctypes, so we can pass this object into C functions""" + return self._instance + + def __iter__(self): + return self + + def next(self): + """So you can iterate over the events.""" + try: + return self.getNextEvent() + except NormError: + raise StopIteration + + def __cmp__(self, other): + return cmp(self._as_parameter_, other._as_parameter) + + def __hash__(self): + return self._as_parameter_ diff --git a/src/pynorm/node.py b/src/pynorm/node.py new file mode 100644 index 0000000..a26d166 --- /dev/null +++ b/src/pynorm/node.py @@ -0,0 +1,75 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes + +import pynorm.constants as c +from pynorm.core import libnorm, NormError + +class Node(object): + """Represents a NORM node instance""" + + ## Public functions + def __init__(self, node): + libnorm.NormNodeRetain(node) + self._node = node + + def getId(self): + id = libnorm.NormNodeGetId(self) + if id == c.NORM_NODE_ANY: + return None + return id + + def getAddress(self): + try: + return self._address + except AttributeError: + port = ctypes.c_uint16() + buffer = ctypes.create_string_buffer(50) + size = ctypes.c_uint(50) + if not libnorm.NormNodeGetAddress(self, buffer, ctypes.byref(size), + ctypes.byref(port)): + raise NormError("Node getAddress failed") + self._address = (buffer.value, port.value) + return self._address + + def getGrtt(self): + grtt = libnorm.NormNodeGetGrtt(self) + if grtt == -1.0: + raise NormError("getGrtt failed") + return grtt + + def setUnicastNack(self, mode): + libnorm.NormNodeSetUnicastNack(self, mode) + + def setNackingMode(self, mode): + libnorm.NormNodeSetNackingMode(self, mode) + + def setRepairBoundary(self, boundary): + libnorm.NormNodeSetRepairBoundary(self, boundary) + + ## Properties + id = property(getId) + address = property(getAddress) + grtt = property(getGrtt) + + ## Private Functions + def __del__(self): + libnorm.NormNodeRelease(self) + + @property + def _as_parameter_(self): + return self._node + + def __str__(self): + return "Node - Id=%i" % self.id + + def __cmp__(self, other): + return cmp(self._as_parameter_, other._as_parameter_) + + def __hash__(self): + return self._as_parameter_ diff --git a/src/pynorm/object.py b/src/pynorm/object.py new file mode 100644 index 0000000..7bd56ee --- /dev/null +++ b/src/pynorm/object.py @@ -0,0 +1,136 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes + +import pynorm.constants as c +from pynorm.core import libnorm, NormError +from pynorm.node import Node + +class Object(object): + """Represents a NORM object instance""" + + ## Public functions + def __init__(self, object): + libnorm.NormObjectRetain(object) + self._object = object + + def getType(self): + return libnorm.NormObjectGetType(self) + + def hasObjectInfo(self): + return libnorm.NormObjectHasInfo(self) + + def getInfo(self): + if not self.hasObjectInfo(): + raise NormError("No object info received yet.") + + length = libnorm.NormObjectGetInfoLength(self) + if length == 0: + raise NormError("No object info received yet.") + + buffer = ctypes.create_string_buffer(length) + recv = libnorm.NormObjectGetInfo(self, buffer, length) + if recv == 0: + raise NormError("No object info received yet.") + return buffer.value + + def getSize(self): + return libnorm.NormObjectGetSize(self) + + def getBytesPending(self): + return libnorm.NormObjectGetBytesPending(self) + + def cancel(self): + libnorm.NormObjectCancel(self) + + def getFileName(self): + buffer = ctypes.create_string_buffer(100) + libnorm.NormFileGetName(self, buffer, ctypes.sizeof(buffer)) + return buffer.value + + def renameFile(self, name): + libnorm.NormFileRename(self, name) + + 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)) + + def setNackingMode(self, mode): + libnorm.NormObjectSetNackingMode(self, mode) + + ## Stream sending functions + def streamClose(self, graceful=False): + libnorm.NormStreamClose(self, graceful) + + def streamWrite(self, msg): + return libnorm.NormStreamWrite(self, msg, len(msg)) + + def streamFlush(self, eom=False, flushmode=c.NORM_FLUSH_PASSIVE): + libnorm.NormStreamFlush(self, eom, flushmode) + + def streamSetAutoFlush(self, mode): + libnorm.NormStreamSetAutoFlush(self, mode) + + def streamPushEnable(self, push): + libnorm.NormStreamSetPushEnable(self, push) + + def streamHasVacancy(self): + return libnorm.NormStreamHasVacancy(self) + + def streamMarkEom(self): + libnorm.NormStreamMarkEom(self) + + ## Stream receiving functions + def streamRead(self, size): + buffer = ctypes.create_string_buffer(size) + numBytes = ctypes.c_uint(ctypes.sizeof(buffer)) + libnorm.NormStreamRead(self, buffer, ctypes.byref(numBytes)) + return (numBytes, buffer.value) + + def streamSeekMsgStart(self): + return libnorm.NormStreamSeekMsgStart(self) + + def streamGetReadOffset(self): + return libnorm.NormStreamGetReadOffset(self) + + ## Properties + type = property(getType) + info = property(getInfo) + size = property(getSize) + bytesPending = property(getBytesPending) + filename = property(getFileName, renameFile) + sender = property(getSender) + + ## Private functions + def __del__(self): + libnorm.NormObjectRelease(self) + + @property + def _as_parameter_(self): + return self._object + + def __str__(self): + if self.type == c.NORM_OBJECT_DATA: + return "NORM_OBJECT_DATA" + elif self.type == c.NORM_OBJECT_FILE: + return "NORM_OBJECT_FILE" + elif self.type == c.NORM_OBJECT_STREAM: + return "NORM_OBJECT_STREAM" + elif self.type == c.NORM_OBJECT_NONE: + return "NORM_OBJECT_NONE" + + def __cmp__(self, other): + return cmp(self._as_parameter_, other._as_parameter_) + + def __hash__(self): + return self._as_parameter_ diff --git a/src/pynorm/session.py b/src/pynorm/session.py new file mode 100644 index 0000000..c11067a --- /dev/null +++ b/src/pynorm/session.py @@ -0,0 +1,192 @@ +""" +pynorm - Python wrapper for NRL's libnorm +By: Tom Wambold +""" + +from __future__ import absolute_import + +import ctypes + +import pynorm.constants as c +from pynorm.core import libnorm, NormError +from pynorm.object import Object + +class Session(object): + """This represents a session tied to a particular NORM instance""" + + ## Public functions + def __init__(self, instance, address, port, localId=c.NORM_NODE_ANY): + """ + instance - An instance of NormInstance + address - String of multicast address to join + port - valid unused port number + localId - NormNodeId + """ + self._instance = instance + self._session = libnorm.NormCreateSession(instance, address, port, + localId) + self.sendGracefulStop = False + self.gracePeriod = 0 + + def destroy(self): + libnorm.NormDestroySession(self) + + def setUserData(self, data): + """data should be a string""" + libnorm.NormSetUserData(self, data) + + def getUserData(self): + return libnorm.NormGetUserData(self) + + def getNodeId(self): + return libnorm.NormGetLocalNodeId(self) + + def setTxPort(self, port): + libnorm.NormSetTxPort(self, port) + + def setRxPortReuse(self, enable, bindToSessionAddr=True): + libnorm.NormSetRxPortReuse(self, enable, bindToSessionAddr) + + def setMulticastInterface(self, iface): + libnorm.NormSetMulticastInterface(self, iface) + + def setTTL(self, ttl): + libnorm.NormSetTTL(self, ttl) + + def setTOS(self, tos): + libnorm.NormSetTOS(self, tos) + + def setLoopback(self, loop): + libnorm.NormSetLoopback(self, loop) + + def getReportInterval(self): + libnorm.NormGetReportInterval(self) + + def setReportInterval(self, interval): + libnorm.NormSetReportInterval(self, interval) + + ## Sender functions + 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 setTxRate(self, rate): + libnorm.NormSetTxRate(self, rate) + + def setTxSocketBuffer(self, size): + libnorm.NormSetTxSocketBuffer(self, size) + + def setCongestionControl(self, cc): + libnorm.NormSetCongestionControl(self, cc) + + def setTxRateBounds(self, min, max): + libnorm.NormSetTxRateBounds(self, min, max) + + def setTxCacheBounds(self, sizeMax, countMin, countMax): + libnorm.NormSetTxCacheBounds(self, sizeMax, countMin, countMax) + + def setAutoParity(self, parity): + libnorm.NormSetAutoParity(self, parity) + + def getGrttEstimate(self): + return libnorm.NormGetGrttEstimate(self) + + def setGrttEstimate(self, grtt): + libnorm.NormSetGrttEstimate(self, grtt) + + def setGrttMax(self, max): + libnorm.NormSetGrttMax(self, max) + + def setGrttProbingMode(self, mode): + libnorm.NormSetGrttProbingMode(self, mode) + + def setGrttProbingInterval(self, min, max): + libnorm.NormSetGrttProbingInterval(self, min, max) + + def setBackoffFactor(self, factor): + libnorm.NormSetBackoffFactor(self, factor) + + def setGroupSize(self, size): + libnorm.NormSetGroupSize(self, size) + + def fileEnqueue(self, filename, 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))) + + def requeueObject(self, object): + libnorm.NormRequeueObject(self, object) + + def streamOpen(self, bufferSize, info=""): + return Object(libnorm.NormStreamOpen(self, bufferSize, info, + len(info))) + + def setWatermark(self, object): + libnorm.NormSetWatermark(self, object) + + def addAckingNode(self, nodeId): + libnorm.NormAddAckingNode(self, nodeId) + + def removeAckingNode(self, nodeId): + libnorm.NormRemoveAckingNode(self, nodeId) + + def getAckingStatus(self, nodeId=c.NORM_NODE_ANY): + return libnorm.NormGetAckingStatus(self, nodeId) + + ## Receiver functions + def startReceiver(self, bufferSpace): + libnorm.NormStartReceiver(self, bufferSpace) + + def stopReceiver(self, gracePeriod=0): + """This will be called automatically if the receiver is active""" + libnorm.NormStopReceiver(self, gracePeriod) + + def setRxSocketBuffer(self, size): + libnorm.NormSetRxSocketBuffer(self, size) + + def setSilentReceiver(self, silent, maxDelay=None): + if maxDelay == None: + maxDelay = -1 + libnorm.NormSetSilentReceiver(self, silent, maxDelay) + + def setDefaultUnicastNack(self, mode): + libnorm.NormSetDefaultUnicastNack(self, mode) + + def setDefaultNackingMode(self, mode): + libnorm.NormSetDefaultNackingMode(self, mode) + + def setDefaultRepairBoundary(self, boundary): + libnorm.NormSetDefaultRepairBoundary(self, boundary) + + ## Properties + nodeId = property(getNodeId) + grtt = property(getGrttEstimate, setGrttEstimate) + userData = property(getUserData, setUserData) + reportInterval = property(getReportInterval, setReportInterval) + + ## Private functions + def __del__(self): + self.stopReceiver(self.gracePeriod) + self.stopSender(self.sendGracefulStop) + libnorm.NormDestroySession(self) + + @property + def _as_parameter_(self): + """Used when passing this object to ctypes functions""" + return self._session + + def __cmp__(self, other): + try: + return cmp(self._as_parameter_, other._as_parameter) + except AttributeError: + return cmp(self._as_parameter_, other) + + def __hash__(self): + return self._as_parameter_ diff --git a/ns/README.txt b/src/sim/ns/README.txt similarity index 100% rename from ns/README.txt rename to src/sim/ns/README.txt diff --git a/ns/example.tcl b/src/sim/ns/example.tcl similarity index 100% rename from ns/example.tcl rename to src/sim/ns/example.tcl diff --git a/ns/nackCount.cpp b/src/sim/ns/nackCount.cpp similarity index 100% rename from ns/nackCount.cpp rename to src/sim/ns/nackCount.cpp diff --git a/ns/normcc.tcl b/src/sim/ns/normcc.tcl similarity index 80% rename from ns/normcc.tcl rename to src/sim/ns/normcc.tcl index 3b44ab9..be7e989 100644 --- a/ns/normcc.tcl +++ b/src/sim/ns/normcc.tcl @@ -114,12 +114,12 @@ if {"RED" == $queueType} { # Non-bottleneck link rate is 10 * bottleneckRate set linkRate [expr 10 * [bw_parse $bottleneckRate]] -# 4) Create a single NORM server and link to bottleneck -puts "normcc: Creating NORM server ..." +# 4) Create a single NORM sender and link to bottleneck +puts "normcc: Creating NORM sender ..." set n(2) [$ns_ node] -set norm_server [new Agent/NORM] -$ns_ attach-agent $n(2) $norm_server -# Link from NORM server to bottleneck +set norm_sender [new Agent/NORM] +$ns_ attach-agent $n(2) $norm_sender +# Link from NORM sender to bottleneck $ns_ duplex-link $n(2) $n(0) $linkRate 1ms DropTail $ns_ queue-limit $n(2) $n(0) 100 $ns_ duplex-link-op $n(2) $n(0) queuePos 0.5 @@ -144,14 +144,14 @@ for {set i 0} {$i < $numTcp} {incr i} { $ftp($i) attach-agent $tcp_src($i) } -# 6) Create nodes with NORM clients and links to bottleneck -puts "normcc: Creating NORM clients ..." +# 6) Create nodes with NORM receivers and links to bottleneck +puts "normcc: Creating NORM receivers ..." for {set i 0} {$i < $groupSize} {incr i} { set k [expr $i + 3 + $numTcp] set n($k) [$ns_ node] - set norm_client($i) [new Agent/NORM] - $ns_ attach-agent $n($k) $norm_client($i) - # Links from bottleneck to NORM clients + set norm_receiver($i) [new Agent/NORM] + $ns_ attach-agent $n($k) $norm_receiver($i) + # Links from bottleneck to NORM receivers $ns_ duplex-link $n(1) $n($k) $linkRate 1ms DropTail $ns_ queue-limit $n(1) $n($k) 100 $ns_ duplex-link-op $n(1) $n($k) queuePos 0.5 @@ -186,42 +186,43 @@ set group [Node allocaddr] puts "normcc: Configuring NORM agents ..." -# 10) Configure global NORM agent commands (using norm_server) -$norm_server debug 2 -#$norm_server log normLog.txt +# 10) Configure global NORM agent commands (using norm_sender) +$norm_sender debug 2 +#$norm_sender log normLog.txt -# 11) Configure NORM server parameters -$norm_server address $group/5000 -$norm_server rate [bw_parse $sendRate] -$norm_server cc on -$norm_server backoff $backoffFactor +# 11) Configure NORM sender parameters +$norm_sender address $group/5000 +$norm_sender rate [bw_parse $sendRate] +$norm_sender cc on +$norm_sender backoff $backoffFactor -$norm_server segment 532 -$norm_server block 64 -$norm_server parity 32 +$norm_sender segment 532 +$norm_sender block 64 +$norm_sender parity 32 +$norm_sender txbuffer 1000000 -$norm_server repeat -1 -$norm_server interval 0.0 -$norm_server txloss 0.0 -$norm_server gsize $groupSize -#$norm_server trace +$norm_sender repeat -1 +$norm_sender interval 0.0 +$norm_sender txloss 0.0 +$norm_sender gsize $groupSize +#$norm_sender trace -# 12) Configure NORM client parameters +# 12) Configure NORM receiver parameters for {set i 0} {$i < $groupSize} {incr i} { - $norm_client($i) address $group/5000 - $norm_client($i) backoff $backoffFactor - $norm_client($i) rxbuffer 1000000 - $norm_client($i) txloss 0.0 - $norm_client($i) gsize $groupSize - #$norm_client($i) trace + $norm_receiver($i) address $group/5000 + $norm_receiver($i) backoff $backoffFactor + $norm_receiver($i) rxbuffer 1000000 + $norm_receiver($i) txloss 0.0 + $norm_receiver($i) gsize $groupSize + #$norm_receiver($i) trace } -# 13) Start NORM server and clients -$ns_ at 0.0 "$norm_server start server" +# 13) Start NORM sender and receivers +$ns_ at 0.0 "$norm_sender start sender" for {set i 0} {$i < $groupSize} {incr i} { - $ns_ at 0.0 "$norm_client($i) start client" + $ns_ at 0.0 "$norm_receiver($i) start receiver" } -$ns_ at 0.0 "$norm_server sendStream 1000000" +$ns_ at 0.0 "$norm_sender sendStream 1000000" # 14) Start tcp flows for {set i 0} {$i < $numTcp} {incr i} { diff --git a/ns/ns226-Makefile.in b/src/sim/ns/ns226-Makefile.in similarity index 100% rename from ns/ns226-Makefile.in rename to src/sim/ns/ns226-Makefile.in diff --git a/ns/nsNormAgent.cpp b/src/sim/ns/nsNormAgent.cpp similarity index 100% rename from ns/nsNormAgent.cpp rename to src/sim/ns/nsNormAgent.cpp diff --git a/ns/nsNormAgent.h b/src/sim/ns/nsNormAgent.h similarity index 100% rename from ns/nsNormAgent.h rename to src/sim/ns/nsNormAgent.h diff --git a/ns/simplenorm.tcl b/src/sim/ns/simplenorm.tcl similarity index 94% rename from ns/simplenorm.tcl rename to src/sim/ns/simplenorm.tcl index fc3c7e9..532a6c7 100644 --- a/ns/simplenorm.tcl +++ b/src/sim/ns/simplenorm.tcl @@ -99,7 +99,7 @@ for {set i 1} {$i <= $numNodes} {incr i} { # 4) Configure multicast routing for topology set mproto DM set mrthandle [$ns_ mrtproto $mproto {}] - if {$mrthandle != ""} { +if {$mrthandle != ""} { $mrthandle set_c_rp [list $n(0)] } @@ -112,7 +112,7 @@ puts "simplenorm: Configuring NORM agents ..." $norm(1) debug 2 #$norm(1) log normLog.txt -# 7) Configure NORM server agent at node 1 +# 7) Configure NORM sender agent at node 1 $norm(1) address $group/5000 $norm(1) rate [bw_parse $sendRate] $norm(1) backoff $backoffFactor @@ -124,7 +124,7 @@ $norm(1) gsize $groupSize $norm(1) cc on #$norm(1) trace on -# 8) Configure NORM client agents at other nodes +# 8) Configure NORM receiver agents at other nodes for {set i 2} {$i <= $numNodes} {incr i} { $norm($i) address $group/5000 $norm($i) backoff $backoffFactor @@ -135,10 +135,10 @@ for {set i 2} {$i <= $numNodes} {incr i} { #$norm($i) trace } -# 9) Start server and clients -$ns_ at 0.0 "$norm(1) start server" +# 9) Start sender and receivers +$ns_ at 0.0 "$norm(1) start sender" for {set i 2} {$i <= $numNodes} {incr i} { - $ns_ at 0.0 "$norm($i) start client" + $ns_ at 0.0 "$norm($i) start receiver" } $ns_ at 0.0 "$norm(1) sendFile 1000000" diff --git a/ns/sizeAve.cpp b/src/sim/ns/sizeAve.cpp similarity index 100% rename from ns/sizeAve.cpp rename to src/sim/ns/sizeAve.cpp diff --git a/ns/suppress.tcl b/src/sim/ns/suppress.tcl similarity index 100% rename from ns/suppress.tcl rename to src/sim/ns/suppress.tcl diff --git a/opnet/11.5/OpnetNormProcess.h b/src/sim/opnet/11.5/OpnetNormProcess.h similarity index 100% rename from opnet/11.5/OpnetNormProcess.h rename to src/sim/opnet/11.5/OpnetNormProcess.h diff --git a/opnet/11.5/norm_protolib.pr.cpp b/src/sim/opnet/11.5/norm_protolib.pr.cpp similarity index 100% rename from opnet/11.5/norm_protolib.pr.cpp rename to src/sim/opnet/11.5/norm_protolib.pr.cpp diff --git a/opnet/11.5/norm_protolib.pr.m b/src/sim/opnet/11.5/norm_protolib.pr.m similarity index 100% rename from opnet/11.5/norm_protolib.pr.m rename to src/sim/opnet/11.5/norm_protolib.pr.m diff --git a/opnet/12.0/OpnetNormProcess.h b/src/sim/opnet/12.0/OpnetNormProcess.h similarity index 100% rename from opnet/12.0/OpnetNormProcess.h rename to src/sim/opnet/12.0/OpnetNormProcess.h diff --git a/opnet/12.0/norm_protolib.pr.cpp b/src/sim/opnet/12.0/norm_protolib.pr.cpp similarity index 100% rename from opnet/12.0/norm_protolib.pr.cpp rename to src/sim/opnet/12.0/norm_protolib.pr.cpp diff --git a/opnet/12.0/norm_protolib.pr.m b/src/sim/opnet/12.0/norm_protolib.pr.m similarity index 100% rename from opnet/12.0/norm_protolib.pr.m rename to src/sim/opnet/12.0/norm_protolib.pr.m diff --git a/unix/unixPostProcess.cpp b/src/unix/unixPostProcess.cpp similarity index 100% rename from unix/unixPostProcess.cpp rename to src/unix/unixPostProcess.cpp diff --git a/unix/Makefile.common b/unix/Makefile.common deleted file mode 100644 index 18d7192..0000000 --- a/unix/Makefile.common +++ /dev/null @@ -1,118 +0,0 @@ -######################################################################### -# COMMON NORM MAKEFILE STUFF -# - -SHELL=/bin/sh - -.SUFFIXES: .cpp -sim.o $(.SUFFIXES) - -PROTOLIB = ../protolib -COMMON = ../common -UNIX = ../unix -NS = ../ns - -INCLUDES = $(SYSTEM_INCLUDES) -I$(UNIX) -I$(COMMON) -I$(PROTOLIB)/common - -CFLAGS = -g -DPROTO_DEBUG -DUNIX -D_FILE_OFFSET_BITS=64 -O $(SYSTEM_CFLAGS) $(SYSTEM_HAVES) $(INCLUDES) - -LDFLAGS = $(SYSTEM_LDFLAGS) - -# Note: Even command line app needs X11 for Netscape post-processing -LIBS = $(SYSTEM_LIBS) -lm -lpthread -XLIBS = -lXmu -lXt -lX11 - - -TARGETS = norm raft - -# Rule for C++ .cpp extension -.cpp.o: - $(CC) -c $(CFLAGS) -o $*.o $*.cpp - -# NORM depends upon the NRL Protean Group's development library -LIBPROTO = $(PROTOLIB)/unix/libProtokit.a -$(PROTOLIB)/unix/libProtokit.a: - cd $(PROTOLIB)/unix; $(MAKE) -f Makefile.$(SYSTEM) libProtokit.a - -NORM_SRC = $(COMMON)/normMessage.cpp $(COMMON)/normSession.cpp \ - $(COMMON)/normNode.cpp $(COMMON)/normObject.cpp \ - $(COMMON)/normSegment.cpp \ - $(COMMON)/normEncoder.cpp $(COMMON)/galois.cpp \ - $(COMMON)/normFile.cpp $(COMMON)/normApi.cpp - -NORM_OBJ = $(NORM_SRC:.cpp=.o) - -LIB_SRC = $(NORM_SRC) -LIB_OBJ = $(LIB_SRC:.cpp=.o) - -# NORM as a static library -libnorm.a: $(LIB_OBJ) - $(AR) rcv $@ $(LIB_OBJ) - $(RANLIB) $@ - -# NORM as a shared library -libnorm.so: $(LIB_OBJ) - $(CC) $(SYSTEM_SOFLAGS) $(LIB_OBJ) $(LIBS) -o $@ - -# NORM as a dynamic library for MacOS -libnorm.dylib: $(LIB_OBJ) $(LIBPROTO) - $(CC) $(SYSTEM_SOFLAGS) $(LIB_OBJ) $(LIBPROTO) $(LIBS) -o $@ - - -SIM_SRC = $(NORM_SRC) $(COMMON)/normSimAgent.cpp -SIM_OBJ = $(SIM_SRC:.cpp=-sim.o) - -libnormSim.a: $(SIM_OBJ) - ar rcv $@ $(SIM_OBJ) - ranlib $@ - -# (norm) command-line file broadcaster/receiver -APP_SRC = $(COMMON)/normApp.cpp $(COMMON)/normPostProcess.cpp \ - $(UNIX)/unixPostProcess.cpp -APP_OBJ = $(APP_SRC:.cpp=.o) - -norm: $(APP_OBJ) libnorm.a $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(APP_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) - - -# (normTest) test of NORM API -TEST_SRC = $(COMMON)/normTest.cpp -TEST_OBJ = $(TEST_SRC:.cpp=.o) - -normTest: $(TEST_OBJ) libnorm.a $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(TEST_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) - -# (normThreadTest) test of threaded use of NORM API -TTEST_SRC = $(COMMON)/normThreadTest.cpp -TTEST_OBJ = $(TTEST_SRC:.cpp=.o) -normThreadTest: $(TTEST_OBJ) libnorm.a $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(TTEST_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) - -# (normThreadTest2) alt test of threaded use of NORM API -TTEST2_SRC = $(UNIX)/normThreadTest2.cpp -TTEST2_OBJ = $(TTEST2_SRC:.cpp=.o) -normThreadTest2: $(TTEST2_OBJ) libnorm.a $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(TTEST2_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) - - -# (npc) NORM Pre-Coder -PCODE_SRC = $(COMMON)/normPrecode.cpp -PCODE_OBJ = $(PCODE_SRC:.cpp=.o) -npc: $(PCODE_OBJ) libnorm.a $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(PCODE_OBJ) $(LDFLAGS) libnorm.a $(LIBPROTO) $(LIBS) - - -# (raft) command-line reliable tunnel helper -RAFT_SRC = $(COMMON)/raft.cpp -RAFT_OBJ = $(RAFT_SRC:.cpp=.o) - -raft: $(RAFT_OBJ) $(LIBPROTO) - $(CC) $(CFLAGS) -o $@ $(RAFT_OBJ) $(LDFLAGS) $(LIBPROTO) $(LIBS) - -clean: - rm -f $(COMMON)/*.o $(UNIX)/*.o $(UNIX)/libnorm.a \ - $(UNIX)/libnorm.so $(UNIX)/norm $(NS)/*.o; - cd $(PROTOLIB)/unix; $(MAKE) -f Makefile.$(SYSTEM) clean - -# DO NOT DELETE THIS LINE -- mkdep uses it. -# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. - diff --git a/waf b/waf new file mode 100755 index 0000000000000000000000000000000000000000..e1e34d431afba28d52add07a6094226a1a74374b GIT binary patch literal 87674 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 literal 0 HcmV?d00001 diff --git a/win32/Norm.dsw b/win32/Norm.dsw deleted file mode 100644 index ef867b8..0000000 --- a/win32/Norm.dsw +++ /dev/null @@ -1,77 +0,0 @@ -Microsoft Developer Studio Workspace File, Format Version 6.00 -# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! - -############################################################################### - -Project: "NormLib"=.\NormLib.dsp - Package Owner=<4> - -Package=<5> -{{{ -}}} - -Package=<4> -{{{ -}}} - -############################################################################### - -Project: "Protokit"=..\protolib\win32\Protokit.dsp - Package Owner=<4> - -Package=<5> -{{{ -}}} - -Package=<4> -{{{ -}}} - -############################################################################### - -Project: "norm"=.\norm\norm.dsp - Package Owner=<4> - -Package=<5> -{{{ -}}} - -Package=<4> -{{{ - Begin Project Dependency - Project_Dep_Name NormLib - End Project Dependency - Begin Project Dependency - Project_Dep_Name Protokit - End Project Dependency -}}} - -############################################################################### - -Project: "normTest"=..\normTest.dsp - Package Owner=<4> - -Package=<5> -{{{ -}}} - -Package=<4> -{{{ - Begin Project Dependency - Project_Dep_Name NormLib - End Project Dependency - Begin Project Dependency - Project_Dep_Name Protokit - End Project Dependency -}}} - -############################################################################### - -Global: - -Package=<5> -{{{ -}}} - -Package=<3> -{{{ -}}} - -############################################################################### - diff --git a/win32/Norm.opt b/win32/Norm.opt deleted file mode 100644 index 3ef6944aaff66af0d78648aaa81b001b8c6c97d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68096 zcmeHQZEPGz8Q!yF`|LQ0lQ>TLy)86On>uxpeumQ4zO!2g+i~nPrp3^%@7Cw7?{<&f zbv|0!xPZhTXbSzH5+7130Y#`kkl+tUs34H43aR`gh#!SNh$4^>5<-F!o_BZ0*}LrR z&H3)A)4XFno}HO@-kE)Oc5m;QnfHG4&enI&{qX8PC}rV(rB(Uwg-)eCl;48mgh=-& zD8F!lk|L(#(*UwLTqYKHAN^Eu&02vCKpQ~UyaPxAoj@0`5uoe53GrrN3xFY@Yz4Lf z+kswS2S9Q=5$^)70qE1Fi>d07&i#;+uev03QWz26hAez#iZh;8uWSZ$mr) z>;rBG27&#+0pK8T2XH5F7eMm&Aiftk1bhs*5BNB6KkxwXATR_B10cYKjscGV$AMYklfa_@$$kp)9Pnx2Gr;4(6MzQH0~vti zbi@ll4p;;X;7MQ!$O8p{)V<#>Q_Pw&>u^Zn?P@ja{ELyj5b`H))gB5i2WCs1v z0J253peij18m}~VsT0-Ysl0{$(O^XwM(Y-=<)AgPW38bF?bE$O_ls=%-PB0X{)m#r zHFiBKP#-pH+f`wVvh3X zP8#OH19Ou&95?3oWu0OIRgy*9a;+u99gM8wl|o}sc`6VVwKtIBJ&`s2*1U%4pQw?f zgsVGlf0bItAR{V0ii4z${Py+sQ{Iy^x(2;lRBC@{ zWhK25T`e-VlAcK6m`tIE-f?~h&}S&+(ev~?K%cAC(h%X$1Pjphp!X!QKY^|Xy*pJ) zLqz#Y@VUvc|Nc;~9muBj1iEHX!F$2MzqLGf-e@_iobB1N`rNq|FFh&EG%Z(MFwCs# zF6ye|+J>1^`xjg*-%npMIIsoS0#~#JcDH;~y;OYGlpE|3<-;w#GEo3+oeUib*f&BfXTS_9#vA6pQxv;h4U^oJaf<#ElNWhu-f(@81#sv;xbd;pGQkZo|M$W0eu5vY<@yPpPsE~bd0OO) zYs~zgun5QN%>q&TuV=Ay%EZ28+813d+=TtdT@Vs;^VQ_3{6_j0>^~k;rm)BUaqOw5 zec%P`u{XT^@FXG=cAVr(3y(C1=2@pj4Q{Fys+K0X5?Z@^@tN*_m?bAB` zTF(D&Qd`!(8JvIh7E2|zp?&hr&<)6*e~MHD?YClecE_Z@vAZ*s*Bs}#p`UEKZ{*1I z{o(&x0vRiJ_0X1V+HPH%og6jt`b9n0&sz49Q`9oL+D~fw52<&k*dG3*o^kg$ zw`>xs7f{RTp0So~NRo=6l`YiDgzh+6PT!X)7X7xPbE~u!KBcl=^8Rlc7WD?^f0_S{ z8bgAI;r-uH*5SN*Ex`OQ^S@DJNIZ9#|BbQ^=dlIY0&x~#{+IdRxbd;pGQkZp|I7Su zcnpd672f|HwhA9N%K}fl=X0k!$=T!h*swaF&W=qUJfKdGjE@Y>jBE_ZIp3dRQc1y& zBk^{XRHq<VbsJ+W_7sC>{U`}y@)$pH#4VGmg#DSsoNzAB?Py6jy1Ce2vs+;sN4G%6Y$0x(oD>0 zptQ@A)r!UHLh$1mC*-#Z1Gu`Y&FA$v+0K(r(a?%UoJ`U!8pRWL#fsP%IF=bFve`So zdBZJe&QhFUw{J8WQE_5jZ~>;}ja+P_o%0yau9J;Zw8?AFu`)|~tXcgN$SlGWnVyeR z*a?M2BO5E@D?6dvjuCIREne@ui&@CU4j|udu2F~|XPbzj$Edb&GsK=nZrR9Y^@TW@ zB;3F13r?Jf*BmAxs-35A5G|XvAseS~V=z48o9(8vwC3sYS6vx~cl=F4%BtKXT_i)Z zB5}=mLMAL9C)DAdz^xl6;n}nhOtQp0QhZanwjSGFaD{Rh6tS%)b6VaqVy}(=qz(V4 z2X%R1{*U>;@-aba@jlJ`UzBw?k1fC!h_e9mf6V{IjgPgKG5`0$@_xMjbFCwn>wTCk za5(W?3;P-UFkMkT7q)=B1qA=!Hx>Lo6HNek#JxwqcZ=+D{=b6!E+N0FCdB_ovHlhG zRGK6Rc(;C^1`8%Vd*pn<}$c@)I7?b0}Tl z_fHiqJC@#t&|o)Z2{*08*Ne6#}>GJEFk#*ZNwt2lJ*|= zpQNmE-akUFx1?%_?@z3VRsK=Te}twrFZPe@0$qfNMku>Uy#MBD-3fUwiP;b!T&^UP zZL3= zKYir?>Db83u~i39>5<__9`Yz%a`0Y04^%;Z7qNVvLwK=`q+~vb8m_dpqwkWcWe%X) zQTfkp9a(bXqaLWFrqT|POSA_aqb^lwnu>U)D9&9~m|BHTtB~g@>2>rx{}Zwr&;JO| zWjy~=ay#+-kN+47-77r*vtH+&=yS84!1F)#SdZ(n1=s?OvH;Kj@cd7s#!^@h&;RiJ zPk20tW1jyJmf-mRVFAJaZBRB4Lq<)CwaIkstpO)K566ks^~h8bMfTD%Fy{Yc9Kv7r zE5*aipMT8!Uz~ewUjJR{u%wz-bLRh;|BLt8&-@?rfAk^}AH@4Kum6s>6PL0D*aDYY zfcZb>|1KRB_2)4E$NXQ^C=%~0?EfXoI-J*N3nY{_WdoF6xZwSMVk+;9=Cd<-Yd*{Z zCRR$Qzr@NaSBMg^u#S4h{Ga3xLNfm+7JBjeztEZ{UjHYC1@nK*|1tkp>Q`aC@_ps~ zs`nj**ZhKj#0+#{{LB|BJE;=QYa$T}o@9`WnP- zO5(xJlx4cMl^;k^0lkYSk-JIc4&ztEmP83Q;jZjRYpzB=V5CkMj+QraWWFPywkWv{CELgHc|E7))iKj??d43k z=9f{vW5~9()9SFbY-SyG!~)NRdN6?q;ay@7~v z!pQ3C{Au+u-L`~|vlj)%O#PIWaaDMDUCytb-3t)d>1!A>PvGRUejDMgeibsCeVHTh zhq{orP73{BL(x{hXvWntOKNIS&n$`37g4&!FMR~N-%4ukL|NygrB3Jt%RVh?+>G2^ zla`sm6*SCcZ9WgP!Y;*de|AG?yXbA&aNsS{2$#Qv^1ddBO_DopWWt{pYD$byAJDlof^z5=7QJ$oJ3DZ1`k&@TNNY6NhcXv)08QXHK z1y@b&-FvBJNtU?u_F2u%EbbQv2gJcaad3yP^%ve965ch<{2%jw7u`UIh+*FU%lm&X z8$Y%5bgL)O6F(}j|JNw1a2{KLEzm3rF#pH=Uo*xQ^MB0$MU5-w|DvqIdCjswTCtQ2 z{0|o3eb9v8N2kVyde`88@F{8|@PAlibCKfWdqLEe`9bUXsi z8x;AzOtHB8=>tO}2kss^m>SrB_voDi`}d~@2Zl!X4-X6u4h^R69UQ$kJ#yDGigH`f z9(7Dy^#9=CkAUdPUtyaU0A(E*rzhx?h(#Z(8fOBNFPLGe**J9GG&@vooId1Xf=1~; z;Y$!Iotj|&pZR|`&J_ax{+pQO;NO44*B$&CV|WV8-`3B+|Ayyl`MTQ6C5^7v6{6o1 vE`AR2@4wY$Uarg*U<*iEfcbys|0Rb?O=8UdGyfkoV3_}pvI^%l%L4xci{+UZ diff --git a/win32/Norm.sln b/win32/Norm.sln deleted file mode 100644 index ceb58db..0000000 --- a/win32/Norm.sln +++ /dev/null @@ -1,68 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NormLib", "NormLib.vcproj", "{D7B0023C-8798-4918-8DA0-05C9054D70B9}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Protokit", "..\protolib\win32\Protokit.vcproj", "{50D1D52B-702A-49FE-B490-9104FC72598D}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "norm", "norm\norm.vcproj", "{6E1308A6-D40F-489E-A4F1-40D859380D64}" - ProjectSection(ProjectDependencies) = postProject - {50D1D52B-702A-49FE-B490-9104FC72598D} = {50D1D52B-702A-49FE-B490-9104FC72598D} - {D7B0023C-8798-4918-8DA0-05C9054D70B9} = {D7B0023C-8798-4918-8DA0-05C9054D70B9} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "normTest", "normTest.vcproj", "{32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}" - ProjectSection(ProjectDependencies) = postProject - {50D1D52B-702A-49FE-B490-9104FC72598D} = {50D1D52B-702A-49FE-B490-9104FC72598D} - {182006F3-188F-466E-89FE-8421C0478691} = {182006F3-188F-466E-89FE-8421C0478691} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NormDll", "NormDll.vcproj", "{182006F3-188F-466E-89FE-8421C0478691}" - ProjectSection(ProjectDependencies) = postProject - {50D1D52B-702A-49FE-B490-9104FC72598D} = {50D1D52B-702A-49FE-B490-9104FC72598D} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "npc", "npc.vcproj", "{32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}" - ProjectSection(ProjectDependencies) = postProject - {50D1D52B-702A-49FE-B490-9104FC72598D} = {50D1D52B-702A-49FE-B490-9104FC72598D} - {D7B0023C-8798-4918-8DA0-05C9054D70B9} = {D7B0023C-8798-4918-8DA0-05C9054D70B9} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - Release = Release - EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Debug.ActiveCfg = Debug|Win32 - {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Debug.Build.0 = Debug|Win32 - {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Release.ActiveCfg = Release|Win32 - {D7B0023C-8798-4918-8DA0-05C9054D70B9}.Release.Build.0 = Release|Win32 - {50D1D52B-702A-49FE-B490-9104FC72598D}.Debug.ActiveCfg = Debug|Win32 - {50D1D52B-702A-49FE-B490-9104FC72598D}.Debug.Build.0 = Debug|Win32 - {50D1D52B-702A-49FE-B490-9104FC72598D}.Release.ActiveCfg = Release|Win32 - {50D1D52B-702A-49FE-B490-9104FC72598D}.Release.Build.0 = Release|Win32 - {6E1308A6-D40F-489E-A4F1-40D859380D64}.Debug.ActiveCfg = Debug|Win32 - {6E1308A6-D40F-489E-A4F1-40D859380D64}.Debug.Build.0 = Debug|Win32 - {6E1308A6-D40F-489E-A4F1-40D859380D64}.Release.ActiveCfg = Release|Win32 - {6E1308A6-D40F-489E-A4F1-40D859380D64}.Release.Build.0 = Release|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug.ActiveCfg = Debug|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug.Build.0 = Debug|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release.ActiveCfg = Release|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release.Build.0 = Release|Win32 - {182006F3-188F-466E-89FE-8421C0478691}.Debug.ActiveCfg = Debug|Win32 - {182006F3-188F-466E-89FE-8421C0478691}.Debug.Build.0 = Debug|Win32 - {182006F3-188F-466E-89FE-8421C0478691}.Release.ActiveCfg = Release|Win32 - {182006F3-188F-466E-89FE-8421C0478691}.Release.Build.0 = Release|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug.ActiveCfg = Debug|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Debug.Build.0 = Debug|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release.ActiveCfg = Release|Win32 - {32CF83E0-D2E0-4E43-8F7B-72BD0AB64647}.Release.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal diff --git a/win32/Norm.suo b/win32/Norm.suo deleted file mode 100644 index a374c82242e920d2899e6f407ae91f74dbe52cb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20992 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/win32/NormLib.dsp b/win32/NormLib.dsp deleted file mode 100644 index f8d1afa..0000000 --- a/win32/NormLib.dsp +++ /dev/null @@ -1,216 +0,0 @@ -# Microsoft Developer Studio Project File - Name="NormLib" - Package Owner=<4> -# Microsoft Developer Studio Generated Build File, Format Version 6.00 -# ** DO NOT EDIT ** - -# TARGTYPE "Win32 (x86) Static Library" 0x0104 - -CFG=NormLib - Win32 Debug -!MESSAGE This is not a valid makefile. To build this project using NMAKE, -!MESSAGE use the Export Makefile command and run -!MESSAGE -!MESSAGE NMAKE /f "NormLib.mak". -!MESSAGE -!MESSAGE You can specify a configuration when running NMAKE -!MESSAGE by defining the macro CFG on the command line. For example: -!MESSAGE -!MESSAGE NMAKE /f "NormLib.mak" CFG="NormLib - Win32 Debug" -!MESSAGE -!MESSAGE Possible choices for configuration are: -!MESSAGE -!MESSAGE "NormLib - Win32 Release" (based on "Win32 (x86) Static Library") -!MESSAGE "NormLib - Win32 Debug" (based on "Win32 (x86) Static Library") -!MESSAGE - -# Begin Project -# PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "" -# PROP Scc_LocalPath "" -CPP=cl.exe -RSC=rc.exe - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 0 -# PROP BASE Output_Dir "Release" -# PROP BASE Intermediate_Dir "Release" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 0 -# PROP Output_Dir "Release" -# PROP Intermediate_Dir "Release" -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c -# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\common" /I "..\protolib\common" /I "..\protolib\win32" /D "NDEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_MBCS" /D "_LIB" /YX /FD /c -# ADD BASE RSC /l 0x409 /d "NDEBUG" -# ADD RSC /l 0x409 /d "NDEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LIB32=link.exe -lib -# ADD BASE LIB32 /nologo -# ADD LIB32 /nologo - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 1 -# PROP BASE Output_Dir "Debug" -# PROP BASE Intermediate_Dir "Debug" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 1 -# PROP Output_Dir "Debug" -# PROP Intermediate_Dir "Debug" -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c -# ADD CPP /nologo /MDd /W3 /GX /Od /I "..\common" /I "..\protolib\common" /I "..\protolib\win32" /D "_DEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c -# ADD BASE RSC /l 0x409 /d "_DEBUG" -# ADD RSC /l 0x409 /d "_DEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LIB32=link.exe -lib -# ADD BASE LIB32 /nologo -# ADD LIB32 /nologo - -!ENDIF - -# Begin Target - -# Name "NormLib - Win32 Release" -# Name "NormLib - Win32 Debug" -# Begin Group "Source Files" - -# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" -# Begin Source File - -SOURCE=..\common\galois.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normApi.cpp -# End Source File -# Begin Source File - -SOURCE=..\common\normEncoder.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normFile.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normMessage.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normNode.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normObject.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normSegment.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# Begin Source File - -SOURCE=..\common\normSession.cpp - -!IF "$(CFG)" == "NormLib - Win32 Release" - -# ADD CPP /MD /vmg /I "..\win32" - -!ELSEIF "$(CFG)" == "NormLib - Win32 Debug" - -# ADD CPP /MDd /vmg /I "..\win32" - -!ENDIF - -# End Source File -# End Group -# Begin Group "Header Files" - -# PROP Default_Filter "h;hpp;hxx;hm;inl" -# End Group -# End Target -# End Project diff --git a/win32/NormLib.vcproj b/win32/NormLib.vcproj deleted file mode 100644 index 9a1578b..0000000 --- a/win32/NormLib.vcproj +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/win32/norm/norm.dsp b/win32/norm/norm.dsp deleted file mode 100644 index 245a5b5..0000000 --- a/win32/norm/norm.dsp +++ /dev/null @@ -1,115 +0,0 @@ -# Microsoft Developer Studio Project File - Name="norm" - Package Owner=<4> -# Microsoft Developer Studio Generated Build File, Format Version 6.00 -# ** DO NOT EDIT ** - -# TARGTYPE "Win32 (x86) Application" 0x0101 - -CFG=norm - Win32 Debug -!MESSAGE This is not a valid makefile. To build this project using NMAKE, -!MESSAGE use the Export Makefile command and run -!MESSAGE -!MESSAGE NMAKE /f "norm.mak". -!MESSAGE -!MESSAGE You can specify a configuration when running NMAKE -!MESSAGE by defining the macro CFG on the command line. For example: -!MESSAGE -!MESSAGE NMAKE /f "norm.mak" CFG="norm - Win32 Debug" -!MESSAGE -!MESSAGE Possible choices for configuration are: -!MESSAGE -!MESSAGE "norm - Win32 Release" (based on "Win32 (x86) Application") -!MESSAGE "norm - Win32 Debug" (based on "Win32 (x86) Application") -!MESSAGE - -# Begin Project -# PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "" -# PROP Scc_LocalPath "" -CPP=cl.exe -MTL=midl.exe -RSC=rc.exe - -!IF "$(CFG)" == "norm - Win32 Release" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 0 -# PROP BASE Output_Dir "Release" -# PROP BASE Intermediate_Dir "Release" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 0 -# PROP Output_Dir "Release" -# PROP Intermediate_Dir "Release" -# PROP Ignore_Export_Lib 0 -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MD /W3 /vmg /GX /O2 /I "..\..\common" /I "..\..\protolib\common" /I "..\..\protolib\win32" /D "NDEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /FR /YX /FD /c -# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 -# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 -# ADD BASE RSC /l 0x409 /d "NDEBUG" -# ADD RSC /l 0x409 /d "NDEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 -# ADD LINK32 iphlpapi.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 - -!ELSEIF "$(CFG)" == "norm - Win32 Debug" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 1 -# PROP BASE Output_Dir "Debug" -# PROP BASE Intermediate_Dir "Debug" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 1 -# PROP Output_Dir "Debug" -# PROP Intermediate_Dir "Debug" -# PROP Ignore_Export_Lib 0 -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MDd /W3 /vmg /GX /Od /I "..\..\common" /I "..\..\protolib\common" /I "..\..\protolib\win32" /D "_DEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c -# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 -# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 -# ADD BASE RSC /l 0x409 /d "_DEBUG" -# ADD RSC /l 0x409 /d "_DEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept -# ADD LINK32 iphlpapi.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept - -!ENDIF - -# Begin Target - -# Name "norm - Win32 Release" -# Name "norm - Win32 Debug" -# Begin Group "Source Files" - -# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" -# Begin Source File - -SOURCE=..\..\common\normApp.cpp -# End Source File -# Begin Source File - -SOURCE=..\..\common\normPostProcess.cpp -# End Source File -# Begin Source File - -SOURCE=..\win32PostProcess.cpp -# End Source File -# End Group -# Begin Group "Header Files" - -# PROP Default_Filter "h;hpp;hxx;hm;inl" -# End Group -# Begin Group "Resource Files" - -# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" -# End Group -# End Target -# End Project diff --git a/win32/norm/norm.vcproj b/win32/norm/norm.vcproj deleted file mode 100644 index 0e28ee8..0000000 --- a/win32/norm/norm.vcproj +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/win32/normTest.dsp b/win32/normTest.dsp deleted file mode 100644 index 684009b..0000000 --- a/win32/normTest.dsp +++ /dev/null @@ -1,103 +0,0 @@ -# Microsoft Developer Studio Project File - Name="normTest" - Package Owner=<4> -# Microsoft Developer Studio Generated Build File, Format Version 6.00 -# ** DO NOT EDIT ** - -# TARGTYPE "Win32 (x86) Console Application" 0x0103 - -CFG=normTest - Win32 Debug -!MESSAGE This is not a valid makefile. To build this project using NMAKE, -!MESSAGE use the Export Makefile command and run -!MESSAGE -!MESSAGE NMAKE /f "normTest.mak". -!MESSAGE -!MESSAGE You can specify a configuration when running NMAKE -!MESSAGE by defining the macro CFG on the command line. For example: -!MESSAGE -!MESSAGE NMAKE /f "normTest.mak" CFG="normTest - Win32 Debug" -!MESSAGE -!MESSAGE Possible choices for configuration are: -!MESSAGE -!MESSAGE "normTest - Win32 Release" (based on "Win32 (x86) Console Application") -!MESSAGE "normTest - Win32 Debug" (based on "Win32 (x86) Console Application") -!MESSAGE - -# Begin Project -# PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "" -# PROP Scc_LocalPath "" -CPP=cl.exe -RSC=rc.exe - -!IF "$(CFG)" == "normTest - Win32 Release" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 0 -# PROP BASE Output_Dir "Release" -# PROP BASE Intermediate_Dir "Release" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 0 -# PROP Output_Dir "Release" -# PROP Intermediate_Dir "Release" -# PROP Ignore_Export_Lib 0 -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\common" /I "..\protolib\common" /I "..\protolib\win32" /D "NDEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# SUBTRACT CPP /Z -# ADD BASE RSC /l 0x409 /d "NDEBUG" -# ADD RSC /l 0x409 /d "NDEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 -# ADD LINK32 iphlpapi.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 - -!ELSEIF "$(CFG)" == "normTest - Win32 Debug" - -# PROP BASE Use_MFC 0 -# PROP BASE Use_Debug_Libraries 1 -# PROP BASE Output_Dir "Debug" -# PROP BASE Intermediate_Dir "Debug" -# PROP BASE Target_Dir "" -# PROP Use_MFC 0 -# PROP Use_Debug_Libraries 1 -# PROP Output_Dir "Debug" -# PROP Intermediate_Dir "Debug" -# PROP Ignore_Export_Lib 0 -# PROP Target_Dir "" -# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /MDd /W3 /GX /Od /I "..\common" /I "..\protolib\common" /I "..\protolib\win32" /D "_DEBUG" /D "PROTO_DEBUG" /D "HAVE_IPV6" /D "HAVE_ASSERT" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD BASE RSC /l 0x409 /d "_DEBUG" -# ADD RSC /l 0x409 /d "_DEBUG" -BSC32=bscmake.exe -# ADD BASE BSC32 /nologo -# ADD BSC32 /nologo -LINK32=link.exe -# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept -# ADD LINK32 iphlpapi.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept - -!ENDIF - -# Begin Target - -# Name "normTest - Win32 Release" -# Name "normTest - Win32 Debug" -# Begin Group "Source Files" - -# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" -# Begin Source File - -SOURCE=..\common\normTest.cpp -# End Source File -# End Group -# Begin Group "Header Files" - -# PROP Default_Filter "h;hpp;hxx;hm;inl" -# End Group -# Begin Group "Resource Files" - -# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" -# End Group -# End Target -# End Project diff --git a/win32/normTest.vcproj b/win32/normTest.vcproj deleted file mode 100644 index 199e922..0000000 --- a/win32/normTest.vcproj +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/win32/npc.vcproj b/win32/npc.vcproj deleted file mode 100644 index bba8790..0000000 --- a/win32/npc.vcproj +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/wscript b/wscript new file mode 100644 index 0000000..29fc37b --- /dev/null +++ b/wscript @@ -0,0 +1,158 @@ +#!/usr/bin/env python +''' +wscript - Waf build script for NORM +See http://waf.googlecode.com/ for more information. + +In order to use different build directories (for example, a release and a debug +build), use the -o (--out) flag when configuring. For example: + + ./waf configure -o build-debug --debug + ./waf + + ./waf configure -o build-release + ./waf + +''' + +import platform + +import waflib + +# So you don't need to do ./waf configure if you are just using the defaults +waflib.Configure.autoconfig = True + +# Top-level project directory +top = '.' +# Directory where build files are placed +out = 'build' + +# System waf is running on: linux, darwin (Mac OSX), freebsd, windows, etc. +system = platform.system().lower() + +def options(ctx): + ctx.recurse('protolib') + +def configure(ctx): + ctx.recurse('protolib') + + # Use this USE variable to add flags to NORM's compilation + ctx.env.USE_BUILD_NORM += ['BUILD_NORM', 'protolib'] + + if system in ('linux', 'darwin', 'freebsd'): + ctx.env.DEFINES_BUILD_NORM += ['ECN_SUPPORT'] + + if system == 'windows': + ctx.env.DEFINES_BUILD_NORM += ['NORM_USE_DLL'] + +def build(ctx): + ctx.recurse('protolib') + + ctx.shlib( + target = 'norm', + includes = ['include'], + export_includes = ['include'], + use = ctx.env.USE_BUILD_NORM, + source = ['src/common/{0}.cpp'.format(x) for x in [ + 'galois', + 'normApi', + 'normEncoder', + 'normEncoderMDP', + 'normEncoderRS16', + 'normEncoderRS8', + 'normFile', + 'normMessage', + 'normNode', + 'normObject', + 'normSegment', + 'normSession', + ]], + ) + + if ctx.env.BUILD_PYTHON: + ctx( + features='py', + source=ctx.path.ant_glob('src/pynorm/**/*.py'), + install_from='src', + ) + + if ctx.env.BUILD_JAVA: + ctx.shlib( + target = 'mil_navy_nrl_norm', + use = ['norm', 'JAVA'], + source = ['src/java/jni/{0}.cpp'.format(x) for x in [ + 'normJni', + 'normInstanceJni', + 'normSessionJni', + 'normObjectJni', + 'normDataJni', + 'normFileJni', + 'normStreamJni', + 'normEventJni', + 'normNodeJni', + ]], + ) + ctx( + features = ['javac', 'jar'], + srcdir = 'src/java/src', + outdir = 'src/java/src', + basedir = 'src/java/src', + destfile = 'norm.jar', + ) + + normapp = ctx.program( + target = 'norm', + # Need to explicitly set a different name, because the library is also + # named "norm" + name = 'normapp', + use = ['protolib', 'norm'], + source = ['src/common/{0}'.format(x) for x in [ + 'normPostProcess.cpp', + 'normApp.cpp', + ]], + # Disabled by default + posted = True, + ) + + if system in ('linux', 'darwin', 'freebsd'): + normapp.source.append('src/unix/unixPostProcess.cpp') + + if system == 'windows': + normapp.source.append('src/win32/win32PostProcess.cpp') + + for example in ( + 'normDataExample', + 'normDataRecv', + 'normDataSend', + 'normFileRecv', + 'normFileSend', + 'normStreamRecv', + 'normStreamSend', + ): + _make_simple_example(ctx, example) + + for prog in ( + 'fecTest', + 'normPrecode', + 'normTest', + 'normThreadTest', + 'raft', + ): + _make_simple_example(ctx, prog, 'src/common') + + # Enable example targets specified on the command line + ctx._parse_targets() + +def _make_simple_example(ctx, name, path='examples'): + '''Makes a task from a single source file in the examples directory. + + These tasks are not built by default. Use the --targets flag. + ''' + ctx.program( + target = name, + use = ['protolib', 'norm'], + source = ['{0}/{1}.cpp'.format(path, name)], + # Don't build examples by default + posted = True, + # Don't install examples + install_path = False, + )

NORM Developer's Guide (version 1.4b4)

Abstract

This document describes an application programming interface (API) for the Nack-Oriented Reliable Multicast (NORM) protocol implementation developed by the Protocol Engineering and Advance Networking (PROTEAN) Research Group of the United States Naval Research Laboratory (NRL). The NORM protocol provides general purpose reliable data transport for applications wishing to use Internet Protocol (IP) Multicast services for group data delivery. NORM can also support unicast (point-to-point) data communication and may be used for such when deemed appropriate. The current NORM protocol specification is given in the Internet Engineering Task Force (IETF) RFC 3940. This document is currently a reference guide to the NORM API of the NRL reference implementation. More tutorial material may be include in a future version of this document or a separate developer's tutorial may be created at a later date.


1. Background
2. Overview
2.1. API Initialization
2.2. Session Creation and Control
2.3. Data Transport
2.3.1. Data Transmission
2.3.2. Data Reception
2.4. API Event Notification
3. Build Notes
3.1. Unix Platforms
3.2. Win32/WiNCE Platforms
4. API Reference
4.1. API Variable Types and Constants
4.1.1. NormInstanceHandle
4.1.2. NormSessionHandle
4.1.3. NormSessionId
4.1.4. NormNodeHandle
4.1.5. NormNodeId
4.1.6. NormObjectHandle
4.1.7. NormObjectType
4.1.8. NormSize
4.1.9. NormObjectTransportId
4.1.10. NormEventType
4.1.11. NormEvent
4.1.12. NormDescriptor
4.1.13. NormFlushMode
4.1.14. NormProbingMode
4.1.15. NormSyncPolicy
4.1.16. NormNackingMode
4.1.17. NormRepairBoundary
4.1.18. NormAckingStatus
4.2. API Initialization and Operation
4.2.1. NormCreateInstance()
4.2.2. NormDestroyInstance()
4.2.3. NormStopInstance()
4.2.4. NormRestartInstance()
4.2.5. NormSetCacheDirectory()
4.2.6. NormGetNextEvent()
4.2.7. NormGetDescriptor()
4.3. Session Creation and Control Functions
4.3.1. NormCreateSession()
4.3.2. NormDestroySession()
4.3.3. NormSetUserData()
4.3.4. NormGetUserData()
4.3.5. NormGetLocalNodeId()
4.3.6. NormSetTxPort()
4.3.7. NormSetTxOnly()
4.3.8. NormSetRxPortReuse()
4.3.9. NormSetMulticastInterface()
4.3.10. NormSetTTL()
4.3.11. NormSetTOS()
4.3.12. NormSetLoopback()
4.3.13. NormSetFragmentation()
4.4. NORM Sender Functions
4.4.1. NormStartSender()
4.4.2. NormStopSender()
4.4.3. NormSetTxRate()
4.4.4. NormGetTxRate()
4.4.5. NormSetTxSocketBuffer()
4.4.6. NormSetFlowControl()
4.4.7. NormSetCongestionControl()
4.4.8. NormSetTxRateBounds()
4.4.9. NormSetTxCacheBounds()
4.4.10. NormSetAutoParity()
4.4.11. NormGetGrttEstimate()
4.4.12. NormSetGrttEstimate()
4.4.13. NormSetGrttMax()
4.4.14. NormSetGrttProbingMode()
4.4.15. NormSetGrttProbingInterval()
4.4.16. NormSetBackoffFactor()
4.4.17. NormSetGroupSize()
4.4.18. NormSetTxRobustFactor()
4.4.19. NormFileEnqueue()
4.4.20. NormDataEnqueue()
4.4.21. NormRequeueObject()
4.4.22. NormStreamOpen()
4.4.23. NormStreamClose()
4.4.24. NormStreamWrite()
4.4.25. NormStreamFlush()
4.4.26. NormStreamSetAutoFlush()
4.4.27. NormStreamSetPushEnable()
4.4.28. NormStreamHasVacancy()
4.4.29. NormStreamMarkEom()
4.4.30. NormSetWatermark()
4.4.31. NormCancelWatermark()
4.4.32. NormAddAckingNode()
4.4.33. NormRemoveAckingNode()
4.4.34. NormGetNextAckingNode()
4.4.35. NormGetAckingStatus()
4.4.36. NormSendCommand()
4.4.37. NormCancelCommand()
4.5. NORM Receiver Functions
4.5.1. NormStartReceiver()
4.5.2. NormStopReceiver()
4.5.3. NormSetRxCacheLimit()
4.5.4. NormSetRxSocketBuffer()
4.5.5. NormSetSilentReceiver()
4.5.6. NormSetDefaultUnicastNack()
4.5.7. NormNodeSetUnicastNack()
4.5.8. NormSetDefaultSyncPolicy()
4.5.9. NormSetDefaultNackingMode()
4.5.10. NormNodeSetNackingMode()
4.5.11. NormObjectSetNackingMode()
4.5.12. NormSetDefaultRepairBoundary()
4.5.13. NormNodeSetRepairBoundary()
4.5.14. NormSetDefaultRxRobustFactor()
4.5.15. NormNodeSetRxRobustFactor()
4.5.16. NormStreamRead()
4.5.17. NormStreamSeekMsgStart()
4.5.18. NormStreamGetReadOffset()
4.6. NORM Object Functions
4.6.1. NormObjectGetType()
4.6.2. NormObjectHasInfo()
4.6.3. NormObjectGetInfoLength()
4.6.4. NormObjectGetInfo()
4.6.5. NormObjectGetSize()
4.6.6. NormObjectGetBytesPending()
4.6.7. NormObjectCancel()
4.6.8. NormObjectRetain()
4.6.9. NormObjectRelease()
4.6.10. NormFileGetName()
4.6.11. NormFileRename()
4.6.12. NormDataAccessData()
4.6.13. NormDataDetachData()
4.6.14. NormObjectGetSender()
4.7. NORM Node Functions
4.7.1. NormNodeGetId()
4.7.2. NormNodeGetAddress()
4.7.3. NormNodeGetGrtt()
4.7.4. NormNodeGetCommand()
4.7.5. NormNodeFreeBuffers()
4.7.6. NormNodeDelete()
4.7.7. NormNodeRetain()
4.7.8. NormNodeRelease()
4.8. NORM Debugging Functions
4.8.1. NormSetDebugLevel()
4.8.2. NormOpenDebugLog()
4.8.3. NormCloseDebugLog()
4.8.4. NormOpenDebugPipe()
4.8.5. NormCloseDebugPipe()

1. Background

This document describes an application programming interface (API) for the Nack-Oriented Reliable Multicast (NORM) protocol implementation developed by the Protocol Engineering and Advance Networking (PROTEAN) Research Group of the United States Naval Research Laboratory (NRL). The NORM protocol provides general purpose reliable data transport for applications wishing to use Internet Protocol (IP) Multicast services for group data delivery. NORM can also support unicast (point-to-point) data communication and may be used for such when deemed appropriate. The current NORM protocol specification is given in the Internet Engineering Task Force (IETF) RFC 5740.

The NORM protocol is designed to provide end-to-end reliable transport of bulk data objects or streams over generic IP multicast routing and forwarding services. NORM uses a selective, negative acknowledgement (NACK) mechanism for transport reliability and offers additional protocol mechanisms to conduct reliable multicast sessions with limited "a priori" coordination among senders and receivers. A congestion control scheme is specified to allow the NORM protocol to fairly share available network bandwidth with other transport protocols such as Transmission Control Protocol (TCP). It is capable of operating with both reciprocal multicast routing among senders and receivers and with asymmetric connectivity (possibly a unicast return path) from the senders to receivers. The protocol offers a number of features to allow different types of applications or possibly other higher-level transport protocols to utilize its service in different ways. The protocol leverages the use of FEC-based repair and other proven reliable multicast transport techniques in its design.

The NRL NORM library attempts to provide a general useful capability for development of reliable multicast applications for bulk file or other data delivery as well as support of stream-based transport with possible real-time delivery requirements. The API allows access to many NORM protocol parameters and control functions to tailor performance for specific applications. While default parameters, where provided, can be useful to a potential wide range of requirements, the many different possible group communication paradigms dictate different needs for different applications. Even with NORM, the developer should have a thorough understanding of the specific application's group communication needs.

2. Overview

The NORM API has been designed to provide simple, straightforward access to and control of NORM protocol state and functions. Functions are provided to create and initialize instances of the NORM API and associated transport sessions (NormSessions). Subsequently, NORM data transmission (NormSender) operation can be activated and the application can queue various types of data (NormObjects) for reliable transport. Additionally or alternatively, NORM reception (NormReceiver) operation can also be enabled on a per-session basis and the protocol implementation alerts the application of receive events.

By default, the NORM API will create an operating system thread in which the NORM protocol engine runs. This allows user application code and the underlying NORM code to execute somewhat independently of one another. The NORM protocol thread notifies the application of various protocol events through a thread-safe event dispatching mechanism and API calls are provided to allow the application to control NORM operation. (Note: API mechanisms for lower-level, non-threaded control and execution of the NORM protocol engine code may also be provided in the future.)

The NORM API operation can be roughly summarized with the following categories of functions:

  1. API Initialization

  2. Session Creation and Control

  3. Data Transport

  4. API Event Notification

Note the order of these categories roughly reflects the order of function calls required to use NORM in an application. The first step is to create and initialize, as needed, at least one instance of the NORM API. Then one or more NORM transport sessions (where a "session" corresponds to data exchanges on a given multicast group (or unicast address) and host port number) may be created and controlled. Applications may participate as senders and/or receivers within a NORM session. NORM senders transmit data to the session destination address (usually an IP multicast group) while receivers are notified of incoming data. The NORM API provides and event notification scheme to notify the application of significant sender and receiver events. There are also a number support functions provided for the application to control and monitor its participation within a NORM transport session.

2.1. API Initialization

The NORM API requires that an application explicitly create at least one instance of the NORM protocol engine that is subsequently used as a conduit for further NORM API calls. By default, the NORM protocol engine runs in its own operating system thread and interacts with the application in a thread-safe manner through the API calls and event dispatching mechanism.

In general, only a single thread should access the NormGetNextEvent() API call for a given NormInstance. This function serves as the conduit for delivering NORM protocol engine events to the application. A NORM application can be designed to be single-threaded, even with multiple active NormSessions, but also multiple API instances can be created (see NormCreateInstance()) as needed for applications with specific requirements for accessing and controlling participation in multiple NormSessions from separate operating system multiple threads. Or, alternatively, a single NormInstance could be used, with a "master thread" serving as an intermediary between the NormGetNextEvent() function, demultiplexing and dispatching events as appropriate to other "child threads" that are created to handle "per-NormSession" input/output. The advantage of this alternative approach is that the end result would be one NORM protocol engine thread plus one "master thread" plus one "child thread" per NormSession instead of two threads (protocol engine plus application thread) per NormSession if such multi-threaded operation is needed by the application.

2.2. Session Creation and Control

Once an API instance has been successfully created, the application may then create NORM transport session instances as needed. The application can participate in each session as a sender and/or receiver of data. If an application is participating as a sender, it may enqueue data transport objects for transmission. The control of transmission is largely left to the senders and API calls are provided to control transmission rate, FEC parameters, etc. Applications participating as receivers will be notified via the NORM API's event dispatching mechanism of pending and completed reliable reception of data along with other significant events. Additionally, API controls for some optional NORM protocol mechanisms, such as positive acknowledgment collection, are also provided.

Note when multiple senders are involved, receivers allocate system resources (buffer space) for each active sender. With a very large number of concurrently active senders, this may translate to significant memory allocation on receiver nodes. Currently, the API allows the application to control how much buffer space is allocated for each active sender (NOTE: In the future, API functions may be provided limit the number of active senders monitored and/or provide the application with finer control over receive buffer allocation, perhaps on a per sender basis).

2.3. Data Transport

The NORM protocol supports transport of three basic types of data content. These include the types NORM_OBJECT_FILE and NORM_OBJECT_DATA which represent predetermined, fixed-size application data content. The only differentiation with respect to these two types is the implicit "hint" to the receiver to use non-volatile (i.e. file system) storage or memory. This "hint" lets the receiver allocate appropriate storage space with no other information on the incoming data. The NORM implementation reads/writes data for the NORM_OBJECT_FILE type directly from/to file storage, while application memory space is accessed for the NORM_OBJECT_DATA type. The third data content type, NORM_OBJECT_STREAM, represents unbounded, possibly persistent, streams of data content. Using this transport paradigm, traditional, byte-oriented streaming transport service (e.g. similar to that provided by a TCP socket) can be provided. Additionally, NORM has provisions for application-defined message-oriented transport where receivers can recover message boundaries without any "handshake" with the sender. Stream content is buffered by the NORM implementation for transmission/retransmission and as it is received.

2.3.1. Data Transmission

The behavior of data transport operation is largely placed in the control of the NORM sender(s). NORM senders controls their data transmission rate, forward error correction (FEC) encoding settings, and parameters controlling feedback from the receiver group. Multiple senders may operate in a session, each with independent transmission parameters. NORM receivers learn needed parameter values from fields in NORM message headers.

NORM transport "objects" (file, data, or stream) are queued for transmission by NORM senders. NORM senders may also cancel transmission of objects at any time. The NORM sender controls the transmission rate either manually (fixed transmission rate) or automatically when NORM congestion control operation is enabled. The NORM congestion control mechanism is designed to be "friendly" to other data flows on the network, fairly sharing available bandwidth.NormSetAutoParity()) to achieve reliable transfer) receive object transmission before any extensive repair process that may be required to satisfy other receivers with poor network connectivity. The repair boundary can also be set for individual remote senders using the NormNodeSetRepairBoundary() function.NORM_OBJECT_FILE objects. This function must be called before any file objects may be received and thus should be called before any calls to NormStartReceiver() are made. However, note that the cache directory may be changed even during active NORM reception. In this case, the new specified directory path will be used for subsequently-received files. Any files received before a directory path change will remain in the previous cache location. Note that the NormFileRename() function may be used to rename, and thus potentially move, received files after reception has begun.

By default, the NORM sender transmits application-enqueued data content, providing repair transmissions (usually in the form of FEC messages) only when requested by NACKs from the receivers. However, the application may also configure NORM to proactively send some amount of FEC content along with the original data content to create a "robust" transmission that, in some cases, may be reliably received without any NACKing activity. This can allow for some degree of reliable protocol operation even without receiver feedback available. NORM senders may also requeue (within the limits of "transmit cache" settings) objects for repeat transmission, and receivers may combine together multiple transmissions to reliably receive content. Additionally, hybrid proactive/reactive FEC repair operation is possible with the receiver NACK process as a "backup" for when network packet loss exceeds the repair capability of the proactive FEC settings.

The NRL NORM implementation also supports optional collection of positive acknowledgment from a subset of the receiver group at application-determined positions during data transmission. The NORM API allows the application to specify the receiver subset ("acking node list") and set "watermark" points for which positive acknowledgement is collected. This process can provide the application with explicit flow control for an application-determined critical set of receivers in the group.

For a NORM application to perform data transmission, it must first create a session using NormCreateSession() and make a call to NormStartSender() before sending actual user data. The functions NormFileEnqueue(), NormDataEnqueue(), and NormStreamWrite() are available for the application to pass data to the NORM protocol engine for transmission. Note that to use NormStreamWrite(), a "sender stream" must first be created using NormStreamOpen(). In the case of NormFileEnqueue() and NormDataEnqueue(), the NORM protocol engine directly accesses the application file or memory space to refer to the transmitted content and does not make its own copy of this data.

The calls to enqueue transport objects or write to a stream may be called at any time, but the NORM_TX_QUEUE_EMPTY and NORM_TX_QUEUE_VACANCY notification events (see NormGetNextEvent()) provide useful cues for when these functions may be successfully called. Typically, an application might catch both NORM_TX_QUEUE_EMPTY and NORM_TX_QUEUE_VACANCY event types as cues for enqueuing additional transport objects or writing to a stream. However, an application may choose to cue off of NORM_TX_QUEUE_EMPTY only if it wishes to provide the "freshest" data to NORM for transmission. The advantage of additionally using NORM_TX_QUEUE_VACANCY is that if the application uses this cue to fill up NORM transport object or stream buffers, it can keep the NORM stream busy sending data and realize the highest possible transmission rate when attempting very high speed communication (Otherwise, the NORM protocol engine may experience some "dead air time" waiting for the application thread to respond to a NORM_TX_QUEUE_EMPTY event). Note the sender application can control buffer depths as needed with the NormSetTxCacheBounds() and NormStreamOpen() calls. Additionally, it is possible for applications to configure the transmit object "cache" (see NormSetTxCacheBounds()) and use the NormRequeueObject() call (for objects that have not yet received a NORM_TX_OBJECT_PURGED notification) to effect a sort of "data carousel" operation with repeated transmission of the cached objects. The NORM_TX_OBJECT_SENT notification can be used a cue to properly control the "requeue" cycle(s).

The NORM implementation provides a form of timer-based flow control that limits how quickly sender applications may enqueue new objects or stream data for transmission. The NormSetFlowControl() call is provided to control this behavior, including the option to disable it. This timer-based mechanism is a type of "soft" flow control by allowing receivers "sufficient" time to request repair of pending data the sender has enqueued. A more explicit form of flow control using the optional "watermark flushing" mechanism is described below.

Another cue that can be leveraged by the sender application to determine when it is appropriate to enqueue (or write) additional data for transmission is the NORM_TX_WATERMARK_COMPLETED event. This event is posted when the flushing or explicit positive acknowledgment collection process has completed for a "watermark" point in transmission that was set by the sender (see NormSetWatermark() and NormAddAckingNode()). A list of NormNodeId values can be supplied from which explicit acknowledgement is expected and/or the NormNodeId NORM_NODE_NONE can be set (using NormAddAckingNode()) for completion of a NACK-based version of the watermark flushing procedure. This flushing process can be used as a flow control mechanism for NORM applications. Note this is distinct from NORM's congestion control mechanism that, while it provides network-friendly transmission rate control, does guarantee flow control to receiving nodes.NORM_NODE_NONE can be set (using NormAddAckingNode()) for completion of a NACK-based version of the watermark flushing procedure. This flushing process can be used as a flow control mechanism for NORM applications. Note this is distinct from NORM's congestion control mechanism that, while it provides network-friendly transmission rate control, does guarantee flow control to receiving nodes.

2.3.2. Data Reception

NORM receiver applications learn of active senders and their corresponding pending and completed data transfers, etc via the API event dispatching mechanism. By default, NORM receivers use NACK messages to request repair of transmitted content from the originating sender as needed to achieve reliable transfer. Some API functions are available to provide some additional control over the NACKing behavior, such as initially NACKing for NORM_INFO content only or even to the extent of disabling receiver feedback (silent receiver or emission-controlled (EMCON) operation) entirely. Otherwise, the parameters and operation of reliable data transmission are left to sender applications and receivers learn of sender parameters in NORM protocol message headers and are instructed by NORM_CMD messages from the sender(s).

With respect to the NORM API, the receiver application is informed of new senders and receive data objects via the the NORM_REMOTE_SENDER_NEW and NORM_RX_OBJECT_NEW notifications, respectfully. Additionally, object reception progress is indicated with the NORM_RX_OBJECT_UPDATED notification and this also serves as an indicator for the NORM_OBJECT_STREAM type that the receive application should make calls to NormStreamRead() to read newly received stream content. NORM sender status is also conveyed via the NORM_REMOTE_SENDER_ACTIVE and NORM_REMOTE_SENDER_INACTIVE notifications. For example, the receiver application may use the NORM_REMOTE_SENDER_INACTIVE as a cue to make calls to NormNodeFreeBuffers() and/or NormNodeDelete() to free memory resources allocated for buffering received content for the given sender. The amount of memory allocated per sender is set in the NormStartReceiver() call.

2.4. API Event Notification

An asynchronous event dispatching mechanism is provided to notify the application of significant NORM protocol events. The centerpiece of this is the NormGetNextEvent() function that can be used to retrieve the next NORM protocol engine event in the form of a NormEvent structure. This function will typically block until a NormEvent occurs. However, non-blocking operation may be achieved by using the NormGetDescriptor() call to get a NormDescriptor (file descriptor) value (Unix int or Win32 HANDLE) suitable for use in a asynchronous I/O monitoring functions such as the Unix select() or Win32 MsgWaitForMultipleObjects() system calls. The a NormDescriptor will be signaled when a NormEvent is available. For Win32 platforms, dispatching of a user-defined Windows message for NORM event notification is also planned for a future update to the NORM API.

3. Build Notes

To build applications that use the NORM library, a path to the "normApi.h" header file must be provided and the linker step needs to reference the NORM library file ("libnorm.a" for Unix platforms and "Norm.lib" for Win32 platforms). NORM also depends upon the NRL Protean Protocol Prototyping toolkit "Protokit" library (a.k.a "Protolib") (static library files "libProtokit.a" for Unix and "Protokit.lib" for Win32). Shared or dynamically-linked versions of these libraries may also be built from the NORM source code or provided. Depending upon the platform, some additional library dependencies may be required to support the needs of NORM and/or Protokit. These are described below.

The "makefiles" directory contains Unix Makefiles for various platforms the "win32" and "wince" sub-directories there contain Microsoft Visual C++ (VC++) and Embedded VC++ project files for building the NORM implementation. Additionally, a "waf" (Python-based build tool) build option is supported that can be used to build and install the NORM library code on the supported platforms. Finally, Python and Java bindings to the NORM API are included and "src/python" and "src/java" directories contain the code for these and the "makefiles/java" directory contains Makefiles to build the NORM Java JNI bindings. Note the "waf" tool can also be used to build the Java and Python bindings.

3.1. Unix Platforms

NORM has been built and tested on Linux (various architectures), MacOS (BSD), Solaris, and IRIX (SGI) platforms. The code should be readily portable to other Unix platforms.

To support IPv6 operation, the NORM and the Protokit library must be compiled with the "HAVE_IPV6" macro defined. This is default in the NORM and Protokit Makefiles for platforms that support IPv6. It is important that NORM and Protokit be built with this macro defined the same. With NORM, it is recommended that "large file support" options be enabled when possible.

The NORM API uses threading so that the NORM protocol engine may run independent of the application. Thus the "POSIX Threads" library must be included ("-pthread") in the linking step. MacOS/BSD also requires the addition of the "-lresolv" (resolver) library and Solaris requires the dynamic loader, network/socket, and resolver libraries ("-lnsl -lsocket -lresolv") to achieve successful compilation. The Makefiles in the NORM source code distribution are a reference for these requirements. Note that MacOS 9 and earlier are not supported.

Additionally, it is critical that the _FILE_OFFSET_BITS macro be consistently defined for the NORM library build and the application build using the library. The distributed NORM Makefiles have -D_FILE_OFFSET_BITS=64 set in the compilation to enable "large file support". Applications built using NORM should have the same compilation option set to operate correctly (The definition of the NormSize type in "normApi.h" depends upon this compilation flag).

3.2. Win32/WiNCE Platforms

NORM has been built using Microsoft's Visual C++ (6.0 and .NET) and Embedded VC++ 4.2 environments. In addition to proper macro definitions (e.g., HAVE_IPV6, etc) that are included in the respective "Protokit" and "NORM" project files, it is important that common code generation settings be used when building the NORM application. The NORM and Protokit projects are built with the "Multi-threading DLL" library usage set. The NORM API requires multi-threading support. This is a critical setting and numerous compiler and linker errors will result if this is not properly set for your application project.

NORM and Protokit also depend on the Winsock 2.0 ("ws2_32.lib" (or "ws2.lib" (WinCE)) and the IP Helper API ("iphlpapi.lib") libraries and these must be included in the project "Link" attributes.

An additional note is that a bug in VC++ 6.0 and earlier compilers (includes embedded VC++ 4.x compilers) prevent compilation of Protokit-based code with debugging capabilities enabled. However, this has been resolved in VC++ .NET and is hoped to be resolved in the future for the WinCE build tools.

Operation on Windows NT4 (and perhaps other older Windows operating systems) requires that the compile time macro WINVER=0x0400 defined. This is because the version of the IP Helper API library (iphlpapi.lib) used by Protolib (and hence NORM) for this system doesn't support some of the functions defined for this library. This may be related to IPv6 support issues so it may be possible that the Protolib build could be tweaked to provide a single binary executable suitable for IPv4 operation only across a large range of Windows platforms.

4. API Reference

This section provides a reference to the NORM API variable types, constants and functions.

4.1. API Variable Types and Constants

The NORM API defines and enumerates a number of supporting variable types and values which are used in different function calls. The variable types are described here.

4.1.1. NormInstanceHandle

The NormInstanceHandle type is returned when a NORM API instance is created (see NormCreateInstance()). This handle can be subsequently used for API calls which require reference to a specific NORM API instance. By default, each NORM API instance instantiated creates an operating system thread for protocol operation. Note that multiple NORM transport sessions may be created for a single API instance. In general, it is expected that applications will create a single NORM API instance, but some multi-threaded application designs may prefer multiple corresponding NORM API instances. The value NORM_INSTANCE_INVALID corresponds to an invalid API instance.

4.1.2. NormSessionHandle

The NormSessionHandle type is used to reference NORM transport sessions which have been created using the NormCreateSession() API call. Multiple NormSessionHandle values may be associated with a given NormInstanceHandle. The special value NORM_SESSION_INVALID is used to refer to invalid session references.

4.1.3. NormSessionId

The NormSessionId type is used by applications to uniquely identify their instance of participation as a sender within a NormSession. This type is a parameter to the NormStartSender() function. Robust applications can use different NormSessionId values when initiating sender operation so that receivers can discriminate when a sender has terminated and restarted (whether intentional or due to system failure). For example, an application could cache its prior NormSessionId value in non-volatile storage which could then be recovered and incremented (for example) upon system restart to produce a new value. The NormSessionId value is used for the value of the instance_id field in NORM protocol sender messages (see the NORM protocol specification) and receivers use this field to detect sender restart within a NormSession.

4.1.4. NormNodeHandle

The NormNodeHandle type is used to reference state kept by the NORM implementation with respect to other participants within a NormSession. Most typically, the NormNodeHandle is used by receiver applications to dereference information about remote senders of data as needed. The special value NORM_NODE_INVALID corresponds to an invalid reference.

4.1.5. NormNodeId

The NormNodeId type corresponds to a 32-bit numeric value which should uniquely identify a participant (node) in a given NormSession. The NormNodeGetId() function can be used to retrieve this value given a valid NormNodeHandle. The special value NORM_NODE_NONE corresponds to an invalid (or null) node while the value NORM_NODE_ANY serves as a wild card value for some functions.

4.1.6. NormObjectHandle

The NormObjectHandle type is used to reference state kept for data transport objects being actively transmitted or received. The state kept for NORM transport objects is temporary, but the NORM API provides a function to persistently retain state associated with a sender or receiver NormObjectHandle (see NormObjectRetain()) if needed. For sender objects, unless explicitly retained, the NormObjectHandle can be considered valid until the referenced object is explicitly canceled (see NormObjectCancel()) or purged from the sender transmission queue (see the event NORM_TX_OBJECT_PURGED). For receiver objects, these handles should be treated as valid only until a subsequent call to NormGetNextEvent() unless, again, specifically retained. The special value NORM_OBJECT_INVALID corresponds to an invalid transport object reference.

4.1.7. NormObjectType

The NormObjectType type is an enumeration of possible NORM data transport object types. As previously mentioned, valid types include:

  1. NORM_OBJECT_FILE

  2. NORM_OBJECT_DATA, and

  3. NORM_OBJECT_STREAM

Given a NormObjectHandle, the application may determine an object's type using the NormObjectGetType() function call. A special NormObjectType value, NORM_OBJECT_NONE, indicates an invalid object type.

4.1.8. NormSize

The NormSize is the type used for NormObject size information. For example, the NormObjectGetSize() function returns a value of type NormSize. The range of NormSize values depends upon the operating system and NORM library compilation settings. With "large file support" enabled, as is the case with distributed NORM library "Makefiles", the NormSize type is a 64-bit integer. However, some platforms may support only 32-bit object sizes.

4.1.9. NormObjectTransportId

The NormObjectTransportId type is a 16-bit numerical value assigned to NormObjects by senders during active transport. These values are temporarily unique with respect to a given sender within a NormSession and may be "recycled" for use for future transport objects. NORM sender nodes assign these values in a monotonically increasing fashion during the course of a session as part of protocol operation. Typically, the application should not need access to these values, but an API call such as NormObjectGetTransportId() (TBD) may be provided to retrieve these values if needed. (Note this type may be deprecated; i.e., it may not be needed at since the NormRequeueObject() function is implemented using handles only, but _some_ applications requiring persistence even after a system reboot may need the ability to recall previous transport ids?)

4.1.10. NormEventType

The NormEventType is an enumeration of NORM API events. "Events" are used by the NORM API to signal the application of significant NORM protocol operation events (e.g., receipt of a new receive object, etc). A description of possible NormEventType values and their interpretation is given below. The function call NormGetNextEvent() is used to retrieve events from the NORM protocol engine.

4.1.11. NormEvent

The NormEvent type is a structure used to describe significant NORM protocol events. This structure is defined as follows:

typedef struct
+{
+    NormEventType     type;
+    NormSessionHandle session; 
+    NormNodeHandle    node;
+    NormObjectHandle  object;
+} NormEvent;

The type field indicates the NormEventType and determines how the other fields should be interpreted. Note that not all NormEventType fields are relevant to all events. The session, node, and object fields indicate the applicable NormSessionHandle, NormNodeHandle, and NormObjectHandle, respectively, to which the event applies. NORM protocol events are made available to the application via the NormGetNextEvent() function call.

4.1.12. NormDescriptor

The NormDescriptor type can provide a reference to a corresponding file descriptor (Unix int or Win32 HANDLE) for the NormInstance. For a given NormInstanceHandle, the NormGetDescriptor() function can be used to retrieve a NormDescriptor value that may, in turn, used in appropriate system calls (e.g. select() or MsgWaitForMultipleObjects()) to asynchronously monitor the NORM protocol engine for notification events (see NormEvent description).

4.1.13. NormFlushMode

The NormFlushMode type consists of the following enumeration:

enum NormFlushMode
+{
+    NORM_FLUSH_NONE,
+    NORM_FLUSH_PASSIVE,
+    NORM_FLUSH_ACTIVE
+};

The use and interpretation of these values is given in the descriptions of NormStreamFlush() and NormStreamSetAutoFlush() functions.

4.1.14. NormProbingMode

The NormProbingMode type consists of the following enumeration:

enum NormProbingMode
+{
+    NORM_PROBE_NONE,
+    NORM_PROBE_PASSIVE,
+    NORM_PROBE_ACTIVE
+};

The use and interpretation of these values is given in the description of NormSetGrttProbingMode() function.

4.1.15. NormSyncPolicy

The NormSyncPolicy type consists of the following enumeration:

enum NormSyncPolicy
+{
+    NORM_SYNC_CURRENT,
+    NORM_SYNC_ALL
+};

The use and interpretation of these values is given in the descriptions of the NormSetDefaultSyncPolicy() function.

4.1.16. NormNackingMode

The NormNackingMode type consists of the following enumeration:

enum NormNackingMode
+{
+    NORM_NACK_NONE,
+    NORM_NACK_INFO_ONLY,
+    NORM_NACK_NORMAL
+};

The use and interpretation of these values is given in the descriptions of the NormSetDefaultNackingMode(), NormNodeSetNackingMode() and NormObjectSetNackingMode() functions.

4.1.17. NormRepairBoundary

The NormRepairBoundary types consists of the following enumeration:

enum NormRepairBoundary
+{
+    NORM_BOUNDARY_BLOCK,
+    NORM_BOUNDARY_OBJECT
+};

The interpretation of these values is given in the descriptions of the NormSetDefaultRepairBoundary() and NormNodeSetRepairBoundary() functions.

4.1.18. NormAckingStatus

The NormAckingStatus consist of the following enumeration:

enum NormAckingStatus
+{
+    NORM_ACK_INVALID, 
+    NORM_ACK_FAILURE,
+    NORM_ACK_PENDING,
+    NORM_ACK_SUCCESS
+};

The interpretation of these values is given in the descriptions of the NormGetAckingStatus() function.

4.2. API Initialization and Operation

The first step in using the NORM API is to create an "instance" of the NORM protocol engine. Note that multiple instances may be created by the application if necessary, but generally only a single instance is required since multiple NormSessions may be managed under a single NORM API instance.

4.2.1. NormCreateInstance()

4.2.1.1. Synopsis
#include <normApi.h>
+
+NormInstanceHandle NormCreateInstance(bool priorityBoost = false);
4.2.1.2. Description

This function creates an instance of a NORM protocol engine and is the necessary first step before any other API functions may be used. With the instantiation of the NORM protocol engine, an operating system thread is created for protocol execution. The returned NormInstanceHandle value may be used in subsequent API calls as needed, such NormCreateSession(), etc. The optional priorityBoost parameter, when set to a value of true, specifies that the NORM protocol engine thread be run with higher priority scheduling. On Win32 platforms, this corresponds to THREAD_PRIORITY_TIME_CRITICAL and on Unix systems with the sched_setscheduler() API, an attempt to get the maximum allowed SCHED_FIFO priority is made. The use of this option should be carefully evaluated since, depending upon the application's scheduling priority and NORM API usage, this may have adverse effects instead of a guaranteed performance increase!

4.2.1.3. Return Values

A value of NORM_INSTANCE_INVALID is returned upon failure. The function will only fail if system resources are unavailable to allocate the instance and/or create the corresponding thread.

4.2.2. NormDestroyInstance()

4.2.2.1. Synopsis
#include <normApi.h>
+
+void NormDestroyInstance(NormInstanceHandle instanceHandle);
4.2.2.2. Description

The NormDestroyInstance() function immediately shuts down and destroys the NORM protocol engine instance referred to by the instanceHandle parameter. The application should make no subsequent references to the indicated NormInstanceHandle or any other API handles or objects associated with it. However, the application is still responsible for releasing any object handles it has retained (see NormObjectRetain() and NormObjectRelease()).

4.2.2.3. Return Values

The function has no return value.

4.2.3. NormStopInstance()

4.2.3.1. Synopsis
#include <normApi.h>
+
+void NormStopInstance(NormInstanceHandle instanceHandle);
4.2.3.2. Description

This function immediately stops the NORM protocol engine thread corresponding to the given instanceHandle parameter. It also posts a "dummy" notification event so that if another thread is blocked on a call to NormGetNextEvent(), that thread will be released. Hence, for some multi-threaded uses of the NORM API, this function may be useful as a preliminary step to safely coordinate thread shutdown before a call is made to NormDestroyInstance(). After NormStopInstance() is called and any pending events posted prior to its call have been retrieved, NormGetNextEvent() will return a value of false.

When this function is invoked, state for any NormSessions associated with the given instance is "frozen". The complementary function, NormRestartInstance() can be subsequently used to "unfreeze" and resume NORM protocol operation (a new thread is created and started).

4.2.3.3. Return Values

The function has no return value.

4.2.4. NormRestartInstance()

4.2.4.1. Synopsis
#include <normApi.h>
+
+bool NormRestartInstance(NormInstanceHandle instanceHandle);
4.2.4.2. Description

This function creates and starts an operating system thread to resume NORM protocol engine operation for the given instanceHandle that was previously stopped by a call to NormStopInstance(). It is not expected that this function will be used often, but there may be special application cases where "freezing" and later resuming NORM protocol operation may be useful.

4.2.4.3. Return Values

The function returns true when the NORM protocol engine thread is successfully restarted, and false otherwise.

4.2.5. NormSetCacheDirectory()

4.2.5.1. Synopsis
#include <normApi.h>
+
+bool NormSetCacheDirectory(NormInstanceHandle    instanceHandle,
+                           const char*           cachePath);
4.2.5.2. Description

This function sets the directory path used by receivers to cache newly-received NORM_OBJECT_FILE content. The instanceHandle parameter specifies the NORM protocol engine instance (all NormSessions associated with that instanceHandle share the same cache path) and the cachePath is a string specifying a valid (and writable) directory path.

4.2.5.3. Return Values

The function returns true on success and false on failure. The failure conditions are that the indicated directory does not exist or the process does not have permissions to write.

4.2.6. NormGetNextEvent()

4.2.6.1. Synopsis
#include <normApi.h>
+
+bool NormGetNextEvent(NormInstanceHandle instanceHandle,
+                      NormEvent*         theEvent);
4.2.6.2. Description

This function retrieves the next available NORM protocol event from the protocol engine. The instanceHandle parameter specifies the applicable NORM protocol engine, and the theEvent parameter must be a valid pointer to a NormEvent structure capable of receiving the NORM event information. For expected reliable protocol operation, the application should make every attempt to retrieve and process NORM notification events in a timely manner.

Note that this is currently the only blocking call in the NORM API. But non-blocking operation may be achieved by using the NormGetDescriptor() function to obtain a descriptor (int for Unix or HANDLE for WIN32) suitable for asynchronous input/output (I/O) notification using such system calls the Unix select() or Win32 WaitForMultipleObjects() calls. The descriptor is signaled when a notification event is pending and a call to NormGetNextEvent() will not block.NormGetNextEvent

4.2.6.3. Return Values

The function returns true when a NormEvent is successfully retrieved, and false otherwise. Note that a return value of false does not indicate an error or signify end of NORM operation.

4.2.6.4. NORM Notification Event Types

The following table enumerates the possible NormEvent values and describes how these notifications should be interpreted as they are retrieved by the application via the NormGetNextEvent() function call.

Sender Notifications:

 

NORM_TX_QUEUE_VACANCY

This event indicates that there is room for additional transmit objects to be enqueued, or, if the handle of NORM_OBJECT_STREAM is given in the corresponding event "object" field, the application may successfully write to the indicated stream object. Note this event is not dispatched until a call to NormFileEnqueue(), NormDataEnqueue(), or NormStreamWrite() fails because of a filled transmit cache or stream buffer.

NORM_TX_QUEUE_EMPTY

This event indicates the NORM protocol engine has no new data pending transmission and the application may enqueue additional objects for transmission. If the handle of a sender NORM_OBJECT_STREAM is given in the corresponding event "object" field, this indicates the stream transmit buffer has been emptied and the sender application may write to the stream (Use of NORM_TX_QUEUE_VACANCY may be preferred for this purpose since it allows the application to keep the NORM protocol engine busier sending data, resulting in higher throughput when attempting very high transfer rates).

NORM_TX_FLUSH_COMPLETED

This event indicates that the flushing process the NORM sender observes when it no longer has data ready for transmission has completed. The completion of the flushing process is a reasonable indicator (with a sufficient NORM "robust factor" value) that the receiver set no longer has any pending repair requests. Note the use of NORM's optional positive acknowledgement feature is more deterministic in this regards, but this notification is useful when there are non-acking (NACK-only) receivers. The default NORM robust factor of 20 (20 flush messages are sent at end-of-transmission) provides a high assurance of reliable transmission, even with packet loss rates of 50%.

NORM_TX_WATERMARK_COMPLETED

This event indicates that the flushing process initiated by a prior application call to NormSetWatermark() has completed The posting of this event indicates the appropriate time for the application to make a call NormGetAckingStatus() to determine the results of the watermark flushing process.

NORM_TX_OBJECT_SENT

This event indicates that the transport object referenced by the event's "object" field has completed at least one pass of total transmission. Note that this does not guarantee that reliable transmission has yet completed; only that the entire object content has been transmitted. Depending upon network behavior, several rounds of NACKing and repair transmissions may be required to complete reliable transfer.

NORM_TX_OBJECT_PURGED

This event indicates that the NORM protocol engine will no longer refer to the transport object identified by the event's "object' field. Typically, this will occur when the application has enqueued more objects than space available within the set sender transmit cache bounds (see NormSetTxCacheBounds()). Posting of this notification means the application is free to free any resources (memory, files, etc) associated with the indicated "object". After this event, the given "object" handle (NormObjectHandle) is no longer valid unless it is specifically retained by the application.NormObjectHandle

NORM_TX_CMD_SENTThis event indicates that an application-defined command previously enqueued with a call to NormSendCommand() has been transmitted, including any repetition.
NORM_TX_RATE_CHANGEDThis event indicates that NORM Congestion Control operation has adjusted the transmission rate. The NormGetTxRate() call may be used to retrieve the new corresponding transmission rate. Note that if NormSetCongestionControl() was called with its adjustRate parameter set to false, then no actual rate change has occurred and the rate value returned by NormGetTxRate() reflects a "suggested" rate and not the actual transmission rate.

NORM_LOCAL_SENDER_CLOSED

This event is posted when the NORM protocol engine completes the "graceful shutdown" of its participation as a sender in the indicated "session" (see NormStopSender()).

NORM_CC_ACTIVE

This event indicates that congestion control feedback from receivers has begun to be received (This also implies that receivers in the group are actually present and can be used as a cue to begin data transmission.). Note that congestion control must be enabled (see NormSetCongestionControl()) for this event to be posted. Congestion control feedback can be assumed to be received until a NORM_CC_INACTIVE event is posted.

NORM_CC_INACTIVE

This event indicates there has been no recent congestion control feedback received from the receiver set and that the local NORM sender has reached its minimum transmit rate. Applications may wish to refrain from new data transmission until a NORM_CC_ACTIVE event is posted. This notification is only posted when congestion control operation is enabled (see NormSetCongestionControl()) and a previous NORM_CC_ACTIVE event has occurred.

Receiver Notifications:

 

NORM_REMOTE_SENDER_NEW

This event is posted when a receiver first receives messages from a specific remote NORM sender. This marks the beginning of the interval during which the application may reference the provided "node" handle (NormNodeHandle).

NORM_REMOTE_SENDER_ACTIVE

This event is posted when a previously inactive (or new) remote sender is detected operating as an active sender within the session.

NORM_REMOTE_SENDER_INACTIVE

This event is posted after a significant period of inactivity (no sender messages received) of a specific NORM sender within the session. The NORM protocol engine frees buffering resources allocated for this sender when it becomes inactive.

NORM_REMOTE_SENDER_PURGED

This event is posted when the NORM protocol engine frees resources for, and thus invalidates the indicated "node" handle.

NORM_RX_OBJECT_NEW

This event is posted when reception of a new transport object begins and marks the beginning of the interval during which the specified "object" (NormObjectHandle) is valid.

NORM_RX_OBJECT_INFO

This notification is posted when the NORM_INFO content for the indicated "object" is received.

NORM_RX_OBJECT_UPDATED

This event indicates that the identified receive "object" has newly received data content.

NORM_RX_OBJECT_COMPLETED

This event is posted when a receive object is completely received, including available NORM_INFO content. Unless the application specifically retains the "object" handle, the indicated NormObjectHandle becomes invalid and must no longer be referenced.

NORM_RX_OBJECT_ABORTED

This notification is posted when a pending receive object's transmission is aborted by the remote sender. Unless the application specifically retains the "object" handle, the indicated NormObjectHandle becomes invalid and must no longer be referenced.

NORM_RX_CMD_NEWThis event indicates that an application-defined command has been received from a remote sender. The NormEvent node element indicates the NormNodeHandle value associated with the given sender. The NormNodeGetCommand() call can be used to retrieve the received command content.

Miscellaneous Notifications:

 

NORM_GRTT_UPDATED

This notification indicates that either the local sender estimate of GRTT has changed, or that a remote sender's estimate of GRTT has changed. The "sender" member of the NormEvent is set to NORM_NODE_INVALID if the local sender's GRTT estimate has changed or to the NormNodeHandle of the remote sender that has updated its estimate of GRTT.

NORM_EVENT_INVALID

This NormEventType indicates an invalid or "null" notification which should be ignored.

4.2.6.5. Return Values

This function generally blocks the thread of application execution until a NormEvent is available and returns true when a NormEvent is available. However, there are some exceptional cases when the function may immediately return even when no event is pending. In these cases, the return value is false indicating the NormEvent should be ignored.

Win32 Note: A future version of this API will provide an option to have a user-defined Window message posted when a NORM API event is pending. (Also some event filtering calls may be provided (e.g. avoid the potentially numerous NORM_RX_OBJECT_UPDATED events if not needed by the application)).

4.2.7. NormGetDescriptor()

4.2.7.1. Synopsis
#include <normApi.h>
+
+NormDescriptor NormGetDescriptor(NormInstanceHandle instance);
4.2.7.2. Description

This function is used to retrieve a NormDescriptor (Unix int file descriptor or Win32 HANDLE) suitable for asynchronous I/O notification to avoid blocking calls to NormGetNextEvent(). A NormDescriptor is available for each protocol engine instance created using NormCreateInstance(). The descriptor returned is suitable for use as an input (or "read") descriptor which is signaled when a NORM protocol event is ready for retrieval via NormGetNextEvent(). Hence, a call to NormGetNextEvent() will not block when the descriptor has been signaled. The Unix select() or Win32 WaitForMultipleObjects() system calls can be used to detect when the NormDescriptor is signaled. Note that for Unix select() call usage, the NORM descriptor should be treated as a "read" descriptor.

4.2.7.3. Return Values

A NormDescriptor value is returned which is valid until a call to NormDestroyInstance() is made. Upon error, a value of NORM_DESCRIPTOR_INVALID is returned.

4.3. Session Creation and Control Functions

Whether participating in a NORM protocol session as a sender, receiver, or both, there are some common API calls used to instantiate a NormSession and set some common session parameters. Functions are provided to control network socket and multicast parameters. Additionally, a "user data" value may be associated with a NormSessionHandle for programming convenience when dealing with multiple sessions.

4.3.1. NormCreateSession()

4.3.1.1. Synopsis
#include <normApi.h
+NormSessionHandle NormCreateSession(NormInstanceHandle       instance,
+                                    const char*              address,
+                                    unsigned short           port,
+                                    NormNodeId               localId);
4.3.1.2. Description

This function creates a NORM protocol session (NormSession) using the address (multicast or unicast) and port parameters provided. While session state is allocated and initialized, active session participation does not begin until a call is made to NormStartSender() and/or NormStartReceiver() to join the specified multicast group (if applicable) and start protocol operation. The following parameters are required in this function call:

instance

This must be a valid NormInstanceHandle previously obtained with a call to NormCreateInstance().

address

This points to a string containing an IP address (e.g. dotted decimal IPv4 address (or IPv6 address) or name resolvable to a valid IP address. The specified address (along with the port number) determines the destination of NORM messages sent. For multicast sessions, NORM senders and receivers must use a common multicast address and port number. For unicast sessions, the sender and receiver must use a common port number, but specify the other node's IP address as the session address (Although note that receiver-only unicast nodes who are providing unicast feedback to senders will not generate any messages to the session IP address and the address parameter value is thus inconsequential for this special case).

port

This must be a valid, unused port number corresponding to the desired NORM session address. See the address parameter description for more details.

localId

The localId parameter specifies the NormNodeId that should be used to identify the application's presence in the NormSession. All participant's in a NormSession should use unique localId values. The application may specify a value of NORM_NODE_ANY or NORM_NODE_ANY for the localId parameter. In this case, the NORM implementation will attempt to pick an identifier based on the host computer's "default" IP address (based on the computer's default host name). Note there is a chance that this approach may not provide unique node identifiers in some situations and the NORM protocol does not currently provide a mechanism to detect or resolve NormNodeId collisions. Thus, the application should explicitly specify the localId unless there is a high degree of confidence that the default IP address will provide a unique identifier.

4.3.1.3. Return Values

The returned NormSessionHandle value is valid until a call to NormDestroySession() is made. A value of NORM_SESSION_INVALID is returned upon error.

4.3.2. NormDestroySession()

4.3.2.1. Synopsis
#include <normApi.h>
+
+void NormDestroySession(NormSessionHandle sessionHandle);
4.3.2.2. Description

This function immediately terminates the application's participation in the NormSession identified by the sessionHandle parameter and frees any resources used by that session. An exception to this is that the application is responsible for releasing any explicitly retained NormObjectHandle values (See NormObjectRetain() and NormObjectRelease()).

4.3.2.3. Return Values

This function has no returned values.

4.3.3. NormSetUserData()

4.3.3.1. Synopsis
#include <normApi.h>
+
+void NormSetUserData(NormSessionHandle sessionHandle,
+                     const void*       userData);
4.3.3.2. Description

This function allows the application to attach a value to the previously-created NormSession instance specified by the sessionHandle parameter. This value is not used or interpreted by NORM, but is available to the application for use at the programmer's discretion. The set userData value can be later retrieved using the NormGetUserData() function call.

4.3.3.3. Return Values

This function has no returned values.

4.3.4. NormGetUserData()

4.3.4.1. Synopsis
#include <normApi.h>
+
+const void* NormGetUserData(NormSessionHandle sessionHandle);
4.3.4.2. Description

This function retrieves the "user data" value set for the specified sessionHandle with a prior call to NormSetUserData().

4.3.4.3. Return Values

This function returns the user data value set for the specified session. If no user data value has been previously set, a NULL (i.e., (const void*)0) value is returned.

4.3.5. NormGetLocalNodeId()

4.3.5.1. Synopsis
#include <normApi.h>
+
+NormNodeId NormGetLocalNodeId(NormSessionHandle sessionHandle);
4.3.5.2. Description

This function retrieves the NormNodeId value used for the application's participation in the NormSession identified by the sessionHandle parameter. The value may have been explicitly set during the NormCreateSession() call or may have been automatically derived using the host computer's "default" IP network address.

4.3.5.3. Return Values

The returned value indicates the NormNode identifier used by the NORM protocol engine for the local application's participation in the specified NormSession.

4.3.6. NormSetTxPort()

4.3.6.1. Synopsis
#include <normApi.h>
+
+bool NormSetTxPort(NormSessionHandle sessionHandle,
+                   unsigned short    txPort,
+                   bool              enableReuse = false,
+                   const char*       txBindAddress = (const char*)0);
4.3.6.2. Description

This function is used to force NORM to use a specific port number for UDP packets sent for the specified sessionHandle. Additionally, it can optionally enable reuse of the specified port number and/or specify a specific source address binding that is used for packet transmission. By default, NORM uses separate port numbers for packet transmission and session packet reception (the receive port is specified as part of the NormCreateSession() call), allowing the operating system to pick a freely available port for transmission. This call allows the application to pick a specific port number for transmission, and furthermore allows the application to even specify the same port number for transmission as is used for reception. However, the use of separate transmit/receive ports allows NORM to discriminate when unicast feedback is occurring and thus it is not generally recommended that the transmit port be set to the same value as the session receive port.

The enableReuse parameter, when set to true, allows that the specified port may be reused for multiple sessions, but care must be taken when enabling this option. The txBindAddress parameter allows specification of a specific source address binding for packet transmission. The specified address MUST be a valid unicast IP address assigned and configured for the host system. Additionally, the address specified must be compatible with multicast routing and/or the interfaces specified in any calls to NormSetMulticastInterface() for the given session when IP multicast is used.

When the txPort is set equal to the session port number and a txBindAddress is not specified or set equal to the session address, a single socket is used for both transmission and reception. If the same port number is desired for both packet transmission and reception, and a specific source address binding is set, then the enableReuse parameter MUST be (and is automatically) set to true for successful operation. In this case, the receive socket is bound to session address if it is multicast and the transmit socket is bound to the specified txAddress although both are bound to the same port number.

Note this call MUST be made before any calls to NormStartSender() or NormStartReceiver() for the given session to work as described.

4.3.6.3. Return Values

This function returns true upon success and false upon failure. Failure will occur if a txBindAddress is provided that does not correspond to a valid, configured IP address for the local host system.

4.3.7. NormSetTxOnly()

4.3.7.1. Synopsis
#include <normApi.h>
+
+void NormSetTxOnly(NormSessionHandle sessionHandle,
+                   bool              txOnly,
+                   bool              connectToSessionAddress = false);
4.3.7.2. Description

This function limits the NormSession to perform NORM sender functions only. It also limits the underlying NORM UDP socket usage to open only a single transmit socket (tx_socket) and does not open or bind a receive socket for the given session address or port number. Thus, if this property is set, any NORM receivers MUST enable unicast feedback via a call to the NormSetDefaultUnicastNack() or appropriate NormNodeSetUnicastNack() function in order for their feedback messages (NACKs and ACKs) to be received by this sender. The purpose of this function is to allow NORM sender sessions to be created as separate process from a corresponding NORM receiver session for the same session address and port number. By default (when this call is not made), a NormSession, even when acting as only a sender (see NormStartSender()) opens two separate UDP sockets including a "receive" socket bound to the session port number and a "transmit" socket used for message transmission and reception of unicast feedback messages when receivers are so configured.

The optional connectToSessionAddress parameter, when set to true, causes the underlying NORM code to "connect()" the UDP socket to the session (remote receiver) address and port number. If the corresponding NORM remote receiver instance uses NormSetTxPort() to set its transmit port to the same as the session port number, the result is a unique binding between this "tx only" sender instance and the remote NORM receiver instance. With proper use of NormSetRxPortReuse(), this allows multiple senders to be properly associated (i.e., binded with respect to UDP socket packet demultiplexing) with multiple receivers on a single host (all using the same session port number). Note the NORM receiver MUST also use the NormSetDefaultUnicastNack() call so that its feedback messages are directed to the "tx only" sender address/port. The motivation for this API call is to allow systems have NORM sender and receiver instances in separate processes supporting a set (e.g. a mesh) of unicast connections to other hosts. The only constraint is that the senders uses a "tx port" number that is different from the "rx port" number. This enables firewall configurations that only open a pair of UDP ports and allow for connection among an arbitrary number of hosts. This option is really only relevant for unicast NORM sessions.

4.3.7.3. Return Values

This function has no return values.

4.3.8. NormSetRxPortReuse()

4.3.8.1. Synopsis
#include <normApi.h>
+
+void NormSetRxPortReuse(NormSessionHandle session,
+                        bool              enableReuse,
+                        const char*       rxBindAddress = (const char*)0,
+                        const char*       senderAddress = (const char*)0,
+                        UINT16            senderPort = 0);
4.3.8.2. Description

This function allows the user to control the port reuse and binding behavior for the receive socket used for the given NORM sessionHandle. When the enablReuse parameter is set to true, reuse of the NormSession port number by multiple NORM instances or sessions is enabled.

If the optional rxBindAddress is supplied (an IP address or host name in string form), the socket will bind() to the given address when it is opened in a call to NormStartReceiver() or NormStartSender(). The rxBindAddress MUST be the session multicast address (if it is a multicast session) or a valid local unicast address in the case of NORM unicast operation. This binding limits the socket to receive only packets destined for the specified rxBindAddress. This allows multiple NORM sessions to reuse the same port number, but use different multicast addresses (or allow for multiple NORM sessions for multiple local unicast addresses).

The optional senderAddress and senderPort parameters can be used to connect() the underlying NORM receive socket to specific address/port. This limits the socket to receiving only packets from the specified senderAddress/senderPort. This, with receive port reuse enabled, allows for multiple NORM receiver instances to be listening to different NORM senders and have proper UDP socket demultiplexing occur. Note that it is also possible to have single NORM receiver receive transmissions from multiple senders, but in some cases it may be desirable for separate NORM processes or threads to be used to handle reception from separate senders. Thus, this socket binding option is provided.

When this call is not made in any form, the default socket binding to IP address INADDR_ANY (equivalent to when this call is made and rxBindAddress is set to NULL) allows the NormSession receive socket to receive any multicast or unicast transmissions to the session port number provided in the call to NormCreateSession(). This allows a NORM receiver to receive from senders sending to a multicast session address or the receiver's unicast address. As mentioned, enabling port reuse and binding the session destination address allows multiple NORM sessions on the same port number, but participating in different multicast groups.

Note this call MUST be made before any calls to NormStartSender() or NormStartReceiver() for the given sessionHandle to succeed.

This call could also be used in conjunction with NormSetMulticastInterface() so that multiple NormSessions, using the same port and multicast address, could separately cover multiple network interfaces (and some sort of application-layer bridging of reliable multicast could be realized if desired).

4.3.8.3. Return Values

This function has no return values.

4.3.9. NormSetMulticastInterface()

4.3.9.1. Synopsis
#include <normApi.h>
+
+bool NormSetMulticastInterface(NormSessionHandle session,
+                               const char*       interfaceName);
4.3.9.2. Description

This function specifies which host network interface is used for IP Multicast transmissions and group membership. This should be called before any call to NormStartSender() or NormStartReceiver() is made so that the IP multicast group is joined on the proper host interface. However, if a call to NormSetMulticastInterface() is made after either of these function calls, the call will not affect the group membership interface, but only dictate that a possibly different network interface is used for transmitted NORM messages. Thus, the code:

NormSetMulticastInterface(session, "interface1");
+NormStartReceiver(session, ...);
+NormSetMulticastInterface(session, "interface2");

will result in NORM group membership (i.e. multicast reception) being managed on "interface1" while NORM multicast transmissions are made via "interface2".

4.3.9.3. Return Values

A return value of true indicates success while a return value of false indicates that the specified interface was valid. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). However, those calls may fail if an invalid interface was specified with the call described here.

4.3.10. NormSetTTL()

4.3.10.1. Synopsis
#include <normApi.h>
+
+bool NormSetTTL(NormSessionHandle session,
+                unsigned char     ttl);
4.3.10.2. Description

This function specifies the time-to-live (ttl) for IP Multicast datagrams generated by NORM for the specified sessionHandle. The IP TTL field limits the number of router "hops" that a generated multicast packet may traverse before being dropped. For example, if TTL is equal to one, the transmissions will be limited to the local area network (LAN) of the host computers network interface. Larger TTL values should be specified to span large networks. Also note that some multicast router configurations use artificial "TTL threshold" values to constrain some multicast traffic to an administrative boundary. In these cases, the NORM TTL setting must also exceed the router "TTL threshold" in order for the NORM traffic to be allowed to exit the administrative area.

4.3.10.3. Return Values

A return value of true indicates success while a return value of false indicates that the specified ttl could not be set. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). However, those calls may fail if the desired ttl value cannot be set.

4.3.11. NormSetTOS()

4.3.11.1. Synopsis
#include <normApi.h>
+
+bool NormSetTOS(NormSessionHandle       sessionHandle,
+                unsigned char           tos);
4.3.11.2. Description

This function specifies the type-of-service (tos) field value used in IP Multicast datagrams generated by NORM for the specified sessionHandle. The IP TOS field value can be used as an indicator that a "flow" of packets may merit special Quality-of-Service (QoS) treatment by network devices. Users should refer to applicable QoS information for their network to determine the expected interpretation and treatment (if any) of packets with explicit TOS marking.

4.3.11.3. Return Values

A return value of true indicates success while a return value of false indicates that the specified tos could not be set. This function will always return true if made before calls to NormStartSender() or NormStartReceiver(). However, those calls may fail if the desired tos value cannot be set.

4.3.12. NormSetLoopback()

4.3.12.1. Synopsis
#include <normApi.h>
+
+void NormSetLoopback(NormSessionHandle sessionHandle,
+                     bool              loopbackEnable);
4.3.12.2. Description

This function enables or disables loopback operation for the indicated NORM sessionHandle. If loopbackEnable is set to true, loopback operation is enabled which allows the application to receive its own message traffic. Thus, an application which is both actively receiving and sending may receive its own transmissions. Note it is expected that this option would be principally be used for test purposes and that applications would generally not need to transfer data to themselves. If loopbackEnable is false, the application is prevented from receiving its own NORM message transmissions. By default, loopback operation is disabled when a NormSession is created.

4.3.12.3. Return Values

This function has no return values.

4.3.13. NormSetFragmentation()

4.3.13.1. Synopsis
#include <normApi.h>
+
+bool NormSetFragmentation(NormSessionHandle sessionHandle,
+                          bool              fragmentation);
4.3.13.2. Description

This function sets an underlying socket option that enables or disables IP datagram fragmentation by network intermediate systems according to whether the fragmentation parameter is set to a value of true or false, respectively. If set to true to enable fragmentation, the DF (don't fragment) bit of the headers of NORM UDP/IP packets sent will be cleared. Otherwise the DF bit is set and packets will not be fragmented by network devices if they exceed a link Maximum Transmission Unit (MTU) and will instead be dropped. For IP Multicast destinations, some operating systems may always set the DF bit of transmitted packets, regardless of the setting here and the underlying socket option status. Typically, the DF bit is set (i.e., fragmentation disabled) by default on most operating systems.

This call is not currently functional on the Mac OSX system that does not support the needed IP_MTU_DISCOVER or IP_DONTFRAG socket options.

4.3.13.3. Return Values

This function returns true upon success and false upon failure.

4.4. NORM Sender Functions

The functions described in this section apply only to NORM sender operation. Applications may participate strictly as senders or as receivers, or may act as both in the context of a NORM protocol session. The NORM sender is responsible for most parameters pertaining to its transmission of data. This includes transmission rate, data segmentation sizes, FEC coding parameters, stream buffer sizes, etc.

4.4.1. NormStartSender()

4.4.1.1. Synopsis
#include <normApi.h>
+
+bool NormStartSender(NormSessionHandle        sessionHandle,
+                     NormSessionId            instanceId,
+                     unsigned long            bufferSpace,
+                     unsigned short           segmentSize,
+                     unsigned char            blockSize,
+                     unsigned char            numParity);
4.4.1.2. Description

The application's participation as a sender within a specified NormSession begins when this function is called. This includes protocol activity such as congestion control and/or group round-trip timing (GRTT) feedback collection and application API activity such as posting of sender-related NormEvent notifications. The parameters required for this function call include:

sessionHandle

This must be a valid NormSessionHandle previously obtained with a call to NormCreateSession().

instanceId

Application-defined value used as the instance_id field of NORM sender messages for the application's participation within a session. Receivers can detect when a sender has terminated and restarted if the application uses different instanceId values when initiating sender operation. For example, a robust application could cache previous instanceId values in non-volatile storage and gracefully recover (without confusing receivers) from a total system shutdown and reboot by using a new instanceId value upon restart.

bufferSpace

This specifies the maximum memory space (in bytes) the NORM protocol engine is allowed to use to buffer any sender calculated FEC segments and repair state for the session. The optimum bufferSpace value is function of the network topology bandwidth*delay product and packet loss characteristics. If the bufferSpace limit is too small, the protocol may operate less efficiently as the sender is required to possibly recalculate FEC parity segments and/or provide less efficient repair transmission strategies (resort to explicit repair) when state is dropped due to constrained buffering resources. However, note the protocol will still provide reliable transfer. A large bufferSpace allocation is safer at the expense of possibly committing more memory resources.

segmentSize

This parameter sets the maximum payload size (in bytes) of NORM sender messages (not including any NORM message header fields). A sender's segmentSize value is also used by receivers to limit the payload content of some feedback messages (e.g. NORM_NACK message content, etc.) generated in response to that sender. Note different senders within a NormSession may use different segmentSize values. Generally, the appropriate segment size to use is dependent upon the types of networks forming the multicast topology, but applications may choose different values for other purposes. Note that application designers MUST account for the size of NORM message headers when selecting a segmentSize. For example, the NORM_DATA message header for a NORM_OBJECT_STREAM with full header extensions is 48 bytes in length. In this case, the UDP payload size of these messages generated by NORM would be up to (48 + segmentSize) bytes.

blockSize

This parameter sets the number of source symbol segments (packets) per coding block, for the systematic Reed-Solomon FEC code used in the current NORM implementation. For traditional systematic block code "(n,k)" nomenclature, the blockSize value corresponds to "k". NORM logically segments transport object data content into coding blocks and the blockSize parameter determines the number of source symbol segments (packets) comprising a single coding block where each source symbol segment is up to segmentSize bytes in length.. A given block's parity symbol segments are calculated using the corresponding set of source symbol segments. The maximum blockSize allowed by the 8-bit Reed-Solomon codes in NORM is 255, with the further limitation that (blockSize + numParity) <= 255.

numParity

This parameter sets the maximum number of parity symbol segments (packets) the sender is willing to calculate per FEC coding block. The parity symbol segments for a block are calculated from the corresponding blockSize source symbol segments. In the "(n,k)" nomenclature mention above, the numParity value corresponds to "n - k". A property of the Reed-Solomon FEC codes used in the current NORM implementation is that one parity segment can fill any one erasure (missing segment (packet)) for a coding block. For a given blockSize, the maximum numParity value is (255 - blockSize). However, note that computational complexity increases significantly with increasing numParity values and applications may wish to be conservative with respect to numParity selection, given anticipated network packet loss conditions and group size scalability concerns. Additional FEC code options may be provided for this NORM implementation in the future with different parameters, capabilities, trade-offs, and computational requirements.

These parameters are currently immutable with respect to a sender's participation within a NormSession. Sender operation must be stopped (see NormStopSender()) and restarted with another call to NormStartSender() if these parameters require alteration. The API may be extended in the future to support additional flexibility here, if required. For example, the NORM protocol "intance_id" field may possibly be leveraged to permit a node to establish multiple virtual presences as a sender within a NormSession in the future. This would allow the sender to provide multiple concurrent streams of transport, with possibly different FEC and other parameters if appropriate within the context of a single NormSession. Again, this extended functionality is not yet supported in this implementation.

4.4.1.3. Return Values

A value of true is returned upon success and false upon failure. The reasons failure may occur include limited system resources or that the network sockets required for communication failed to open or properly configure. (TBD - Provide a NormGetError(NormSessionHandle sessionHandle) function to retrieve a more specific error indication for this and other functions.)

4.4.2. NormStopSender()

4.4.2.1. Synopsis
#include <normApi.h>
+
+void NormStopSender(NormSessionHandle sessionHandle,
+                    bool              graceful = false);
4.4.2.2. Description

This function terminates the application's participation in a NormSession as a sender. By default, the sender will immediately exit the session identified by the sessionHandle parameter without notifying the receiver set of its intention. However a "graceful shutdown" option, enabled by setting the graceful parameter to true, is provided to terminate sender operation gracefully, notifying the receiver set its pending exit with appropriate protocol messaging. A NormEvent, NORM_LOCAL_SENDER_CLOSED, is dispatched when the graceful shutdown process has completed.

4.4.2.3. Return Values

This function has no return values.

4.4.3. NormSetTxRate()

4.4.3.1. Synopsis
#include <normApi.h>
+
+void NormSetTxRate(NormSessionHandle sessionHandle,
+                   double            rate);
4.4.3.2. Description

This function sets the transmission rate (in bits per second (bps)) limit used for NormSender transmissions for the given sessionHandle. For fixed-rate transmission of NORM_OBJECT_FILE or NORM_OBJECT_DATA, this limit determines the data rate at which NORM protocol messages and data content are sent. For NORM_OBJECT_STREAM transmissions, this is the maximum rate allowed for transmission (i.e. if the application writes to the stream at a lower rate, a lower average NORM transmission rate will occur). Note that the application will need to consider the overhead of NORM protocol headers when determining an appropriate transmission rate for its purposes. When NORM congestion control is enabled (see NormSetCongestionControl()), the rate set here will be set, but congestion control operation, if enabled, may quickly readjust the transmission rate.

4.4.3.3. Return Values

This function has no return values.

4.4.4. NormGetTxRate()

4.4.4.1. Synopsis
#include <normApi.h>
+
+double NormGetTxRate(NormSessionHandle sessionHandle);
4.4.4.2. Description

This function retrieves the current sender transmission rate in units of bits per second (bps) for the given sessionHandle. When NORM congestion control is enabled (see NormSetCongestionControl()), this reflects the current rate set (or suggested) by NORM congestion control operation. Otherwise, this returns the rate that was set with the NormSetTxRate() call.

4.4.4.3. Return Values

This function returns the sender transmission rate in units of bits per second (bps).

4.4.5. NormSetTxSocketBuffer()

4.4.5.1. Synopsis
#include <normApi.h>
+
+bool NormSetTxSocketBuffer(NormSessionHandle sessionHandle,
+                           unsigned int      bufferSize);
4.4.5.2. Description

This function can be used to set a non-default socket buffer size for the UDP socket used by the specified NORM sessionHandle for data transmission. The bufferSize parameter specifies the desired socket buffer size in bytes. Large transmit socket buffer sizes may be necessary to achieve high transmission rates when NORM, as a user-space process, is unable to precisely time its packet transmissions. Similarly, NORM receivers may need to set large receive socket buffer sizes to achieve successful, sustained high data rate reception (see NormSetRxSocketBuffer()). Typically, it is more important to set the receive socket buffer size (see NormSetRxSocketBuffer()) as this maintains reliability (i.e. by avoiding receive socket buffer overflow) at high data rates while setting a larger transmit socket buffer size allows higher average transmission rates to be achieved.

4.4.5.3. Return Values

This function returns true upon success and false upon failure. Possible failure modes include an invalid sessionHandle parameter, a call to NormStartReceiver() or NormStartSender() has not yet been made for the session, or an invalid bufferSize was given. Note some operating systems may require additional system configuration to use non-standard socket buffer sizes.

4.4.6. NormSetFlowControl()

4.4.6.1. Synopsis
#include <normApi.h>
+
+void NormSetFlowControl(NormSessionHandle sessionHandle,
+                        double            flowControlFactor);
4.4.6.2. Description

This function controls a scaling factor that is used for sender timer-based flow control for the the specified NORM sessionHandle. Timer-based flow control works by preventing the NORM sender application from enqueueing new transmit objects or stream data that would purge "old" objects or stream data when there has been recent NACK activity for those old objects or data. If the flowControlFactor is set to ZERO, then the flow control mechanism is effectively disabled. Larger flowControlFactor values enforce more robust flow control by forcing the sender to maintain state longer, but then larger transmit buffer, stream buffer, transmit cache bounds and receive cache limits (see NormStartSender(), NormStreamOpen(), NormSetTxCacheBounds(), and NormSetRxCacheLimit(), respectively) may be needed to maintain throughput in larger <delay*bandwidth, loss> conditions. Effectively, a larger flowControlFactor can favor reliability over throughput when buffer-constrained.

The flowControlFactor is used to compute a delay time for when a sender buffered object (or block of stream data) may be released (i.e. purged) after transmission or applicable NACKs reception. The delay time function is:

flowControlDelay = flowControlFactor * GRTT * (backoffFactor + 1)

where the "GRTT" is the sender's advertised GRTT estimate and the backoffFactor is the sender's configured timer-based feedback scaling factor.

The default value (when this function is not called) of the flowControlFactor is 2.0. Note that a NORM application can also implement more explicit, deterministic flow control through use of the NormSetWatermark() API call, potentially even requiring positive acknowledgement of older data before enqueueing new data. Note that using the NormSetWatermark() API call with a NORM_NODE_NONE member in acking node list to force a "full" watermark flush is somewhat equivalent to timer-based flow control with a flowControlFactor equal to 2.0 * txRobustFactor.

If such explicit flow control is implemented by the application, then a reduced flowControlFactor (or even ZERO) may be used. If "push mode" is enabled for a NORM_OBJECT_STREAM (see NormStreamSetPushEnable()), then flow control has no effect for the stream.

4.4.6.3. Return Values

This function has no return values.

4.4.7. NormSetCongestionControl()

4.4.7.1. Synopsis
#include <normApi.h>
+
+void NormSetCongestionControl(NormSessionHandle sessionHandle,
+                              bool              enable,
+                              bool              adjustRate = true);
4.4.7.2. Description

This function enables (or disables) the NORM sender congestion control operation for the session designated by the sessionHandle parameter. For best operation, this function should be called before the call to NormStartSender() is made, but congestion control operation can be dynamically enabled/disabled during the course of sender operation. If the value of the enable parameter is true, congestion control operation is enabled while it is disabled for enable equal to false. When congestion control operation is enabled, the NORM sender automatically adjusts its transmission rate based on feedback from receivers. If bounds on transmission rate have been set (see NormSetTxRateBounds()) the rate adjustment will remain within the set bounds. The application will be notified of any changes to the sender transmission rate via a NormEvent of type NORM_TX_RATE_CHANGED.

The rate set by NormSetTxRate() has no effect when congestion control operation is enabled, unless the adjustRate parameter here is set to false. When the adjustRate parameter is set to false, the NORM Congestion Control operates as usual, with feedback collected from the receiver set and the "current limiting receiver" identified, except that no actual adjustment is made to the sender's transmission rate. I.e., the transmission rate that was set by NormSetTxRate() is observed by the sender regardless of the feedback received. The NORM_TX_RATE_CHANGED notification will still occur as if the rate were being adjusted and the value returned by NormGetTxRate() reflects the rate that would have been used had the adjustRate parameter been enabled even though no actual rate change has occurred. The purpose of this variation of NORM Congestion Control operation is to allow applications to get a "suggested" rate from the NORM-CC mechanism. But, it is important to note that this "suggested" rate may or may not be appropriate since the operation of the NORM-CC algorithm is somewhat dependent on the associated NORM sender load on the network. For example, the "suggested" rate may be artificially high if the sender application has not been correspondingly setting the rate and actively transmitting data at that rate. This optional mode of operation is provided for EXPERIMENTAL purposes and is NOT RECOMMENDED for typical use of NORM.

NORM's congestion algorithm provides rate adjustment to fairly compete for available network bandwidth with other TCP, NORM, or similarly governed traffic flows.

(TBD - Describe the NormSetEcnSupport() function as this experimental option matures.)

4.4.7.3. Return Values

This function has no return values.

4.4.8. NormSetTxRateBounds()

4.4.8.1. Synopsis
#include <normApi.h>
+
+bool NormSetTxRateBounds(NormSessionHandle sessionHandle,
+                               double            rateMin,
+                               double            rateMax);
4.4.8.2. Description

This function sets the range of sender transmission rates within which the NORM congestion control algorithm is allowed to operate for the given sessionHandle. By default, the NORM congestion control algorithm operates with no lower or upper bound on its rate adjustment. This function allows this to be limited where rateMin corresponds to the minimum transmission rate (bps) and rateMax corresponds to the maximum transmission rate. One or both of these parameters may be set to values less than zero to remove one or both bounds. For example, the call "NormSetTxRateBounds(session, -1.0, 64000.0)" will set an upper limit of 64 kbps for the sender transmission rate with no lower bound. These rate bounds apply only when congestion control operation is enabled (see NormSetCongestionControl()). If the current congestion control rate falls outside of the specified bounds, the sender transmission rate will be adjusted to stay within the set bounds.

4.4.8.3. Return Values

This function returns true upon success. If both rateMin and rateMax are greater than or equal to zero, but (rateMax < rateMin), the rate bounds will remain unset or unchanged and the function will return false.

4.4.9. NormSetTxCacheBounds()

4.4.9.1. Synopsis
#include <normApi.h>
+
+void NormSetTxCacheBounds(NormSessionHandle sessionHandle,
+                                NormSize          sizeMax,
+                                unsigned int      countMin,
+                                unsigned int      countMax);
4.4.9.2. Description

This function sets limits that define the number and total size of pending transmit objects a NORM sender will allow to be enqueued by the application. Setting these bounds to large values means the NORM protocol engine will keep history and state for previously transmitted objects for a larger interval of time (depending upon the transmission rate) when the application is actively enqueueing additional objects in response to NORM_TX_QUEUE_EMPTY notifications. This can allow more time for receivers suffering degraded network conditions to make repair requests before the sender "purges" older objects from its "transmit cache" when new objects are enqueued. A NORM_TX_OBJECT_PURGED notification is issued when the enqueuing of a new transmit object causes the NORM transmit cache to overflow, indicating the NORM sender no longer needs to reference the designated old transmit object and the application is free to release related resources as needed.

The sizeMax parameter sets the maximum total size, in bytes, of enqueued objects allowed, providing the constraints of the countMin and countMax parameters are met. The countMin parameter sets the minimum number of objects the application may enqueue, regardless of the objects' sizes and the sizeMax value. For example, the default sizeMax value is 20 Mbyte and the default countMin is 8, thus allowing the application to always have at least 8 pending objects enqueued for transmission if it desires, even if their total size is greater than 20 Mbyte. Similarly, the countMax parameter sets a ceiling on how many objects may be enqueued, regardless of their total sizes with respect to the sizeMax setting. For example, the default countMax value is 256, which means the application is never allowed to have more than 256 objects pending transmission enqueued, even if they are 256 very small objects. Note that countMax must be greater than or equal to countMin and countMin is recommended to be at least two.

Note that in the case of NORM_OBJECT_FILE objects, some operating systems impose limits (e.g. 256) on how many open files a process may have at one time and it may be appropriate to limit the countMax value accordingly. In other cases, a large countMin or countMax may be desired to allow the NORM sender to act as virtual cache of files or other data available for reliable transmission. Future iterations of the NRL NORM implementation may support alternative NORM receiver "group join" policies that would allow the receivers to request transmission of cached content.

The utility of the NormRequeueObject() API call also depends on the parameters set by this function. The NormRequeueObject() call will only succeed when the given objectHandle corresponds to an object maintained in the NORM senders "transmit cache".

4.4.9.3. Return Values

This function has no return value.

4.4.10. NormSetAutoParity()

4.4.10.1. Synopsis
#include <normApi.h>
+
+void NormSetAutoParity(NormSessionHandle sessionHandle,
+                       unsigned char     autoParity);
4.4.10.2. Description

This function sets the quantity of proactive "auto parity" NORM_DATA messages sent at the end of each FEC coding block. By default (i.e., autoParity = 0), FEC content is sent only in response to repair requests (NACKs) from receivers. But, by setting a non-zero value for autoParity, the sender can automatically accompany each coding block of transport object source data segments ((NORM_DATA messages) with the set number of FEC segments. The number of source symbol messages (segments) per FEC coding block is determined by the blockSize parameter used when NormStartSender() was called for the given sessionHandle.

The use of proactively-sent "auto parity" may eliminate the need for any receiver NACKing to achieve reliable transfer in networks with low packet loss. However, note that the quantity of "auto parity" set adds overhead to transport object transmission. In networks with a predictable level of packet loss and potentially large round-trip times, the use of "auto parity" may allow lower latency in the reliable delivery process. Also, its use may contribute to a smaller amount of receiver feedback as only receivers with exceptional packet loss may need to NACK for additional repair content.

The value of autoParity set must be less than or equal to the numParity parameter set when NormStartSender() was called for the given sessionHandle.

4.4.10.3. Return Values

This function has no return values.

4.4.11. NormGetGrttEstimate()

4.4.11.1. Synopsis
#include <normApi.h>
+
+double NormGetGrttEstimate(NormSessionHandle sessionHandle);
4.4.11.2. Description

This function returns the sender's current estimate(in seconds) of group round-trip timing (GRTT) for the given NORM session. This function may be useful for applications to leverage for other purposes the assessment of round-trip timing made by the NORM protocol engine. For example, an application may scale its own timeouts based on connectivity delays among participants in a NORM session. Note that the NORM_GRTT_UPDATED event is posted (see NormGetNextEvent()) by the NORM protocol engine to indicate when changes in the local sender or remote senders' GRTT estimate occurs.

4.4.11.3. Return Values

This function returns the current sender group round-trip timing (GRTT) estimate (in units of seconds). A value of -1.0 is returned if an invalid session value is provided.

4.4.12. NormSetGrttEstimate()

4.4.12.1. Synopsis
#include <normApi.h>
+
+void NormSetGrttEstimate(NormSessionHandle sessionHandle,
+                         double            grtt);
4.4.12.2. Description

This function sets the sender's estimate of group round-trip time (GRTT) (in units of seconds) for the given NORM sessionHandle. This function is expected to most typically used to initialize the sender's GRTT estimate prior to the call to NormStartSender() when the application has a priori confidence that the default initial GRTT value of 0.5 second is inappropriate. The sender GRTT estimate will be updated during normal sender protocol operation after sender startup or if this call is made while sender operation is active. For experimental purposes (or very special application needs), this API provides a mechanism to control or disable the sender GRTT update process (see NormSetGrttProbingMode()). The grtt value (in seconds) will be limited to the maximum GRTT as set (see NormSetGrttMax()) or the default maximum of 10 seconds.

The sender GRTT is advertised to the receiver group and is used to scale various NORM protocol timers. The default NORM GRTT estimation process dynamically measures round-trip timing to determine an appropriate operating value. An overly-large GRTT estimate can introduce additional latency into the reliability process (resulting in a larger virtual delay*bandwidth product for the protocol and potentially requiring more buffer space to maintain reliability). An overly-small GRTT estimate may introduce the potential for feedback implosion, limiting the scalability of group size.

Also note that the advertised GRTT estimate can also be limited by transmission rate. When the sender transmission rate is low, the GRTT is also governed to a lower bound of the nominal packet transmission interval (i.e., 1/txRate). This maintains the "event driven" nature of the NORM protocol with respect to receiver reception of NORM sender data and commands.

4.4.12.3. Return Values

This function has no return values.

4.4.13. NormSetGrttMax()

4.4.13.1. Synopsis
#include <normApi.h>
+
+void NormSetGrttMax(NormSessionHandle sessionHandle,
+                    double            grttMax);
4.4.13.2. Description

This function sets the sender's maximum advertised GRTT value for the given NORM sessionHandle. The grttMax parameter, in units of seconds, limits the GRTT used by the group for scaling protocol timers, regardless of larger measured round trip times. The default maximum for the NRL NORM library is 10 seconds. See the NormSetGrttEstimate() function description for the purpose of the NORM GRTT measurement process.

4.4.13.3. Return Values

This function has no return values.

4.4.14. NormSetGrttProbingMode()

4.4.14.1. Synopsis
#include <normApi.h>
+
+void NormSetGrttProbingMode(NormSessionHandle sessionHandle,
+                            NormProbingMode   probingMode);
4.4.14.2. Description

This function sets the sender's mode of probing for round trip timing measurement responses from the receiver set for the given NORM sessionHandle. Possible values for the probingMode parameter include NORM_PROBE_NONE, NORM_PROBE_PASSIVE, and NORM_PROBE_ACTIVE. The default probing mode is NORM_PROBE_ACTIVE. In this mode, the receiver set explicitly acknowledges NORM sender GRTT probes ((NORM_CMD(CC) messages) with NORM_ACK responses that are group-wise suppressed. Note that NORM receivers also will include their response to GRTT probing piggy-backed on any NORM_NACK messages sent in this mode as well to minimize feedback.

Note that the NORM_PROBE_ACTIVE probing mode is required and automatically set when NORM congestion control operation is enabled (see NormSetCongestionControl()). Thus, when congestion control is enabled, the NormSetGrttProbingMode() function has no effect.

If congestion control operation is not enabled, the NORM application may elect to reduce the volume of feedback traffic by setting the probingMode to NORM_PROBE_PASSIVE. Here, the NORM sender still transmits NORM_CMD(CC) probe messages multiplexed with its data transmission, but the receiver set does not explicitly acknowledge these probes. Instead the receiver set is limited to opportunistically piggy-backing responses when NORM_NACK messages are generated. Note that this may, in some cases, introduce some opportunity for bursts of large volume receiver feedback when the sender's estimate of GRTT is incorrect due to the reduced probing feedback. But, in some controlled network environments, this option for passive probing may provide some benefits in reducing protocol overhead.

Finally, the probingMode can be set to NORM_PROBE_NONE to eliminate the overhead (and benefits) of NORM GRTT measurement entirely. In this case, the sender application must explicitly set its estimate of GRTT using the NormSetGrttEstimate() function. See this function for a description of the purpose of the NORM GRTT measurement.

4.4.14.3. Return Values

This function has no return values.

4.4.15. NormSetGrttProbingInterval()

4.4.15.1. Synopsis
#include <normApi.h>
+
+void NormSetGrttProbingInterval(NormSessionHandle sessionHandle,
+                                double            intervalMin,
+                                double            intervalMax);
4.4.15.2. Description

This function controls the sender GRTT measurement and estimation process for the given NORM sessionHandle. The NORM sender multiplexes periodic transmission of NORM_CMD(CC) messages with its ongoing data transmission or when data transmission is idle. When NORM congestion control operation is enabled, these probes are sent once per RTT of the current limiting receiver (with respect to congestion control rate). In this case the intervalMin and intervalMax parameters (in units of seconds) control the rate at which the sender's estimate of GRTT is updated. At session start, the estimate is updated at intervalMin and the update interval time is doubled until intervalMax is reached. This dynamic allows for a rapid initial estimation of GRTT and a slower, steady-state update of GRTT. When congestion control is disabled and NORM GRTT probing is enabled ((NORM_PROBE_ACTIVE or NORM_PROBE_PASSIVE) the intervalMin and intervalMax values also determine the rate at which NORM_CMD(CC) probes are transmitted by the sender. Thus by setting larger values for intervalMin and intervalMax, the NORM sender application can reduce the overhead of the GRTT measurement process. However, this also reduces the ability of NORM to adapt to changes in GRTT.

The default NORM GRTT intervalMin and intervalMax values, i.e., when this call is not made, are 1.0 second and 30.0 seconds, respectively.

4.4.15.3. Return Values

This function has no return values.

4.4.16. NormSetBackoffFactor()

4.4.16.1. Synopsis
#include <normApi.h>
+
+void NormSetBackoffFactor(NormSessionHandle sessionHandle,
+                          double            backoffFactor);
4.4.16.2. Description

This function sets the sender's "backoff factor" for the given sessionHandle. The backoffFactor (in units of seconds) is used to scale various timeouts related to the NACK repair process. The sender advertises its backoffFactor setting to the receiver group in NORM protocol message headers. The default backoffFactor for NORM sessions is 4.0 seconds. The backoffFactor is used to determine the maximum time that receivers may delay NACK transmissions (and other feedback messages) as part of NORM's probabilistic feedback suppression technique. For example, the maximum NACK delay time is backoffFactor*GRTT. Thus a large backoffFactor value introduces latency into the NORM repair process. However, a small backoffFactor value causes feedback suppression to be less effective and increases the risk of feedback implosion for large receiver group sizes.

The default setting of 4.0 provides reasonable feedback suppression for moderate to large group sizes when multicast feedback is possible. The NORM specification recommends a backoffFactor value of 6.0 when unicast feedback is used. However, for demanding applications (with respect to repair latency) when group sizes are modest, a small (even 0.0) backoffFactor value can be specified to reduce the latency of reliable data delivery.

4.4.16.3. Return Values

This function has no return values.

4.4.17. NormSetGroupSize()

4.4.17.1. Synopsis
#include <normApi.h>
+
+void NormSetGroupSize(NormSessionHandle sessionHandle,
+                      unsigned int      groupSize);
4.4.17.2. Description

This function sets the sender's estimate of receiver group size for the given sessionHandle. The sender advertises its groupSize setting to the receiver group in NORM protocol message headers that, in turn, use this information to shape the distribution curve of their random timeouts for the timer-based, probabilistic feedback suppression technique used in the NORM protocol. Note that the groupSize estimate does not have to be very accurate and values within an order of magnitude of the actual group size tend to produce acceptable performance.

The default groupSize setting in NORM is 1,000 and thus can work well for a wide range of actual receiver group sizes. The penalty of an overly large estimate is statistically a little more latency in reliable data delivery with respect to the round trip time and some potential for excess feedback. A substantial underestimation of groupSize increases the risk of feedback implosion. Currently, the NORM implementation does not attempt to automatically measure groupSize from receiver feedback. Applications could add their own mechanism for this (perhaps keeping explicit track of group membership), or it is possible that future versions of the NRL NORM implementation may have some provision for automatic groupSize estimation by the sender based on receiver feedback messages.

4.4.17.3. Return Values

This function has no return values.

4.4.18. NormSetTxRobustFactor()

4.4.18.1. Synopsis
#include <normApi.h>
+
+void NormSetTxRobustFactor(NormSessionHandle sessionHandle,
+                           int               txRobustFactor);
4.4.18.2. Description

This routine sets the "robustness factor" used for various NORM sender functions. These functions include the number of repetitions of "robustly-transmitted" NORM sender commands such as NORM_CMD(FLUSH) or similar application-defined commands, and the number of attempts that are made to collect positive acknowledgement from receivers. These commands are distinct from the NORM reliable data transmission process, but play a role in overall NORM protocol operation. The default txRobustFactor value is 20. This relatively large value makes the NORM sender end-of-transmission flushing and positive acknowledgement collection functions somewhat immune from packet loss. However, for some applications, the default value may make the NORM protocol more "chatty" than desired (particularly if flushing is invoked often). In other situations where the network connectivity may be intermittent or extremely lossy, it may be useful to actually increase this value. The default value (20) is expected to provide reasonable operation across a wide range of network conditions and application types. Since this value is not communicated among NORM participants as part of the protocol operation, it is important that applications consistently set this value among all applications participating in a NORM session.

Setting txRobustFactor to a value of -1 makes the redundant transmission of these commands continue indefinitely until completion. For example, with positive acknowledgement collection, the request process will continue indefinitely until all recipients requested acknowledge or the request is canceled by the application. Similarly, flushing commands would be transmitted repeatedly until data transmission is resumed. Typically, setting txRobustFactor to -1 is not recommended.

4.4.18.3. Return Values

This function has no return values.

4.4.19. NormFileEnqueue()

4.4.19.1. Synopsis
#include <normApi.h>
+
+NormObjectHandle NormFileEnqueue(NormSessionHandle      sessionHandle,
+                                 const char*            filename,
+                                 const char*            infoPtr = NULL,
+                                 unsigned int           infoLen = 0);
4.4.19.2. Description

This function enqueues a file for transmission within the specified NORM sessionHandle. Note that NormStartSender() must have been previously called before files or any transport objects may be enqueued and transmitted. The fileName parameter specifies the path to the file to be transmitted. The NORM protocol engine read and writes directly from/to file system storage for file transport, potentially providing for a very large virtual "repair window" as needed for some applications. While relative paths with respect to the "current working directory" may be used, it is recommended that full paths be used when possible. The optional infoPtr and infoLen parameters are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the NORM_INFO content is left to the application's discretion. Example usage of NORM_INFO content for NORM_OBJECT_FILE might include file name, creation date, MIME-type or other information which will enable NORM receivers to properly handle the file when reception is complete.

The application is allowed to enqueue multiple transmit objects within in the "transmit cache" bounds (see NormSetTxCacheBounds()) and enqueued objects are transmitted (and repaired as needed) within the limits determined by automated congestion control (see NormSetCongestionControl()) or fixed rate (see NormSetTxRate()) parameters.

4.4.19.3. Return Values

A NormObjectHandle is returned which the application may use in other NORM API calls as needed. This handle can be considered valid until the application explicitly cancels the object's transmission (see NormObjectCancel()) or a NORM_TX_OBJECT_PURGED event is received for the given object. Note the application may use the NormObjectRetain() method if it wishes to refer to the object after the NORM_TX_OBJECT_PURGED notification. In this case, the application, when finished with the object, must use NormObjectRelease() to free any resources used or else a memory leak condition will result. A value of NORM_OBJECT_INVALID is return upon error. Possible failure conditions include the specified session is not operating as a NormSender, insufficient memory resources were available, or the "transmit cache" limits have been reached and all previously enqueued NORM transmit objects are pending transmission. Also the call will fail if the infoLen parameter exceeds the local NormSender segmentSize limit.

4.4.20. NormDataEnqueue()

4.4.20.1. Synopsis
#include <normApi.h>
+
+NormObjectHandle NormDataEnqueue(NormSessionHandle      sessionHandle,
+                                 const char*            dataPtr,
+                                 unsigned int           dataLen,
+                                 const char*            infoPtr = NULL,
+                                 unsigned int           infoLen = 0);
4.4.20.2. Description

This function enqueues a segment of application memory space for transmission within the specified NORM sessionHandle. Note that NormStartSender() MUST have been previously called before files or any transport objects may be enqueued and transmitted. The dataPtr parameter must be a valid pointer to the area of application memory to be transmitted and the dataLen parameter indicates the quantity of data to transmit. The NORM protocol engine read and writes directly from/to application memory space so it is important that the application does not modify (or deallocate) the memory space during the time the NORM protocol engine may access this area. After calling NormDataEnqueue() for a specific application "dataPtr" memory space, the application MUST NOT deallocate (or change the contents of) that memory space until a NORM_TX_OBJECT_PURGED notification is received for the given object or the application itself explicitly cancels the object's transmission (see NormObjectCancel()).

The optional infoPtr and infoLen parameters are used to associate NORM_INFO content with the sent transport object. The maximum allowed infoLen corresponds to the segmentSize used in the prior call to NormStartSender(). The use and interpretation of the NORM_INFO content is left to the application's discretion. Example usage of NORM_INFO content for NORM_OBJECT_DATA might include application-defined data typing or other information which will enable NORM receiver applications to properly interpret the received data when reception is complete. Of course, it is possible that the application may embed such typing information in the object data content itself. This is left to the application's discretion.

The application is allowed to enqueue multiple transmit objects within in the "transmit cache" bounds (see NormSetTxCacheBounds()) and enqueued objects are transmitted (and repaired as needed) within the limits determined by automated congestion control (see NormSetCongestionControl()) or fixed rate (see NormSetTxRate()) parameters.

4.4.20.3. Return Values

A NormObjectHandle is returned which the application may use in other NORM API calls as needed. This handle can be considered valid until the application explicitly cancels the object's transmission (see NormObjectCancel()) or a NORM_TX_OBJECT_PURGED event is received for the given object. Note the application may use the NormObjectRetain() method if it wishes to refer to the object after the NORM_TX_OBJECT_PURGED notification. In this case, the application, when finished with the object, must use NormObjectRelease() to free any resources used or else a memory leak condition will result. A value of NORM_OBJECT_INVALID is return upon error. Possible failure conditions include the specified session is not operating as a NormSender, insufficient memory resources were available, or the "transmit cache" limits have been reached and all previously enqueued NORM transmit objects are pending transmission. Also the call will fail if the infoLen parameter exceeds the local NormSender segmentSize limit.

4.4.21. NormRequeueObject()

4.4.21.1. Synopsis
#include <normApi.h>
+
+boolNormRequeueObject(NormSessionHandle sessionHandle,
+                      NormObjectHandle  objectHandle);
4.4.21.2. Description

This function allows the application to resend (or reset transmission of) a NORM_OBJECT_FILE or NORM_OBJECT_DATA transmit object that was previously enqueued for the indicated sessionHandle. This function is useful for applications sending to silent (non-NACKing) receivers as it enables the receivers to take advantage of multiple retransmissions of objects (including any auto-parity set, see NormSetAutoParity()) to more robustly receive content. The objectHandle parameter must be a valid transmit NormObjectHandle that has not yet been "purged" from the sender's transmit queue. Upon success, the specified object will be fully retransmitted using the same NORM object transport identifier as was used on its initial transmission. This call may be made at any time to restart transmission of a previously-enqueued object, but the NORM_TX_OBJECT_SENT or NORM_TX_FLUSH_COMPLETED notifications can serve as good cues for an appropriate time to resend an object. If multiple objects are re-queued, they will be resent in order of their initial enqueueing.

The transmit cache bounds set by NormSetTxCacheBounds() determine the number of previously-sent objects retained in the sender's transmit queue and that are thus eligible to be requeued for retransmission. An object may be requeued via this call multiple times, but each distinct requeue should be done after an indication such as NORM_TX_OBJECT_SENT or NORM_TX_FLUSH_COMPLETED for the given object. Otherwise, the object will simply be reset from its current transmission point to transmit from the beginning (i.e. restart). Note that the object type NORM_OBJECT_STREAM cannot currently be requeued.

(TBD - should a "numRepeats" parameter be added to this function?)

4.4.21.3. Return Values

A value of true is returned upon success and a value of false is returned upon failure. Possible reasons for failure include an invalid objectHandle was provided (i.e. a non-transmit object or transmit object that has been "purged" from the transmit queue (see NORM_TX_OBJECT_PURGED)) or the provided object was of type NORM_OBJECT_STREAM.

4.4.22. NormStreamOpen()

4.4.22.1. Synopsis
#include <normApi.h>
+
+NormObjectHandle NormStreamOpen(NormSessionHandle      sessionHandle,
+                                unsigned int           bufferSize,
+                                const char*            infoPtr = NULL,
+                                unsigned int           infoLen = 0);
4.4.22.2. Description

This function opens a NORM_OBJECT_STREAM sender object and enqueues it for transmission within the indicated sessionHandle. NORM streams provide reliable, in-order delivery of data content written to the stream by the sender application. Note that no data is sent until subsequent calls to NormStreamWrite() are made unless NORM_INFO content is specified for the stream with the infoPtr and infoLen parameters. Example usage of NORM_INFO content for NORM_OBJECT_STREAM might include application-defined data typing or other information which will enable NORM receiver applications to properly interpret the received stream as it is being received. The NORM protocol engine buffers data written to the stream for original transmission and repair transmissions as needed to achieve reliable transfer. The bufferSize parameter controls the size of the stream's "repair window" which limits how far back the sender will "rewind" to satisfy receiver repair requests.

NORM, as a NACK-oriented protocol, currently lacks a mechanism for receivers to explicitly feedback flow control status to the sender unless the sender application specifically leverages NORM's optional positive-acknowledgement (ACK) features. Thus, the bufferSize selection plays an important role in reliable delivery of NORM stream content. Generally, a larger bufferSize value is safer with respect to reliability, but some applications may wish to limit how far the sender rewinds to repair receivers with poor connectivity with respect to the group at large. Such applications may set a smaller bufferSize to avoid the potential for large latency in data delivery (i.e. favor peak delivery latency over full reliability). This may result in breaks in the reliable delivery of stream data to some receivers, but this form of quasi-reliability while limiting latency may be useful for some types of applications (e.g. reliable real-time messaging, video or sensor or media data transport). Note that NORM receivers can quickly, automatically "resync" to the sender after such breaks if the application leverages the application message boundary recovery features of NORM (see NormStreamMarkEom()).

Note that the current implementation of NORM is designed to support only one active stream per session, and that any NORM_OBJECT_DATA or NORM_OBJECT_FILE objects enqueued for transmission will not begin transmission until an active stream is closed. Applications requiring multiple streams or concurrent file/data transfer SHOULD generally instantiate multiple NormSessions as needed.

Note there is no corresponding "open" call for receiver streams. Receiver NORM_OBJECT_STREAMs are automatically opened by the NORM protocol engine and the receiver applications is notified of new streams via the NORM_RX_OBJECT_NEW notification (see NormGetNextEvent()).

4.4.22.3. Return Values

A NormObjectHandle is returned which the application may use in other NORM API calls as needed. This handle can be considered valid until the application explicitly cancels the object's transmission (see NormObjectCancel()) or a NORM_TX_OBJECT_PURGED event is received for the given object. Note the application may use the NormObjectRetain() method if it wishes to refer to the object after the NORM_TX_OBJECT_PURGED notification. In this case, the application, when finished with the object, must use NormObjectRelease() to free any resources used or else a memory leak condition will result. A value of NORM_OBJECT_INVALID is return upon error. Possible failure conditions include the specified session is not operating as a NormSender, insufficient memory resources were available, or the "transmit cache" bounds have been reached and all previously enqueued NORM transmit objects are pending transmission. Also the call will fail if the infoLen parameter exceeds the local NormSender segmentSize limit.

4.4.23. NormStreamClose()

4.4.23.1. Synopsis
#include <normApi.h>
+
+void NormStreamClose(NormObjectHandle streamHandle,
+                     bool             graceful = false);
4.4.23.2. Description

This function halts transfer of the stream specified by the streamHandle parameter and releases any resources used unless the associated object has been explicitly retained by a call to NormObjectRetain(). No further calls to NormStreamWrite() will be successful for the given streamHandle. The optional graceful parameter, when set to a value of true, may be used by NORM senders to initiate "graceful" shutdown of a transmit stream. In this case, the sender application will be notified that stream has (most likely) completed reliable transfer via the NORM_TX_OBJECT_PURGED notification upon completion of the graceful shutdown process. When the graceful option is set to true, receivers are notified of the stream end via an "stream end" stream control code in NORM_DATA message and will receive a NORM_RX_OBJECT_COMPLETED notification after all received stream content has been read. Otherwise, the stream is immediately terminated, regardless of receiver state. In this case, this function is equivalent to the NormObjectCancel() routine and may be used for sender or receiver streams. So, it is expected this function (NormStreamClose()) will typically be used for transmit streams by NORM senders.

4.4.23.3. Return Values

This function has no return values.

4.4.24. NormStreamWrite()

4.4.24.1. Synopsis
#include <normApi.h>
+
+unsigned int NormStreamWrite(NormObjectHandle streamHandle 
+                             const char*      buffer,
+                             unsigned int     numBytes);
4.4.24.2. Description

This function enqueues data for transmission within the NORM stream specified by the streamHandle parameter. The buffer parameter must be a pointer to the data to be enqueued and the numBytes parameter indicates the length of the data content. Note this call does not block and will return immediately. The return value indicates the number of bytes copied from the provided buffer to the internal stream transmission buffers. Calls to this function will be successful unless the stream's transmit buffer space is fully occupied with data pending original or repair transmission if the stream's "push mode" is set to false (default, see NormStreamSetPushEnable() for details). If the stream's "push mode" is set to true, a call to NormStreamWrite() will always result in copying of application data to the stream at the cost of previously enqueued data pending transmission (original or repair) being dropped by the NORM protocol engine. While NORM NACK-based reliability does not provide explicit flow control, there is some degree of implicit flow control in limiting writing new data to the stream against pending repairs. Other flow control strategies are possible using the NormSetWatermark() function.NormSetWatermark() function.

The NormEvent values NORM_TX_QUEUE_EMPTY and NORM_TX_QUEUE_VACANCY are posted with the NormEvent::object field set to a valid sender stream NormObjectHandle to indicate when the stream is ready for writing via this function. Note that the NORM_TX_QUEUE_VACANCY event type is posted only after the stream's transmit buffer has been completely filled. Thus, the application must make a call to NormStreamWrite() that copies less than the requested numBytes value (return value less than numBytes) before additional NORM_TX_QUEUE_VACANCY events are posted for the given streamHandle (i.e., the event type is not re-posted until the application has again filled the available stream transmit buffer space). By cueing off of NORM_TX_QUEUE_EMPTY, the application can write its "freshest" available data to the stream, but by cueing off of NORM_TX_QUEUE_VACANCY, an application can keep the NORM protocol engine busiest, to achieve the maximum possible throughput at high data rates.

4.4.24.3. Return Values

This function returns the number of bytes of data successfully enqueued for NORM stream transmission. If the underlying send stream buffer is full, this function may return zero or a value less than the requested numBytes.

4.4.25. NormStreamFlush()

4.4.25.1. Synopsis
#include <normApi.h>
+
+void NormStreamFlush(NormObjectHandle streamHandle,
+                     bool             eom = false,
+                     NormFlushMode    flushMode = NORM_FLUSH_PASSIVE);
4.4.25.2. Description

This function causes an immediate "flush" of the transmit stream specified by the streamHandle parameter. Normally, unless NormStreamSetAutoFlush() has been invoked, the NORM protocol engine buffers data written to a stream until it has accumulated a sufficient quantity to generate a NORM_DATA message with a full payload (as designated by the segmentSize parameter of the NormStartSender() call). This results in most efficient operation with respect to protocol overhead. However, for some NORM streams, the application may not wish wait for such accumulation when critical data has been written to a stream. The default stream "flush" operation invoked via NormStreamFlush() for flushMode equal to NORM_FLUSH_PASSIVE causes NORM to immediately transmit all enqueued data for the stream (subject to session transmit rate limits), even if this results in NORM_DATA messages with "small" payloads. If the optional flushMode parameter is set to NORM_FLUSH_ACTIVE, the application can achieve reliable delivery of stream content up to the current write position in an even more proactive fashion. In this case, the sender additionally, actively transmits NORM_CMD(FLUSH) messages after any enqueued stream content has been sent. This immediately prompt receivers for repair requests which reduces latency of reliable delivery, but at a cost of some additional messaging. Note any such "active" flush activity will be terminated upon the next subsequent write to the stream. If flushMode is set to NORM_FLUSH_NONE, this call has no effect other than the optional end-of-message marking described here.

The optional eom parameter, when set to true, allows the sender application to mark an end-of-message indication (see NormStreamMarkEom()) for the stream and initiate flushing in a single function call. The end-of-message indication causes NORM to embed the appropriate message start byte offset in the NORM_DATA message generated following a subsequent write to the stream with the NORM_FLAGS_MSG_START flag. This mechanism provide a means for automatic application message boundary recovery when receivers join or re-sync to a sender mid-stream.

Note that frequent flushing, particularly for NORM_FLUSH_ACTIVE operation, may result in more NORM protocol activity than usual, so care must be taken in application design and deployment when scalability to large group sizes is expected.

4.4.25.3. Return Values

This function has no return values.

4.4.26. NormStreamSetAutoFlush()

4.4.26.1. Synopsis
#include <normApi.h>
+
+void NormStreamSetAutoFlush(NormObjectHandle streamHandle
+                            NormFlushMode    flushMode);
4.4.26.2. Description

This function sets "automated flushing" for the NORM transmit stream indicated by the streamHandle parameter. By default, a NORM transmit stream is "flushed" only when explicitly requested by the application (see NormStreamFlush()). However, to simplify programming, the NORM API allows that automated flushing be enabled such that the "flush" operation occurs every time the full requested buffer provided to a NormStreamWrite() call is successfully enqueued. This may be appropriate for messaging applications where the provided buffers corresponds to an application messages requiring immediate, full transmission. This may make the NORM protocol perhaps more "chatty" than its typical "bulk transfer" form of operation, but can provide a useful capability for some applications.

Possible values for the flushMode parameter include NORM_FLUSH_NONE, NORM_FLUSH_PASSIVE, and NORM_FLUSH_ACTIVE. The default setting for a NORM stream is NORM_FLUSH_NONE where no flushing occurs unless explicitly requested via NormStreamFlush(). By setting the automated flushMode to NORM_FLUSH_PASSIVE, the only action taken is to immediately transmit any data that has been written to the stream, even if "runt" NORM_DATA messages (with payloads less than the NormSender segmentSize parameter) are generated as a result. If NORM_FLUSH_ACTIVE is specified, the automated flushing operation is further augmented with the additional transmission of NORM_CMD(FLUSH) messages to proactively excite the receiver group for repair requests.

4.4.26.3. Return Values

This function has no return values.

4.4.27. NormStreamSetPushEnable()

4.4.27.1. Synopsis
#include <normApi.h>
+
+void NormStreamSetPushEnable(NormObjectHandle streamHandle,
+                             bool             pushEnable);
4.4.27.2. Description

This function controls how the NORM API behaves when the application attempts to enqueue new stream data for transmission when the associated stream's transmit buffer is fully occupied with data pending original or repair transmission. By default (pushEnable = false), a call to NormStreamWrite() will return a zero value under this condition, indicating it was unable to enqueue the new data. However, if pushEnable is set to true for a given streamHandle, the NORM protocol engine will discard the oldest buffered stream data (even if it is pending repair transmission or has never been transmitted) as needed to enqueue the new data. Thus a call to NormStreamWrite() will never fail to copy data. This behavior may be desirable for applications where it is more important to quickly delivery new data than to reliably deliver older data written to a stream. The default behavior for a newly opened stream corresponds to pushEnable equals false. This limits the rate to which an application can write new data to the stream to the current transmission rate and status of the reliable repair process.

4.4.27.3. Return Values

This function has no return values.

4.4.28. NormStreamHasVacancy()

4.4.28.1. Synopsis
#include <normApi.h>
+
+bool NormStreamHasVacancy(NormObjectHandle streamHandle);
4.4.28.2. Description

This function can be used to query whether the transmit stream, specified by the streamHandle parameter, has buffer space available so that the application may successfully make a call to NormStreamWrite(). Normally, a call to NormStreamWrite() itself can be used to make this determination, but this function can be useful when "push mode" has been enabled (see the description of the NormStreamSetPushEnable() function) and the application wants to avoid overwriting data previously written to the stream that has not yet been transmitted. Note that when "push mode" is enabled, a call to NormStreamWrite() will always succeed, overwriting previously-enqueued data if necessary. Normally, this function will return true after a NORM_TX_QUEUE_VACANCY notification has been received for a given NORM stream object.

4.4.28.3. Return Values

This function returns a value of true when there is transmit buffer space to which the application may write and false otherwise.

4.4.29. NormStreamMarkEom()

4.4.29.1. Synopsis
#include <normApi.h>
+
+void NormStreamMarkEom(NormObjectHandle streamHandle);
4.4.29.2. Description

This function allows the application to indicate to the NORM protocol engine that the last data successfully written to the stream indicated by streamHandle corresponded to the end of an application-defined message boundary. The end-of-message indication given here will cause the NORM protocol engine to embed the appropriate message start byte offset in the NORM_DATA message generated that contains the data for the subsequent application call to NormStreamWrite(). Use of this end-of-message marking enables NORM receivers to automatically re-sync to application-defined message boundaries when joining (or re-joining) a NORM session already in progress.

4.4.29.3. Return Values

This function has no return values.

4.4.30. NormSetWatermark()

4.4.30.1. Synopsis
#include <normApi.h>
+
+bool NormSetWatermark(NormSessionHandle sessionHandle,
+                      NormObjectHandle  objectHandle, 
+                      bool              overrideFlush = true);
4.4.30.2. Description

This function specifies a "watermark" transmission point at which NORM sender protocol operation should perform a flushing process and/or positive acknowledgment collection for a given sessionHandle. For NORM_OBJECT_FILE and NORM_OBJECT_DATA transmissions, the positive acknowledgement collection will begin when the specified object has been completely transmitted. The objectHandle parameter must be a valid handle to a previously-created sender object (see NormFileEnqueue(), NormDataEnqueue(), or NormStreamOpen()). For NORM_OBJECT_STREAM transmission, the positive acknowledgment collection begins immediately, using the current position (offset of most recent data written) of the sender stream as a reference.

The functions NormAddAckingNode() and NormRemoveAckingNode() are used to manage the list of NormNodeId values corresponding to NORM receivers that are expected to explicitly acknowledge the watermark flushing messages transmitted by the sender. Note that the NormNodeId NORM_NODE_NONE may be included in the list. Inclusion of NORM_NODE_NONE forces the watermark flushing process to proceed through a full NORM_ROBUST_FACTOR number of rounds before completing, prompting any receivers that have not completed reliable reception to the given watermark point to NACK for any repair needs. If NACKs occur, the flushing process is reset and repeated until completing with no NACKs for data through the given watermark transmission point are received. Thus, even without explicit positive acknowledgment, the sender can use this process (by adding NORM_NODE_NONE to the session's list of "acking nodes") for a high level of assurance that the receiver set is "happy" (completed reliable data reception) through the given object (or stream transmission point).

The event NORM_TX_WATERMARK_COMPLETED is posted for the given session when the flushing process or positive acknowledgment collection has completed. The process completes as soon as all listed receivers have responded unless NORM_NODE_NONE is included in the "acking node" list. The sender application may use the function NormGetAckingStatus() to determine the degree of success of the flushing process in general or for individual NormNodeId values.

The flushing is conducted concurrently with ongoing data transmission and does not impede the progress of reliable data transfer. Thus the sender may still enqueue NormObjects for transmission (or write to the existing stream) and the positive acknowledgement collection and flushing procedure will be multiplexed with the ongoing data transmission. However, the sender application may wish to defer from or limit itself in sending more data until a NORM_TX_WATERMARK_COMPLETED event is received for the given session. This provides a form of sender->receiver(s) flow control which does not exist in NORM's default protocol operation. If a subsequent call is made to NormSetWatermark() before the current acknowledgement request has completed, the pending acknowledgment request is canceled and the new one begins.

The optional overrideFlush parameter, when set to true, causes the watermark acknowledgment process that is established with this function call to potentially fully supersede the usual NORM end-of-transmission flushing process that occurs. If overrideFlush is set and the "watermark" transmission point corresponds to the last transmission that will result from data enqueued by the sending application, then the watermark flush completion will terminate the usual flushing process. I.e., if positive acknowledgement of watermark is received from the full "acking node list", then no further flushing is conducted. Thus, the overrideFlush parameter should only be set when the "acking node list" contains a complete list of intended recipients. This is useful for small receiver groups (or unicast operation) to reduce the "chattiness" of NORM's default end-of-transmission flush process. Note that once the watermark flush is completed and further data enqueued and transmitted, the normal default end-of-transmission behavior will be resumed unless another "watermark" is set with overrideFlush enabled. Thus, as long as new watermarks are established by successive use of this API call, this effectively "morphs" NORM into a protocol driven by positive acknowledgement behavior.

4.4.30.3. Return Values

The function returns true upon successful establishment of the watermark point. The function may return false upon failure.

4.4.31. NormCancelWatermark()

4.4.31.1. Synopsis
#include <normApi.h>
+
+bool NormCancelWatermark(NormSessionHandle sessionHandle);
4.4.31.2. Description

This function cancels any "watermark" acknowledgement request that was previously set via the NormSetWatermark() function for the given sessionHandle. The status of any NORM receivers that may have acknowledged prior to cancellation can be queried using the NormGetAckingStatus() function even after NormCancelWatermark() is called. Typically, applications should wait until a event has been posted, but in some special cases it may be useful to terminate the acknowledgement collection process early.

4.4.31.3. Return Values

The function has no return values.

4.4.32. NormAddAckingNode()

4.4.32.1. Synopsis
#include <normApi.h>
+
+bool NormAddAckingNode(NormSessionHandle sessionHandle,
+                       NormNodeId        nodeId);
4.4.32.2. Description

When this function is called, the specified nodeId is added to the list of NormNodeId values (i.e., the "acking node" list) used when NORM sender operation performs positive acknowledgement (ACK) collection for the specified sessionHandle. The optional NORM positive acknowledgement collection occurs when a specified transmission point (see NormSetWatermark()) is reached or for specialized protocol actions such as positively-acknowledged application-defined commands.

Additionally the special value of nodeId equal to NORM_NODE_NONE may be set to force the watermark flushing process through a full NORM_ROBUST_FACTOR number of rounds regardless of actual acking nodes. Otherwise the flushing process is terminated when all of the nodes in the acking node list have responded. Setting a "watermark" and forcing a full flush process with the special NORM_NODE_NONE value of nodeId enables the resultant NORM_TX_WATERMARK_COMPLETED notification to be a indicator with high (but not absolute) assurance that the receiver set has completed reliable reception of content up through the "watermark" transmission point. This provides a form of scalable reliable multicast "flow control" for NACK-based operation without requiring explicit positive acknowledgement from all group members. Note that the use of the NORM_NODE_NONE value may be mixed with other nodeId for a mix of positive acknowledgement collection from some nodes and a measure of assurance for the group at large.

4.4.32.3. Return Values

The function returns true upon success and false upon failure. The only failure condition is that insufficient memory resources were available. If a specific nodeId is added more than once, this has no effect.

4.4.33. NormRemoveAckingNode()

4.4.33.1. Synopsis
#include <normApi.h>
+
+void NormRemoveAckingNode(NormSessionHandle sessionHandle,
+                          NormNodeId        nodeId);
4.4.33.2. Description

This function deletes the specified nodeId from the list of NormNodeId values used when NORM sender operation performs positive acknowledgement (ACK) collection for the specified sessionHandle. Note that if the special nodeId value "NORM_NODE_NONE" has been added to the list, it too must be explicitly removed to change the watermark flushing behavior if desired.

4.4.33.3. Return Values

The function has no return values.

4.4.34. NormGetNextAckingNode()

4.4.34.1. Synopsis
#include <normApi.h>
+
+NormNodeId NormGetNextAckingNode(NormSessionHandle session, bool reset = false);
4.4.34.2. Description

This function iteratively retrieves the NormNodeId values in the "acking node" list maintained by a NORM sender (see NormAddAckingNode()) for the given sessionHandle. If the optional reset parameter is set to a value of true, the first NormNodeId value in the list is returned and subsequent calls to NormGetNextAckingNode()with the reset parameter set to its default false value will iteratively return the remaining NormNodeId values contained in the list. A value of NORM_NODE_NONE is returned when the end of the list is reached.

The "acking node" list is populated with application calls to NormAddAckingNode() or auto-populated if that optional behavior is set for a NormSession. Note that this API does not enable the programmer to check if the NORM_NODE_NONE value itself is contained in the list. The programmer should keep track of that by other means.

The following code example illustrates how to use this call to iterate through the set of stored NormNodeId values and get the current "acking status" for each:

NormNodeId nextNodeId = NormGetNextAckingNode(session, true);
+while(NORM_NODE_NONE != nextNodeId)
+{
+    NormAckingStatus ackingStatus = NormGetAckingStatus(session, nextNodeId);
+    printf("ACKing node id = %lu acking status = %d\n", nextNodeId, (int)ackingStatus);
+}

As noted below, a good time to check the acking status of the receiver set is after a NORM_TX_WATERMARK_COMPLETED notification has occurred.

4.4.34.3. Return Values

The function iteratively returns NormNodeId values from the given session's local sender "acking node" list. A value of NORM_NODE_NONE is returned when the end of the list is reached.

4.4.35. NormGetAckingStatus()

4.4.35.1. Synopsis
#include <normApi.h>
+
+NormAckingStatus NormGetAckingStatus(NormSessionHandle sessionHandle,
+                                     NormNodeId        nodeId = NORM_NODE_ANY);
4.4.35.2. Description

This function queries the status of the watermark flushing process and/or positive acknowledgment collection initiated by a prior call to NormSetWatermark() for the given sessionHandle. In general, it is expected that applications will invoke this function after the corresponding NORM_TX_WATERMARK_COMPLETED event has been posted. Setting the default parameter value nodeId = NORM_NODE_ANY returns a "status" indication for the overall process. Also, individual nodeId values may be queried using the NormNodeId values of receivers that were included in previous calls to NormAddAckingNode() to populate the sender session's acking node list.

If the flushing/acknowledgment process is being used for application flow control, the sender application may wish to reset the watermark and flushing process (using NormSetWatermark()) if the response indicates that some nodes have failed to respond. However, note that the flushing/acknowledgment process itself does elicit NACKs from receivers as needed and is interrupted and reset by any repair response that occurs. Thus, even by the time the flushing process has completed (and NORM_TX_WATERMARK_COMPLETED is posted) once, this is an indication that the NORM protocol has made a valiant attempt to deliver the content. Resetting the watermark process can increase robustness, but it may be in vain to repeat this process multiple times when likely network connectivity has been lost or expected receivers have failed (dropped out, shut down, etc).

4.4.35.3. Return Values

Possible return values include:

NORM_ACK_INVALID

The given sessionHandle is invalid or the given nodeId is not in the sender's acking list.

NORM_ACK_FAILURE

The positive acknowledgement collection process did not receive acknowledgment from every listed receiver (nodeId = NORM_NODE_ANY) or the identified nodeId did not respond.

NORM_ACK_PENDING

The flushing process at large has not yet completed (nodeId = NORM_NODE_ANY) or the given individual nodeId is still being queried for response.

NORM_ACK_SUCCESS

All receivers (nodeId = NORM_NODE_ANY) responded with positive acknowledgement or the given specific nodeId did acknowledge.

4.4.36. NormSendCommand()

4.4.36.1. Synopsis
#include <normApi.h>
+
+bool NormSendCommand(NormSessionHandle session,
+                     const char*       cmdBuffer,
+                     unsigned int      cmdLength,
+                     bool              robust = false); 
4.4.36.2. Description

This function enqueues a NORM application-defined command for transmission. The cmdBuffer parameter points to a buffer containing the application-defined command content that will be contained in the NORM_CMD(APPLICATION) message payload. The cmdLength indicates the length of this content (in bytes) and MUST be less than or equal to the segmentLength value for the given session (see NormStartSender()). The NORM command transmission will be multiplexed with any NORM data transmission. The command is NOT delivered reliably, but can be optionally transmitted with repetition (once per GRTT) according to the NORM transmit robust factor value (see NormSetTxRobustFactor()) for the given session if the robust parameter is set to true. The command transmission is subject to any congestion control or set rate limits for the NORM session. Once the command has been transmitted (with repetition if robust is set to true), a NORM_TX_CMD_SENT notification is issued. An application can only enqueue a single command at a time (i.e. the NORM_TX_CMD_SENT notification must occur before another command can be sent). The NormCancelCommand() call is available to terminate command transmission if needed. Note that if a rapid succession of commands are sent it is possible that the commands may be delivered to the receivers out-of-order. Also, when repetition is requested (i.e., if robust is set to true) the receiver may receive duplicate copies of the same command. It is up to the application to provide any needed mechanism for detecting and/or filtering duplicate command reception.

The application-defined command feature allows NORM applications to provide some out-of-band (with respect to reliable data delivery) signaling to support session management or other functions. The reception of these "atomic" commands is relatively stateless (as compared to reliable data delivery) and thus it is possible for many senders within a group to send commands without extreme resource burden on receivers (i.e. other participants). Again, this "light-weight" signaling mechanism may be used to provide ancillary communication for the group. In the future, an additional API mechanism will be provided to support application-defined positive acknowledgement requests that could conceivably be used to help guarantee command delivery if desired.

4.4.36.3. Return Values

The function returns true upon success. The function may fail, returning false, if the session is not set for sender operation (see NormStartSender()), the cmdLength exceeds the configured session segmentLength, or a previously-enqueued command has not yet been sent.

4.4.37. NormCancelCommand()

4.4.37.1. Synopsis
#include <normApi.h>
+
+void NormCancelCommand(NormSessionHandle session);
4.4.37.2. Description

This function terminates any pending NORM_CMD(APPLICATION) transmission that was previously initiated with the NormSendCommand() call. Due to the asynchrony of the NORM protocol engine thread and the application, it is possible that the command may have been already sent but the NormCancelCommand() call will ensure a NORM_TX_CMD_SENT notification is not issued for that prior command.

The application-defined command feature allows NORM applications to provide some out-of-band (with respect to reliable data delivery) signaling to support session management or other functions. The reception of these "atomic" commands is relatively stateless (as compared to reliable data delivery) and thus it is possible for many senders within a group to send commands without extreme resource burden on receivers (i.e. other participants). Again, this "light-weight" signaling mechanism may be used to provide ancillary communication for the group. In the future, an additional API mechanism will be provided to support application-defined positive acknowledgement requests that could conceivably be used to help guarantee command delivery if desired.

4.4.37.3. Return Values

The function has not return value.

4.5. NORM Receiver Functions

4.5.1. NormStartReceiver()

4.5.1.1. Synopsis
#include <normApi.h>
+
+bool NormStartReceiver(NormSessionHandle sessionHandle,
+                       unsigned long     bufferSpace);
4.5.1.2. Description

This function initiates the application's participation as a receiver within the NormSession identified by the sessionHandle parameter. The NORM protocol engine will begin providing the application with receiver-related NormEvent notifications, and, unless NormSetSilentReceiver(true) is invoked, respond to senders with appropriate protocol messages. The bufferSpace parameter is used to set a limit on the amount of bufferSpace allocated by the receiver per active NormSender within the session. The appropriate bufferSpace to use is a function of expected network delay*bandwidth product and packet loss characteristics. A discussion of trade-offs associated with NORM transmit and receiver buffer space selection is provided later in this document. An insufficient bufferSpace allocation will result in potentially inefficient protocol operation, even though reliable operation may be maintained. In some cases of a large delay*bandwidth product and/or severe packet loss, a small bufferSpace allocation (coupled with the lack of explicit flow control in NORM) may result in the receiver "re-syncing" to the sender, resulting in "outages" in the reliable transmissions from a sender (this is analogous to a TCP connection timeout failure).

4.5.1.3. Return Values

A value of true is returned upon success and false upon failure. The reasons failure may occur include limited system resources or that the network sockets required for session communication failed to open or properly configure.

4.5.2. NormStopReceiver()

4.5.2.1. Synopsis
#include <normApi.h>
+
+void NormStopReceiver(NormSessionHandle sessionHandle,
+                      unsigned int      gracePeriod = 0);
4.5.2.2. Description

This function ends the application's participation as a receiver in the NormSession specified by the session parameter. By default, all receiver-related protocol activity is immediately halted and all receiver-related resources are freed (except for those which have been specifically retained (see NormNodeRetain() and NormObjectRetain()). However, and optional gracePeriod parameter is provided to allow the receiver an opportunity to inform the group of its intention. This is applicable when the local receiving NormNode has been designated as an active congestion control representative (i.e. current limiting receiver (CLR) or potential limiting receiver (PLR)). In this case, a non-zero gracePeriod value provides an opportunity for the receiver to respond to the applicable sender(s) so the sender will not expect further congestion control feedback from this receiver. The gracePeriod integer value is used as a multiplier with the largest sender GRTT to determine the actual time period for which the receiver will linger in the group to provide such feedback (i.e. "graceTime" = (gracePeriod * GRTT)). During this time, the receiver will not generate any requests for repair or other protocol actions aside from response to applicable congestion control probes. When the receiver is removed from the current list of receivers in the sender congestion control probe messages (or the gracePeriod expires, whichever comes first), the NORM protocol engine will post a NORM_LOCAL_RECEIVER_CLOSED event for the applicable session, and related resources are then freed.

4.5.2.3. Return Values

This function has no return values.

4.5.3. NormSetRxCacheLimit()

4.5.3.1. Synopsis
#include <normApi.h>
+
+void NormSetRxCacheLimit(NormSessionHandle sessionHandle,
+                         unsigned short    countMax);
4.5.3.2. Description

This function sets a limit on the number of outstanding (pending) NormObjects for which a receiver will keep state on a per-sender basis. Note that the value countMax sets a limit on the maximum consecutive range of objects that can be pending. The default value (when this function is not called) of countMax is 256. This should be sufficient for most bulk transfer usage, but if small object sizes (e.g. small NORM_OBJECT_DATA messages) are being transferred, it may be useful to raise this limit in cases of high transmission speeds or large <delay*bandwidth, loss> network conditions. If the receiver cache limit is set too small (i.e. for high speed or large <delay*bandwidth> operation), the receiver may not maintain reliable reception or impact session throughput when flow control is enabled (see NormSetFlowControl()). The maximum allowed value of countMax is 16,384.

If this value is changed after NormStartReceiver() has been called, it will only affect newly-detected remote senders, so this should typically be called before NORM receiver operation is initiated.

4.5.3.3. Return Values

This function has no return value.

4.5.4. NormSetRxSocketBuffer()

4.5.4.1. Synopsis
#include <normApi.h>
+
+bool NormSetRxSocketBuffer(NormSessionHandle sessionHandle,
+                           unsigned int      bufferSize);
4.5.4.2. Description

This function allows the application to set an alternative, non-default buffer size for the UDP socket used by the specified NORM sessionHandle for packet reception. This may be necessary for high speed NORM sessions where the UDP receive socket buffer becomes a bottleneck when the NORM protocol engine (which is running as a user-space process) doesn't get to service the receive socket quickly enough resulting in packet loss when the socket buffer overflows. The bufferSize parameter specifies the socket buffer size in bytes. Different operating systems and sometimes system configurations allow different ranges of socket buffer sizes to be set. Note that a call to NormStartReceiver() (or NormStartSender()) must have been previously made for this call to succeed (i.e., the socket must be already open).

4.5.4.3. Return Values

This function returns true upon success and false upon failure. Possible reasons for failure include, 1) the specified session is not valid, 2) that NORM "receiver" (or "sender") operation has not yet been started for the given session, or 3) an invalid bufferSize specification was given.

4.5.5. NormSetSilentReceiver()

4.5.5.1. Synopsis
#include <normApi.h>
+
+void NormSetSilentReceiver(NormSessionHandle sessionHandle,
+                           bool              silent,
+                           INT32             maxDelay = -1);
4.5.5.2. Description

This function provides the option to configure a NORM receiver application as a "silent receiver". This mode of receiver operation dictates that the host does not generate any protocol messages while operating as a receiver within the specified sessionHandle. Setting the silent parameter to true enables silent receiver operation while setting it to false results in normal protocol operation where feedback is provided as needed for reliability and protocol operation. Silent receivers are dependent upon proactive FEC transmission (see NormSetAutoParity()) or using repair information requested by other non-silent receivers within the group to achieve reliable transfers.

The optional maxDelay parameter is most applicable for reception of the NORM_OBJECT_STREAM type. The default value of maxDelay = -1 corresponds to normal operation where source data segments for incompletely-received FEC coding blocks (or transport objects) are passed to the application only when imposed buffer constraints (either the NORM_OBJECT_STREAM buffer size (see NormStreamOpen()) or the FEC receive buffer limit (see NormStartReceiver()) require. Thus, the default behavior (maxDelay = -1), causes the receiver to buffer received FEC code blocks for as long as possible (within buffer constraints as newer data arrives) before allowing the application to read the data. Hence, the receive latency (delay) can be quite long depending upon buffer size settings, transmission rate, etc. When the maxDelay parameter is set to a non-negative value, the value determines the maximum number of FEC coding blocks (according to a NORM sender's current transmit position) the receiver will cache an incompletely-received FEC block before giving the application the (incomplete) set of received source segments. For example, a value of maxDelay = 0 will provide the receive application with any data from the previous FEC block as soon as a subsequent FEC block is begun reception. However, this provide no protection against the possibility of out-of-order delivery of packets by the network. Therefore, if lower latency operation is desired when using silent receivers, a minimum maxDelay value of 1 is recommended. For NORM_OBJECT_FILE and NORM_OBJECT_DATA, the only impact of a non-negative maxDelay value is that previous transport objects will be immediately aborted when subsequent object begin reception. Thus, it is not usually recommended to apply a non-negative maxDelay value when NORM_OBJECT_STREAM is not being used.

4.5.5.3. Return Values

This function has no return values.

4.5.6. NormSetDefaultUnicastNack()

4.5.6.1. Synopsis
#include <normApi.h>
+
+void NormSetDefaultUnicastNack(NormSessionHandle sessionHandle,
+                               bool              enable);
4.5.6.2. Description

This function controls the default behavior determining the destination of receiver feedback messages generated while participating in the session. If the enable parameter is true, "unicast NACKing" is enabled for new remote senders while it is disabled for state equal to false. The NACKing behavior for current remote senders is not affected. When "unicast NACKing" is disabled (default), NACK messages are sent to the session address (usually a multicast address) and port, but when "unicast NACKing" is enabled, receiver feedback messages are sent to the unicast address (and port) based on the source address of sender messages received. For unicast NORM sessions, it is recommended that "unicast NACKing" be enabled. Note that receiver feedback messages subject to potential "unicast NACKing" include NACK-messages as well as some ACK messages such as congestion control feedback. Explicitly solicited ACK messages, such as those used to satisfy sender watermark acknowledgement requests (see NormSetWatermark()) are always unicast to the applicable sender. (TBD - provide API option so that all messages are multicast.) The default session-wide behavior for unicast NACKing can be overridden via the NormNodeSetUnicastNack() function for individual remote senders.

4.5.6.3. Return Values

This function has no return values.

4.5.7. NormNodeSetUnicastNack()

4.5.7.1. Synopsis
#include <normApi.h>
+
+void NormNodeSetUnicastNack(NormNodeHandle senderNode,
+                            bool           enable);
4.5.7.2. Description

This function controls the destination address of receiver feedback messages generated in response to a specific remote NORM sender corresponding to the senderNode parameter. If enable is true, "unicast NACKing" is enabled while it is disabled for enable equal to false. See the description of NormSetDefaultUnicastNack() for details on "unicast NACKing" behavior.

4.5.7.3. Return Values

This function has no return values.

4.5.8. NormSetDefaultSyncPolicy()

4.5.8.1. Synopsis
#include <normApi.h>
+
+void NormSetDefaultSyncPolicy(NormSessionHandle sessionHandle,
+                              NormSyncPolicy   syncPolicy);
4.5.8.2. Description

This function sets the default "synchronization policy" used when beginning (or restarting) reception of objects from a remote sender (i.e., "syncing" to the sender) for the given sessionHandle. The "synchronization policy" is the behavior observed by the receiver with regards to what objects it attempts to reliably receive (via transmissions of Negative Acknowledgements to the sender(s) or group as needed). There are currently two synchronization policy types defined:

NORM_SYNC_CURRENT

Attempt reception of "current" and new objects only. (default)

NORM_SYNC_ALL

Attempt recovery and reliable reception of all objects held in sender transmit object cache and newer objects.

The behavior of a receiver using the default NORM_SYNC_CURRENT policy is to attempt reliable reception only for the first received "current" and newer (with respect to the ordinal NORM object transport identifiers used by the protocol) objects from a given NORM sender. Additionally, reliable reception is only attempted when receiving a non-repair NORM_DATA message (or optionally a NORM positive acknowledgement request) from the first forward error correction (FEC) encoding block of the given object. This somewhat conservative synchronization behavior helps prevent late-joining (or otherwise "flaky" with respect to group membership) receivers from penalizing other receivers in the group by causing the sender to "rewind" and transmit older object content to satisfy the late joiner instead of moving forward with transmission of new content. For large scale, loosely-organized multicast applications, the NORM_SYNC_CURRENT policy is typically recommended.

The NORM_SYNC_ALL policy allows newly joining receivers much more aggressive behavior as they will immediately NACK for all objects from the "current" object backwards through the entire range of objects set by the NormSetRxCacheLimit() function. This behavior depends upon the sender to issue an appropriate NORM_CMD(SQUELCH) response (if applicable) to align (i.e. "synchronize") the new receiver with its current transmit object cache (similar to a "repair window"). This synchronization behavior may be useful for unicast uses of NORM or other applications where the group membership is more carefully managed and it is important that all content (including older content) is received. Note that the sender transmit cache bounds (see NormSetTxCacheBounds()) and the receiver receive cache limit (see NormSetRxCacheLimit()) settings will limit how far back onto the sender transmission history that transmitted objects can be reliably recovered from the "current" transmission point when the receiver begins reception.

When this function is not invoked, the NORM_SYNC_CURRENT behavior is observed as the default receiver synchronization policy. This call SHOULD be made before NormStartReceiver() is called.

4.5.8.3. Return Values

This function has no return values.

4.5.9. NormSetDefaultNackingMode()

4.5.9.1. Synopsis
#include <normApi.h>
+
+void NormSetDefaultNackingMode(NormSessionHandle sessionHandle,
+                               NormNackingMode   nackingMode);
4.5.9.2. Description

This function sets the default "nacking mode" used when receiving objects for the given sessionHandle. This allows the receiver application some control of its degree of participation in the repair process. By limiting receivers to only request repair of objects in which they are really interested in receiving, some overall savings in unnecessary network loading might be realized for some applications and users. Available nacking modes include:

NORM_NACK_NONE

Do not transmit any repair requests for the newly received object.

NORM_NACK_INFO_ONLY

Transmit repair requests for NORM_INFO content only as needed.

NORM_NACK_NORMAL

Transmit repair requests for entire object as needed.

This function specifies the default behavior with respect to any new sender or object. This default behavior may be overridden for specific sender nodes or specific object using NormNodeSetNackingMode() or NormObjectSetNackingMode(), respectively. The receiver application's use of NORM_NACK_NONE essentially disables a guarantee of reliable reception, although the receiver may still take advantage of sender repair transmissions in response to other receivers' requests. When the sender provides, NORM_INFO content for transmitted objects, the NORM_NACK_INFO_ONLY mode may allows the receiver to reliably receive object context information from which it may choose to "upgrade" its nackingMode for the specific object via the NormObjectSetNackingMode() call. Similarly, the receiver may changes its default nackingMode with respect to specific senders via the NormNodeSetNackingMode() call. The default "default nackingMode" when this call is not made is NORM_NACK_NORMAL.

4.5.9.3. Return Values

This function has no return values.

4.5.10. NormNodeSetNackingMode()

4.5.10.1. Synopsis
#include <normApi.h>
+
+void NormNodeSetNackingMode(NormNodeHandle  nodeHandle,
+                            NormNackingMode nackingMode);
4.5.10.2. Description

This function sets the default "nacking mode" used for receiving new objects from a specific sender as identified by the nodeHandle parameter. This overrides the default nackingMode set for the receive session. See NormSetDefaultNackingMode() for a description of possible nackingMode parameter values and other related information.

4.5.10.3. Return Values

This function has no return values.

4.5.11. NormObjectSetNackingMode()

4.5.11.1. Synopsis
#include <normApi.h>
+
+void NormObjectSetNackingMode(NormObjectHandle objectHandle,
+                              NormNackingMode  nackingMode);
4.5.11.2. Description

This function sets the "nacking mode" used for receiving a specific transport object as identified by the objectHandle parameter. This overrides the default nacking mode set for the applicable sender node. See NormSetDefaultNackingMode() for a description of possible nackingMode parameter values and other related information.

4.5.11.3. Return Values

This function has no return values.

4.5.12. NormSetDefaultRepairBoundary()

4.5.12.1. Synopsis
#include <normApi.h>
+
+void NormSetDefaultRepairBoundary(NormSessionHandle  sessionHandle,
+                                  NormRepairBoundary repairBoundary);
4.5.12.2. Description

This function allows the receiver application to customize, for a given sessionHandle, at what points the receiver initiates the NORM NACK repair process during protocol operation. Normally, the NORM receiver initiates NACKing for repairs at the FEC code block and transport object boundaries. For smaller block sizes, the NACK repair process is often/quickly initiated and the repair of an object will occur, as needed, during the transmission of the object. This default operation corresponds to repairBoundary equal to NORM_BOUNDARY_BLOCK. Using this function, the application may alternatively, setting repairBoundary equal to NORM_BOUNDARY_OBJECT, cause the protocol to defer NACK process initiation until the current transport object has been completely transmitted.

4.5.12.3. Return Values

This function has no return values.

4.5.13. NormNodeSetRepairBoundary()

4.5.13.1. Synopsis
#include <normApi.h>
+
+void NormNodeSetRepairBoundary(NormNodeHandle     nodeHandle,
+                               NormRepairBoundary repairBoundary);
4.5.13.2. Description

This function allows the receiver application to customize, for the specific remote sender referenced by the nodeHandle parameter, at what points the receiver initiates the NORM NACK repair process during protocol operation. See the description of NormSetDefaultRepairBoundary() for further details on the impact of setting the NORM receiver repair boundary and possible values for the repairBoundary parameter.

4.5.13.3. Return Values

This function has no return values.

4.5.14. NormSetDefaultRxRobustFactor()

4.5.14.1. Synopsis
#include <normApi.h>
+
+void NormSetDefaultRxRobustFactor(NormSessionHandle sessionHandle,
+                                  int               rxRobustFactor);
4.5.14.2. Description

This routine controls how persistently NORM receivers will maintain state for sender(s) and continue to request repairs from the sender(s) even when packet reception has ceased. The rxRobustFactor value determines how many times a NORM receiver will self-initiate NACKing (repair requests) upon cessation of packet reception from a sender. The default value is 20. Setting rxRobustFactor to -1 will make the NORM receiver infinitely persistent (i.e., it will continue to NACK indefinitely as long as it is missing data content). It is important to note that the NormSetTxRobustFactor() also affects receiver operation in setting the time interval that is used to gauge that sender packet transmission has ceased (i.e., the sender inactivity timeout). This "timeout" interval is a equal of (2 * GRTT * txRobustFactor). Thus the overall timeout before a NORM receiver quits NACKing is (rxRobustFactor * 2 * GRTT * txRobustFactor).

The NormNodeSetRxRobustFactor() function can be used to control this behavior on a per-sender basis. When a new remote sender is detected, the default rxRobustFactor set here is used. Again, the default value is 20.

4.5.14.3. Return Values

This function has no return values.

4.5.15. NormNodeSetRxRobustFactor()

4.5.15.1. Synopsis
#include <normApi.h>
+
+void NormNodeSetRxRobustFactor(NormNodeHandle nodeHandle,
+                               int            rxRobustFactor);
4.5.15.2. Description

This routine sets the rxRobustFactor as described in NormSetDefaultRxRobustFactor() for an individual remote sender identified by the nodeHandle parameter. See the description of NormSetDefaultRxRobustFactor() for details

4.5.15.3. Return Values

This function has no return values.

4.5.16. NormStreamRead()

4.5.16.1. Synopsis
#include <normApi.h>
+
+bool NormStreamRead(NormObjectHandle streamHandle,
+                    char*            buffer
+                    unsigned int*    numBytes);
4.5.16.2. Description

This function can be used by the receiver application to read any available data from an incoming NORM stream. NORM receiver applications "learn" of available NORM streams via NORM_RX_OBJECT_NEW notification events. The streamHandle parameter here must correspond to a valid NormObjectHandle value provided during such a prior NORM_RX_OBJECT_NEW notification. The buffer parameter must be a pointer to an array where the received data can be stored of a length as referenced by the numBytes pointer. On successful completion, the numBytes storage will be modified to indicate the actual number of bytes copied into the provided buffer. If the numBytes storage is modified to a zero value, this indicates that no stream data was currently available for reading.

Note that NormStreamRead() is never a blocking call and only returns failure (false) when a break in the integrity of the received stream occurs. The NORM_RX_OBJECT_UPDATE provides an indication to when there is stream data available for reading. When such notification occurs, the application should repeatedly read from the stream until the numBytes storage is set to zero, even if a false value is returned. Additional NORM_RX_OBJECT_UPDATE notifications might not be posted until the application has read all available data.

4.5.16.3. Return Values

This function normally returns a value of true. However, if a break in the integrity of the reliable received stream occurs (or the stream has been ended by the sender), a value of false is returned to indicate the break. Unless the stream has been ended (and the receiver application will receive NORM_RX_OBJECT_COMPLETED notification for the stream in that case), the application may continue to read from the stream as the NORM protocol will automatically "resync" to streams, even if network conditions are sufficiently poor that breaks in reliability occur. If such a "break" and "resync" occurs, the application may be able to leverage other NORM API calls such as NormStreamSeekMsgStart() or NormStreamGetReadOffset() if needed to recover its alignment with received stream content. This depends upon the nature of the application and its stream content.

4.5.17. NormStreamSeekMsgStart()

4.5.17.1. Synopsis
#include <normApi.h>
+
+bool NormStreamSeekMsgStart(NormObjectHandle streamHandle);
4.5.17.2. Description

This function advances the read offset of the receive stream referenced by the streamHandle parameter to align with the next available message boundary. Message boundaries are defined by the sender application using the NormStreamMarkEom() call. Note that any received data prior to the next message boundary is discarded by the NORM protocol engine and is not available to the application (i.e., there is currently no "rewind" function for a NORM stream). Also note this call cannot be used to skip messages. Once a valid message boundary is found, the application must read from the stream using NormStreamRead() to further advance the read offset. The current offset (in bytes) for the stream can be retrieved via NormStreamGetReadOffset().

4.5.17.3. Return Values

This function returns a value of true when start-of-message is found. The next call to NormStreamRead() will retrieve data aligned with the message start. If no new message boundary is found in the buffered receive data for the stream, the function returns a value of false. In this case, the application should defer repeating a call to this function until a subsequent NORM_RX_OBJECT_UPDATE notification is posted.

4.5.18. NormStreamGetReadOffset()

4.5.18.1. Synopsis
#include <normApi.h>
+
+unsigned long NormStreamGetReadOffset(NormObjectHandle streamHandle);
4.5.18.2. Description

This function retrieves the current read offset value for the receive stream indicated by the streamHandle parameter. Note that for very long-lived streams, this value may wrap. Thus, in general, applications should not be highly dependent upon the stream offset, but this feature may be valuable for certain applications which associate some application context with stream position.

4.5.18.3. Return Values

This function returns the current read offset in bytes. The return value is undefined for sender streams. There is no error result.

4.6. NORM Object Functions

The functions described in this section may be used for sender or receiver purposes to manage transmission and reception of NORM transport objects. In most cases, the receiver will be the typical user of these functions to retrieve additional information on newly-received objects. All of these functions require a valid NormObjectHandle argument which specifies the applicable object. Note that NormObjectHandle values obtained from a NormEvent notification may be considered valid only until a subsequent call to NormGetNextEvent(), unless explicitly retained by the application (see NormObjectRetain()). NormObjectHandle values obtained as a result of NormFileEnqueue(), NormDataEnqueue(), or NormStreamOpen() calls can be considered valid only until a corresponding NORM_TX_OBJECT_PURGED notification is posted or the object is dequeued using NormObjectCancel(), unless, again, otherwise explicitly retained (see NormObjectRetain()).

4.6.1. NormObjectGetType()

4.6.1.1. Synopsis
#include <normApi.h>
+
+NormObjectType NormObjectGetType(NormObjectHandle objectHandle);
4.6.1.2. Description

This function can be used to determine the object type ((NORM_OBJECT_DAT, NORM_OBJECT_FILE, or NORM_OBJECT_STREAM) for the NORM transport object identified by the objectHandle parameter. The objectHandle must refer to a current, valid transport object.

4.6.1.3. Return Values

This function returns the NORM object type. Valid NORM object types include NORM_OBJECT_DATA, NORM_OBJECT_FILE, or NORM_OBJECT_STREAM. A type value of NORM_OBJECT_NONE will be returned for an objectHandle value of NORM_OBJECT_INVALID.

4.6.2. NormObjectHasInfo()

4.6.2.1. Synopsis
#include <normApi.h>
+
+bool NormObjectHasInfo(NormObjectHandle objectHandle);
4.6.2.2. Description

This function can be used to determine if the sender has associated any NORM_INFO content with the transport object specified by the objectHandle parameter. This can even be used before the NORM_INFO content is delivered to the receiver and a NORM_RX_OBJECT_INFO notification is posted.

4.6.2.3. Return Values

A value of true is returned if NORM_INFO is (or will be) available for the specified transport object. A value of false is returned otherwise.

4.6.3. NormObjectGetInfoLength()

4.6.3.1. Synopsis
#include <normApi.h>
+
+unsigned short NormObjectGetInfoLength(NormObjectHandle objectHandle);
4.6.3.2. Description

This function can be used to determine the length of currently available NORM_INFO content (if any) associated with the transport object referenced by the objectHandle parameter.

4.6.3.3. Return Values

The length of the NORM_INFO content, in bytes, of currently available for the specified transport object is returned. A value of 0 is returned if no NORM_INFO content is currently available or associated with the object.

4.6.4. NormObjectGetInfo()

4.6.4.1. Synopsis
#include <normApi.h>
+
+unsigned short NormObjectGetInfo(NormObjectHandle         objectHandle,
+                                 char*                    buffer,
+                                 unsigned short           bufferLen);
4.6.4.2. Description

This function copies any NORM_INFO content associated (by the sender application) with the transport object specified by objectHandle into the provided memory space referenced by the buffer parameter. The bufferLen parameter indicates the length of the buffer space in bytes. If the provided bufferLen is less than the actual NORM_INFO length, a partial copy will occur. The actual length of NORM_INFO content available for the specified object is returned. However, note that until a NORM_RX_OBJECT_INFO notification is posted to the receive application, no NORM_INFO content is available and a zero result will be returned, even if NORM_INFO content may be subsequently available. The NormObjectHasInfo() call can be used to determine if any NORM_INFO content will ever be available for a specified transport object (i.e., determine if the sender has associated any NORM_INFO with the object in question).

4.6.4.3. Return Values

The actual length of currently available NORM_INFO content for the specified transport object is returned. This function can be used to determine the length of NORM_INFO content for the object even if a NULL buffer value and zero bufferLen is provided. A zero value is returned if NORM_INFO content has not yet been received (or is non-existent) for the specified object.

4.6.5. NormObjectGetSize()

4.6.5.1. Synopsis
#include <normApi.h>
+
+NormSize NormObjectGetSize(NormObjectHandle objectHandle);
4.6.5.2. Description

This function can be used to determine the size (in bytes) of the transport object specified by the objectHandle parameter. NORM can support large object sizes for the NORM_OBJECT_FILE type, so typically the NORM library is built with any necessary, related macros defined such that operating system large file support is enabled (e.g., "#define _FILE_OFFSET_BITS 64" or equivalent). The NormSize type is defined accordingly, so the application should be built with the same large file support configuration.

For objects of type NORM_OBJECT_STREAM, the size returned here corresponds to the stream buffer size set by the sender application when opening the referenced stream object.

4.6.5.3. Return Values

A size of the data content of the specified object, in bytes, is returned. Note that it may be possible that some objects have zero data content, but do have NORM_INFO content available.

4.6.6. NormObjectGetBytesPending()

4.6.6.1. Synopsis
#include <normApi.h>
+
+NormSize NormObjectGetBytesPending(NormObjectHandle objectHandle);
4.6.6.2. Description

This function can be used to determine the progress of reception of the NORM transport object identified by the objectHandle parameter. This function indicates the number of bytes that are pending reception (I.e., when the object is completely received, "bytes pending" will equal ZERO). This function is not necessarily applicable to objects of type NORM_OBJECT_STREAM which do not have a finite size. Note it is possible that this function might also be useful to query the "transmit pending" status of sender objects, but it does not account for pending FEC repair transmissions and thus may not produce useful results for this purpose.

4.6.6.3. Return Values

A number of object source data bytes pending reception (or transmission) is returned.

4.6.7. NormObjectCancel()

4.6.7.1. Synopsis
#include <normApi.h>
+
+void NormObjectCancel(NormObjectHandle objectHandle);
4.6.7.2. Description

This function immediately cancels the transmission of a local sender transport object or the reception of a specified object from a remote sender as specified by the objectHandle parameter. The objectHandle must refer to a currently valid NORM transport object. Any resources used by the transport object in question are immediately freed unless the object has been otherwise retained by the application via the NormObjectRetain() call. Unless the application has retained the object in such fashion, the object in question should be considered invalid and the application must not again reference the objectHandle after this call is made.

If the canceled object is a sender object not completely received by participating receivers, the receivers will be informed of the object's cancellation via the NORM protocol NORM_CMD(SQUELCH) message in response to any NACKs requesting repair or retransmission of the applicable object. In the case of receive objects, the NORM receiver will not make further requests for repair of the indicated object, but furthermore, will acknowledge the object as completed with respect to any associated positive acknowledgement requests (see NormSetWatermark()).

4.6.7.3. Return Values

This function has no return value.

4.6.8. NormObjectRetain()

4.6.8.1. Synopsis
#include <normApi.h>
+
+void NormObjectRetain(NormObjectHandle objectHandle);
4.6.8.2. Description

This function "retains" the objectHandle and any state associated with it for further use by the application even when the NORM protocol engine may no longer require access to the associated transport object. Normally, the application is guaranteed that a given NormObjectHandle is valid only while it is being actively transported by NORM (i.e., for sender objects, from the time an object is created by the application until it is canceled by the application or purged (see the NORM_TX_OBJECT_PURGED notification) by the protocol engine, or, for receiver objects, from the time of the object's NORM_RX_OBJECT_NEW notification until its reception is canceled by the application or a NORM_RX_OBJECT_COMPLETED or NORM_RX_OBJECT_ABORTED notification is posted). Note that an application may refer to a given object after any related notification until the application makes a subsequent call to NormGetNextEvent().

When the application makes a call to NormObjectRetain() for a given objectHandle, the application may use that objectHandle value in any NORM API calls until the application makes a call to NormObjectRelease() for the given object. Note that the application MUST make a corresponding call to NormObjectRelease() for each call it has made to NormObjectRetain() in order to free any system resources (i.e., memory) used by that object. Also note that retaining a receive object also automatically retains any state associated with the NormNodeHandle corresponding to the remote sender of that receive object so that the application may use NORM node API calls for the value returned by NormObjectGetSender() as needed.

4.6.8.3. Return Values

This function has no return value.

4.6.9. NormObjectRelease()

4.6.9.1. Synopsis
#include <normApi.h>
+
+void NormObjectRelease(NormObjectHandle objectHandle);
4.6.9.2. Description

This function complements the NormObjectRetain() call by immediately freeing any resources associated with the given objectHandle, assuming the underlying NORM protocol engine no longer requires access to the corresponding transport object. Note the NORM protocol engine retains/releases state for associated objects for its own needs and thus it is very unsafe for an application to call NormObjectRelease() for an objectHandle for which it has not previously explicitly retained via NormObjectRetain().

4.6.9.3. Return Values

This function has no return value.

4.6.10. NormFileGetName()

4.6.10.1. Synopsis
#include <normApi.h>
+
+bool NormFileGetName(NormObjectHandle objectHandle,
+                     char*            nameBuffer,
+                     unsigned int     bufferLen);
4.6.10.2. Description

This function copies the name, as a NULL-terminated string, of the file object specified by the objectHandle parameter into the nameBuffer of length bufferLen bytes provided by the application. The objectHandle parameter must refer to a valid NormObjectHandle for an object of type NORM_OBJECT_FILE. If the actual name is longer than the provided bufferLen, a partial copy will occur. Note that the file name consists of the entire path name of the specified file object and the application should give consideration to operating system file path lengths when providing the nameBuffer.

4.6.10.3. Return Values

This function returns true upon success and false upon failure. Possible failure conditions include the objectHandle does not refer to an object of type NORM_OBJECT_FILE.

4.6.11. NormFileRename()

4.6.11.1. Synopsis
#include <normApi.h>
+
+bool NormFileRename(NormObjectHandle objectHandle,
+                    const char*      fileName);
4.6.11.2. Description

This function renames the file used to store content for the NORM_OBJECT_FILE transport object specified by the objectHandle parameter. This allows receiver applications to rename (or move) received files as needed. NORM uses temporary file names for received files until the application explicitly renames the file. For example, sender applications may choose to use the NORM_INFO content associated with a file object to provide name and/or typing information to receivers. The fileName parameter must be a NULL-terminated string which should specify the full desired path name to be used. NORM will attempt to create sub-directories as needed to satisfy the request. Note that existing files of the same name may be overwritten.

4.6.11.3. Return Values

This function returns true upon success and false upon failure. Possible failure conditions include the case where the objectHandle does not refer to an object of type NORM_OBJECT_FILE and where NORM was unable to successfully create any needed directories and/or the file itself.

4.6.12. NormDataAccessData()

4.6.12.1. Synopsis
#include <normApi.h>
+
+const char* NormDataAccessData(NormObjectHandle objectHandle);
4.6.12.2. Description

This function allows the application to access the data storage area associated with a transport object of type NORM_OBJECT_DATA. For example, the application may use this function to copy the received data content for its own use. Alternatively, the application may establish "ownership" for the allocated memory space using the NormDataDetachData() function if it is desired to avoid the copy.

If the object specified by the objectHandle parameter has no data content (or is not of type NORM_OBJECT_DATA), a NULL value may be returned. The application MUST NOT attempt to modify the memory space used by NORM_OBJECT_DATA objects during the time an associated objectHandle is valid. The length of data storage area can be determined with a call to NormObjectGetSize() for the same objectHandle value.

4.6.12.3. Return Values

This function returns a pointer to the data storage area for the specified transport object. A NULL value may be returned if the object has no associated data content or is not of type NORM_OBJECT_DATA.

4.6.13. NormDataDetachData()

4.6.13.1. Synopsis
#include <normApi.h>
+
+char* NormDataDetachData(NormObjectHandle objectHandle);
4.6.13.2. Description

This function allows the application to disassociate data storage allocated by the NORM protocol engine for a receive object from the NORM_OBJECT_DATA transport object specified by the objectHandle parameter. It is important that this function is called after the NORM protocol engine has indicated it is finished with the data object (i.e., after a NORM_TX_OBJECT_PURGED, NORM_RX_OBJECT_COMPLETED, or NORM_RX_OBJECT_ABORTED notification event). But the application must call NormDataDetachData() before a call is made to NormObjectCancel() or NormObjectRelease() for the object if it plans to access the data content afterwards. Otherwise, the NORM protocol engine will free the applicable memory space when the associated NORM_OBJECT_DATA transport object is deleted and the application will be unable to access the received data unless it has previously copied the content.

Once the application has used this call to "detach" the data content, it is the application's responsibility to subsequently free the data storage space as needed.

4.6.13.3. Return Values

This function returns a pointer to the data storage area for the specified transport object. A NULL value may be returned if the object has no associated data content or is not of type NORM_OBJECT_DATA.

4.6.14. NormObjectGetSender()

4.6.14.1. Synopsis
#include <normApi.h>
+
+NormNodeHandle NormObjectGetSender(NormObjectHandle objectHandle);
4.6.14.2. Description

This function retrieves the NormNodeHandle corresponding to the remote sender of the transport object associated with the given objectHandle parameter. Note that the returned NormNodeHandle value is only valid for the same period that the objectHandle is valid. The returned NormNodeHandle may optionally be retained for further use by the application using the NormNodeRetain() function call. The returned value can be used in the NORM Node Functions described later in this document.

4.6.14.3. Return Values

This function returns the NormNodeHandle corresponding to the remote sender of the transport object associated with the given objectHandle parameter. A value of NORM_NODE_INVALID is returned if the specified objectHandle references a locally originated, sender object.

4.7. NORM Node Functions

The functions described in this section may be used for NORM sender or receiver (most typically receiver) purposes to retrieve additional information about a remote NormNode, given a valid NormNodeHandle. Note that, unless specifically retained (see NormNodeRetain()), a NormNodeHandle provided in a NormEvent notification should be considered valid only until a subsequent call to NormGetNextEvent() is made. NormNodeHandle values retrieved using NormObjectGetSender() can be considered valid for the same period of time as the corresponding NormObjectHandle is valid.

4.7.1. NormNodeGetId()

4.7.1.1. Synopsis
#include <normApi.h>
+
+NormNodeId NormNodeGetId(NormNodeHandle nodeHandle);
4.7.1.2. Description

This function retrieves the NormNodeId identifier for the remote participant referenced by the given nodeHandle value. The NormNodeId is a 32-bit value used within the NORM protocol to uniquely identify participants within a NORM session. The participants identifiers are assigned by the application or derived (by the NORM API code) from the host computers default IP address.

4.7.1.3. Return Values

This function returns the NormNodeId value associated with the specified nodeHandle. In the case nodeHandle is equal to NORM_NODE_INVALID, the return value will be NORM_NODE_NONE.

4.7.2. NormNodeGetAddress()

4.7.2.1. Synopsis
#include <normApi.h>
+
+bool NormNodeGetAddress(NormNodeHandle          nodeHandle,
+                        char*                   addrBuffer,
+                        unsigned int*           bufferLen,
+                        unsigned short*         port = NULL);
4.7.2.2. Description

This function retrieves the current network source address detected for packets received from remote NORM sender referenced by the nodeHandle parameter. The addrBuffer must be a pointer to storage of bufferLen bytes in length in which the referenced sender node's address will be returned. Optionally, the remote sender source port number (see NormSetTxPort()) is also returned if the optional port pointer to storage parameter is provided in the call. Note that in the case of Network Address Translation (NAT) or other firewall activities, the source address detected by the NORM receiver may not be the original address of the original NORM sender.

4.7.2.3. Return Values

A value of true is returned upon success and false upon failure. An invalid nodeHandle parameter value would lead to such failure.

4.7.3. NormNodeGetGrtt()

4.7.3.1. Synopsis
#include <normApi.h>
+
+double NormNodeGetId(NormNodeHandle nodeHandle);
4.7.3.2. Description

This function retrieves the advertised estimate of group round-trip timing (GRTT) for the remote sender referenced by the given nodeHandle value. Newly-starting senders that have been participating as a receiver within a group may wish to use this function to provide a more accurate startup estimate of GRTT (see NormSetGrttEstimate()) prior to a call to NormStartSender(). Applications may use this information for other purpose as well. Note that the NORM_GRTT_UPDATED event is posted (see NormGetNextEvent()) by the NORM protocol engine to indicate when changes in the local sender or remote senders' GRTT estimate occurs.

4.7.3.3. Return Values

This function returns the remote sender's advertised GRTT estimate in units of seconds. A value of -1.0 is returned upon failure. An invalid nodeHandle parameter value will lead to such failure.

4.7.4. NormNodeGetCommand()

4.7.4.1. Synopsis
#include <normApi.h>
+
+bool NormNodeGetCommand(NormNodeHandle nodeHandle,
+                        char*          buffer,
+                        unsigned int*  buflen);
4.7.4.2. Description

This function retrieves the content of an application-defined command that was received from a remote sender associated with the given nodeHandle. This call should be made in response to the NORM_RX_CMD_NEW notification. This notification is issued for each command received. However the application may use this call to poll for received commands if desired. Additionally, the received command length can be "queried" by setting the value referenced by the buflen parameter to ZERO. Upon return, this value referenced by the buflen parameter is adjusted to reflect the command length. Then a subsequent call to NormNodeGetCommand() can be made with an appropriately-sized buffer to retrieve the received command content. The command size will be less than or equal to the NORM segment size configured for the given remote sender.

Note that if a rapid succession of commands are sent it is possible that the commands may be delivered to the receivers out-of-order. Also, when repetition is requested (i.e., if robust is set to true) the receiver may receive duplicate copies of the same command. It is up to the application to provide any needed mechanism for detecting and/or filtering duplicate command reception.

4.7.4.3. Return Values

This function returns true upon successful retrieval of command content. A return value of false indicates that either no command was available or the provided buffer size (buflen parameter) was inadequate. The value referenced by the buflen parameter is adjusted to indicate the actual command length (in bytes) upon return.

4.7.5. NormNodeFreeBuffers()

4.7.5.1. Synopsis
#include <normApi.h>
+
+void NormNodeFreeBuffers(NormNodeHandle nodeHandle);
4.7.5.2. Description

This function releases memory resources that were allocated for a remote sender. For example, the receiver application may wish to free memory resources when receiving a NORM_REMOTE_SENDER_INACTIVE notification for a given remote sender when multiple senders may be providing content. The NORM protocol engine allocates memory for reliable transport buffering on a per sender basis according to the limit set in the NormStartReceiver() call. These buffering resources comprise the majority of the state allocated for a given remote sender. For NORM applications with possibly multiple senders active at different times, this function can be used to manage to amount of memory allocated for reliable reception. If a sender becomes "active" again after a call to NormNodeFreeBuffers(), new memory resources will be allocated. Note that state for any pending (uncompleted) objects will be dropped when this function is called and the receiver may request retransmission and repair of content if the sender once again becomes "active". The application SHOULD call NormObjectCancel() for any pending objects before calling NormNodeFreeBuffers() if it wishes to never receive those pending objects. Alternatively, a call to NormNodeDelete() will completely eliminate all state for a given remote sender and, if that sender becomes "active" again, it will be treated as a completely new sender.

4.7.5.3. Return Values

This function has no return value.

4.7.6. NormNodeDelete()

4.7.6.1. Synopsis
#include <normApi.h>
+
+void NormNodeDelete(NormNodeHandle nodeHandle);
4.7.6.2. Description

This function can be used by a NORM receiver application to completely remove the state associated with a remote sender for the given nodeHandle. For example, when a NORM_REMOTE_SENDER_INACTIVE notification occurs for a given sender, the application may wish to completely free all associated resources. Note this is distinct from the NormNodeFreeBuffers() call where only the buffering resources are freed and other state pertaining to the sender is kept. If the deleted sender again becomes "active", it will be treated as a brand new sender. Unless explicitly retained with a call to NormNodeRetain(), the nodeHandle should be considered invalid after this call is made. Additionally, any NormObjectHandle values for pending objects from this sender are also invalidated (unless otherwise retained), although NORM_RX_OBJECT_ABORTED notifications may be issued for those pending objects.

4.7.6.3. Return Values

This function has no return value.

4.7.7. NormNodeRetain()

4.7.7.1. Synopsis
#include <normApi.h>
+
+void NormNodeRetain(NormNodeHandle nodeHandle);
4.7.7.2. Description

In the same manner as the NormObjectRetain() function, this function allows the application to retain state associated with a given nodeHandle value even when the underlying NORM protocol engine might normally free the associated state and thus invalidate the NormNodeHandle. If the application uses this function, it must make a corresponding call to NormNodeRelease() when finished with the node information to avoid a memory leak condition. NormNodeHandle values (unless retained) are valid from the time of a NORM_REMOTE_SENDER_NEW notification until a complimentary NORM_REMOTE_SENDER_PURGED notification. During that interval, the application will receive NORM_REMOTE_SENDER_ACTIVE and NORM_REMOTE_SENDER_INACTIVE notifications according to the sender's message transmission activity within the session.

It is important to note that, if the NORM protocol engine posts a NORM_REMOTE_SENDER_PURGED notification for a given NormNodeHandle, the NORM protocol engine could possibly, subsequently establish a new, different NormNodeHandle value for the same remote sender (i.e., one of equivalent NormNodeId) if it again becomes active in the session. A new NormNodeHandle may likely be established even if the application has retained the previous NormNodeHandle value. Therefore, to the application, it might appear that two different senders with the same NormNodeId are participating if these notifications are not carefully monitored. This behavior is contingent upon how the application has configured the NORM protocol engine to manage resources when there is potential for a large number of remote senders within a session (related APIs are TBD). For example, the application may wish to control which specific remote senders for which it keeps state (or limit the memory resources used for remote sender state, etc) and the NORM API may be extended in the future to control this behavior.

4.7.7.3. Return Values

This function has no return value.

4.7.8. NormNodeRelease()

4.7.8.1. Synopsis
#include <normApi.h>
+
+void NormNodeRelease(NormNodeHandle nodeHandle);
4.7.8.2. Description

In complement to the NormNodeRetain() function, this API call releases the specified nodeHandle so that the NORM protocol engine may free associated resources as needed. Once this call is made, the application should no longer reference the specified NormNodeHandle, unless it is still valid.

4.7.8.3. Return Values

This function has no return value.

4.8. NORM Debugging Functions

This section describes some additional function calls that are available to set debugging output options and control other aspects of the NORM implementation.

4.8.1. NormSetDebugLevel()

4.8.1.1. Synopsis
#include <normApi.h>
+
+void NormSetDebugLevel(unsigned int level);
4.8.1.2. Description

This function controls the verbosity of NORM debugging output. Higher values of level result in more detailed output. The highest level of debugging is 12. The debug output consists of text written to STDOUT by default but may be directed to a log file using the NormOpenDebugLog() function.

4.8.1.3. Return Values

This function has no return value.

4.8.2. NormOpenDebugLog()

4.8.2.1. Synopsis
#include <normApi.h>
+
+bool NormOpenDebugLog(NormInstanceHandle instance, const char* fileName);
4.8.2.2. Description

This function allows NORM debug output to be directed to a file instead of the default STDERR.

4.8.2.3. Return Values

The function returns true on success. If the specified file cannot be opened a value of false is returned.

4.8.3. NormCloseDebugLog()

4.8.3.1. Synopsis
#include <normApi.h>
+
+bool NormCloseDebugLog(NormInstanceHandle instance);
4.8.3.2. Description

TBD

4.8.3.3. Return Values

TBD

4.8.4. NormOpenDebugPipe()

4.8.4.1. Synopsis
#include <normApi.h>
+
+bool NormOpenDebugPipe(NormInstanceHandle instance, const char* pipeName);
4.8.4.2. Description

TBD

4.8.4.3. Return Values

TBD

4.8.5. NormCloseDebugPipe()

4.8.5.1. Synopsis
#include <normApi.h>
+
+bool NormCloseDebugPipe(NormInstanceHandle instance);
4.8.5.2. Description

TBD

4.8.5.3. Return Values

TBD