/* * 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; 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.fields.AttributeField; import org.restcomm.media.sdp.fields.ConnectionField; import org.restcomm.media.sdp.fields.MediaDescriptionField; import org.restcomm.media.sdp.fields.OriginField; import org.restcomm.media.sdp.fields.SessionNameField; import org.restcomm.media.sdp.fields.TimingField; import org.restcomm.media.sdp.fields.VersionField; import org.restcomm.media.sdp.format.AVProfile; import org.restcomm.media.sdp.format.RTPFormat; import org.restcomm.media.sdp.ice.attributes.CandidateAttribute; import org.restcomm.media.sdp.ice.attributes.IceLiteAttribute; 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; /** * Parses an SDP text description into a {@link SessionDescription} object. * * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class SessionDescriptionParser { private static final String NEWLINE = "\n"; private static final String PARSE_ERROR = "Cannot parse SDP: "; private static final String PARSE_ERROR_EMPTY = PARSE_ERROR + "empty"; private static final SdpParserPipeline PARSERS = new SdpParserPipeline(); public static SessionDescription parse(String text) throws SdpException { if (text == null || text.isEmpty()) { throw new SdpException(PARSE_ERROR_EMPTY); } SdpParsingInfo info = new SdpParsingInfo(); // Process each line of SDP String[] lines = text.split(NEWLINE); for (String line : lines) { try { // Get type of field to check whether its an sdp attribute or // not char fieldType = line.charAt(0); switch (fieldType) { case AttributeField.FIELD_TYPE: // The field is an attribute // Get the type of attribute so we can invoke the right // parser int separator = line .indexOf(AttributeField.ATTRIBUTE_SEPARATOR); String attributeType = (separator == -1) ? line .substring(2).trim() : line.substring(2, separator); // Get the right parser for the attribute and parse the text SdpParser<? extends AttributeField> attributeParser = PARSERS .getAttributeParser(attributeType); if (attributeParser != null) { convertAndApplyAttribute(attributeParser.parse(line), info); } break; default: // Get the right parser for the field and parse the text SdpParser<? extends SdpField> fieldParser = PARSERS .getFieldParser(fieldType); if (fieldParser != null) { convertAndApplyField(fieldParser.parse(line), info); } break; } } catch (Exception e) { throw new SdpException("Could not parse SDP: " + line, e); } } return info.sdp; } private static void convertAndApplyField(SdpField field, SdpParsingInfo info) { switch (field.getFieldType()) { case VersionField.FIELD_TYPE: info.sdp.setVersion((VersionField) field); break; case OriginField.FIELD_TYPE: info.sdp.setOrigin((OriginField) field); break; case SessionNameField.FIELD_TYPE: info.sdp.setSessionName((SessionNameField) field); break; case TimingField.FIELD_TYPE: info.sdp.setTiming((TimingField) field); break; case ConnectionField.FIELD_TYPE: if (info.media == null) { info.sdp.setConnection((ConnectionField) field); } else { info.media.setConnection((ConnectionField) field); } break; case MediaDescriptionField.FIELD_TYPE: info.media = (MediaDescriptionField) field; info.sdp.addMediaDescription(info.media); info.media.setSession(info.sdp); break; default: // Ignore unsupported type break; } } private static void convertAndApplyAttribute(AttributeField attribute, SdpParsingInfo info) { switch (attribute.getKey()) { case RtpMapAttribute.ATTRIBUTE_TYPE: info.format = (RtpMapAttribute) attribute; info.media.addFormat(info.format); break; case FormatParameterAttribute.ATTRIBUTE_TYPE: FormatParameterAttribute fmtp = (FormatParameterAttribute) attribute; if(info.format == null) { // Format unspecified on SDP. Load it manually RTPFormat format = AVProfile.getFormat(fmtp.getFormat()); if(format == null) { // Unsupported codec. Drop it. break; } else { info.format = new RtpMapAttribute(format.getID(), format.getFormat().getName().toString(), format.getClockRate(), RtpMapAttribute.DEFAULT_CODEC_PARAMS); } } info.format.setParameters((FormatParameterAttribute) attribute); break; case PacketTimeAttribute.ATTRIBUTE_TYPE: info.media.setPtime((PacketTimeAttribute) attribute); break; case MaxPacketTimeAttribute.ATTRIBUTE_TYPE: info.media.setMaxptime((MaxPacketTimeAttribute) attribute); break; case ConnectionModeAttribute.SENDONLY: case ConnectionModeAttribute.RECVONLY: case ConnectionModeAttribute.SENDRECV: case ConnectionModeAttribute.INACTIVE: if (info.media == null) { info.sdp.setConnectionMode((ConnectionModeAttribute) attribute); } else { info.media .setConnectionMode((ConnectionModeAttribute) attribute); } break; case RtcpAttribute.ATTRIBUTE_TYPE: info.media.setRtcp((RtcpAttribute) attribute); break; case RtcpMuxAttribute.ATTRIBUTE_TYPE: info.media.setRtcpMux((RtcpMuxAttribute) attribute); break; case IceLiteAttribute.ATTRIBUTE_TYPE: info.sdp.setIceLite((IceLiteAttribute) attribute); break; case IceUfragAttribute.ATTRIBUTE_TYPE: if (info.media == null) { info.sdp.setIceUfrag((IceUfragAttribute) attribute); } else { info.media.setIceUfrag((IceUfragAttribute) attribute); } break; case IcePwdAttribute.ATTRIBUTE_TYPE: if (info.media == null) { info.sdp.setIcePwd((IcePwdAttribute) attribute); } else { info.media.setIcePwd((IcePwdAttribute) attribute); } break; case CandidateAttribute.ATTRIBUTE_TYPE: info.media.addCandidate((CandidateAttribute) attribute); break; case SetupAttribute.ATTRIBUTE_TYPE: if (info.media == null) { info.sdp.setSetup((SetupAttribute) attribute); } else { info.media.setSetup((SetupAttribute) attribute); } break; case FingerprintAttribute.ATTRIBUTE_TYPE: if (info.media == null) { info.sdp.setFingerprint((FingerprintAttribute) attribute); } else { info.media.setFingerprint((FingerprintAttribute) attribute); } break; case SsrcAttribute.ATTRIBUTE_TYPE: SsrcAttribute ssrc = (SsrcAttribute) attribute; SsrcAttribute ssrcAttribute = info.media.getSsrc(); if (ssrcAttribute == null) { info.media.setSsrc(ssrc); } else { ssrcAttribute.addAttribute(ssrc.getLastAttribute(), ssrc.getLastValue()); } break; default: break; } } private static class SdpParsingInfo { final SessionDescription sdp; MediaDescriptionField media; RtpMapAttribute format; public SdpParsingInfo() { this.sdp = new SessionDescription(); } } }