take senders attr into account when converting to and from sdp
This commit is contained in:
parent
59ea66ca78
commit
304205b2e3
|
@ -1272,7 +1272,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
descriptionTransportBuilder.put(
|
descriptionTransportBuilder.put(
|
||||||
content.getKey(),
|
content.getKey(),
|
||||||
new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo)
|
new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(
|
return Futures.immediateFuture(
|
||||||
|
@ -1306,7 +1306,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
omemoVerification.setOrEnsureEqual(decryptedTransport);
|
omemoVerification.setOrEnsureEqual(decryptedTransport);
|
||||||
descriptionTransportBuilder.put(
|
descriptionTransportBuilder.put(
|
||||||
content.getKey(),
|
content.getKey(),
|
||||||
new RtpContentMap.DescriptionTransport(descriptionTransport.description, decryptedTransport.payload)
|
new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
processPostponed();
|
processPostponed();
|
||||||
|
|
|
@ -577,8 +577,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
|
|
||||||
private void sendInitRequest() {
|
private void sendInitRequest() {
|
||||||
final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INITIATE);
|
final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INITIATE);
|
||||||
final Content content = new Content(this.contentCreator, this.contentName);
|
final Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL && remoteSupportsOmemoJet) {
|
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL && remoteSupportsOmemoJet) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": remote announced support for JET");
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": remote announced support for JET");
|
||||||
final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
|
final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
|
||||||
|
@ -656,8 +655,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
gatherAndConnectDirectCandidates();
|
gatherAndConnectDirectCandidates();
|
||||||
this.jingleConnectionManager.getPrimaryCandidate(this.id.account, isInitiator(), (success, candidate) -> {
|
this.jingleConnectionManager.getPrimaryCandidate(this.id.account, isInitiator(), (success, candidate) -> {
|
||||||
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT);
|
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT);
|
||||||
final Content content = new Content(contentCreator, contentName);
|
final Content content = new Content(contentCreator, contentSenders, contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setDescription(this.description);
|
content.setDescription(this.description);
|
||||||
if (success && candidate != null && !equalCandidateExists(candidate)) {
|
if (success && candidate != null && !equalCandidateExists(candidate)) {
|
||||||
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
|
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
|
||||||
|
@ -696,8 +694,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
private void sendAcceptIbb() {
|
private void sendAcceptIbb() {
|
||||||
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
this.transport = new JingleInBandTransport(this, this.transportId, this.ibbBlockSize);
|
||||||
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT);
|
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.SESSION_ACCEPT);
|
||||||
final Content content = new Content(contentCreator, contentName);
|
final Content content = new Content(contentCreator, contentSenders, contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setDescription(this.description);
|
content.setDescription(this.description);
|
||||||
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
|
@ -910,8 +907,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
private void sendFallbackToIbb() {
|
private void sendFallbackToIbb() {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending fallback to ibb");
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending fallback to ibb");
|
||||||
final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.TRANSPORT_REPLACE);
|
final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.TRANSPORT_REPLACE);
|
||||||
final Content content = new Content(this.contentCreator, this.contentName);
|
final Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
this.transportId = JingleConnectionManager.nextRandomId();
|
this.transportId = JingleConnectionManager.nextRandomId();
|
||||||
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
|
@ -944,8 +940,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
|
|
||||||
final JinglePacket answer = bootstrapPacket(JinglePacket.Action.TRANSPORT_ACCEPT);
|
final JinglePacket answer = bootstrapPacket(JinglePacket.Action.TRANSPORT_ACCEPT);
|
||||||
|
|
||||||
final Content content = new Content(contentCreator, contentName);
|
final Content content = new Content(contentCreator, contentSenders, contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
content.setTransport(new IbbTransportInfo(this.transportId, this.ibbBlockSize));
|
||||||
answer.addJingleContent(content);
|
answer.addJingleContent(content);
|
||||||
|
|
||||||
|
@ -1124,8 +1119,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
|
|
||||||
private void sendProxyActivated(String cid) {
|
private void sendProxyActivated(String cid) {
|
||||||
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
||||||
final Content content = new Content(this.contentCreator, this.contentName);
|
final Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setTransport(new S5BTransportInfo(this.transportId, new Element("activated").setAttribute("cid", cid)));
|
content.setTransport(new S5BTransportInfo(this.transportId, new Element("activated").setAttribute("cid", cid)));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
|
@ -1133,8 +1127,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
|
|
||||||
private void sendProxyError() {
|
private void sendProxyError() {
|
||||||
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
final JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
||||||
final Content content = new Content(this.contentCreator, this.contentName);
|
final Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setTransport(new S5BTransportInfo(this.transportId, new Element("proxy-error")));
|
content.setTransport(new S5BTransportInfo(this.transportId, new Element("proxy-error")));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
this.sendJinglePacket(packet);
|
this.sendJinglePacket(packet);
|
||||||
|
@ -1142,8 +1135,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
|
|
||||||
private void sendCandidateUsed(final String cid) {
|
private void sendCandidateUsed(final String cid) {
|
||||||
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
||||||
final Content content = new Content(this.contentCreator, this.contentName);
|
final Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-used").setAttribute("cid", cid)));
|
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-used").setAttribute("cid", cid)));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
this.sentCandidate = true;
|
this.sentCandidate = true;
|
||||||
|
@ -1156,8 +1148,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
private void sendCandidateError() {
|
private void sendCandidateError() {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending candidate error");
|
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": sending candidate error");
|
||||||
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
JinglePacket packet = bootstrapPacket(JinglePacket.Action.TRANSPORT_INFO);
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentSenders, this.contentName);
|
||||||
content.setSenders(this.contentSenders);
|
|
||||||
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-error")));
|
content.setTransport(new S5BTransportInfo(this.transportId, new Element("candidate-error")));
|
||||||
packet.addJingleContent(content);
|
packet.addJingleContent(content);
|
||||||
this.sentCandidate = true;
|
this.sentCandidate = true;
|
||||||
|
|
|
@ -425,7 +425,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
final RtpContentMap restartContentMap,
|
final RtpContentMap restartContentMap,
|
||||||
final boolean isOffer)
|
final boolean isOffer)
|
||||||
throws ExecutionException, InterruptedException {
|
throws ExecutionException, InterruptedException {
|
||||||
final SessionDescription sessionDescription = SessionDescription.of(restartContentMap);
|
final SessionDescription sessionDescription = SessionDescription.of(restartContentMap, !isInitiator());
|
||||||
final org.webrtc.SessionDescription.Type type =
|
final org.webrtc.SessionDescription.Type type =
|
||||||
isOffer
|
isOffer
|
||||||
? org.webrtc.SessionDescription.Type.OFFER
|
? org.webrtc.SessionDescription.Type.OFFER
|
||||||
|
@ -444,7 +444,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
if (isOffer) {
|
if (isOffer) {
|
||||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(false);
|
webRTCWrapper.setIsReadyToReceiveIceCandidates(false);
|
||||||
final SessionDescription localSessionDescription = setLocalSessionDescription();
|
final SessionDescription localSessionDescription = setLocalSessionDescription();
|
||||||
setLocalContentMap(RtpContentMap.of(localSessionDescription));
|
setLocalContentMap(RtpContentMap.of(localSessionDescription, isInitiator()));
|
||||||
// We need to respond OK before sending any candidates
|
// We need to respond OK before sending any candidates
|
||||||
respondOk(jinglePacket);
|
respondOk(jinglePacket);
|
||||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||||
|
@ -726,7 +726,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
this.storePeerDtlsSetup(contentMap.getDtlsSetup());
|
this.storePeerDtlsSetup(contentMap.getDtlsSetup());
|
||||||
final SessionDescription sessionDescription;
|
final SessionDescription sessionDescription;
|
||||||
try {
|
try {
|
||||||
sessionDescription = SessionDescription.of(contentMap);
|
sessionDescription = SessionDescription.of(contentMap, false);
|
||||||
} catch (final IllegalArgumentException | NullPointerException e) {
|
} catch (final IllegalArgumentException | NullPointerException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -763,7 +763,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
final SessionDescription offer;
|
final SessionDescription offer;
|
||||||
try {
|
try {
|
||||||
offer = SessionDescription.of(rtpContentMap);
|
offer = SessionDescription.of(rtpContentMap, true);
|
||||||
} catch (final IllegalArgumentException | NullPointerException e) {
|
} catch (final IllegalArgumentException | NullPointerException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -838,7 +838,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
final org.webrtc.SessionDescription webRTCSessionDescription) {
|
final org.webrtc.SessionDescription webRTCSessionDescription) {
|
||||||
final SessionDescription sessionDescription =
|
final SessionDescription sessionDescription =
|
||||||
SessionDescription.parse(webRTCSessionDescription.description);
|
SessionDescription.parse(webRTCSessionDescription.description);
|
||||||
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
|
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription, false);
|
||||||
this.responderRtpContentMap = respondingRtpContentMap;
|
this.responderRtpContentMap = respondingRtpContentMap;
|
||||||
storePeerDtlsSetup(respondingRtpContentMap.getDtlsSetup().flip());
|
storePeerDtlsSetup(respondingRtpContentMap.getDtlsSetup().flip());
|
||||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||||
|
@ -1289,7 +1289,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) {
|
final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) {
|
||||||
final SessionDescription sessionDescription =
|
final SessionDescription sessionDescription =
|
||||||
SessionDescription.parse(webRTCSessionDescription.description);
|
SessionDescription.parse(webRTCSessionDescription.description);
|
||||||
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription, true);
|
||||||
this.initiatorRtpContentMap = rtpContentMap;
|
this.initiatorRtpContentMap = rtpContentMap;
|
||||||
//TODO delay ready to receive ice until after session-init
|
//TODO delay ready to receive ice until after session-init
|
||||||
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
this.webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||||
|
@ -1922,7 +1922,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage());
|
sendSessionTerminate(Reason.FAILED_APPLICATION, cause.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription);
|
final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription, isInitiator());
|
||||||
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
|
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
|
||||||
final JinglePacket jinglePacket =
|
final JinglePacket jinglePacket =
|
||||||
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
|
@ -15,6 +16,8 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
|
||||||
|
@ -58,13 +61,15 @@ public class RtpContentMap {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RtpContentMap of(final SessionDescription sessionDescription) {
|
public static RtpContentMap of(
|
||||||
|
final SessionDescription sessionDescription, final boolean isInitiator) {
|
||||||
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
|
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
|
||||||
new ImmutableMap.Builder<>();
|
new ImmutableMap.Builder<>();
|
||||||
for (SessionDescription.Media media : sessionDescription.media) {
|
for (SessionDescription.Media media : sessionDescription.media) {
|
||||||
final String id = Iterables.getFirst(media.attributes.get("mid"), null);
|
final String id = Iterables.getFirst(media.attributes.get("mid"), null);
|
||||||
Preconditions.checkNotNull(id, "media has no mid");
|
Preconditions.checkNotNull(id, "media has no mid");
|
||||||
contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
|
contentMapBuilder.put(
|
||||||
|
id, DescriptionTransport.of(sessionDescription, isInitiator, media));
|
||||||
}
|
}
|
||||||
final String groupAttribute =
|
final String groupAttribute =
|
||||||
Iterables.getFirst(sessionDescription.attributes.get("group"), null);
|
Iterables.getFirst(sessionDescription.attributes.get("group"), null);
|
||||||
|
@ -140,11 +145,16 @@ public class RtpContentMap {
|
||||||
jinglePacket.addGroup(this.group);
|
jinglePacket.addGroup(this.group);
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
|
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
|
||||||
final Content content = new Content(Content.Creator.INITIATOR, entry.getKey());
|
final DescriptionTransport descriptionTransport = entry.getValue();
|
||||||
if (entry.getValue().description != null) {
|
final Content content =
|
||||||
content.addChild(entry.getValue().description);
|
new Content(
|
||||||
|
Content.Creator.INITIATOR,
|
||||||
|
descriptionTransport.senders,
|
||||||
|
entry.getKey());
|
||||||
|
if (descriptionTransport.description != null) {
|
||||||
|
content.addChild(descriptionTransport.description);
|
||||||
}
|
}
|
||||||
content.addChild(entry.getValue().transport);
|
content.addChild(descriptionTransport.transport);
|
||||||
jinglePacket.addJingleContent(content);
|
jinglePacket.addJingleContent(content);
|
||||||
}
|
}
|
||||||
return jinglePacket;
|
return jinglePacket;
|
||||||
|
@ -163,7 +173,10 @@ public class RtpContentMap {
|
||||||
newTransportInfo.addChild(candidate);
|
newTransportInfo.addChild(candidate);
|
||||||
return new RtpContentMap(
|
return new RtpContentMap(
|
||||||
null,
|
null,
|
||||||
ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo)));
|
ImmutableMap.of(
|
||||||
|
contentName,
|
||||||
|
new DescriptionTransport(
|
||||||
|
descriptionTransport.senders, null, newTransportInfo)));
|
||||||
}
|
}
|
||||||
|
|
||||||
RtpContentMap transportInfo() {
|
RtpContentMap transportInfo() {
|
||||||
|
@ -171,7 +184,9 @@ public class RtpContentMap {
|
||||||
null,
|
null,
|
||||||
Maps.transformValues(
|
Maps.transformValues(
|
||||||
contents,
|
contents,
|
||||||
dt -> new DescriptionTransport(null, dt.transport.cloneWrapper())));
|
dt ->
|
||||||
|
new DescriptionTransport(
|
||||||
|
dt.senders, null, dt.transport.cloneWrapper())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IceUdpTransportInfo.Credentials getDistinctCredentials() {
|
public IceUdpTransportInfo.Credentials getDistinctCredentials() {
|
||||||
|
@ -179,7 +194,8 @@ public class RtpContentMap {
|
||||||
final IceUdpTransportInfo.Credentials credentials =
|
final IceUdpTransportInfo.Credentials credentials =
|
||||||
Iterables.getFirst(allCredentials, null);
|
Iterables.getFirst(allCredentials, null);
|
||||||
if (allCredentials.size() == 1 && credentials != null) {
|
if (allCredentials.size() == 1 && credentials != null) {
|
||||||
if (Strings.isNullOrEmpty(credentials.password) || Strings.isNullOrEmpty(credentials.ufrag)) {
|
if (Strings.isNullOrEmpty(credentials.password)
|
||||||
|
|| Strings.isNullOrEmpty(credentials.ufrag)) {
|
||||||
throw new IllegalStateException("Credentials are missing password or ufrag");
|
throw new IllegalStateException("Credentials are missing password or ufrag");
|
||||||
}
|
}
|
||||||
return credentials;
|
return credentials;
|
||||||
|
@ -233,23 +249,45 @@ public class RtpContentMap {
|
||||||
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
|
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
|
||||||
new ImmutableMap.Builder<>();
|
new ImmutableMap.Builder<>();
|
||||||
for (final Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
|
for (final Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
|
||||||
final RtpDescription rtpDescription = content.getValue().description;
|
final DescriptionTransport descriptionTransport = content.getValue();
|
||||||
IceUdpTransportInfo transportInfo = content.getValue().transport;
|
final RtpDescription rtpDescription = descriptionTransport.description;
|
||||||
|
final IceUdpTransportInfo transportInfo = descriptionTransport.transport;
|
||||||
final IceUdpTransportInfo modifiedTransportInfo =
|
final IceUdpTransportInfo modifiedTransportInfo =
|
||||||
transportInfo.modifyCredentials(credentials, setup);
|
transportInfo.modifyCredentials(credentials, setup);
|
||||||
contentMapBuilder.put(
|
contentMapBuilder.put(
|
||||||
content.getKey(),
|
content.getKey(),
|
||||||
new DescriptionTransport(rtpDescription, modifiedTransportInfo));
|
new DescriptionTransport(
|
||||||
|
descriptionTransport.senders, rtpDescription, modifiedTransportInfo));
|
||||||
}
|
}
|
||||||
return new RtpContentMap(this.group, contentMapBuilder.build());
|
return new RtpContentMap(this.group, contentMapBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Diff diff(final RtpContentMap rtpContentMap) {
|
||||||
|
final Set<String> existingContentIds = this.contents.keySet();
|
||||||
|
final Set<String> newContentIds = rtpContentMap.contents.keySet();
|
||||||
|
return new Diff(
|
||||||
|
Sets.difference(newContentIds, existingContentIds),
|
||||||
|
Sets.difference(existingContentIds, newContentIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean iceRestart(final RtpContentMap rtpContentMap) {
|
||||||
|
try {
|
||||||
|
return !getDistinctCredentials().equals(rtpContentMap.getDistinctCredentials());
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class DescriptionTransport {
|
public static class DescriptionTransport {
|
||||||
|
public final Content.Senders senders;
|
||||||
public final RtpDescription description;
|
public final RtpDescription description;
|
||||||
public final IceUdpTransportInfo transport;
|
public final IceUdpTransportInfo transport;
|
||||||
|
|
||||||
public DescriptionTransport(
|
public DescriptionTransport(
|
||||||
final RtpDescription description, final IceUdpTransportInfo transport) {
|
final Content.Senders senders,
|
||||||
|
final RtpDescription description,
|
||||||
|
final IceUdpTransportInfo transport) {
|
||||||
|
this.senders = senders;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
}
|
}
|
||||||
|
@ -257,6 +295,7 @@ public class RtpContentMap {
|
||||||
public static DescriptionTransport of(final Content content) {
|
public static DescriptionTransport of(final Content content) {
|
||||||
final GenericDescription description = content.getDescription();
|
final GenericDescription description = content.getDescription();
|
||||||
final GenericTransportInfo transportInfo = content.getTransport();
|
final GenericTransportInfo transportInfo = content.getTransport();
|
||||||
|
final Content.Senders senders = content.getSenders();
|
||||||
final RtpDescription rtpDescription;
|
final RtpDescription rtpDescription;
|
||||||
final IceUdpTransportInfo iceUdpTransportInfo;
|
final IceUdpTransportInfo iceUdpTransportInfo;
|
||||||
if (description == null) {
|
if (description == null) {
|
||||||
|
@ -274,22 +313,26 @@ public class RtpContentMap {
|
||||||
"Content does not contain ICE-UDP transport");
|
"Content does not contain ICE-UDP transport");
|
||||||
}
|
}
|
||||||
return new DescriptionTransport(
|
return new DescriptionTransport(
|
||||||
rtpDescription, OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo));
|
senders,
|
||||||
|
rtpDescription,
|
||||||
|
OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DescriptionTransport of(
|
private static DescriptionTransport of(
|
||||||
final SessionDescription sessionDescription, final SessionDescription.Media media) {
|
final SessionDescription sessionDescription,
|
||||||
|
final boolean isInitiator,
|
||||||
|
final SessionDescription.Media media) {
|
||||||
|
final Content.Senders senders = Content.Senders.of(media, isInitiator);
|
||||||
final RtpDescription rtpDescription = RtpDescription.of(sessionDescription, media);
|
final RtpDescription rtpDescription = RtpDescription.of(sessionDescription, media);
|
||||||
final IceUdpTransportInfo transportInfo =
|
final IceUdpTransportInfo transportInfo =
|
||||||
IceUdpTransportInfo.of(sessionDescription, media);
|
IceUdpTransportInfo.of(sessionDescription, media);
|
||||||
return new DescriptionTransport(rtpDescription, transportInfo);
|
return new DescriptionTransport(senders, rtpDescription, transportInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
|
public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
|
||||||
return ImmutableMap.copyOf(
|
return ImmutableMap.copyOf(
|
||||||
Maps.transformValues(
|
Maps.transformValues(
|
||||||
contents,
|
contents, content -> content == null ? null : of(content)));
|
||||||
content -> content == null ? null : of(content)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,4 +347,27 @@ public class RtpContentMap {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class Diff {
|
||||||
|
public final Set<String> added;
|
||||||
|
public final Set<String> removed;
|
||||||
|
|
||||||
|
private Diff(final Set<String> added, final Set<String> removed) {
|
||||||
|
this.added = added;
|
||||||
|
this.removed = removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasModifications() {
|
||||||
|
return !this.added.isEmpty() || !this.removed.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nonnull
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("added", added)
|
||||||
|
.add("removed", removed)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package eu.siacs.conversations.xmpp.jingle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
@ -21,11 +23,12 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||||
|
|
||||||
public class SessionDescription {
|
public class SessionDescription {
|
||||||
|
|
||||||
public final static String LINE_DIVIDER = "\r\n";
|
public static final String LINE_DIVIDER = "\r\n";
|
||||||
private final static String HARDCODED_MEDIA_PROTOCOL = "UDP/TLS/RTP/SAVPF"; //probably only true for DTLS-SRTP aka when we have a fingerprint
|
private static final String HARDCODED_MEDIA_PROTOCOL =
|
||||||
private final static int HARDCODED_MEDIA_PORT = 9;
|
"UDP/TLS/RTP/SAVPF"; // probably only true for DTLS-SRTP aka when we have a fingerprint
|
||||||
private final static String HARDCODED_ICE_OPTIONS = "trickle";
|
private static final int HARDCODED_MEDIA_PORT = 9;
|
||||||
private final static String HARDCODED_CONNECTION = "IN IP4 0.0.0.0";
|
private static final String HARDCODED_ICE_OPTIONS = "trickle";
|
||||||
|
private static final String HARDCODED_CONNECTION = "IN IP4 0.0.0.0";
|
||||||
|
|
||||||
public final int version;
|
public final int version;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
@ -33,8 +36,12 @@ public class SessionDescription {
|
||||||
public final ArrayListMultimap<String, String> attributes;
|
public final ArrayListMultimap<String, String> attributes;
|
||||||
public final List<Media> media;
|
public final List<Media> media;
|
||||||
|
|
||||||
|
public SessionDescription(
|
||||||
public SessionDescription(int version, String name, String connectionData, ArrayListMultimap<String, String> attributes, List<Media> media) {
|
int version,
|
||||||
|
String name,
|
||||||
|
String connectionData,
|
||||||
|
ArrayListMultimap<String, String> attributes,
|
||||||
|
List<Media> media) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.connectionData = connectionData;
|
this.connectionData = connectionData;
|
||||||
|
@ -42,7 +49,8 @@ public class SessionDescription {
|
||||||
this.media = media;
|
this.media = media;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void appendAttributes(StringBuilder s, ArrayListMultimap<String, String> attributes) {
|
private static void appendAttributes(
|
||||||
|
StringBuilder s, ArrayListMultimap<String, String> attributes) {
|
||||||
for (Map.Entry<String, String> attribute : attributes.entries()) {
|
for (Map.Entry<String, String> attribute : attributes.entries()) {
|
||||||
final String key = attribute.getKey();
|
final String key = attribute.getKey();
|
||||||
final String value = attribute.getValue();
|
final String value = attribute.getValue();
|
||||||
|
@ -109,7 +117,6 @@ public class SessionDescription {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (currentMediaBuilder != null) {
|
if (currentMediaBuilder != null) {
|
||||||
currentMediaBuilder.setAttributes(attributeMap);
|
currentMediaBuilder.setAttributes(attributeMap);
|
||||||
|
@ -121,7 +128,7 @@ public class SessionDescription {
|
||||||
return sessionDescriptionBuilder.createSessionDescription();
|
return sessionDescriptionBuilder.createSessionDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SessionDescription of(final RtpContentMap contentMap) {
|
public static SessionDescription of(final RtpContentMap contentMap, final boolean isInitiatorContentMap) {
|
||||||
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
|
final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
|
||||||
final ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
|
final ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
|
||||||
final ImmutableList.Builder<Media> mediaListBuilder = new ImmutableList.Builder<>();
|
final ImmutableList.Builder<Media> mediaListBuilder = new ImmutableList.Builder<>();
|
||||||
|
@ -129,12 +136,17 @@ public class SessionDescription {
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
final String semantics = group.getSemantics();
|
final String semantics = group.getSemantics();
|
||||||
checkNoWhitespace(semantics, "group semantics value must not contain any whitespace");
|
checkNoWhitespace(semantics, "group semantics value must not contain any whitespace");
|
||||||
attributeMap.put("group", group.getSemantics() + " " + Joiner.on(' ').join(group.getIdentificationTags()));
|
attributeMap.put(
|
||||||
|
"group",
|
||||||
|
group.getSemantics()
|
||||||
|
+ " "
|
||||||
|
+ Joiner.on(' ').join(group.getIdentificationTags()));
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeMap.put("msid-semantic", " WMS my-media-stream");
|
attributeMap.put("msid-semantic", " WMS my-media-stream");
|
||||||
|
|
||||||
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> entry : contentMap.contents.entrySet()) {
|
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> entry :
|
||||||
|
contentMap.contents.entrySet()) {
|
||||||
final String name = entry.getKey();
|
final String name = entry.getKey();
|
||||||
RtpContentMap.DescriptionTransport descriptionTransport = entry.getValue();
|
RtpContentMap.DescriptionTransport descriptionTransport = entry.getValue();
|
||||||
RtpDescription description = descriptionTransport.description;
|
RtpDescription description = descriptionTransport.description;
|
||||||
|
@ -143,19 +155,22 @@ public class SessionDescription {
|
||||||
final String ufrag = transport.getAttribute("ufrag");
|
final String ufrag = transport.getAttribute("ufrag");
|
||||||
final String pwd = transport.getAttribute("pwd");
|
final String pwd = transport.getAttribute("pwd");
|
||||||
if (Strings.isNullOrEmpty(ufrag)) {
|
if (Strings.isNullOrEmpty(ufrag)) {
|
||||||
throw new IllegalArgumentException("Transport element is missing required ufrag attribute");
|
throw new IllegalArgumentException(
|
||||||
|
"Transport element is missing required ufrag attribute");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(ufrag, "ufrag value must not contain any whitespaces");
|
checkNoWhitespace(ufrag, "ufrag value must not contain any whitespaces");
|
||||||
mediaAttributes.put("ice-ufrag", ufrag);
|
mediaAttributes.put("ice-ufrag", ufrag);
|
||||||
if (Strings.isNullOrEmpty(pwd)) {
|
if (Strings.isNullOrEmpty(pwd)) {
|
||||||
throw new IllegalArgumentException("Transport element is missing required pwd attribute");
|
throw new IllegalArgumentException(
|
||||||
|
"Transport element is missing required pwd attribute");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(pwd, "pwd value must not contain any whitespaces");
|
checkNoWhitespace(pwd, "pwd value must not contain any whitespaces");
|
||||||
mediaAttributes.put("ice-pwd", pwd);
|
mediaAttributes.put("ice-pwd", pwd);
|
||||||
mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS);
|
mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS);
|
||||||
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
|
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
|
||||||
if (fingerprint != null) {
|
if (fingerprint != null) {
|
||||||
mediaAttributes.put("fingerprint", fingerprint.getHash() + " " + fingerprint.getContent());
|
mediaAttributes.put(
|
||||||
|
"fingerprint", fingerprint.getHash() + " " + fingerprint.getContent());
|
||||||
final IceUdpTransportInfo.Setup setup = fingerprint.getSetup();
|
final IceUdpTransportInfo.Setup setup = fingerprint.getSetup();
|
||||||
if (setup != null) {
|
if (setup != null) {
|
||||||
mediaAttributes.put("setup", setup.toString().toLowerCase(Locale.ROOT));
|
mediaAttributes.put("setup", setup.toString().toLowerCase(Locale.ROOT));
|
||||||
|
@ -174,37 +189,56 @@ public class SessionDescription {
|
||||||
mediaAttributes.put("rtpmap", payloadType.toSdpAttribute());
|
mediaAttributes.put("rtpmap", payloadType.toSdpAttribute());
|
||||||
final List<RtpDescription.Parameter> parameters = payloadType.getParameters();
|
final List<RtpDescription.Parameter> parameters = payloadType.getParameters();
|
||||||
if (parameters.size() == 1) {
|
if (parameters.size() == 1) {
|
||||||
mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters.get(0)));
|
mediaAttributes.put(
|
||||||
|
"fmtp", RtpDescription.Parameter.toSdpString(id, parameters.get(0)));
|
||||||
} else if (parameters.size() > 0) {
|
} else if (parameters.size() > 0) {
|
||||||
mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters));
|
mediaAttributes.put(
|
||||||
|
"fmtp", RtpDescription.Parameter.toSdpString(id, parameters));
|
||||||
}
|
}
|
||||||
for (RtpDescription.FeedbackNegotiation feedbackNegotiation : payloadType.getFeedbackNegotiations()) {
|
for (RtpDescription.FeedbackNegotiation feedbackNegotiation :
|
||||||
|
payloadType.getFeedbackNegotiations()) {
|
||||||
final String type = feedbackNegotiation.getType();
|
final String type = feedbackNegotiation.getType();
|
||||||
final String subtype = feedbackNegotiation.getSubType();
|
final String subtype = feedbackNegotiation.getSubType();
|
||||||
if (Strings.isNullOrEmpty(type)) {
|
if (Strings.isNullOrEmpty(type)) {
|
||||||
throw new IllegalArgumentException("a feedback for payload-type " + id + " negotiation is missing type");
|
throw new IllegalArgumentException(
|
||||||
|
"a feedback for payload-type "
|
||||||
|
+ id
|
||||||
|
+ " negotiation is missing type");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(type, "feedback negotiation type must not contain whitespace");
|
checkNoWhitespace(
|
||||||
mediaAttributes.put("rtcp-fb", id + " " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
|
type, "feedback negotiation type must not contain whitespace");
|
||||||
|
mediaAttributes.put(
|
||||||
|
"rtcp-fb",
|
||||||
|
id
|
||||||
|
+ " "
|
||||||
|
+ type
|
||||||
|
+ (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
|
||||||
}
|
}
|
||||||
for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : payloadType.feedbackNegotiationTrrInts()) {
|
for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt :
|
||||||
mediaAttributes.put("rtcp-fb", id + " trr-int " + feedbackNegotiationTrrInt.getValue());
|
payloadType.feedbackNegotiationTrrInts()) {
|
||||||
|
mediaAttributes.put(
|
||||||
|
"rtcp-fb", id + " trr-int " + feedbackNegotiationTrrInt.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RtpDescription.FeedbackNegotiation feedbackNegotiation : description.getFeedbackNegotiations()) {
|
for (RtpDescription.FeedbackNegotiation feedbackNegotiation :
|
||||||
|
description.getFeedbackNegotiations()) {
|
||||||
final String type = feedbackNegotiation.getType();
|
final String type = feedbackNegotiation.getType();
|
||||||
final String subtype = feedbackNegotiation.getSubType();
|
final String subtype = feedbackNegotiation.getSubType();
|
||||||
if (Strings.isNullOrEmpty(type)) {
|
if (Strings.isNullOrEmpty(type)) {
|
||||||
throw new IllegalArgumentException("a feedback negotiation is missing type");
|
throw new IllegalArgumentException("a feedback negotiation is missing type");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(type, "feedback negotiation type must not contain whitespace");
|
checkNoWhitespace(type, "feedback negotiation type must not contain whitespace");
|
||||||
mediaAttributes.put("rtcp-fb", "* " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
|
mediaAttributes.put(
|
||||||
|
"rtcp-fb",
|
||||||
|
"* " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
|
||||||
}
|
}
|
||||||
for (final RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : description.feedbackNegotiationTrrInts()) {
|
for (final RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt :
|
||||||
|
description.feedbackNegotiationTrrInts()) {
|
||||||
mediaAttributes.put("rtcp-fb", "* trr-int " + feedbackNegotiationTrrInt.getValue());
|
mediaAttributes.put("rtcp-fb", "* trr-int " + feedbackNegotiationTrrInt.getValue());
|
||||||
}
|
}
|
||||||
for (final RtpDescription.RtpHeaderExtension extension : description.getHeaderExtensions()) {
|
for (final RtpDescription.RtpHeaderExtension extension :
|
||||||
|
description.getHeaderExtensions()) {
|
||||||
final String id = extension.getId();
|
final String id = extension.getId();
|
||||||
final String uri = extension.getUri();
|
final String uri = extension.getUri();
|
||||||
if (Strings.isNullOrEmpty(id)) {
|
if (Strings.isNullOrEmpty(id)) {
|
||||||
|
@ -218,7 +252,8 @@ public class SessionDescription {
|
||||||
mediaAttributes.put("extmap", id + " " + uri);
|
mediaAttributes.put("extmap", id + " " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (description.hasChild("extmap-allow-mixed", Namespace.JINGLE_RTP_HEADER_EXTENSIONS)) {
|
if (description.hasChild(
|
||||||
|
"extmap-allow-mixed", Namespace.JINGLE_RTP_HEADER_EXTENSIONS)) {
|
||||||
mediaAttributes.put("extmap-allow-mixed", "");
|
mediaAttributes.put("extmap-allow-mixed", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,13 +261,16 @@ public class SessionDescription {
|
||||||
final String semantics = sourceGroup.getSemantics();
|
final String semantics = sourceGroup.getSemantics();
|
||||||
final List<String> groups = sourceGroup.getSsrcs();
|
final List<String> groups = sourceGroup.getSsrcs();
|
||||||
if (Strings.isNullOrEmpty(semantics)) {
|
if (Strings.isNullOrEmpty(semantics)) {
|
||||||
throw new IllegalArgumentException("A SSRC group is missing semantics attribute");
|
throw new IllegalArgumentException(
|
||||||
|
"A SSRC group is missing semantics attribute");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(semantics, "source group semantics must not contain whitespace");
|
checkNoWhitespace(semantics, "source group semantics must not contain whitespace");
|
||||||
if (groups.size() == 0) {
|
if (groups.size() == 0) {
|
||||||
throw new IllegalArgumentException("A SSRC group is missing SSRC ids");
|
throw new IllegalArgumentException("A SSRC group is missing SSRC ids");
|
||||||
}
|
}
|
||||||
mediaAttributes.put("ssrc-group", String.format("%s %s", semantics, Joiner.on(' ').join(groups)));
|
mediaAttributes.put(
|
||||||
|
"ssrc-group",
|
||||||
|
String.format("%s %s", semantics, Joiner.on(' ').join(groups)));
|
||||||
}
|
}
|
||||||
for (final RtpDescription.Source source : description.getSources()) {
|
for (final RtpDescription.Source source : description.getSources()) {
|
||||||
for (final RtpDescription.Source.Parameter parameter : source.getParameters()) {
|
for (final RtpDescription.Source.Parameter parameter : source.getParameters()) {
|
||||||
|
@ -240,14 +278,18 @@ public class SessionDescription {
|
||||||
final String parameterName = parameter.getParameterName();
|
final String parameterName = parameter.getParameterName();
|
||||||
final String parameterValue = parameter.getParameterValue();
|
final String parameterValue = parameter.getParameterValue();
|
||||||
if (Strings.isNullOrEmpty(id)) {
|
if (Strings.isNullOrEmpty(id)) {
|
||||||
throw new IllegalArgumentException("A source specific media attribute is missing the id");
|
throw new IllegalArgumentException(
|
||||||
|
"A source specific media attribute is missing the id");
|
||||||
}
|
}
|
||||||
checkNoWhitespace(id, "A source specific media attributes must not contain whitespaces");
|
checkNoWhitespace(
|
||||||
|
id, "A source specific media attributes must not contain whitespaces");
|
||||||
if (Strings.isNullOrEmpty(parameterName)) {
|
if (Strings.isNullOrEmpty(parameterName)) {
|
||||||
throw new IllegalArgumentException("A source specific media attribute is missing its name");
|
throw new IllegalArgumentException(
|
||||||
|
"A source specific media attribute is missing its name");
|
||||||
}
|
}
|
||||||
if (Strings.isNullOrEmpty(parameterValue)) {
|
if (Strings.isNullOrEmpty(parameterValue)) {
|
||||||
throw new IllegalArgumentException("A source specific media attribute is missing its value");
|
throw new IllegalArgumentException(
|
||||||
|
"A source specific media attribute is missing its value");
|
||||||
}
|
}
|
||||||
mediaAttributes.put("ssrc", id + " " + parameterName + ":" + parameterValue);
|
mediaAttributes.put("ssrc", id + " " + parameterName + ":" + parameterValue);
|
||||||
}
|
}
|
||||||
|
@ -255,14 +297,14 @@ public class SessionDescription {
|
||||||
|
|
||||||
mediaAttributes.put("mid", name);
|
mediaAttributes.put("mid", name);
|
||||||
|
|
||||||
//random additional attributes
|
mediaAttributes.put(descriptionTransport.senders.asMediaAttribute(isInitiatorContentMap), "");
|
||||||
mediaAttributes.put("rtcp", "9 IN IP4 0.0.0.0");
|
|
||||||
mediaAttributes.put("sendrecv", "");
|
|
||||||
|
|
||||||
if (description.hasChild("rtcp-mux", Namespace.JINGLE_APPS_RTP)) {
|
if (description.hasChild("rtcp-mux", Namespace.JINGLE_APPS_RTP)) {
|
||||||
mediaAttributes.put("rtcp-mux", "");
|
mediaAttributes.put("rtcp-mux", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// random additional attributes
|
||||||
|
mediaAttributes.put("rtcp", "9 IN IP4 0.0.0.0");
|
||||||
|
|
||||||
final MediaBuilder mediaBuilder = new MediaBuilder();
|
final MediaBuilder mediaBuilder = new MediaBuilder();
|
||||||
mediaBuilder.setMedia(description.getMedia().toString().toLowerCase(Locale.ROOT));
|
mediaBuilder.setMedia(description.getMedia().toString().toLowerCase(Locale.ROOT));
|
||||||
mediaBuilder.setConnectionData(HARDCODED_CONNECTION);
|
mediaBuilder.setConnectionData(HARDCODED_CONNECTION);
|
||||||
|
@ -271,7 +313,6 @@ public class SessionDescription {
|
||||||
mediaBuilder.setAttributes(mediaAttributes);
|
mediaBuilder.setAttributes(mediaAttributes);
|
||||||
mediaBuilder.setFormats(formatBuilder.build());
|
mediaBuilder.setFormats(formatBuilder.build());
|
||||||
mediaListBuilder.add(mediaBuilder.createMedia());
|
mediaListBuilder.add(mediaBuilder.createMedia());
|
||||||
|
|
||||||
}
|
}
|
||||||
sessionDescriptionBuilder.setVersion(0);
|
sessionDescriptionBuilder.setVersion(0);
|
||||||
sessionDescriptionBuilder.setName("-");
|
sessionDescriptionBuilder.setName("-");
|
||||||
|
@ -317,17 +358,33 @@ public class SessionDescription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder s = new StringBuilder()
|
final StringBuilder s =
|
||||||
.append("v=").append(version).append(LINE_DIVIDER)
|
new StringBuilder()
|
||||||
|
.append("v=")
|
||||||
|
.append(version)
|
||||||
|
.append(LINE_DIVIDER)
|
||||||
// TODO randomize or static
|
// TODO randomize or static
|
||||||
.append("o=- 8770656990916039506 2 IN IP4 127.0.0.1").append(LINE_DIVIDER) //what ever that means
|
.append("o=- 8770656990916039506 2 IN IP4 127.0.0.1")
|
||||||
.append("s=").append(name).append(LINE_DIVIDER)
|
.append(LINE_DIVIDER) // what ever that means
|
||||||
.append("t=0 0").append(LINE_DIVIDER);
|
.append("s=")
|
||||||
|
.append(name)
|
||||||
|
.append(LINE_DIVIDER)
|
||||||
|
.append("t=0 0")
|
||||||
|
.append(LINE_DIVIDER);
|
||||||
appendAttributes(s, attributes);
|
appendAttributes(s, attributes);
|
||||||
for (Media media : this.media) {
|
for (Media media : this.media) {
|
||||||
s.append("m=").append(media.media).append(' ').append(media.port).append(' ').append(media.protocol).append(' ').append(Joiner.on(' ').join(media.formats)).append(LINE_DIVIDER);
|
s.append("m=")
|
||||||
|
.append(media.media)
|
||||||
|
.append(' ')
|
||||||
|
.append(media.port)
|
||||||
|
.append(' ')
|
||||||
|
.append(media.protocol)
|
||||||
|
.append(' ')
|
||||||
|
.append(Joiner.on(' ').join(media.formats))
|
||||||
|
.append(LINE_DIVIDER);
|
||||||
s.append("c=").append(media.connectionData).append(LINE_DIVIDER);
|
s.append("c=").append(media.connectionData).append(LINE_DIVIDER);
|
||||||
appendAttributes(s, media.attributes);
|
appendAttributes(s, media.attributes);
|
||||||
}
|
}
|
||||||
|
@ -342,7 +399,13 @@ public class SessionDescription {
|
||||||
public final String connectionData;
|
public final String connectionData;
|
||||||
public final ArrayListMultimap<String, String> attributes;
|
public final ArrayListMultimap<String, String> attributes;
|
||||||
|
|
||||||
public Media(String media, int port, String protocol, List<Integer> formats, String connectionData, ArrayListMultimap<String, String> attributes) {
|
public Media(
|
||||||
|
String media,
|
||||||
|
int port,
|
||||||
|
String protocol,
|
||||||
|
List<Integer> formats,
|
||||||
|
String connectionData,
|
||||||
|
ArrayListMultimap<String, String> attributes) {
|
||||||
this.media = media;
|
this.media = media;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
|
@ -351,5 +414,4 @@ public class SessionDescription {
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import eu.siacs.conversations.xmpp.jingle.SessionDescription;
|
||||||
|
|
||||||
public class Content extends Element {
|
public class Content extends Element {
|
||||||
|
|
||||||
public Content(final Creator creator, final String name) {
|
public Content(final Creator creator, final Senders senders, final String name) {
|
||||||
super("content", Namespace.JINGLE);
|
super("content", Namespace.JINGLE);
|
||||||
this.setAttribute("creator", creator.toString());
|
this.setAttribute("creator", creator.toString());
|
||||||
this.setAttribute("name", name);
|
this.setAttribute("name", name);
|
||||||
|
this.setSenders(senders);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Content() {
|
private Content() {
|
||||||
|
@ -38,12 +45,18 @@ public class Content extends Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Senders getSenders() {
|
public Senders getSenders() {
|
||||||
|
final String attribute = getAttribute("senders");
|
||||||
|
if (Strings.isNullOrEmpty(attribute)) {
|
||||||
|
return Senders.BOTH;
|
||||||
|
}
|
||||||
return Senders.of(getAttribute("senders"));
|
return Senders.of(getAttribute("senders"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSenders(Senders senders) {
|
public void setSenders(final Senders senders) {
|
||||||
|
if (senders != null && senders != Senders.BOTH) {
|
||||||
this.setAttribute("senders", senders.toString());
|
this.setAttribute("senders", senders.toString());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public GenericDescription getDescription() {
|
public GenericDescription getDescription() {
|
||||||
final Element description = this.findChild("description");
|
final Element description = this.findChild("description");
|
||||||
|
@ -51,9 +64,7 @@ public class Content extends Element {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final String namespace = description.getNamespace();
|
final String namespace = description.getNamespace();
|
||||||
if (FileTransferDescription.NAMESPACES.contains(namespace)) {
|
if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
|
||||||
return FileTransferDescription.upgrade(description);
|
|
||||||
} else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
|
|
||||||
return RtpDescription.upgrade(description);
|
return RtpDescription.upgrade(description);
|
||||||
} else {
|
} else {
|
||||||
return GenericDescription.upgrade(description);
|
return GenericDescription.upgrade(description);
|
||||||
|
@ -73,11 +84,7 @@ public class Content extends Element {
|
||||||
public GenericTransportInfo getTransport() {
|
public GenericTransportInfo getTransport() {
|
||||||
final Element transport = this.findChild("transport");
|
final Element transport = this.findChild("transport");
|
||||||
final String namespace = transport == null ? null : transport.getNamespace();
|
final String namespace = transport == null ? null : transport.getNamespace();
|
||||||
if (Namespace.JINGLE_TRANSPORTS_IBB.equals(namespace)) {
|
if (Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(namespace)) {
|
||||||
return IbbTransportInfo.upgrade(transport);
|
|
||||||
} else if (Namespace.JINGLE_TRANSPORTS_S5B.equals(namespace)) {
|
|
||||||
return S5BTransportInfo.upgrade(transport);
|
|
||||||
} else if (Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(namespace)) {
|
|
||||||
return IceUdpTransportInfo.upgrade(transport);
|
return IceUdpTransportInfo.upgrade(transport);
|
||||||
} else if (transport != null) {
|
} else if (transport != null) {
|
||||||
return GenericTransportInfo.upgrade(transport);
|
return GenericTransportInfo.upgrade(transport);
|
||||||
|
@ -91,7 +98,8 @@ public class Content extends Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Creator {
|
public enum Creator {
|
||||||
INITIATOR, RESPONDER;
|
INITIATOR,
|
||||||
|
RESPONDER;
|
||||||
|
|
||||||
public static Creator of(final String value) {
|
public static Creator of(final String value) {
|
||||||
return Creator.valueOf(value.toUpperCase(Locale.ROOT));
|
return Creator.valueOf(value.toUpperCase(Locale.ROOT));
|
||||||
|
@ -105,16 +113,56 @@ public class Content extends Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Senders {
|
public enum Senders {
|
||||||
BOTH, INITIATOR, NONE, RESPONDER;
|
BOTH,
|
||||||
|
INITIATOR,
|
||||||
|
NONE,
|
||||||
|
RESPONDER;
|
||||||
|
|
||||||
public static Senders of(final String value) {
|
public static Senders of(final String value) {
|
||||||
return Senders.valueOf(value.toUpperCase(Locale.ROOT));
|
return Senders.valueOf(value.toUpperCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Senders of(final SessionDescription.Media media, final boolean initiator) {
|
||||||
|
final Set<String> attributes = media.attributes.keySet();
|
||||||
|
if (attributes.contains("sendrecv")) {
|
||||||
|
return BOTH;
|
||||||
|
} else if (attributes.contains("inactive")) {
|
||||||
|
return NONE;
|
||||||
|
} else if (attributes.contains("sendonly")) {
|
||||||
|
return initiator ? INITIATOR : RESPONDER;
|
||||||
|
} else if (attributes.contains("recvonly")) {
|
||||||
|
return initiator ? RESPONDER : INITIATOR;
|
||||||
|
}
|
||||||
|
Log.w(Config.LOGTAG,"assuming default value for senders");
|
||||||
|
// If none of the attributes "sendonly", "recvonly", "inactive", and "sendrecv" is
|
||||||
|
// present, "sendrecv" SHOULD be assumed as the default
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc4566
|
||||||
|
return BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return super.toString().toLowerCase(Locale.ROOT);
|
return super.toString().toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String asMediaAttribute(final boolean initiator) {
|
||||||
|
final boolean responder = !initiator;
|
||||||
|
if (this == Content.Senders.BOTH) {
|
||||||
|
return "sendrecv";
|
||||||
|
} else if (this == Content.Senders.NONE) {
|
||||||
|
return "inactive";
|
||||||
|
} else if ((initiator && this == Content.Senders.INITIATOR)
|
||||||
|
|| (responder && this == Content.Senders.RESPONDER)) {
|
||||||
|
return "sendonly";
|
||||||
|
} else if ((initiator && this == Content.Senders.RESPONDER)
|
||||||
|
|| (responder && this == Content.Senders.INITIATOR)) {
|
||||||
|
return "recvonly";
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"illegal combination of initiator=%s and %s", initiator, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue