/* * 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.impl.protocol.jabber.extensions.colibri; import java.util.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import org.jitsi.util.*; import org.jitsi.service.neomedia.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.packet.IQ; /** * Implements the Jitsi Videobridge conference IQ within the * COnferencing with LIghtweight BRIdging. * * @author Lyubomir Marinov * @author Boris Grozev * @author George Politis */ public class ColibriConferenceIQ extends IQ { /** * The XML element name of the Jitsi Videobridge conference IQ. */ public static final String ELEMENT_NAME = "conference"; /** * The XML name of the id attribute of the Jitsi Videobridge * conference IQ which represents the value of the id * property of ColibriConferenceIQ. */ public static final String ID_ATTR_NAME = "id"; /** * The XML name of the name attribute of the Jitsi Videobridge * conference IQ which represents the value of the name * property of ColibriConferenceIQ if available. */ public static final String NAME_ATTR_NAME = "name"; /** * The XML COnferencing with LIghtweight BRIdging namespace of the Jitsi * Videobridge conference IQ. */ public static final String NAMESPACE = "http://jitsi.org/protocol/colibri"; /** * The logger instance used by this class. */ private final static Logger logger = Logger.getLogger(ColibriConferenceIQ.class); /** * An array of ints which represents the lack of any (RTP) SSRCs * seen/received on a Channel. Explicitly defined to reduce * unnecessary allocations. */ public static final int[] NO_SSRCS = new int[0]; /** * The list of {@link ChannelBundle}s included into this conference * IQ. */ private final List channelBundles = new LinkedList(); /** * The list of {@link Content}s included into this conference IQ. */ private final List contents = new LinkedList(); /** * The list of Endpoints included into this conference IQ. */ private final List endpoints = new LinkedList(); /** * The ID of the conference represented by this IQ. */ private String id; /** * Media recording. */ private Recording recording; private RTCPTerminationStrategy rtcpTerminationStrategy; /** * Indicates if the information about graceful shutdown status is being * carried by this IQ. */ private boolean gracefulShutdown; /** * World readable name for the conference. */ private String name; /** * Returns an error response for given IQ that is returned by * the videobridge after it has entered graceful shutdown mode and new * conferences can no longer be created. * * @param request the IQ for which error response will be created. * @return an IQ of 'error' type and 'service-unavailable' condition plus * the body of request IQ. */ public static IQ createGracefulShutdownErrorResponse(final IQ request) { final XMPPError error = new XMPPError(XMPPError.Condition.service_unavailable); error.addExtension(new GracefulShutdown()); final IQ result = IQ.createErrorResponse(request, error); result.setType(Type.ERROR); result.setPacketID(request.getPacketID()); result.setFrom(request.getTo()); result.setTo(request.getFrom()); result.setError(error); return result; } /** Initializes a new ColibriConferenceIQ instance. */ public ColibriConferenceIQ() { } /** * Adds a specific {@link Content} instance to the list of Content * instances included into this conference IQ. * @param channelBundle the ChannelBundle to add. */ public boolean addChannelBundle(ChannelBundle channelBundle) { if (channelBundle == null) throw new NullPointerException("channelBundle"); return channelBundles.contains(channelBundles) ? false : channelBundles.add(channelBundle); } /** * Adds a specific {@link Content} instance to the list of Content * instances included into this conference IQ. * * @param content the Content instance to be added to this list of * Content instances included into this conference IQ * @return true if the list of Content instances included * into this conference IQ has been modified as a result of the * method call; otherwise, false * @throws NullPointerException if the specified content is * null */ public boolean addContent(Content content) { if (content == null) throw new NullPointerException("content"); return contents.contains(content) ? false : contents.add(content); } /** * Initializes a new {@link Content} instance with a specific name and adds * it to the list of Content instances included into this * conference IQ. * * @param contentName the name which which the new Content instance * is to be initialized * @return true if the list of Content instances included * into this conference IQ has been modified as a result of the * method call; otherwise, false */ public boolean addContent(String contentName) { return addContent(new Content(contentName)); } /** * Add an Endpoint to this ColibriConferenceIQ. * @param endpoint the Endpoint to add. */ public void addEndpoint(Endpoint endpoint) { endpoints.add(endpoint); } /** * Returns a list of the ChannelBundles included into this * conference IQ. * * @return an unmodifiable List of the ChannelBundles * included into this conference IQ. */ public List getChannelBundles() { return Collections.unmodifiableList(channelBundles); } /** * Finds {@link ChannelBundle} identified by given bundleId. * @param bundleId ChannelBundle identifier. * @return {@link ChannelBundle} identified by given bundleId or * null if not found. */ public ChannelBundle getChannelBundle(String bundleId) { if (bundleId == null) { return null; } for (ChannelBundle bundle : channelBundles) { if (bundleId.equals(bundle.getId())) { return bundle; } } return null; } /** * Returns an XML String representation of this IQ. * * @return an XML String representation of this IQ */ @Override public String getChildElementXML() { StringBuilder xml = new StringBuilder(); xml.append('<').append(ELEMENT_NAME); xml.append(" xmlns='").append(NAMESPACE).append('\''); String id = getID(); if (id != null) xml.append(' ').append(ID_ATTR_NAME).append("='").append(id) .append('\''); if (name != null) xml.append(' ').append(NAME_ATTR_NAME).append("='").append(name) .append('\''); List contents = getContents(); List channelBundles = getChannelBundles(); boolean hasChildren = (recording != null) || (rtcpTerminationStrategy != null) || (gracefulShutdown) || (contents.size() > 0) || (channelBundles.size() > 0); if (!hasChildren) { xml.append(" />"); } else { xml.append('>'); for (Content content : contents) content.toXML(xml); for (ChannelBundle channelBundle : channelBundles) channelBundle.toXML(xml); if (recording != null) recording.toXML(xml); if (rtcpTerminationStrategy != null) rtcpTerminationStrategy.toXML(xml); if (gracefulShutdown) xml.append(new GracefulShutdown().toXML()); xml.append("'); } return xml.toString(); } /** * Returns a Content from the list of Contents of this * conference IQ which has a specific name. If no such * Content exists, returns null. * * @param contentName the name of the Content to be returned * @return a Content from the list of Contents of this * conference IQ which has the specified contentName if * such a Content exists; otherwise, null */ public Content getContent(String contentName) { for (Content content : getContents()) { if (contentName.equals(content.getName())) return content; } return null; } /** * Returns a list of the Contents included into this * conference IQ. * * @return an unmodifiable List of the Contents included * into this conference IQ */ public List getContents() { return Collections.unmodifiableList(contents); } /** * Returns the list of Endpoints included in this * ColibriConferenceIQ. * @return the list of Endpoints included in this * ColibriConferenceIQ. */ public List getEndpoints() { return Collections.unmodifiableList(endpoints); } /** * Gets the ID of the conference represented by this IQ. * * @return the ID of the conference represented by this IQ */ public String getID() { return id; } /** * Returns a Content from the list of Contents of this * conference IQ which has a specific name. If no such * Content exists at the time of the invocation of the method, * initializes a new Content instance with the specified * contentName and includes it into this conference IQ. * * @param contentName the name of the Content to be returned * @return a Content from the list of Contents of this * conference IQ which has the specified contentName */ public Content getOrCreateContent(String contentName) { Content content = getContent(contentName); if (content == null) { content = new Content(contentName); addContent(content); } return content; } /** * Gets the value of the recording field. * @return the value of the recording field. */ public Recording getRecording() { return recording; } public RTCPTerminationStrategy getRTCPTerminationStrategy() { return rtcpTerminationStrategy; } /** * Removes a specific {@link Content} instance from the list of * Content instances included into this conference IQ. * * @param content the Content instance to be removed from the list * of Content instances included into this conference IQ * @return true if the list of Content instances included * into this conference IQ has been modified as a result of the * method call; otherwise, false */ public boolean removeContent(Content content) { return contents.remove(content); } /** * Sets the ID of the conference represented by this IQ. * * @param id the ID of the conference represented by this IQ */ public void setID(String id) { this.id = id; } /** * Sets the recording field. * @param recording the value to set. */ public void setRecording(Recording recording) { this.recording = recording; } public void setRTCPTerminationStrategy( RTCPTerminationStrategy rtcpTerminationStrategy) { this.rtcpTerminationStrategy = rtcpTerminationStrategy; } /** * Sets whether this IQ should contain the information about graceful * shutdown in progress status. * * @param isGracefulShutdown true if graceful shutdown status * should be indicated in this IQ. */ public void setGracefulShutdown(boolean isGracefulShutdown) { this.gracefulShutdown = isGracefulShutdown; } /** * Returns true if graceful shutdown status info is indicated in * this ColibriConferenceIQ instance. */ public boolean isGracefulShutdown() { return gracefulShutdown; } /** * The world readable name of the conference. * @return name of the conference. */ public String getName() { return name; } /** * Sets name. * @param name the name to set. */ public void setName(String name) { this.name = name; } /** * Represents a channel included into a content of a Jitsi * Videobridge conference IQ. */ public static class Channel extends ChannelCommon { /** * The name of the XML attribute of a channel which represents * its direction. */ public static final String DIRECTION_ATTR_NAME = "direction"; /** * The XML element name of a channel of a content of a * Jitsi Videobridge conference IQ. */ public static final String ELEMENT_NAME = "channel"; /** * The XML name of the host attribute of a channel of * a content of a conference IQ which represents the * value of the host property of * ColibriConferenceIQ.Channel. * * @deprecated The attribute is supported for the purposes of * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public static final String HOST_ATTR_NAME = "host"; /** * The XML name of the last-n attribute of a video * channel which specifies the maximum number of video RTP * streams to be sent from Jitsi Videobridge to the endpoint associated * with the video channel. The value of the last-n * attribute is a positive number. */ public static final String LAST_N_ATTR_NAME = "last-n"; /** * The XML name of the adaptive-last-n attribute of a video * channel. */ public static final String ADAPTIVE_LAST_N_ATTR_NAME = "adaptive-last-n"; /** * The XML name of the adaptive-simulcast attribute of a video * channel. */ public static final String ADAPTIVE_SIMULCAST_ATTR_NAME = "adaptive-simulcast"; /** * The XML name of the simulcast-mode attribute of a video * channel. */ public static final String SIMULCAST_MODE_ATTR_NAME = "simulcast-mode"; /** * The XML name of the receive-simulcast-layer attribute of a * video Channel which specifies the target quality of the * simulcast substreams to be sent from Jitsi Videobridge to the * endpoint associated with the video Channel. The value of the * receive-simulcast-layer attribute is an unsigned integer. * Typically used for debugging purposes. */ public static final String RECEIVING_SIMULCAST_LAYER = "receive-simulcast-layer"; /** * The XML name of the rtcpport attribute of a channel * of a content of a conference IQ which represents * the value of the rtcpPort property of * ColibriConferenceIQ.Channel. * * @deprecated The attribute is supported for the purposes of * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public static final String RTCP_PORT_ATTR_NAME = "rtcpport"; public static final String RTP_LEVEL_RELAY_TYPE_ATTR_NAME = "rtp-level-relay-type"; /** * The XML name of the rtpport attribute of a channel * of a content of a conference IQ which represents * the value of the rtpPort property of * ColibriConferenceIQ.Channel. * * @deprecated The attribute is supported for the purposes of * compatibility with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public static final String RTP_PORT_ATTR_NAME = "rtpport"; /** * The name of the XML element which is a child of the <channel> * element and which identifies/specifies an (RTP) SSRC which has been * seen/received on the respective Channel. */ public static final String SSRC_ELEMENT_NAME = "ssrc"; /** * The direction of the channel represented by this instance. */ private MediaDirection direction; /** * The host of the channel represented by this instance. * * @deprecated The field is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated private String host; /** * The maximum number of video RTP streams to be sent from Jitsi * Videobridge to the endpoint associated with this video * Channel. */ private Integer lastN; /** * The 'adaptive-last-n' flag. */ private Boolean adaptiveLastN; /** * The 'adaptive-simulcast' flag. */ private Boolean adaptiveSimulcast; /** * The 'simulcast-mode' flag. */ private SimulcastMode simulcastMode; /** * The payload-type elements defined by XEP-0167: Jingle RTP * Sessions associated with this channel. */ private final List payloadTypes = new ArrayList(); /** * The rtp-hdrext elements defined by XEP-0294: Jingle RTP * Header Extensions Negotiation associated with this channel. */ private final Map rtpHeaderExtensions = new HashMap(); /** * The target quality of the simulcast substreams to be sent from Jitsi * Videobridge to the endpoint associated with this video * Channel. */ private Integer receivingSimulcastLayer; /** * The RTCP port of the channel represented by this instance. * * @deprecated The field is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated private int rtcpPort; /** * The type of RTP-level relay (in the terms specified by RFC 3550 * "RTP: A Transport Protocol for Real-Time Applications" in * section 2.3 "Mixers and Translators") used for this * Channel. */ private RTPLevelRelayType rtpLevelRelayType; /** * The RTP port of the channel represented by this instance. * * @deprecated The field is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated private int rtpPort; /** * The SourceGroupPacketExtensions of this channel. */ private List sourceGroups; /** * The SourcePacketExtensions of this channel. */ private final List sources = new LinkedList(); /** * The list of (RTP) SSRCs which have been seen/received on this * Channel by now. These may exclude SSRCs which are no longer * active. Set by the Jitsi Videobridge server, not its clients. */ private int[] ssrcs = NO_SSRCS; /** Initializes a new Channel instance. */ public Channel() { super(Channel.ELEMENT_NAME); } /** * Adds a payload-type element defined by XEP-0167: Jingle RTP * Sessions to this channel. * * @param payloadType the payload-type element to be added to * this channel * @return true if the list of payload-type elements * associated with this channel has been modified as part of * the method call; otherwise, false * @throws NullPointerException if the specified payloadType is * null */ public boolean addPayloadType(PayloadTypePacketExtension payloadType) { if (payloadType == null) throw new NullPointerException("payloadType"); // Make sure that the COLIBRI namespace is used. payloadType.setNamespace(null); for (ParameterPacketExtension p : payloadType.getParameters()) p.setNamespace(null); return payloadTypes.contains(payloadType) ? false : payloadTypes.add(payloadType); } /** * Adds an rtp-hdrext element defined by XEP-0294: Jingle RTP * Header Extensions Negotiation to this Channel. * * @param ext the payload-type element to be added to * this channel * @return true if the list of rtp-hdrext elements * associated with this channel has been modified as part of * the method call; otherwise, false * @throws NullPointerException if the specified ext is * null */ public void addRtpHeaderExtension(RTPHdrExtPacketExtension ext) { if (ext == null) throw new NullPointerException("payloadType"); // Create a new instance, because we are going to modify the NS RTPHdrExtPacketExtension newExt = new RTPHdrExtPacketExtension(ext); // Make sure that the parent namespace (COLIBRI) is used. newExt.setNamespace(null); int id = -1; try { id = Integer.valueOf(newExt.getID()); } catch (NumberFormatException nfe) {} // Only accept valid extension IDs (4-bits, 0xF reserved) if (id < 0 || id > 14) { logger.warn("Failed to add an RTP header extension element " + "with an invalid ID: " + newExt.getID()); return; } rtpHeaderExtensions.put(id, newExt); } /** * Adds a SourcePacketExtension to the list of sources of this * channel. * * @param source the SourcePacketExtension to add to the list * of sources of this channel * @return true if the list of sources of this channel changed * as a result of the execution of the method; otherwise, false */ public synchronized boolean addSource(SourcePacketExtension source) { if (source == null) throw new NullPointerException("source"); return sources.contains(source) ? false : sources.add(source); } /** * Adds a SourceGroupPacketExtension to the list of source * groups of this channel. * * @param sourceGroup the SourcePacketExtension to add to the * list of sources of this channel * * @return true if the list of sources of this channel changed * as a result of the execution of the method; otherwise, false */ public synchronized boolean addSourceGroup( SourceGroupPacketExtension sourceGroup) { if (sourceGroup == null) throw new NullPointerException("sourceGroup"); if (sourceGroups == null) sourceGroups = new LinkedList(); return sourceGroups.contains(sourceGroup) ? false : sourceGroups.add(sourceGroup); } /** * Adds a specific (RTP) SSRC to the list of SSRCs seen/received on this * Channel. Invoked by the Jitsi Videobridge server, not its * clients. * * @param ssrc the (RTP) SSRC to be added to the list of SSRCs * seen/received on this Channel * @return true if the list of SSRCs seen/received on this * Channel has been modified as part of the method call; * otherwise, false */ public synchronized boolean addSSRC(int ssrc) { // contains for (int i = 0; i < ssrcs.length; i++) { if (ssrcs[i] == ssrc) return false; } // add int[] newSSRCs = new int[ssrcs.length + 1]; System.arraycopy(ssrcs, 0, newSSRCs, 0, ssrcs.length); newSSRCs[ssrcs.length] = ssrc; ssrcs = newSSRCs; return true; } /** * Gets the direction of this Channel. * * @return the direction of this Channel. */ public MediaDirection getDirection() { return (direction == null) ? MediaDirection.SENDRECV : direction; } /** * Gets the IP address (as a String value) of the host on which * the channel represented by this instance has been allocated. * * @return a String value which represents the IP address of * the host on which the channel represented by this instance * has been allocated * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public String getHost() { return host; } /** * Gets the maximum number of video RTP streams to be sent from Jitsi * Videobridge to the endpoint associated with this video * Channel. * * @return the maximum number of video RTP streams to be sent from Jitsi * Videobridge to the endpoint associated with this video * Channel */ public Integer getLastN() { return lastN; } /** * Gets the value of the 'adaptive-last-n' flag. * @return the value of the 'adaptive-last-n' flag. */ public Boolean getAdaptiveLastN() { return adaptiveLastN; } /** * Gets the value of the 'adaptive-simulcast' flag. * @return the value of the 'adaptive-simulcast' flag. */ public Boolean getAdaptiveSimulcast() { return adaptiveSimulcast; } /** * Gets the value of the 'simulcast-mode' flag. * @return the value of the 'simulcast-mode' flag. */ public SimulcastMode getSimulcastMode() { return simulcastMode; } /** * Gets a list of payload-type elements defined by XEP-0167: * Jingle RTP Sessions added to this channel. * * @return an unmodifiable List of payload-type * elements defined by XEP-0167: Jingle RTP Sessions added to this * channel */ public List getPayloadTypes() { return Collections.unmodifiableList(payloadTypes); } /** * Gets a list of rtp-hdrext elements defined by XEP-0294: * Jingle RTP Header Extensions Negotiation added to this * channel. * * @return an unmodifiable List of rtp-hdrext * elements defined by XEP-0294: Jingle RTP Header Extensions * Negotiation added to this channel */ public Collection getRtpHeaderExtensions() { return Collections .unmodifiableCollection(rtpHeaderExtensions.values()); } /** * Gets the target quality of the simulcast substreams to be sent from * Jitsi Videobridge to the endpoint associated with this video * Channel. * * @return the target quality of the simulcast substreams to be sent * from Jitsi Videobridge to the endpoint associated with this video * Channel. */ public Integer getReceivingSimulcastLayer() { return receivingSimulcastLayer; } /** * Gets the port which has been allocated to this channel for * the purposes of transmitting RTCP packets. * * @return the port which has been allocated to this channel * for the purposes of transmitting RTCP packets * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public int getRTCPPort() { return rtcpPort; } /** * Gets the type of RTP-level relay (in the terms specified by RFC 3550 * "RTP: A Transport Protocol for Real-Time Applications" in * section 2.3 "Mixers and Translators") used for this * Channel. * * @return the type of RTP-level relay used for this Channel */ public RTPLevelRelayType getRTPLevelRelayType() { return rtpLevelRelayType; } /** * Gets the port which has been allocated to this channel for * the purposes of transmitting RTP packets. * * @return the port which has been allocated to this channel * for the purposes of transmitting RTP packets * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public int getRTPPort() { return rtpPort; } /** * Gets the list of SourceGroupPacketExtensionss which * represent the source groups of this channel. * * @return a List of SourceGroupPacketExtensions which * represent the source groups of this channel */ public synchronized List getSourceGroups() { return (sourceGroups == null) ? null : new ArrayList(sourceGroups); } /** * Gets the list of SourcePacketExtensionss which represent the * sources of this channel. * * @return a List of SourcePacketExtensions which * represent the sources of this channel */ public synchronized List getSources() { return new ArrayList(sources); } /** * Gets (a copy of) the list of (RTP) SSRCs seen/received on this * Channel. * * @return an array of ints which represents (a copy of) the * list of (RTP) SSRCs seen/received on this Channel */ public synchronized int[] getSSRCs() { return (ssrcs.length == 0) ? NO_SSRCS : ssrcs.clone(); } @Override protected boolean hasContent() { List payloadTypes = getPayloadTypes(); if (!payloadTypes.isEmpty()) return true; List sourceGroups = getSourceGroups(); if (sourceGroups != null && !getSourceGroups().isEmpty()) return true; List sources = getSources(); if (!sources.isEmpty()) return true; int[] ssrcs = getSSRCs(); return (ssrcs.length != 0); } @Override protected void printAttributes(StringBuilder xml) { // direction MediaDirection direction = getDirection(); if ((direction != null) && (direction != MediaDirection.SENDRECV)) { xml.append(' ').append(DIRECTION_ATTR_NAME).append("='") .append(direction.toString()).append('\''); } // host String host = getHost(); if (host != null) { xml.append(' ').append(HOST_ATTR_NAME).append("='").append(host) .append('\''); } // lastN Integer lastN = getLastN(); if (lastN != null) { xml.append(' ').append(LAST_N_ATTR_NAME).append("='") .append(lastN).append('\''); } // simulcastMode SimulcastMode simulcastMode = getSimulcastMode(); if (simulcastMode != null) { xml.append(' ').append(SIMULCAST_MODE_ATTR_NAME).append("='") .append(simulcastMode).append('\''); } // rtcpPort int rtcpPort = getRTCPPort(); if (rtcpPort > 0) { xml.append(' ').append(RTCP_PORT_ATTR_NAME).append("='") .append(rtcpPort).append('\''); } // rtpLevelRelayType RTPLevelRelayType rtpLevelRelayType = getRTPLevelRelayType(); if (rtpLevelRelayType != null) { xml.append(' ').append(RTP_LEVEL_RELAY_TYPE_ATTR_NAME) .append("='").append(rtpLevelRelayType).append('\''); } // rtpPort int rtpPort = getRTPPort(); if (rtpPort > 0) { xml.append(' ').append(RTP_PORT_ATTR_NAME).append("='") .append(rtpPort).append('\''); } } @Override protected void printContent(StringBuilder xml) { List payloadTypes = getPayloadTypes(); Collection rtpHdrExtPacketExtensions = getRtpHeaderExtensions(); List sources = getSources(); List sourceGroups = getSourceGroups(); int[] ssrcs = getSSRCs(); for (PayloadTypePacketExtension payloadType : payloadTypes) xml.append(payloadType.toXML()); for (RTPHdrExtPacketExtension ext : rtpHdrExtPacketExtensions) xml.append(ext.toXML()); for (SourcePacketExtension source : sources) xml.append(source.toXML()); if (sourceGroups != null && sourceGroups.size() != 0) for (SourceGroupPacketExtension sourceGroup : sourceGroups) xml.append(sourceGroup.toXML()); for (int i = 0; i < ssrcs.length; i++) { xml.append('<').append(SSRC_ELEMENT_NAME).append('>') .append(Long.toString(ssrcs[i] & 0xFFFFFFFFL)) .append("'); } } /** * Removes a payload-type element defined by XEP-0167: Jingle * RTP Sessions from this channel. * * @param payloadType the payload-type element to be removed * from this channel * @return true if the list of payload-type elements * associated with this channel has been modified as part of * the method call; otherwise, false */ public boolean removePayloadType(PayloadTypePacketExtension payloadType) { return payloadTypes.remove(payloadType); } /** * Removes a rtp-hdrext element defined by XEP-0294: Jingle * RTP Header Extensions Negotiation from this channel. * * @param ext the rtp-hdrext element to be removed * from this channel * @return true if the list of rtp-hdrext elements * associated with this channel has been modified as part of * the method call; otherwise, false */ public void removeRtpHeaderExtension(RTPHdrExtPacketExtension ext) { int id = -1; try { id = Integer.valueOf(ext.getID()); } catch (NumberFormatException nfe) { logger.warn("Invalid ID: " + ext.getID()); return; } rtpHeaderExtensions.remove(id); } /** * Removes a SourcePacketExtension from the list of sources of * this channel. * * @param source the SourcePacketExtension to remove from the * list of sources of this channel * @return true if the list of sources of this channel changed * as a result of the execution of the method; otherwise, false */ public synchronized boolean removeSource(SourcePacketExtension source) { return sources.remove(source); } /** * Removes a specific (RTP) SSRC from the list of SSRCs seen/received on * this Channel. Invoked by the Jitsi Videobridge server, not * its clients. * * @param ssrc the (RTP) SSRC to be removed from the list of SSRCs * seen/received on this Channel * @return true if the list of SSRCs seen/received on this * Channel has been modified as part of the method call; * otherwise, false */ public synchronized boolean removeSSRC(int ssrc) { if (ssrcs.length == 1) { if (ssrcs[0] == ssrc) { ssrcs = NO_SSRCS; return true; } else return false; } else { for (int i = 0; i < ssrcs.length; i++) { if (ssrcs[i] == ssrc) { int[] newSSRCs = new int[ssrcs.length - 1]; if (i != 0) System.arraycopy(ssrcs, 0, newSSRCs, 0, i); if (i != newSSRCs.length) { System.arraycopy( ssrcs, i + 1, newSSRCs, i, newSSRCs.length - i); } ssrcs = newSSRCs; return true; } } return false; } } /** * Sets the direction of this Channel * * @param direction the MediaDirection to set the * direction of this Channel to. */ public void setDirection(MediaDirection direction) { this.direction = direction; } /** * Sets the IP address (as a String value) of the host on which * the channel represented by this instance has been allocated. * * @param host a String value which represents the IP address * of the host on which the channel represented by this * instance has been allocated * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public void setHost(String host) { this.host = host; } /** * Sets the maximum number of video RTP streams to be sent from Jitsi * Videobridge to the endpoint associated with this video * Channel. * * @param lastN the maximum number of video RTP streams to be sent from * Jitsi Videobridge to the endpoint associated with this video * Channel */ public void setLastN(Integer lastN) { this.lastN = lastN; } /** * Sets the value of the 'adaptive-last-n' flag. * @param adaptiveLastN the value to set. */ public void setAdaptiveLastN(Boolean adaptiveLastN) { this.adaptiveLastN = adaptiveLastN; } /** * Sets the value of the 'adaptive-simulcast' flag. * @param adaptiveSimulcast the value to set. */ public void setAdaptiveSimulcast(Boolean adaptiveSimulcast) { this.adaptiveSimulcast = adaptiveSimulcast; } /** * Sets the value of the 'simulcast-mode' flag. * @param simulcastMode the value to set. */ public void setSimulcastMode(SimulcastMode simulcastMode) { this.simulcastMode = simulcastMode; } /** * Sets the target quality of the simulcast substreams to be sent from * Jitsi Videobridge to the endpoint associated with this video * Channel. * * @param simulcastLayer the target quality of the simulcast substreams * to be sent from Jitsi Videobridge to the endpoint associated with * this video Channel. */ public void setReceivingSimulcastLayer(Integer simulcastLayer) { this.receivingSimulcastLayer = simulcastLayer; } /** * Sets the port which has been allocated to this channel for * the purposes of transmitting RTCP packets. * * @param rtcpPort the port which has been allocated to this * channel for the purposes of transmitting RTCP packets * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public void setRTCPPort(int rtcpPort) { this.rtcpPort = rtcpPort; } /** * Sets the type of RTP-level relay (in the terms specified by RFC 3550 * "RTP: A Transport Protocol for Real-Time Applications" in * section 2.3 "Mixers and Translators") used for this * Channel. * * @param rtpLevelRelayType the type of RTP-level relay used for * this Channel */ public void setRTPLevelRelayType(RTPLevelRelayType rtpLevelRelayType) { this.rtpLevelRelayType = rtpLevelRelayType; } /** * Sets the type of RTP-level relay (in the terms specified by RFC 3550 * "RTP: A Transport Protocol for Real-Time Applications" in * section 2.3 "Mixers and Translators") used for this * Channel. * * @param s the type of RTP-level relay used for this Channel */ public void setRTPLevelRelayType(String s) { setRTPLevelRelayType(RTPLevelRelayType.parseRTPLevelRelayType(s)); } /** * Sets the port which has been allocated to this channel for * the purposes of transmitting RTP packets. * * @param rtpPort the port which has been allocated to this * channel for the purposes of transmitting RTP packets * * @deprecated The method is supported for the purposes of compatibility * with legacy versions of Jitsi and Jitsi Videobridge. */ @Deprecated public void setRTPPort(int rtpPort) { this.rtpPort = rtpPort; } /** * Sets the list of (RTP) SSRCs seen/received on this Channel. * * @param ssrcs the list of (RTP) SSRCs to be set as seen/received on * this Channel */ public void setSSRCs(int[] ssrcs) { /* * TODO Make sure that the SSRCs set on this instance do not contain * duplicates. */ this.ssrcs = ((ssrcs == null) || (ssrcs.length == 0)) ? NO_SSRCS : ssrcs.clone(); } } /** * Represents a "channel-bundle" element. */ public static class ChannelBundle { /** * The name of the "channel-bundle" element. */ public static final String ELEMENT_NAME = "channel-bundle"; /** * The name of the "id" attribute. */ public static final String ID_ATTR_NAME = "id"; /** * The ID of this ChannelBundle. */ private String id; /** * The transport element of this ChannelBundle. */ private IceUdpTransportPacketExtension transport; /** * Initializes a new ChannelBundle with the given ID. * @param id the ID. */ public ChannelBundle(String id) { this.id = id; } /** * Returns the ID of this ChannelBundle. * @return the ID of this ChannelBundle. */ public String getId() { return id; } /** * Returns the transport element of this ChannelBundle. * @return the transport element of this ChannelBundle. */ public IceUdpTransportPacketExtension getTransport() { return transport; } /** * Sets the ID of this ChannelBundle. * @param id the ID to set. */ public void setId(String id) { this.id = id; } /** * Sets the transport element of this ChannelBundle. * @param transport the transport to set. */ public void setTransport(IceUdpTransportPacketExtension transport) { this.transport = transport; } /** * Appends an XML representation of this ChannelBundle to * xml. * @param xml the StringBuilder to append to. */ public void toXML(StringBuilder xml) { xml.append('<').append(ELEMENT_NAME).append(' ') .append(ID_ATTR_NAME).append("='").append(id).append('\''); if (transport != null) { xml.append('>'); xml.append(transport.toXML()); xml.append("'); } else { xml.append(" />"); } } } /** * Class contains common code for both Channel and * SctpConnection IQ classes. * * @author Pawel Domas */ public static abstract class ChannelCommon { /** * The name of the "channel-bundle-id" attribute. */ public static final String CHANNEL_BUNDLE_ID_ATTR_NAME = "channel-bundle-id"; /** * The XML name of the endpoint attribute which specifies the * optional identifier of the endpoint of the conference participant * associated with a channel. The value of the * endpoint attribute is an opaque String from the * point of view of Jitsi Videobridge. */ public static final String ENDPOINT_ATTR_NAME = "endpoint"; /** * The XML name of the expire attribute of a channel * of a content of a conference IQ which represents * the value of the expire property of * ColibriConferenceIQ.Channel. */ public static final String EXPIRE_ATTR_NAME = "expire"; /** * The value of the expire property of * ColibriConferenceIQ.Channel which indicates that no actual * value has been specified for the property in question. */ public static final int EXPIRE_NOT_SPECIFIED = -1; /** * The XML name of the id attribute of a channel of a * content of a conference IQ which represents the * value of the id property of * ColibriConferenceIQ.Channel. */ public static final String ID_ATTR_NAME = "id"; /** * The XML name of the initiator attribute of a * channel of a content of a conference IQ * which represents the value of the initiator property of * ColibriConferenceIQ.Channel. */ public static final String INITIATOR_ATTR_NAME = "initiator"; /** * The channel-bundle-id attribute of this CommonChannel. */ private String channelBundleId = null; /** * XML element name. */ private String elementName; /** * The identifier of the endpoint of the conference participant * associated with this Channel. */ private String endpoint; /** * The number of seconds of inactivity after which the channel * represented by this instance expires. */ private int expire = EXPIRE_NOT_SPECIFIED; /** * The ID of the channel represented by this instance. */ private String id; /** * The indicator which determines whether the conference focus is the * initiator/offerer (as opposed to the responder/answerer) of the media * negotiation associated with this instance. */ private Boolean initiator; private IceUdpTransportPacketExtension transport; /** * Initializes this class with given XML elementName. * @param elementName XML element name to be used for producing XML * representation of derived IQ class. */ protected ChannelCommon(String elementName) { this.elementName = elementName; } /** * Get the channel-bundle-id attribute of this CommonChannel. * @return the channel-bundle-id attribute of this * CommonChannel. */ public String getChannelBundleId() { return channelBundleId; } /** * Gets the identifier of the endpoint of the conference participant * associated with this Channel. * * @return the identifier of the endpoint of the conference participant * associated with this Channel */ public String getEndpoint() { return endpoint; } /** * Gets the number of seconds of inactivity after which the * channel represented by this instance expires. * * @return the number of seconds of inactivity after which the * channel represented by this instance expires */ public int getExpire() { return expire; } /** * Gets the ID of the channel represented by this instance. * * @return the ID of the channel represented by this instance */ public String getID() { return id; } public IceUdpTransportPacketExtension getTransport() { return transport; } /** * Indicates whether there are some contents that should be printed as * child elements of this IQ. If true is returned * {@link #printContent(StringBuilder)} method will be called when * XML representation of this IQ is being constructed. * @return true if there are content to be printed as child * elements of this IQ or false otherwise. */ protected abstract boolean hasContent(); /** * Gets the indicator which determines whether the conference focus is * the initiator/offerer (as opposed to the responder/answerer) of the * media negotiation associated with this instance. * * @return {@link Boolean#TRUE} if the conference focus is the * initiator/offerer of the media negotiation associated with this * instance, {@link Boolean#FALSE} if the conference focus is the * responder/answerer or null if the initiator state * is unspecified */ public Boolean isInitiator() { return initiator; } /** * Derived class implements this method in order to print additional * attributes to main XML element. * @param xml StringBuilder to which the XML * String representation of this Channel * is to be appended */ protected abstract void printAttributes(StringBuilder xml); /** * Implement in order to print content child elements of this IQ using * given StringBuilder. Called during construction of XML * representation if {@link #hasContent()} returns true. * * @param xml the StringBuilder to which the XML * String representation of this Channel * is to be appended. */ protected abstract void printContent(StringBuilder xml); /** * Sets the channel-bundle-id attribute of this CommonChannel. * @param channelBundleId the value to set. */ public void setChannelBundleId(String channelBundleId) { this.channelBundleId = channelBundleId; } /** * Sets the identifier of the endpoint of the conference participant * associated with this Channel. * * @param endpoint the identifier of the endpoint of the conference * participant associated with this Channel */ public void setEndpoint(String endpoint) { this.endpoint = endpoint; } /** * Sets the number of seconds of inactivity after which the * channel represented by this instance expires. * * @param expire the number of seconds of activity after which the * channel represented by this instance expires * @throws IllegalArgumentException if the value of the specified * expire is other than {@link #EXPIRE_NOT_SPECIFIED} and * negative */ public void setExpire(int expire) { if ((expire != EXPIRE_NOT_SPECIFIED) && (expire < 0)) throw new IllegalArgumentException("expire"); this.expire = expire; } /* * Sets the ID of the channel represented by this instance. * * @param id the ID of the channel represented by this instance */ public void setID(String id) { this.id = id; } /** * Sets the indicator which determines whether the conference focus is * the initiator/offerer (as opposed to the responder/answerer) of the * media negotiation associated with this instance. * * @param initiator {@link Boolean#TRUE} if the conference focus is the * initiator/offerer of the media negotiation associated with this * instance, {@link Boolean#FALSE} if the conference focus is the * responder/answerer or null if the initiator state * is to be unspecified */ public void setInitiator(Boolean initiator) { this.initiator = initiator; } public void setTransport(IceUdpTransportPacketExtension transport) { this.transport = transport; } /** * Appends the XML String representation of this * Channel to a specific StringBuilder. * * @param xml the StringBuilder to which the XML * String representation of this Channel is to be * appended */ public void toXML(StringBuilder xml) { xml.append('<').append(elementName); // endpoint String endpoint = getEndpoint(); if (endpoint != null) { xml.append(' ').append(ENDPOINT_ATTR_NAME).append("='") .append(endpoint).append('\''); } // expire int expire = getExpire(); if (expire >= 0) { xml.append(' ').append(EXPIRE_ATTR_NAME).append("='") .append(expire).append('\''); } // id String id = getID(); if (id != null) { xml.append(' ').append(ID_ATTR_NAME).append("='") .append(id).append('\''); } // initiator Boolean initiator = isInitiator(); if (initiator != null) { xml.append(' ').append(INITIATOR_ATTR_NAME).append("='") .append(initiator).append('\''); } String channelBundleId = getChannelBundleId(); if (channelBundleId != null) { xml.append(' ').append(CHANNEL_BUNDLE_ID_ATTR_NAME) .append("='").append(channelBundleId).append('\''); } // Print derived class attributes printAttributes(xml); IceUdpTransportPacketExtension transport = getTransport(); boolean hasTransport = (transport != null); if (hasTransport || hasContent()) { xml.append('>'); if(hasContent()) printContent(xml); if (hasTransport) xml.append(transport.toXML()); xml.append("'); } else { xml.append(" />"); } } } /** * Represents a content included into a Jitsi Videobridge * conference IQ. */ public static class Content { /** * The XML element name of a content of a Jitsi Videobridge * conference IQ. */ public static final String ELEMENT_NAME = "content"; /** * The XML name of the name attribute of a content of * a conference IQ which represents the name property * of ColibriConferenceIQ.Content. */ public static final String NAME_ATTR_NAME = "name"; /** * The list of {@link Channel}s included into this content of a * conference IQ. */ private final List channels = new LinkedList(); /** * The name of the content represented by this instance. */ private String name; /** * The list of {@link SctpConnection}s included into this * content of a conference IQ. */ private final List sctpConnections = new LinkedList(); /** * Initializes a new Content instance without a name and * channels. */ public Content() { } /** * Initializes a new Content instance with a specific name and * without channels. * * @param name the name to initialize the new instance with */ public Content(String name) { setName(name); } /** * Adds a specific Channel to the list of Channels * included into this Content. * * @param channel the Channel to be included into this * Content * @return true if the list of Channels included into * this Content was modified as a result of the execution of * the method; otherwise, false * @throws NullPointerException if the specified channel is * null */ public boolean addChannel(Channel channel) { if (channel == null) throw new NullPointerException("channel"); return channels.contains(channel) ? false : channels.add(channel); } /** * Adds a specific SctpConnection to the list of * SctpConnections included into this Content. * * @param conn the SctpConnection to be included into this * Content * @return true if the list of SctpConnections * included into this Content was modified as a result of * the execution of the method; otherwise, false * @throws NullPointerException if the specified conn is * null */ public boolean addSctpConnection(SctpConnection conn) { if(conn == null) throw new NullPointerException("Sctp connection"); return !sctpConnections.contains(conn) && sctpConnections.add(conn); } /** * Gets the Channel at a specific index/position within the * list of Channels included in this Content. * * @param channelIndex the index/position within the list of * Channels included in this Content of the * Channel to be returned * @return the Channel at the specified channelIndex * within the list of Channels included in this * Content */ public Channel getChannel(int channelIndex) { return getChannels().get(channelIndex); } /** * Gets a Channel which is included into this Content * and which has a specific ID. * * @param channelID the ID of the Channel included into this * Content to be returned * @return the Channel which is included into this * Content and which has the specified channelID if * such a Channel exists; otherwise, null */ public Channel getChannel(String channelID) { for (Channel channel : getChannels()) { if (channelID.equals(channel.getID())) return channel; } return null; } /** * Finds an SCTP connection identified by given connectionID. * @param connectionID the ID of the SCTP connection to find. * @return SctpConnection instance identified by given ID * or null if no such connection is contained in * this IQ. */ public SctpConnection getSctpConnection(String connectionID) { for (SctpConnection conn : getSctpConnections()) if (connectionID.equals(conn.getID())) return conn; return null; } /** * Gets the number of Channels included into/associated with * this Content. * * @return the number of Channels included into/associated with * this Content */ public int getChannelCount() { return getChannels().size(); } /** * Gets a list of the Channel included into/associated with * this Content. * * @return an unmodifiable List of the Channels * included into/associated with this Content */ public List getChannels() { return Collections.unmodifiableList(channels); } /** * Gets the name of the content represented by this instance. * * @return the name of the content represented by this instance */ public String getName() { return name; } /** * Gets a list of the SctpConnections included into/associated * with this Content. * * @return an unmodifiable List of the SctpConnections * included into/associated with this Content */ public List getSctpConnections() { return Collections.unmodifiableList(sctpConnections); } /** * Removes a specific Channel from the list of * Channels included into this Content. * * @param channel the Channel to be excluded from this * Content * @return true if the list of Channels included into * this Content was modified as a result of the execution of * the method; otherwise, false */ public boolean removeChannel(Channel channel) { return channels.remove(channel); } /** * Sets the name of the content represented by this instance. * * @param name the name of the content represented by this * instance * @throws NullPointerException if the specified name is * null */ public void setName(String name) { if (name == null) throw new NullPointerException("name"); this.name = name; } /** * Appends the XML String representation of this * Content to a specific StringBuilder. * * @param xml the StringBuilder to which the XML * String representation of this Content is to be * appended */ public void toXML(StringBuilder xml) { xml.append('<').append(ELEMENT_NAME); xml.append(' ').append(NAME_ATTR_NAME).append("='") .append(getName()).append('\''); List channels = getChannels(); List connections = getSctpConnections(); if (channels.size() == 0 && connections.size() == 0) { xml.append(" />"); } else { xml.append('>'); for (Channel channel : channels) channel.toXML(xml); for(SctpConnection conn : connections) conn.toXML(xml); xml.append("'); } } /** * Removes given SCTP connection from this IQ. * @param connection the SCTP connection instance to be removed. * @return true if given connection was contained in * this IQ and has been removed successfully. */ public boolean removeSctpConnection(SctpConnection connection) { return sctpConnections.remove(connection); } } /** * Represents an 'endpoint' element. */ public static class Endpoint { /** * The name of the 'displayname' attribute. */ public static final String DISPLAYNAME_ATTR_NAME = "displayname"; /** * The name of the 'endpoint' element. */ public static final String ELEMENT_NAME = "endpoint"; /** * The name of the 'id' attribute. */ public static final String ID_ATTR_NAME = "id"; /** * The 'display name' of this Endpoint. */ private String displayName; /** * The 'id' of this Endpoint. */ private String id; /** * Initializes a new Endpoint with the given ID and display * name. * @param id the ID. * @param displayName the display name. */ public Endpoint(String id, String displayName) { this.id = id; this.displayName = displayName; } /** * Returns the display name of this Endpoint. * @return the display name of this Endpoint. */ public String getDisplayName() { return displayName; } /** * Returns the ID of this Endpoint. * @return the ID of this Endpoint. */ public String getId() { return id; } /** * Sets the display name of this Endpoint. * @param displayName the display name to set. */ public void setDisplayName(String displayName) { this.displayName = displayName; } /** * Sets the ID of this Endpoint. * @param id the ID to set. */ public void setId(String id) { this.id = id; } } /** * Represents a recording element. */ public static class Recording { /** * The XML name of the recording element. */ public static final String ELEMENT_NAME = "recording"; /** * The XML name of the path attribute. */ public static final String DIRECTORY_ATTR_NAME = "directory"; /** * The XML name of the state attribute. */ public static final String STATE_ATTR_NAME = "state"; /** * The XML name of the token attribute. */ public static final String TOKEN_ATTR_NAME = "token"; /** * The target directory. */ private String directory; /** * State of the recording.. */ private State state; /** * Access token. */ private String token; /** * Construct new recording element. * @param state the state as string */ public Recording(String state) { this.state = State.parseString(state); } /** * Construct new recording element. * @param state */ public Recording(State state) { this.state = state; } /** * Construct new recording element. * @param state the state as string * @param token the token to authenticate */ public Recording(String state, String token) { this(State.parseString(state), token); } /** * Construct new recording element. * @param state the state * @param token the token to authenticate */ public Recording(State state, String token) { this(state); this.token = token; } public String getDirectory() { return directory; } public State getState() { return state; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public void setDirectory(String directory) { this.directory = directory; } public void toXML(StringBuilder xml) { xml.append('<').append(ELEMENT_NAME); xml.append(' ').append(STATE_ATTR_NAME).append("='") .append(state).append('\''); if (token != null) { xml.append(' ').append(TOKEN_ATTR_NAME).append("='") .append(token).append('\''); } if (directory != null) { xml.append(' ').append(DIRECTORY_ATTR_NAME).append("='") .append(directory).append('\''); } xml.append("/>"); } /** * The recording state. */ public enum State { /** * Recording is started. */ ON("on"), /** * Recording is stopped. */ OFF("off"), /** * Recording is pending. Record has been requested but no conference * has been established and it will be started once this is done. */ PENDING("pending"); /** * The name. */ private String name; /** * Constructs new state. * @param name */ private State(String name) { this.name = name; } /** * Returns state name. * @return returns state name. */ public String toString() { return name; } /** * Parses state. * @param s state name. * @return the state found. */ public static State parseString(String s) { if (ON.toString().equalsIgnoreCase(s)) return ON; else if (PENDING.toString().equalsIgnoreCase(s)) return PENDING; return OFF; } } } /** * Packet extension indicating graceful shutdown in progress status. */ public static class GracefulShutdown extends AbstractPacketExtension { public static final String ELEMENT_NAME = "graceful-shutdown"; public static final String NAMESPACE = ColibriConferenceIQ.NAMESPACE; public GracefulShutdown() { super(ColibriConferenceIQ.NAMESPACE, ELEMENT_NAME); } } public static class RTCPTerminationStrategy { public static final String ELEMENT_NAME = "rtcp-termination-strategy"; public static final String NAME_ATTR_NAME = "name"; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void toXML(StringBuilder xml) { xml.append('<').append(ELEMENT_NAME); xml.append(' ').append(NAME_ATTR_NAME).append("='") .append(name).append('\''); xml.append("/>"); } } /** * Represents a SCTP connection included into a content * of a Jitsi Videobridge conference IQ. * * @author Pawel Domas */ public static class SctpConnection extends ChannelCommon { /** * The XML element name of a content of a Jitsi Videobridge * conference IQ. */ public static final String ELEMENT_NAME = "sctpconnection"; /** * The XML name of the port attribute of a * SctpConnection of a conference IQ which represents * the SCTP port property of * ColibriConferenceIQ.SctpConnection. */ public static final String PORT_ATTR_NAME = "port"; /** * SCTP port attribute. 5000 by default. */ private int port = 5000; /** * Initializes a new SctpConnection instance without an * endpoint name and with default port value set. */ public SctpConnection() { super(SctpConnection.ELEMENT_NAME); } /** * Gets the SCTP port of the SctpConnection described by this * instance. * * @return the SCTP port of the SctpConnection represented by * this instance. */ public int getPort() { return port; } /** * {@inheritDoc} * * No content other than transport for SctpConnection. */ @Override protected boolean hasContent() { return false; } /** * {@inheritDoc} */ @Override protected void printAttributes(StringBuilder xml) { xml.append(' ').append(PORT_ATTR_NAME).append("='") .append(getPort()).append('\''); } @Override protected void printContent(StringBuilder xml) { // No other content than the transport shared from ChannelCommon } /** * Sets the SCTP port of the SctpConnection represented by this * instance. * * @param port the SCTP port of the SctpConnection * represented by this instance */ public void setPort(int port) { this.port = port; } } }