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); + } +}