├── .gitignore ├── COPYRIGHT.txt ├── HISTORY.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── TODO.txt ├── license ├── LICENSE.netty.txt └── LICENSE.slf4j.txt ├── pom.xml └── src ├── assembly └── default.xml ├── functionaltest └── java │ └── com │ └── biasedbit │ └── efflux │ └── session │ ├── ControlPacketFunctionalTest.java │ ├── MultiParticipantSessionFunctionalTest.java │ └── SingleParticipantSessionFunctionalTest.java ├── javadoc ├── overview.html ├── resources │ ├── gradient.png │ ├── h1_header.png │ ├── nav_header.png │ ├── table_header.png │ └── warning_24.png └── stylesheet.css ├── main ├── java │ └── com │ │ └── biasedbit │ │ └── efflux │ │ ├── logging │ │ └── Logger.java │ │ ├── network │ │ ├── ControlHandler.java │ │ ├── ControlPacketDecoder.java │ │ ├── ControlPacketEncoder.java │ │ ├── ControlPacketReceiver.java │ │ ├── DataHandler.java │ │ ├── DataPacketDecoder.java │ │ ├── DataPacketEncoder.java │ │ └── DataPacketReceiver.java │ │ ├── packet │ │ ├── AbstractReportPacket.java │ │ ├── AppDataPacket.java │ │ ├── ByePacket.java │ │ ├── CompoundControlPacket.java │ │ ├── ControlPacket.java │ │ ├── DataPacket.java │ │ ├── ReceiverReportPacket.java │ │ ├── ReceptionReport.java │ │ ├── RtpVersion.java │ │ ├── SdesChunk.java │ │ ├── SdesChunkItem.java │ │ ├── SdesChunkItems.java │ │ ├── SdesChunkPrivItem.java │ │ ├── SenderReportPacket.java │ │ └── SourceDescriptionPacket.java │ │ ├── participant │ │ ├── DefaultParticipantDatabase.java │ │ ├── ParticipantDatabase.java │ │ ├── ParticipantEventListener.java │ │ ├── ParticipantOperation.java │ │ ├── RtpParticipant.java │ │ ├── RtpParticipantInfo.java │ │ └── SingleParticipantDatabase.java │ │ ├── session │ │ ├── AbstractRtpSession.java │ │ ├── MultiParticipantSession.java │ │ ├── RtpSession.java │ │ ├── RtpSessionControlListener.java │ │ ├── RtpSessionDataListener.java │ │ ├── RtpSessionEventListener.java │ │ └── SingleParticipantSession.java │ │ └── util │ │ ├── ByteUtils.java │ │ └── TimeUtils.java └── resources │ └── log4j.properties ├── site ├── css │ └── stylesheet.css ├── img │ ├── dots.png │ ├── dots.xcf │ ├── gradient.png │ ├── h1_header.png │ ├── icon.png │ ├── icon.xcf │ ├── squarelogo.png │ ├── squarelogo.xcf │ └── squarelogo_alt.png └── index.html ├── test └── java │ └── com │ └── biasedbit │ └── efflux │ ├── packet │ ├── ByePacketTest.java │ ├── ControlPacketTest.java │ ├── DataPacketTest.java │ ├── ReceiverReportPacketTest.java │ ├── SenderReportPacketTest.java │ ├── SourceChunkItemsTest.java │ ├── SourceChunkTest.java │ └── SourceDescriptionPacketTest.java │ ├── participant │ └── DefaultParticipantDatabaseTest.java │ └── session │ └── MultiParticipantSessionTest.java └── xref └── stylesheet.css /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | /.settings 4 | /target 5 | /reports 6 | *.ipr 7 | *.iws 8 | *.iml 9 | 10 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | efflux is licensed under the Apache License version 2.0 as published by the 2 | Apache Software Foundation. 3 | 4 | Copyright 2010 Bruno de Carvalho 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | efflux 2 | ====== 3 | 4 | 0.5.0 5 | ----- 6 | * First release! 7 | * Multiple participant RTP session 8 | * Special purpose two-way session (NAT tolerant) 9 | * Automated RTCP handling (leaving, joining, handling new participants, processing and sending reports) 10 | * Adaptative RTCP bandwidth usage 11 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | efflux 2 | ====== 3 | 4 | Please visit the project web site for more information: 5 | 6 | * http://efflux.biasedbit.com 7 | 8 | Copyright 2010 Bruno de Carvalho 9 | 10 | Bruno de Carvalho licenses this product to you under the Apache License, 11 | version 2.0 (the "License"); you may not use this product except in compliance 12 | with the License. 13 | You may obtain a copy of the License at: 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software distributed 18 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 19 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 20 | specific language governing permissions and limitations under the License. 21 | 22 | Also, please refer to each LICENSE..txt file, which is located in 23 | the 'license' directory of the distribution file, for the license terms of the 24 | components that this product depends on. 25 | 26 | ------------------------------------------------------------------------------- 27 | This product depends on 'JBoss Netty', an open source Java NIO framework, which 28 | can be obtained at: 29 | 30 | * LICENSE: 31 | * license/LICENSE.netty.txt (Apache License 2.0) 32 | * HOMEPAGE: 33 | * http://www.jboss.org/netty/ 34 | 35 | This product depends on 'SLF4J', a Simple Logging Facade for Java, which 36 | can be obtained at: 37 | 38 | * LICENSE: 39 | * license/LICENSE.slf4j.txt (MIT license) 40 | * HOMEPAGE: 41 | * http://www.slf4j.org/ 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | efflux 2 | ====== 3 | 4 | A Java RTP stack 5 | ---------------- 6 | **efflux** is a simple Java RTP stack, whose target are applications who do not directly generate RTP content themselves but need to send or receive data using this protocol. 7 | It aims to be fully RFC compliant but has utilities for those special cases where the other end doesn't quite follow the RFC (which, sadly, happens often). 8 | 9 | Project page: [http://efflux.biasedbit.com](http://efflux.biasedbit.com) 10 | 11 | Dependencies 12 | ------------ 13 | 14 | * JDK 1.6 15 | * [SLF4J 1.6](http://www.slf4j.org/download.html) 16 | * [Netty 3.2](http://jboss.org/netty/downloads.html) 17 | 18 | License 19 | ------- 20 | 21 | **efflux** is licensed under the [Apache License version 2.0](http://www.apache.org/licenses/LICENSE-2.0) as published by the Apache Software Foundation. 22 | 23 | Quick & Dirty examples 24 | ---------------------- 25 | Coming soon. -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | * Finish off automated RTCP handling 2 | * Update local sender statistics 3 | * Process receiver reports and fire events related to traffic conditions changing 4 | * RTCP bandwidth adjustment 5 | * Automated sending of SR/RR packets -------------------------------------------------------------------------------- /license/LICENSE.slf4j.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2008 QOS.ch 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.factor45.efflux 6 | efflux 7 | 0.4.0 8 | jar 9 | 10 | efflux 11 | http://efflux.biasedbit.com 12 | 13 | Missing description 14 | 15 | 2010 16 | 17 | 18 | Bruno de Carvalho 19 | bruno@biasedbit.com 20 | http://bruno.biasedbit.com 21 | 22 | 23 | 24 | 25 | 26 | Apache License, Version 2.0 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | 30 | 31 | 32 | UTF-8 33 | 34 | 35 | 36 | 37 | repository.jboss.org 38 | http://repository.jboss.org/nexus/content/groups/public/ 39 | 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | repository.jboss.org 49 | http://repository.jboss.org/nexus/content/groups/public/ 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 57 | 58 | junit 59 | junit 60 | 4.7 61 | test 62 | 63 | 64 | org.jboss.netty 65 | netty 66 | 3.2.2.Final 67 | compile 68 | 69 | 70 | org.slf4j 71 | slf4j-api 72 | 1.6.1 73 | compile 74 | 75 | 76 | 77 | 78 | true 79 | org.slf4j 80 | slf4j-log4j12 81 | 1.6.1 82 | runtime 83 | 84 | 85 | true 86 | log4j 87 | log4j 88 | 1.2.16 89 | runtime 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | maven-compiler-plugin 98 | 99 | 1.6 100 | 1.6 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-jar-plugin 108 | 2.3.1 109 | 110 | 111 | **/log4j.properties 112 | 113 | 114 | 115 | 116 | 117 | 118 | maven-surefire-plugin 119 | 120 | never 121 | 122 | **/Abstract* 123 | **/*TestUtil* 124 | 125 | 126 | 127 | 128 | 129 | 130 | maven-javadoc-plugin 131 | 2.7 132 | 133 | 134 | generate-javadoc 135 | package 136 | 137 | javadoc 138 | 139 | 140 | 141 | 142 | org.jboss.apiviz.APIviz 143 | 144 | org.jboss.apiviz 145 | apiviz 146 | 1.3.1.GA 147 | 148 | false 149 | public 150 | ${basedir}/src/javadoc/stylesheet.css 151 | ${basedir}/src/javadoc/ 152 | true 153 | true 154 | ${project.build.directory}/api 155 | ${project.build.directory}/api 156 | api 157 | UTF-8 158 | UTF-8 159 | true 160 | true 161 | true 162 | true 163 | ${basedir}/src/javadoc/overview.html 164 | ${project.name} API Reference (${project.version}) 165 | ${project.name} API Reference (${project.version) 166 | 167 | -link http://java.sun.com/javase/6/docs/api/ 168 | -link http://docs.jboss.org/netty/3.2/api/ 169 | 170 | -sourceclasspath ${project.build.outputDirectory} 171 | -nopackagediagram 172 | 173 | UTF-8 174 | en_GB 175 | 176 | ${project.groupId}.util*:org.jboss* 177 | 178 | 179 | 180 | 181 | 182 | 183 | maven-jxr-plugin 184 | 185 | 186 | generate-xref 187 | package 188 | 189 | jxr 190 | 191 | 192 | 193 | 194 | UTF-8 195 | UTF-8 196 | true 197 | ${project.build.directory}/xref 198 | ${project.build.directory}/api 199 | ${basedir}/src/xref/stylesheet.css 200 | ${project.name} Source Xref (${project.version}) 201 | ${project.name} Source Xref (${project.version}) 202 | 120 203 | 204 | 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-source-plugin 210 | 2.1.1 211 | 212 | 213 | attach-source 214 | package 215 | 216 | jar 217 | 218 | 219 | true 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | maven-assembly-plugin 228 | 229 | 230 | generate-distribution 231 | package 232 | 233 | single 234 | 235 | 236 | 237 | 238 | 239 | ${basedir}/src/assembly/default.xml 240 | 241 | false 242 | true 243 | gnu 244 | 245 | 246 | 247 | 248 | 249 | org.apache.maven.plugins 250 | maven-idea-plugin 251 | 2.2 252 | 253 | true 254 | 1.6 255 | 256 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /src/assembly/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | dist 4 | 5 | tar.bz2 6 | zip 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | **/README* 15 | **/HISTORY* 16 | **/LICENSE* 17 | **/NOTICE* 18 | **/COPYRIGHT* 19 | **/license/LICENSE** 20 | 21 | **/src/main/** 22 | **/src/test/** 23 | **/src/functionaltest/** 24 | 25 | 26 | 27 | **/pom.xml 28 | **/target/** 29 | **/.*/** 30 | 31 | 32 | 33 | 34 | 35 | target 36 | jar 37 | 38 | ${project.build.finalName}*.jar 39 | 40 | 41 | ${project.build.finalName}*-javadoc.jar 42 | 43 | 44 | 45 | 46 | 47 | target/api 48 | doc/api 49 | 50 | **/** 51 | 52 | 53 | 54 | 55 | 56 | target/xref 57 | doc/xref 58 | 59 | **/** 60 | 61 | 62 | 63 | 64 | 65 | target/docbook/publish/en-GB 66 | doc/guide 67 | 68 | **/** 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/functionaltest/java/com/biasedbit/efflux/session/MultiParticipantSessionFunctionalTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import com.biasedbit.efflux.participant.RtpParticipant; 21 | import com.biasedbit.efflux.participant.RtpParticipantInfo; 22 | import org.junit.After; 23 | import org.junit.Test; 24 | 25 | import java.util.concurrent.CountDownLatch; 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | /** 33 | * @author Bruno de Carvalho 34 | */ 35 | public class MultiParticipantSessionFunctionalTest { 36 | 37 | private static final byte N = 5; 38 | 39 | private MultiParticipantSession[] sessions; 40 | 41 | @After 42 | public void tearDown() { 43 | if (this.sessions != null) { 44 | for (MultiParticipantSession session : this.sessions) { 45 | if (session != null) { 46 | session.terminate(); 47 | } 48 | } 49 | } 50 | } 51 | 52 | @Test 53 | public void testDeliveryToAllParticipants() throws Exception { 54 | this.sessions = new MultiParticipantSession[N]; 55 | final AtomicInteger[] counters = new AtomicInteger[N]; 56 | final CountDownLatch latch = new CountDownLatch(N); 57 | 58 | for (byte i = 0; i < N; i++) { 59 | RtpParticipant participant = RtpParticipant 60 | .createReceiver(new RtpParticipantInfo(i), "127.0.0.1", 10000 + (i * 2), 20001 + (i * 2)); 61 | this.sessions[i] = new MultiParticipantSession("session" + i, 8, participant); 62 | assertTrue(this.sessions[i].init()); 63 | final AtomicInteger counter = new AtomicInteger(); 64 | counters[i] = counter; 65 | this.sessions[i].addDataListener(new RtpSessionDataListener() { 66 | 67 | @Override 68 | public void dataPacketReceived(RtpSession session, RtpParticipantInfo participant, DataPacket packet) { 69 | System.err.println(session.getId() + " received data from " + participant + ": " + packet); 70 | if (counter.incrementAndGet() == ((N - 1) * 2)) { 71 | // Release the latch once all N-1 messages (because it wont receive the message it sends) are 72 | // received. 73 | latch.countDown(); 74 | } 75 | } 76 | }); 77 | } 78 | 79 | // All sessions set up, now add all participants to the other sessions 80 | for (byte i = 0; i < N; i++) { 81 | for (byte j = 0; j < N; j++) { 82 | if (j == i) { 83 | continue; 84 | } 85 | 86 | // You can NEVER add the same participant to two distinct sessions otherwise you'll ruin it (as both 87 | // will be messing in the same participant). 88 | RtpParticipant participant = RtpParticipant 89 | .createReceiver(new RtpParticipantInfo(j), "127.0.0.1", 10000 + (j * 2), 20001 + (j * 2)); 90 | System.err.println("Adding " + participant + " to session " + this.sessions[i].getId()); 91 | assertTrue(this.sessions[i].addReceiver(participant)); 92 | } 93 | } 94 | 95 | byte[] deadbeef = {(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef}; 96 | for (byte i = 0; i < N; i++) { 97 | assertTrue(this.sessions[i].sendData(deadbeef, 0x45, false)); 98 | assertTrue(this.sessions[i].sendData(deadbeef, 0x45, false)); 99 | } 100 | 101 | latch.await(5000L, TimeUnit.MILLISECONDS); 102 | 103 | for (byte i = 0; i < N; i++) { 104 | assertEquals(((N - 1) * 2), counters[i].get()); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/javadoc/overview.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Description missing. 9 | 10 | -------------------------------------------------------------------------------- /src/javadoc/resources/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/javadoc/resources/gradient.png -------------------------------------------------------------------------------- /src/javadoc/resources/h1_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/javadoc/resources/h1_header.png -------------------------------------------------------------------------------- /src/javadoc/resources/nav_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/javadoc/resources/nav_header.png -------------------------------------------------------------------------------- /src/javadoc/resources/table_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/javadoc/resources/table_header.png -------------------------------------------------------------------------------- /src/javadoc/resources/warning_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/javadoc/resources/warning_24.png -------------------------------------------------------------------------------- /src/javadoc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* Javadoc style sheet */ 2 | 3 | /* Define colors, fonts and other style attributes here to override the defaults */ 4 | 5 | /* Page background color */ 6 | body { 7 | background: #fff url("resources/gradient.png") repeat-x; 8 | margin: 0 auto; 9 | font-family: sans-serif; 10 | font-size: 12px; 11 | padding: 0 2em; 12 | color: #000; 13 | } 14 | 15 | /* Common elements */ 16 | 17 | font { 18 | font-size: inherit; 19 | color: inherit; 20 | font-weight: inherit; 21 | } 22 | 23 | hr { 24 | border-top: 1px dotted #eee; 25 | border-bottom: 0 none; 26 | } 27 | 28 | tt, tt *, pre, pre *, code, code * { 29 | font-family: "Liberation Mono", "DejaVu Sans Mono", Consolas, Monaco, "Vera Sans Mono", "Lucida Console", "Courier New", monospace; 30 | } 31 | 32 | a:link { 33 | color: #555; 34 | text-decoration: none; 35 | } 36 | 37 | a:visited { 38 | color: #555; 39 | text-decoration: none; 40 | } 41 | 42 | a:hover { 43 | color: #58a037; 44 | text-decoration: underline; 45 | } 46 | 47 | /* Headings */ 48 | h1 { 49 | background: url("resources/h1_header.png") no-repeat; 50 | border-top: 1px dotted #ccc; 51 | line-height: 1.2em; 52 | color: #333; 53 | font-size: 2em; 54 | padding: 1.5em; 55 | margin-top: 0; 56 | text-align: left; 57 | } 58 | 59 | /* Default Table elements and colors */ 60 | 61 | th, table { 62 | border-collapse: collapse; 63 | border-color: #eee; 64 | } 65 | 66 | .TableHeadingColor { 67 | background: #000 url("resources/table_header.png") repeat-x scroll left top; 68 | color: #fff; 69 | font-size: 12px; 70 | font-weight: bold; 71 | height: 31px; 72 | text-align: left; 73 | padding: 1.5em; 74 | } 75 | 76 | .TableHeadingColor th { 77 | padding-left: 10px; 78 | } 79 | 80 | .TableSubHeadingColor { 81 | background: #eee; 82 | } 83 | 84 | /* Light mauve */ 85 | .TableRowColor { 86 | background: #fff; 87 | border-color: #eee; 88 | } 89 | 90 | .TableRowColor td { 91 | line-height: 175%; 92 | padding-left: 10px; 93 | } 94 | 95 | /* Font used in left-hand frame lists */ 96 | .FrameTitleFont { 97 | font-weight: bold; 98 | margin-top: 1em; 99 | display: block; 100 | } 101 | 102 | .FrameHeadingFont { 103 | font-weight: bold; 104 | margin-top: 1em; 105 | display: block; 106 | } 107 | 108 | .FrameItemFont { 109 | font-size: 100%; 110 | } 111 | 112 | /* Navigation bar fonts and colors */ 113 | 114 | .NavBarCell1 { 115 | background: #fff url("resources/nav_header.png") repeat-x scroll left top; 116 | line-height: 2em; 117 | padding-left: 6px; 118 | padding-right: 6px; 119 | } 120 | 121 | .NavBarFont1 { 122 | color: white; 123 | } 124 | 125 | .NavBarCell1 a { 126 | color: white; 127 | } 128 | 129 | .NavBarCell1Rev { 130 | background-color: #fff; 131 | padding-left: 6px; 132 | padding-right: 6px; 133 | } 134 | 135 | .NavBarFont1 { 136 | color: #fff; 137 | } 138 | 139 | .NavBarFont1Rev { 140 | color: #333; 141 | } 142 | 143 | .NavBarCell2 { 144 | background-color: #fff; 145 | } 146 | 147 | .NavBarCell3 { 148 | background-color: #fff; 149 | } 150 | 151 | .note { 152 | border: 1px dotted #333; 153 | margin: 15px; 154 | background-color: #ddd; 155 | padding: 10px; 156 | } 157 | 158 | .note .header { 159 | background: url("resources/warning_24.png") no-repeat; 160 | margin: 0 0 10px; 161 | padding: 2px 2px 2px 30px; 162 | font-size: 1.2em; 163 | font-weight: bold; 164 | } 165 | 166 | pre.code { 167 | border: 1px dotted #aaa; 168 | margin: 15px; 169 | padding: 10px; 170 | background-color: #eee; 171 | } 172 | 173 | em { 174 | color: #666; 175 | } -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/ControlHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.CompoundControlPacket; 21 | import org.jboss.netty.channel.ChannelHandlerContext; 22 | import org.jboss.netty.channel.ExceptionEvent; 23 | import org.jboss.netty.channel.MessageEvent; 24 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 25 | 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | /** 29 | * @author Bruno de Carvalho 30 | */ 31 | public class ControlHandler extends SimpleChannelUpstreamHandler { 32 | 33 | // constants ------------------------------------------------------------------------------------------------------ 34 | 35 | private static final Logger LOG = Logger.getLogger(ControlHandler.class); 36 | 37 | // internal vars -------------------------------------------------------------------------------------------------- 38 | 39 | private final AtomicInteger counter; 40 | private final ControlPacketReceiver receiver; 41 | 42 | // constructors --------------------------------------------------------------------------------------------------- 43 | 44 | public ControlHandler(ControlPacketReceiver receiver) { 45 | this.receiver = receiver; 46 | this.counter = new AtomicInteger(); 47 | } 48 | 49 | // SimpleChannelUpstreamHandler ----------------------------------------------------------------------------------- 50 | 51 | @Override 52 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { 53 | if (e.getMessage() instanceof CompoundControlPacket) { 54 | this.receiver.controlPacketReceived(e.getRemoteAddress(), (CompoundControlPacket) e.getMessage()); 55 | } 56 | } 57 | 58 | @Override 59 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { 60 | // Just log and proceed... 61 | LOG.error("Caught exception on channel {}.", e.getCause(), e.getChannel()); 62 | } 63 | 64 | // public methods ------------------------------------------------------------------------------------------------- 65 | 66 | public int getPacketsReceived() { 67 | return this.counter.get(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/ControlPacketDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.CompoundControlPacket; 21 | import com.biasedbit.efflux.packet.ControlPacket; 22 | import org.jboss.netty.buffer.ChannelBuffer; 23 | import org.jboss.netty.channel.ChannelEvent; 24 | import org.jboss.netty.channel.ChannelHandlerContext; 25 | import org.jboss.netty.channel.ChannelUpstreamHandler; 26 | import org.jboss.netty.channel.Channels; 27 | import org.jboss.netty.channel.MessageEvent; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * @author Bruno de Carvalho 34 | */ 35 | public class ControlPacketDecoder implements ChannelUpstreamHandler { 36 | 37 | // constants ------------------------------------------------------------------------------------------------------ 38 | 39 | protected static final Logger LOG = Logger.getLogger(ControlPacketDecoder.class); 40 | 41 | // ChannelUpstreamHandler ----------------------------------------------------------------------------------------- 42 | 43 | public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { 44 | // Only handle MessageEvent. 45 | if (!(evt instanceof MessageEvent)) { 46 | ctx.sendUpstream(evt); 47 | return; 48 | } 49 | 50 | // Only decode if it's a ChannelBuffer. 51 | MessageEvent e = (MessageEvent) evt; 52 | if (!(e.getMessage() instanceof ChannelBuffer)) { 53 | return; 54 | } 55 | 56 | ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); 57 | if ((buffer.readableBytes() % 4) != 0) { 58 | LOG.debug("Invalid RTCP packet received: total length should be multiple of 4 but is {}", 59 | buffer.readableBytes()); 60 | return; 61 | } 62 | 63 | // Usually 2 packets per UDP frame... 64 | List controlPacketList = new ArrayList(2); 65 | 66 | // While there's data to read, keep on decoding. 67 | while (buffer.readableBytes() > 0) { 68 | try { 69 | controlPacketList.add(ControlPacket.decode(buffer)); 70 | } catch (Exception e1) { 71 | LOG.debug("Exception caught while decoding RTCP packet.", e1); 72 | } 73 | } 74 | 75 | if (!controlPacketList.isEmpty()) { 76 | // Only send upwards when there were more than one valid decoded packets. 77 | // TODO shouldn't the whole compound packet be discarded when one of them has errors?! 78 | Channels.fireMessageReceived(ctx, new CompoundControlPacket(controlPacketList), e.getRemoteAddress()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/ControlPacketEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.CompoundControlPacket; 21 | import com.biasedbit.efflux.packet.ControlPacket; 22 | import org.jboss.netty.buffer.ChannelBuffer; 23 | import org.jboss.netty.buffer.ChannelBuffers; 24 | import org.jboss.netty.channel.ChannelDownstreamHandler; 25 | import org.jboss.netty.channel.ChannelEvent; 26 | import org.jboss.netty.channel.ChannelHandler; 27 | import org.jboss.netty.channel.ChannelHandlerContext; 28 | import org.jboss.netty.channel.Channels; 29 | import org.jboss.netty.channel.MessageEvent; 30 | 31 | import java.util.List; 32 | 33 | /** 34 | * @author Bruno de Carvalho 35 | */ 36 | @ChannelHandler.Sharable 37 | public class ControlPacketEncoder implements ChannelDownstreamHandler { 38 | 39 | // constants ------------------------------------------------------------------------------------------------------ 40 | 41 | protected static final Logger LOG = Logger.getLogger(ControlPacketEncoder.class); 42 | 43 | // constructors --------------------------------------------------------------------------------------------------- 44 | 45 | private ControlPacketEncoder() { 46 | } 47 | 48 | // public static methods ------------------------------------------------------------------------------------------ 49 | 50 | public static ControlPacketEncoder getInstance() { 51 | return InstanceHolder.INSTANCE; 52 | } 53 | 54 | // ChannelDownstreamHandler --------------------------------------------------------------------------------------- 55 | 56 | @Override 57 | public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { 58 | if (!(evt instanceof MessageEvent)) { 59 | ctx.sendDownstream(evt); 60 | return; 61 | } 62 | 63 | MessageEvent e = (MessageEvent) evt; 64 | try { 65 | if (e.getMessage() instanceof ControlPacket) { 66 | Channels.write(ctx, e.getFuture(), ((ControlPacket) e.getMessage()).encode(), e.getRemoteAddress()); 67 | } else if (e.getMessage() instanceof CompoundControlPacket) { 68 | List packets = ((CompoundControlPacket) e.getMessage()).getControlPackets(); 69 | ChannelBuffer[] buffers = new ChannelBuffer[packets.size()]; 70 | for (int i = 0; i < buffers.length; i++) { 71 | buffers[i] = packets.get(i).encode(); 72 | } 73 | ChannelBuffer compoundBuffer = ChannelBuffers.wrappedBuffer(buffers); 74 | Channels.write(ctx, e.getFuture(), compoundBuffer, e.getRemoteAddress()); 75 | } 76 | } catch (Exception e1) { 77 | LOG.error("Failed to encode compound RTCP packet to send.", e1); 78 | } 79 | 80 | // Otherwise do nothing. 81 | } 82 | 83 | // private classes ------------------------------------------------------------------------------------------------ 84 | 85 | private static final class InstanceHolder { 86 | private static final ControlPacketEncoder INSTANCE = new ControlPacketEncoder(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/ControlPacketReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.packet.CompoundControlPacket; 20 | 21 | import java.net.SocketAddress; 22 | 23 | /** 24 | * @author Bruno de Carvalho 25 | */ 26 | public interface ControlPacketReceiver { 27 | 28 | void controlPacketReceived(SocketAddress origin, CompoundControlPacket packet); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/DataHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.DataPacket; 21 | import org.jboss.netty.channel.ChannelHandlerContext; 22 | import org.jboss.netty.channel.ExceptionEvent; 23 | import org.jboss.netty.channel.MessageEvent; 24 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 25 | 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | /** 29 | * @author Bruno de Carvalho 30 | */ 31 | public class DataHandler extends SimpleChannelUpstreamHandler { 32 | 33 | // constants ------------------------------------------------------------------------------------------------------ 34 | 35 | private static final Logger LOG = Logger.getLogger(DataHandler.class); 36 | 37 | // internal vars -------------------------------------------------------------------------------------------------- 38 | 39 | private final AtomicInteger counter; 40 | private final DataPacketReceiver receiver; 41 | 42 | // constructors --------------------------------------------------------------------------------------------------- 43 | 44 | public DataHandler(DataPacketReceiver receiver) { 45 | this.receiver = receiver; 46 | this.counter = new AtomicInteger(); 47 | } 48 | 49 | // SimpleChannelUpstreamHandler ----------------------------------------------------------------------------------- 50 | 51 | @Override 52 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { 53 | if (e.getMessage() instanceof DataPacket) { 54 | this.receiver.dataPacketReceived(e.getRemoteAddress(), (DataPacket) e.getMessage()); 55 | } 56 | } 57 | 58 | @Override 59 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { 60 | // Just log and proceed... 61 | LOG.error("Caught exception on channel {}.", e.getCause(), e.getChannel()); 62 | } 63 | 64 | // public methods ------------------------------------------------------------------------------------------------- 65 | 66 | public int getPacketsReceived() { 67 | return this.counter.get(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/DataPacketDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.DataPacket; 21 | import org.jboss.netty.buffer.ChannelBuffer; 22 | import org.jboss.netty.channel.Channel; 23 | import org.jboss.netty.channel.ChannelHandlerContext; 24 | import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class DataPacketDecoder extends OneToOneDecoder { 30 | 31 | // constants ------------------------------------------------------------------------------------------------------ 32 | 33 | protected static final Logger LOG = Logger.getLogger(OneToOneDecoder.class); 34 | 35 | // OneToOneDecoder ------------------------------------------------------------------------------------------------ 36 | 37 | @Override 38 | protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { 39 | if (!(msg instanceof ChannelBuffer)) { 40 | return null; 41 | } 42 | 43 | try { 44 | return DataPacket.decode((ChannelBuffer) msg); 45 | } catch (Exception e) { 46 | LOG.debug("Failed to decode RTP packet.", e); 47 | return null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/DataPacketEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | import org.jboss.netty.channel.Channel; 22 | import org.jboss.netty.channel.ChannelHandler; 23 | import org.jboss.netty.channel.ChannelHandlerContext; 24 | import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | @ChannelHandler.Sharable 30 | public class DataPacketEncoder extends OneToOneEncoder { 31 | 32 | // constructors --------------------------------------------------------------------------------------------------- 33 | 34 | private DataPacketEncoder() { 35 | } 36 | 37 | // public static methods ------------------------------------------------------------------------------------------ 38 | 39 | public static DataPacketEncoder getInstance() { 40 | return InstanceHolder.INSTANCE; 41 | } 42 | 43 | // OneToOneEncoder ------------------------------------------------------------------------------------------------ 44 | 45 | @Override 46 | protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { 47 | if (!(msg instanceof DataPacket)) { 48 | return ChannelBuffers.EMPTY_BUFFER; 49 | } 50 | 51 | DataPacket packet = (DataPacket) msg; 52 | if (packet.getDataSize() == 0) { 53 | return ChannelBuffers.EMPTY_BUFFER; 54 | } 55 | return packet.encode(); 56 | } 57 | 58 | // private classes ------------------------------------------------------------------------------------------------ 59 | 60 | private static final class InstanceHolder { 61 | private static final DataPacketEncoder INSTANCE = new DataPacketEncoder(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/network/DataPacketReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.network; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | 21 | import java.net.SocketAddress; 22 | 23 | /** 24 | * @author Bruno de Carvalho 25 | */ 26 | public interface DataPacketReceiver { 27 | 28 | void dataPacketReceived(SocketAddress origin, DataPacket packet); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/AbstractReportPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Bruno de Carvalho 25 | */ 26 | public abstract class AbstractReportPacket extends ControlPacket { 27 | 28 | // internal vars -------------------------------------------------------------------------------------------------- 29 | 30 | protected long senderSsrc; 31 | protected List receptionReports; 32 | 33 | // constructors --------------------------------------------------------------------------------------------------- 34 | 35 | protected AbstractReportPacket(Type type) { 36 | super(type); 37 | } 38 | 39 | // public methods ------------------------------------------------------------------------------------------------- 40 | 41 | public boolean addReceptionReportBlock(ReceptionReport block) { 42 | if (this.receptionReports == null) { 43 | this.receptionReports = new ArrayList(); 44 | return this.receptionReports.add(block); 45 | } 46 | 47 | // 5 bits is the limit 48 | return (this.receptionReports.size() < 31) && this.receptionReports.add(block); 49 | } 50 | 51 | public byte getReceptionReportCount() { 52 | if (this.receptionReports == null) { 53 | return 0; 54 | } 55 | 56 | return (byte) this.receptionReports.size(); 57 | } 58 | 59 | // getters & setters ---------------------------------------------------------------------------------------------- 60 | 61 | public long getSenderSsrc() { 62 | return senderSsrc; 63 | } 64 | 65 | public void setSenderSsrc(long senderSsrc) { 66 | if ((senderSsrc < 0) || (senderSsrc > 0xffffffffL)) { 67 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 68 | } 69 | this.senderSsrc = senderSsrc; 70 | } 71 | 72 | public List getReceptionReports() { 73 | if (this.receptionReports == null) { 74 | return null; 75 | } 76 | return Collections.unmodifiableList(this.receptionReports); 77 | } 78 | 79 | public void setReceptionReports(List receptionReports) { 80 | if (receptionReports.size() >= 31) { 81 | throw new IllegalArgumentException("At most 31 report blocks can be sent in a *ReportPacket"); 82 | } 83 | this.receptionReports = receptionReports; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/AppDataPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | 21 | /** 22 | * @author Bruno de Carvalho 23 | */ 24 | public class AppDataPacket extends ControlPacket { 25 | 26 | // constructors --------------------------------------------------------------------------------------------------- 27 | 28 | public AppDataPacket(Type type) { 29 | super(type); 30 | } 31 | 32 | // public static methods ------------------------------------------------------------------------------------------ 33 | 34 | public static ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize, AppDataPacket packet) { 35 | return null; 36 | } 37 | 38 | // ControlPacket -------------------------------------------------------------------------------------------------- 39 | 40 | @Override 41 | public ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize) { 42 | return encode(currentCompoundLength, fixedBlockSize, this); 43 | } 44 | 45 | @Override 46 | public ChannelBuffer encode() { 47 | return encode(0, 0, this); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/ByePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | import org.jboss.netty.util.CharsetUtil; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | /** 28 | * @author Bruno de Carvalho 29 | */ 30 | public class ByePacket extends ControlPacket { 31 | 32 | // internal vars -------------------------------------------------------------------------------------------------- 33 | 34 | private List ssrcList; 35 | private String reasonForLeaving; 36 | 37 | // constructors --------------------------------------------------------------------------------------------------- 38 | 39 | public ByePacket() { 40 | super(Type.BYE); 41 | } 42 | 43 | // public static methods ------------------------------------------------------------------------------------------ 44 | 45 | public static ByePacket decode(ChannelBuffer buffer, boolean hasPadding, byte innerBlocks, int length) { 46 | ByePacket packet = new ByePacket(); 47 | int read = 0; 48 | for (int i = 0; i < innerBlocks; i++) { 49 | packet.addSsrc(buffer.readUnsignedInt()); 50 | read += 4; 51 | } 52 | 53 | // Length is written in 32bit words, not octet count. 54 | int lengthInOctets = (length * 4); 55 | if (read < lengthInOctets) { 56 | byte[] reasonBytes = new byte[buffer.readUnsignedByte()]; 57 | buffer.readBytes(reasonBytes); 58 | packet.reasonForLeaving = new String(reasonBytes, CharsetUtil.UTF_8); 59 | read += (1 + reasonBytes.length); 60 | if (read < lengthInOctets) { 61 | // Skip remaining bytes (used for padding). This takes care of both the null termination bytes (padding 62 | // of the 'reason for leaving' string and the packet padding bytes. 63 | buffer.skipBytes(lengthInOctets - read); 64 | } 65 | } 66 | 67 | return packet; 68 | } 69 | 70 | public static ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize, ByePacket packet) { 71 | if ((currentCompoundLength < 0) || ((currentCompoundLength % 4) > 0)) { 72 | throw new IllegalArgumentException("Current compound length must be a non-negative multiple of 4"); 73 | } 74 | if ((fixedBlockSize < 0) || ((fixedBlockSize % 4) > 0)) { 75 | throw new IllegalArgumentException("Padding modulus must be a non-negative multiple of 4"); 76 | } 77 | 78 | int size = 4; 79 | ChannelBuffer buffer; 80 | if (packet.ssrcList != null) { 81 | size += packet.ssrcList.size() * 4; 82 | } 83 | byte[] reasonForLeavingBytes = null; 84 | int reasonForLeavingPadding = 0; 85 | if (packet.reasonForLeaving != null) { 86 | reasonForLeavingBytes = packet.reasonForLeaving.getBytes(CharsetUtil.UTF_8); 87 | if (reasonForLeavingBytes.length > 255) { 88 | throw new IllegalArgumentException("Reason for leaving cannot exceed 255 bytes and this has " + 89 | reasonForLeavingBytes.length); 90 | } 91 | 92 | size += (1 + reasonForLeavingBytes.length); 93 | // 'reason for leaving' must be 32bit aligned, so extra null octets might be needed. 94 | reasonForLeavingPadding = 4 - ((1 + reasonForLeavingBytes.length) % 4); 95 | if (reasonForLeavingPadding == 4) { 96 | reasonForLeavingPadding = 0; 97 | } 98 | if (reasonForLeavingPadding > 0) { 99 | size += reasonForLeavingPadding; 100 | } 101 | } 102 | 103 | // If packet was configured to have padding, calculate padding and add it. 104 | int padding = 0; 105 | if (fixedBlockSize > 0) { 106 | // If padding modulus is > 0 then the padding is equal to: 107 | // (global size of the compound RTCP packet) mod (block size) 108 | // Block size alignment might be necessary for some encryption algorithms 109 | // RFC section 6.4.1 110 | padding = fixedBlockSize - ((size + currentCompoundLength) % fixedBlockSize); 111 | if (padding == fixedBlockSize) { 112 | padding = 0; 113 | } 114 | } 115 | 116 | size += padding; 117 | 118 | // Allocate the buffer and write contents 119 | buffer = ChannelBuffers.buffer(size); 120 | // First byte: Version (2b), Padding (1b), SSRC (chunks) count (5b) 121 | byte b = packet.getVersion().getByte(); 122 | if (padding > 0) { 123 | b |= 0x20; 124 | } 125 | if (packet.ssrcList != null) { 126 | b |= packet.ssrcList.size(); 127 | } 128 | buffer.writeByte(b); 129 | // Second byte: Packet Type 130 | buffer.writeByte(packet.type.getByte()); 131 | // Third byte: total length of the packet, in multiples of 4 bytes (32bit words) - 1 132 | int sizeInOctets = (size / 4) - 1; 133 | buffer.writeShort(sizeInOctets); 134 | // Payload: ssrc list 135 | if (packet.ssrcList != null) { 136 | for (Long ssrc : packet.ssrcList) { 137 | buffer.writeInt(ssrc.intValue()); 138 | } 139 | } 140 | // If 'reason for leaving' was specified, add it. 141 | if (reasonForLeavingBytes != null) { 142 | buffer.writeByte(reasonForLeavingBytes.length); 143 | buffer.writeBytes(reasonForLeavingBytes); 144 | for (int i = 0; i < reasonForLeavingPadding; i++) { 145 | buffer.writeByte(0x00); 146 | } 147 | } 148 | 149 | if (padding > 0) { 150 | // Final bytes: padding 151 | for (int i = 0; i < (padding - 1); i++) { 152 | buffer.writeByte(0x00); 153 | } 154 | 155 | // Final byte: the amount of padding bytes that should be discarded. 156 | // Unless something's wrong, it will be a multiple of 4. 157 | buffer.writeByte(padding); 158 | } 159 | 160 | return buffer; 161 | } 162 | 163 | // ControlPacket -------------------------------------------------------------------------------------------------- 164 | 165 | @Override 166 | public ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize) { 167 | return encode(currentCompoundLength, fixedBlockSize, this); 168 | } 169 | 170 | @Override 171 | public ChannelBuffer encode() { 172 | return encode(0, 0, this); 173 | } 174 | 175 | // public methods ------------------------------------------------------------------------------------------------- 176 | 177 | public boolean addSsrc(long ssrc) { 178 | if ((ssrc < 0) || (ssrc > 0xffffffffL)) { 179 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 180 | } 181 | 182 | if (this.ssrcList == null) { 183 | this.ssrcList = new ArrayList(); 184 | } 185 | 186 | return this.ssrcList.add(ssrc); 187 | } 188 | 189 | // getters & setters ---------------------------------------------------------------------------------------------- 190 | 191 | public List getSsrcList() { 192 | return Collections.unmodifiableList(this.ssrcList); 193 | } 194 | 195 | public void setSsrcList(List ssrcList) { 196 | this.ssrcList = new ArrayList(ssrcList.size()); 197 | for (Long ssrc : ssrcList) { 198 | // Validate each ssrc being added. 199 | this.addSsrc(ssrc); 200 | } 201 | } 202 | 203 | public String getReasonForLeaving() { 204 | return reasonForLeaving; 205 | } 206 | 207 | public void setReasonForLeaving(String reasonForLeaving) { 208 | this.reasonForLeaving = reasonForLeaving; 209 | } 210 | 211 | // low level overrides -------------------------------------------------------------------------------------------- 212 | 213 | @Override 214 | public String toString() { 215 | return new StringBuilder() 216 | .append("ByePacket{") 217 | .append("ssrcList=").append(this.ssrcList) 218 | .append(", reasonForLeaving='").append(reasonForLeaving).append('\'') 219 | .append('}').toString(); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/CompoundControlPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class CompoundControlPacket { 26 | 27 | // internal vars -------------------------------------------------------------------------------------------------- 28 | 29 | private final List controlPackets; 30 | 31 | // constructors --------------------------------------------------------------------------------------------------- 32 | 33 | public CompoundControlPacket(ControlPacket... controlPackets) { 34 | if (controlPackets.length == 0) { 35 | throw new IllegalArgumentException("At least one RTCP packet must be provided"); 36 | } 37 | this.controlPackets = Arrays.asList(controlPackets); 38 | } 39 | 40 | public CompoundControlPacket(List controlPackets) { 41 | if ((controlPackets == null) || controlPackets.isEmpty()) { 42 | throw new IllegalArgumentException("ControlPacket list cannot be null or empty"); 43 | } 44 | this.controlPackets = controlPackets; 45 | } 46 | 47 | // public methods ------------------------------------------------------------------------------------------------- 48 | 49 | public int getPacketCount() { 50 | return this.controlPackets.size(); 51 | } 52 | 53 | // getters & setters ---------------------------------------------------------------------------------------------- 54 | 55 | public List getControlPackets() { 56 | return this.controlPackets; 57 | } 58 | 59 | // low level overrides -------------------------------------------------------------------------------------------- 60 | 61 | @Override 62 | public String toString() { 63 | StringBuilder builder = new StringBuilder(); 64 | builder.append("CompoundControlPacket{\n"); 65 | for (ControlPacket packet : this.controlPackets) { 66 | builder.append(" ").append(packet.toString()).append('\n'); 67 | } 68 | return builder.append('}').toString(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/ControlPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | 21 | /** 22 | * @author Bruno de Carvalho 23 | */ 24 | public abstract class ControlPacket { 25 | 26 | // internal vars -------------------------------------------------------------------------------------------------- 27 | 28 | protected RtpVersion version; 29 | protected Type type; 30 | 31 | // constructors --------------------------------------------------------------------------------------------------- 32 | 33 | protected ControlPacket(Type type) { 34 | this.version = RtpVersion.V2; 35 | this.type = type; 36 | } 37 | 38 | // public methods ------------------------------------------------------------------------------------------------- 39 | 40 | public static ControlPacket decode(ChannelBuffer buffer) { 41 | if ((buffer.readableBytes() % 4) > 0) { 42 | throw new IllegalArgumentException("Invalid RTCP packet length: expecting multiple of 4 and got " + 43 | buffer.readableBytes()); 44 | } 45 | byte b = buffer.readByte(); 46 | RtpVersion version = RtpVersion.fromByte(b); 47 | if (!version.equals(RtpVersion.V2)) { 48 | return null; 49 | } 50 | boolean hasPadding = (b & 0x20) > 0; // mask 0010 0000 51 | byte innerBlocks = (byte) (b & 0x1f); // mask 0001 1111 52 | 53 | ControlPacket.Type type = ControlPacket.Type.fromByte(buffer.readByte()); 54 | 55 | // This length is in 32bit (4byte) words. These first 4 bytes already read don't count. 56 | int length = buffer.readShort(); 57 | if (length == 0) { 58 | return null; 59 | } 60 | 61 | // No need to pass version downwards, only V2 is supported so subclasses can safely assume V2. 62 | // I know it's ugly when the superclass knows about the subclasses but since this method is static (and NEEDS 63 | // to be) the alternative was having this method in a external class. Pointless. 64 | switch (type) { 65 | case SENDER_REPORT: 66 | return SenderReportPacket.decode(buffer, hasPadding, innerBlocks, length); 67 | case RECEIVER_REPORT: 68 | return ReceiverReportPacket.decode(buffer, hasPadding, innerBlocks, length); 69 | case SOURCE_DESCRIPTION: 70 | return SourceDescriptionPacket.decode(buffer, hasPadding, innerBlocks, length); 71 | case BYE: 72 | return ByePacket.decode(buffer, hasPadding, innerBlocks, length); 73 | case APP_DATA: 74 | return null; 75 | default: 76 | throw new IllegalArgumentException("Unknown RTCP packet type: " + type); 77 | } 78 | } 79 | 80 | public abstract ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize); 81 | 82 | public abstract ChannelBuffer encode(); 83 | 84 | // getters & setters ---------------------------------------------------------------------------------------------- 85 | 86 | public RtpVersion getVersion() { 87 | return version; 88 | } 89 | 90 | public void setVersion(RtpVersion version) { 91 | if (version != RtpVersion.V2) { 92 | throw new IllegalArgumentException("Only V2 is supported"); 93 | } 94 | this.version = version; 95 | } 96 | 97 | public Type getType() { 98 | return type; 99 | } 100 | 101 | // public classes ------------------------------------------------------------------------------------------------- 102 | 103 | public static enum Type { 104 | 105 | // constants -------------------------------------------------------------------------------------------------- 106 | 107 | SENDER_REPORT((byte) 0xc8), 108 | RECEIVER_REPORT((byte) 0xc9), 109 | SOURCE_DESCRIPTION((byte) 0xca), 110 | BYE((byte) 0xcb), 111 | APP_DATA((byte) 0xcc); 112 | 113 | // internal vars ---------------------------------------------------------------------------------------------- 114 | 115 | private byte b; 116 | 117 | // constructors ----------------------------------------------------------------------------------------------- 118 | 119 | Type(byte b) { 120 | this.b = b; 121 | } 122 | 123 | // public methods --------------------------------------------------------------------------------------------- 124 | 125 | public static Type fromByte(byte b) { 126 | switch (b) { 127 | case (byte) 0xc8: 128 | return SENDER_REPORT; 129 | case (byte) 0xc9: 130 | return RECEIVER_REPORT; 131 | case (byte) 0xca: 132 | return SOURCE_DESCRIPTION; 133 | case (byte) 0xcb: 134 | return BYE; 135 | case (byte) 0xcc: 136 | return APP_DATA; 137 | default: 138 | throw new IllegalArgumentException("Unknown RTCP packet type: " + b); 139 | } 140 | } 141 | 142 | // getters & setters ------------------------------------------------------------------------------------------ 143 | 144 | public byte getByte() { 145 | return this.b; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/ReceiverReportPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class ReceiverReportPacket extends AbstractReportPacket { 26 | 27 | // constructors --------------------------------------------------------------------------------------------------- 28 | 29 | public ReceiverReportPacket() { 30 | super(Type.RECEIVER_REPORT); 31 | } 32 | 33 | // public static methods ------------------------------------------------------------------------------------------ 34 | 35 | public static ReceiverReportPacket decode(ChannelBuffer buffer, boolean hasPadding, byte innerBlocks, int length) { 36 | ReceiverReportPacket packet = new ReceiverReportPacket(); 37 | 38 | packet.setSenderSsrc(buffer.readUnsignedInt()); 39 | 40 | int read = 4; 41 | for (int i = 0; i < innerBlocks; i++) { 42 | packet.addReceptionReportBlock(ReceptionReport.decode(buffer)); 43 | read += 24; // Each SR/RR block has 24 bytes (6 32bit words) 44 | } 45 | 46 | // Length is written in 32bit words, not octet count. 47 | int lengthInOctets = (length * 4); 48 | // (hasPadding == true) check is not done here. RFC respecting implementations will set the padding bit to 1 49 | // if length of packet is bigger than the necessary to convey the data; therefore it's a redundant check. 50 | if (read < lengthInOctets) { 51 | // Skip remaining bytes (used for padding). 52 | buffer.skipBytes(lengthInOctets - read); 53 | } 54 | 55 | return packet; 56 | } 57 | 58 | public static ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize, ReceiverReportPacket packet) { 59 | if ((currentCompoundLength < 0) || ((currentCompoundLength % 4) > 0)) { 60 | throw new IllegalArgumentException("Current compound length must be a non-negative multiple of 4"); 61 | } 62 | if ((fixedBlockSize < 0) || ((fixedBlockSize % 4) > 0)) { 63 | throw new IllegalArgumentException("Padding modulus must be a non-negative multiple of 4"); 64 | } 65 | 66 | // Common header + sender ssrc 67 | int size = 4 + 4; 68 | ChannelBuffer buffer; 69 | if (packet.receptionReports != null) { 70 | size += packet.receptionReports.size() * 24; 71 | } 72 | 73 | // If packet was configured to have padding, calculate padding and add it. 74 | int padding = 0; 75 | if (fixedBlockSize > 0) { 76 | // If padding modulus is > 0 then the padding is equal to: 77 | // (global size of the compound RTCP packet) mod (block size) 78 | // Block size alignment might be necessary for some encryption algorithms 79 | // RFC section 6.4.1 80 | padding = fixedBlockSize - ((size + currentCompoundLength) % fixedBlockSize); 81 | if (padding == fixedBlockSize) { 82 | padding = 0; 83 | } 84 | } 85 | size += padding; 86 | 87 | // Allocate the buffer and write contents 88 | buffer = ChannelBuffers.buffer(size); 89 | // First byte: Version (2b), Padding (1b), RR count (5b) 90 | byte b = packet.getVersion().getByte(); 91 | if (padding > 0) { 92 | b |= 0x20; 93 | } 94 | b |= packet.getReceptionReportCount(); 95 | buffer.writeByte(b); 96 | // Second byte: Packet Type 97 | buffer.writeByte(packet.type.getByte()); 98 | // Third byte: total length of the packet, in multiples of 4 bytes (32bit words) - 1 99 | int sizeInOctets = (size / 4) - 1; 100 | buffer.writeShort(sizeInOctets); 101 | // Next 24 bytes: ssrc, ntp timestamp, rtp timestamp, octet count, packet count 102 | buffer.writeInt((int) packet.senderSsrc); 103 | // Payload: report blocks 104 | if (packet.getReceptionReportCount() > 0) { 105 | for (ReceptionReport block : packet.receptionReports) { 106 | buffer.writeBytes(block.encode()); 107 | } 108 | } 109 | 110 | if (padding > 0) { 111 | // Final bytes: padding 112 | for (int i = 0; i < (padding - 1); i++) { 113 | buffer.writeByte(0x00); 114 | } 115 | 116 | // Final byte: the amount of padding bytes that should be discarded. 117 | // Unless something's wrong, it will be a multiple of 4. 118 | buffer.writeByte(padding); 119 | } 120 | 121 | return buffer; 122 | } 123 | 124 | // ControlPacket -------------------------------------------------------------------------------------------------- 125 | 126 | @Override 127 | public ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize) { 128 | return encode(currentCompoundLength, fixedBlockSize, this); 129 | } 130 | 131 | @Override 132 | public ChannelBuffer encode() { 133 | return encode(0, 0, this); 134 | } 135 | 136 | // low level overrides -------------------------------------------------------------------------------------------- 137 | 138 | @Override 139 | public String toString() { 140 | return new StringBuilder() 141 | .append("ReceiverReportPacket{") 142 | .append("senderSsrc=").append(this.senderSsrc) 143 | .append(", receptionReports=").append(this.receptionReports) 144 | .append('}').toString(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/ReceptionReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class ReceptionReport { 26 | 27 | // internal vars -------------------------------------------------------------------------------------------------- 28 | 29 | private long ssrc; 30 | private short fractionLost; 31 | private int cumulativeNumberOfPacketsLost; 32 | private long extendedHighestSequenceNumberReceived; 33 | private long interArrivalJitter; 34 | private long lastSenderReport; 35 | private long delaySinceLastSenderReport; 36 | 37 | // constructors --------------------------------------------------------------------------------------------------- 38 | 39 | public ReceptionReport() { 40 | } 41 | 42 | // public static methods ------------------------------------------------------------------------------------------ 43 | 44 | public static ChannelBuffer encode(ReceptionReport block) { 45 | ChannelBuffer buffer = ChannelBuffers.buffer(24); // 4 + 1 + 3 + 4 + 4 + 4 + 4 46 | buffer.writeInt((int) block.ssrc); 47 | buffer.writeByte(block.fractionLost); 48 | buffer.writeMedium(block.cumulativeNumberOfPacketsLost); 49 | buffer.writeInt((int) block.extendedHighestSequenceNumberReceived); 50 | buffer.writeInt((int) block.interArrivalJitter); 51 | buffer.writeInt((int) block.lastSenderReport); 52 | buffer.writeInt((int) block.delaySinceLastSenderReport); 53 | return buffer; 54 | } 55 | 56 | public static ReceptionReport decode(ChannelBuffer buffer) { 57 | ReceptionReport block = new ReceptionReport(); 58 | block.setSsrc(buffer.readUnsignedInt()); 59 | block.setFractionLost(buffer.readUnsignedByte()); 60 | block.setCumulativeNumberOfPacketsLost(buffer.readUnsignedMedium()); 61 | block.setExtendedHighestSequenceNumberReceived(buffer.readUnsignedInt()); 62 | block.setInterArrivalJitter(buffer.readUnsignedInt()); 63 | block.setLastSenderReport(buffer.readUnsignedInt()); 64 | block.setDelaySinceLastSenderReport(buffer.readUnsignedInt()); 65 | return block; 66 | } 67 | 68 | // public methods ------------------------------------------------------------------------------------------------- 69 | 70 | public ChannelBuffer encode() { 71 | return encode(this); 72 | } 73 | 74 | // getters & setters ---------------------------------------------------------------------------------------------- 75 | 76 | public long getSsrc() { 77 | return ssrc; 78 | } 79 | 80 | public void setSsrc(long ssrc) { 81 | if ((ssrc < 0) || (ssrc > 0xffffffffL)) { 82 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 83 | } 84 | this.ssrc = ssrc; 85 | } 86 | 87 | public short getFractionLost() { 88 | return fractionLost; 89 | } 90 | 91 | public void setFractionLost(short fractionLost) { 92 | if ((fractionLost < 0) || (fractionLost > 0xffffffffL)) { 93 | throw new IllegalArgumentException("Valid range for Fraction Lost is [0;0xff]"); 94 | } 95 | this.fractionLost = fractionLost; 96 | } 97 | 98 | public int getCumulativeNumberOfPacketsLost() { 99 | return cumulativeNumberOfPacketsLost; 100 | } 101 | 102 | public void setCumulativeNumberOfPacketsLost(int cumulativeNumberOfPacketsLost) { 103 | if ((cumulativeNumberOfPacketsLost < 0) || (cumulativeNumberOfPacketsLost > 0x00ffffff)) { 104 | throw new IllegalArgumentException("Valid range for Cumulative Number of Packets Lost is [0;0x00ffffff]"); 105 | } 106 | this.cumulativeNumberOfPacketsLost = cumulativeNumberOfPacketsLost; 107 | } 108 | 109 | public long getExtendedHighestSequenceNumberReceived() { 110 | return extendedHighestSequenceNumberReceived; 111 | } 112 | 113 | public void setExtendedHighestSequenceNumberReceived(long extendedHighestSequenceNumberReceived) { 114 | if ((extendedHighestSequenceNumberReceived < 0) || (extendedHighestSequenceNumberReceived > 0xffffffffL)) { 115 | throw new IllegalArgumentException("Valid range for Extended Highest SeqNumber Received is [0;0xffffffff]"); 116 | } 117 | this.extendedHighestSequenceNumberReceived = extendedHighestSequenceNumberReceived; 118 | } 119 | 120 | public long getInterArrivalJitter() { 121 | return interArrivalJitter; 122 | } 123 | 124 | public void setInterArrivalJitter(long interArrivalJitter) { 125 | if ((interArrivalJitter < 0) || (interArrivalJitter > 0xffffffffL)) { 126 | throw new IllegalArgumentException("Valid range for Interarrival Jitter is [0;0xffffffff]"); 127 | } 128 | this.interArrivalJitter = interArrivalJitter; 129 | } 130 | 131 | public long getLastSenderReport() { 132 | return lastSenderReport; 133 | } 134 | 135 | public void setLastSenderReport(long lastSenderReport) { 136 | if ((lastSenderReport < 0) || (lastSenderReport > 0xffffffffL)) { 137 | throw new IllegalArgumentException("Valid range for Last Sender Report is [0;0xffffffff]"); 138 | } 139 | this.lastSenderReport = lastSenderReport; 140 | } 141 | 142 | public long getDelaySinceLastSenderReport() { 143 | return delaySinceLastSenderReport; 144 | } 145 | 146 | public void setDelaySinceLastSenderReport(long delaySinceLastSenderReport) { 147 | if ((delaySinceLastSenderReport < 0) || (delaySinceLastSenderReport > 0xffffffffL)) { 148 | throw new IllegalArgumentException("Valid range for Delay Since Last Sender Report is [0;0xffffffff]"); 149 | } 150 | this.delaySinceLastSenderReport = delaySinceLastSenderReport; 151 | } 152 | 153 | // low level overrides -------------------------------------------------------------------------------------------- 154 | 155 | 156 | @Override 157 | public String toString() { 158 | return new StringBuilder() 159 | .append("ReceptionReport{") 160 | .append("ssrc=").append(this.ssrc) 161 | .append(", fractionLost=").append(this.fractionLost) 162 | .append(", cumulativeNumberOfPacketsLost=").append(this.cumulativeNumberOfPacketsLost) 163 | .append(", extendedHighestSequenceNumberReceived=").append(this.extendedHighestSequenceNumberReceived) 164 | .append(", interArrivalJitter=").append(this.interArrivalJitter) 165 | .append(", lastSenderReport=").append(this.lastSenderReport) 166 | .append(", delaySinceLastSenderReport=").append(this.delaySinceLastSenderReport) 167 | .append('}').toString(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/RtpVersion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | /** 20 | * @author Bruno de Carvalho 21 | */ 22 | public enum RtpVersion { 23 | 24 | // constants ------------------------------------------------------------------------------------------------------ 25 | 26 | V2((byte) 0x80), 27 | V1((byte) 0x40), 28 | V0((byte) 0x00); 29 | 30 | // internal vars -------------------------------------------------------------------------------------------------- 31 | 32 | private final byte b; 33 | 34 | // constructors --------------------------------------------------------------------------------------------------- 35 | 36 | private RtpVersion(byte b) { 37 | this.b = b; 38 | } 39 | 40 | // public static methods ------------------------------------------------------------------------------------------ 41 | 42 | public static RtpVersion fromByte(byte b) throws IllegalArgumentException { 43 | byte tmp = (byte) (b & 0xc0); 44 | // Starts from version 2, which is the most common. 45 | for (RtpVersion version : values()) { 46 | if (version.getByte() == tmp) { 47 | return version; 48 | } 49 | } 50 | 51 | throw new IllegalArgumentException("Unknown version for byte: " + b); 52 | } 53 | 54 | // getters & setters ---------------------------------------------------------------------------------------------- 55 | 56 | public byte getByte() { 57 | return b; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SdesChunk.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class SdesChunk { 30 | 31 | // internal vars -------------------------------------------------------------------------------------------------- 32 | 33 | private long ssrc; 34 | private List items; 35 | 36 | // constructors --------------------------------------------------------------------------------------------------- 37 | 38 | public SdesChunk() { 39 | } 40 | 41 | public SdesChunk(long ssrc) { 42 | this.ssrc = ssrc; 43 | } 44 | 45 | // public static methods ------------------------------------------------------------------------------------------ 46 | 47 | public static SdesChunk decode(ChannelBuffer buffer) { 48 | SdesChunk chunk = new SdesChunk(); 49 | chunk.ssrc = buffer.readUnsignedInt(); 50 | 51 | // Because some genious thought that 32bit alignment would be cool, we must count the amount of bytes remaining 52 | // after decoding each SdesChunkItem so that when we read the end/null item, we know how many more bytes we 53 | // must read to discard the padding bytes (hit the 32bit alignment barrier). 54 | int read = 0; 55 | for (;;) { 56 | if (buffer.readableBytes() == 0) { 57 | // Some implementations don't write the mandatory last item (end/null). 58 | return chunk; 59 | } 60 | int remaining = buffer.readableBytes(); 61 | SdesChunkItem item = SdesChunkItems.decode(buffer); 62 | read += remaining - buffer.readableBytes(); 63 | if (item.getType().equals(SdesChunkItem.Type.NULL)) { 64 | int paddingBytes = 4 - (read % 4); 65 | if (paddingBytes != 4) { 66 | buffer.skipBytes(paddingBytes); 67 | } 68 | return chunk; 69 | } 70 | 71 | chunk.addItem(item); 72 | } 73 | } 74 | 75 | public static ChannelBuffer encode(SdesChunk chunk) { 76 | ChannelBuffer buffer; 77 | if (chunk.items == null) { 78 | // Allocate 8 bytes: 4 for ssrc, 1 for null item and other 3 null octets for 32 bit alignment 79 | buffer = ChannelBuffers.buffer(8); 80 | buffer.writeInt((int) chunk.ssrc); // ssrc 81 | buffer.writeInt(0); // 4 null octets (1 for null item and 3 for 32bit alignment) 82 | return buffer; 83 | } else { 84 | // Start with SSRC 85 | int size = 4; 86 | // Add the length of each item 87 | List encodedChunkItems = new ArrayList(chunk.items.size()); 88 | for (SdesChunkItem item : chunk.items) { 89 | ChannelBuffer encodedChunk = item.encode(); 90 | encodedChunkItems.add(encodedChunk); 91 | size += encodedChunk.readableBytes(); 92 | } 93 | // Add the null item 94 | size += 1; 95 | // Calculate padding and add it (for 32bit alignment). 96 | int padding = 4 - (size % 4); 97 | if (padding == 4) { 98 | padding = 0; 99 | } 100 | size += padding; 101 | 102 | // Write the buffer contents: SSRC, chunks, null item and padding 103 | buffer = ChannelBuffers.buffer(size); 104 | buffer.writeInt((int) chunk.ssrc); 105 | for (ChannelBuffer encodedChunk : encodedChunkItems) { 106 | buffer.writeBytes(encodedChunk); 107 | } 108 | buffer.writeByte(0x00); 109 | for (int i = 0; i < padding; i++) { 110 | buffer.writeByte(0x00); 111 | } 112 | } 113 | 114 | return buffer; 115 | } 116 | 117 | // public methods ------------------------------------------------------------------------------------------------- 118 | 119 | public ChannelBuffer encode() { 120 | return encode(this); 121 | } 122 | 123 | public boolean addItem(SdesChunkItem item) { 124 | if (item.getType() == SdesChunkItem.Type.NULL) { 125 | throw new IllegalArgumentException("You don't need to manually add the null/end element"); 126 | } 127 | 128 | if (this.items == null) { 129 | this.items = new ArrayList(); 130 | } 131 | 132 | return this.items.add(item); 133 | } 134 | 135 | public String getItemValue(SdesChunkItem.Type type) { 136 | if (this.items == null) { 137 | return null; 138 | } 139 | 140 | for (SdesChunkItem item : this.items) { 141 | if (item.getType() == type) { 142 | return item.getValue(); 143 | } 144 | } 145 | 146 | return null; 147 | } 148 | 149 | // getters & setters ---------------------------------------------------------------------------------------------- 150 | 151 | public long getSsrc() { 152 | return ssrc; 153 | } 154 | 155 | public void setSsrc(long ssrc) { 156 | if ((ssrc < 0) || (ssrc > 0xffffffffL)) { 157 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 158 | } 159 | this.ssrc = ssrc; 160 | } 161 | 162 | public List getItems() { 163 | if (this.items == null) { 164 | return null; 165 | } 166 | 167 | return Collections.unmodifiableList(this.items); 168 | } 169 | 170 | public void setItems(List items) { 171 | this.items = items; 172 | } 173 | 174 | // low level overrides -------------------------------------------------------------------------------------------- 175 | 176 | @Override 177 | public String toString() { 178 | return new StringBuilder() 179 | .append("SdesChunk{") 180 | .append("ssrc=").append(this.ssrc) 181 | .append(", items=").append(this.items) 182 | .append('}').toString(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SdesChunkItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | import org.jboss.netty.util.CharsetUtil; 22 | 23 | /** 24 | * @author Bruno de Carvalho 25 | */ 26 | public class SdesChunkItem { 27 | 28 | // internal vars -------------------------------------------------------------------------------------------------- 29 | 30 | protected final Type type; 31 | protected final String value; 32 | 33 | // constructors --------------------------------------------------------------------------------------------------- 34 | 35 | protected SdesChunkItem(Type type, String value) { 36 | this.type = type; 37 | this.value = value; 38 | } 39 | 40 | // public methods ------------------------------------------------------------------------------------------------- 41 | 42 | public ChannelBuffer encode() { 43 | // Technically, this never happens as you're not allowed to add NULL items to a SdesChunk instance, but... 44 | if (this.type == Type.NULL) { 45 | ChannelBuffer buffer = ChannelBuffers.buffer(1); 46 | buffer.writeByte(0x00); 47 | return buffer; 48 | } 49 | 50 | byte[] valueBytes; 51 | if (this.value != null) { 52 | // RFC section 6.5 mandates that this must be UTF8 53 | // http://tools.ietf.org/html/rfc3550#section-6.5 54 | valueBytes = this.value.getBytes(CharsetUtil.UTF_8); 55 | } else { 56 | valueBytes = new byte[]{}; 57 | } 58 | 59 | if (valueBytes.length > 255) { 60 | throw new IllegalArgumentException("Content (text) can be no longer than 255 bytes and this has " + 61 | valueBytes.length); 62 | } 63 | 64 | // Type (1b), length (1b), value (xb) 65 | ChannelBuffer buffer = ChannelBuffers.buffer(2 + valueBytes.length); 66 | buffer.writeByte(this.type.getByte()); 67 | buffer.writeByte(valueBytes.length); 68 | buffer.writeBytes(valueBytes); 69 | 70 | return buffer; 71 | } 72 | 73 | // getters & setters ---------------------------------------------------------------------------------------------- 74 | 75 | public Type getType() { 76 | return type; 77 | } 78 | 79 | public String getValue() { 80 | return value; 81 | } 82 | 83 | // low level overrides -------------------------------------------------------------------------------------------- 84 | 85 | @Override 86 | public String toString() { 87 | return new StringBuilder() 88 | .append("SdesChunkItem{") 89 | .append("type=").append(this.type) 90 | .append(", value='").append(this.value).append('\'') 91 | .append('}').toString(); 92 | } 93 | 94 | // public classes ------------------------------------------------------------------------------------------------- 95 | 96 | public static enum Type { 97 | 98 | // constants -------------------------------------------------------------------------------------------------- 99 | 100 | NULL((byte) 0), 101 | CNAME((byte) 1), 102 | NAME((byte) 2), 103 | EMAIL((byte) 3), 104 | PHONE((byte) 4), 105 | LOCATION((byte) 5), 106 | TOOL((byte) 6), 107 | NOTE((byte) 7), 108 | PRIV((byte) 8); 109 | 110 | // internal vars ---------------------------------------------------------------------------------------------- 111 | 112 | private final byte b; 113 | 114 | // constructors ----------------------------------------------------------------------------------------------- 115 | 116 | Type(byte b) { 117 | this.b = b; 118 | } 119 | 120 | // public static methods -------------------------------------------------------------------------------------- 121 | 122 | public static Type fromByte(byte b) { 123 | switch (b) { 124 | case 0: return NULL; 125 | case 1: return CNAME; 126 | case 2: return NAME; 127 | case 3: return EMAIL; 128 | case 4: return PHONE; 129 | case 5: return LOCATION; 130 | case 6: return TOOL; 131 | case 7: return NOTE; 132 | case 8: return PRIV; 133 | default: throw new IllegalArgumentException("Unknown SSRC Chunk Item type: " + b); 134 | } 135 | } 136 | 137 | // getters & setters ------------------------------------------------------------------------------------------ 138 | 139 | public byte getByte() { 140 | return b; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SdesChunkItems.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.util.CharsetUtil; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class SdesChunkItems { 26 | 27 | // constants ------------------------------------------------------------------------------------------------------ 28 | 29 | public static final SdesChunkItem NULL_ITEM = new SdesChunkItem(SdesChunkItem.Type.NULL, null); 30 | 31 | // public static methods ------------------------------------------------------------------------------------------ 32 | 33 | public static SdesChunkItem createNullItem() { 34 | return NULL_ITEM; 35 | } 36 | 37 | public static SdesChunkItem createCnameItem(String cname) { 38 | return new SdesChunkItem(SdesChunkItem.Type.CNAME, cname); 39 | } 40 | 41 | public static SdesChunkItem createNameItem(String name) { 42 | return new SdesChunkItem(SdesChunkItem.Type.NAME, name); 43 | } 44 | 45 | public static SdesChunkItem createEmailItem(String email) { 46 | return new SdesChunkItem(SdesChunkItem.Type.EMAIL, email); 47 | } 48 | 49 | public static SdesChunkItem createPhoneItem(String phone) { 50 | return new SdesChunkItem(SdesChunkItem.Type.PHONE, phone); 51 | } 52 | 53 | public static SdesChunkItem createLocationItem(String location) { 54 | return new SdesChunkItem(SdesChunkItem.Type.LOCATION, location); 55 | } 56 | 57 | public static SdesChunkItem createToolItem(String tool) { 58 | return new SdesChunkItem(SdesChunkItem.Type.TOOL, tool); 59 | } 60 | 61 | public static SdesChunkItem createNoteItem(String note) { 62 | return new SdesChunkItem(SdesChunkItem.Type.NOTE, note); 63 | } 64 | 65 | public static SdesChunkPrivItem createPrivItem(String prefix, String value) { 66 | return new SdesChunkPrivItem(prefix, value); 67 | } 68 | 69 | public static SdesChunkItem decode(ChannelBuffer buffer) { 70 | SdesChunkItem.Type type = SdesChunkItem.Type.fromByte(buffer.readByte()); 71 | switch (type) { 72 | case NULL: 73 | return NULL_ITEM; 74 | case CNAME: 75 | case NAME: 76 | case EMAIL: 77 | case PHONE: 78 | case LOCATION: 79 | case TOOL: 80 | case NOTE: 81 | byte[] value = new byte[buffer.readUnsignedByte()]; 82 | buffer.readBytes(value); 83 | return new SdesChunkItem(type, new String(value, CharsetUtil.UTF_8)); 84 | case PRIV: 85 | short valueLength = buffer.readUnsignedByte(); 86 | short prefixLength = buffer.readUnsignedByte(); 87 | // Value length field indicates the length of all that follows this field: 88 | // Prefix length (1b), Prefix (xb) and Value itself (xb). Thus, the actual value length is equal to 89 | // the length indicated by this field - 1b (prefix length) and - xb (prefix value). 90 | value = new byte[valueLength - prefixLength - 1]; 91 | byte[] prefix = new byte[prefixLength]; 92 | buffer.readBytes(prefix); 93 | buffer.readBytes(value); 94 | return new SdesChunkPrivItem(new String(prefix, CharsetUtil.UTF_8), 95 | new String(value, CharsetUtil.UTF_8)); 96 | default: 97 | throw new IllegalArgumentException("Unknown type of SDES chunk: " + type); 98 | } 99 | } 100 | 101 | public static ChannelBuffer encode(SdesChunkItem item) { 102 | return item.encode(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SdesChunkPrivItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | import org.jboss.netty.util.CharsetUtil; 22 | 23 | /** 24 | * @author Bruno de Carvalho 25 | */ 26 | public class SdesChunkPrivItem extends SdesChunkItem { 27 | 28 | // internal vars -------------------------------------------------------------------------------------------------- 29 | 30 | private final String prefix; 31 | 32 | // constructors --------------------------------------------------------------------------------------------------- 33 | 34 | protected SdesChunkPrivItem(String prefix, String value) { 35 | super(SdesChunkItem.Type.PRIV, value); 36 | this.prefix = prefix; 37 | } 38 | 39 | // public methods ------------------------------------------------------------------------------------------------- 40 | 41 | @Override 42 | public ChannelBuffer encode() { 43 | byte[] prefixBytes; 44 | if (this.prefix != null) { 45 | // RFC section 6.5 mandates that this must be UTF8 46 | // http://tools.ietf.org/html/rfc3550#section-6.5 47 | prefixBytes = this.prefix.getBytes(CharsetUtil.UTF_8); 48 | } else { 49 | prefixBytes = new byte[]{}; 50 | } 51 | 52 | byte[] valueBytes; 53 | if (this.value != null) { 54 | // RFC section 6.5 mandates that this must be UTF8 55 | // http://tools.ietf.org/html/rfc3550#section-6.5 56 | valueBytes = this.value.getBytes(CharsetUtil.UTF_8); 57 | } else { 58 | valueBytes = new byte[]{}; 59 | } 60 | 61 | if ((prefixBytes.length + valueBytes.length) > 254) { 62 | throw new IllegalArgumentException("Content (prefix + text) can be no longer than 255 bytes and this has " + 63 | valueBytes.length); 64 | } 65 | 66 | // Type (1b), total item length (1b), prefix length (1b), prefix (xb), text (xb) 67 | ChannelBuffer buffer = ChannelBuffers.buffer(2 + 1 + prefixBytes.length + valueBytes.length); 68 | buffer.writeByte(this.type.getByte()); 69 | buffer.writeByte(1 + prefixBytes.length + valueBytes.length); 70 | buffer.writeByte(prefixBytes.length); 71 | buffer.writeBytes(prefixBytes); 72 | buffer.writeBytes(valueBytes); 73 | 74 | return buffer; 75 | } 76 | 77 | // getters & setters ---------------------------------------------------------------------------------------------- 78 | 79 | public String getPrefix() { 80 | return prefix; 81 | } 82 | 83 | // low level overrides -------------------------------------------------------------------------------------------- 84 | 85 | @Override 86 | public String toString() { 87 | return new StringBuilder() 88 | .append("SdesChunkPrivItem{") 89 | .append("prefix='").append(this.prefix).append('\'') 90 | .append(", value='").append(this.value).append('\'') 91 | .append('}').toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SenderReportPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class SenderReportPacket extends AbstractReportPacket { 26 | 27 | // internal vars -------------------------------------------------------------------------------------------------- 28 | 29 | // TODO this might not be a long... 30 | private long ntpTimestamp; 31 | private long rtpTimestamp; 32 | private long senderPacketCount; 33 | private long senderOctetCount; 34 | 35 | // constructors --------------------------------------------------------------------------------------------------- 36 | 37 | public SenderReportPacket() { 38 | super(Type.SENDER_REPORT); 39 | } 40 | 41 | // public static methods ------------------------------------------------------------------------------------------ 42 | 43 | public static SenderReportPacket decode(ChannelBuffer buffer, boolean hasPadding, byte innerBlocks, int length) { 44 | SenderReportPacket packet = new SenderReportPacket(); 45 | 46 | packet.setSenderSsrc(buffer.readUnsignedInt()); 47 | packet.setNtpTimestamp(buffer.readLong()); 48 | packet.setRtpTimestamp(buffer.readUnsignedInt()); 49 | packet.setSenderPacketCount(buffer.readUnsignedInt()); 50 | packet.setSenderOctetCount(buffer.readUnsignedInt()); 51 | 52 | int read = 24; 53 | for (int i = 0; i < innerBlocks; i++) { 54 | packet.addReceptionReportBlock(ReceptionReport.decode(buffer)); 55 | read += 24; // Each SR/RR block has 24 bytes (6 32bit words) 56 | } 57 | 58 | // Length is written in 32bit words, not octet count. 59 | int lengthInOctets = (length * 4); 60 | // (hasPadding == true) check is not done here. RFC respecting implementations will set the padding bit to 1 61 | // if length of packet is bigger than the necessary to convey the data; therefore it's a redundant check. 62 | if (read < lengthInOctets) { 63 | // Skip remaining bytes (used for padding). 64 | buffer.skipBytes(lengthInOctets - read); 65 | } 66 | 67 | return packet; 68 | } 69 | 70 | public static ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize, SenderReportPacket packet) { 71 | if ((currentCompoundLength < 0) || ((currentCompoundLength % 4) > 0)) { 72 | throw new IllegalArgumentException("Current compound length must be a non-negative multiple of 4"); 73 | } 74 | if ((fixedBlockSize < 0) || ((fixedBlockSize % 4) > 0)) { 75 | throw new IllegalArgumentException("Padding modulus must be a non-negative multiple of 4"); 76 | } 77 | 78 | // Common header + other fields (sender ssrc, ntp timestamp, rtp timestamp, packet count, octet count) 79 | int size = 4 + 24; 80 | ChannelBuffer buffer; 81 | if (packet.receptionReports != null) { 82 | size += packet.receptionReports.size() * 24; 83 | } 84 | 85 | // If packet was configured to have padding, calculate padding and add it. 86 | int padding = 0; 87 | if (fixedBlockSize > 0) { 88 | // If padding modulus is > 0 then the padding is equal to: 89 | // (global size of the compound RTCP packet) mod (block size) 90 | // Block size alignment might be necessary for some encryption algorithms 91 | // RFC section 6.4.1 92 | padding = fixedBlockSize - ((size + currentCompoundLength) % fixedBlockSize); 93 | if (padding == fixedBlockSize) { 94 | padding = 0; 95 | } 96 | } 97 | size += padding; 98 | 99 | // Allocate the buffer and write contents 100 | buffer = ChannelBuffers.buffer(size); 101 | // First byte: Version (2b), Padding (1b), RR count (5b) 102 | byte b = packet.getVersion().getByte(); 103 | if (padding > 0) { 104 | b |= 0x20; 105 | } 106 | b |= packet.getReceptionReportCount(); 107 | buffer.writeByte(b); 108 | // Second byte: Packet Type 109 | buffer.writeByte(packet.type.getByte()); 110 | // Third byte: total length of the packet, in multiples of 4 bytes (32bit words) - 1 111 | int sizeInOctets = (size / 4) - 1; 112 | buffer.writeShort(sizeInOctets); 113 | // Next 24 bytes: ssrc, ntp timestamp, rtp timestamp, octet count, packet count 114 | buffer.writeInt((int) packet.senderSsrc); 115 | buffer.writeLong(packet.ntpTimestamp); 116 | buffer.writeInt((int) packet.rtpTimestamp); 117 | buffer.writeInt((int) packet.senderPacketCount); 118 | buffer.writeInt((int) packet.senderOctetCount); 119 | // Payload: report blocks 120 | if (packet.getReceptionReportCount() > 0) { 121 | for (ReceptionReport block : packet.receptionReports) { 122 | buffer.writeBytes(block.encode()); 123 | } 124 | } 125 | 126 | if (padding > 0) { 127 | // Final bytes: padding 128 | for (int i = 0; i < (padding - 1); i++) { 129 | buffer.writeByte(0x00); 130 | } 131 | 132 | // Final byte: the amount of padding bytes that should be discarded. 133 | // Unless something's wrong, it will be a multiple of 4. 134 | buffer.writeByte(padding); 135 | } 136 | 137 | return buffer; 138 | } 139 | 140 | // ControlPacket -------------------------------------------------------------------------------------------------- 141 | 142 | @Override 143 | public ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize) { 144 | return encode(currentCompoundLength, fixedBlockSize, this); 145 | } 146 | 147 | @Override 148 | public ChannelBuffer encode() { 149 | return encode(0, 0, this); 150 | } 151 | 152 | // getters & setters ---------------------------------------------------------------------------------------------- 153 | 154 | public long getNtpTimestamp() { 155 | return ntpTimestamp; 156 | } 157 | 158 | public void setNtpTimestamp(long ntpTimestamp) { 159 | // TODO 160 | // if ((ntpTimestamp < 0) || (ntpTimestamp > 0xffffffffffffffffl)) { 161 | // throw new IllegalArgumentException("Valid range for NTP timestamp is [0;0xffffffffffffffff]"); 162 | // } 163 | this.ntpTimestamp = ntpTimestamp; 164 | } 165 | 166 | public long getRtpTimestamp() { 167 | return rtpTimestamp; 168 | } 169 | 170 | public void setRtpTimestamp(long rtpTimestamp) { 171 | if ((rtpTimestamp < 0) || (rtpTimestamp > 0xffffffffL)) { 172 | throw new IllegalArgumentException("Valid range for RTP timestamp is [0;0xffffffff]"); 173 | } 174 | this.rtpTimestamp = rtpTimestamp; 175 | } 176 | 177 | public long getSenderPacketCount() { 178 | return senderPacketCount; 179 | } 180 | 181 | public void setSenderPacketCount(long senderPacketCount) { 182 | if ((senderPacketCount < 0) || (senderPacketCount > 0xffffffffL)) { 183 | throw new IllegalArgumentException("Valid range for Sender Packet Count is [0;0xffffffff]"); 184 | } 185 | this.senderPacketCount = senderPacketCount; 186 | } 187 | 188 | public long getSenderOctetCount() { 189 | return senderOctetCount; 190 | } 191 | 192 | public void setSenderOctetCount(long senderOctetCount) { 193 | if ((senderOctetCount < 0) || (senderOctetCount > 0xffffffffL)) { 194 | throw new IllegalArgumentException("Valid range for Sender Octet Count is [0;0xffffffff]"); 195 | } 196 | this.senderOctetCount = senderOctetCount; 197 | } 198 | 199 | // low level overrides -------------------------------------------------------------------------------------------- 200 | 201 | @Override 202 | public String toString() { 203 | return new StringBuilder() 204 | .append("SenderReportPacket{") 205 | .append("senderSsrc=").append(this.senderSsrc) 206 | .append(", ntpTimestamp=").append(this.ntpTimestamp) 207 | .append(", rtpTimestamp=").append(this.rtpTimestamp) 208 | .append(", senderPacketCount=").append(this.senderPacketCount) 209 | .append(", senderOctetCount=").append(this.senderOctetCount) 210 | .append(", receptionReports=").append(this.receptionReports) 211 | .append('}').toString(); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/packet/SourceDescriptionPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.jboss.netty.buffer.ChannelBuffers; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class SourceDescriptionPacket extends ControlPacket { 30 | 31 | // internal vars -------------------------------------------------------------------------------------------------- 32 | 33 | private List chunks; 34 | 35 | // constructors --------------------------------------------------------------------------------------------------- 36 | 37 | public SourceDescriptionPacket() { 38 | super(Type.SOURCE_DESCRIPTION); 39 | } 40 | 41 | // public static methods ------------------------------------------------------------------------------------------ 42 | 43 | public static SourceDescriptionPacket decode(ChannelBuffer buffer, boolean hasPadding, byte innerBlocks, 44 | int length) { 45 | SourceDescriptionPacket packet = new SourceDescriptionPacket(); 46 | int readable = buffer.readableBytes(); 47 | for (int i = 0; i < innerBlocks; i++) { 48 | packet.addItem(SdesChunk.decode(buffer)); 49 | } 50 | 51 | if (hasPadding) { 52 | // Number of 32bit words read. 53 | int read = (readable - buffer.readableBytes()) / 4; 54 | 55 | // Rest is padding 32bit words, so skip it. 56 | buffer.skipBytes((length - read) * 4); 57 | } 58 | 59 | return packet; 60 | } 61 | 62 | public static ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize, SourceDescriptionPacket packet) { 63 | if ((currentCompoundLength < 0) || ((currentCompoundLength % 4) > 0)) { 64 | throw new IllegalArgumentException("Current compound length must be a non-negative multiple of 4"); 65 | } 66 | if ((fixedBlockSize < 0) || ((fixedBlockSize % 4) > 0)) { 67 | throw new IllegalArgumentException("Padding modulus must be a non-negative multiple of 4"); 68 | } 69 | 70 | int size = 4; 71 | ChannelBuffer buffer; 72 | List encodedChunks = null; 73 | if (packet.chunks != null) { 74 | encodedChunks = new ArrayList(packet.chunks.size()); 75 | for (SdesChunk chunk : packet.chunks) { 76 | ChannelBuffer encodedChunk = chunk.encode(); 77 | encodedChunks.add(encodedChunk); 78 | size += encodedChunk.readableBytes(); 79 | } 80 | } 81 | 82 | // If packet was configured to have padding, calculate padding and add it. 83 | int padding = 0; 84 | if (fixedBlockSize > 0) { 85 | // If padding modulus is > 0 then the padding is equal to: 86 | // (global size of the compound RTCP packet) mod (block size) 87 | // Block size alignment might be necessary for some encryption algorithms 88 | // RFC section 6.4.1 89 | padding = fixedBlockSize - ((size + currentCompoundLength) % fixedBlockSize); 90 | if (padding == fixedBlockSize) { 91 | padding = 0; 92 | } 93 | } 94 | size += padding; 95 | 96 | // Allocate the buffer and write contents 97 | buffer = ChannelBuffers.buffer(size); 98 | // First byte: Version (2b), Padding (1b), SSRC (chunks) count (5b) 99 | byte b = packet.getVersion().getByte(); 100 | if (padding > 0) { 101 | b |= 0x20; 102 | } 103 | if (packet.chunks != null) { 104 | b |= packet.chunks.size(); 105 | } 106 | buffer.writeByte(b); 107 | // Second byte: Packet Type 108 | buffer.writeByte(packet.type.getByte()); 109 | // Third byte: total length of the packet, in multiples of 4 bytes (32bit words) - 1 110 | int sizeInOctets = (size / 4) - 1; 111 | buffer.writeShort(sizeInOctets); 112 | // Remaining bytes: encoded chunks 113 | if (encodedChunks != null) { 114 | for (ChannelBuffer encodedChunk : encodedChunks) { 115 | buffer.writeBytes(encodedChunk); 116 | } 117 | } 118 | 119 | if (padding > 0) { 120 | // Final bytes: padding 121 | for (int i = 0; i < (padding - 1); i++) { 122 | buffer.writeByte(0x00); 123 | } 124 | 125 | // Final byte: the amount of padding bytes that should be discarded. 126 | // Unless something's wrong, it will be a multiple of 4. 127 | buffer.writeByte(padding); 128 | } 129 | 130 | return buffer; 131 | } 132 | 133 | // ControlPacket -------------------------------------------------------------------------------------------------- 134 | 135 | @Override 136 | public ChannelBuffer encode(int currentCompoundLength, int fixedBlockSize) { 137 | return encode(currentCompoundLength, fixedBlockSize, this); 138 | } 139 | 140 | @Override 141 | public ChannelBuffer encode() { 142 | return encode(0, 0, this); 143 | } 144 | 145 | // public methods ------------------------------------------------------------------------------------------------- 146 | 147 | public boolean addItem(SdesChunk chunk) { 148 | if (this.chunks == null) { 149 | this.chunks = new ArrayList(); 150 | return this.chunks.add(chunk); 151 | } 152 | 153 | // 5 bits (31) is the limit of chunks 154 | return (this.chunks.size() < 31) && this.chunks.add(chunk); 155 | } 156 | 157 | // getters & setters ---------------------------------------------------------------------------------------------- 158 | 159 | public List getChunks() { 160 | if (this.chunks == null) { 161 | return null; 162 | } 163 | return Collections.unmodifiableList(this.chunks); 164 | } 165 | 166 | public void setChunks(List chunks) { 167 | if (chunks.size() >= 31) { 168 | throw new IllegalArgumentException("At most 31 SSRC/CSRC chunks can be sent in a SourceDescriptionPacket"); 169 | } 170 | this.chunks = chunks; 171 | } 172 | 173 | // low level overrides -------------------------------------------------------------------------------------------- 174 | 175 | @Override 176 | public String toString() { 177 | return new StringBuilder() 178 | .append("SourceDescriptionPacket{") 179 | .append("chunks=").append(this.chunks) 180 | .append('}').toString(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/ParticipantDatabase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import com.biasedbit.efflux.packet.SdesChunk; 21 | 22 | import java.net.SocketAddress; 23 | import java.util.Collection; 24 | import java.util.Map; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public interface ParticipantDatabase { 30 | 31 | String getId(); 32 | 33 | Collection getReceivers(); 34 | 35 | Map getMembers(); 36 | 37 | void doWithReceivers(ParticipantOperation operation); 38 | 39 | void doWithParticipants(ParticipantOperation operation); 40 | 41 | boolean addReceiver(RtpParticipant remoteParticipant); 42 | 43 | boolean removeReceiver(RtpParticipant remoteParticipant); 44 | 45 | RtpParticipant getParticipant(long ssrc); 46 | 47 | RtpParticipant getOrCreateParticipantFromDataPacket(SocketAddress origin, DataPacket packet); 48 | 49 | RtpParticipant getOrCreateParticipantFromSdesChunk(SocketAddress origin, SdesChunk chunk); 50 | 51 | int getReceiverCount(); 52 | 53 | int getParticipantCount(); 54 | 55 | void cleanup(); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/ParticipantEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | /** 20 | * @author Bruno de Carvalho 21 | */ 22 | public interface ParticipantEventListener { 23 | 24 | void participantCreatedFromSdesChunk(RtpParticipant participant); 25 | 26 | void participantCreatedFromDataPacket(RtpParticipant participant); 27 | 28 | void participantDeleted(RtpParticipant participant); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/ParticipantOperation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | /** 20 | * @author Bruno de Carvalho 21 | */ 22 | public interface ParticipantOperation { 23 | 24 | void doWithParticipant(RtpParticipant participant) throws Exception; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/RtpParticipant.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import com.biasedbit.efflux.packet.SdesChunk; 21 | import com.biasedbit.efflux.util.TimeUtils; 22 | 23 | import java.net.InetSocketAddress; 24 | import java.net.SocketAddress; 25 | import java.util.Collection; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | import java.util.concurrent.atomic.AtomicLong; 28 | 29 | /** 30 | * @author Bruno de Carvalho 31 | */ 32 | public class RtpParticipant { 33 | 34 | // constants ------------------------------------------------------------------------------------------------------ 35 | 36 | private static final int VALID_PACKETS_UNTIL_VALID_PARTICIPANT = 3; 37 | 38 | // configuration -------------------------------------------------------------------------------------------------- 39 | 40 | private final RtpParticipantInfo info; 41 | 42 | // internal vars -------------------------------------------------------------------------------------------------- 43 | 44 | private SocketAddress dataDestination; 45 | private SocketAddress controlDestination; 46 | private SocketAddress lastDataOrigin; 47 | private SocketAddress lastControlOrigin; 48 | private long lastReceptionInstant; 49 | private long byeReceptionInstant; 50 | private int lastSequenceNumber; 51 | private boolean receivedSdes; 52 | private final AtomicLong receivedByteCounter; 53 | private final AtomicLong receivedPacketCounter; 54 | private final AtomicInteger validPacketCounter; 55 | 56 | // constructors --------------------------------------------------------------------------------------------------- 57 | 58 | private RtpParticipant(RtpParticipantInfo info) { 59 | // For internal use only. 60 | this.info = info; 61 | 62 | this.lastSequenceNumber = -1; 63 | this.lastReceptionInstant = 0; 64 | this.byeReceptionInstant = 0; 65 | 66 | this.receivedByteCounter = new AtomicLong(); 67 | this.receivedPacketCounter = new AtomicLong(); 68 | this.validPacketCounter = new AtomicInteger(); 69 | } 70 | 71 | // public static methods ------------------------------------------------------------------------------------------ 72 | 73 | public static RtpParticipant createReceiver(String host, int dataPort, int controlPort) { 74 | RtpParticipant participant = new RtpParticipant(new RtpParticipantInfo()); 75 | 76 | if ((dataPort < 0) || (dataPort > 65536)) { 77 | throw new IllegalArgumentException("Invalid port number; use range [0;65536]"); 78 | } 79 | if ((controlPort < 0) || (controlPort > 65536)) { 80 | throw new IllegalArgumentException("Invalid port number; use range [0;65536]"); 81 | } 82 | 83 | participant.dataDestination = new InetSocketAddress(host, dataPort); 84 | participant.controlDestination = new InetSocketAddress(host, controlPort); 85 | 86 | return participant; 87 | } 88 | 89 | public static RtpParticipant createReceiver(RtpParticipantInfo info, String host, int dataPort, int controlPort) { 90 | RtpParticipant participant = new RtpParticipant(info); 91 | 92 | if ((dataPort < 0) || (dataPort > 65536)) { 93 | throw new IllegalArgumentException("Invalid port number; use range [0;65536]"); 94 | } 95 | if ((controlPort < 0) || (controlPort > 65536)) { 96 | throw new IllegalArgumentException("Invalid port number; use range [0;65536]"); 97 | } 98 | 99 | participant.dataDestination = new InetSocketAddress(host, dataPort); 100 | participant.controlDestination = new InetSocketAddress(host, controlPort); 101 | 102 | return participant; 103 | } 104 | 105 | public static RtpParticipant createFromUnexpectedDataPacket(SocketAddress origin, DataPacket packet) { 106 | RtpParticipant participant = new RtpParticipant(new RtpParticipantInfo()); 107 | participant.lastDataOrigin = origin; 108 | participant.getInfo().setSsrc(packet.getSsrc()); 109 | 110 | return participant; 111 | } 112 | 113 | public static RtpParticipant createFromSdesChunk(SocketAddress origin, SdesChunk chunk) { 114 | RtpParticipant participant = new RtpParticipant(new RtpParticipantInfo()); 115 | participant.lastControlOrigin = origin; 116 | participant.getInfo().updateFromSdesChunk(chunk); 117 | participant.receivedSdes(); 118 | 119 | return participant; 120 | } 121 | 122 | // public methods ------------------------------------------------------------------------------------------------- 123 | 124 | public long resolveSsrcConflict(long ssrcToAvoid) { 125 | // Will hardly ever loop more than once... 126 | while (this.getSsrc() == ssrcToAvoid) { 127 | this.getInfo().setSsrc(RtpParticipantInfo.generateNewSsrc()); 128 | } 129 | 130 | return this.getSsrc(); 131 | } 132 | 133 | public long resolveSsrcConflict(Collection ssrcsToAvoid) { 134 | // Probability to execute more than once is higher than the other method that takes just a long as parameter, 135 | // but its still incredibly low: for 1000 participants, there's roughly 2*10^-7 chance of collision 136 | while (ssrcsToAvoid.contains(this.getSsrc())) { 137 | this.getInfo().setSsrc(RtpParticipantInfo.generateNewSsrc()); 138 | } 139 | 140 | return this.getSsrc(); 141 | } 142 | 143 | 144 | public void byeReceived() { 145 | this.byeReceptionInstant = TimeUtils.now(); 146 | } 147 | 148 | public void receivedSdes() { 149 | this.receivedSdes = true; 150 | } 151 | 152 | public void packetReceived() { 153 | this.lastReceptionInstant = TimeUtils.now(); 154 | } 155 | 156 | public boolean isReceiver() { 157 | return (this.dataDestination != null) && (this.controlDestination != null); 158 | } 159 | 160 | // getters & setters ---------------------------------------------------------------------------------------------- 161 | 162 | public long getSsrc() { 163 | return this.getInfo().getSsrc(); 164 | } 165 | 166 | public RtpParticipantInfo getInfo() { 167 | return info; 168 | } 169 | 170 | public long getLastReceptionInstant() { 171 | return lastReceptionInstant; 172 | } 173 | 174 | public long getByeReceptionInstant() { 175 | return byeReceptionInstant; 176 | } 177 | 178 | public int getLastSequenceNumber() { 179 | return lastSequenceNumber; 180 | } 181 | 182 | public void setLastSequenceNumber(int lastSequenceNumber) { 183 | this.lastSequenceNumber = lastSequenceNumber; 184 | } 185 | 186 | public boolean receivedBye() { 187 | return this.byeReceptionInstant > 0; 188 | } 189 | 190 | public long getReceivedPackets() { 191 | return this.receivedPacketCounter.get(); 192 | } 193 | 194 | public long getReceivedBytes() { 195 | return this.receivedByteCounter.get(); 196 | } 197 | 198 | public boolean hasReceivedSdes() { 199 | return receivedSdes; 200 | } 201 | 202 | public SocketAddress getDataDestination() { 203 | return dataDestination; 204 | } 205 | 206 | public void setDataDestination(SocketAddress dataDestination) { 207 | if (dataDestination == null) { 208 | throw new IllegalArgumentException("Argument cannot be null"); 209 | } 210 | this.dataDestination = dataDestination; 211 | } 212 | 213 | public SocketAddress getControlDestination() { 214 | return controlDestination; 215 | } 216 | 217 | public void setControlDestination(SocketAddress controlDestination) { 218 | if (dataDestination == null) { 219 | throw new IllegalArgumentException("Argument cannot be null"); 220 | } 221 | this.controlDestination = controlDestination; 222 | } 223 | 224 | public SocketAddress getLastDataOrigin() { 225 | return lastDataOrigin; 226 | } 227 | 228 | public void setLastDataOrigin(SocketAddress lastDataOrigin) { 229 | this.lastDataOrigin = lastDataOrigin; 230 | } 231 | 232 | public SocketAddress getLastControlOrigin() { 233 | return lastControlOrigin; 234 | } 235 | 236 | public void setLastControlOrigin(SocketAddress lastControlOrigin) { 237 | this.lastControlOrigin = lastControlOrigin; 238 | } 239 | 240 | // low level overrides -------------------------------------------------------------------------------------------- 241 | 242 | @Override 243 | public boolean equals(Object o) { 244 | if (this == o) { 245 | return true; 246 | } 247 | if (!(o instanceof RtpParticipant)) { 248 | return false; 249 | } 250 | 251 | RtpParticipant that = (RtpParticipant) o; 252 | return this.controlDestination.equals(that.controlDestination) && 253 | this.dataDestination.equals(that.dataDestination) && 254 | this.info.getCname().equals(that.info.getCname()); 255 | } 256 | 257 | @Override 258 | public int hashCode() { 259 | int result = dataDestination.hashCode(); 260 | result = 31 * result + controlDestination.hashCode(); 261 | return result; 262 | } 263 | 264 | @Override 265 | public String toString() { 266 | return this.getInfo().toString(); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/RtpParticipantInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | import com.biasedbit.efflux.packet.SdesChunk; 20 | import com.biasedbit.efflux.packet.SdesChunkItem; 21 | import com.biasedbit.efflux.packet.SdesChunkPrivItem; 22 | 23 | import java.util.Random; 24 | 25 | /** 26 | * @author Bruno de Carvalho 27 | */ 28 | public class RtpParticipantInfo { 29 | 30 | // constants ------------------------------------------------------------------------------------------------------ 31 | 32 | private static final Random RANDOM = new Random(System.nanoTime()); 33 | 34 | // internal vars -------------------------------------------------------------------------------------------------- 35 | 36 | private long ssrc; 37 | private String name; 38 | private String cname; 39 | private String email; 40 | private String phone; 41 | private String location; 42 | private String tool; 43 | private String note; 44 | private String privPrefix; 45 | private String priv; 46 | 47 | // constructors --------------------------------------------------------------------------------------------------- 48 | 49 | public RtpParticipantInfo(long ssrc) { 50 | if ((ssrc < 0) || (ssrc > 0xffffffffL)) { 51 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 52 | } 53 | 54 | this.ssrc = ssrc; 55 | } 56 | 57 | public RtpParticipantInfo() { 58 | this(generateNewSsrc()); 59 | } 60 | 61 | /** 62 | * Randomly generates a new SSRC. 63 | *

64 | * Assuming no other source can obtain the exact same seed (or they're using a different algorithm for the random 65 | * generation) the probability of collision is roughly 10^-4 when the number of RTP sources is 1000. 66 | * RFC 3550, Section 8.1 67 | *

68 | * In this case, collision odds are slightly bigger because the identifier size will be 31 bits (0x7fffffff, 69 | * {@link Integer#MAX_VALUE} rather than the full 32 bits. 70 | * 71 | * @return A new, random, SSRC identifier. 72 | */ 73 | public static long generateNewSsrc() { 74 | return RANDOM.nextInt(Integer.MAX_VALUE); 75 | } 76 | 77 | // public methods ------------------------------------------------------------------------------------------------- 78 | 79 | public boolean updateFromSdesChunk(SdesChunk chunk) { 80 | boolean modified = false; 81 | if (this.ssrc != chunk.getSsrc()) { 82 | this.ssrc = chunk.getSsrc(); 83 | modified = true; 84 | } 85 | if (chunk.getItems() == null) { 86 | return modified; 87 | } 88 | 89 | for (SdesChunkItem item : chunk.getItems()) { 90 | switch (item.getType()) { 91 | case CNAME: 92 | if (this.willCauseModification(this.cname, item.getValue())) { 93 | this.setCname(item.getValue()); 94 | modified = true; 95 | } 96 | break; 97 | case NAME: 98 | if (this.willCauseModification(this.name, item.getValue())) { 99 | this.setName(item.getValue()); 100 | modified = true; 101 | } 102 | break; 103 | case EMAIL: 104 | if (this.willCauseModification(this.email, item.getValue())) { 105 | this.setEmail(item.getValue()); 106 | modified = true; 107 | } 108 | break; 109 | case PHONE: 110 | if (this.willCauseModification(this.phone, item.getValue())) { 111 | this.setPhone(item.getValue()); 112 | modified = true; 113 | } 114 | break; 115 | case LOCATION: 116 | if (this.willCauseModification(this.location, item.getValue())) { 117 | this.setLocation(item.getValue()); 118 | modified = true; 119 | } 120 | break; 121 | case TOOL: 122 | if (this.willCauseModification(this.tool, item.getValue())) { 123 | this.setTool(item.getValue()); 124 | modified = true; 125 | } 126 | break; 127 | case NOTE: 128 | if (this.willCauseModification(this.note, item.getValue())) { 129 | this.setNote(item.getValue()); 130 | modified = true; 131 | } 132 | break; 133 | case PRIV: 134 | String prefix = ((SdesChunkPrivItem) item).getPrefix(); 135 | if (this.willCauseModification(this.privPrefix, prefix) || 136 | this.willCauseModification(this.priv, item.getValue())) { 137 | this.setPriv(prefix, item.getValue()); 138 | modified = true; 139 | } 140 | break; 141 | default: 142 | // Never falls here... 143 | } 144 | } 145 | 146 | return modified; 147 | } 148 | 149 | // private helpers ------------------------------------------------------------------------------------------------ 150 | 151 | private boolean willCauseModification(String originalValue, String newValue) { 152 | return newValue != null && !newValue.equals(originalValue); 153 | } 154 | 155 | // getters & setters ---------------------------------------------------------------------------------------------- 156 | 157 | public long getSsrc() { 158 | return this.ssrc; 159 | } 160 | 161 | /** 162 | * USE THIS WITH EXTREME CAUTION at the risk of seriously screwing up the way sessions handle data from incoming 163 | * participants. 164 | * 165 | * @param ssrc The new SSRC. 166 | */ 167 | public void setSsrc(long ssrc) { 168 | if ((ssrc < 0) || (ssrc > 0xffffffffL)) { 169 | throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); 170 | } 171 | 172 | this.ssrc = ssrc; 173 | } 174 | 175 | public String getCname() { 176 | return this.cname; 177 | } 178 | 179 | public void setCname(String cname) { 180 | this.cname = cname; 181 | } 182 | 183 | public String getName() { 184 | return this.name; 185 | } 186 | 187 | public void setName(String name) { 188 | this.name = name; 189 | } 190 | 191 | public String getEmail() { 192 | return this.email; 193 | } 194 | 195 | public void setEmail(String email) { 196 | this.email = email; 197 | } 198 | 199 | public String getPhone() { 200 | return this.phone; 201 | } 202 | 203 | public void setPhone(String phone) { 204 | this.phone = phone; 205 | } 206 | 207 | public String getLocation() { 208 | return this.location; 209 | } 210 | 211 | public void setLocation(String location) { 212 | this.location = location; 213 | } 214 | 215 | public String getTool() { 216 | return this.tool; 217 | } 218 | 219 | public void setTool(String tool) { 220 | this.tool = tool; 221 | } 222 | 223 | public String getNote() { 224 | return this.note; 225 | } 226 | 227 | public void setNote(String note) { 228 | this.note = note; 229 | } 230 | 231 | public String getPrivPrefix() { 232 | return this.privPrefix; 233 | } 234 | 235 | public String getPriv() { 236 | return this.priv; 237 | } 238 | 239 | public void setPriv(String prefix, String priv) { 240 | this.privPrefix = prefix; 241 | this.priv = priv; 242 | } 243 | 244 | // low level overrides -------------------------------------------------------------------------------------------- 245 | 246 | @Override 247 | public String toString() { 248 | StringBuilder builder = new StringBuilder() 249 | .append("RtpParticipantInfo{") 250 | .append("ssrc=").append(this.ssrc); 251 | 252 | if (this.cname != null) { 253 | builder.append(", cname='").append(this.cname).append('\''); 254 | } 255 | 256 | if (this.name != null) { 257 | builder.append(", name='").append(this.name).append('\''); 258 | } 259 | 260 | if (this.email != null) { 261 | builder.append(", email='").append(this.email).append('\''); 262 | } 263 | 264 | if (this.phone != null) { 265 | builder.append(", phone='").append(this.phone).append('\''); 266 | } 267 | 268 | if (this.location != null) { 269 | builder.append(", location='").append(this.location).append('\''); 270 | } 271 | 272 | if (this.tool != null) { 273 | builder.append(", tool='").append(this.tool).append('\''); 274 | } 275 | 276 | if (this.note != null) { 277 | builder.append(", note='").append(this.note).append('\''); 278 | } 279 | 280 | if (this.priv != null) { 281 | builder.append(", priv='").append(this.privPrefix).append(':').append(this.priv).append('\''); 282 | } 283 | 284 | return builder.append('}').toString(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/participant/SingleParticipantDatabase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.participant; 18 | 19 | import com.biasedbit.efflux.logging.Logger; 20 | import com.biasedbit.efflux.packet.DataPacket; 21 | import com.biasedbit.efflux.packet.SdesChunk; 22 | 23 | import java.net.SocketAddress; 24 | import java.util.Arrays; 25 | import java.util.Collection; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | /** 30 | * @author Bruno de Carvalho 31 | */ 32 | public class SingleParticipantDatabase implements ParticipantDatabase { 33 | 34 | // constants ------------------------------------------------------------------------------------------------------ 35 | 36 | private static final Logger LOG = Logger.getLogger(SingleParticipantDatabase.class); 37 | 38 | // configuration -------------------------------------------------------------------------------------------------- 39 | 40 | private String id; 41 | private RtpParticipant participant; 42 | 43 | // constructors --------------------------------------------------------------------------------------------------- 44 | 45 | public SingleParticipantDatabase(String id) { 46 | this.id = id; 47 | } 48 | 49 | // ParticipantDatabase -------------------------------------------------------------------------------------------- 50 | 51 | @Override 52 | public String getId() { 53 | return this.id; 54 | } 55 | 56 | @Override 57 | public Collection getReceivers() { 58 | return Arrays.asList(this.participant); 59 | } 60 | 61 | @Override 62 | public Map getMembers() { 63 | // Could be optimised, but then again this'll be used so little... 64 | Map map = new HashMap(1); 65 | map.put(this.participant.getSsrc(), this.participant); 66 | return map; 67 | } 68 | 69 | @Override 70 | public void doWithReceivers(ParticipantOperation operation) { 71 | try { 72 | operation.doWithParticipant(this.participant); 73 | } catch (Exception e) { 74 | LOG.error("Failed to perform operation {} on remote participant {}.", e, operation, this.participant); 75 | } 76 | } 77 | 78 | @Override 79 | public void doWithParticipants(ParticipantOperation operation) { 80 | try { 81 | operation.doWithParticipant(this.participant); 82 | } catch (Exception e) { 83 | LOG.error("Failed to perform operation {} on remote participant {}.", e, operation, this.participant); 84 | } 85 | } 86 | 87 | @Override 88 | public boolean addReceiver(RtpParticipant remoteParticipant) { 89 | return remoteParticipant == this.participant; 90 | } 91 | 92 | @Override 93 | public boolean removeReceiver(RtpParticipant remoteParticipant) { 94 | return false; 95 | } 96 | 97 | @Override 98 | public RtpParticipant getParticipant(long ssrc) { 99 | if (ssrc == this.participant.getSsrc()) { 100 | return this.participant; 101 | } 102 | 103 | return null; 104 | } 105 | 106 | @Override 107 | public RtpParticipant getOrCreateParticipantFromDataPacket(SocketAddress origin, DataPacket packet) { 108 | if (packet.getSsrc() == this.participant.getSsrc()) { 109 | return this.participant; 110 | } 111 | 112 | return null; 113 | } 114 | 115 | @Override 116 | public RtpParticipant getOrCreateParticipantFromSdesChunk(SocketAddress origin, SdesChunk chunk) { 117 | if (chunk.getSsrc() == this.participant.getSsrc()) { 118 | return this.participant; 119 | } 120 | 121 | return null; 122 | } 123 | 124 | @Override 125 | public int getReceiverCount() { 126 | return 1; 127 | } 128 | 129 | @Override 130 | public int getParticipantCount() { 131 | return 1; 132 | } 133 | 134 | @Override 135 | public void cleanup() { 136 | // Nothing to do here. 137 | } 138 | 139 | // getters & setters ---------------------------------------------------------------------------------------------- 140 | 141 | public void setParticipant(RtpParticipant remoteParticipant) { 142 | this.participant = remoteParticipant; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/session/MultiParticipantSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.participant.DefaultParticipantDatabase; 20 | import com.biasedbit.efflux.participant.ParticipantDatabase; 21 | import com.biasedbit.efflux.participant.ParticipantEventListener; 22 | import com.biasedbit.efflux.participant.RtpParticipant; 23 | import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor; 24 | import org.jboss.netty.util.HashedWheelTimer; 25 | 26 | /** 27 | * A regular RTP session, as described in RFC3550. 28 | * 29 | * Unlike {@link SingleParticipantSession}, this session starts off with 0 remote participants. 30 | * 31 | * @author Bruno de Carvalho 32 | */ 33 | public class MultiParticipantSession extends AbstractRtpSession implements ParticipantEventListener { 34 | 35 | // constructors --------------------------------------------------------------------------------------------------- 36 | 37 | public MultiParticipantSession(String id, int payloadType, RtpParticipant localParticipant) { 38 | super(id, payloadType, localParticipant, null, null); 39 | } 40 | 41 | public MultiParticipantSession(String id, int payloadType, RtpParticipant localParticipant, 42 | HashedWheelTimer timer) { 43 | super(id, payloadType, localParticipant, timer, null); 44 | } 45 | 46 | public MultiParticipantSession(String id, int payloadType, RtpParticipant localParticipant, 47 | OrderedMemoryAwareThreadPoolExecutor executor) { 48 | super(id, payloadType, localParticipant, null, executor); 49 | } 50 | 51 | public MultiParticipantSession(String id, int payloadType, RtpParticipant localParticipant, 52 | HashedWheelTimer timer, OrderedMemoryAwareThreadPoolExecutor executor) { 53 | super(id, payloadType, localParticipant, timer, executor); 54 | } 55 | 56 | // AbstractRtpSession --------------------------------------------------------------------------------------------- 57 | 58 | @Override 59 | protected ParticipantDatabase createDatabase() { 60 | return new DefaultParticipantDatabase(this.id, this); 61 | } 62 | 63 | // ParticipantEventListener --------------------------------------------------------------------------------------- 64 | 65 | @Override 66 | public void participantCreatedFromSdesChunk(RtpParticipant participant) { 67 | for (RtpSessionEventListener listener : this.eventListeners) { 68 | listener.participantJoinedFromControl(this, participant); 69 | } 70 | } 71 | 72 | @Override 73 | public void participantCreatedFromDataPacket(RtpParticipant participant) { 74 | for (RtpSessionEventListener listener : this.eventListeners) { 75 | listener.participantJoinedFromData(this, participant); 76 | } 77 | } 78 | 79 | @Override 80 | public void participantDeleted(RtpParticipant participant) { 81 | for (RtpSessionEventListener listener : this.eventListeners) { 82 | listener.participantDeleted(this, participant); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/session/RtpSession.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.network.ControlPacketReceiver; 20 | import com.biasedbit.efflux.network.DataPacketReceiver; 21 | import com.biasedbit.efflux.packet.CompoundControlPacket; 22 | import com.biasedbit.efflux.packet.ControlPacket; 23 | import com.biasedbit.efflux.packet.DataPacket; 24 | import com.biasedbit.efflux.participant.RtpParticipant; 25 | 26 | import java.util.Map; 27 | 28 | /** 29 | * @author Bruno de Carvalho 30 | */ 31 | public interface RtpSession extends DataPacketReceiver, ControlPacketReceiver { 32 | 33 | String getId(); 34 | 35 | int getPayloadType(); 36 | 37 | boolean init(); 38 | 39 | void terminate(); 40 | 41 | boolean sendData(byte[] data, long timestamp, boolean marked); 42 | 43 | boolean sendDataPacket(DataPacket packet); 44 | 45 | boolean sendControlPacket(ControlPacket packet); 46 | 47 | boolean sendControlPacket(CompoundControlPacket packet); 48 | 49 | RtpParticipant getLocalParticipant(); 50 | 51 | boolean addReceiver(RtpParticipant remoteParticipant); 52 | 53 | boolean removeReceiver(RtpParticipant remoteParticipant); 54 | 55 | RtpParticipant getRemoteParticipant(long ssrsc); 56 | 57 | Map getRemoteParticipants(); 58 | 59 | void addDataListener(RtpSessionDataListener listener); 60 | 61 | void removeDataListener(RtpSessionDataListener listener); 62 | 63 | void addControlListener(RtpSessionControlListener listener); 64 | 65 | void removeControlListener(RtpSessionControlListener listener); 66 | 67 | void addEventListener(RtpSessionEventListener listener); 68 | 69 | void removeEventListener(RtpSessionEventListener listener); 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/session/RtpSessionControlListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.packet.AppDataPacket; 20 | import com.biasedbit.efflux.packet.CompoundControlPacket; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public interface RtpSessionControlListener { 26 | 27 | void controlPacketReceived(RtpSession session, CompoundControlPacket packet); 28 | 29 | void appDataReceived(RtpSession session, AppDataPacket appDataPacket); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/session/RtpSessionDataListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import com.biasedbit.efflux.participant.RtpParticipantInfo; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public interface RtpSessionDataListener { 26 | 27 | void dataPacketReceived(RtpSession session, RtpParticipantInfo participant, DataPacket packet); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/session/RtpSessionEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.participant.RtpParticipant; 20 | 21 | /** 22 | * @author Bruno de Carvalho 23 | */ 24 | public interface RtpSessionEventListener { 25 | 26 | static final Throwable TERMINATE_CALLED = new Throwable("RtpSession.terminate() called"); 27 | 28 | void participantJoinedFromData(RtpSession session, RtpParticipant participant); 29 | 30 | void participantJoinedFromControl(RtpSession session, RtpParticipant participant); 31 | 32 | void participantDataUpdated(RtpSession session, RtpParticipant participant); 33 | 34 | void participantLeft(RtpSession session, RtpParticipant participant); 35 | 36 | void participantDeleted(RtpSession session, RtpParticipant participant); 37 | 38 | void resolvedSsrcConflict(RtpSession session, long oldSsrc, long newSsrc); 39 | 40 | void sessionTerminated(RtpSession session, Throwable cause); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/util/ByteUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.util; 18 | 19 | import java.security.MessageDigest; 20 | import java.security.NoSuchAlgorithmException; 21 | 22 | /** 23 | * @author Bruno de Carvalho 24 | */ 25 | public class ByteUtils { 26 | 27 | // constructors --------------------------------------------------------------------------------------------------- 28 | 29 | private ByteUtils() { 30 | } 31 | 32 | // public static methods ------------------------------------------------------------------------------------------ 33 | 34 | /** 35 | * Hash a string 36 | * 37 | * @param toHash String to be hashed. 38 | * 39 | * @return Hashed string. 40 | */ 41 | public static String hash(Object toHash) { 42 | String hashString = toHash.toString(); 43 | MessageDigest md; 44 | try { 45 | md = MessageDigest.getInstance("MD5"); 46 | } catch (NoSuchAlgorithmException e) { 47 | return hashString; 48 | } 49 | 50 | md.update(hashString.getBytes(), 0, hashString.length()); 51 | return convertToHex(md.digest()); 52 | } 53 | 54 | public static String convertToHex(byte[] data) { 55 | StringBuffer buf = new StringBuffer(); 56 | for (byte aData : data) { 57 | int halfbyte = (aData >>> 4) & 0x0F; 58 | int two_halfs = 0; 59 | do { 60 | if ((0 <= halfbyte) && (halfbyte <= 9)) { 61 | buf.append((char) ('0' + halfbyte)); 62 | } else { 63 | buf.append((char) ('a' + (halfbyte - 10))); 64 | } 65 | halfbyte = aData & 0x0F; 66 | } while (two_halfs++ < 1); 67 | } 68 | return buf.toString(); 69 | } 70 | 71 | public static String writeArrayAsHex(byte[] array, boolean packedPrint) { 72 | if (packedPrint) { 73 | return convertToHex(array); 74 | } 75 | 76 | StringBuilder builder = new StringBuilder(); 77 | for (byte b : array) { 78 | builder.append(" 0x"); 79 | String hex = Integer.toHexString(b); 80 | switch (hex.length()) { 81 | case 1: 82 | builder.append('0').append(hex); 83 | break; 84 | case 2: 85 | builder.append(hex); 86 | break; 87 | default: 88 | builder.append(hex.substring(6, 8)); 89 | } 90 | } 91 | 92 | return builder.toString(); 93 | } 94 | 95 | public static byte[] convertHexStringToByteArray(String hexString) { 96 | if ((hexString.length() % 2) != 0) { 97 | throw new IllegalArgumentException("Invalid hex string (length % 2 != 0)"); 98 | } 99 | 100 | byte[] array = new byte[hexString.length() / 2]; 101 | for (int i = 0, arrayIndex = 0; i < hexString.length(); i += 2, arrayIndex++) { 102 | array[arrayIndex] = Integer.valueOf(hexString.substring(i, i + 2), 16).byteValue(); 103 | } 104 | 105 | return array; 106 | } 107 | 108 | /** 109 | * Get a byte array in a printable binary form. 110 | * 111 | * @param bytes The bytes to be writen. 112 | * @return A String representation of the bytes. 113 | */ 114 | public static String writeBits(byte[] bytes) { 115 | StringBuilder stringBuilder = new StringBuilder(); 116 | for (int i = 0; i < bytes.length; i++) { 117 | // New line every 4 bytes 118 | if ((i % 4) == 0) { 119 | stringBuilder.append("\n"); 120 | } 121 | stringBuilder.append(writeBits(bytes[i])).append(" "); 122 | } 123 | return stringBuilder.toString(); 124 | } 125 | 126 | /** 127 | * Get a byte in a printable binary form. 128 | * 129 | * @param b The byte to be writen. 130 | * @return A String representation of the byte. 131 | */ 132 | public static String writeBits(byte b) { 133 | StringBuffer stringBuffer = new StringBuffer(); 134 | int bit; 135 | for (int i = 7; i >= 0; i--) { 136 | bit = (b >>> i) & 0x01; 137 | stringBuffer.append(bit); 138 | } 139 | return stringBuffer.toString(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/biasedbit/efflux/util/TimeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.util; 18 | 19 | /** 20 | * @author Bruno de Carvalho 21 | */ 22 | public class TimeUtils { 23 | 24 | // constructors --------------------------------------------------------------------------------------------------- 25 | 26 | private TimeUtils() { 27 | } 28 | 29 | // public static methods ------------------------------------------------------------------------------------------ 30 | 31 | /** 32 | * Retrieve a timestamp for the current instant. 33 | * 34 | * @return Current instant. 35 | */ 36 | public static long now() { 37 | return System.currentTimeMillis(); 38 | } 39 | 40 | /** 41 | * Retrieve a timestamp for the current instant, in nanoseconds. 42 | * 43 | * @return Current instant. 44 | */ 45 | public static long nowNanos() { 46 | return System.nanoTime(); 47 | } 48 | 49 | /** 50 | * Test whether a given event has timed out (in seconds). 51 | * 52 | * @param now Current instant. 53 | * @param eventTime Instant at which the event took place. 54 | * @param timeBuffer The amount of time for which the event is valid (in seconds). 55 | * 56 | * @return true if the event has expired, false otherwise 57 | */ 58 | public static boolean hasExpired(long now, long eventTime, long timeBuffer) { 59 | return hasExpiredMillis(now, eventTime, timeBuffer * 1000); 60 | } 61 | 62 | /** 63 | * Test whether a given event has timed out (in milliseconds). 64 | * 65 | * @param now Current instant. 66 | * @param eventTime Instant at which the event took place. 67 | * @param timeBuffer The amount of time for which the event is valid (in milliseconds). 68 | * 69 | * @return true if the event has expired, false otherwise 70 | */ 71 | public static boolean hasExpiredMillis(long now, long eventTime, long timeBuffer) { 72 | return (eventTime + timeBuffer) < now; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.logger.com.biasedbit=TRACE, console 2 | log4j.additivity.com.biasedbit=false 3 | 4 | log4j.appender.console=org.apache.log4j.ConsoleAppender 5 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}|%-5p|%m%n -------------------------------------------------------------------------------- /src/site/css/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff url("../img/gradient.png") repeat-x; 3 | margin: 0 auto; 4 | font-family: sans-serif; 5 | font-size: 12px; 6 | padding: 0 2em; 7 | color: #000; 8 | } 9 | 10 | #container { 11 | width: 650px; 12 | margin-top: 25px; 13 | margin-left: auto; 14 | margin-right: auto; 15 | } 16 | 17 | #lastupdate { 18 | text-align: right; 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | #projects { 24 | border-top: 1px dotted #ccc; 25 | padding: 10px; 26 | border-bottom: 1px dotted #ccc; 27 | } 28 | 29 | h1 { 30 | background: url("../img/h1_header.png") no-repeat center; 31 | line-height: 1.2em; 32 | font-size: 2em; 33 | padding: 1.5em; 34 | margin-top: 0; 35 | text-align: left; 36 | } 37 | 38 | h1, h2, h3 { 39 | color: #333; 40 | } 41 | 42 | hr { 43 | border-top: 1px dotted #333; 44 | border-bottom: 0 none; 45 | } 46 | 47 | a:link { 48 | color: #555; 49 | text-decoration: none; 50 | } 51 | 52 | a:visited { 53 | color: #555; 54 | text-decoration: none; 55 | } 56 | 57 | a:hover { 58 | color: #58a037; 59 | text-decoration: underline; 60 | } 61 | 62 | .subtitle { 63 | margin-top: -50px; 64 | margin-left: 50px; 65 | font-size: 1em; 66 | color: #58a037; 67 | } 68 | 69 | .efflux { 70 | color: #58a037; 71 | font-weight: bold; 72 | } 73 | 74 | .text { 75 | color: #000; 76 | } 77 | 78 | ul { 79 | padding-left: 25px; 80 | } 81 | 82 | li { 83 | margin: 5px; 84 | list-style: square; 85 | color: #58a037; 86 | } 87 | 88 | tt, tt *, pre, pre *, code, code * { 89 | font-family: "Liberation Mono", "DejaVu Sans Mono", Consolas, Monaco, "Vera Sans Mono", "Lucida Console", "Courier New", monospace; 90 | } 91 | 92 | p { 93 | margin-left: 10px; 94 | } 95 | 96 | pre { 97 | border: 1px solid #ccc; 98 | padding: 5px 20px; 99 | color: #333; 100 | background-color: #f5f5f5; 101 | border-radius: 11px; 102 | -webkit-border-radius: 11px; 103 | -moz-border-radius: 11px; 104 | } 105 | 106 | #footer { 107 | width: 100%; 108 | margin: 10px; 109 | text-align: center; 110 | } -------------------------------------------------------------------------------- /src/site/img/dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/dots.png -------------------------------------------------------------------------------- /src/site/img/dots.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/dots.xcf -------------------------------------------------------------------------------- /src/site/img/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/gradient.png -------------------------------------------------------------------------------- /src/site/img/h1_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/h1_header.png -------------------------------------------------------------------------------- /src/site/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/icon.png -------------------------------------------------------------------------------- /src/site/img/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/icon.xcf -------------------------------------------------------------------------------- /src/site/img/squarelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/squarelogo.png -------------------------------------------------------------------------------- /src/site/img/squarelogo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/squarelogo.xcf -------------------------------------------------------------------------------- /src/site/img/squarelogo_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonbo372/efflux/e6fe99d1f8a74359083c6c9f6965081f15503e4e/src/site/img/squarelogo_alt.png -------------------------------------------------------------------------------- /src/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | efflux's project page 5 | 6 | 21 | 22 | 23 |

24 |
25 | hotpotato | 26 | efflux | 27 | windroplr 28 |
29 |

efflux's project page

30 |

a Java RTP stack

31 | 32 |
30/08/2010
33 |

What is efflux?

34 |

35 | efflux is an effort to provide a Java implementation of RFC 3550. 36 |

37 | 38 |

Note:

39 |

40 | Until this software hits version 1.0, it's considered to be in BETA stage.
41 | I encourage you to look & hack the code to your needs. Comments, improvements, patches, rants and praises are all 42 | welcome at the mailing list. 43 |

44 | 45 |
46 |

Repository

47 | 48 |

49 | The project is currently hosted at GitHub. 50 |

51 | 52 |
53 |

Releases

54 |

Nothing yet.

55 | 56 |
57 |

Roadmap

58 |
    59 |
  • 60 | Terminate automated RTCP support. 61 |
  • 62 |
  • 63 | Add support for SRTP. 64 |
  • 65 |
66 | 67 |
68 |

License

69 |

70 | efflux is distributed under Apache License, Version 2.0. 71 | Please see the enclosed NOTICE.txt, COPYRIGHT.txt, and LICENSE.txt for more information. 72 |

73 | 74 |
75 |

Mailing list & other useful stuff

76 |

Mailing list

77 |

78 | None yet... 79 |

80 | 81 |

Dependencies

82 |

83 | efflux needs Netty 3.2, 84 | SLF4J 1.6 and JDK 1.6. 85 |

86 | 87 |

Useless links

88 | 92 | 93 |
94 |

Examples

95 |

Coming up soon...

96 | 97 |
98 | 99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/ByePacketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.*; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class ByePacketTest { 30 | 31 | @Test 32 | public void testDecode() throws Exception { 33 | // wireshark capture, X-lite 34 | byte [] packetBytes = ByteUtils.convertHexStringToByteArray("81cb0001e6aa996e"); 35 | 36 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(packetBytes); 37 | ControlPacket controlPacket = ControlPacket.decode(buffer); 38 | 39 | assertEquals(ControlPacket.Type.BYE, controlPacket.getType()); 40 | 41 | ByePacket byePacket = (ByePacket) controlPacket; 42 | assertNotNull(byePacket.getSsrcList()); 43 | assertEquals(1, byePacket.getSsrcList().size()); 44 | assertEquals(new Long(0xe6aa996eL), byePacket.getSsrcList().get(0)); 45 | assertEquals(null, byePacket.getReasonForLeaving()); 46 | 47 | assertEquals(0, buffer.readableBytes()); 48 | } 49 | 50 | @Test 51 | public void testDecode2() throws Exception { 52 | // wireshark capture, jlibrtp 53 | byte[] packetBytes = ByteUtils.convertHexStringToByteArray("81cb000a4f52eb38156a6c69627274702073617973206279" + 54 | "6520627965210000000000000000000000000000"); 55 | 56 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(packetBytes); 57 | ControlPacket controlPacket = ControlPacket.decode(buffer); 58 | 59 | assertEquals(ControlPacket.Type.BYE, controlPacket.getType()); 60 | 61 | ByePacket byePacket = (ByePacket) controlPacket; 62 | assertNotNull(byePacket.getSsrcList()); 63 | assertEquals(1, byePacket.getSsrcList().size()); 64 | assertEquals(new Long(0x4f52eb38L), byePacket.getSsrcList().get(0)); 65 | assertEquals("jlibrtp says bye bye!", byePacket.getReasonForLeaving()); 66 | 67 | assertEquals(0, buffer.readableBytes()); 68 | } 69 | 70 | @Test 71 | public void testEncodeDecode() throws Exception { 72 | ByePacket packet = new ByePacket(); 73 | packet.addSsrc(0x45); 74 | packet.addSsrc(0x46); 75 | packet.setReasonForLeaving("So long, cruel world."); 76 | 77 | ChannelBuffer buffer = packet.encode(); 78 | assertEquals(36, buffer.readableBytes()); 79 | System.out.println(ByteUtils.writeArrayAsHex(buffer.array(), true)); 80 | assertEquals(0, buffer.readableBytes() % 4); 81 | 82 | ControlPacket controlPacket = ControlPacket.decode(buffer); 83 | assertEquals(ControlPacket.Type.BYE, controlPacket.getType()); 84 | 85 | ByePacket byePacket = (ByePacket) controlPacket; 86 | assertNotNull(byePacket.getSsrcList()); 87 | assertEquals(2, byePacket.getSsrcList().size()); 88 | assertEquals(new Long(0x45), byePacket.getSsrcList().get(0)); 89 | assertEquals(new Long(0x46), byePacket.getSsrcList().get(1)); 90 | assertEquals("So long, cruel world.", byePacket.getReasonForLeaving()); 91 | 92 | assertEquals(0, buffer.readableBytes()); 93 | } 94 | 95 | @Test 96 | public void testEncodeDecodeWithFixedBlockSize64() throws Exception { 97 | ByePacket packet = new ByePacket(); 98 | packet.addSsrc(0x45); 99 | packet.addSsrc(0x46); 100 | packet.setReasonForLeaving("So long, cruel world."); 101 | 102 | ChannelBuffer buffer = packet.encode(0, 64); 103 | assertEquals(64, buffer.readableBytes()); 104 | byte[] bufferArray = buffer.array(); 105 | System.out.println(ByteUtils.writeArrayAsHex(bufferArray, true)); 106 | assertEquals(0, buffer.readableBytes() % 4); 107 | 108 | ControlPacket controlPacket = ControlPacket.decode(buffer); 109 | assertEquals(ControlPacket.Type.BYE, controlPacket.getType()); 110 | 111 | ByePacket byePacket = (ByePacket) controlPacket; 112 | assertNotNull(byePacket.getSsrcList()); 113 | assertEquals(2, byePacket.getSsrcList().size()); 114 | assertEquals(new Long(0x45), byePacket.getSsrcList().get(0)); 115 | assertEquals(new Long(0x46), byePacket.getSsrcList().get(1)); 116 | assertEquals("So long, cruel world.", byePacket.getReasonForLeaving()); 117 | 118 | // Size without fixed block size would be 36 so padding is 64 - 36 119 | assertEquals(64 - 36, bufferArray[bufferArray.length - 1]); 120 | assertEquals(0, buffer.readableBytes()); 121 | } 122 | 123 | @Test 124 | public void testEncodeDecodeWithFixedBlockSize64AndCompound() throws Exception { 125 | ByePacket packet = new ByePacket(); 126 | packet.addSsrc(0x45); 127 | packet.addSsrc(0x46); 128 | packet.setReasonForLeaving("So long, cruel world."); 129 | 130 | ChannelBuffer buffer = packet.encode(60, 64); 131 | // Alignment would be to 128 bytes *with* the other RTCP packets. So this packet is sized at 128 - 60 = 68 132 | assertEquals(68, buffer.readableBytes()); 133 | byte[] bufferArray = buffer.array(); 134 | System.out.println(ByteUtils.writeArrayAsHex(bufferArray, true)); 135 | assertEquals(0, buffer.readableBytes() % 4); 136 | 137 | ControlPacket controlPacket = ControlPacket.decode(buffer); 138 | assertEquals(ControlPacket.Type.BYE, controlPacket.getType()); 139 | 140 | ByePacket byePacket = (ByePacket) controlPacket; 141 | assertNotNull(byePacket.getSsrcList()); 142 | assertEquals(2, byePacket.getSsrcList().size()); 143 | assertEquals(new Long(0x45), byePacket.getSsrcList().get(0)); 144 | assertEquals(new Long(0x46), byePacket.getSsrcList().get(1)); 145 | assertEquals("So long, cruel world.", byePacket.getReasonForLeaving()); 146 | 147 | // Size without fixed block size would be 36 so padding is 128 - (60 + 36) because current compound length is 60 148 | assertEquals(128 - (60 + 36), bufferArray[bufferArray.length - 1]); 149 | assertEquals(0, buffer.readableBytes()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/ControlPacketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import static org.junit.Assert.*; 28 | 29 | /** 30 | * @author Bruno de Carvalho 31 | */ 32 | public class ControlPacketTest { 33 | 34 | @Test 35 | public void testDecodeCompoundPacket() throws Exception { 36 | // wireshark capture, 3 packets (SR, SDES, BYE), from X-lite 37 | byte[] firstPacketBytes = ByteUtils 38 | .convertHexStringToByteArray("80c80006e6aa996ed01f8460ea7ef9db001eb9b4000006e30004a084"); 39 | byte[] secondPacketBytes = ByteUtils 40 | .convertHexStringToByteArray("81ca001ee6aa996e013d383232433634303536464438344539414231324438333442463" + 41 | "836303931354140756e697175652e7a333644423331373042303744344333302e6f7267" + 42 | "083110782d7274702d73657373696f6e2d6964363539413238344341443842344436313" + 43 | "83641324643304336383039363137300000"); 44 | byte[] thirdPacketBytes = ByteUtils 45 | .convertHexStringToByteArray("81cb0001e6aa996e"); 46 | 47 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(firstPacketBytes, secondPacketBytes, thirdPacketBytes); 48 | 49 | List controlPackets = new ArrayList(3); 50 | while (buffer.readableBytes() > 0) { 51 | controlPackets.add(ControlPacket.decode(buffer)); 52 | } 53 | 54 | assertEquals(0, buffer.readableBytes()); 55 | assertEquals(3, controlPackets.size()); 56 | 57 | assertEquals(ControlPacket.Type.SENDER_REPORT, controlPackets.get(0).getType()); 58 | assertEquals(ControlPacket.Type.SOURCE_DESCRIPTION, controlPackets.get(1).getType()); 59 | assertEquals(ControlPacket.Type.BYE, controlPackets.get(2).getType()); 60 | 61 | // No more tests needed as there is plenty of unit testing for each of those packets individually. 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/ReceiverReportPacketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | import static org.junit.Assert.assertNull; 26 | 27 | /** 28 | * @author Bruno de Carvalho 29 | */ 30 | public class ReceiverReportPacketTest { 31 | 32 | @Test 33 | public void testDecode() throws Exception { 34 | // wireshark capture, from jlibrtp 35 | byte[] packetBytes = ByteUtils.convertHexStringToByteArray("80c90001e6aa996e"); 36 | 37 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(packetBytes); 38 | ControlPacket controlPacket = ControlPacket.decode(buffer); 39 | 40 | assertEquals(ControlPacket.Type.RECEIVER_REPORT, controlPacket.getType()); 41 | 42 | ReceiverReportPacket srPacket = (ReceiverReportPacket) controlPacket; 43 | 44 | assertEquals(0xe6aa996eL, srPacket.getSenderSsrc()); 45 | assertEquals(0, srPacket.getReceptionReportCount()); 46 | assertNull(srPacket.getReceptionReports()); 47 | 48 | assertEquals(0, buffer.readableBytes()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/SenderReportPacketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.*; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class SenderReportPacketTest { 30 | 31 | @Test 32 | public void testDecode() throws Exception { 33 | // wireshark capture, from X-lite 34 | byte[] packetBytes = ByteUtils.convertHexStringToByteArray("80c800064f52eb38d01f84417f3b6459a91e7bd9000000020" + 35 | "0000002"); 36 | 37 | 38 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(packetBytes); 39 | ControlPacket controlPacket = ControlPacket.decode(buffer); 40 | 41 | assertEquals(ControlPacket.Type.SENDER_REPORT, controlPacket.getType()); 42 | 43 | SenderReportPacket srPacket = (SenderReportPacket) controlPacket; 44 | 45 | assertEquals(0x4f52eb38L, srPacket.getSenderSsrc()); 46 | assertEquals(2837347289L, srPacket.getRtpTimestamp()); 47 | assertEquals(2, srPacket.getSenderPacketCount()); 48 | assertEquals(2, srPacket.getSenderOctetCount()); 49 | assertEquals(0, srPacket.getReceptionReportCount()); 50 | assertNull(srPacket.getReceptionReports()); 51 | 52 | assertEquals(0, buffer.readableBytes()); 53 | } 54 | 55 | @Test 56 | public void testDecode2() throws Exception { 57 | // wireshark capture, from jlibrtp 58 | byte[] packetBytes = ByteUtils.convertHexStringToByteArray("80c80006e6aa996ed01f84481be76c8b001bb2b40000020b0" + 59 | "0015f64"); 60 | 61 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(packetBytes); 62 | ControlPacket controlPacket = ControlPacket.decode(buffer); 63 | 64 | assertEquals(ControlPacket.Type.SENDER_REPORT, controlPacket.getType()); 65 | 66 | SenderReportPacket srPacket = (SenderReportPacket) controlPacket; 67 | 68 | assertEquals(0xe6aa996eL, srPacket.getSenderSsrc()); 69 | assertEquals(1815220L, srPacket.getRtpTimestamp()); 70 | assertEquals(523, srPacket.getSenderPacketCount()); 71 | assertEquals(89956, srPacket.getSenderOctetCount()); 72 | assertEquals(0, srPacket.getReceptionReportCount()); 73 | assertNull(srPacket.getReceptionReports()); 74 | 75 | assertEquals(0, buffer.readableBytes()); 76 | } 77 | 78 | @Test 79 | public void testEncodeDecode() throws Exception { 80 | SenderReportPacket packet = new SenderReportPacket(); 81 | packet.setSenderSsrc(0x45); 82 | packet.setNtpTimestamp(0x45); 83 | packet.setRtpTimestamp(0x45); 84 | packet.setSenderOctetCount(20); 85 | packet.setSenderPacketCount(2); 86 | ReceptionReport block = new ReceptionReport(); 87 | block.setSsrc(10); 88 | block.setCumulativeNumberOfPacketsLost(11); 89 | block.setFractionLost((short) 12); 90 | block.setDelaySinceLastSenderReport(13); 91 | block.setInterArrivalJitter(14); 92 | block.setExtendedHighestSequenceNumberReceived(15); 93 | packet.addReceptionReportBlock(block); 94 | block = new ReceptionReport(); 95 | block.setSsrc(20); 96 | block.setCumulativeNumberOfPacketsLost(21); 97 | block.setFractionLost((short) 22); 98 | block.setDelaySinceLastSenderReport(23); 99 | block.setInterArrivalJitter(24); 100 | block.setExtendedHighestSequenceNumberReceived(25); 101 | packet.addReceptionReportBlock(block); 102 | 103 | ChannelBuffer encoded = packet.encode(); 104 | assertEquals(0, encoded.readableBytes() % 4); 105 | 106 | ControlPacket controlPacket = ControlPacket.decode(encoded); 107 | assertEquals(ControlPacket.Type.SENDER_REPORT, controlPacket.getType()); 108 | 109 | SenderReportPacket srPacket = (SenderReportPacket) controlPacket; 110 | 111 | assertEquals(0x45, srPacket.getNtpTimestamp()); 112 | assertEquals(0x45, srPacket.getRtpTimestamp()); 113 | assertEquals(20, srPacket.getSenderOctetCount()); 114 | assertEquals(2, srPacket.getSenderPacketCount()); 115 | assertNotNull(srPacket.getReceptionReports()); 116 | assertEquals(2, srPacket.getReceptionReportCount()); 117 | assertEquals(2, srPacket.getReceptionReports().size()); 118 | assertEquals(10, srPacket.getReceptionReports().get(0).getSsrc()); 119 | assertEquals(11, srPacket.getReceptionReports().get(0).getCumulativeNumberOfPacketsLost()); 120 | assertEquals(12, srPacket.getReceptionReports().get(0).getFractionLost()); 121 | assertEquals(13, srPacket.getReceptionReports().get(0).getDelaySinceLastSenderReport()); 122 | assertEquals(14, srPacket.getReceptionReports().get(0).getInterArrivalJitter()); 123 | assertEquals(15, srPacket.getReceptionReports().get(0).getExtendedHighestSequenceNumberReceived()); 124 | assertEquals(20, srPacket.getReceptionReports().get(1).getSsrc()); 125 | assertEquals(21, srPacket.getReceptionReports().get(1).getCumulativeNumberOfPacketsLost()); 126 | assertEquals(22, srPacket.getReceptionReports().get(1).getFractionLost()); 127 | assertEquals(23, srPacket.getReceptionReports().get(1).getDelaySinceLastSenderReport()); 128 | assertEquals(24, srPacket.getReceptionReports().get(1).getInterArrivalJitter()); 129 | assertEquals(25, srPacket.getReceptionReports().get(1).getExtendedHighestSequenceNumberReceived()); 130 | 131 | assertEquals(0, encoded.readableBytes()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/SourceChunkItemsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.*; 25 | 26 | /** 27 | * @author Bruno de Carvalho 28 | */ 29 | public class SourceChunkItemsTest { 30 | 31 | @Test 32 | public void testDecode() throws Exception { 33 | // From partial wireshark capture 34 | String hexString = "010e6e756c6c406c6f63616c686f7374"; 35 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(ByteUtils.convertHexStringToByteArray(hexString)); 36 | 37 | SdesChunkItem item = SdesChunkItems.decode(buffer); 38 | assertEquals(SdesChunkItem.Type.CNAME, item.getType()); 39 | assertEquals("null@localhost", item.getValue()); 40 | assertEquals(0, buffer.readableBytes()); 41 | } 42 | 43 | @Test 44 | public void testEncodeNull() throws Exception { 45 | ChannelBuffer buffer = SdesChunkItems.encode(SdesChunkItems.NULL_ITEM); 46 | assertEquals(1, buffer.capacity()); 47 | assertEquals(0x00, buffer.array()[0]); 48 | } 49 | 50 | @Test 51 | public void testEncodeDecodeSimpleItem() throws Exception { 52 | String value = "cname value"; 53 | ChannelBuffer buffer = SdesChunkItems.encode(SdesChunkItems.createCnameItem(value)); 54 | SdesChunkItem item = SdesChunkItems.decode(buffer); 55 | assertEquals(SdesChunkItem.Type.CNAME, item.getType()); 56 | assertEquals(value, item.getValue()); 57 | } 58 | 59 | @Test 60 | public void testEncodeDecodeSimpleEmptyItem() throws Exception { 61 | String value = ""; 62 | ChannelBuffer buffer = SdesChunkItems.encode(SdesChunkItems.createNameItem(value)); 63 | SdesChunkItem item = SdesChunkItems.decode(buffer); 64 | assertEquals(SdesChunkItem.Type.NAME, item.getType()); 65 | assertEquals(value, item.getValue()); 66 | } 67 | 68 | @Test 69 | public void testEncodeDecodeSimpleItemMaxLength() throws Exception { 70 | StringBuilder value = new StringBuilder(); 71 | for (int i = 0; i < 255; i++) { 72 | value.append('a'); 73 | } 74 | ChannelBuffer buffer = SdesChunkItems.encode(SdesChunkItems.createCnameItem(value.toString())); 75 | SdesChunkItem item = SdesChunkItems.decode(buffer); 76 | assertEquals(SdesChunkItem.Type.CNAME, item.getType()); 77 | assertEquals(value.toString(), item.getValue()); 78 | } 79 | 80 | @Test 81 | public void testEncodeDecodeSimpleItemOverMaxLength() throws Exception { 82 | StringBuilder value = new StringBuilder(); 83 | for (int i = 0; i < 256; i++) { 84 | value.append('a'); 85 | } 86 | try { 87 | SdesChunkItems.encode(SdesChunkItems.createCnameItem(value.toString())); 88 | } catch (Exception e) { 89 | return; 90 | } 91 | fail("Expected exception wasn't caught"); 92 | } 93 | 94 | @Test 95 | public void testEncoderDecodePrivItem() throws Exception { 96 | String prefix = "prefixValue"; 97 | String value = "someOtherThink"; 98 | ChannelBuffer buffer = SdesChunkItems.encode(SdesChunkItems.createPrivItem(prefix, value)); 99 | SdesChunkItem item = SdesChunkItems.decode(buffer); 100 | assertEquals(SdesChunkItem.Type.PRIV, item.getType()); 101 | assertEquals(value, item.getValue()); 102 | assertEquals(prefix, ((SdesChunkPrivItem) item).getPrefix()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/SourceChunkTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import org.jboss.netty.buffer.ChannelBuffer; 20 | import org.junit.Test; 21 | 22 | import static org.junit.Assert.*; 23 | 24 | /** 25 | * @author Bruno de Carvalho 26 | */ 27 | public class SourceChunkTest { 28 | 29 | @Test 30 | public void testEncodeDecode() throws Exception { 31 | long ssrc = 0x0000ffff; 32 | SdesChunk chunk = new SdesChunk(ssrc); 33 | chunk.addItem(SdesChunkItems.createCnameItem("cname")); 34 | chunk.addItem(SdesChunkItems.createNameItem("name")); 35 | chunk.addItem(SdesChunkItems.createEmailItem("email")); 36 | chunk.addItem(SdesChunkItems.createPrivItem("prefix", "value")); 37 | 38 | ChannelBuffer encoded = chunk.encode(); 39 | // Must be 32 bit aligned. 40 | assertEquals(0, encoded.readableBytes() % 4); 41 | System.err.println("encoded readable bytes: " + encoded.readableBytes()); 42 | SdesChunk decoded = SdesChunk.decode(encoded); 43 | 44 | assertEquals(chunk.getSsrc(), decoded.getSsrc()); 45 | assertNotNull(decoded.getItems()); 46 | assertEquals(4, decoded.getItems().size()); 47 | 48 | for (int i = 0; i < chunk.getItems().size(); i++) { 49 | assertEquals(chunk.getItems().get(i).getType(), decoded.getItems().get(i).getType()); 50 | assertEquals(chunk.getItems().get(i).getValue(), decoded.getItems().get(i).getValue()); 51 | } 52 | 53 | assertEquals(0, encoded.readableBytes()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/packet/SourceDescriptionPacketTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.packet; 18 | 19 | import com.biasedbit.efflux.util.ByteUtils; 20 | import org.jboss.netty.buffer.ChannelBuffer; 21 | import org.jboss.netty.buffer.ChannelBuffers; 22 | import org.junit.Test; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | import static org.junit.Assert.assertNotNull; 26 | 27 | /** 28 | * @author Bruno de Carvalho 29 | */ 30 | public class SourceDescriptionPacketTest { 31 | 32 | @Test 33 | public void testDecode() throws Exception { 34 | // packet captured with wireshark, from X-lite 35 | String hexString = "81ca00054f52eb38010e6e756c6c406c6f63616c686f7374"; 36 | byte[] bytes = ByteUtils.convertHexStringToByteArray(hexString); 37 | 38 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(bytes); 39 | ControlPacket controlPacket = ControlPacket.decode(buffer); 40 | assertEquals(RtpVersion.V2, controlPacket.getVersion()); 41 | assertEquals(ControlPacket.Type.SOURCE_DESCRIPTION, controlPacket.getType()); 42 | 43 | SourceDescriptionPacket sdesPacket = (SourceDescriptionPacket) controlPacket; 44 | assertNotNull(sdesPacket.getChunks()); 45 | assertEquals(1, sdesPacket.getChunks().size()); 46 | assertEquals(0x4f52eb38, sdesPacket.getChunks().get(0).getSsrc()); 47 | assertNotNull(sdesPacket.getChunks().get(0).getItems()); 48 | assertEquals(1, sdesPacket.getChunks().get(0).getItems().size()); 49 | assertEquals(SdesChunkItem.Type.CNAME, sdesPacket.getChunks().get(0).getItems().get(0).getType()); 50 | assertEquals("null@localhost", sdesPacket.getChunks().get(0).getItems().get(0).getValue()); 51 | 52 | assertEquals(0, buffer.readableBytes()); 53 | } 54 | 55 | @Test 56 | public void testDecode2() throws Exception { 57 | // packet capture with wireshark, from jlibrtp 58 | String hexString = "81ca001ee6aa996e013d383232433634303536464438344539414231324438333442463836303931354140756" + 59 | "e697175652e7a333644423331373042303744344333302e6f7267083110782d7274702d73657373696f6e2d69" + 60 | "6436353941323834434144384234443631383641324643304336383039363137300000"; 61 | byte[] bytes = ByteUtils.convertHexStringToByteArray(hexString); 62 | 63 | ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(bytes); 64 | ControlPacket controlPacket = ControlPacket.decode(buffer); 65 | assertEquals(RtpVersion.V2, controlPacket.getVersion()); 66 | assertEquals(ControlPacket.Type.SOURCE_DESCRIPTION, controlPacket.getType()); 67 | 68 | SourceDescriptionPacket sdesPacket = (SourceDescriptionPacket) controlPacket; 69 | assertNotNull(sdesPacket.getChunks()); 70 | assertEquals(1, sdesPacket.getChunks().size()); 71 | assertEquals(0xe6aa996eL, sdesPacket.getChunks().get(0).getSsrc()); 72 | assertNotNull(sdesPacket.getChunks().get(0).getItems()); 73 | assertEquals(2, sdesPacket.getChunks().get(0).getItems().size()); 74 | assertEquals(SdesChunkItem.Type.CNAME, sdesPacket.getChunks().get(0).getItems().get(0).getType()); 75 | assertEquals("822C64056FD84E9AB12D834BF860915A@unique.z36DB3170B07D4C30.org", 76 | sdesPacket.getChunks().get(0).getItems().get(0).getValue()); 77 | assertEquals(SdesChunkItem.Type.PRIV, sdesPacket.getChunks().get(0).getItems().get(1).getType()); 78 | assertEquals("x-rtp-session-id", 79 | ((SdesChunkPrivItem) sdesPacket.getChunks().get(0).getItems().get(1)).getPrefix()); 80 | assertEquals("659A284CAD8B4D6186A2FC0C68096170", sdesPacket.getChunks().get(0).getItems().get(1).getValue()); 81 | 82 | assertEquals(0, buffer.readableBytes()); 83 | } 84 | 85 | @Test 86 | public void testEncode() throws Exception { 87 | SourceDescriptionPacket packet = new SourceDescriptionPacket(); 88 | SdesChunk chunk = new SdesChunk(); 89 | chunk.setSsrc(0x45); 90 | chunk.addItem(SdesChunkItems.createCnameItem("karma")); 91 | chunk.addItem(SdesChunkItems.createNameItem("Earl")); 92 | chunk.addItem(SdesChunkItems.createNoteItem("Hey crabman")); 93 | packet.addItem(chunk); 94 | chunk = new SdesChunk(); 95 | chunk.setSsrc(0x46); 96 | chunk.addItem(SdesChunkItems.createCnameItem("Randy")); 97 | packet.addItem(chunk); 98 | 99 | ChannelBuffer encoded = packet.encode(); 100 | System.out.println(ByteUtils.writeArrayAsHex(encoded.array(), true)); 101 | 102 | assertEquals(0, encoded.readableBytes() % 4); 103 | 104 | ControlPacket decoded = ControlPacket.decode(encoded); 105 | assertEquals(packet.getType(), decoded.getType()); 106 | 107 | SourceDescriptionPacket decodedSdes = (SourceDescriptionPacket) decoded; 108 | assertNotNull(decodedSdes.getChunks()); 109 | assertEquals(2, decodedSdes.getChunks().size()); 110 | 111 | assertEquals(0x45, decodedSdes.getChunks().get(0).getSsrc()); 112 | assertNotNull(decodedSdes.getChunks().get(0).getItems()); 113 | assertEquals(SdesChunkItem.Type.CNAME, decodedSdes.getChunks().get(0).getItems().get(0).getType()); 114 | assertEquals("karma", decodedSdes.getChunks().get(0).getItems().get(0).getValue()); 115 | assertEquals(SdesChunkItem.Type.NAME, decodedSdes.getChunks().get(0).getItems().get(1).getType()); 116 | assertEquals("Earl", decodedSdes.getChunks().get(0).getItems().get(1).getValue()); 117 | assertEquals(SdesChunkItem.Type.NOTE, decodedSdes.getChunks().get(0).getItems().get(2).getType()); 118 | assertEquals("Hey crabman", decodedSdes.getChunks().get(0).getItems().get(2).getValue()); 119 | 120 | assertEquals(0x46, decodedSdes.getChunks().get(1).getSsrc()); 121 | assertNotNull(decodedSdes.getChunks().get(1).getItems()); 122 | assertEquals(SdesChunkItem.Type.CNAME, decodedSdes.getChunks().get(1).getItems().get(0).getType()); 123 | assertEquals("Randy", decodedSdes.getChunks().get(1).getItems().get(0).getValue()); 124 | 125 | assertEquals(0, encoded.readableBytes()); 126 | } 127 | 128 | @Test 129 | public void testEncode2() throws Exception { 130 | SourceDescriptionPacket packet = new SourceDescriptionPacket(); 131 | SdesChunk chunk = new SdesChunk(); 132 | chunk.setSsrc(0x45); 133 | chunk.addItem(SdesChunkItems.createCnameItem("karma")); 134 | chunk.addItem(SdesChunkItems.createNameItem("Earl")); 135 | packet.addItem(chunk); 136 | chunk = new SdesChunk(); 137 | chunk.setSsrc(0x46); 138 | chunk.addItem(SdesChunkItems.createCnameItem("Randy")); 139 | packet.addItem(chunk); 140 | 141 | ChannelBuffer encoded = packet.encode(); 142 | System.out.println(ByteUtils.writeArrayAsHex(encoded.array(), true)); 143 | 144 | assertEquals(0, encoded.readableBytes() % 4); 145 | 146 | ControlPacket decoded = ControlPacket.decode(encoded); 147 | assertEquals(packet.getType(), decoded.getType()); 148 | 149 | SourceDescriptionPacket decodedSdes = (SourceDescriptionPacket) decoded; 150 | assertNotNull(decodedSdes.getChunks()); 151 | assertEquals(2, decodedSdes.getChunks().size()); 152 | 153 | assertEquals(0x45, decodedSdes.getChunks().get(0).getSsrc()); 154 | assertNotNull(decodedSdes.getChunks().get(0).getItems()); 155 | assertEquals(SdesChunkItem.Type.CNAME, decodedSdes.getChunks().get(0).getItems().get(0).getType()); 156 | assertEquals("karma", decodedSdes.getChunks().get(0).getItems().get(0).getValue()); 157 | assertEquals(SdesChunkItem.Type.NAME, decodedSdes.getChunks().get(0).getItems().get(1).getType()); 158 | assertEquals("Earl", decodedSdes.getChunks().get(0).getItems().get(1).getValue()); 159 | 160 | assertEquals(0x46, decodedSdes.getChunks().get(1).getSsrc()); 161 | assertNotNull(decodedSdes.getChunks().get(1).getItems()); 162 | assertEquals(SdesChunkItem.Type.CNAME, decodedSdes.getChunks().get(1).getItems().get(0).getType()); 163 | assertEquals("Randy", decodedSdes.getChunks().get(1).getItems().get(0).getValue()); 164 | 165 | assertEquals(0, encoded.readableBytes()); 166 | } 167 | 168 | @Test 169 | public void testEncodeAsPartOfCompound() throws Exception { 170 | SourceDescriptionPacket packet = new SourceDescriptionPacket(); 171 | SdesChunk chunk = new SdesChunk(); 172 | chunk.setSsrc(0x45); 173 | chunk.addItem(SdesChunkItems.createCnameItem("karma")); 174 | chunk.addItem(SdesChunkItems.createNameItem("Earl")); 175 | packet.addItem(chunk); 176 | chunk = new SdesChunk(); 177 | chunk.setSsrc(0x46); 178 | chunk.addItem(SdesChunkItems.createCnameItem("Randy")); 179 | packet.addItem(chunk); 180 | 181 | // 36 bytes 182 | ChannelBuffer encoded = packet.encode(); 183 | System.out.println(ByteUtils.writeArrayAsHex(encoded.array(), true)); 184 | System.out.println("simple encoding length: " + encoded.readableBytes()); 185 | assertEquals(0, encoded.readableBytes() % 4); 186 | // assuming previous 20 bytes, 36 bytes of normally encoded packet thus become 44 (+8 for padding, 20+36+8 = 64) 187 | encoded = packet.encode(20, 64); 188 | System.out.println("compound encoding length: " + encoded.readableBytes()); // 20 189 | encoded.skipBytes(encoded.readableBytes() - 1); 190 | assertEquals(8, encoded.readByte()); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/java/com/biasedbit/efflux/session/MultiParticipantSessionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Bruno de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.biasedbit.efflux.session; 18 | 19 | import com.biasedbit.efflux.packet.DataPacket; 20 | import com.biasedbit.efflux.participant.RtpParticipant; 21 | import com.biasedbit.efflux.participant.RtpParticipantInfo; 22 | import org.junit.After; 23 | import org.junit.Test; 24 | 25 | import java.net.InetSocketAddress; 26 | import java.net.SocketAddress; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | /** 33 | * @author Bruno de Carvalho 34 | */ 35 | public class MultiParticipantSessionTest { 36 | 37 | private MultiParticipantSession session; 38 | 39 | @After 40 | public void tearDown() { 41 | if (this.session != null) { 42 | this.session.terminate(); 43 | } 44 | } 45 | 46 | @Test 47 | public void testNewParticipantFromDataPacket() throws Exception { 48 | RtpParticipant participant = RtpParticipant.createReceiver("localhost", 8000, 8001); 49 | participant.getInfo().setSsrc(6969); 50 | this.session = new MultiParticipantSession("id", 8, participant); 51 | assertTrue(this.session.init()); 52 | 53 | this.session.addEventListener(new RtpSessionEventListener() { 54 | @Override 55 | public void participantJoinedFromData(RtpSession session, RtpParticipant participant) { 56 | assertEquals(69, participant.getSsrc()); 57 | } 58 | 59 | @Override 60 | public void participantJoinedFromControl(RtpSession session, RtpParticipant participant) { 61 | } 62 | 63 | @Override 64 | public void participantDataUpdated(RtpSession session, RtpParticipant participant) { 65 | } 66 | 67 | @Override 68 | public void participantLeft(RtpSession session, RtpParticipant participant) { 69 | } 70 | 71 | @Override 72 | public void participantDeleted(RtpSession session, RtpParticipant participant) { 73 | } 74 | 75 | @Override 76 | public void resolvedSsrcConflict(RtpSession session, long oldSsrc, long newSsrc) { 77 | } 78 | 79 | @Override 80 | public void sessionTerminated(RtpSession session, Throwable cause) { 81 | System.err.println("Session terminated: " + cause.getMessage()); 82 | } 83 | }); 84 | 85 | DataPacket packet = new DataPacket(); 86 | packet.setSequenceNumber(1); 87 | packet.setPayloadType(8); 88 | packet.setSsrc(69); 89 | SocketAddress address = new InetSocketAddress("localhost", 8000); 90 | this.session.dataPacketReceived(address, packet); 91 | } 92 | 93 | @Test 94 | public void testOutOfOrderDiscard() throws Exception { 95 | RtpParticipant participant = RtpParticipant.createReceiver("localhost", 8000, 8001); 96 | participant.getInfo().setSsrc(6969); 97 | this.session = new MultiParticipantSession("id", 8, participant); 98 | this.session.setDiscardOutOfOrder(true); 99 | assertTrue(this.session.init()); 100 | 101 | final AtomicInteger counter = new AtomicInteger(0); 102 | 103 | this.session.addDataListener(new RtpSessionDataListener() { 104 | @Override 105 | public void dataPacketReceived(RtpSession session, RtpParticipantInfo participant, DataPacket packet) { 106 | counter.incrementAndGet(); 107 | } 108 | }); 109 | 110 | DataPacket packet = new DataPacket(); 111 | packet.setSequenceNumber(10); 112 | packet.setPayloadType(8); 113 | packet.setSsrc(69); 114 | SocketAddress address = new InetSocketAddress("localhost", 8000); 115 | this.session.dataPacketReceived(address, packet); 116 | packet.setSequenceNumber(11); 117 | this.session.dataPacketReceived(address, packet); 118 | packet.setSequenceNumber(10); 119 | this.session.dataPacketReceived(address, packet); 120 | 121 | assertEquals(2, counter.get()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/xref/stylesheet.css: -------------------------------------------------------------------------------- 1 | @import url("../api/stylesheet.css"); 2 | 3 | /* copy javadoc's h1 style */ 4 | h2 { 5 | background: url("../api/resources/h1_header.png") no-repeat; 6 | border-top: 1px dotted #ccc; 7 | line-height: 1.2em; 8 | color: #333; 9 | font-size: 2em; 10 | padding: 1.5em; 11 | margin-top: 0; 12 | text-align: left; 13 | } 14 | 15 | .summary { 16 | margin-bottom: 1em; 17 | width: 100%; 18 | } 19 | 20 | .summary, .summary th, .summary td { 21 | border: solid #E6E7E8 1px; 22 | font-weight: bold; 23 | } 24 | 25 | .summary th { 26 | background: #000000 url("../api/resources/table_header.png") repeat-x scroll left top; 27 | color: #FFFFFF; 28 | font-size: 12px; 29 | font-weight: bold; 30 | height: 31px; 31 | text-align: left; 32 | padding: 0 10px 0 10px; 33 | } 34 | 35 | .summary td { 36 | background: #FFFFFF; 37 | line-height: 175%; 38 | padding-left: 10px; 39 | } 40 | 41 | /* Navigation bar fonts and colors */ 42 | 43 | .overview { 44 | background: #333 url("../api/resources/nav_header.png") repeat-x scroll left top; 45 | line-height: 2em; 46 | font-weight: bold; 47 | font-size: 1.1em; 48 | padding-left: 6px; 49 | } 50 | 51 | .overview ul { 52 | margin-bottom: 0; 53 | } 54 | 55 | .overview li, .overview li a, .overview li a:visited { 56 | display: inline; 57 | padding-left: 6px; 58 | padding-right: 6px; 59 | color: #fff; 60 | } 61 | 62 | .overview li.selected { 63 | background-color: #fff; 64 | color: #333; 65 | } 66 | 67 | .framenoframe { 68 | background-color: #fff; 69 | line-height: 2em; 70 | padding-left: 6px; 71 | padding-right: 6px; 72 | } 73 | 74 | .framenoframe ul { 75 | margin: 0; 76 | } 77 | 78 | .framenoframe li { 79 | display: inline; 80 | list-style-type: none; 81 | padding-right: 10px; 82 | } 83 | 84 | .framenoframe li a { 85 | font-weight: bold; 86 | } 87 | 88 | /* Class/package list on the left frame */ 89 | body ul { 90 | padding: 0; 91 | margin-top: 0; 92 | } 93 | 94 | body li { 95 | list-style-type: none; 96 | font-family: Helvetica, Arial, sans-serif; 97 | } 98 | 99 | div#footer { 100 | text-align: center; 101 | } 102 | 103 | /* Syntax highlighting */ 104 | 105 | em { 106 | color: rgb(63, 63, 191); 107 | font-style: normal; 108 | } 109 | 110 | em.comment { 111 | color: rgb(170, 170, 170); 112 | font-style: normal; 113 | } 114 | 115 | .string { 116 | color: rgb(1, 140, 49); 117 | } 118 | 119 | .jxr_comment { 120 | color: rgb(170, 170, 170); 121 | font-style: normal; 122 | } 123 | 124 | .jxr_javadoccomment { 125 | color: rgb(170, 170, 170); 126 | font-style: normal; 127 | } 128 | 129 | .jxr_string { 130 | color: rgb(1, 140, 49); 131 | } 132 | 133 | .jxr_keyword { 134 | color: rgb(0, 0, 100); 135 | font-weight: bold; 136 | } --------------------------------------------------------------------------------