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