From 05b37ae994b2e3ebcab654cbcc8ce231628c6d9d Mon Sep 17 00:00:00 2001 From: bgrozev Date: Mon, 17 Sep 2018 14:27:11 -0500 Subject: [PATCH] Add the server (videobridge) region to jingle (#316) * ref: Refactors the way extensions are added to Jingle packets. The Jingle operation set does not need to know about all of the extensions we support (and take their configuration as parameters). * feat: Adds the bridge region to session-initiate packets. * ref: Renames and moves jicofo's JingleUtils. * ref: Fixes docs and renames a method. --- .../ServerRegionPacketExtension.java | 81 +++++++++++++ .../java/org/jitsi/jicofo/LipSyncHack.java | 47 +++++--- .../jicofo/ParticipantChannelAllocator.java | 113 +++++++++++++----- .../xmpp/AbstractOperationSetJingle.java | 105 +++++++++------- .../protocol/xmpp/OperationSetJingle.java | 72 ++++++----- .../protocol/xmpp/util/JicofoJingleUtils.java | 70 +++++++++++ 6 files changed, 369 insertions(+), 119 deletions(-) create mode 100644 src/main/java/org/jitsi/impl/protocol/xmpp/extensions/ServerRegionPacketExtension.java create mode 100644 src/main/java/org/jitsi/protocol/xmpp/util/JicofoJingleUtils.java diff --git a/src/main/java/org/jitsi/impl/protocol/xmpp/extensions/ServerRegionPacketExtension.java b/src/main/java/org/jitsi/impl/protocol/xmpp/extensions/ServerRegionPacketExtension.java new file mode 100644 index 0000000000..d143467235 --- /dev/null +++ b/src/main/java/org/jitsi/impl/protocol/xmpp/extensions/ServerRegionPacketExtension.java @@ -0,0 +1,81 @@ +/* + * Jicofo, the Jitsi Conference Focus. + * + * Copyright @ 2018 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 org.jitsi.impl.protocol.xmpp.extensions; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.*; + +/** + * A packet extension which contains the server's region. + * + * @author Boris Grozev + */ +public class ServerRegionPacketExtension + extends AbstractPacketExtension +{ + /** + * The name of the {@code server-region} element. + */ + public static final String ELEMENT_NAME = "server-region"; + + /** + * The namespace for the {@code server-region} element. + */ + public static final String NAMESPACE = ConferenceIq.NAMESPACE; + + /** + * The name of the "region" attribute. + */ + public static final String REGION_ATTR_NAME = "region"; + + /** + * Creates an {@link AbstractPacketExtension} instance for the specified + * namespace and elementName. + * + * @param namespace the XML namespace for this element. + * @param elementName the name of the element + */ + protected ServerRegionPacketExtension(String namespace, + String elementName) + { + super(namespace, elementName); + } + + public ServerRegionPacketExtension(String region) + { + this(NAMESPACE, ELEMENT_NAME); + + setRegion(region); + } + + /** + * @return the region. + */ + public String getRegion() + { + return getAttributeAsString(REGION_ATTR_NAME); + } + + /** + * Sets the region. + * @param region the value to set. + */ + public void setRegion(String region) + { + setAttribute(REGION_ATTR_NAME, region); + } +} diff --git a/src/main/java/org/jitsi/jicofo/LipSyncHack.java b/src/main/java/org/jitsi/jicofo/LipSyncHack.java index aeb0b54915..09bb555786 100644 --- a/src/main/java/org/jitsi/jicofo/LipSyncHack.java +++ b/src/main/java/org/jitsi/jicofo/LipSyncHack.java @@ -243,17 +243,13 @@ private void processAllParticipantsSSRCs( */ @Override public boolean initiateSession( - boolean useBundle, - Jid address, - List contents, - JingleRequestHandler requestHandler, - boolean[] startMuted) + JingleIQ jingleIQ, + JingleRequestHandler requestHandler) throws OperationFailedException { - processAllParticipantsSSRCs(contents, address); + processAllParticipantsSSRCs(jingleIQ.getContentList(), jingleIQ.getTo()); - return jingleImpl.initiateSession( - useBundle, address, contents, requestHandler, startMuted); + return jingleImpl.initiateSession(jingleIQ, requestHandler); } /** @@ -263,17 +259,36 @@ public boolean initiateSession( * {@inheritDoc} */ @Override - public boolean replaceTransport( - boolean useBundle, - JingleSession session, - List contents, - boolean[] startMuted) + public boolean replaceTransport(JingleIQ jingleIQ, JingleSession session) throws OperationFailedException { - processAllParticipantsSSRCs(contents, session.getAddress()); + processAllParticipantsSSRCs( + jingleIQ.getContentList(), + session.getAddress()); + + return jingleImpl.replaceTransport(jingleIQ, session); + } - return jingleImpl.replaceTransport( - useBundle, session, contents, startMuted); + /** + * {@inheritDoc} + */ + @Override + public JingleIQ createTransportReplace( + JingleSession session, + List contents) + { + return jingleImpl.createTransportReplace(session, contents); + } + + /** + * {@inheritDoc} + */ + @Override + public JingleIQ createSessionInitiate( + Jid address, + List contents) + { + return jingleImpl.createSessionInitiate(address, contents); } /** diff --git a/src/main/java/org/jitsi/jicofo/ParticipantChannelAllocator.java b/src/main/java/org/jitsi/jicofo/ParticipantChannelAllocator.java index e09361c029..570164aebe 100644 --- a/src/main/java/org/jitsi/jicofo/ParticipantChannelAllocator.java +++ b/src/main/java/org/jitsi/jicofo/ParticipantChannelAllocator.java @@ -22,6 +22,7 @@ import net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet.*; import net.java.sip.communicator.impl.protocol.jabber.jinglesdp.*; import net.java.sip.communicator.service.protocol.*; +import org.jitsi.impl.protocol.xmpp.extensions.*; import org.jitsi.jicofo.discovery.*; import org.jitsi.jicofo.util.*; import org.jitsi.protocol.xmpp.*; @@ -76,6 +77,9 @@ public ParticipantChannelAllocator( this.logger = Logger.getLogger(classLogger, meetConference.getLogger()); } + /** + * {@inheritDoc} + */ @Override protected List createOffer() { @@ -130,6 +134,9 @@ protected List createOffer() return contents; } + /** + * {@inheritDoc} + */ @Override protected ColibriConferenceIQ doAllocateChannels( List offer) @@ -143,6 +150,9 @@ protected ColibriConferenceIQ doAllocateChannels( offer); } + /** + * {@inheritDoc} + */ @Override protected void invite(List offer) throws OperationFailedException @@ -179,38 +189,9 @@ else if (meetConference.findMember(address) == null) } else if (!canceled) { - OperationSetJingle jingle = meetConference.getJingle(); - boolean ack; - JingleSession jingleSession = participant.getJingleSession(); - if (!reInvite || jingleSession == null) - { - // will throw OperationFailedExc if XMPP connection is broken - ack = jingle.initiateSession( - participant.hasBundleSupport(), - address, - offer, - meetConference, - startMuted); - } - else + if (!doInviteOrReinvite(address, offer)) { - // will throw OperationFailedExc if XMPP connection is broken - ack = jingle.replaceTransport( - participant.hasBundleSupport(), - jingleSession, - offer, - startMuted); - } - if (!ack) - { - // Failed to invite - logger.info( - "Expiring " + address + " channels - no RESULT for " - + (reInvite ? "transport-replace" : "session-initiate")); expireChannels = true; - - // TODO: let meetConference know that our Jingle session failed, - // so it can either retry or remove the participant? } } @@ -233,6 +214,78 @@ else if (reInvite) } } + /** + * Invites or re-invites (based on the value of {@link #reInvite}) the + * {@code participant} to the jingle session. + * Creates and sends the appropriate Jingle IQ ({@code session-initiate} for + * and invite or {@code transport-replace} for a re-invite) and sends it to + * the {@code participant}. Blocks until a response is received or a timeout + * occurs. + * + * @param address the destination JID. + * @param contents the list of contents to include. + * @return {@code false} on failure. + * @throws OperationFailedException if we are unable to send a packet + * because the XMPP connection is broken. + */ + private boolean doInviteOrReinvite( + Jid address, List contents) + throws OperationFailedException + { + OperationSetJingle jingle = meetConference.getJingle(); + JingleSession jingleSession = participant.getJingleSession(); + boolean initiateSession = !reInvite || jingleSession == null; + boolean ack; + JingleIQ jingleIQ; + + if (initiateSession) + { + // will throw OperationFailedExc if XMPP connection is broken + jingleIQ = jingle.createSessionInitiate(address, contents); + } + else + { + jingleIQ = jingle.createTransportReplace(jingleSession, contents); + } + + if (participant.hasBundleSupport()) + { + JicofoJingleUtils.addBundleExtensions(jingleIQ); + } + if (startMuted[0] || startMuted[1]) + { + JicofoJingleUtils.addStartMutedExtension( + jingleIQ, startMuted[0], startMuted[1]); + } + String serverRegion = bridgeSession.bridge.getRegion(); + if (serverRegion != null) + { + jingleIQ.addExtension(new ServerRegionPacketExtension(serverRegion)); + } + + if (initiateSession) + { + ack = jingle.initiateSession(jingleIQ, meetConference); + } + else + { + // will throw OperationFailedExc if XMPP connection is broken + ack = jingle.replaceTransport(jingleIQ, jingleSession); + } + + if (!ack) + { + // Failed to invite + logger.info( + "Expiring " + address + " channels - no RESULT for " + + (initiateSession ? "session-initiate" + : "transport-replace")); + return false; + } + + return true; + } + /** * {@inheritDoc} */ diff --git a/src/main/java/org/jitsi/protocol/xmpp/AbstractOperationSetJingle.java b/src/main/java/org/jitsi/protocol/xmpp/AbstractOperationSetJingle.java index 2cd96083d4..6530cd0131 100644 --- a/src/main/java/org/jitsi/protocol/xmpp/AbstractOperationSetJingle.java +++ b/src/main/java/org/jitsi/protocol/xmpp/AbstractOperationSetJingle.java @@ -58,7 +58,7 @@ public abstract class AbstractOperationSetJingle protected AbstractOperationSetJingle() { super(JingleIQ.ELEMENT_NAME, JingleIQ.NAMESPACE, - IQ.Type.set, Mode.sync); + IQ.Type.set, Mode.sync); } @Override @@ -69,7 +69,9 @@ public IQ handleIQRequest(IQ iqRequest) if (session == null) { logger.error("No session found for SID " + packet.getSID()); - return IQ.createErrorResponse(packet, + return + IQ.createErrorResponse( + packet, XMPPError.getBuilder(XMPPError.Condition.bad_request)); } @@ -94,9 +96,8 @@ public IQ handleIQRequest(IQ iqRequest) * Finds Jingle session for given session identifier. * * @param sid the identifier of the session which we're looking for. - * - * @return Jingle session for given session identifier or null - * if no such session exists. + * @return Jingle session for given session identifier or null if + * no such session exists. */ public JingleSession getSession(String sid) { @@ -104,41 +105,44 @@ public JingleSession getSession(String sid) } /** - * Sends 'session-initiate' to the peer identified by given address - * - * @param useBundle true if invite IQ should include - * {@link GroupPacketExtension} - * @param address the XMPP address where 'session-initiate' will be sent. - * @param contents the list of ContentPacketExtension describing - * media offer. - * @param requestHandler JingleRequestHandler that will be used - * to process request related to newly created - * JingleSession. - * @param startMuted if the first element is true the participant - * will start audio muted. if the second element is true the - * participant will start video muted. * {@inheritDoc} */ @Override - public boolean initiateSession(boolean useBundle, - Jid address, - List contents, - JingleRequestHandler requestHandler, - boolean[] startMuted) - throws OperationFailedException + public JingleIQ createSessionInitiate( + Jid address, List contents) { - logger.info("INVITE PEER: " + address); - String sid = JingleIQ.generateSID(); - JingleSession session = new JingleSession(sid, address, requestHandler); + JingleIQ jingleIQ = new JingleIQ(JingleAction.SESSION_INITIATE, sid); - sessions.put(sid, session); + jingleIQ.setTo(address); + jingleIQ.setFrom(getOurJID()); + jingleIQ.setInitiator(getOurJID()); + jingleIQ.setType(IQ.Type.set); - JingleIQ inviteIQ - = createInviteIQ(JingleAction.SESSION_INITIATE, - sid, useBundle, address, contents, startMuted); + for (ContentPacketExtension content : contents) + { + jingleIQ.addContent(content); + } + + return jingleIQ; + } - IQ reply = (IQ) getConnection().sendPacketAndGetReply(inviteIQ); + /** + * {@inheritDoc} + */ + @Override + public boolean initiateSession( + JingleIQ inviteIQ, + JingleRequestHandler requestHandler) + throws OperationFailedException + { + String sid = inviteIQ.getSID(); + JingleSession session + = new JingleSession(sid, inviteIQ.getTo(), requestHandler); + + sessions.put(sid, session); + + IQ reply = getConnection().sendPacketAndGetReply(inviteIQ); return wasInviteAccepted(session, reply); } @@ -262,10 +266,32 @@ else if (IQ.Type.result.equals(reply.getType())) * {@inheritDoc} */ @Override - public boolean replaceTransport(boolean useBundle, - JingleSession session, - List contents, - boolean[] startMuted) + public JingleIQ createTransportReplace( + JingleSession session, List contents) + { + JingleIQ jingleIQ + = new JingleIQ( + JingleAction.TRANSPORT_REPLACE, session.getSessionID()); + jingleIQ.setTo(session.getAddress()); + jingleIQ.setFrom(getOurJID()); + jingleIQ.setInitiator(getOurJID()); + jingleIQ.setType(IQ.Type.set); + + for (ContentPacketExtension content : contents) + { + jingleIQ.addContent(content); + } + + return jingleIQ; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean replaceTransport( + JingleIQ jingleIQ, + JingleSession session) throws OperationFailedException { Jid address = session.getAddress(); @@ -281,12 +307,7 @@ public boolean replaceTransport(boolean useBundle, // Reset 'accepted' flag on the session session.setAccepted(false); - JingleIQ inviteIQ - = createInviteIQ(JingleAction.TRANSPORT_REPLACE, - session.getSessionID(), useBundle, address, - contents, startMuted); - - IQ reply = getConnection().sendPacketAndGetReply(inviteIQ); + IQ reply = getConnection().sendPacketAndGetReply(jingleIQ); return wasInviteAccepted(session, reply); } diff --git a/src/main/java/org/jitsi/protocol/xmpp/OperationSetJingle.java b/src/main/java/org/jitsi/protocol/xmpp/OperationSetJingle.java index d459dd5533..59c52fe4ae 100644 --- a/src/main/java/org/jitsi/protocol/xmpp/OperationSetJingle.java +++ b/src/main/java/org/jitsi/protocol/xmpp/OperationSetJingle.java @@ -36,58 +36,68 @@ public interface OperationSetJingle extends OperationSet { /** - * Start new session by sending 'session-initiate' IQ to given XMPP address. + * Initiates a Jingle session by sending the provided + * {@code session-initiate} IQ. Blocks until a response is received or + * until a timeout is reached. * - * @param useBundle true if contents description in the IQ sent - * should contain additional signaling required for RTP - * bundle usage in Jitsi Meet. - * @param address the XMPP address that will be remote destination of new - * Jingle session. - * @param contents media contents description of our offer. + * @param jingleIQ the IQ to send. * @param requestHandler JingleRequestHandler that will be bound - * to new Jingle session instance. - * @param startMuted if the first element is true the participant - * will start audio muted. if the second element is true the - * participant will start video muted. + * to new Jingle session instance. * - * @return true if have have received RESULT response to - * session-initiate IQ. + * @return {@code true} if a response of type {@code result} is received + * before the timeout. * * @throws OperationFailedException with * {@link OperationFailedException#PROVIDER_NOT_REGISTERED} if the operation * fails, because the XMPP connection is broken. */ boolean initiateSession( - boolean useBundle, - Jid address, - List contents, - JingleRequestHandler requestHandler, - boolean[] startMuted) + JingleIQ jingleIQ, + JingleRequestHandler requestHandler) throws OperationFailedException; /** - * Sends 'transport-replace' IQ to the client. + * Creates a {@code session-initiate} IQ for a specific address and adds + * a list of {@link ContentPacketExtension} to it. * - * @param useBundle true if bundled transport is being used or - * false otherwise - * @param session the JingleSession used to send the notification. - * @param contents the list of Jingle contents which describes the actual - * offer - * @param startMuted an array where the first value stands for "start with - * audio muted" and the seconds one for "start video muted" + * @param address the destination JID. + * @param contents the list of contents to add. + * + * @return the IQ which was created. + */ + JingleIQ createSessionInitiate( + Jid address, + List contents); + + /** + * Sends a 'transport-replace' IQ to the client. Blocks waiting for a + * response and returns {@code true} if a response with type {@code result} + * is received before a certain timeout. * - * @return true if have have received RESULT response to the IQ. + * @param jingleIQ the IQ which to be sent. + * @param session the JingleSession for which the IQ will be sent. + * + * @return {@code true} if an IQ of type {@code result} is received. * * @throws OperationFailedException with * {@link OperationFailedException#PROVIDER_NOT_REGISTERED} if the operation * fails, because the XMPP connection is broken. */ - boolean replaceTransport(boolean useBundle, - JingleSession session, - List contents, - boolean[] startMuted) + boolean replaceTransport(JingleIQ jingleIQ, JingleSession session) throws OperationFailedException; + /** + * Creates a {@code transport-replace} packet for a particular + * {@link JingleSession}. + * + * @param session the {@link JingleSession}. + * @param contents the list of {@code content}s to include. + * @return the IQ which was created. + */ + JingleIQ createTransportReplace( + JingleSession session, + List contents); + /** * Sends 'source-add' proprietary notification. * diff --git a/src/main/java/org/jitsi/protocol/xmpp/util/JicofoJingleUtils.java b/src/main/java/org/jitsi/protocol/xmpp/util/JicofoJingleUtils.java new file mode 100644 index 0000000000..9544a6e6e4 --- /dev/null +++ b/src/main/java/org/jitsi/protocol/xmpp/util/JicofoJingleUtils.java @@ -0,0 +1,70 @@ +/* + * Jicofo, the Jitsi Conference Focus. + * + * Copyright @ 2018 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 org.jitsi.protocol.xmpp.util; + +import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.jitsimeet.*; +import org.jitsi.impl.protocol.xmpp.extensions.*; + +/** + * Jicofo specific utilities for Jingle. + * + * @author Boris Grozev + */ +public class JicofoJingleUtils +{ + /** + * Adds a group packet extension to a {@link JingleIQ}, and a + * {@link BundlePacketExtension} to each of its contents. I.e. adds + * everything that we deem necessary to enable {@code bundle} in an offer. + * It is unclear how much of this is actually necessary for + * {@code jitsi-meet}. + * + * @param jingleIQ the IQ to add extensions to. + */ + public static void addBundleExtensions(JingleIQ jingleIQ) + { + GroupPacketExtension group + = GroupPacketExtension.createBundleGroup(jingleIQ.getContentList()); + + jingleIQ.addExtension(group); + + for (ContentPacketExtension content : jingleIQ.getContentList()) + { + // FIXME: is it mandatory ? + // http://estos.de/ns/bundle + content.addChildExtension(new BundlePacketExtension()); + } + } + + /** + * Adds a {@link StartMutedPacketExtension} to a specific {@link JingleIQ}. + * @param jingleIQ the {@link JingleIQ} to add extensions to. + * @param audioMute the value to set for the {@code audio} attribute. + * @param videoMute the value to set for the {@code video} attribute. + */ + public static void addStartMutedExtension( + JingleIQ jingleIQ, boolean audioMute, boolean videoMute) + { + StartMutedPacketExtension startMutedExt + = new StartMutedPacketExtension(); + startMutedExt.setAudioMute(audioMute); + startMutedExt.setVideoMute(videoMute); + jingleIQ.addExtension(startMutedExt); + } +}