/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.service.protocol.media;
import java.util.*;
import org.jitsi.service.neomedia.*;
/**
* RFC [RFC 5285] defines a mechanism for attaching multiple extensions to
* RTP packets. Part of this mechanism consists in negotiating their
* identifiers using <tt>extmap</tt> attributes pretty much the same way one
* would negotiate payload types with <tt>rtpmap</tt> attributes.
* <p>
* Mappings of extension IDs are handled with SDP. They are created for
* a particular session and remain the same for its entire lifetime. They may
* however change in following sessions.
* </p>
* <p>
* We use this class as a utility for easily creating and tracking extension
* mappings for the lifetime of a particular session. One instance of this
* registry is supposed to be mapped to one media session and they should
* have the same life cycle.
*
* @author Emil Ivov
*/
public class DynamicRTPExtensionsRegistry
{
/**
* The minimum integer that is allowed for use when mapping extensions using
* the one-byte header.
*/
public static final int MIN_HEADER_ID = 1;
/**
* The maximum integer that is allowed for use when mapping extensions using
* the one-byte header. Note that 15 is reserved for future use by 5285
*/
public static final int MAX_ONE_BYTE_HEADER_ID = 14;
/**
* The maximum integer that is allowed for use when mapping extensions using
* the two-byte header.
*/
public static final int MAX_TWO_BYTE_HEADER_ID = 255;
/**
* A field that we use to track mapping IDs.
*/
private byte nextExtensionMapping = MIN_HEADER_ID;
/**
* A table mapping <tt>RTPExtension</tt> instances to the dynamically
* allocated ID they have obtained for the lifetime of this registry.
*/
private Map<RTPExtension, Byte> extMap
= new Hashtable<RTPExtension, Byte>();
/**
* Returns the ID that has been allocated for <tt>extension</tt>. A mapping
* for the specified <tt>extension</tt> would be created even if it did not
* previously exist. The method is meant for use primarily during generation
* of SDP descriptions.
*
* @param extension the <tt>RTPExtension</tt> instance that we'd like to
* obtain a dynamic ID for.
*
* @return the (possibly newly allocated) ID corresponding to the specified
* <tt>extension</tt> and valid for the lifetime of the media session.
*
* @throws IllegalStateException if we have already registered more RTP
* extensions than allowed for by RTP.
*/
public byte obtainExtensionMapping(RTPExtension extension)
throws IllegalStateException
{
Byte extID = extMap.get(extension);
//hey, we already had this one, let's return it ;)
if( extID == null)
{
extID = nextExtensionID();
extMap.put(extension, extID);
}
return extID;
}
/**
* Returns the ID that has been allocated for <tt>extension</tt> or
* <tt>-1</tt> if no extension exists.
*
* @param extension the <tt>RTPExtension</tt> instance whose ID we'd like to
* find.
*
* @return the ID corresponding to the specified <tt>extension</tt> or
* <tt>-1</tt> if <tt>extension</tt> is not registered with this registry.
*/
public byte getExtensionMapping(RTPExtension extension)
{
Byte extID = extMap.get(extension);
//hey, we already had this one, let's return it ;)
if( extID == null)
{
return -1;
}
return extID;
}
/**
* Adds the specified <tt>extension</tt> to <tt>extID</tt> mapping to
* the list of mappings known to this registry. The method is meant for
* use primarily when handling incoming media descriptions, methods
* generating local SDP should use the <tt>obtainExtensionMapping</tt>
* instead.
*
* @param extID the extension ID that we'd like to allocated to
* <tt>extension</tt>.
* @param extension the <tt>RTPExtension</tt> that we'd like to create a
* dynamic mapping for.
*
* @throws IllegalArgumentException in case <tt>extID</tt> has already been
* assigned to another <tt>RTPExtension</tt>.
*/
public void addMapping(RTPExtension extension, byte extID)
throws IllegalArgumentException
{
RTPExtension alreadyMappedExt = findExtension(extID);
if(alreadyMappedExt != null)
{
throw new IllegalArgumentException(extID
+ " has already been allocated to " + alreadyMappedExt);
}
if( extID < MIN_HEADER_ID)
{
throw new IllegalArgumentException(extID
+ " is not a valid RTP extensino header ID."
+ " (must be between " + MIN_HEADER_ID
+ " and " + MAX_TWO_BYTE_HEADER_ID);
}
extMap.put(extension, Byte.valueOf(extID));
}
/**
* Returns a reference to the <tt>RTPExtension</tt> with the specified
* mapping or <tt>null</tt> if the number specified by <tt>extID</tt>
* has not been allocated yet.
*
* @param extID the ID whose <tt>RTPExtension</tt> we are trying to
* discover.
*
* @return the <tt>RTPExtension</tt> that has been mapped to
* <tt>extID</tt> in this registry or <tt>null</tt> if it hasn't been
* allocated yet.
*/
public RTPExtension findExtension(byte extID)
{
for (Map.Entry<RTPExtension, Byte> entry
: extMap.entrySet())
{
byte currentExtensionID = entry.getValue();
if(currentExtensionID == extID)
return entry.getKey();
}
return null;
}
/**
* Returns the first non-allocated dynamic extension ID number.
*
* @return the first non-allocated dynamic extension ID number..
*
* @throws IllegalStateException if we have already registered more RTP
* extension headers than allowed for by RTP.
*/
private byte nextExtensionID()
throws IllegalStateException
{
while (true)
{
if (nextExtensionMapping < 0)
{
throw new IllegalStateException(
"Impossible to map more than the 255 already mapped "
+" RTP extensions");
}
byte extID = nextExtensionMapping++;
if(findExtension(extID) == null)
return extID;
//if we get here then that means that the number we obtained by
//incrementing our ID counter was already occupied (probably by an
//incoming SDP). continue bravely and get the next free one.
}
}
/**
* Returns a copy of all mappings currently registered in this registry.
*
* @return a copy of all mappings currently registered in this registry.
*/
public Map<RTPExtension, Byte> getMappings()
{
return new Hashtable<RTPExtension, Byte>(extMap);
}
}