/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
package org.restcomm.media.sdp.fields;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.restcomm.media.sdp.MediaProfile;
import org.restcomm.media.sdp.SdpField;
import org.restcomm.media.sdp.SessionLevelAccessor;
import org.restcomm.media.sdp.attributes.ConnectionModeAttribute;
import org.restcomm.media.sdp.attributes.FormatParameterAttribute;
import org.restcomm.media.sdp.attributes.MaxPacketTimeAttribute;
import org.restcomm.media.sdp.attributes.PacketTimeAttribute;
import org.restcomm.media.sdp.attributes.RtpMapAttribute;
import org.restcomm.media.sdp.attributes.SsrcAttribute;
import org.restcomm.media.sdp.dtls.attributes.FingerprintAttribute;
import org.restcomm.media.sdp.dtls.attributes.SetupAttribute;
import org.restcomm.media.sdp.ice.attributes.CandidateAttribute;
import org.restcomm.media.sdp.ice.attributes.IcePwdAttribute;
import org.restcomm.media.sdp.ice.attributes.IceUfragAttribute;
import org.restcomm.media.sdp.rtcp.attributes.RtcpAttribute;
import org.restcomm.media.sdp.rtcp.attributes.RtcpMuxAttribute;
/**
* m=[media] [port] [proto] [fmt]
*
* <p>
* A session description may contain a number of media descriptions.<br>
* Each media description starts with an "m=" field and is terminated by either
* the next "m=" field or by the end of the session description.
* </p>
*
* @author Henrique Rosa (henrique.rosa@telestax.com)
*
*/
public class MediaDescriptionField implements SdpField {
private static final String NEWLINE = "\n";
public static final char FIELD_TYPE = 'm';
private static final String BEGIN = "m=";
private SessionLevelAccessor session;
// SDP fields (media description specific)
private String media;
private int port;
private String protocol;
private final List<Integer> payloadTypes;
private final Map<Integer, RtpMapAttribute> formats;
// SDP fields and attributes (media-level)
private ConnectionField connection;
private ConnectionModeAttribute connectionMode;
private RtcpAttribute rtcp;
private RtcpMuxAttribute rtcpMux;
private SsrcAttribute ssrc;
private PacketTimeAttribute ptime;
private MaxPacketTimeAttribute maxptime;
// ICE attributes (session-level)
private IcePwdAttribute icePwd;
private IceUfragAttribute iceUfrag;
private List<CandidateAttribute> candidates;
// WebRTC attributes (session-level)
private FingerprintAttribute fingerprint;
private SetupAttribute setup;
private final StringBuilder builder;
public MediaDescriptionField() {
this(null);
}
public MediaDescriptionField(final SessionLevelAccessor sessionAccessor) {
this.session = sessionAccessor;
this.builder = new StringBuilder(BEGIN);
this.payloadTypes = new ArrayList<Integer>(10);
this.formats = new HashMap<Integer, RtpMapAttribute>(10);
}
public void setSession(SessionLevelAccessor session) {
this.session = session;
}
public String getMedia() {
return media;
}
public void setMedia(String media) {
this.media = media;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public void addPayloadType(int payloadType) {
if(!this.payloadTypes.contains(payloadType)) {
this.payloadTypes.add(payloadType);
}
}
public void setPayloadTypes(int... payloadTypes) {
this.payloadTypes.clear();
for (int payloadType : payloadTypes) {
addPayloadType(payloadType);
}
}
public boolean containsPayloadType(int payloadType) {
return this.payloadTypes.contains(payloadType);
}
public void setFormats(RtpMapAttribute ...formats) {
this.formats.clear();
int numFormats = formats.length;
for (int i = 0; i < numFormats; i++) {
addFormat(formats[i]);
}
}
public void addFormat(RtpMapAttribute format) {
this.formats.put(format.getPayloadType(), format);
}
public void addFormats(RtpMapAttribute ...formats) {
int numFormats = formats.length;
for (int i = 0; i < numFormats; i++) {
addFormat(formats[i]);
}
}
public boolean containsFormat(int format) {
return this.formats.containsKey(format);
}
public void setFormatParameters(short payloadType, FormatParameterAttribute parameters) {
RtpMapAttribute format = this.formats.get(payloadType);
if(format != null) {
format.setParameters(parameters);
}
}
public ConnectionField getConnection() {
if(this.connection == null && this.session != null) {
return session.getConnection();
}
return this.connection;
}
public void setConnection(ConnectionField connection) {
this.connection = connection;
}
public ConnectionModeAttribute getConnectionMode() {
if(this.connectionMode == null && this.session != null) {
if(session.getConnectionMode() != null) {
return session.getConnectionMode();
}
}
return this.connectionMode;
}
public void setConnectionMode(ConnectionModeAttribute connectionMode) {
this.connectionMode = connectionMode;
}
public RtpMapAttribute[] getFormats() {
if(this.formats.isEmpty()) {
return null;
}
return this.formats.values().toArray(new RtpMapAttribute[this.formats.size()]);
}
public int[] getPayloadTypes() {
int[] values = new int[this.payloadTypes.size()];
int index = 0;
for (Integer value : this.payloadTypes) {
values[index++] = value;
}
return values;
}
public RtpMapAttribute getFormat(int payloadType) {
return this.formats.get(payloadType);
}
public RtcpAttribute getRtcp() {
return rtcp;
}
public int getRtcpPort() {
if(this.rtcp != null) {
return rtcp.getPort();
} else if(this.rtcpMux != null) {
return this.port;
} else {
return this.port + 1;
}
}
public void setRtcp(RtcpAttribute rtcp) {
this.rtcp = rtcp;
}
public RtcpMuxAttribute getRtcpMux() {
return rtcpMux;
}
public boolean isRtcpMux() {
return this.rtcpMux != null;
}
public void setRtcpMux(RtcpMuxAttribute rtcpMux) {
this.rtcpMux = rtcpMux;
}
public PacketTimeAttribute getPtime() {
return ptime;
}
public void setPtime(PacketTimeAttribute ptime) {
this.ptime = ptime;
}
public MaxPacketTimeAttribute getMaxptime() {
return maxptime;
}
public void setMaxptime(MaxPacketTimeAttribute maxptime) {
this.maxptime = maxptime;
}
public SsrcAttribute getSsrc() {
return ssrc;
}
public void setSsrc(SsrcAttribute ssrc) {
this.ssrc = ssrc;
}
public IceUfragAttribute getIceUfrag() {
if(this.iceUfrag == null && this.session != null) {
return this.session.getIceUfrag();
}
return this.iceUfrag;
}
public void setIceUfrag(IceUfragAttribute iceUfrag) {
this.iceUfrag = iceUfrag;
}
public IcePwdAttribute getIcePwd() {
if(this.icePwd == null && this.session != null) {
return this.session.getIcePwd();
}
return this.icePwd;
}
public void setIcePwd(IcePwdAttribute icePwd) {
this.icePwd = icePwd;
}
public CandidateAttribute[] getCandidates() {
if(this.candidates == null || this.candidates.isEmpty()) {
return null;
}
return candidates.toArray(new CandidateAttribute[this.candidates.size()]);
}
public boolean containsCandidates() {
return this.candidates != null && !this.candidates.isEmpty();
}
public void addCandidate(CandidateAttribute candidate) {
if(this.candidates == null) {
this.candidates = new ArrayList<CandidateAttribute>(8);
this.candidates.add(candidate);
} else if(!this.candidates.contains(candidate)) {
this.candidates.add(candidate);
}
}
public void removeCandidate(CandidateAttribute candidate) {
if(this.candidates != null) {
this.candidates.remove(candidate);
}
}
public void removeAllCandidates() {
if(this.candidates != null) {
this.candidates.clear();
}
}
public boolean containsIce() {
if(this.iceUfrag != null || this.icePwd != null || containsCandidates()) {
return true;
}
return false;
}
public FingerprintAttribute getFingerprint() {
if(this.fingerprint == null && this.session != null) {
return session.getFingerprint();
}
return fingerprint;
}
public void setFingerprint(FingerprintAttribute fingerprint) {
this.fingerprint = fingerprint;
}
public boolean containsDtls() {
return (this.fingerprint != null);
}
public SetupAttribute getSetup() {
if(this.setup == null && this.session != null) {
if(this.session.getSetup() != null) {
return this.session.getSetup();
}
}
return this.setup;
}
public void setSetup(SetupAttribute setup) {
this.setup = setup;
}
@Override
public char getFieldType() {
return FIELD_TYPE;
}
@Override
public String toString() {
// Clean builder
this.builder.setLength(0);
this.builder.append(BEGIN)
.append(this.media).append(" ")
.append(this.port).append(" ")
.append(this.protocol);
for (Integer payloadType : this.payloadTypes) {
this.builder.append(" ").append(payloadType);
}
appendField(this.connection);
appendField(this.connectionMode);
appendField(this.rtcp);
appendField(this.rtcpMux);
appendField(this.ptime);
appendField(this.maxptime);
appendField(this.iceUfrag);
appendField(this.icePwd);
if (this.candidates != null && !this.candidates.isEmpty()) {
for (CandidateAttribute candidate : this.candidates) {
appendField(candidate);
}
}
if (this.formats != null && !this.formats.isEmpty()) {
for (RtpMapAttribute format : this.formats.values()) {
appendField(format);
}
}
appendField(this.setup);
appendField(this.fingerprint);
appendField(this.ssrc);
return this.builder.toString();
}
private void appendField(SdpField field) {
if(field != null) {
this.builder.append(NEWLINE).append(field.toString());
}
}
public static boolean isValidProfile(String profile) {
return MediaProfile.containsProfile(profile);
}
}