├── .classpath ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── DEBIAN ├── conffiles ├── control ├── postinst └── prerm ├── LICENSE ├── README ├── bpf ├── build.sh ├── build.xml ├── config ├── log4j.properties └── reaper.properties ├── data ├── 100.data ├── 180.data ├── 200.data ├── ack.data ├── bye.data ├── bye200.data ├── invite.data ├── rtcp.data ├── rtcp_receiver_report.data ├── rtcp_sender_report.data └── rtcpxr.data ├── filter.sh ├── lib ├── jain-sip-sdp-1.2.142.jar └── log4j-1.2.15.jar ├── raspberrybuild.xml ├── reaper.sh ├── src └── com │ └── tt │ └── reaper │ ├── CollectorManager.java │ ├── Configuration.java │ ├── Nics.java │ ├── Reaper.java │ ├── ReaperLogger.java │ ├── call │ ├── AudioData.java │ ├── CallContext.java │ ├── CallManager.java │ ├── RtpPacketFactory.java │ ├── RtpStream.java │ ├── State.java │ ├── StateConnected.java │ ├── StateInvited.java │ ├── StateTerminated.java │ └── StateTerminating.java │ ├── filter │ └── FilterExecute.java │ ├── http │ ├── WebHandler.java │ └── WebServer.java │ ├── message │ ├── AckMessage.java │ ├── ByeMessage.java │ ├── CancelMessage.java │ ├── DataRequest.java │ ├── DataResponse.java │ ├── ErrorMessage.java │ ├── InviteMessage.java │ ├── Message.java │ ├── MessageFactory.java │ ├── MessageQueue.java │ ├── NotifyMessage.java │ ├── ProvisionalMessage.java │ ├── PublishMessage.java │ ├── RequestMessage.java │ ├── ResponseMessage.java │ ├── RtpPacket.java │ ├── SipMessage.java │ └── SuccessMessage.java │ ├── rtcp │ ├── DataPacket.java │ ├── ExtendedReportBlock.java │ ├── ReportBlock.java │ ├── RtcpExtendedReport.java │ ├── RtcpPacket.java │ ├── RtcpReceiverReport.java │ ├── RtcpSenderReport.java │ ├── RtcpSourceDescription.java │ └── VoipMetricsExtendedReportBlock.java │ ├── sip │ ├── CollectorListener.java │ ├── CollectorStack.java │ ├── ReaperListener.java │ └── ReaperStack.java │ └── vq │ ├── LocalMetrics.java │ ├── Metrics.java │ ├── RemoteMetrics.java │ ├── VQIntervalReport.java │ ├── VQReportEvent.java │ └── VQSessionReport.java ├── tcmpdump ├── README ├── patch.txt ├── reaper.c ├── reaper.h └── tcpdump-4.1.1.tar.gz └── test └── com └── tt └── reaper ├── TestConfiguration.java ├── TestNics.java ├── call ├── TestCallContext.java ├── TestCallManager.java ├── TestRtpPacketFactory.java ├── TestRtpStream.java ├── TestStateConnected.java ├── TestStateInvited.java └── TestStateTerminating.java ├── filter └── MockPacketFilter.java ├── message ├── TestMessageFactory.java ├── TestPublishMessage.java └── TestRtpPacket.java ├── rtcp └── TestRtcpPacket.java ├── sip ├── MockCollector.java └── TestReaperListener.java ├── testFile └── vq ├── TestMetrics.java └── TestVQReportEvent.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | reaper 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Fri Apr 30 20:57:36 MDT 2010 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.6 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 11 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 12 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 13 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 14 | org.eclipse.jdt.core.compiler.problem.deadCode=warning 15 | org.eclipse.jdt.core.compiler.problem.deprecation=warning 16 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 17 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 18 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 19 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore 20 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 21 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore 22 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled 23 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 24 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 25 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 26 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=ignore 27 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 28 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 29 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore 30 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore 31 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 32 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 33 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore 34 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore 35 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore 36 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning 37 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 38 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 39 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 40 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 41 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 42 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 43 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore 44 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore 45 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore 46 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning 47 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore 48 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 49 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 50 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 51 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 52 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore 53 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 54 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning 55 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore 56 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 57 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore 58 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore 59 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 60 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore 61 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 62 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 63 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled 64 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning 65 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 66 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning 67 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore 68 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 69 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled 70 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled 71 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning 72 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning 73 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 74 | org.eclipse.jdt.core.compiler.source=1.6 75 | -------------------------------------------------------------------------------- /DEBIAN/conffiles: -------------------------------------------------------------------------------- 1 | /opt/reaper/config/reaper.properties 2 | -------------------------------------------------------------------------------- /DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: reaper 2 | Essential: no 3 | Priority: optional 4 | Section: net 5 | Maintainer: Terry Howe 6 | Architecture: i386 7 | Version: 0.0.2 8 | Depends: openjdk-6-jre-headless 9 | Description: The reaper is a probe for gathering voice quality data from SIP calls 10 | -------------------------------------------------------------------------------- /DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | update-rc.d reaper defaults 3 | /etc/init.d/reaper start 4 | -------------------------------------------------------------------------------- /DEBIAN/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /etc/init.d/reaper stop 3 | update-rc.d -f reaper remove 4 | rm -f /etc/init.d/reaper 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Terry Howe 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the FreeBSD Project. 27 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The SIP Voice Quality Report Reaper sniffs RTCP and RTP packets and generates 2 | SIP PUBLISH messages with voice quality reports. It does this in accordance 3 | with the following RFC: 4 | 5 | http://www.rfc-editor.org/rfc/rfc6035.txt 6 | 7 | The tool is designed to sniff packets on a network and generate voice quality 8 | reports most likely to another network. The advantage of using the mode where 9 | RTP packets are sniffed is most devices don't support voice quality reports 10 | and the Reaper can be used to analyze a segment of the network so that 11 | fingers can be pointed. 12 | 13 | There is a shell script to generate an installable deb file and it has been 14 | tested on some Ubuntu distros. 15 | 16 | In order to work properly, the RTP voice packets and the SIP signalling packets 17 | must be on the same network. 18 | 19 | The tool is written in Java with some C code to customize tcpdump so that it 20 | can be used as a Berkeley Packet Filter for the Reaper. 21 | 22 | There is a build.sh shell script to generate an installable package. When you install, it may 23 | prompt you to install sun-java6-bin. If it doesn't, install it if it isn't installed. To install: 24 | 25 | dpkg -i reaper.deb 26 | 27 | After you install the package, you'll need to edit the configuration 28 | in /opt/reaper/config/reaper.properties 29 | 30 | 1) Set readInterface to the interface you want to monitor. e.g. eth0 31 | 2) Set writeIp to the IP for the NIC to write to the Collector 32 | 3) Set CollectorIp to the Collector IP 33 | 4) Set CollectorPort to the Collector port if they aren't using 5060 34 | 5) Set the collectorUsername if the Collector cares what that is. 35 | 36 | After that, restart the reaper: 37 | 38 | /etc/init.d/reaper restart 39 | 40 | Filtered packets get a one line print in /opt/reaper/log/bpf.err Look 41 | at that to make sure it is seeing packets. 42 | 43 | Call state prints in /opt/reaper/log/out Might look at that if you see 44 | packets, but no PUBLISH messages. 45 | 46 | A very pimitive and simple web server is available on http://127.0.0.1:8060/ on the 47 | probe where you can see active calls and some current call stats -------------------------------------------------------------------------------- /bpf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/bpf -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | rm -rf reaper 2 | ant all 3 | chown -R root:root reaper 4 | chmod 775 reaper/DEBIAN 5 | chmod 775 reaper/DEBIAN/* 6 | chmod 755 reaper/etc 7 | chmod 755 reaper/etc/init.d 8 | chmod 755 reaper/etc/init.d/reaper 9 | chmod 755 reaper/opt 10 | chmod 770 reaper/opt/reaper 11 | chmod 770 reaper/opt/reaper/* 12 | chmod 550 reaper/opt/reaper/bin/bpf 13 | chmod 550 reaper/opt/reaper/bin/filter.sh 14 | chmod 770 reaper/opt/reaper/config/* 15 | chmod 550 reaper/opt/reaper/lib/* 16 | dpkg-deb -b reaper 17 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /config/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n -------------------------------------------------------------------------------- /config/reaper.properties: -------------------------------------------------------------------------------- 1 | gov.nist.javax.sip.TRACE_LEVEL=0 2 | gov.nist.javax.sip.SERVER_LOG=server.log 3 | gov.nist.javax.sip.DEBUG_LOG=debug.log 4 | readInterface=eth0 5 | writeInterface=lo 6 | collectorHost=127.0.0.3 7 | collectorPort=5060 8 | collectorUsername=collector 9 | fromUsername=reaper 10 | softwareVersion=ReaperV1.0 11 | -------------------------------------------------------------------------------- /data/100.data: -------------------------------------------------------------------------------- 1 | SIP/2.0 100 Trying 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bK291d90e31a47b225bd0ddff4353e9cc0 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Contact: 7 | Call-ID: 12013223@200.57.7.195 8 | CSeq: 1 INVITE 9 | Server: X-Lite release 1103m 10 | Content-Length: 0 11 | 12 | -------------------------------------------------------------------------------- /data/180.data: -------------------------------------------------------------------------------- 1 | SIP/2.0 180 Ringing 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bK291d90e31a47b225bd0ddff4353e9cc0 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Contact: 7 | Call-ID: 12013223@200.57.7.195 8 | CSeq: 1 INVITE 9 | Server: X-Lite release 1103m 10 | Content-Length: 0 11 | 12 | -------------------------------------------------------------------------------- /data/200.data: -------------------------------------------------------------------------------- 1 | SIP/2.0 200 Ok 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bK291d90e31a47b225bd0ddff4353e9cc0 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Contact: 7 | Call-ID: 12013223@200.57.7.195 8 | CSeq: 1 INVITE 9 | Content-Type: application/sdp 10 | Server: X-Lite release 1103m 11 | Content-Length: 298 12 | 13 | v=0 14 | o=francisco 13004970 13013442 IN IP4 200.57.7.204 15 | s=X-Lite 16 | c=IN IP4 200.57.7.204 17 | t=0 0 18 | m=audio 8000 RTP/AVP 8 0 3 98 97 101 19 | a=rtpmap:0 pcmu/8000 20 | a=rtpmap:8 pcma/8000 21 | a=rtpmap:3 gsm/8000 22 | a=rtpmap:98 iLBC/8000 23 | a=rtpmap:97 speex/8000 24 | a=rtpmap:101 telephone-event/8000 25 | a=fmtp:101 0-15 26 | -------------------------------------------------------------------------------- /data/ack.data: -------------------------------------------------------------------------------- 1 | ACK sip:francisco@200.57.7.204:5061 SIP/2.0 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bK0f496e3b6e7bd140e0b701071c1245ab 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bKa829b54f167d2bb5b96662b4efa0bbc6 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Call-ID: 12013223@200.57.7.195 7 | CSeq: 1 ACK 8 | Contact: 9 | Content-Length: 0 10 | 11 | -------------------------------------------------------------------------------- /data/bye.data: -------------------------------------------------------------------------------- 1 | BYE sip:francisco@200.57.7.204:5061 SIP/2.0 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bK0f496e3b6e7bd140e0b701071c1245ab 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bKa829b54f167d2bb5b96662b4efa0bbc6 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Call-ID: 12013223@200.57.7.195 7 | CSeq: 2 BYE 8 | Contact: 9 | Content-Length: 0 10 | 11 | -------------------------------------------------------------------------------- /data/bye200.data: -------------------------------------------------------------------------------- 1 | SIP/2.0 200 Ok 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bK291d90e31a47b225bd0ddff4353e9cc0 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" ;tag=298852044 6 | Contact: 7 | Call-ID: 12013223@200.57.7.195 8 | CSeq: 2 BYE 9 | Server: X-Lite release 1103m 10 | Content-Length: 0 11 | 12 | -------------------------------------------------------------------------------- /data/invite.data: -------------------------------------------------------------------------------- 1 | INVITE sip:francisco@bestel.com:55060 SIP/2.0 2 | Via: SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290 3 | Via: SIP/2.0/UDP 200.57.7.195:55061;branch=z9hG4bK291d90e31a47b225bd0ddff4353e9cc0 4 | From: ;tag=GR52RWG346-34 5 | To: "francisco@bestel.com" 6 | Call-ID: 12013223@200.57.7.195 7 | CSeq: 1 INVITE 8 | Contact: 9 | Content-Type: application/sdp 10 | Content-Length: 229 11 | 12 | v=0 13 | o=Clarent 120386 120387 IN IP4 200.57.7.196 14 | s=Clarent C5CM 15 | c=IN IP4 200.57.7.196 16 | t=0 0 17 | m=audio 40376 RTP/AVP 8 18 4 0 18 | a=rtpmap:8 PCMA/8000 19 | a=rtpmap:18 G729/8000 20 | a=rtpmap:4 G723/8000 21 | a=rtpmap:0 PCMU/8000 22 | a=SendRecv 23 | -------------------------------------------------------------------------------- /data/rtcp.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/data/rtcp.data -------------------------------------------------------------------------------- /data/rtcp_receiver_report.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/data/rtcp_receiver_report.data -------------------------------------------------------------------------------- /data/rtcp_sender_report.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/data/rtcp_sender_report.data -------------------------------------------------------------------------------- /data/rtcpxr.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/data/rtcpxr.data -------------------------------------------------------------------------------- /filter.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | cd /opt/reaper 3 | /opt/reaper/bin/bpf ${*} >/dev/null 2>/opt/reaper/log/bpf.err -------------------------------------------------------------------------------- /lib/jain-sip-sdp-1.2.142.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/lib/jain-sip-sdp-1.2.142.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.15.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/lib/log4j-1.2.15.jar -------------------------------------------------------------------------------- /raspberrybuild.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 | 99 | 101 | 102 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /reaper.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: reaper 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Start and stop the voice quality reaper 9 | ### END INIT INFO 10 | STARTER=com.tt.reaper.Reaper 11 | 12 | do_start() { 13 | cd /opt/reaper || exit 1 14 | JRE=/usr/lib/jvm/java-6-openjdk/jre/bin/java 15 | nohup $JRE -cp /opt/reaper/lib/reaper.jar:./lib/jain-sip-sdp-1.2.142.jar:./lib/log4j-1.2.15.jar $STARTER >/opt/reaper/log/out 2>/opt/reaper/log/err & 16 | } 17 | do_stop() { 18 | ps -eaf | grep /opt/reaper/bin/bpf | grep -v grep | 19 | while read USER PID ROL 20 | do 21 | kill -9 $PID 22 | done 23 | ps -eaf | grep $STARTER | grep -v grep | 24 | while read USER PID ROL 25 | do 26 | kill -9 $PID 27 | done 28 | } 29 | status() { 30 | LINE=`ps -eaf | grep $STARTER | grep -v grep` 31 | if [ -n "${LINE}" ] 32 | then 33 | LINE=`ps -eaf | grep bpf | grep -v grep` 34 | if [ -n "${LINE}" ] 35 | then 36 | echo $STARTER " process running" 37 | exit 0 38 | fi 39 | fi 40 | echo $STARTER " process not found" 41 | exit 1 42 | } 43 | 44 | case "$1" in 45 | start|restart|force-reload|reload) 46 | do_stop 47 | do_start 48 | ;; 49 | stop) 50 | do_stop 51 | ;; 52 | status) 53 | status 54 | ;; 55 | *) 56 | echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 57 | exit 3 58 | ;; 59 | esac 60 | 61 | : 62 | -------------------------------------------------------------------------------- /src/com/tt/reaper/CollectorManager.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import com.tt.reaper.message.Message; 6 | import com.tt.reaper.message.MessageQueue; 7 | import com.tt.reaper.message.RequestMessage; 8 | import com.tt.reaper.message.ResponseMessage; 9 | import com.tt.reaper.sip.CollectorStack; 10 | 11 | public class CollectorManager extends Thread { 12 | private static Logger logger = Logger.getLogger(CollectorManager.class); 13 | public static CollectorManager instance = new CollectorManager(); 14 | private MessageQueue queue = new MessageQueue(); 15 | private boolean initialized = false; 16 | 17 | private CollectorManager() { 18 | super("CollectorManager"); 19 | } 20 | 21 | public synchronized void init() { 22 | if (initialized == true) 23 | return; 24 | initialized = true; 25 | start(); 26 | } 27 | 28 | public void run() 29 | { 30 | Message message; 31 | while ((message = queue.getBlocking()) != null) { 32 | process(message); 33 | } 34 | } 35 | 36 | void process(Message message) { 37 | logger.info("processRequest: " + message); 38 | if (message instanceof RequestMessage) { 39 | RequestMessage request = (RequestMessage)message; 40 | CollectorStack.instance.sendResponse(new ResponseMessage(request.getRequest())); 41 | } 42 | } 43 | 44 | public void send(Message message) { 45 | queue.add(message); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/tt/reaper/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import java.io.FileInputStream; 4 | import java.util.Properties; 5 | 6 | import org.apache.log4j.Logger; 7 | 8 | public class Configuration extends Properties { 9 | private static final long serialVersionUID = 1L; 10 | private static Logger logger = Logger.getLogger(Configuration.class); 11 | private static final String FILE_NAME = "config/reaper.properties"; 12 | public static final String STACK_NAME = "javax.sip.STACK_NAME"; 13 | public static final String STACK_TRACE_LEVEL = "gov.nist.javax.sip.TRACE_LEVEL"; 14 | public static final String STACK_SERVER_LOG = "gov.nist.javax.sip.SERVER_LOG"; 15 | public static final String STACK_DEBUG_LOG = "gov.nist.javax.sip.DEBUG_LOG"; 16 | private static final String READ_INTERFACE = "readInterface"; 17 | private static final String READ_PORT = "readPort"; 18 | private static final String WRITE_INTERFACE = "writeInterface"; 19 | private static final String FROM_PORT = "fromPort"; 20 | private static final String FROM_USERNAME = "fromUsername"; 21 | private static final String COLLECTOR_HOST = "collectorHost"; 22 | private static final String COLLECTOR_PORT = "collectorPort"; 23 | private static final String COLLECTOR_USERNAME = "collectorUsername"; 24 | private static final String COMMAND_ARGUMENTS = "commandArguments"; 25 | private static final String SOFTWARE_VERSION = "softwareVersion"; 26 | 27 | public Configuration() 28 | { 29 | try { 30 | FileInputStream in = new FileInputStream(FILE_NAME); 31 | load(in); 32 | in.close(); 33 | } 34 | catch (Exception e) 35 | { 36 | logger.error("Error reading properties file"); 37 | } 38 | } 39 | 40 | public void setStackName(String value) { 41 | setProperty(STACK_NAME, value); 42 | } 43 | 44 | public String getStackTraceLevel() { 45 | return getProperty(Configuration.STACK_TRACE_LEVEL, "0"); 46 | } 47 | 48 | public String getStackServerLog() { 49 | return getProperty(Configuration.STACK_SERVER_LOG, "/dev/null"); 50 | } 51 | 52 | public String getStackDebugLog() { 53 | return getProperty(Configuration.STACK_DEBUG_LOG, "/dev/null"); 54 | } 55 | 56 | public String getReadInterface() { 57 | return getProperty(Configuration.READ_INTERFACE, "eth0"); 58 | } 59 | 60 | 61 | public int getReadPort() { 62 | try { 63 | return Integer.parseInt(getProperty(Configuration.READ_PORT, "5050")); 64 | } 65 | catch (Exception e) 66 | { 67 | logger.error("Error reading port configuration: ", e); 68 | } 69 | return 5050; 70 | } 71 | 72 | public String getWriteInterface() { 73 | return Nics.getIp(getProperty(Configuration.WRITE_INTERFACE, "lo")); 74 | } 75 | 76 | public int getWritePort() { 77 | try { 78 | return Integer.parseInt(getProperty(Configuration.FROM_PORT, "5060")); 79 | } 80 | catch (Exception e) 81 | { 82 | logger.error("Error reading port configuration: ", e); 83 | } 84 | return 5060; 85 | } 86 | 87 | public String getWriteUsername() { 88 | return getProperty(Configuration.FROM_USERNAME, "reaper"); 89 | } 90 | 91 | public String getCollectorHost() { 92 | return getProperty(Configuration.COLLECTOR_HOST, "127.0.0.3"); 93 | } 94 | 95 | public int getCollectorPort() { 96 | try { 97 | return Integer.parseInt(getProperty(Configuration.COLLECTOR_PORT, "5060")); 98 | } 99 | catch (Exception e) 100 | { 101 | logger.error("Error reading port configuration: ", e); 102 | } 103 | return 5060; 104 | } 105 | 106 | public String getCollectorUsername() { 107 | return getProperty(Configuration.COLLECTOR_USERNAME, "collector"); 108 | } 109 | 110 | public String getCommand() { 111 | return "/opt/reaper/bin/filter.sh " + getProperty(Configuration.COMMAND_ARGUMENTS, " -n -e ") + " -i " + getReadInterface(); 112 | } 113 | 114 | public String getSoftwareVersion() { 115 | return getProperty(Configuration.SOFTWARE_VERSION, "reaperv1.0"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/com/tt/reaper/Nics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import java.net.Inet4Address; 4 | import java.net.InetAddress; 5 | import java.net.NetworkInterface; 6 | import java.util.ArrayList; 7 | import java.util.Enumeration; 8 | import java.util.List; 9 | 10 | import org.apache.log4j.Logger; 11 | 12 | public class Nics { 13 | private static Logger logger = Logger.getLogger(Nics.class); 14 | 15 | public static String getIp(String name) 16 | { 17 | if (name.equals("lo")) { 18 | logger.warn("Using loopback interface"); 19 | return "127.0.0.2"; 20 | } 21 | 22 | String otherThanIpv4 = null; 23 | List nicList = new ArrayList(); 24 | try { 25 | Enumeration nics = NetworkInterface.getNetworkInterfaces(); 26 | while (nics.hasMoreElements()) { 27 | NetworkInterface ifc = nics.nextElement(); 28 | nicList.add(ifc.getDisplayName()); 29 | if (ifc.getDisplayName().equals(name) == false) 30 | continue; 31 | if (ifc.isUp()) { 32 | Enumeration iface = ifc.getInetAddresses(); 33 | while (iface.hasMoreElements()) { 34 | InetAddress addr = iface.nextElement(); 35 | if (addr instanceof Inet4Address) 36 | { 37 | return addr.getHostAddress(); 38 | } 39 | else 40 | { 41 | otherThanIpv4 = addr.getHostAddress(); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | catch (Exception e) 48 | { 49 | logger.warn("Error getting interfaces: ", e); 50 | } 51 | if (otherThanIpv4 != null) 52 | return otherThanIpv4; 53 | logger.error("Cannot find interface <" + name + "> known nics are: " + nicList); 54 | return "127.0.0.1"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/com/tt/reaper/Reaper.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import com.tt.reaper.call.CallManager; 4 | import com.tt.reaper.filter.FilterExecute; 5 | import com.tt.reaper.http.WebServer; 6 | import com.tt.reaper.sip.CollectorStack; 7 | import com.tt.reaper.sip.ReaperStack; 8 | 9 | public class Reaper { 10 | private static boolean initialized = false; 11 | public static Reaper instance = new Reaper(); 12 | 13 | private Reaper() { 14 | } 15 | 16 | public synchronized void init() { 17 | if (initialized == true) 18 | return; 19 | initialized = true; 20 | ReaperLogger.init(); 21 | CollectorManager.instance.init(); 22 | CallManager.instance.init(); 23 | ReaperStack.instance.init(); 24 | CollectorStack.instance.init(); 25 | FilterExecute.instance.init(); 26 | WebServer.instance.init(); 27 | } 28 | 29 | public static void main(String[] args) { 30 | Reaper.instance.init(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/tt/reaper/ReaperLogger.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import org.apache.log4j.PropertyConfigurator; 4 | 5 | public class ReaperLogger { 6 | private static boolean initialized = false; 7 | private static final String FILE_NAME = "config/log4j.properties"; 8 | 9 | public static synchronized void init() { 10 | if (initialized == true) 11 | return; 12 | initialized = true; 13 | PropertyConfigurator.configure(FILE_NAME); 14 | } 15 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/call/AudioData.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | 4 | public class AudioData extends RtpStream { 5 | public String ipAddy; 6 | public int rtpPort; 7 | public String payloadType; 8 | public String payloadDescription; 9 | public String sampleRate; 10 | public boolean from; 11 | 12 | public AudioData() { 13 | ipAddy = "127.0.0.1"; 14 | rtpPort = 404; 15 | payloadType = "0"; 16 | payloadDescription = "unknown"; 17 | sampleRate = "0"; 18 | from = true; 19 | } 20 | 21 | public boolean equals(String value) { 22 | if (value.equals(getIpRtpPort())) 23 | return true; 24 | return value.equals(getIpRtcpPort()); 25 | } 26 | 27 | public String getIpRtpPort() { 28 | return ipAddy + ":" + rtpPort; 29 | } 30 | 31 | public String getIpRtcpPort() { 32 | return ipAddy + ":" + (rtpPort + 1); 33 | } 34 | 35 | public String toString() { 36 | return ipAddy + ":" + rtpPort + " " + payloadType + " " + payloadDescription + " " + sampleRate + "\n" + 37 | " first=" + getFirst() + " last=" + getLast() + " count=" + getPacketCount() + " lost=" + getPacketLoss() + " dup=" + getDuplicates() + "\n"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/CallContext.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.Iterator; 6 | 7 | import org.apache.log4j.Logger; 8 | 9 | import com.tt.reaper.message.Message; 10 | import com.tt.reaper.rtcp.DataPacket; 11 | import com.tt.reaper.vq.LocalMetrics; 12 | 13 | public class CallContext { 14 | protected static Logger logger = Logger.getLogger(CallContext.class); 15 | public State state = StateInvited.instance; 16 | public Date startTime; 17 | public Date endTime; 18 | public String from; 19 | public String to; 20 | public String callId; 21 | public String fromMac; 22 | public String toMac; 23 | public LocalMetrics localMetrics; 24 | public ArrayList audioFrom = new ArrayList(); 25 | public ArrayList audioTo = new ArrayList(); 26 | 27 | public CallContext() 28 | { 29 | startTime = new Date(); 30 | } 31 | 32 | public boolean process(Message message) { 33 | State previous = state; 34 | state = state.process(this, message); 35 | if (previous != state) 36 | logger.debug(callId + ": Going to " + state); 37 | if (state == StateTerminated.instance) 38 | return false; 39 | return true; 40 | } 41 | 42 | public void unregister() { 43 | Iterator it; 44 | it = audioFrom.iterator(); 45 | while (it.hasNext()) { 46 | AudioData data = it.next(); 47 | CallManager.instance.unregister(data.getIpRtpPort()); 48 | CallManager.instance.unregister(data.getIpRtcpPort()); 49 | } 50 | it = audioTo.iterator(); 51 | while (it.hasNext()) { 52 | AudioData data = it.next(); 53 | CallManager.instance.unregister(data.getIpRtpPort()); 54 | CallManager.instance.unregister(data.getIpRtcpPort()); 55 | } 56 | return; 57 | } 58 | 59 | public AudioData getAudio(String source) 60 | { 61 | Iterator it; 62 | it = audioTo.iterator(); 63 | while (it.hasNext()) { 64 | AudioData data = it.next(); 65 | if (data.equals(source)) 66 | return data; 67 | } 68 | it = audioFrom.iterator(); 69 | while (it.hasNext()) { 70 | AudioData data = it.next(); 71 | if (data.equals(source)) 72 | return data; 73 | } 74 | return null; 75 | } 76 | 77 | public AudioData getFromAudio(String source, String destination) 78 | { 79 | Iterator it; 80 | it = audioFrom.iterator(); 81 | while (it.hasNext()) { 82 | AudioData data = it.next(); 83 | if (data.equals(source)) 84 | return data; 85 | } 86 | it = audioTo.iterator(); 87 | while (it.hasNext()) { 88 | AudioData data = it.next(); 89 | if (data.equals(destination)) 90 | return data; 91 | } 92 | return null; 93 | } 94 | 95 | public AudioData getToAudio(String source, String destination) 96 | { 97 | Iterator it; 98 | it = audioTo.iterator(); 99 | while (it.hasNext()) { 100 | AudioData data = it.next(); 101 | if (data.equals(source)) 102 | return data; 103 | } 104 | it = audioFrom.iterator(); 105 | while (it.hasNext()) { 106 | AudioData data = it.next(); 107 | if (data.equals(destination)) 108 | return data; 109 | } 110 | return null; 111 | } 112 | 113 | public void setAudioFrom(ArrayList list) { 114 | if (list == null) 115 | return; 116 | Iterator it; 117 | it = list.iterator(); 118 | while (it.hasNext()) { 119 | AudioData data = it.next(); 120 | data.from = true; 121 | CallManager.instance.register(this, data.getIpRtpPort()); 122 | CallManager.instance.register(this, data.getIpRtcpPort()); 123 | audioFrom.add(data); 124 | } 125 | } 126 | 127 | public void setAudioTo(ArrayList list) { 128 | if (list == null) 129 | return; 130 | Iterator it; 131 | it = list.iterator(); 132 | while (it.hasNext()) { 133 | AudioData data = it.next(); 134 | data.from = false; 135 | CallManager.instance.register(this, data.getIpRtpPort()); 136 | CallManager.instance.register(this, data.getIpRtcpPort()); 137 | audioTo.add(data); 138 | } 139 | } 140 | 141 | LocalMetrics createMetrics(DataPacket packet) 142 | { 143 | AudioData data = getAudio(packet.getSource()); 144 | if (data == null) { 145 | logger.warn("Unexpected data packet: " + packet); 146 | return null; 147 | } 148 | 149 | if (data.from){ 150 | setFromMac(packet.getSourceMac()); 151 | setToMac(packet.getDestinationMac()); 152 | } 153 | else { 154 | setFromMac(packet.getDestinationMac()); 155 | setToMac(packet.getSourceMac()); 156 | } 157 | localMetrics = new LocalMetrics(this, data); 158 | return localMetrics; 159 | } 160 | 161 | public void setFromMac(String mac) { 162 | fromMac = mac; 163 | } 164 | 165 | public void setToMac(String mac) { 166 | toMac = mac; 167 | } 168 | 169 | public String toString() 170 | { 171 | String response = callId + " START:" + startTime + " FROM:" + from + " TO:" + to + "\n"; 172 | Iterator it; 173 | it = audioTo.iterator(); 174 | while (it.hasNext()) { 175 | AudioData data = it.next(); 176 | response += " TO: " + data.toString(); 177 | } 178 | it = audioFrom.iterator(); 179 | while (it.hasNext()) { 180 | AudioData data = it.next(); 181 | response += " FROM: " + data.toString(); 182 | } 183 | response += "\n"; 184 | return response; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/CallManager.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | 7 | import org.apache.log4j.Logger; 8 | 9 | import com.tt.reaper.message.DataRequest; 10 | import com.tt.reaper.message.DataResponse; 11 | import com.tt.reaper.message.InviteMessage; 12 | import com.tt.reaper.message.Message; 13 | import com.tt.reaper.message.MessageQueue; 14 | import com.tt.reaper.message.NotifyMessage; 15 | import com.tt.reaper.message.RtpPacket; 16 | import com.tt.reaper.message.SipMessage; 17 | 18 | public class CallManager extends Thread { 19 | protected static Logger logger = Logger.getLogger(CallManager.class); 20 | public static CallManager instance = new CallManager(); 21 | private HashMap callIdMap = new HashMap(); 22 | private HashMap rtcpMap = new HashMap(); 23 | private MessageQueue queue = new MessageQueue(); 24 | private RtpPacketFactory factory = new RtpPacketFactory(); 25 | private boolean initialized = false; 26 | 27 | private CallManager() { 28 | super("CallManager"); 29 | } 30 | 31 | public synchronized void init() { 32 | if (initialized == true) 33 | return; 34 | initialized = true; 35 | start(); 36 | } 37 | 38 | public void run() 39 | { 40 | logger.info("Call manager started"); 41 | Message message; 42 | while ((message = queue.getBlocking()) != null) { 43 | process(message); 44 | } 45 | logger.warn("Call manager finished"); 46 | } 47 | 48 | void process(Message message) 49 | { 50 | try { 51 | if (message instanceof NotifyMessage) { 52 | RtpPacket packet; 53 | factory.init(((NotifyMessage)message).getRequest().getRawContent()); 54 | while ((packet = factory.getNext()) != null) 55 | { 56 | CallContext context; 57 | if ((context = rtcpMap.get(packet.getSource())) != null) { 58 | logger.debug("Found source: " + packet.getSource()); 59 | if (context.process(packet) == false) { 60 | logger.warn("Removing rtcp map that should of been removed"); 61 | rtcpMap.remove(packet.getSource()); 62 | } 63 | } 64 | else if ((context = rtcpMap.get(packet.getDestination())) != null) { 65 | logger.debug("Found destination: " + packet.getDestination()); 66 | if (context.process(packet) == false) { 67 | logger.warn("Removing rtcp map that should of been removed"); 68 | rtcpMap.remove(packet.getDestination()); 69 | } 70 | } 71 | else { 72 | logger.warn("RTCP stream not found: " + packet); 73 | } 74 | } 75 | } 76 | if (message instanceof SipMessage) { 77 | SipMessage sipMessage = (SipMessage)message; 78 | CallContext context = callIdMap.get(sipMessage.getCallId()); 79 | if (context == null) { 80 | if (! (sipMessage instanceof InviteMessage)) 81 | return; 82 | context = new CallContext(); 83 | callIdMap.put(sipMessage.getCallId(), context); 84 | logger.info("Creating call context: " + sipMessage.getCallId()); 85 | } 86 | if (context.process(sipMessage) == false) { 87 | callIdMap.remove(context.callId); 88 | context.unregister(); 89 | } 90 | } 91 | else if (message instanceof DataRequest) { 92 | DataRequest request = (DataRequest)message; 93 | request.queue.add(new DataResponse(toString())); 94 | } 95 | else { 96 | logger.error("Unexpected message: " + message); 97 | } 98 | } 99 | catch (Exception e) { 100 | logger.error("Error processing packet: ", e); 101 | } 102 | } 103 | 104 | public CallContext getContextCallId(String callId) { 105 | return callIdMap.get(callId); 106 | } 107 | 108 | final void register(CallContext context, String ipPort) 109 | { 110 | logger.debug("Registering " + ipPort); 111 | rtcpMap.put(ipPort, context); 112 | } 113 | 114 | public final CallContext getContextRtcp(String ipPort) 115 | { 116 | return rtcpMap.get(ipPort); 117 | } 118 | 119 | public final CallContext getContextRtcp(String ip, int port) 120 | { 121 | return rtcpMap.get(ip + ":" + port); 122 | } 123 | 124 | final void unregister(String ipPort) 125 | { 126 | logger.debug("Unregistering " + ipPort); 127 | rtcpMap.remove(ipPort); 128 | } 129 | 130 | final void clearMaps() 131 | { 132 | callIdMap.clear(); 133 | rtcpMap.clear(); 134 | } 135 | 136 | public void send(Message message) { 137 | queue.add(message); 138 | } 139 | 140 | public String toString() 141 | { 142 | String response = ""; 143 | Collection collection = callIdMap.values(); 144 | Iterator it = collection.iterator(); 145 | while (it.hasNext()) { 146 | CallContext context = it.next(); 147 | response += context.toString(); 148 | } 149 | return response; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/RtpPacketFactory.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import com.tt.reaper.message.RtpPacket; 4 | 5 | public class RtpPacketFactory { 6 | String[] lines; 7 | int current; 8 | 9 | void init(byte[] data) 10 | { 11 | lines = null; 12 | if (data == null) 13 | return; 14 | lines = new String(data).split("\n"); 15 | current = 0; 16 | } 17 | 18 | RtpPacket getNext() 19 | { 20 | if (lines == null) 21 | return null; 22 | if (current >= lines.length) 23 | return null; 24 | return new RtpPacket(lines[current++]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/RtpStream.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | public class RtpStream { 4 | static final int SIZE = 128; 5 | private boolean first = true; 6 | private int firstPacket = 0; 7 | private int lastPacket = 0; 8 | private int totalPackets = 0; 9 | private int packetCount = 0; 10 | private int packetLoss = 0; 11 | private int discardCount = 0; 12 | private int transit = 0; 13 | private double jitter = 0.0; 14 | private double minJitter = 0.0; 15 | private double maxJitter = 0.0; 16 | private double totalJitter = 0.0; 17 | private int[] received = new int[SIZE]; 18 | 19 | public RtpStream() 20 | { 21 | for (int j=0; j lastPacket) 54 | { 55 | for (int j=start; j<=0xFFFF; j++) 56 | { 57 | if (received[j%SIZE] != getNextExpected(j)) 58 | addLost(getNextExpected(j)); 59 | } 60 | start = 0; 61 | } 62 | for (int j=start; j<=lastPacket; j++) 63 | { 64 | if (received[j%SIZE] != getNextExpected(j)) 65 | addLost(getNextExpected(j)); 66 | } 67 | } 68 | 69 | public void receive(int packetNumber, int timeStamp, int arrival) 70 | { 71 | ++totalPackets; 72 | if (first) 73 | open(packetNumber, timeStamp, arrival); 74 | if (received[packetNumber % SIZE] != packetNumber) 75 | { 76 | if (received[packetNumber % SIZE] == getNextExpected(packetNumber)) 77 | { 78 | ++discardCount; 79 | return; 80 | } 81 | addLost(packetNumber); 82 | } 83 | ++packetCount; 84 | received[packetNumber % SIZE] = getNextExpected(packetNumber); 85 | lastPacket = packetNumber; 86 | jitter += calculateJitter(timeStamp, arrival); 87 | if (jitter < minJitter) 88 | minJitter = jitter; 89 | if (jitter > maxJitter) 90 | maxJitter = jitter; 91 | totalJitter += jitter; 92 | } 93 | 94 | final double calculateJitter(int timeStamp, int arrival) 95 | { 96 | int xit = arrival - timeStamp; 97 | int d = xit - transit; 98 | transit = xit; 99 | if (d < 0) 100 | d = -d; 101 | return (1.0 / 16.0) * ((double) d - jitter); 102 | } 103 | 104 | final int getFirst() 105 | { 106 | return firstPacket; 107 | } 108 | 109 | final int getLast() 110 | { 111 | return lastPacket; 112 | } 113 | 114 | public int getPacketCount() 115 | { 116 | return packetCount; 117 | } 118 | 119 | public int getPacketLoss() 120 | { 121 | return packetLoss; 122 | } 123 | 124 | final int getDuplicates() { 125 | return discardCount; 126 | } 127 | 128 | public double getLossRate() { 129 | if (packetCount == 0) 130 | return 0.0; 131 | return((256.0 * (double)packetLoss) / (double)packetCount); 132 | } 133 | 134 | public double getDiscardRate() { 135 | if (packetCount == 0) 136 | return 0.0; 137 | return((256.0 * (double)discardCount) / (double)packetCount); 138 | } 139 | 140 | public int getJitter() { 141 | return (int)jitter; 142 | } 143 | 144 | public int getMinJitter() { 145 | return (int) minJitter; 146 | } 147 | 148 | public int getMaxJitter() { 149 | return (int) maxJitter; 150 | } 151 | 152 | public int getMeanJitter() { 153 | return (int)(totalJitter / (long)packetCount); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/State.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import java.util.Iterator; 4 | 5 | import org.apache.log4j.Logger; 6 | 7 | import com.tt.reaper.message.Message; 8 | import com.tt.reaper.message.RtpPacket; 9 | import com.tt.reaper.rtcp.DataPacket; 10 | import com.tt.reaper.rtcp.RtcpExtendedReport; 11 | import com.tt.reaper.rtcp.RtcpPacket; 12 | import com.tt.reaper.rtcp.RtcpReceiverReport; 13 | import com.tt.reaper.rtcp.RtcpSenderReport; 14 | import com.tt.reaper.rtcp.VoipMetricsExtendedReportBlock; 15 | import com.tt.reaper.sip.CollectorStack; 16 | import com.tt.reaper.vq.LocalMetrics; 17 | import com.tt.reaper.vq.VQIntervalReport; 18 | 19 | public abstract class State { 20 | protected static Logger logger = Logger.getLogger(State.class); 21 | 22 | protected State() 23 | { 24 | } 25 | 26 | public void processRtpPacket(CallContext context, RtpPacket packet) { 27 | AudioData data; 28 | data = context.getAudio(packet.getSource()); 29 | if (data == null) 30 | return; 31 | data.receive(packet.getSequenceNumber(), packet.getTimeStamp(), packet.getArrival()); 32 | } 33 | 34 | public void processDataPacket(CallContext context, DataPacket packet) { 35 | Iterator it = packet.getIterator(); 36 | while (it.hasNext()) { 37 | RtcpPacket rtcp = it.next(); 38 | switch (rtcp.getPacketType()) { 39 | case RtcpPacket.TYPE_SOURCE_DESCRIPTION: 40 | break; 41 | case RtcpPacket.TYPE_SENDER_REPORT: 42 | RtcpSenderReport sender = (RtcpSenderReport)rtcp; 43 | for (int j=0; j eit = extendedReport.getIterator(); 67 | while (eit.hasNext()) { 68 | LocalMetrics metrics = context.createMetrics(packet); 69 | if (metrics == null) 70 | break; 71 | metrics.setMetrics(eit.next()); 72 | CollectorStack.instance.sendMessage(new VQIntervalReport(metrics).toString()); 73 | } 74 | break; 75 | case RtcpPacket.TYPE_APPLICATION_DEFINED: 76 | break; 77 | } 78 | } 79 | } 80 | 81 | public String toString() 82 | { 83 | String name = getClass().getName(); 84 | return name.substring(name.lastIndexOf('.')+1); 85 | } 86 | 87 | abstract State process(CallContext context, Message message); 88 | } 89 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/StateConnected.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import com.tt.reaper.message.Message; 4 | import com.tt.reaper.message.RtpPacket; 5 | import com.tt.reaper.message.SipMessage; 6 | import com.tt.reaper.rtcp.DataPacket; 7 | 8 | public class StateConnected extends State { 9 | public static final StateConnected instance = new StateConnected(); 10 | 11 | private StateConnected() 12 | { 13 | } 14 | 15 | State process(CallContext context, Message message) 16 | { 17 | switch (message.getType()) 18 | { 19 | case Message.RTP_PACKET: 20 | processRtpPacket(context, (RtpPacket)message); 21 | break; 22 | case Message.DATA_PACKET: 23 | processDataPacket(context, (DataPacket)message); 24 | break; 25 | case Message.SUCCESS: 26 | case Message.FAILURE: 27 | break; 28 | case Message.INVITE: 29 | case Message.ACK: 30 | context.setAudioFrom(((SipMessage)message).getAudioData()); 31 | logger.warn("Unexpected message in connected: " + message); 32 | break; 33 | case Message.BYE: 34 | return StateTerminating.instance; 35 | } 36 | return this; 37 | } 38 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/call/StateInvited.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import com.tt.reaper.message.InviteMessage; 4 | import com.tt.reaper.message.Message; 5 | import com.tt.reaper.message.RtpPacket; 6 | import com.tt.reaper.message.SipMessage; 7 | 8 | class StateInvited extends State { 9 | public static final StateInvited instance = new StateInvited(); 10 | 11 | private StateInvited() 12 | { 13 | } 14 | 15 | State process(CallContext context, Message message) 16 | { 17 | switch (message.getType()) 18 | { 19 | case Message.INVITE: 20 | InviteMessage invite = (InviteMessage)message; 21 | context.from = invite.getFrom(); 22 | context.to = invite.getTo(); 23 | context.callId = invite.getCallId(); 24 | context.setAudioFrom(invite.getAudioData()); 25 | logger.debug(context.callId + ": Going to " + this); 26 | break; 27 | case Message.PROVISIONAL: 28 | case Message.SUCCESS: 29 | context.setAudioTo(((SipMessage)message).getAudioData()); 30 | break; 31 | case Message.FAILURE: 32 | return StateTerminated.instance; 33 | case Message.ACK: 34 | context.setAudioFrom(((SipMessage)message).getAudioData()); 35 | return StateConnected.instance; 36 | case Message.CANCEL: 37 | case Message.BYE: 38 | return StateTerminated.instance; 39 | case Message.RTP_PACKET: 40 | processRtpPacket(context, (RtpPacket)message); 41 | break; 42 | } 43 | return this; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/tt/reaper/call/StateTerminated.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import com.tt.reaper.message.Message; 4 | 5 | public class StateTerminated extends State { 6 | public static final StateTerminated instance = new StateTerminated(); 7 | 8 | private StateTerminated() 9 | { 10 | } 11 | 12 | State process(CallContext context, Message message) 13 | { 14 | logger.warn("Unexpected message in terminated: " + message); 15 | return this; 16 | } 17 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/call/StateTerminating.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import java.util.Iterator; 4 | 5 | import com.tt.reaper.message.Message; 6 | import com.tt.reaper.message.RtpPacket; 7 | import com.tt.reaper.sip.CollectorStack; 8 | import com.tt.reaper.vq.LocalMetrics; 9 | import com.tt.reaper.vq.VQSessionReport; 10 | 11 | public class StateTerminating extends State { 12 | public static final StateTerminating instance = new StateTerminating(); 13 | 14 | private StateTerminating() 15 | { 16 | } 17 | 18 | State process(CallContext context, Message message) 19 | { 20 | switch (message.getType()) 21 | { 22 | case Message.SUCCESS: 23 | case Message.FAILURE: 24 | Iterator it; 25 | it = context.audioFrom.iterator(); 26 | while (it.hasNext()) { 27 | AudioData data = it.next(); 28 | data.close(); 29 | LocalMetrics metrics = new LocalMetrics(context, data); 30 | metrics.setPacketLoss(data.getLossRate()); 31 | metrics.setDelay(data.getJitter()); 32 | VQSessionReport report = new VQSessionReport(metrics); 33 | CollectorStack.instance.sendMessage(report.toString()); 34 | } 35 | it = context.audioTo.iterator(); 36 | while (it.hasNext()) { 37 | AudioData data = it.next(); 38 | data.close(); 39 | LocalMetrics metrics = new LocalMetrics(context, data); 40 | metrics.setPacketLoss(data.getLossRate()); 41 | metrics.setDelay(data.getJitter()); 42 | VQSessionReport report = new VQSessionReport(metrics); 43 | CollectorStack.instance.sendMessage(report.toString()); 44 | } 45 | return StateTerminated.instance; 46 | case Message.INVITE: 47 | case Message.ACK: 48 | logger.warn("Unexpected message in terminating: " + message); 49 | break; 50 | case Message.BYE: 51 | break; 52 | case Message.RTP_PACKET: 53 | processRtpPacket(context, (RtpPacket)message); 54 | break; 55 | } 56 | return this; 57 | } 58 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/filter/FilterExecute.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.filter; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | import com.tt.reaper.Configuration; 6 | 7 | public class FilterExecute extends Thread { 8 | private static Logger logger = Logger.getLogger(FilterExecute.class); 9 | public static final FilterExecute instance = new FilterExecute(); 10 | private String command; 11 | private boolean running = false; 12 | 13 | private FilterExecute() { 14 | } 15 | 16 | public synchronized void init() { 17 | command = new Configuration().getCommand(); 18 | if (command == null) { 19 | logger.error("Filter command not configured"); 20 | } 21 | if (running == false) 22 | start(); 23 | running = true; 24 | } 25 | 26 | public void run() { 27 | try { 28 | logger.info("Running filter: " + command); 29 | Process p = Runtime.getRuntime().exec(command); 30 | p.waitFor(); 31 | logger.warn("Filter exited with return value: " + p.exitValue()); 32 | } catch (Exception e) { 33 | logger.error("Error running filter: ", e); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/com/tt/reaper/http/WebHandler.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.http; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | import com.sun.net.httpserver.HttpExchange; 7 | import com.sun.net.httpserver.HttpHandler; 8 | import com.tt.reaper.call.CallManager; 9 | import com.tt.reaper.message.DataRequest; 10 | import com.tt.reaper.message.DataResponse; 11 | import com.tt.reaper.message.Message; 12 | import com.tt.reaper.message.MessageQueue; 13 | 14 | public class WebHandler implements HttpHandler { 15 | 16 | @Override 17 | public void handle(HttpExchange xchange) throws IOException { 18 | String body = "Active Calls

Active Calls

"; 19 | body += "
";
20 | 		MessageQueue queue = new MessageQueue();
21 | 		CallManager.instance.send(new DataRequest(queue));
22 | 		Message response = queue.getBlocking();
23 | 		if (response instanceof DataResponse)
24 | 		{
25 | 			body += ((DataResponse)response).data.replaceAll("<", "<").replaceAll(">", ">");
26 | 		}
27 | 		body += "
"; 28 | xchange.sendResponseHeaders(200, body.length()); 29 | OutputStream outputStream = xchange.getResponseBody(); 30 | outputStream.write(body.getBytes()); 31 | outputStream.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/tt/reaper/http/WebServer.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.http; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | import org.apache.log4j.Logger; 6 | 7 | import com.sun.net.httpserver.HttpServer; 8 | import com.tt.reaper.call.CallContext; 9 | 10 | public class WebServer { 11 | protected static Logger logger = Logger.getLogger(CallContext.class); 12 | public static WebServer instance = new WebServer(); 13 | private HttpServer server; 14 | 15 | private WebServer() 16 | { 17 | 18 | } 19 | 20 | public void init() 21 | { 22 | try { 23 | server = HttpServer.create(new InetSocketAddress("127.0.0.1", 8060), 10); 24 | server.createContext("/", new WebHandler()); 25 | server.start(); 26 | } 27 | catch (Exception e) 28 | { 29 | logger.error("Error starting web server"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/AckMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Request; 4 | 5 | public class AckMessage extends RequestMessage { 6 | public AckMessage(Request request) { 7 | super(Message.ACK, request); 8 | } 9 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/message/ByeMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Request; 4 | 5 | public class ByeMessage extends RequestMessage { 6 | public ByeMessage(Request request) { 7 | super(Message.BYE, request); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/CancelMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Request; 4 | 5 | public class CancelMessage extends RequestMessage { 6 | public CancelMessage(Request request) { 7 | super(Message.CANCEL, request); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/DataRequest.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | public class DataRequest extends Message { 4 | public MessageQueue queue; 5 | 6 | public DataRequest(MessageQueue queue) { 7 | super(Message.DATA_REQUEST); 8 | this.queue = queue; 9 | } 10 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/message/DataResponse.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | public class DataResponse extends Message { 4 | public String data; 5 | 6 | public DataResponse(String data) { 7 | super(Message.DATA_REQUEST); 8 | this.data = data; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Response; 4 | 5 | public class ErrorMessage extends ResponseMessage { 6 | private ErrorMessage(Response response) 7 | { 8 | super(Message.FAILURE, response); 9 | } 10 | 11 | static SipMessage create(Response response) 12 | { 13 | if (response.getStatusCode() >= 300) { 14 | return new ErrorMessage(response); 15 | } 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/InviteMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Request; 4 | 5 | public class InviteMessage extends RequestMessage { 6 | public InviteMessage() 7 | { 8 | super(Message.INVITE, Request.INVITE, getNewCallId()); 9 | status = init(); 10 | } 11 | 12 | public InviteMessage(Request request) { 13 | super(Message.INVITE, request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/Message.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | public abstract class Message { 6 | protected static Logger logger = Logger.getLogger(Message.class); 7 | public static final int PROVISIONAL = 180; 8 | public static final int SUCCESS = 200; 9 | public static final int FAILURE = 400; 10 | public static final int ACK = 1; 11 | public static final int BYE = 2; 12 | public static final int INVITE = 3; 13 | public static final int PUBLISH = 4; 14 | public static final int NOTIFY = 5; 15 | public static final int CANCEL = 6; 16 | public static final int DATA_RESPONSE = 996; 17 | public static final int DATA_REQUEST = 997; 18 | public static final int RTP_PACKET = 998; 19 | public static final int DATA_PACKET = 999; 20 | 21 | private int type; 22 | 23 | protected Message(int type) 24 | { 25 | this.type = type; 26 | } 27 | 28 | public final int getType() 29 | { 30 | return type; 31 | } 32 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/message/MessageFactory.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Request; 4 | import javax.sip.message.Response; 5 | 6 | public class MessageFactory { 7 | public SipMessage create(Request request) 8 | { 9 | String eventName = request.getMethod(); 10 | 11 | if (Request.ACK.equals(eventName)) 12 | { 13 | return new AckMessage(request); 14 | } 15 | if (Request.BYE.equals(eventName)) 16 | { 17 | return new ByeMessage(request); 18 | } 19 | if (Request.CANCEL.equals(eventName)) 20 | { 21 | return new CancelMessage(request); 22 | } 23 | if (Request.INFO.equals(eventName)) 24 | { 25 | return null; 26 | } 27 | if (Request.INVITE.equals(eventName)) 28 | { 29 | return new InviteMessage(request); 30 | } 31 | if (Request.MESSAGE.equals(eventName)) 32 | { 33 | return null; 34 | } 35 | if (Request.NOTIFY.equals(eventName)) 36 | { 37 | return new NotifyMessage(request); 38 | } 39 | if (Request.OPTIONS.equals(eventName)) 40 | { 41 | return null; 42 | } 43 | if (Request.PRACK.equals(eventName)) 44 | { 45 | return null; 46 | } 47 | if (Request.PUBLISH.equals(eventName)) 48 | { 49 | return new PublishMessage(request); 50 | } 51 | if (Request.REFER.equals(eventName)) 52 | { 53 | return null; 54 | } 55 | if (Request.REGISTER.equals(eventName)) 56 | { 57 | return null; 58 | } 59 | if (Request.SUBSCRIBE.equals(eventName)) 60 | { 61 | return null; 62 | } 63 | if (Request.UPDATE.equals(eventName)) 64 | { 65 | return null; 66 | } 67 | 68 | return null; 69 | } 70 | 71 | public SipMessage create(Response response) 72 | { 73 | SipMessage message; 74 | if ((message = SuccessMessage.create(response)) != null) 75 | return message; 76 | if ((message = ProvisionalMessage.create(response)) != null) 77 | return message; 78 | if ((message = ErrorMessage.create(response)) != null) 79 | return message; 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | import org.apache.log4j.Logger; 8 | 9 | public class MessageQueue 10 | { 11 | private static Logger logger = Logger.getLogger(MessageQueue.class); 12 | protected List queue; 13 | 14 | public MessageQueue() 15 | { 16 | queue = Collections.synchronizedList(new LinkedList()); 17 | } 18 | 19 | public Message getBlocking() 20 | { 21 | try 22 | { 23 | synchronized (queue) 24 | { 25 | while (queue.isEmpty()) 26 | queue.wait(); 27 | return (Message) queue.remove(0); 28 | } 29 | } 30 | catch (Exception e) { 31 | logger.error("Error with queue: ", e); 32 | } 33 | return null; 34 | } 35 | 36 | public Message get() 37 | { 38 | try 39 | { 40 | return (Message) queue.remove(0); 41 | } 42 | catch (Exception e) {} 43 | return null; 44 | } 45 | 46 | public void add(Message e) 47 | { 48 | synchronized (queue) 49 | { 50 | queue.add(e); 51 | queue.notify(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/message/NotifyMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.header.ContentTypeHeader; 4 | import javax.sip.message.Request; 5 | 6 | public class NotifyMessage extends RequestMessage { 7 | public NotifyMessage(Request request) { 8 | super(Message.NOTIFY, request); 9 | } 10 | 11 | public NotifyMessage(String data) 12 | { 13 | super(SipMessage.NOTIFY, Request.NOTIFY, getNewCallId()); 14 | init(data); 15 | } 16 | 17 | private boolean init(String data) 18 | { 19 | if (super.init() == false) 20 | return false; 21 | try { 22 | ContentTypeHeader contentTypeHeader; 23 | contentTypeHeader = headerFactory 24 | .createContentTypeHeader("application", "text/plain"); 25 | request.setContent(data, contentTypeHeader); 26 | return true; 27 | } 28 | catch (Exception e) 29 | { 30 | logger.error("Error adding content: ", e); 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/ProvisionalMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Response; 4 | 5 | public class ProvisionalMessage extends ResponseMessage { 6 | private ProvisionalMessage(Response response) 7 | { 8 | super(SipMessage.PROVISIONAL, response); 9 | } 10 | 11 | static SipMessage create(Response response) 12 | { 13 | if (response.getStatusCode() < 200) { 14 | return new ProvisionalMessage(response); 15 | } 16 | return null; 17 | } 18 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/message/PublishMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.header.ContentTypeHeader; 4 | import javax.sip.message.Request; 5 | 6 | public class PublishMessage extends RequestMessage { 7 | 8 | public PublishMessage(String data) 9 | { 10 | super(SipMessage.PUBLISH, Request.PUBLISH, getNewCallId()); 11 | status = init(data); 12 | } 13 | 14 | public PublishMessage(Request request) { 15 | super(SipMessage.PUBLISH, request); 16 | status = true; 17 | } 18 | 19 | private boolean init(String data) 20 | { 21 | if (super.init() == false) 22 | return false; 23 | try { 24 | ContentTypeHeader contentTypeHeader; 25 | contentTypeHeader = headerFactory 26 | .createContentTypeHeader("application", "vq-rtcpxr"); 27 | request.setContent(data, contentTypeHeader); 28 | return true; 29 | } 30 | catch (Exception e) 31 | { 32 | logger.error("Error adding content: ", e); 33 | } 34 | return false; 35 | } 36 | 37 | public boolean getStatus() { 38 | return status; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/RequestMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import gov.nist.javax.sip.message.SIPMessage; 4 | 5 | import java.util.ArrayList; 6 | 7 | import javax.sip.address.Address; 8 | import javax.sip.address.SipURI; 9 | import javax.sip.header.CSeqHeader; 10 | import javax.sip.header.CallIdHeader; 11 | import javax.sip.header.ContactHeader; 12 | import javax.sip.header.FromHeader; 13 | import javax.sip.header.MaxForwardsHeader; 14 | import javax.sip.header.ToHeader; 15 | import javax.sip.header.ViaHeader; 16 | import javax.sip.message.Request; 17 | 18 | public class RequestMessage extends SipMessage { 19 | protected boolean status = false; 20 | Request request; 21 | String message; 22 | CallIdHeader callIdHeader; 23 | 24 | RequestMessage(int type, String message, CallIdHeader callIdHeader) 25 | { 26 | super(type); 27 | this.message = message; 28 | this.callIdHeader = callIdHeader; 29 | } 30 | 31 | RequestMessage(int type, Request request) 32 | { 33 | super(type); 34 | this.request = request; 35 | } 36 | 37 | boolean init() 38 | { 39 | try { 40 | SipURI from = addressFactory.createSipURI(getFromUsername(), getFromHost() 41 | + ":" + getFromPort()); 42 | Address fromNameAddress = addressFactory.createAddress(from); 43 | fromNameAddress.setDisplayName(getFromUsername()); 44 | FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, 45 | getSoftwareVersion()); 46 | 47 | SipURI toAddress = addressFactory.createSipURI(getToUsername(), getToHost() 48 | + ":" + getToPort()); 49 | Address toNameAddress = addressFactory.createAddress(toAddress); 50 | toNameAddress.setDisplayName(getToUsername()); 51 | ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null); 52 | 53 | SipURI requestURI = addressFactory.createSipURI(getToUsername(), getToHost() 54 | + ":" + getToPort()); 55 | requestURI.setTransportParam("udp"); 56 | 57 | ArrayList viaHeaders = new ArrayList(); 58 | ViaHeader viaHeader = headerFactory.createViaHeader(getFromHost(), 59 | getFromPort(), "udp", null); 60 | viaHeaders.add(viaHeader); 61 | 62 | CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, message); 63 | 64 | MaxForwardsHeader maxForwards = headerFactory 65 | .createMaxForwardsHeader(70); 66 | request = messageFactory.createRequest(requestURI, 67 | message, callIdHeader, cSeqHeader, fromHeader, 68 | toHeader, viaHeaders, maxForwards); 69 | 70 | SipURI contactURI = addressFactory.createSipURI(getFromUsername(), 71 | getFromHost()); 72 | contactURI.setPort(getFromPort()); 73 | Address contactAddress = addressFactory.createAddress(contactURI); 74 | contactAddress.setDisplayName(getFromUsername()); 75 | ContactHeader contactHeader = headerFactory 76 | .createContactHeader(contactAddress); 77 | request.addHeader(contactHeader); 78 | } 79 | catch (Exception e) 80 | { 81 | logger.error("Error initializing stack: ", e); 82 | return false; 83 | } 84 | return true; 85 | } 86 | 87 | public final Request getRequest() { 88 | return request; 89 | } 90 | 91 | public final String getFrom() 92 | { 93 | FromHeader header = (FromHeader) request.getHeader(FromHeader.NAME); 94 | if (header == null) 95 | return "null"; 96 | return header.getAddress().toString(); 97 | } 98 | 99 | public final String getTo() 100 | { 101 | ToHeader header = (ToHeader) request.getHeader(ToHeader.NAME); 102 | if (header == null) 103 | return "null"; 104 | return header.getAddress().toString(); 105 | } 106 | 107 | public final String getCallId() 108 | { 109 | CallIdHeader header = (CallIdHeader) request.getHeader(CallIdHeader.NAME); 110 | if (header == null) 111 | return "null"; 112 | return header.getCallId(); 113 | } 114 | 115 | public final SIPMessage getMessage() { 116 | return (SIPMessage)request; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/ResponseMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import gov.nist.javax.sip.message.SIPMessage; 4 | import gov.nist.javax.sip.message.SIPResponse; 5 | 6 | import javax.sip.header.CallIdHeader; 7 | import javax.sip.header.ToHeader; 8 | import javax.sip.message.Request; 9 | import javax.sip.message.Response; 10 | 11 | public class ResponseMessage extends SipMessage { 12 | protected Response response; 13 | private Request request; 14 | 15 | protected ResponseMessage(int type, Response response) 16 | { 17 | super(type); 18 | this.response = response; 19 | } 20 | 21 | public ResponseMessage(Request request) { 22 | super(Message.SUCCESS); 23 | try { 24 | this.request = request; 25 | response = messageFactory.createResponse(200, request); 26 | ToHeader toHeader = (ToHeader)response.getHeader(ToHeader.NAME); 27 | toHeader.setTag("888"); 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | public ResponseMessage(int type) { 34 | super(type); 35 | response = new SIPResponse(); 36 | } 37 | 38 | public Response getResponse() { 39 | return response; 40 | } 41 | 42 | public Request getRequest() { 43 | return request; 44 | } 45 | 46 | public final String getCallId() 47 | { 48 | CallIdHeader header = (CallIdHeader) response.getHeader(CallIdHeader.NAME); 49 | if (header == null) 50 | return "null"; 51 | return header.getCallId(); 52 | } 53 | 54 | public final SIPMessage getMessage() { 55 | return (SIPMessage)response; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/RtpPacket.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | 4 | public class RtpPacket extends Message { 5 | private String sourceMacAddress; 6 | private String sourceIPAddress; 7 | private int sourcePort; 8 | private String destinationMacAddress; 9 | private String destinationIPAddress; 10 | private int destinationPort; 11 | private int sequenceNumber; 12 | private int timeStamp; 13 | private int arrival; 14 | 15 | public RtpPacket(String value) { 16 | super(Message.RTP_PACKET); 17 | String[] fields = value.split(";"); 18 | if (fields.length < 9) { 19 | logger.error("Failed to parse content"); 20 | return; 21 | } 22 | sourceMacAddress = fields[0]; 23 | sourceIPAddress = fields[1]; 24 | sourcePort = Integer.parseInt(fields[2]); 25 | destinationMacAddress = fields[3]; 26 | destinationIPAddress = fields[4]; 27 | destinationPort = Integer.parseInt(fields[5]); 28 | sequenceNumber = Integer.parseInt(fields[6]) & 0xFFFF; 29 | timeStamp = Integer.parseInt(fields[7]); 30 | arrival = Integer.parseInt(fields[8]); 31 | } 32 | 33 | public String getSource() { 34 | return sourceIPAddress + ":" + sourcePort; 35 | } 36 | 37 | public String getDestination() { 38 | return destinationIPAddress + ":" + destinationPort; 39 | } 40 | 41 | public String toString() { 42 | return "RtpPacket(" + getSource() + "," + getDestination() + ")"; 43 | } 44 | 45 | public String getSourceMac() { 46 | return sourceMacAddress; 47 | } 48 | 49 | public String getDestinationMac() { 50 | return destinationMacAddress; 51 | } 52 | 53 | public int getSequenceNumber() { 54 | return sequenceNumber; 55 | } 56 | 57 | public int getTimeStamp() { 58 | return timeStamp; 59 | } 60 | 61 | public int getArrival() { 62 | return arrival; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/SipMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import gov.nist.javax.sdp.MediaDescriptionImpl; 4 | import gov.nist.javax.sdp.fields.AttributeField; 5 | import gov.nist.javax.sdp.fields.MediaField; 6 | import gov.nist.javax.sip.message.SIPMessage; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Iterator; 10 | import java.util.Vector; 11 | 12 | import javax.sdp.SdpFactory; 13 | import javax.sdp.SessionDescription; 14 | import javax.sip.SipFactory; 15 | import javax.sip.address.AddressFactory; 16 | import javax.sip.header.CallIdHeader; 17 | import javax.sip.header.ContentTypeHeader; 18 | import javax.sip.header.HeaderFactory; 19 | import javax.sip.message.MessageFactory; 20 | 21 | import com.tt.reaper.Configuration; 22 | import com.tt.reaper.call.AudioData; 23 | import com.tt.reaper.sip.CollectorStack; 24 | 25 | public abstract class SipMessage extends Message { 26 | protected static AddressFactory addressFactory; 27 | protected static HeaderFactory headerFactory; 28 | protected static MessageFactory messageFactory; 29 | protected static SdpFactory sdpFactory; 30 | private static int fromPort; 31 | private static String fromHost; 32 | private static String fromUsername; 33 | private static String toHost; 34 | private static String toPort; 35 | private static String toUsername; 36 | private static String softwareVersion; 37 | 38 | protected SipMessage(int type) 39 | { 40 | super(type); 41 | } 42 | 43 | public static boolean initFactory(SipFactory sipFactory, Configuration configuration) 44 | { 45 | try { 46 | headerFactory = sipFactory.createHeaderFactory(); 47 | addressFactory = sipFactory.createAddressFactory(); 48 | messageFactory = sipFactory.createMessageFactory(); 49 | sdpFactory = SdpFactory.getInstance(); 50 | fromPort = configuration.getWritePort(); 51 | fromHost = configuration.getWriteInterface(); 52 | fromUsername = configuration.getWriteUsername(); 53 | toHost = configuration.getCollectorHost(); 54 | toPort = "" + configuration.getCollectorPort(); 55 | toUsername = configuration.getCollectorUsername(); 56 | softwareVersion = configuration.getSoftwareVersion(); 57 | return true; 58 | } 59 | catch (Exception e) 60 | { 61 | logger.error("Error initializing stack: ", e); 62 | } 63 | return false; 64 | } 65 | 66 | void setSdp(String sdpContent) { 67 | try { 68 | ContentTypeHeader contentTypeHeader; 69 | contentTypeHeader = headerFactory 70 | .createContentTypeHeader("application", "sdp"); 71 | getMessage().setContent(sdpContent, contentTypeHeader); 72 | } 73 | catch (Exception e) 74 | { 75 | e.printStackTrace(); 76 | } 77 | } 78 | 79 | protected static CallIdHeader getNewCallId() { 80 | return CollectorStack.instance.getNewCallId(); 81 | } 82 | 83 | protected final int getFromPort() { 84 | return fromPort; 85 | } 86 | 87 | protected final String getFromHost() { 88 | return fromHost; 89 | } 90 | 91 | protected final String getFromUsername() { 92 | return fromUsername; 93 | } 94 | 95 | protected final String getToHost() { 96 | return toHost; 97 | } 98 | 99 | protected final String getToPort() { 100 | return toPort; 101 | } 102 | 103 | protected final String getToUsername() { 104 | return toUsername; 105 | } 106 | 107 | protected final String getSoftwareVersion() { 108 | return softwareVersion; 109 | } 110 | 111 | public abstract String getCallId(); 112 | public abstract SIPMessage getMessage(); 113 | 114 | @SuppressWarnings("unchecked") 115 | public final ArrayList getAudioData() 116 | { 117 | if (getMessage().getRawContent() == null) 118 | return null; 119 | ArrayList list = new ArrayList(); 120 | String content = new String(getMessage().getRawContent()); 121 | logger.debug("content=<" + content + ">"); 122 | try { 123 | SessionDescription sdp = sdpFactory.createSessionDescription(content); 124 | String ipAddy = sdp.getConnection().getAddress(); 125 | Vector descriptors = (Vector)sdp.getMediaDescriptions(true); 126 | Iterator it = descriptors.iterator(); 127 | while (it.hasNext()) { 128 | MediaDescriptionImpl mediaDescription = it.next(); 129 | MediaField field = mediaDescription.getMediaField(); 130 | if (field == null) { 131 | logger.warn("Missing media field"); 132 | continue; 133 | } 134 | AudioData audio = new AudioData(); 135 | audio.ipAddy = ipAddy; 136 | audio.rtpPort = field.getPort(); 137 | Vector formats = field.getFormats(); 138 | if (formats == null) { 139 | list.add(audio); 140 | continue; 141 | } 142 | if (formats.size() < 1) { 143 | list.add(audio); 144 | continue; 145 | } 146 | audio.payloadType = (String) field.getFormats().get(0); 147 | 148 | Vector attributes = mediaDescription.getAttributeFields(); 149 | if (attributes == null) { 150 | list.add(audio); 151 | continue; 152 | } 153 | Iterator ait = attributes.iterator(); 154 | while (ait.hasNext()) { 155 | Object objay = ait.next(); 156 | if (! (objay instanceof AttributeField)) 157 | continue; 158 | AttributeField afield = (AttributeField)objay; 159 | if (! "rtpmap".equals(afield.getName())) 160 | continue; 161 | String [] parsed = afield.getValue().split("[ /]"); 162 | if (parsed.length < 3) 163 | continue; 164 | if (! parsed[0].equals(audio.payloadType)) 165 | continue; 166 | audio.payloadDescription = parsed[1]; 167 | audio.sampleRate = parsed[2]; 168 | } 169 | list.add(audio); 170 | logger.info(getCallId() + " audio=" + audio.toString()); 171 | } 172 | return list; 173 | } catch (Exception e) { 174 | e.printStackTrace(); 175 | } 176 | return list; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/com/tt/reaper/message/SuccessMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import javax.sip.message.Response; 4 | 5 | public class SuccessMessage extends ResponseMessage { 6 | private SuccessMessage(Response response) 7 | { 8 | super(Message.SUCCESS, response); 9 | } 10 | 11 | static SipMessage create(Response response) 12 | { 13 | int status = response.getStatusCode(); 14 | if ((status >= 200) && (status < 300)) { 15 | return new SuccessMessage(response); 16 | } 17 | return null; 18 | } 19 | 20 | public Response getResponse() 21 | { 22 | return response; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/DataPacket.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | 6 | import com.tt.reaper.message.Message; 7 | 8 | public class DataPacket extends Message { 9 | private String sourceMacAddress; 10 | private String sourceIPAddress; 11 | private int sourcePort; 12 | private String destinationMacAddress; 13 | private String destinationIPAddress; 14 | private int destinationPort; 15 | private ArrayList list = new ArrayList(); 16 | 17 | public DataPacket(byte[] content) { 18 | super(Message.DATA_PACKET); 19 | if (content == null) 20 | return; 21 | String header = new String(content); 22 | String[] fields = header.split(";"); 23 | if (fields.length < 6) { 24 | logger.error("Failed to parse content"); 25 | return; 26 | } 27 | sourceMacAddress = fields[0]; 28 | sourceIPAddress = fields[1]; 29 | sourcePort = Integer.parseInt(fields[2]); 30 | destinationMacAddress = fields[3]; 31 | destinationIPAddress = fields[4]; 32 | destinationPort = Integer.parseInt(fields[5]); 33 | 34 | String encodedData = fields[6]; 35 | byte[] rtcpPacket = new byte[encodedData.length()/2]; 36 | int index = 0; 37 | for (int j=0; j getIterator() { 72 | return list.iterator(); 73 | } 74 | 75 | public String getSource() { 76 | return sourceIPAddress + ":" + sourcePort; 77 | } 78 | 79 | public String getDestination() { 80 | return destinationIPAddress + ":" + destinationPort; 81 | } 82 | 83 | public String toString() { 84 | return "DataPacket(" + getSource() + "," + getDestination() + ")"; 85 | } 86 | 87 | public String getSourceMac() { 88 | return sourceMacAddress; 89 | } 90 | 91 | public String getDestinationMac() { 92 | return destinationMacAddress; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/ExtendedReportBlock.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | public class ExtendedReportBlock { 4 | public static final int LOSS_RLE = 1; 5 | public static final int DUPLICATE_RLE = 2; 6 | public static final int PACKET_RECEIPT_TIMES = 3; 7 | public static final int RECEIVER_REFERENCE_TIME = 4; 8 | public static final int DLRR = 5; 9 | public static final int STATISTICS_SUMMARY = 6; 10 | public static final int VOIP_METRICS = 7; 11 | public static final int HEADER_LENGTH = 4; 12 | 13 | protected byte [] data; 14 | protected int offset; 15 | 16 | public ExtendedReportBlock(byte[] data, int offset) { 17 | this.data = data; 18 | this.offset = offset; 19 | } 20 | 21 | static int probeBlockType(byte[] data, int offset) { 22 | return (data[offset + 0] & 0xFF); 23 | } 24 | 25 | public int getBlockType() 26 | { 27 | return data[offset + 0] & 0xFF; 28 | } 29 | 30 | public int getTypeSpecific() 31 | { 32 | return data[offset + 1] & 0xFF; 33 | } 34 | 35 | public int getBlockLength() 36 | { 37 | return (RtcpPacket.getInt(data, offset) & 0xFFFF) * 4; 38 | } 39 | 40 | public int getLength() { 41 | return getBlockLength() + HEADER_LENGTH; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/ReportBlock.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | public class ReportBlock { 4 | private byte [] data; 5 | private int start; 6 | 7 | public ReportBlock(byte [] data, int start) 8 | { 9 | this.data = data; 10 | this.start = start; 11 | } 12 | 13 | public int getSSRC() { 14 | return RtcpPacket.getInt(data, start + 0); 15 | } 16 | 17 | public int getFractionLost() { 18 | return data[start + 4]; 19 | } 20 | 21 | public int getCumulativeLost() { 22 | return (RtcpPacket.getInt(data, start + 4) & 0xFFFFFF); 23 | } 24 | 25 | public int getExtendedHighestSequenceReceived() { 26 | return RtcpPacket.getInt(data, start + 8); 27 | } 28 | 29 | public int getJitter() 30 | { 31 | return RtcpPacket.getInt(data, start + 12); 32 | } 33 | 34 | public int getLastReport() { 35 | return RtcpPacket.getInt(data, start + 16); 36 | } 37 | 38 | public int getDelaySinceLastReport() { 39 | return RtcpPacket.getInt(data, start + 20); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/RtcpExtendedReport.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | 6 | public class RtcpExtendedReport extends RtcpPacket { 7 | private static final int HEADER_LENGTH = 8; 8 | private ArrayList list = new ArrayList(); 9 | 10 | public RtcpExtendedReport(byte[] data, int offset) { 11 | super(data, offset); 12 | int start = offset + HEADER_LENGTH; 13 | int end = offset + getLength(); 14 | while (start < end) { 15 | ExtendedReportBlock block = null; 16 | switch (ExtendedReportBlock.probeBlockType(data, start)) { 17 | case ExtendedReportBlock.VOIP_METRICS: 18 | block = new VoipMetricsExtendedReportBlock(data, start); 19 | list.add((VoipMetricsExtendedReportBlock) block); 20 | break; 21 | default: 22 | logger.info("Ignoring RTCP-XR: " + ExtendedReportBlock.probeBlockType(data, start)); 23 | block = new ExtendedReportBlock(data, start); 24 | break; 25 | } 26 | start += block.getLength(); 27 | } 28 | } 29 | 30 | public int getSSRC() 31 | { 32 | return getInt(data, offset + 4); 33 | } 34 | 35 | public Iterator getIterator() { 36 | return list.iterator(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/RtcpPacket.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | import org.apache.log4j.Logger; 4 | 5 | public class RtcpPacket { 6 | protected static Logger logger = Logger.getLogger(RtcpPacket.class); 7 | public static final int HEADER_SIZE = 8; 8 | public static final int SENDER_INFO_SIZE = 20; 9 | public static final int REPORT_SIZE = 24; 10 | public static final int TYPE_SENDER_REPORT = 0xC8; 11 | public static final int TYPE_RECEIVER_REPORT = 0xC9; 12 | public static final int TYPE_SOURCE_DESCRIPTION = 0xCA; 13 | public static final int TYPE_GOODBYE = 0xCB; 14 | public static final int TYPE_APPLICATION_DEFINED = 0xCC; 15 | public static final int TYPE_EXTENDED_REPORT = 0xCF; 16 | 17 | protected byte[] data; 18 | protected int offset; 19 | 20 | RtcpPacket(byte[] data, int offset) { 21 | this.data = data; 22 | this.offset = offset; 23 | } 24 | 25 | static int probePacketType(byte[] data, int offset) { 26 | return (data[offset + 1] & 0xFF); 27 | } 28 | 29 | static int getInt(byte[] data, int offset) { 30 | long value; 31 | value = data[offset++] & 0xFF; 32 | value <<= 8; 33 | value |= data[offset++] & 0xFF; 34 | value <<= 8; 35 | value |= data[offset++] & 0xFF; 36 | value <<= 8; 37 | value |= data[offset++] & 0xFF; 38 | return (int)value; 39 | } 40 | 41 | public int getVersion() { 42 | return (data[offset + 0] >> 6) & 0x03; 43 | } 44 | 45 | public int getCount() { 46 | return (data[offset + 0] & 0x1F); 47 | } 48 | 49 | public int getPacketType() { 50 | return (data[offset + 1] & 0xFF); 51 | } 52 | 53 | public int getLength() 54 | { 55 | return (((data[offset + 2] << 8) | data[offset + 3]) + 1) * 4; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/RtcpReceiverReport.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | public class RtcpReceiverReport extends RtcpPacket { 4 | 5 | public RtcpReceiverReport(byte[] data, int offset) { 6 | super(data, offset); 7 | } 8 | 9 | public int getSSRC() 10 | { 11 | return getInt(data, offset + 4); 12 | } 13 | 14 | public ReportBlock getReport(int number) 15 | { 16 | return new ReportBlock(data, ((offset + HEADER_SIZE) + (number * REPORT_SIZE))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/RtcpSenderReport.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | 4 | public class RtcpSenderReport extends RtcpPacket { 5 | 6 | public RtcpSenderReport(byte[] data, int offset) { 7 | super(data, offset); 8 | } 9 | 10 | public int getSSRC() 11 | { 12 | return getInt(data, offset + 4); 13 | } 14 | 15 | public long getTimeStamp() { 16 | int start = offset + 8; 17 | long value; 18 | value = data[start++] & 0xFF; 19 | value <<= 8; 20 | value |= data[start++] & 0xFF; 21 | value <<= 8; 22 | value |= data[start++] & 0xFF; 23 | value <<= 8; 24 | value |= data[start++] & 0xFF; 25 | value <<= 8; 26 | value |= data[start++] & 0xFF; 27 | value <<= 8; 28 | value |= data[start++] & 0xFF; 29 | value <<= 8; 30 | value |= data[start++] & 0xFF; 31 | value <<= 8; 32 | value |= data[start++] & 0xFF; 33 | return value; 34 | } 35 | 36 | public int getRTPTimeStamp() 37 | { 38 | return getInt(data, offset + 16); 39 | } 40 | 41 | public int getSenderPacketCount() 42 | { 43 | return getInt(data, offset + 20); 44 | } 45 | 46 | public int getSenderOctetCount() 47 | { 48 | return getInt(data, offset + 24); 49 | } 50 | 51 | public ReportBlock getReport(int number) 52 | { 53 | return new ReportBlock(data, ((offset + HEADER_SIZE + SENDER_INFO_SIZE) + (number * REPORT_SIZE))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/RtcpSourceDescription.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | public class RtcpSourceDescription extends RtcpPacket { 4 | 5 | public RtcpSourceDescription(byte[] data, int offset) { 6 | super(data, offset); 7 | } 8 | } -------------------------------------------------------------------------------- /src/com/tt/reaper/rtcp/VoipMetricsExtendedReportBlock.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | public class VoipMetricsExtendedReportBlock extends ExtendedReportBlock { 4 | 5 | public VoipMetricsExtendedReportBlock(byte[] data, int offset) { 6 | super(data, offset); 7 | } 8 | 9 | public double getLossRate() 10 | { 11 | return ((double)(data[offset + 8] & 0xFF)) / 256.0; 12 | } 13 | 14 | public double getDiscardRate() 15 | { 16 | return ((double)(data[offset + 9] & 0xFF)) / 256.0; 17 | } 18 | 19 | public double getBurstDensity() 20 | { 21 | return ((double)(data[offset + 10] & 0xFF)) / 256.0; 22 | } 23 | 24 | public double getGapDensity() 25 | { 26 | return ((double)(data[offset + 11] & 0xFF)) / 256.0; 27 | } 28 | 29 | public int getBurstDuration() 30 | { 31 | return ((RtcpPacket.getInt(data, offset + 12) >> 16) & 0xFFFF); 32 | } 33 | 34 | public int getGapDuration() 35 | { 36 | return (RtcpPacket.getInt(data, offset + 12) & 0xFFFF); 37 | } 38 | 39 | public int getRoundTripDelay() 40 | { 41 | return ((RtcpPacket.getInt(data, offset + 16) >> 16) & 0xFFFF); 42 | } 43 | 44 | public int getEndSystemDelay() 45 | { 46 | return (RtcpPacket.getInt(data, offset + 16) & 0xFFFF); 47 | } 48 | 49 | public int getSignalLevel() 50 | { 51 | return data[offset + 20]; 52 | } 53 | 54 | public int getNoiseLevel() 55 | { 56 | return data[offset + 21]; 57 | } 58 | 59 | public int getResidualEchoReturnLoss() 60 | { 61 | return data[offset + 22] & 0xFF; 62 | } 63 | 64 | public int getGmin() 65 | { 66 | return data[offset + 23] & 0xFF; 67 | } 68 | 69 | public int getRFactor() 70 | { 71 | return data[offset + 24] & 0xFF; 72 | } 73 | 74 | public int getExtRFactor() 75 | { 76 | return data[offset + 25] & 0xFF; 77 | } 78 | 79 | public double getMOSLQ() 80 | { 81 | return ((double)(data[offset + 26] & 0xFF)) / 10.0; 82 | } 83 | 84 | public double getMOSCQ() 85 | { 86 | return ((double)(data[offset + 27] & 0xFF)) / 10.0; 87 | } 88 | 89 | private int getRxConfig() 90 | { 91 | return data[offset + 28] & 0xFF; 92 | } 93 | 94 | public int getPacketLossConcealment() 95 | { 96 | return (getRxConfig() >> 6) & 0x03; 97 | } 98 | 99 | public int getJitterBufferAdaptive() 100 | { 101 | return (getRxConfig() >> 4) & 0x03; 102 | } 103 | 104 | public int getJitterBufferRate() 105 | { 106 | return getRxConfig() & 0x0F; 107 | } 108 | 109 | public int getJitterBufferNominal() 110 | { 111 | return (RtcpPacket.getInt(data, offset + 28) & 0xFFFF); 112 | } 113 | 114 | public int getJitterBufferMaximum() 115 | { 116 | return ((RtcpPacket.getInt(data, offset + 32) >> 16) & 0xFFFF); 117 | } 118 | 119 | public int getJitterBufferAbsMaximum() 120 | { 121 | return (RtcpPacket.getInt(data, offset + 32) & 0xFFFF); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/com/tt/reaper/sip/CollectorListener.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import javax.sip.DialogTerminatedEvent; 4 | import javax.sip.IOExceptionEvent; 5 | import javax.sip.RequestEvent; 6 | import javax.sip.ResponseEvent; 7 | import javax.sip.SipListener; 8 | import javax.sip.TimeoutEvent; 9 | import javax.sip.TransactionTerminatedEvent; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | import com.tt.reaper.CollectorManager; 14 | import com.tt.reaper.message.MessageFactory; 15 | import com.tt.reaper.message.SipMessage; 16 | 17 | class CollectorListener implements SipListener { 18 | private static Logger logger = Logger.getLogger(CollectorListener.class); 19 | private MessageFactory factory = new MessageFactory(); 20 | 21 | CollectorListener() { 22 | } 23 | 24 | @Override 25 | public void processDialogTerminated(DialogTerminatedEvent evt) { 26 | logger.info("processDialogTerminated"); 27 | } 28 | 29 | @Override 30 | public void processIOException(IOExceptionEvent evt) { 31 | logger.info("processIOException"); 32 | } 33 | 34 | @Override 35 | public void processRequest(RequestEvent evt) { 36 | SipMessage message = factory.create(evt.getRequest()); 37 | CollectorManager.instance.send(message); 38 | } 39 | 40 | @Override 41 | public void processResponse(ResponseEvent evt) { 42 | SipMessage message = factory.create(evt.getResponse()); 43 | CollectorManager.instance.send(message); 44 | } 45 | 46 | @Override 47 | public void processTimeout(TimeoutEvent evt) { 48 | logger.info("processTimeout"); 49 | } 50 | 51 | @Override 52 | public void processTransactionTerminated(TransactionTerminatedEvent evt) { 53 | logger.info("processTransactionTerminated"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/com/tt/reaper/sip/CollectorStack.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import javax.sip.ListeningPoint; 4 | import javax.sip.ServerTransaction; 5 | import javax.sip.SipFactory; 6 | import javax.sip.SipProvider; 7 | import javax.sip.SipStack; 8 | import javax.sip.header.CallIdHeader; 9 | 10 | import org.apache.log4j.Logger; 11 | 12 | import com.tt.reaper.Configuration; 13 | import com.tt.reaper.message.PublishMessage; 14 | import com.tt.reaper.message.RequestMessage; 15 | import com.tt.reaper.message.ResponseMessage; 16 | 17 | public class CollectorStack { 18 | private static final String STACK_NAME = "CollectorStack"; 19 | private static Logger logger = Logger.getLogger(CollectorStack.class); 20 | public static CollectorStack instance = new CollectorStack(); 21 | private SipProvider collectorProvider; 22 | private static boolean initialized = false; 23 | public String lastSendData; 24 | 25 | private CollectorStack() 26 | { 27 | } 28 | 29 | public synchronized boolean init() 30 | { 31 | if (initialized == true) 32 | return true; 33 | initialized = true; 34 | logger.info("Starting the collector stack..."); 35 | try { 36 | SipStack sipStack; 37 | Configuration configuration; 38 | configuration = new Configuration(); 39 | configuration.setStackName(STACK_NAME); 40 | SipFactory.getInstance().setPathName("gov.nist"); 41 | sipStack = SipFactory.getInstance().createSipStack(configuration); 42 | RequestMessage.initFactory(SipFactory.getInstance(), configuration); 43 | ListeningPoint reaperUdp = sipStack.createListeningPoint(configuration.getWriteInterface(), configuration.getWritePort(), "udp"); 44 | collectorProvider = sipStack.createSipProvider(reaperUdp); 45 | collectorProvider.addSipListener(new CollectorListener()); 46 | collectorProvider.setAutomaticDialogSupportEnabled(false); 47 | logger.info("Collector SIP stack initialized successfully"); 48 | } 49 | catch (Exception e) 50 | { 51 | logger.error("Error initializing stack: ", e); 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | public boolean sendResponse(ResponseMessage response) { 58 | try { 59 | ServerTransaction st = collectorProvider.getNewServerTransaction(response.getRequest()); 60 | st.sendResponse(response.getResponse()); 61 | } 62 | catch (Exception e) { 63 | logger.error("Error sending response: ", e); 64 | } 65 | return true; 66 | } 67 | 68 | public boolean sendMessage(String data) { 69 | lastSendData = data; 70 | try { 71 | PublishMessage publish = new PublishMessage(data); 72 | collectorProvider.sendRequest(publish.getRequest()); 73 | } 74 | catch (Exception e) 75 | { 76 | logger.error("Error sending message: ", e); 77 | return false; 78 | } 79 | return true; 80 | } 81 | 82 | public CallIdHeader getNewCallId() { 83 | logger.info("Get new call id: " + collectorProvider); 84 | return collectorProvider.getNewCallId(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/com/tt/reaper/sip/ReaperListener.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import javax.sip.DialogTerminatedEvent; 4 | import javax.sip.IOExceptionEvent; 5 | import javax.sip.RequestEvent; 6 | import javax.sip.ResponseEvent; 7 | import javax.sip.SipListener; 8 | import javax.sip.TimeoutEvent; 9 | import javax.sip.TransactionTerminatedEvent; 10 | 11 | import org.apache.log4j.Logger; 12 | 13 | import com.tt.reaper.call.CallManager; 14 | import com.tt.reaper.message.MessageFactory; 15 | import com.tt.reaper.message.SipMessage; 16 | 17 | class ReaperListener implements SipListener { 18 | private static Logger logger = Logger.getLogger(ReaperListener.class); 19 | private MessageFactory factory = new MessageFactory(); 20 | 21 | ReaperListener() 22 | { 23 | } 24 | 25 | @Override 26 | public void processDialogTerminated(DialogTerminatedEvent evt) { 27 | logger.info("processDialogTerminated"); 28 | } 29 | 30 | @Override 31 | public void processIOException(IOExceptionEvent evt) { 32 | logger.info("processIOException"); 33 | } 34 | 35 | @Override 36 | public void processRequest(RequestEvent evt) { 37 | logger.debug("ReaperListener processRequest():" + evt.getRequest().getMethod()); 38 | SipMessage message = factory.create(evt.getRequest()); 39 | if (message != null) 40 | CallManager.instance.send(message); 41 | } 42 | 43 | @Override 44 | public void processResponse(ResponseEvent evt) { 45 | logger.debug("ReaperListener processRequest():" + evt.getResponse().getStatusCode()); 46 | SipMessage message = factory.create(evt.getResponse()); 47 | if (message != null) 48 | CallManager.instance.send(message); 49 | } 50 | 51 | @Override 52 | public void processTimeout(TimeoutEvent evt) { 53 | logger.info("processTimeout"); 54 | } 55 | 56 | @Override 57 | public void processTransactionTerminated(TransactionTerminatedEvent evt) { 58 | logger.info("processTransactionTerminated"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/com/tt/reaper/sip/ReaperStack.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import javax.sip.ListeningPoint; 4 | import javax.sip.SipFactory; 5 | import javax.sip.SipProvider; 6 | import javax.sip.SipStack; 7 | 8 | import org.apache.log4j.Logger; 9 | 10 | import com.tt.reaper.Configuration; 11 | import com.tt.reaper.message.RequestMessage; 12 | 13 | public class ReaperStack { 14 | private static final String STACK_NAME = "ReaperStack"; 15 | private static Logger logger = Logger.getLogger(ReaperStack.class); 16 | public static ReaperStack instance = new ReaperStack(); 17 | private SipProvider reaperProvider; 18 | private static boolean initialized = false; 19 | 20 | private ReaperStack() 21 | { 22 | } 23 | 24 | public synchronized boolean init() 25 | { 26 | if (initialized == true) 27 | return true; 28 | initialized = true; 29 | logger.info("Starting the reaper stack..."); 30 | try { 31 | SipStack sipStack; 32 | Configuration configuration; 33 | configuration = new Configuration(); 34 | configuration.setStackName(STACK_NAME); 35 | SipFactory.getInstance().setPathName("gov.nist"); 36 | sipStack = SipFactory.getInstance().createSipStack(configuration); 37 | RequestMessage.initFactory(SipFactory.getInstance(), configuration); 38 | ListeningPoint reaperTcp = sipStack.createListeningPoint("127.0.0.1", configuration.getReadPort(), "tcp"); 39 | reaperProvider = sipStack.createSipProvider(reaperTcp); 40 | reaperProvider.addSipListener(new ReaperListener()); 41 | reaperProvider.setAutomaticDialogSupportEnabled(false); 42 | logger.info("Reaper SIP stack initialized successfully"); 43 | } 44 | catch (Exception e) 45 | { 46 | logger.error("Error initializing stack: ", e); 47 | return false; 48 | } 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/LocalMetrics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | import com.tt.reaper.call.AudioData; 4 | import com.tt.reaper.call.CallContext; 5 | 6 | public class LocalMetrics extends Metrics { 7 | private static final String NAME = "LocalMetrics"; 8 | 9 | LocalMetrics() 10 | { 11 | super(NAME); 12 | } 13 | 14 | public LocalMetrics(CallContext context, AudioData data) { 15 | super(NAME, context, data); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/Metrics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.TimeZone; 6 | 7 | import com.tt.reaper.call.AudioData; 8 | import com.tt.reaper.call.CallContext; 9 | import com.tt.reaper.rtcp.ReportBlock; 10 | import com.tt.reaper.rtcp.VoipMetricsExtendedReportBlock; 11 | 12 | public class Metrics { 13 | String metrics; 14 | 15 | protected Metrics(String name) 16 | { 17 | metrics = name + ":\r\n"; 18 | } 19 | 20 | protected Metrics(String name, CallContext context, AudioData data) { 21 | metrics = name + ":\r\n"; 22 | if (data.from) { 23 | context.endTime = new Date(); 24 | setSessionDescription(data.payloadType, data.payloadDescription, data.sampleRate); 25 | setTimeStamps(context.startTime, context.endTime); 26 | setCallID(context.callId); 27 | setFromID(context.from); 28 | setToID(context.to); 29 | setOrigID(context.from); 30 | setLocalAddr(context.from); 31 | setLocalMac(context.fromMac); 32 | setRemoteAddr(context.to); 33 | setRemoteMac(context.toMac); 34 | } 35 | else { 36 | if (context.endTime == null) 37 | context.endTime = new Date(); 38 | setSessionDescription(data.payloadType, data.payloadDescription, data.sampleRate); 39 | setTimeStamps(context.startTime, context.endTime); 40 | setCallID(context.callId); 41 | setFromID(context.from); 42 | setToID(context.to); 43 | setOrigID(context.from); 44 | setLocalAddr(context.to); 45 | setLocalMac(context.toMac); 46 | setRemoteAddr(context.from); 47 | setRemoteMac(context.fromMac); 48 | } 49 | } 50 | public static String formatDate(Date date) { 51 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"); 52 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 53 | return dateFormat.format(date); 54 | } 55 | 56 | public void setSessionDescription(String type, String description, String sample) { 57 | if ((type == null) && (description == null) && (sample == null)) 58 | return; 59 | metrics += "SessionDesc:"; 60 | if (type != null) 61 | metrics += "PT=" + type; 62 | if (description != null) 63 | metrics += " PD=" + description; 64 | if (sample != null) 65 | metrics += " SR="+ sample; 66 | metrics += "\r\n"; 67 | } 68 | 69 | public void setTimeStamps(Date startTime, Date endTime) { 70 | metrics += "Timestamps:START=" + formatDate(startTime) + " STOP=" + formatDate(endTime) + "\r\n"; 71 | } 72 | 73 | public void setCallID(String value) 74 | { 75 | if (value == null) 76 | return; 77 | metrics += "CallID:" + value + "\r\n"; 78 | } 79 | 80 | public void setFromID(String value) 81 | { 82 | if (value == null) 83 | return; 84 | metrics += "FromID:" + value + "\r\n"; 85 | } 86 | 87 | public void setToID(String value) 88 | { 89 | if (value == null) 90 | return; 91 | metrics += "ToID:" + value + "\r\n"; 92 | } 93 | 94 | public void setOrigID(String value) 95 | { 96 | if (value == null) 97 | return; 98 | metrics += "OrigID:" + value + "\r\n"; 99 | } 100 | 101 | public void setLocalAddr(String value) 102 | { 103 | if (value == null) 104 | return; 105 | metrics += "LocalAddr:" + value + "\r\n"; 106 | } 107 | 108 | public void setLocalMac(String value) { 109 | if (value == null) 110 | return; 111 | metrics += "LocalMAC:" + value + "\r\n"; 112 | } 113 | 114 | public void setRemoteAddr(String value) 115 | { 116 | if (value == null) 117 | return; 118 | metrics += "RemoteAddr:" + value + "\r\n"; 119 | } 120 | 121 | public void setRemoteMac(String value) { 122 | if (value == null) 123 | return; 124 | metrics += "RemoteMAC:" + value + "\r\n"; 125 | } 126 | 127 | private String addAttribute(String result, String name, int value) 128 | { 129 | if (! result.isEmpty()) 130 | result += " "; 131 | result += name + "=" + value; 132 | return result; 133 | } 134 | 135 | private String addAttribute(String result, String name, double value) 136 | { 137 | if (! result.isEmpty()) 138 | result += " "; 139 | result += name + String.format("=%.1f", value); 140 | return result; 141 | } 142 | 143 | public void setJitterBuffer(int adaptive, int rate, int nominal, int max, int absMax) { 144 | metrics += String.format("JitterBuffer:JBA=%d JBR=%d JBN=%d JBM=%d JBX=%d\r\n", adaptive, rate, nominal, max, absMax); 145 | } 146 | 147 | public void setPacketLoss(double lossRate, double discardRate) 148 | { 149 | metrics += String.format("PacketLoss:NLR=%.1f JDR=%.1f\r\n", lossRate, discardRate); 150 | } 151 | 152 | public void setPacketLoss(double lossRate) 153 | { 154 | metrics += String.format("PacketLoss:NLR=%.1f\r\n", lossRate); 155 | } 156 | 157 | public void setBurstGapLoss(double burstDensity, int burstDuration, double gapDensity, int gapDuration, int gmin) 158 | { 159 | metrics += String.format("BurstGapLoss:BLD=%.1f BD=%d GLD=%.1f GD=%d GMIN=%d\r\n", burstDensity, burstDuration, gapDensity, gapDuration, gmin); 160 | } 161 | 162 | public void setDelay(int roundTrip, int endSystem, int symmOneWay, int interarrivalJitter, int meanAbsJitter) 163 | { 164 | metrics += String.format("Delay:RTD=%d ESD=%d SOWD=%d IAJ=%d MAJ=%d\r\n", roundTrip, endSystem, symmOneWay, interarrivalJitter, meanAbsJitter); 165 | } 166 | 167 | public void setDelay(int interarrivalJitter) 168 | { 169 | metrics += String.format("Delay:IAJ=%d\r\n", interarrivalJitter); 170 | } 171 | 172 | public void setDelay(int roundTrip, int endSystem) 173 | { 174 | metrics += String.format("Delay:RTD=%d ESD=%d\r\n", roundTrip, endSystem); 175 | } 176 | 177 | public void setSignal(int signal, int noise, int rerl) { 178 | metrics += String.format("Signal:SL=%d NL=%d RERL=%d\r\n", signal, noise, rerl); 179 | } 180 | 181 | public void setQualityEst(int rlq, int rcq, int extri, double moslq, double moscq, boolean usedP564) { 182 | metrics += String.format("QualityEst:RLQ=%d RCQ=%d EXTRI=%d MOSLQ=%.1f MOSCQ=%.1f QoEEstAlg=%s\r\n", rlq, rcq, extri, moslq, moscq, (usedP564?"P.564":"other")); 183 | } 184 | 185 | public void setQualityEst(int rcq, int extri, double moslq, double moscq) { 186 | String result = new String(); 187 | if ((rcq < 127) && (rcq >= 0)) 188 | result = addAttribute(result, "RCQ", rcq); 189 | if ((extri < 127) && (extri >= 0)) 190 | result = addAttribute(result, "EXTRI", extri); 191 | if ((moslq <= 5.0) && (moslq > 0)) 192 | result = addAttribute(result, "MOSLQ", moslq); 193 | if ((moscq <= 5.0) && (moscq > 0)) 194 | result = addAttribute(result, "MOSCQ", moscq); 195 | if (! result.isEmpty()) 196 | metrics += "QualityEst:" + result + "\r\n"; 197 | } 198 | 199 | public void setMetrics(VoipMetricsExtendedReportBlock b) 200 | { 201 | setJitterBuffer(b.getJitterBufferAdaptive(), b.getJitterBufferRate(), b.getJitterBufferNominal(), b.getJitterBufferMaximum(), b.getJitterBufferAbsMaximum()); 202 | setPacketLoss(b.getLossRate(), b.getDiscardRate()); 203 | setBurstGapLoss(b.getBurstDensity(), b.getBurstDuration(), b.getGapDensity(), b.getGapDuration(), b.getGmin()); 204 | setDelay(b.getRoundTripDelay(), b.getEndSystemDelay()); 205 | setSignal(b.getSignalLevel(), b.getNoiseLevel(), b.getResidualEchoReturnLoss()); 206 | setQualityEst(b.getRFactor(), b.getExtRFactor(), b.getMOSLQ(), b.getMOSCQ()); 207 | } 208 | 209 | 210 | public void setMetrics(ReportBlock report) { 211 | report.getCumulativeLost(); 212 | setPacketLoss(report.getFractionLost()); 213 | setDelay(report.getJitter()); 214 | } 215 | 216 | public String toString() 217 | { 218 | return metrics; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/RemoteMetrics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | import com.tt.reaper.call.AudioData; 4 | import com.tt.reaper.call.CallContext; 5 | 6 | public class RemoteMetrics extends Metrics { 7 | private static final String NAME = "RemoteMetrics"; 8 | 9 | RemoteMetrics() 10 | { 11 | super(NAME); 12 | } 13 | 14 | public RemoteMetrics(CallContext context, AudioData data) { 15 | super(NAME, context, data); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/VQIntervalReport.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | public class VQIntervalReport extends VQReportEvent { 4 | private static final String HEADER = "VQIntervalReport\r\n"; 5 | 6 | public VQIntervalReport(LocalMetrics localMetrics) { 7 | super(HEADER, localMetrics); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/VQReportEvent.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | 4 | class VQReportEvent { 5 | String header; 6 | private LocalMetrics localMetrics; 7 | private RemoteMetrics remoteMetrics; 8 | 9 | protected VQReportEvent(String header, LocalMetrics localMetrics) { 10 | this.header = header; 11 | this.localMetrics = localMetrics; 12 | } 13 | 14 | public void setRemoteMetrics(RemoteMetrics remoteMetrics) { 15 | this.remoteMetrics = remoteMetrics; 16 | } 17 | 18 | public String toString() 19 | { 20 | String message = header; 21 | message += localMetrics; 22 | if (remoteMetrics != null) 23 | message += remoteMetrics; 24 | return message; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/tt/reaper/vq/VQSessionReport.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | public class VQSessionReport extends VQReportEvent { 4 | private static final String HEADER = "VQSessionReport : CallTerm\r\n"; 5 | 6 | public VQSessionReport(LocalMetrics localMetrics) { 7 | super(HEADER, localMetrics); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tcmpdump/README: -------------------------------------------------------------------------------- 1 | This patched version of tcpdump was built with 4.1.1. Download tcpdump-4.1.1 2 | and cd to the main source directory. Apply the patch file: 3 | 4 | patch -p1 -i patch.txt 5 | 6 | -------------------------------------------------------------------------------- /tcmpdump/patch.txt: -------------------------------------------------------------------------------- 1 | diff -Naur tcpdump-4.1.1/Makefile.in tcpdump-reaper/Makefile.in 2 | --- tcpdump-4.1.1/Makefile.in 2010-11-16 07:43:33.000000000 -0700 3 | +++ tcpdump-reaper/Makefile.in 2010-11-16 08:11:21.000000000 -0700 4 | @@ -90,6 +90,7 @@ 5 | print-symantec.c print-syslog.c print-tcp.c print-telnet.c print-tftp.c \ 6 | print-timed.c print-token.c print-udld.c print-udp.c print-usb.c \ 7 | print-vjc.c print-vqp.c print-vrrp.c print-vtp.c print-forces.c \ 8 | + reaper.c \ 9 | print-wb.c print-zephyr.c signature.c setsignal.c tcpdump.c util.c 10 | 11 | LIBNETDISSECT_SRC=print-isakmp.c 12 | diff -Naur tcpdump-4.1.1/print-ether.c tcpdump-reaper/print-ether.c 13 | --- tcpdump-4.1.1/print-ether.c 2010-11-16 07:43:33.000000000 -0700 14 | +++ tcpdump-reaper/print-ether.c 2010-11-16 08:16:30.000000000 -0700 15 | @@ -38,6 +38,7 @@ 16 | #include "ethertype.h" 17 | 18 | #include "ether.h" 19 | +#include "reaper.h" 20 | 21 | const struct tok ethertype_values[] = { 22 | { ETHERTYPE_IP, "IPv4" }, 23 | @@ -94,6 +95,8 @@ 24 | (void)printf("%s > %s", 25 | etheraddr_string(ESRC(ep)), 26 | etheraddr_string(EDST(ep))); 27 | + reaper_set_source_mac(etheraddr_string(ESRC(ep))); 28 | + reaper_set_destination_mac(etheraddr_string(EDST(ep))); 29 | 30 | ether_type = EXTRACT_16BITS(&ep->ether_type); 31 | if (!qflag) { 32 | diff -Naur tcpdump-4.1.1/print-sip.c tcpdump-reaper/print-sip.c 33 | --- tcpdump-4.1.1/print-sip.c 2010-11-16 07:43:33.000000000 -0700 34 | +++ tcpdump-reaper/print-sip.c 2010-11-16 07:43:57.000000000 -0700 35 | @@ -31,12 +31,14 @@ 36 | #include "extract.h" 37 | 38 | #include "udp.h" 39 | +#include "reaper.h" 40 | 41 | void 42 | sip_print(register const u_char *pptr, register u_int len) 43 | { 44 | u_int idx; 45 | 46 | + reaper_sip_writer(pptr, len); 47 | printf("SIP, length: %u%s", len, vflag ? "\n\t" : ""); 48 | 49 | /* in non-verbose mode just lets print the protocol and length */ 50 | diff -Naur tcpdump-4.1.1/print-udp.c tcpdump-reaper/print-udp.c 51 | --- tcpdump-4.1.1/print-udp.c 2010-11-16 07:43:33.000000000 -0700 52 | +++ tcpdump-reaper/print-udp.c 2010-11-16 07:43:48.000000000 -0700 53 | @@ -56,6 +56,7 @@ 54 | #include "nameser.h" 55 | #include "nfs.h" 56 | #include "bootp.h" 57 | +#include "reaper.h" 58 | 59 | struct rtcphdr { 60 | u_int16_t rh_flags; /* T:2 P:1 CNT:5 PT:8 */ 61 | @@ -467,6 +468,7 @@ 62 | (void)printf("truncated-udplength %d", ulen); 63 | return; 64 | } 65 | + reaper_rtp_writer(ip, sport, dport, cp, ep); 66 | if (packettype) { 67 | register struct sunrpc_msg *rp; 68 | enum sunrpc_msg_type direction; 69 | -------------------------------------------------------------------------------- /tcmpdump/reaper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "ip.h" 7 | #include "addrtoname.h" 8 | #include "reaper.h" 9 | 10 | #define FORMAT "%s;%s;%s;%s;%s;%s;" 11 | #define REAPER_SIP_PORT 5050 12 | #define REAPER_BUFFER_SIZE (10*1024) 13 | #define REAPER_BUFFER_MAX (8*1024) 14 | 15 | static int reaper_initialized = 0; 16 | static int reaper_sock = -1; 17 | static struct sockaddr_in reaper_server; 18 | static int reaper_server_length; 19 | static char reaper_packet_buffer[REAPER_BUFFER_SIZE]; 20 | static char *reaper_packet_pointer = NULL; 21 | static char reaper_buffer[REAPER_BUFFER_SIZE]; 22 | static char reaper_source_mac[64]; 23 | static char reaper_destination_mac[64]; 24 | static int reaper_base_sec; 25 | 26 | static void reaper_init() 27 | { 28 | if (reaper_initialized) 29 | return; 30 | reaper_set_source_mac("00:00:00:00:00:00"); 31 | reaper_set_destination_mac("00:00:00:00:00:00"); 32 | reaper_initialized = 1; 33 | reaper_sock= socket(AF_INET, SOCK_STREAM, 0); 34 | if (reaper_sock < 0) { 35 | fprintf(stderr, "Error creating socket %d\n", errno); 36 | exit(1); 37 | } 38 | 39 | struct hostent *hp = gethostbyname("127.0.0.1"); 40 | if (hp == 0) { 41 | fprintf(stderr, "Error getting host ent %d\n", errno); 42 | exit(1); 43 | } 44 | 45 | reaper_server.sin_family = AF_INET; 46 | reaper_server.sin_port = htons(REAPER_SIP_PORT); 47 | bcopy((char *)hp->h_addr, (char *)&reaper_server.sin_addr, hp->h_length); 48 | reaper_server_length = sizeof(struct sockaddr_in); 49 | if (connect(reaper_sock, (struct sockaddr *)&reaper_server, sizeof(reaper_server)) < 0) { 50 | fprintf(stderr, "Error connecting to server %d\n", errno); 51 | exit(1); 52 | } 53 | struct timeval val; 54 | gettimeofday(&val, NULL); 55 | reaper_base_sec = val.tv_sec; 56 | 57 | return; 58 | } 59 | 60 | static int reaper_time_stamp() 61 | { 62 | struct timeval val; 63 | gettimeofday(&val, NULL); 64 | return ((val.tv_sec - reaper_base_sec) * 1000) + (val.tv_usec / 1000); 65 | } 66 | 67 | static void reaper_writer(const u_char *data, int len) 68 | { 69 | int n; 70 | n = write(reaper_sock, data, len); 71 | if (n < 0) { 72 | fprintf(stderr, "Error writing to server %d\n", errno); 73 | exit(1); 74 | } 75 | } 76 | 77 | static int 78 | reaper_rtp_format(const struct ip *ip, int sport, int dport, const char* data, int len) 79 | { 80 | if (reaper_packet_pointer == NULL) 81 | reaper_packet_pointer = reaper_packet_buffer; 82 | char *buffer = reaper_packet_pointer; 83 | #ifdef INET6 84 | if (IP_V(ip) == 6) { 85 | const struct ip6_hdr *ip6; 86 | ip6 = (const struct ip6_hdr *)ip; 87 | buffer += sprintf(buffer, FORMAT, 88 | reaper_source_mac, 89 | ip6addr_string(&ip6->ip6_src), 90 | udpport_string(sport), 91 | reaper_destination_mac, 92 | ip6addr_string(&ip6->ip6_dst), 93 | udpport_string(dport)); 94 | } 95 | else { 96 | #endif /*INET6*/ 97 | buffer += sprintf(buffer, FORMAT, 98 | reaper_source_mac, 99 | ipaddr_string(&ip->ip_src), 100 | udpport_string(sport), 101 | reaper_destination_mac, 102 | ipaddr_string(&ip->ip_dst), 103 | udpport_string(dport)); 104 | #ifdef INET6 105 | } 106 | #endif /*INET6*/ 107 | short sequenceNumber = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF); 108 | int timeStamp = ((data[4] & 0xFF) << 24) | ((data[5] & 0xFF) << 16) | 109 | ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); 110 | buffer += sprintf(buffer, "%d;%d;%d", sequenceNumber, timeStamp, reaper_time_stamp()); 111 | *(buffer++) = '\n'; 112 | *(buffer) = '\0'; 113 | reaper_packet_pointer = buffer; 114 | return (buffer - reaper_packet_buffer); 115 | } 116 | 117 | static void 118 | reaper_rtp_flush(int force) 119 | { 120 | static const char *notify = "NOTIFY sip:rtp.example.com SIP/2.0\r\nVia: SIP/2.0/UDP 192.168.1.2;branch=z9hG4bKnp149505178-438c528b192.168.1.2;rport\r\nFrom: ;tag=4442\r\nTo: ;tag=78923\r\nCall-Id: rtp@example.com\r\nCSeq: 20 NOTIFY\r\nContact: \r\nMax-Forwards: 10\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s"; 121 | if (reaper_packet_pointer == reaper_packet_buffer) 122 | return; 123 | int contentLength = (reaper_packet_pointer - reaper_packet_buffer); 124 | if (force == FALSE) 125 | { 126 | if (contentLength < REAPER_BUFFER_MAX) 127 | return; 128 | } 129 | contentLength = sprintf(reaper_buffer, notify, contentLength, reaper_packet_buffer); 130 | /* printf("%s\n", reaper_buffer); */ 131 | reaper_writer(reaper_buffer, contentLength); 132 | reaper_packet_pointer = reaper_packet_buffer; 133 | *reaper_packet_pointer = '\0'; 134 | return; 135 | } 136 | 137 | static int 138 | reaper_rtcp_format(const struct ip *ip, int sport, int dport, const char* data, int len) 139 | { 140 | char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 141 | char *buffer = reaper_packet_buffer; 142 | #ifdef INET6 143 | if (IP_V(ip) == 6) { 144 | const struct ip6_hdr *ip6; 145 | ip6 = (const struct ip6_hdr *)ip; 146 | buffer += sprintf(reaper_packet_buffer, FORMAT, 147 | reaper_source_mac, 148 | ip6addr_string(&ip6->ip6_src), 149 | udpport_string(sport), 150 | reaper_destination_mac, 151 | ip6addr_string(&ip6->ip6_dst), 152 | udpport_string(dport)); 153 | } 154 | else { 155 | #endif /*INET6*/ 156 | buffer += sprintf(reaper_packet_buffer, FORMAT, 157 | reaper_source_mac, 158 | ipaddr_string(&ip->ip_src), 159 | udpport_string(sport), 160 | reaper_destination_mac, 161 | ipaddr_string(&ip->ip_dst), 162 | udpport_string(dport)); 163 | #ifdef INET6 164 | } 165 | #endif /*INET6*/ 166 | fprintf(stderr, "Sending RTCP packet <%s>\n", reaper_packet_buffer); 167 | char *end_buffer = reaper_packet_buffer + sizeof(reaper_packet_buffer) - 3; 168 | const char *end_data = data + len; 169 | while (data < end_data) { 170 | *(buffer++) = hex[((*data >> 4) & 0xF)]; 171 | *(buffer++) = hex[(*data & 0xF)]; 172 | if (buffer >= end_buffer) 173 | break; 174 | ++data; 175 | } 176 | *(buffer++) = '\n'; 177 | *(buffer) = '\0'; 178 | return (buffer - reaper_packet_buffer); 179 | } 180 | 181 | void reaper_sip_writer(const u_char *data, int len) 182 | { 183 | reaper_init(); 184 | char *ptr; 185 | if (((ptr = index(data, '\r')) == NULL) || 186 | ((ptr = index(data, '\n')) == NULL)) { 187 | fprintf(stderr, "Error unexpected packet\n"); 188 | return; 189 | } 190 | int lineLen = (ptr - (char*)data); 191 | reaper_rtp_flush(TRUE); 192 | fprintf(stderr, "Sending SIP: %.*s\n", lineLen, (char*)data); 193 | reaper_writer(data, len); 194 | return; 195 | } 196 | 197 | void 198 | reaper_rtp_writer(const struct ip *ip, int sport, int dport, const u_char *hdr, const u_char *ep) 199 | { 200 | int len = (int)(ep - hdr); 201 | if (len < 0) 202 | return; 203 | if ((dport % 2) != 0) 204 | return; 205 | if ((hdr[0] & 0xE0) != 0x80) /* v2 */ 206 | return; 207 | reaper_init(); 208 | reaper_rtp_format(ip, sport, dport, hdr, len); 209 | reaper_rtp_flush(FALSE); 210 | } 211 | 212 | void 213 | reaper_rtcp_writer(const struct ip *ip, int sport, int dport, const u_char *hdr, const u_char *ep) 214 | { 215 | static const char *notify = "NOTIFY sip:rtcp.example.com SIP/2.0\r\nVia: SIP/2.0/UDP 192.168.1.2;branch=z9hG4bKnp149505178-438c528b192.168.1.2;rport\r\nFrom: ;tag=4442\r\nTo: ;tag=78923\r\nCall-Id: rtcp@example.com\r\nCSeq: 20 NOTIFY\r\nContact: \r\nMax-Forwards: 10\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s"; 216 | int len = (int)(ep - hdr); 217 | if (len < 0) 218 | return; 219 | if ((dport % 2) != 1) 220 | return; 221 | if ((hdr[0] & 0xE0) != 0x80) /* v2 */ 222 | return; 223 | if ((hdr[1] < 0xc8) || (hdr[1] > 0xcf)) /* SR to XR */ 224 | return; 225 | reaper_init(); 226 | int contentLength = reaper_rtcp_format(ip, sport, dport, hdr, len); 227 | contentLength = sprintf(reaper_buffer, notify, contentLength, reaper_packet_buffer); 228 | printf("%s\n", reaper_buffer); 229 | reaper_writer(reaper_buffer, contentLength); 230 | } 231 | 232 | #include 233 | 234 | void reaper_set_source_mac(const char* value) { 235 | strncpy(reaper_source_mac, value, sizeof(reaper_source_mac)); 236 | reaper_source_mac[sizeof(reaper_source_mac)-1] = '\0'; 237 | } 238 | 239 | void reaper_set_destination_mac(const char* value) { 240 | strncpy(reaper_destination_mac, value, sizeof(reaper_destination_mac)); 241 | reaper_destination_mac[sizeof(reaper_destination_mac)-1] = '\0'; 242 | } 243 | -------------------------------------------------------------------------------- /tcmpdump/reaper.h: -------------------------------------------------------------------------------- 1 | void reaper_rtp_writer(const struct ip *ip, int sport, int dport, const u_char *hdr, const u_char *ep); 2 | void reaper_sip_writer(const u_char *data, int len); 3 | void reaper_set_source_mac(const char* value); 4 | void reaper_set_destination_mac(const char* value); 5 | -------------------------------------------------------------------------------- /tcmpdump/tcpdump-4.1.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryHowe/SIP-Voice-Quality-Report-Reaper/5b32b960fcd06f4ead2e2a1b5ee185a61fa6e2d6/tcmpdump/tcpdump-4.1.1.tar.gz -------------------------------------------------------------------------------- /test/com/tt/reaper/TestConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | 8 | public class TestConfiguration { 9 | 10 | @Test 11 | public void testConfiguration() 12 | { 13 | Configuration sot = new Configuration(); 14 | assertEquals("0", sot.getStackTraceLevel()); 15 | assertEquals("server.log", sot.getStackServerLog()); 16 | assertEquals("debug.log", sot.getStackDebugLog()); 17 | assertEquals("eth0", sot.getReadInterface()); 18 | assertEquals(5050, sot.getReadPort()); 19 | assertEquals("127.0.0.2", sot.getWriteInterface()); 20 | assertEquals(5060, sot.getWritePort()); 21 | assertEquals("reaper", sot.getWriteUsername()); 22 | assertEquals(5060, sot.getCollectorPort()); 23 | assertEquals("127.0.0.3", sot.getCollectorHost()); 24 | assertEquals("collector", sot.getCollectorUsername()); 25 | assertEquals("/opt/reaper/bin/filter.sh -n -e -i eth0", sot.getCommand()); 26 | assertEquals("ReaperV1.0", sot.getSoftwareVersion()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/com/tt/reaper/TestNics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | public class TestNics { 9 | @Before 10 | public void setUp() 11 | { 12 | ReaperLogger.init(); 13 | } 14 | 15 | @Test 16 | public void testNics() 17 | { 18 | assertEquals("127.0.0.1", Nics.getIp("unknown")); 19 | assertEquals("127.0.0.2", Nics.getIp("lo")); 20 | assertEquals("192.168.1.101", Nics.getIp("eth0")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestCallContext.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import com.tt.reaper.ReaperLogger; 8 | import com.tt.reaper.message.TestMessageFactory; 9 | 10 | public class TestCallContext { 11 | 12 | @Test 13 | public void testIt() 14 | { 15 | ReaperLogger.init(); 16 | CallContext context = new CallContext(); 17 | assertEquals(StateInvited.instance, context.state); 18 | assertEquals(true, context.process(TestMessageFactory.createAckMessage())); 19 | assertEquals(StateConnected.instance, context.state); 20 | assertEquals(true, context.process(TestMessageFactory.createByeMessage())); 21 | assertEquals(StateTerminating.instance, context.state); 22 | assertEquals(false, context.process(TestMessageFactory.createSuccessMessage())); 23 | assertEquals(StateTerminated.instance, context.state); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestCallManager.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import com.tt.reaper.Reaper; 10 | import com.tt.reaper.message.DataRequest; 11 | import com.tt.reaper.message.DataResponse; 12 | import com.tt.reaper.message.InviteMessage; 13 | import com.tt.reaper.message.MessageQueue; 14 | import com.tt.reaper.message.NotifyMessage; 15 | import com.tt.reaper.message.TestMessageFactory; 16 | import com.tt.reaper.message.TestRtpPacket; 17 | import com.tt.reaper.rtcp.DataPacket; 18 | import com.tt.reaper.rtcp.TestRtcpPacket; 19 | 20 | public class TestCallManager { 21 | private static final String RTP_IP_ADDY = "127.0.0.2"; 22 | private static final int RTP_PORT = 2222; 23 | private static final String RTCP_IP_PORT = RTP_IP_ADDY + ":" + (RTP_PORT + 1); 24 | 25 | private CallManager sot = CallManager.instance; 26 | CallContext one = new CallContext(); 27 | CallContext two = new CallContext(); 28 | InviteMessage message; 29 | String callId; 30 | 31 | @Before 32 | public void setUp() 33 | { 34 | Reaper.instance.init(); 35 | message = TestMessageFactory.createInviteMessage(RTP_IP_ADDY, RTP_PORT); 36 | callId = message.getCallId(); 37 | sot.clearMaps(); 38 | } 39 | 40 | @Test 41 | public void testCallIdMap() 42 | { 43 | assertEquals(null, sot.getContextCallId(callId)); 44 | sot.process(message); 45 | CallContext context = sot.getContextCallId(callId); 46 | assertTrue(context.getFromAudio(RTCP_IP_PORT, RTCP_IP_PORT) != null); 47 | assertEquals(context, sot.getContextRtcp(RTCP_IP_PORT)); 48 | } 49 | 50 | @Test 51 | public void testRegister() 52 | { 53 | sot.register(one, RTCP_IP_PORT); 54 | assertEquals(one, sot.getContextRtcp(RTCP_IP_PORT)); 55 | } 56 | 57 | @Test 58 | public void testUnregister() 59 | { 60 | sot.process(message); 61 | CallContext context = sot.getContextCallId(callId); 62 | context.unregister(); 63 | assertEquals(null, sot.getContextRtcp(RTCP_IP_PORT)); 64 | } 65 | 66 | @Test 67 | public void testDoubleRegister() 68 | { 69 | sot.register(one, RTCP_IP_PORT); 70 | sot.register(two, RTCP_IP_PORT); 71 | assertEquals(two, sot.getContextRtcp(RTCP_IP_PORT)); 72 | } 73 | 74 | @Test 75 | public void testClearMaps() 76 | { 77 | sot.process(message); 78 | sot.clearMaps(); 79 | assertEquals(null, sot.getContextRtcp(RTCP_IP_PORT)); 80 | assertEquals(null, sot.getContextCallId(callId)); 81 | } 82 | 83 | @Test 84 | public void testDataPacketSource() 85 | { 86 | message = TestMessageFactory.createInviteMessage(TestRtcpPacket.SOURCE_IP, (TestRtcpPacket.SOURCE_PORT - 1)); 87 | callId = message.getCallId(); 88 | sot.process(message); 89 | String rtcpString = TestRtcpPacket.SOURCE_IP + ":" + TestRtcpPacket.SOURCE_PORT; 90 | CallContext context = sot.getContextCallId(callId); 91 | assertTrue(context.getFromAudio(rtcpString, rtcpString) != null); 92 | assertEquals(context, sot.getContextRtcp(rtcpString)); 93 | DataPacket packet = TestRtcpPacket.create(TestRtcpPacket.XRFILE); 94 | 95 | sot.process(packet); 96 | } 97 | 98 | @Test 99 | public void testNotifyMessage() 100 | { 101 | message = TestMessageFactory.createInviteMessage(TestRtpPacket.SOURCE_IP, Integer.parseInt(TestRtpPacket.SOURCE_PORT)); 102 | callId = message.getCallId(); 103 | sot.process(message); 104 | String rtpString = TestRtpPacket.SOURCE_IP + ":" + TestRtpPacket.SOURCE_PORT; 105 | CallContext context = sot.getContextCallId(callId); 106 | assertTrue(context.getFromAudio(rtpString, rtpString) != null); 107 | assertEquals(context, sot.getContextRtcp(rtpString)); 108 | NotifyMessage message = new NotifyMessage(TestRtpPacket.create(1, 2) + TestRtpPacket.create(2, 3) + TestRtpPacket.create(3, 4)); 109 | 110 | sot.process(message); 111 | 112 | AudioData audio = context.getFromAudio(rtpString, rtpString); 113 | audio.close(); 114 | assertEquals(true, audio.from); 115 | assertEquals(TestRtpPacket.SOURCE_IP, audio.ipAddy); 116 | assertEquals("pcma", audio.payloadDescription); 117 | assertEquals("8", audio.payloadType); 118 | assertEquals(Integer.parseInt(TestRtpPacket.SOURCE_PORT), audio.rtpPort); 119 | assertEquals("8000", audio.sampleRate); 120 | assertEquals(rtpString, audio.getIpRtpPort()); 121 | assertEquals(0.0, audio.getDiscardRate(), 0.001); 122 | assertEquals(0, audio.getDuplicates()); 123 | assertEquals(1, audio.getFirst()); 124 | assertEquals(3, audio.getLast()); 125 | assertEquals(0.0, audio.getLossRate(), 0.001); 126 | assertEquals(3, audio.getPacketCount()); 127 | assertEquals(0, audio.getPacketLoss()); 128 | assertEquals(0, audio.getJitter()); 129 | } 130 | 131 | @Test 132 | public void testDataResponse() 133 | { 134 | sot.process(message); 135 | MessageQueue queue = new MessageQueue(); 136 | sot.process(new DataRequest(queue)); 137 | DataResponse response = (DataResponse)queue.getBlocking(); 138 | assertTrue(response.data.contains("FROM:\"reaper\" TO:\"collector\" ")); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestRtpPacketFactory.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import com.tt.reaper.message.RtpPacket; 8 | import com.tt.reaper.message.TestRtpPacket; 9 | 10 | public class TestRtpPacketFactory { 11 | 12 | @Test 13 | public void testFactory() { 14 | RtpPacketFactory factory = new RtpPacketFactory(); 15 | factory.init(null); 16 | assertEquals(null, factory.getNext()); 17 | } 18 | 19 | @Test 20 | public void testFactoryOne() 21 | { 22 | RtpPacketFactory factory = new RtpPacketFactory(); 23 | factory.init(TestRtpPacket.create(1, 2).getBytes()); 24 | RtpPacket packet = factory.getNext(); 25 | assertEquals(1, packet.getSequenceNumber()); 26 | assertEquals(2, packet.getTimeStamp()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestRtpStream.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | public class TestRtpStream { 9 | RtpStream stream; 10 | 11 | @Before 12 | public void setUp() 13 | { 14 | stream = new RtpStream(); 15 | } 16 | 17 | @Test 18 | public void testSimple() 19 | { 20 | stream.receive(22, 10, 110); 21 | stream.receive(23, 10, 10); 22 | stream.receive(25, 10, 110); 23 | stream.receive(25, 10, 10); 24 | stream.receive(25, 10, 110); 25 | stream.receive(26, 10, 10); 26 | stream.close(); 27 | 28 | assertEquals(22, stream.getFirst()); 29 | assertEquals(26, stream.getLast()); 30 | assertEquals(2, stream.getDuplicates()); 31 | assertEquals(1, stream.getPacketLoss()); 32 | assertEquals(4, stream.getPacketCount()); 33 | assertEquals(64.0, stream.getLossRate(), 0.001); 34 | assertEquals(128.0, stream.getDiscardRate(), 0.001); 35 | } 36 | 37 | @Test 38 | public void testJitterOnePacket() 39 | { 40 | stream.receive(22, 10, 110); 41 | assertEquals(6, stream.getJitter()); 42 | assertEquals(6, stream.getMinJitter()); 43 | assertEquals(6, stream.getMaxJitter()); 44 | assertEquals(6, stream.getMeanJitter()); 45 | } 46 | 47 | @Test 48 | public void testJitter() 49 | { 50 | for (int j=0; j<1000; j++) 51 | { 52 | if ((j % 2) == 0) 53 | stream.receive(22, 10, 110); 54 | else 55 | stream.receive(23, 10, 10); 56 | } 57 | 58 | assertEquals(12, stream.getJitter()); 59 | assertEquals(6, stream.getMinJitter()); 60 | assertEquals(12, stream.getMaxJitter()); 61 | assertEquals(9, stream.getMeanJitter()); 62 | } 63 | 64 | @Test 65 | public void testEdgeOne() 66 | { 67 | stream.receive(RtpStream.SIZE-1, 10, 11); 68 | stream.receive(RtpStream.SIZE, 10, 11); 69 | stream.close(); 70 | assertEquals(0, stream.getPacketLoss()); 71 | assertEquals(2, stream.getPacketCount()); 72 | assertEquals(RtpStream.SIZE-1, stream.getFirst()); 73 | assertEquals(RtpStream.SIZE, stream.getLast()); 74 | assertEquals(0, stream.getDuplicates()); 75 | assertEquals(0.0, stream.getLossRate(), 0.001); 76 | assertEquals(0.0, stream.getDiscardRate(), 0.001); 77 | } 78 | 79 | @Test 80 | public void testEdgeTwo() 81 | { 82 | stream.receive(RtpStream.SIZE, 10, 11); 83 | stream.close(); 84 | assertEquals(0, stream.getPacketLoss()); 85 | assertEquals(1, stream.getPacketCount()); 86 | assertEquals(RtpStream.SIZE, stream.getFirst()); 87 | assertEquals(RtpStream.SIZE, stream.getLast()); 88 | assertEquals(0, stream.getDuplicates()); 89 | } 90 | 91 | @Test 92 | public void testEdgeThree() 93 | { 94 | stream.receive(RtpStream.SIZE-1, 10, 11); 95 | stream.close(); 96 | assertEquals(0, stream.getPacketLoss()); 97 | assertEquals(1, stream.getPacketCount()); 98 | assertEquals(RtpStream.SIZE-1, stream.getFirst()); 99 | assertEquals(RtpStream.SIZE-1, stream.getLast()); 100 | assertEquals(0, stream.getDuplicates()); 101 | } 102 | 103 | @Test 104 | public void testBigOne() 105 | { 106 | for (int j=111; j<2000; j++) 107 | { 108 | switch (j) 109 | { 110 | case 144: 111 | case 202: 112 | case 888: 113 | case 998: 114 | continue; 115 | } 116 | stream.receive(j, 10, 11); 117 | } 118 | stream.close(); 119 | assertEquals(4, stream.getPacketLoss()); 120 | assertEquals(1885, stream.getPacketCount()); 121 | assertEquals(111, stream.getFirst()); 122 | assertEquals(1999, stream.getLast()); 123 | assertEquals(0, stream.getDuplicates()); 124 | assertEquals(0.543, stream.getLossRate(), 0.001); 125 | assertEquals(0.0, stream.getDiscardRate(), 0.001); 126 | } 127 | 128 | @Test 129 | public void testBigTwo() 130 | { 131 | for (int j=0; j<5022; j++) 132 | { 133 | switch (j) 134 | { 135 | case 144: 136 | case 244: 137 | case 344: 138 | continue; 139 | } 140 | stream.receive(j, 10, 11); 141 | } 142 | stream.close(); 143 | assertEquals(3, stream.getPacketLoss()); 144 | assertEquals(5019, stream.getPacketCount()); 145 | assertEquals(0, stream.getFirst()); 146 | assertEquals(5021, stream.getLast()); 147 | assertEquals(0, stream.getDuplicates()); 148 | } 149 | 150 | @Test 151 | public void testBigThree() 152 | { 153 | for (int j=0; j<430; j++) 154 | { 155 | switch (j) 156 | { 157 | case (RtpStream.SIZE+44): 158 | case (RtpStream.SIZE*2+44): 159 | case (RtpStream.SIZE*3+44): 160 | continue; 161 | } 162 | stream.receive(j, 10, 11); 163 | } 164 | stream.close(); 165 | assertEquals(3, stream.getPacketLoss()); 166 | assertEquals(427, stream.getPacketCount()); 167 | assertEquals(0, stream.getFirst()); 168 | assertEquals(429, stream.getLast()); 169 | assertEquals(0, stream.getDuplicates()); 170 | } 171 | 172 | @Test 173 | public void testShortOne() 174 | { 175 | stream.receive(0xFFFF, 10, 11); 176 | for (int j=0; j<400; j++) 177 | { 178 | switch (j) 179 | { 180 | case 144: 181 | case 244: 182 | case 344: 183 | continue; 184 | } 185 | stream.receive(j, 10, 11); 186 | } 187 | stream.close(); 188 | assertEquals(3, stream.getPacketLoss()); 189 | assertEquals(398, stream.getPacketCount()); 190 | assertEquals(0xFFFF, stream.getFirst()); 191 | assertEquals(399, stream.getLast()); 192 | assertEquals(0, stream.getDuplicates()); 193 | } 194 | 195 | @Test 196 | public void testShortTwo() 197 | { 198 | for (int j=0xFF00; j<=400+0xFFFF; j++) 199 | { 200 | switch (j & 0xFFFF) 201 | { 202 | case 0xFF0F: 203 | case 244: 204 | case 344: 205 | continue; 206 | } 207 | stream.receive(j & 0xFFFF, 10, 11); 208 | } 209 | stream.close(); 210 | assertEquals(3, stream.getPacketLoss()); 211 | assertEquals(653, stream.getPacketCount()); 212 | assertEquals(0xFF00, stream.getFirst()); 213 | assertEquals(399, stream.getLast()); 214 | assertEquals(0, stream.getDuplicates()); 215 | } 216 | 217 | @Test 218 | public void testShortThree() 219 | { 220 | for (int j=0xFF00; j<=(1+0xFFFF); j++) 221 | { 222 | switch (j & 0xFFFF) 223 | { 224 | case 0xFF0F: 225 | case 0xFFF0: 226 | continue; 227 | } 228 | stream.receive(j & 0xFFFF, 10, 11); 229 | } 230 | stream.close(); 231 | assertEquals(2, stream.getPacketLoss()); 232 | assertEquals(255, stream.getPacketCount()); 233 | assertEquals(0xFF00, stream.getFirst()); 234 | assertEquals(0, stream.getLast()); 235 | assertEquals(0, stream.getDuplicates()); 236 | } 237 | 238 | @Test 239 | public void testLong() 240 | { 241 | for (int j=0xFF00; j<=0xFFFF; j++) 242 | { 243 | switch (j & 0xFFFF) 244 | { 245 | case 0xFF0F: 246 | case 0xFFF0: 247 | continue; 248 | } 249 | stream.receive(j & 0xFFFF, 10, 11); 250 | } 251 | for (int j=0; j<=0xFFFF; j++) 252 | { 253 | switch (j & 0xFFFF) 254 | { 255 | case 128: 256 | case 129: 257 | continue; 258 | case 555: 259 | case 1000: 260 | stream.receive(j & 0xFFFF, 10, 11); 261 | break; 262 | } 263 | stream.receive(j & 0xFFFF, 10, 11); 264 | } 265 | for (int j=0; j<=10; j++) 266 | { 267 | switch (j & 0xFFFF) 268 | { 269 | case 9: 270 | continue; 271 | } 272 | stream.receive(j & 0xFFFF, 10, 11); 273 | } 274 | stream.close(); 275 | assertEquals(5, stream.getPacketLoss()); 276 | assertEquals(65798, stream.getPacketCount()); 277 | assertEquals(0xFF00, stream.getFirst()); 278 | assertEquals(10, stream.getLast()); 279 | assertEquals(2, stream.getDuplicates()); 280 | } 281 | 282 | @Test 283 | public void testSuperLong() 284 | { 285 | for (int j=4; j<=0xFFFF; j++) 286 | stream.receive(j & 0xFFFF, 10, 11); 287 | for (int j=0; j<=0xFFFF; j++) 288 | { 289 | switch (j & 0xFFFF) 290 | { 291 | case 128: 292 | case 129: 293 | continue; 294 | case 555: 295 | case 1000: 296 | stream.receive(j & 0xFFFF, 10, 11); 297 | break; 298 | } 299 | stream.receive(j & 0xFFFF, 10, 11); 300 | } 301 | for (int j=0; j<=0xFFFF; j++) 302 | stream.receive(j & 0xFFFF, 10, 11); 303 | for (int j=0; j<=0xFFFF; j++) 304 | stream.receive(j & 0xFFFF, 10, 11); 305 | for (int j=0; j<=0xFFFF; j++) 306 | stream.receive(j & 0xFFFF, 10, 11); 307 | for (int j=0; j<=0xFFFF; j++) 308 | stream.receive(j & 0xFFFF, 10, 11); 309 | for (int j=0; j<=0xFFFF; j++) 310 | stream.receive(j & 0xFFFF, 10, 11); 311 | for (int j=0; j<=0xFFFF; j++) 312 | stream.receive(j & 0xFFFF, 10, 11); 313 | for (int j=0; j<=0xFFFF; j++) 314 | stream.receive(j & 0xFFFF, 10, 11); 315 | for (int j=0; j<=0xFFFF; j++) 316 | stream.receive(j & 0xFFFF, 10, 11); 317 | for (int j=0; j<=0xFFFF; j++) 318 | stream.receive(j & 0xFFFF, 10, 11); 319 | for (int j=0; j<=0xFFFF; j++) 320 | stream.receive(j & 0xFFFF, 10, 11); 321 | for (int j=0; j<=0xFFFF; j++) 322 | stream.receive(j & 0xFFFF, 10, 11); 323 | for (int j=0; j<=128; j++) 324 | stream.receive(j & 0xFFFF, 10, 11); 325 | stream.close(); 326 | assertEquals(2, stream.getPacketLoss()); 327 | assertEquals(852091, stream.getPacketCount()); 328 | assertEquals(4, stream.getFirst()); 329 | assertEquals(128, stream.getLast()); 330 | assertEquals(2, stream.getDuplicates()); 331 | assertEquals(0, stream.getJitter()); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestStateConnected.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import com.tt.reaper.Reaper; 11 | import com.tt.reaper.message.TestMessageFactory; 12 | import com.tt.reaper.message.TestRtpPacket; 13 | import com.tt.reaper.rtcp.TestRtcpPacket; 14 | import com.tt.reaper.sip.CollectorStack; 15 | import com.tt.reaper.vq.Metrics; 16 | 17 | public class TestStateConnected { 18 | CallContext context; 19 | 20 | @Before 21 | public void setUp() 22 | { 23 | Reaper.instance.init(); 24 | context = new CallContext(); 25 | context.callId = "2020202"; 26 | context.from = "from@example.com"; 27 | context.to = "to@example.com"; 28 | } 29 | 30 | @Test 31 | public void testInvite() 32 | { 33 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestMessageFactory.createInviteMessage("127.0.0.3", 9090))); 34 | } 35 | 36 | @Test 37 | public void testSuccess() 38 | { 39 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestMessageFactory.createSuccessMessage())); 40 | } 41 | 42 | @Test 43 | public void testError() 44 | { 45 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestMessageFactory.createErrorMessage())); 46 | } 47 | 48 | @Test 49 | public void testAck() 50 | { 51 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestMessageFactory.createAckMessage())); 52 | } 53 | 54 | @Test 55 | public void testBye() 56 | { 57 | assertEquals(StateTerminating.instance, StateConnected.instance.process(context, TestMessageFactory.createByeMessage())); 58 | } 59 | 60 | @Test 61 | public void testDataPacket() 62 | { 63 | CollectorStack.instance.lastSendData = null; 64 | AudioData data = new AudioData(); 65 | data.ipAddy = TestRtcpPacket.SOURCE_IP; 66 | data.rtpPort = TestRtcpPacket.SOURCE_PORT - 1; 67 | data.payloadType = "8"; 68 | data.payloadDescription = "pcma"; 69 | data.sampleRate = "8000"; 70 | ArrayList list = new ArrayList(); 71 | list.add(data); 72 | context.setAudioFrom(list); 73 | 74 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtcpPacket.create(TestRtcpPacket.XRFILE))); 75 | 76 | String expectedMessage = 77 | "VQIntervalReport\r\n" + 78 | "LocalMetrics:\r\n" + 79 | "SessionDesc:PT=8 PD=pcma SR=8000\r\n" + 80 | "Timestamps:START=" + Metrics.formatDate(context.startTime) + " STOP=" + Metrics.formatDate(context.endTime) + "\r\n" + 81 | "CallID:2020202\r\n" + 82 | "FromID:from@example.com\r\n" + 83 | "ToID:to@example.com\r\n" + 84 | "OrigID:from@example.com\r\n" + 85 | "LocalAddr:from@example.com\r\n" + 86 | "LocalMAC:00:00:00:00:00:00\r\n" + 87 | "RemoteAddr:to@example.com\r\n" + 88 | "RemoteMAC:01:01:01:01:01:01\r\n" + 89 | "JitterBuffer:JBA=3 JBR=0 JBN=20 JBM=100 JBX=300\r\n" + 90 | "PacketLoss:NLR=0.0 JDR=0.0\r\n" + 91 | "BurstGapLoss:BLD=0.0 BD=0 GLD=0.0 GD=0 GMIN=16\r\n" + 92 | "Delay:RTD=0 ESD=124\r\n" + 93 | "Signal:SL=-85 NL=-72 RERL=75\r\n" + 94 | "QualityEst:RCQ=96 MOSLQ=4.4 MOSCQ=4.4\r\n"; 95 | 96 | assertEquals(expectedMessage, CollectorStack.instance.lastSendData); 97 | } 98 | 99 | @Test 100 | public void testRtpPacket() 101 | { 102 | AudioData data = new AudioData(); 103 | data.ipAddy = TestRtpPacket.SOURCE_IP; 104 | data.rtpPort = Integer.valueOf(TestRtpPacket.SOURCE_PORT); 105 | data.payloadType = "8"; 106 | data.payloadDescription = "pcma"; 107 | data.sampleRate = "8000"; 108 | ArrayList list = new ArrayList(); 109 | list.add(data); 110 | context.setAudioFrom(list); 111 | assertEquals(0, data.getLast()); 112 | assertEquals(0, data.getFirst()); 113 | assertEquals(0, data.getPacketCount()); 114 | 115 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(2))); 116 | 117 | assertEquals(2, data.getLast()); 118 | assertEquals(2, data.getFirst()); 119 | assertEquals(1, data.getPacketCount()); 120 | } 121 | 122 | @Test 123 | public void testRtpPackets() 124 | { 125 | AudioData data = new AudioData(); 126 | data.ipAddy = TestRtpPacket.SOURCE_IP; 127 | data.rtpPort = Integer.valueOf(TestRtpPacket.SOURCE_PORT); 128 | data.payloadType = "8"; 129 | data.payloadDescription = "pcma"; 130 | data.sampleRate = "8000"; 131 | ArrayList list = new ArrayList(); 132 | list.add(data); 133 | context.setAudioFrom(list); 134 | assertEquals(0, data.getLast()); 135 | assertEquals(0, data.getFirst()); 136 | assertEquals(0, data.getPacketCount()); 137 | 138 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(2))); 139 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(3))); 140 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(2))); 141 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(4))); 142 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(5))); 143 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(7))); 144 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(8))); 145 | assertEquals(StateConnected.instance, StateConnected.instance.process(context, TestRtpPacket.create(9))); 146 | 147 | 148 | assertEquals(2, data.getFirst()); 149 | assertEquals(9, data.getLast()); 150 | assertEquals(7, data.getPacketCount()); 151 | assertEquals(1, data.getDuplicates()); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestStateInvited.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.ArrayList; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import com.tt.reaper.Reaper; 12 | import com.tt.reaper.message.InviteMessage; 13 | import com.tt.reaper.message.TestMessageFactory; 14 | import com.tt.reaper.message.TestRtpPacket; 15 | 16 | public class TestStateInvited { 17 | CallContext context; 18 | 19 | @Before 20 | public void setUp() 21 | { 22 | Reaper.instance.init(); 23 | context = new CallContext(); 24 | } 25 | 26 | @Test 27 | public void testInvite() 28 | { 29 | InviteMessage message = TestMessageFactory.createInviteMessage("127.0.0.3", 9090); 30 | assertEquals(StateInvited.instance, StateInvited.instance.process(context, message)); 31 | assertEquals(message.getCallId(), context.callId); 32 | assertEquals(message.getFrom(), context.from); 33 | assertEquals(message.getTo(), context.to); 34 | assertTrue(context.getFromAudio("127.0.0.3:9091", "127.0.0.3:404") != null); 35 | assertTrue(context.getToAudio("127.0.0.3:9091", "127.0.0.3:404") == null); 36 | assertTrue(context.getFromAudio("127.0.0.3:404", "127.0.0.3:9091") == null); 37 | assertTrue(context.getToAudio("127.0.0.3:404", "127.0.0.3:9091") != null); 38 | assertTrue(context.getFromAudio("127.0.0.3:9090", "127.0.0.3:404") != null); 39 | assertTrue(context.getToAudio("127.0.0.3:404", "127.0.0.3:9090") != null); 40 | } 41 | 42 | @Test 43 | public void testSuccess() 44 | { 45 | assertEquals(StateInvited.instance, StateInvited.instance.process(context, TestMessageFactory.createSuccessMessage("127.0.0.4", 7070))); 46 | assertTrue(context.getFromAudio("127.0.0.4:7071", "127.0.0.4:404") == null); 47 | assertTrue(context.getToAudio("127.0.0.4:7071", "127.0.0.4:404") != null); 48 | assertTrue(context.getFromAudio("127.0.0.4:404", "127.0.0.4:7071") != null); 49 | assertTrue(context.getToAudio("127.0.0.4:404", "127.0.0.4:7071") == null); 50 | assertTrue(context.getFromAudio("127.0.0.4:404", "127.0.0.4:7070") != null); 51 | assertTrue(context.getToAudio("127.0.0.4:7070", "127.0.0.4:404") != null); 52 | } 53 | 54 | @Test 55 | public void testProvisional() 56 | { 57 | assertEquals(StateInvited.instance, StateInvited.instance.process(context, TestMessageFactory.createProvisionalMessage())); 58 | } 59 | 60 | @Test 61 | public void testError() 62 | { 63 | assertEquals(StateTerminated.instance, StateInvited.instance.process(context, TestMessageFactory.createErrorMessage())); 64 | } 65 | 66 | @Test 67 | public void testAck() 68 | { 69 | assertEquals(StateConnected.instance, StateInvited.instance.process(context, TestMessageFactory.createAckMessage())); 70 | } 71 | 72 | @Test 73 | public void testBye() 74 | { 75 | assertEquals(StateTerminated.instance, StateInvited.instance.process(context, TestMessageFactory.createByeMessage())); 76 | } 77 | 78 | @Test 79 | public void testCancel() 80 | { 81 | assertEquals(StateTerminated.instance, StateInvited.instance.process(context, TestMessageFactory.createCancelMessage())); 82 | } 83 | 84 | @Test 85 | public void testRtpPacket() 86 | { 87 | AudioData data = new AudioData(); 88 | data.ipAddy = TestRtpPacket.SOURCE_IP; 89 | data.rtpPort = Integer.valueOf(TestRtpPacket.SOURCE_PORT); 90 | data.payloadType = "8"; 91 | data.payloadDescription = "pcma"; 92 | data.sampleRate = "8000"; 93 | ArrayList list = new ArrayList(); 94 | list.add(data); 95 | context.setAudioFrom(list); 96 | assertEquals(0, data.getLast()); 97 | assertEquals(0, data.getFirst()); 98 | assertEquals(0, data.getPacketCount()); 99 | 100 | assertEquals(StateInvited.instance, StateInvited.instance.process(context, TestRtpPacket.create(2))); 101 | 102 | assertEquals(2, data.getFirst()); 103 | assertEquals(2, data.getLast()); 104 | assertEquals(1, data.getPacketCount()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/com/tt/reaper/call/TestStateTerminating.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.call; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import com.tt.reaper.Reaper; 13 | import com.tt.reaper.message.TestMessageFactory; 14 | import com.tt.reaper.message.TestRtpPacket; 15 | import com.tt.reaper.sip.CollectorStack; 16 | import com.tt.reaper.sip.MockCollector; 17 | import com.tt.reaper.vq.Metrics; 18 | 19 | public class TestStateTerminating { 20 | CallContext context; 21 | Date remoteEndTime; 22 | 23 | private String getExpectedData(double packetLoss) { 24 | return 25 | "VQSessionReport : CallTerm\r\n" + 26 | "LocalMetrics:\r\n" + 27 | "SessionDesc:PT=0 PD=pcmu SR=8000\r\n" + 28 | "Timestamps:START=" + Metrics.formatDate(context.startTime) + " STOP=" + Metrics.formatDate(context.endTime) + "\r\n" + 29 | "CallID:2020202\r\n" + 30 | "FromID:from@example.com\r\n" + 31 | "ToID:to@example.com\r\n" + 32 | "OrigID:from@example.com\r\n" + 33 | "LocalAddr:from@example.com\r\n" + 34 | "RemoteAddr:to@example.com\r\n" + 35 | "PacketLoss:NLR=" + packetLoss + "\r\n" + 36 | "Delay:IAJ=0\r\n"; 37 | } 38 | 39 | @Before 40 | public void setUp() 41 | { 42 | Reaper.instance.init(); 43 | MockCollector.instance.init(); 44 | context = new CallContext(); 45 | context.callId = "2020202"; 46 | context.from = "from@example.com"; 47 | context.to = "to@example.com"; 48 | context.audioTo.add(new AudioData()); 49 | context.audioTo.get(0).payloadType = "0"; 50 | context.audioTo.get(0).payloadDescription = "pcmu"; 51 | context.audioTo.get(0).sampleRate = "8000"; 52 | context.audioFrom.add(new AudioData()); 53 | context.audioFrom.get(0).payloadType = "0"; 54 | context.audioFrom.get(0).payloadDescription = "pcmu"; 55 | context.audioFrom.get(0).sampleRate = "8000"; 56 | } 57 | 58 | @Test 59 | public void testInvite() 60 | { 61 | assertEquals(StateTerminating.instance, StateTerminating.instance.process(context, TestMessageFactory.createInviteMessage("127.0.0.3", 9090))); 62 | } 63 | 64 | @Test 65 | public void testSuccess() 66 | { 67 | remoteEndTime = context.endTime; 68 | assertEquals(StateTerminated.instance, StateTerminating.instance.process(context, TestMessageFactory.createSuccessMessage())); 69 | assertEquals(getExpectedData(0.0), CollectorStack.instance.lastSendData); 70 | try { Thread.sleep(750); } catch (Exception e) {} 71 | verifyMessage(0.0); 72 | } 73 | 74 | @Test 75 | public void testError() 76 | { 77 | remoteEndTime = context.endTime; 78 | assertEquals(StateTerminated.instance, StateTerminating.instance.process(context, TestMessageFactory.createErrorMessage())); 79 | assertEquals(getExpectedData(0.0), CollectorStack.instance.lastSendData); 80 | try { Thread.sleep(750); } catch (Exception e) {} 81 | verifyMessage(0.0); 82 | } 83 | 84 | @Test 85 | public void testPacketLoss() 86 | { 87 | remoteEndTime = context.endTime; 88 | AudioData data = context.audioTo.get(0); 89 | data.receive(1, 10, 11); 90 | data.receive(2, 10, 11); 91 | data.receive(1, 10, 11); 92 | data.receive(1, 10, 11); 93 | data.receive(3, 10, 11); 94 | data.receive(4, 10, 11); 95 | data.receive(6, 10, 11); 96 | data.receive(7, 10, 11); 97 | data.receive(8, 10, 11); 98 | data.receive(9, 10, 11); 99 | 100 | assertEquals(StateTerminated.instance, StateTerminating.instance.process(context, TestMessageFactory.createSuccessMessage())); 101 | 102 | assertEquals(getExpectedData(32.0), CollectorStack.instance.lastSendData); 103 | } 104 | 105 | private void verifyMessage(double packetLoss) { 106 | // System.out.println("--------------------------"); 107 | // System.out.println(MockCollector.instance.message); 108 | // System.out.println("--------------------------"); 109 | assertTrue(MockCollector.instance.message.contains("PUBLISH sip:collector@127.0.0.3:5060;transport=udp SIP/2.0")); 110 | assertTrue(MockCollector.instance.message.contains("CSeq: 1 PUBLISH")); 111 | assertTrue(MockCollector.instance.message.contains("From: \"reaper\" ;tag=ReaperV1.0")); 112 | assertTrue(MockCollector.instance.message.contains("To: \"collector\" ")); 113 | assertTrue(MockCollector.instance.message.contains("Max-Forwards: 70")); 114 | assertTrue(MockCollector.instance.message.contains("Contact: \"reaper\" ")); 115 | assertTrue(MockCollector.instance.message.contains("Content-Type: application/vq-rtcpxr")); 116 | assertTrue(MockCollector.instance.message.contains(getExpectedData(packetLoss))); 117 | } 118 | 119 | @Test 120 | public void testAck() 121 | { 122 | assertEquals(StateTerminating.instance, StateTerminating.instance.process(context, TestMessageFactory.createAckMessage())); 123 | } 124 | 125 | @Test 126 | public void testBye() 127 | { 128 | assertEquals(StateTerminating.instance, StateTerminating.instance.process(context, TestMessageFactory.createByeMessage())); 129 | } 130 | 131 | @Test 132 | public void testRtpPacket() 133 | { 134 | AudioData data = new AudioData(); 135 | data.ipAddy = TestRtpPacket.SOURCE_IP; 136 | data.rtpPort = Integer.valueOf(TestRtpPacket.SOURCE_PORT); 137 | data.payloadType = "8"; 138 | data.payloadDescription = "pcma"; 139 | data.sampleRate = "8000"; 140 | ArrayList list = new ArrayList(); 141 | list.add(data); 142 | context.setAudioFrom(list); 143 | assertEquals(0, data.getLast()); 144 | assertEquals(0, data.getFirst()); 145 | assertEquals(0, data.getPacketCount()); 146 | 147 | assertEquals(StateTerminating.instance, StateTerminating.instance.process(context, TestRtpPacket.create(2))); 148 | 149 | assertEquals(2, data.getFirst()); 150 | assertEquals(2, data.getLast()); 151 | assertEquals(1, data.getPacketCount()); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /test/com/tt/reaper/filter/MockPacketFilter.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.filter; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.OutputStream; 5 | import java.net.InetAddress; 6 | import java.net.Socket; 7 | 8 | import com.tt.reaper.Configuration; 9 | import com.tt.reaper.message.NotifyMessage; 10 | import com.tt.reaper.rtcp.TestRtcpPacket; 11 | 12 | public class MockPacketFilter { 13 | Socket socket; 14 | InetAddress addy; 15 | OutputStream stream; 16 | 17 | public MockPacketFilter() { 18 | try { 19 | Configuration configuration = new Configuration(); 20 | addy = InetAddress.getByName("127.0.0.1"); 21 | socket = new Socket(addy, configuration.getReadPort()); 22 | stream = socket.getOutputStream(); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | public void sendSip(String fileName) { 29 | try { 30 | byte[] buffer = new byte[2048]; 31 | FileInputStream in = new FileInputStream(fileName); 32 | int length = in.read(buffer); 33 | in.close(); 34 | stream.write(buffer, 0, length); 35 | } 36 | catch (Exception e) 37 | { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | public void sendRtcp(String fileName, String fromAddy, int fromPort, String toAddy, int toPort) { 43 | try { 44 | NotifyMessage notify = TestRtcpPacket.createNotifyDataMessage(fileName, fromAddy, fromPort, toAddy, toPort); 45 | String buffer = notify.getRequest().toString(); 46 | stream.write(buffer.getBytes(), 0, buffer.length()); 47 | } 48 | catch (Exception e) 49 | { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/com/tt/reaper/message/TestMessageFactory.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import gov.nist.javax.sip.message.SIPRequest; 6 | import gov.nist.javax.sip.message.SIPResponse; 7 | 8 | import java.text.ParseException; 9 | 10 | import javax.sip.message.Request; 11 | 12 | import org.junit.Test; 13 | 14 | public class TestMessageFactory { 15 | public static final String CALL_ID = "woot"; 16 | static MessageFactory sot = new MessageFactory(); 17 | static SIPResponse response = new SIPResponse(); 18 | static SIPRequest request = new SIPRequest(); 19 | 20 | public static SuccessMessage createSuccessMessage() 21 | { 22 | try { 23 | response.setStatusCode(200); 24 | response.setCallId(CALL_ID); 25 | } catch (ParseException e) { 26 | } 27 | return (SuccessMessage)sot.create(response); 28 | } 29 | 30 | public static SuccessMessage createSuccessMessage(String ipAddy, int port) 31 | { 32 | SuccessMessage success = createSuccessMessage(); 33 | success.setSdp(createSdp(ipAddy, port)); 34 | return success; 35 | } 36 | 37 | public static SipMessage createProvisionalMessage() { 38 | try { 39 | response.setStatusCode(180); 40 | response.setCallId(CALL_ID); 41 | } catch (ParseException e) { 42 | } 43 | return (ProvisionalMessage)sot.create(response); 44 | } 45 | 46 | public static ErrorMessage createErrorMessage() 47 | { 48 | try { 49 | response.setStatusCode(400); 50 | response.setCallId(CALL_ID); 51 | } catch (ParseException e) { 52 | } 53 | return (ErrorMessage)sot.create(response); 54 | } 55 | 56 | public static InviteMessage createInviteMessage(String ipAddy, int port) 57 | { 58 | InviteMessage invite = new InviteMessage(); 59 | invite.setSdp(createSdp(ipAddy, port)); 60 | return invite; 61 | } 62 | 63 | public static AckMessage createAckMessage() 64 | { 65 | request.setMethod(Request.ACK); 66 | return (AckMessage)sot.create(request); 67 | } 68 | 69 | public static ByeMessage createByeMessage() 70 | { 71 | request.setMethod(Request.BYE); 72 | return (ByeMessage)sot.create(request); 73 | } 74 | 75 | public static CancelMessage createCancelMessage() 76 | { 77 | request.setMethod(Request.CANCEL); 78 | return (CancelMessage)sot.create(request); 79 | } 80 | 81 | public static String createSdp(String ipAddy, int port) { 82 | return "v=0\n" + 83 | "o=francisco 13004970 13013442 IN IP4 200.57.7.204\n" + 84 | "s=X-Lite\n" + 85 | "c=IN IP4 " + ipAddy + "\n" + 86 | "t=0 0\n" + 87 | "m=audio " + port + " RTP/AVP 8 0 3 98 97 101\n" + 88 | "a=rtpmap:0 pcmu/8000\n" + 89 | "a=rtpmap:8 pcma/8000\n" + 90 | "a=rtpmap:3 gsm/8000\n" + 91 | "a=rtpmap:98 iLBC/8000\n" + 92 | "a=rtpmap:97 speex/8000\n" + 93 | "a=rtpmap:101 telephone-event/8000\n" + 94 | "a=fmtp:101 0-15\n"; 95 | } 96 | 97 | @Test 98 | public void testRequest() 99 | { 100 | request.setMethod(Request.ACK); 101 | assertTrue(sot.create(request) instanceof AckMessage); 102 | 103 | request.setMethod(Request.BYE); 104 | assertTrue(sot.create(request) instanceof ByeMessage); 105 | 106 | request.setMethod(Request.CANCEL); 107 | assertTrue(sot.create(request) instanceof CancelMessage); 108 | 109 | request.setMethod(Request.INFO); 110 | assertEquals(null, sot.create(request)); 111 | 112 | request.setMethod(Request.INVITE); 113 | assertTrue(sot.create(request) instanceof InviteMessage); 114 | 115 | request.setMethod(Request.MESSAGE); 116 | assertEquals(null, sot.create(request)); 117 | 118 | request.setMethod(Request.NOTIFY); 119 | assertTrue(sot.create(request) instanceof NotifyMessage); 120 | 121 | request.setMethod(Request.OPTIONS); 122 | assertEquals(null, sot.create(request)); 123 | 124 | request.setMethod(Request.PRACK); 125 | assertEquals(null, sot.create(request)); 126 | 127 | request.setMethod(Request.PUBLISH); 128 | assertTrue(sot.create(request) instanceof PublishMessage); 129 | 130 | request.setMethod(Request.REFER); 131 | assertEquals(null, sot.create(request)); 132 | 133 | request.setMethod(Request.REGISTER); 134 | assertEquals(null, sot.create(request)); 135 | 136 | request.setMethod(Request.SUBSCRIBE); 137 | assertEquals(null, sot.create(request)); 138 | 139 | request.setMethod(Request.UPDATE); 140 | assertEquals(null, sot.create(request)); 141 | 142 | request.setMethod("bogus"); 143 | assertEquals(null, sot.create(request)); 144 | } 145 | @Test 146 | public void testResponse() throws Exception 147 | { 148 | SIPResponse response = new SIPResponse(); 149 | 150 | response.setStatusCode(180); 151 | assertTrue(sot.create(response) instanceof ProvisionalMessage); 152 | response.setStatusCode(199); 153 | assertTrue(sot.create(response) instanceof ProvisionalMessage); 154 | response.setStatusCode(200); 155 | assertTrue(sot.create(response) instanceof SuccessMessage); 156 | response.setStatusCode(299); 157 | assertTrue(sot.create(response) instanceof SuccessMessage); 158 | response.setStatusCode(300); 159 | assertTrue(sot.create(response) instanceof ErrorMessage); 160 | response.setStatusCode(404); 161 | assertTrue(sot.create(response) instanceof ErrorMessage); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /test/com/tt/reaper/message/TestPublishMessage.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import org.junit.Test; 6 | 7 | import com.tt.reaper.ReaperLogger; 8 | 9 | public class TestPublishMessage { 10 | 11 | @Test 12 | public void testInit() 13 | { 14 | ReaperLogger.init(); 15 | PublishMessage sot = new PublishMessage("data"); 16 | assertTrue(sot.getStatus()); 17 | String strMessage = sot.getRequest().toString(); 18 | assertTrue(strMessage.contains("PUBLISH sip:collector@127.0.0.3:5060;transport=udp SIP/2.0")); 19 | assertTrue(strMessage.contains("Call-ID: ")); 20 | assertTrue(strMessage.contains("CSeq: 1 PUBLISH")); 21 | assertTrue(strMessage.contains("From: \"reaper\" ;tag=ReaperV1.0")); 22 | assertTrue(strMessage.contains("To: \"collector\" ")); 23 | assertTrue(strMessage.contains("Via: SIP/2.0/UDP 127.0.0.2:5060")); 24 | assertTrue(strMessage.contains("Max-Forwards: 70")); 25 | assertTrue(strMessage.contains("Contact: \"reaper\" ")); 26 | assertTrue(strMessage.contains("Content-Type: application/vq-rtcpxr")); 27 | assertTrue(strMessage.contains("Content-Length: 4")); 28 | assertTrue(strMessage.contains("data")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/com/tt/reaper/message/TestRtpPacket.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.message; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import com.tt.reaper.ReaperLogger; 9 | 10 | public class TestRtpPacket { 11 | public static final String SOURCE_MAC = "1:1:1:1:1:1"; 12 | public static final String SOURCE_IP = "10.0.0.1"; 13 | public static final String SOURCE_PORT = "9090"; 14 | public static final String DESTINATION_MAC = "2:2:2:2:2:2"; 15 | public static final String DESTINATION_IP = "10.0.0.2"; 16 | public static final String DESTINATION_PORT = "8080"; 17 | 18 | public static RtpPacket create(int sequenceNumber) 19 | { 20 | return new RtpPacket(create(sequenceNumber, 3)); 21 | } 22 | 23 | public static String create(int sequenceNumber, int timeStamp) { 24 | return SOURCE_MAC + ";" + SOURCE_IP + ";" + SOURCE_PORT + ";" + 25 | DESTINATION_MAC + ";" + DESTINATION_IP + ";" + DESTINATION_PORT + ";" + 26 | sequenceNumber + ";" + timeStamp + ";" + (timeStamp + 1) + ";\n"; 27 | } 28 | 29 | @Before 30 | public void setUp() 31 | { 32 | ReaperLogger.init(); 33 | } 34 | 35 | @Test 36 | public void testPacket() 37 | { 38 | RtpPacket packet = new RtpPacket(create(1, 2)); 39 | assertEquals(SOURCE_MAC, packet.getSourceMac()); 40 | assertEquals(SOURCE_IP + ":" + SOURCE_PORT, packet.getSource()); 41 | assertEquals(DESTINATION_MAC, packet.getDestinationMac()); 42 | assertEquals(DESTINATION_IP + ":" + DESTINATION_PORT, packet.getDestination()); 43 | assertEquals(1, packet.getSequenceNumber()); 44 | assertEquals(2, packet.getTimeStamp()); 45 | assertEquals(3, packet.getArrival()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/com/tt/reaper/rtcp/TestRtcpPacket.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.rtcp; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.io.FileInputStream; 7 | import java.util.Iterator; 8 | 9 | import org.junit.Test; 10 | 11 | import com.tt.reaper.message.NotifyMessage; 12 | 13 | public class TestRtcpPacket { 14 | public static final String XRFILE = "data/rtcpxr.data"; 15 | public static final String RECEIVER_FILE = "data/rtcp_receiver_report.data"; 16 | public static final String SENDER_FILE = "data/rtcp_sender_report.data"; 17 | public static final String SOURCE_MAC = "00:00:00:00:00:00"; 18 | public static final String SOURCE_IP = "10.0.100.1"; 19 | public static final int SOURCE_PORT = 9091; 20 | public static final String DESTINATION_MAC = "01:01:01:01:01:01"; 21 | public static final String DESTINATION_IP = "100.1.1.1"; 22 | public static final int DESTINATION_PORT = 8081; 23 | private static final int BUFFER_SIZE = 1024; 24 | private static byte[] buffer = new byte[BUFFER_SIZE]; 25 | 26 | public static DataPacket create(String fileName) 27 | { 28 | NotifyMessage message = createDatagram(fileName); 29 | assertTrue(message != null); 30 | DataPacket dataPacket = new DataPacket(message.getRequest().getRawContent()); 31 | return dataPacket; 32 | } 33 | 34 | public static NotifyMessage createNotifyDataMessage(String fileName, String fromAddy, int fromPort, String toAddy, int toPort) { 35 | try { 36 | String header = SOURCE_MAC + ";" + fromAddy + ";" + fromPort + ";" + DESTINATION_MAC + ";" + toAddy + ";" + toPort + ";"; 37 | System.arraycopy(header.getBytes(), 0, buffer, 0, header.length()); 38 | FileInputStream input = new FileInputStream(fileName); 39 | byte[] data = new byte[BUFFER_SIZE]; 40 | int length = input.read(data, 0, BUFFER_SIZE); 41 | input.close(); 42 | int index = header.length(); 43 | byte[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 44 | for (int j=0; j> 4) & 0x0F]; 46 | buffer[index++] = hex[data[j] & 0x0F]; 47 | } 48 | buffer[index++] = '\n'; 49 | return new NotifyMessage(new String(buffer, 0, index)); 50 | } 51 | catch (Exception e) 52 | { 53 | e.printStackTrace(); 54 | } 55 | return null; 56 | } 57 | 58 | public static NotifyMessage createDatagram(String fileName) 59 | { 60 | return createNotifyDataMessage(fileName, SOURCE_IP, SOURCE_PORT, DESTINATION_IP, DESTINATION_PORT); 61 | } 62 | 63 | @Test 64 | public void testXR() 65 | { 66 | Iterator it = create(XRFILE).getIterator(); 67 | 68 | RtcpSenderReport senderReport = (RtcpSenderReport)it.next(); 69 | assertEquals(2, senderReport.getVersion()); 70 | assertEquals(0, senderReport.getCount()); 71 | assertEquals(RtcpPacket.TYPE_SENDER_REPORT, senderReport.getPacketType()); 72 | assertEquals(28, senderReport.getLength()); 73 | assertEquals(0x302867b9, senderReport.getSSRC()); 74 | assertEquals(13599, senderReport.getSenderPacketCount()); 75 | assertEquals(2175840, senderReport.getSenderOctetCount()); 76 | 77 | RtcpSourceDescription sourceDescription = (RtcpSourceDescription) it.next(); 78 | assertEquals(2, sourceDescription.getVersion()); 79 | assertEquals(1, sourceDescription.getCount()); 80 | assertEquals(RtcpPacket.TYPE_SOURCE_DESCRIPTION, sourceDescription.getPacketType()); 81 | assertEquals(20, sourceDescription.getLength()); 82 | 83 | RtcpExtendedReport extendedReport = (RtcpExtendedReport) it.next(); 84 | assertEquals(2, extendedReport.getVersion()); 85 | assertEquals(0, extendedReport.getCount()); 86 | assertEquals(RtcpPacket.TYPE_EXTENDED_REPORT, extendedReport.getPacketType()); 87 | assertEquals(44, extendedReport.getLength()); 88 | assertEquals(0x302867b9, extendedReport.getSSRC()); 89 | 90 | Iterator eit = extendedReport.getIterator(); 91 | VoipMetricsExtendedReportBlock reportBlock = eit.next(); 92 | assertEquals(ExtendedReportBlock.VOIP_METRICS, reportBlock.getBlockType()); 93 | assertEquals(0, reportBlock.getTypeSpecific()); 94 | assertEquals(32, reportBlock.getBlockLength()); 95 | assertTrue(0.0 == reportBlock.getLossRate()); 96 | assertTrue(0.0 == reportBlock.getDiscardRate()); 97 | assertTrue(0.0 == reportBlock.getBurstDensity()); 98 | assertTrue(0.0 == reportBlock.getGapDensity()); 99 | assertEquals(0, reportBlock.getBurstDuration()); 100 | assertEquals(0, reportBlock.getGapDuration()); 101 | assertEquals(0, reportBlock.getRoundTripDelay()); 102 | assertEquals(124, reportBlock.getEndSystemDelay()); 103 | assertEquals(-85, reportBlock.getSignalLevel()); 104 | assertEquals(-72, reportBlock.getNoiseLevel()); 105 | assertEquals(75, reportBlock.getResidualEchoReturnLoss()); 106 | assertEquals(16, reportBlock.getGmin()); 107 | assertEquals(96, reportBlock.getRFactor()); 108 | assertEquals(127, reportBlock.getExtRFactor()); 109 | assertTrue(4.4 == reportBlock.getMOSLQ()); 110 | assertTrue(4.4 == reportBlock.getMOSCQ()); 111 | assertEquals(0, reportBlock.getPacketLossConcealment()); 112 | assertEquals(3, reportBlock.getJitterBufferAdaptive()); 113 | assertEquals(0, reportBlock.getJitterBufferRate()); 114 | assertEquals(20, reportBlock.getJitterBufferNominal()); 115 | assertEquals(100, reportBlock.getJitterBufferMaximum()); 116 | assertEquals(300, reportBlock.getJitterBufferAbsMaximum()); 117 | 118 | assertEquals(false, eit.hasNext()); 119 | 120 | assertEquals(false, it.hasNext()); 121 | } 122 | 123 | @Test 124 | public void testReceiverReport() 125 | { 126 | Iterator it = create(RECEIVER_FILE).getIterator(); 127 | 128 | RtcpReceiverReport receiverReport = (RtcpReceiverReport)it.next(); 129 | assertEquals(2, receiverReport.getVersion()); 130 | assertEquals(1, receiverReport.getCount()); 131 | assertEquals(RtcpPacket.TYPE_RECEIVER_REPORT, receiverReport.getPacketType()); 132 | assertEquals(32, receiverReport.getLength()); 133 | assertEquals(0xD2BD4E3E, receiverReport.getSSRC()); 134 | ReportBlock block = receiverReport.getReport(0); 135 | assertEquals(0x58f33dea, block.getSSRC()); 136 | assertEquals(0, block.getFractionLost()); 137 | assertEquals(0, block.getCumulativeLost()); 138 | assertEquals(11732, block.getExtendedHighestSequenceReceived()); 139 | assertEquals(1890, block.getJitter()); 140 | assertEquals(0x86defef9, block.getLastReport()); 141 | assertEquals(1, block.getDelaySinceLastReport()); 142 | } 143 | 144 | @Test 145 | public void testSenderReport() 146 | { 147 | Iterator it = create(SENDER_FILE).getIterator(); 148 | 149 | RtcpSenderReport senderReport = (RtcpSenderReport)it.next(); 150 | assertEquals(2, senderReport.getVersion()); 151 | assertEquals(1, senderReport.getCount()); 152 | assertEquals(RtcpPacket.TYPE_SENDER_REPORT, senderReport.getPacketType()); 153 | assertEquals(52, senderReport.getLength()); 154 | assertEquals(0xD2BD4E3E, senderReport.getSSRC()); 155 | assertEquals(0xc59286defef9db23L, senderReport.getTimeStamp()); 156 | assertEquals(49760, senderReport.getRTPTimeStamp()); 157 | assertEquals(158, senderReport.getSenderPacketCount()); 158 | assertEquals(25280, senderReport.getSenderOctetCount()); 159 | ReportBlock block = senderReport.getReport(0); 160 | assertEquals(0xd2bd4e3e, block.getSSRC()); 161 | assertEquals(0, block.getFractionLost()); 162 | assertEquals(0, block.getCumulativeLost()); 163 | assertEquals(10354846, block.getExtendedHighestSequenceReceived()); 164 | assertEquals(0, block.getJitter()); 165 | assertEquals(0x86defef9, block.getLastReport()); 166 | assertEquals(1, block.getDelaySinceLastReport()); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /test/com/tt/reaper/sip/MockCollector.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import java.io.FileInputStream; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetAddress; 7 | 8 | import com.tt.reaper.Configuration; 9 | 10 | public class MockCollector extends Thread { 11 | public static final MockCollector instance = new MockCollector(); 12 | DatagramSocket socket; 13 | byte[] buffer = new byte[2048]; 14 | Configuration configuration = new Configuration(); 15 | int port; 16 | InetAddress addy; 17 | public String message; 18 | 19 | private MockCollector() { 20 | try { 21 | port = configuration.getWritePort(); 22 | addy = InetAddress.getByName(configuration.getWriteInterface()); 23 | socket = new DatagramSocket(configuration.getCollectorPort(), InetAddress.getByName(configuration.getCollectorHost())); 24 | start(); 25 | } catch (Exception e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | public void init() { 31 | } 32 | 33 | public void run() { 34 | int count = 0; 35 | DatagramPacket packet= new DatagramPacket(new byte[2048], 0, 2048, addy, port); 36 | while (count++ < 100) { 37 | try { 38 | socket.receive(packet); 39 | message = new String(packet.getData(), 0, packet.getLength()); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | 46 | public void sendResponse(String fileName) { 47 | try { 48 | FileInputStream in = new FileInputStream(fileName); 49 | int length = in.read(buffer); 50 | in.close(); 51 | DatagramPacket packet; 52 | packet = new DatagramPacket(buffer, 0, length, addy, port); 53 | socket.send(packet); 54 | } 55 | catch (Exception e) 56 | { 57 | e.printStackTrace(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/com/tt/reaper/sip/TestReaperListener.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.sip; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | import com.tt.reaper.Reaper; 9 | import com.tt.reaper.call.CallContext; 10 | import com.tt.reaper.call.CallManager; 11 | import com.tt.reaper.filter.MockPacketFilter; 12 | import com.tt.reaper.rtcp.TestRtcpPacket; 13 | 14 | public class TestReaperListener { 15 | private static final String callId = "12013223@200.57.7.195"; 16 | private static final String fromRtcpAddy = "200.57.7.196"; 17 | private static final int fromRtcpPort = 40377; 18 | private static final String toRtcpAddy = "200.57.7.204"; 19 | private static final int toRtcpPort = 8001; 20 | 21 | @Test 22 | public void testIt() 23 | { 24 | Reaper.instance.init(); 25 | MockPacketFilter packetFilter = new MockPacketFilter(); 26 | MockCollector.instance.init(); 27 | packetFilter.sendSip("data/invite.data"); 28 | try { Thread.sleep(750); } catch (Exception e) {} 29 | 30 | CallContext context = CallManager.instance.getContextCallId(callId); 31 | assertTrue("ReaperStack.init() failed perhaps.", context != null); 32 | assertEquals("StateInvited", context.state.toString()); 33 | assertEquals("\"francisco@bestel.com\" ", context.to); 34 | assertEquals("", context.from); 35 | assertEquals(callId, context.callId); 36 | assertEquals(context, CallManager.instance.getContextRtcp(fromRtcpAddy, fromRtcpPort)); 37 | packetFilter.sendSip("data/100.data"); 38 | packetFilter.sendSip("data/180.data"); 39 | 40 | packetFilter.sendSip("data/200.data"); 41 | try { Thread.sleep(750); } catch (Exception e) {} 42 | assertEquals(context, CallManager.instance.getContextRtcp(toRtcpAddy, toRtcpPort)); 43 | 44 | assertEquals("StateInvited", context.state.toString()); 45 | packetFilter.sendSip("data/ack.data"); 46 | try { Thread.sleep(750); } catch (Exception e) {} 47 | assertEquals("StateConnected", context.state.toString()); 48 | 49 | packetFilter.sendRtcp(TestRtcpPacket.XRFILE, fromRtcpAddy, fromRtcpPort, toRtcpAddy, toRtcpPort); 50 | try { Thread.sleep(750); } catch (Exception e) {} 51 | assertEquals("StateConnected", context.state.toString()); 52 | assertTrue(MockCollector.instance.message.contains("QualityEst:RCQ=96 MOSLQ=4.4 MOSCQ=4.4")); 53 | 54 | packetFilter.sendSip("data/bye.data"); 55 | try { Thread.sleep(750); } catch (Exception e) {} 56 | assertEquals("StateTerminating", context.state.toString()); 57 | 58 | packetFilter.sendSip("data/bye200.data"); 59 | try { Thread.sleep(750); } catch (Exception e) {} 60 | assertEquals("StateTerminated", context.state.toString()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/com/tt/reaper/testFile: -------------------------------------------------------------------------------- 1 | IP address HW type Flags HW address Mask Device 2 | 192.168.1.1 0x1 0x2 00:1c:10:28:49:9c * eth0 3 | 10.0.0.2 0x1 0x2 00:1c:10:28:49:9d * eth1 4 | -------------------------------------------------------------------------------- /test/com/tt/reaper/vq/TestMetrics.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.Date; 7 | import java.util.GregorianCalendar; 8 | import java.util.Iterator; 9 | import java.util.TimeZone; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import com.tt.reaper.Reaper; 15 | import com.tt.reaper.rtcp.RtcpExtendedReport; 16 | import com.tt.reaper.rtcp.RtcpPacket; 17 | import com.tt.reaper.rtcp.RtcpSenderReport; 18 | import com.tt.reaper.rtcp.RtcpSourceDescription; 19 | import com.tt.reaper.rtcp.TestRtcpPacket; 20 | import com.tt.reaper.rtcp.VoipMetricsExtendedReportBlock; 21 | 22 | public class TestMetrics { 23 | 24 | @Before 25 | public void setUp() 26 | { 27 | Reaper.instance.init(); 28 | } 29 | 30 | @Test 31 | public void testLocalMetrics() 32 | { 33 | LocalMetrics sot = new LocalMetrics(); 34 | assertEquals("LocalMetrics:\r\n", sot.toString()); 35 | GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 36 | Date startTime = calendar.getTime(); 37 | startTime.setTime(0); 38 | Date endTime = calendar.getTime(); 39 | endTime.setTime(1000*60*60); 40 | sot.setSessionDescription("8", "pcma", "8000"); 41 | sot.setTimeStamps(startTime, endTime); 42 | sot.setCallID("6dg37f1890463"); 43 | sot.setFromID("Alice "); 44 | sot.setToID("Bill "); 45 | sot.setLocalAddr("IP=11.1.1.150 PORT=5002 SSRC=0x2468abcd"); 46 | sot.setJitterBuffer(3, 2, 40, 80, 120); 47 | sot.setPacketLoss(5.01, 2.04); 48 | sot.setBurstGapLoss(0.0, 0, 2.0, 500, 16); 49 | sot.setDelay(200, 140, 200, 2, 10); 50 | sot.setSignal(-18, -50, 55); 51 | sot.setQualityEst(88, 85, 90, 4.1, 4.0, true); 52 | String bigMessage = "LocalMetrics:\r\n" + 53 | "SessionDesc:PT=8 PD=pcma SR=8000\r\n" + 54 | "Timestamps:START=1970-01-01T00:00:00.000000Z STOP=1970-01-01T01:00:00.000000Z\r\n" + 55 | "CallID:6dg37f1890463\r\n" + 56 | "FromID:Alice \r\n" + 57 | "ToID:Bill \r\n" + 58 | "LocalAddr:IP=11.1.1.150 PORT=5002 SSRC=0x2468abcd\r\n" + 59 | "JitterBuffer:JBA=3 JBR=2 JBN=40 JBM=80 JBX=120\r\n" + 60 | "PacketLoss:NLR=5.0 JDR=2.0\r\n" + 61 | "BurstGapLoss:BLD=0.0 BD=0 GLD=2.0 GD=500 GMIN=16\r\n" + 62 | "Delay:RTD=200 ESD=140 SOWD=200 IAJ=2 MAJ=10\r\n" + 63 | "Signal:SL=-18 NL=-50 RERL=55\r\n" + 64 | "QualityEst:RLQ=88 RCQ=85 EXTRI=90 MOSLQ=4.1 MOSCQ=4.0 QoEEstAlg=P.564\r\n"; 65 | assertEquals(bigMessage, sot.toString()); 66 | } 67 | 68 | @Test 69 | public void testRemoteMetrics() 70 | { 71 | RemoteMetrics sot = new RemoteMetrics(); 72 | assertEquals("RemoteMetrics:\r\n", sot.toString()); 73 | } 74 | 75 | @Test 76 | public void testSetVoipMetrics() 77 | { 78 | RemoteMetrics sot = new RemoteMetrics(); 79 | sot.setSessionDescription("0", "pcmu", "8000"); 80 | sot.setCallID("6dg37f1890463"); 81 | sot.setFromID("Alice "); 82 | sot.setToID("Bill "); 83 | sot.setLocalAddr("IP=11.1.1.150 PORT=5002 SSRC=0x2468abcd"); 84 | sot.setLocalMac("01:02:03:04:05:06"); 85 | Iterator it = TestRtcpPacket.create(TestRtcpPacket.XRFILE).getIterator(); 86 | assertTrue(it.next() instanceof RtcpSenderReport); 87 | assertTrue(it.next() instanceof RtcpSourceDescription); 88 | RtcpExtendedReport report = (RtcpExtendedReport)it.next(); 89 | Iterator eit = report.getIterator(); 90 | VoipMetricsExtendedReportBlock reportBlock = eit.next(); 91 | sot.setMetrics(reportBlock); 92 | String bigMessage = "RemoteMetrics:\r\n" + 93 | "SessionDesc:PT=0 PD=pcmu SR=8000\r\n" + 94 | "CallID:6dg37f1890463\r\n" + 95 | "FromID:Alice \r\n" + 96 | "ToID:Bill \r\n" + 97 | "LocalAddr:IP=11.1.1.150 PORT=5002 SSRC=0x2468abcd\r\n" + 98 | "LocalMAC:01:02:03:04:05:06\r\n" + 99 | "JitterBuffer:JBA=3 JBR=0 JBN=20 JBM=100 JBX=300\r\n" + 100 | "PacketLoss:NLR=0.0 JDR=0.0\r\n" + 101 | "BurstGapLoss:BLD=0.0 BD=0 GLD=0.0 GD=0 GMIN=16\r\n" + 102 | "Delay:RTD=0 ESD=124\r\n" + 103 | "Signal:SL=-85 NL=-72 RERL=75\r\n" + 104 | "QualityEst:RCQ=96 MOSLQ=4.4 MOSCQ=4.4\r\n"; 105 | assertEquals(bigMessage, sot.toString()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/com/tt/reaper/vq/TestVQReportEvent.java: -------------------------------------------------------------------------------- 1 | package com.tt.reaper.vq; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | import com.tt.reaper.vq.VQReportEvent; 8 | 9 | public class TestVQReportEvent { 10 | 11 | @Test 12 | public void testNoMetrics() 13 | { 14 | VQReportEvent sot; 15 | sot = new VQSessionReport(new LocalMetrics()); 16 | assertEquals("VQSessionReport : CallTerm\r\nLocalMetrics:\r\n", sot.toString()); 17 | sot = new VQIntervalReport(new LocalMetrics()); 18 | assertEquals("VQIntervalReport\r\nLocalMetrics:\r\n", sot.toString()); 19 | } 20 | } 21 | --------------------------------------------------------------------------------