Search in sources :

Example 1 with RtpDescription

use of eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription in project Conversations by siacs.

the class RtpContentMap method modifiedCredentials.

public RtpContentMap modifiedCredentials(IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) {
    final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
    for (final Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
        final RtpDescription rtpDescription = content.getValue().description;
        IceUdpTransportInfo transportInfo = content.getValue().transport;
        final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials, setup);
        contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo));
    }
    return new RtpContentMap(this.group, contentMapBuilder.build());
}
Also used : IceUdpTransportInfo(eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo) OmemoVerifiedIceUdpTransportInfo(eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo) ImmutableMap(com.google.common.collect.ImmutableMap) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) RtpDescription(eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription)

Example 2 with RtpDescription

use of eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription in project Conversations by siacs.

the class SessionDescription method of.

public static SessionDescription of(final RtpContentMap contentMap) {
    final SessionDescriptionBuilder sessionDescriptionBuilder = new SessionDescriptionBuilder();
    final ArrayListMultimap<String, String> attributeMap = ArrayListMultimap.create();
    final ImmutableList.Builder<Media> mediaListBuilder = new ImmutableList.Builder<>();
    final Group group = contentMap.group;
    if (group != null) {
        final String semantics = group.getSemantics();
        checkNoWhitespace(semantics, "group semantics value must not contain any whitespace");
        attributeMap.put("group", group.getSemantics() + " " + Joiner.on(' ').join(group.getIdentificationTags()));
    }
    attributeMap.put("msid-semantic", " WMS my-media-stream");
    for (final Map.Entry<String, RtpContentMap.DescriptionTransport> entry : contentMap.contents.entrySet()) {
        final String name = entry.getKey();
        RtpContentMap.DescriptionTransport descriptionTransport = entry.getValue();
        RtpDescription description = descriptionTransport.description;
        IceUdpTransportInfo transport = descriptionTransport.transport;
        final ArrayListMultimap<String, String> mediaAttributes = ArrayListMultimap.create();
        final String ufrag = transport.getAttribute("ufrag");
        final String pwd = transport.getAttribute("pwd");
        if (Strings.isNullOrEmpty(ufrag)) {
            throw new IllegalArgumentException("Transport element is missing required ufrag attribute");
        }
        checkNoWhitespace(ufrag, "ufrag value must not contain any whitespaces");
        mediaAttributes.put("ice-ufrag", ufrag);
        if (Strings.isNullOrEmpty(pwd)) {
            throw new IllegalArgumentException("Transport element is missing required pwd attribute");
        }
        checkNoWhitespace(pwd, "pwd value must not contain any whitespaces");
        mediaAttributes.put("ice-pwd", pwd);
        mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS);
        final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
        if (fingerprint != null) {
            mediaAttributes.put("fingerprint", fingerprint.getHash() + " " + fingerprint.getContent());
            final IceUdpTransportInfo.Setup setup = fingerprint.getSetup();
            if (setup != null) {
                mediaAttributes.put("setup", setup.toString().toLowerCase(Locale.ROOT));
            }
        }
        final ImmutableList.Builder<Integer> formatBuilder = new ImmutableList.Builder<>();
        for (RtpDescription.PayloadType payloadType : description.getPayloadTypes()) {
            final String id = payloadType.getId();
            if (Strings.isNullOrEmpty(id)) {
                throw new IllegalArgumentException("Payload type is missing id");
            }
            if (!isInt(id)) {
                throw new IllegalArgumentException("Payload id is not numeric");
            }
            formatBuilder.add(payloadType.getIntId());
            mediaAttributes.put("rtpmap", payloadType.toSdpAttribute());
            final List<RtpDescription.Parameter> parameters = payloadType.getParameters();
            if (parameters.size() == 1) {
                mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters.get(0)));
            } else if (parameters.size() > 0) {
                mediaAttributes.put("fmtp", RtpDescription.Parameter.toSdpString(id, parameters));
            }
            for (RtpDescription.FeedbackNegotiation feedbackNegotiation : payloadType.getFeedbackNegotiations()) {
                final String type = feedbackNegotiation.getType();
                final String subtype = feedbackNegotiation.getSubType();
                if (Strings.isNullOrEmpty(type)) {
                    throw new IllegalArgumentException("a feedback for payload-type " + id + " negotiation is missing type");
                }
                checkNoWhitespace(type, "feedback negotiation type must not contain whitespace");
                mediaAttributes.put("rtcp-fb", id + " " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
            }
            for (RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : payloadType.feedbackNegotiationTrrInts()) {
                mediaAttributes.put("rtcp-fb", id + " trr-int " + feedbackNegotiationTrrInt.getValue());
            }
        }
        for (RtpDescription.FeedbackNegotiation feedbackNegotiation : description.getFeedbackNegotiations()) {
            final String type = feedbackNegotiation.getType();
            final String subtype = feedbackNegotiation.getSubType();
            if (Strings.isNullOrEmpty(type)) {
                throw new IllegalArgumentException("a feedback negotiation is missing type");
            }
            checkNoWhitespace(type, "feedback negotiation type must not contain whitespace");
            mediaAttributes.put("rtcp-fb", "* " + type + (Strings.isNullOrEmpty(subtype) ? "" : " " + subtype));
        }
        for (final RtpDescription.FeedbackNegotiationTrrInt feedbackNegotiationTrrInt : description.feedbackNegotiationTrrInts()) {
            mediaAttributes.put("rtcp-fb", "* trr-int " + feedbackNegotiationTrrInt.getValue());
        }
        for (final RtpDescription.RtpHeaderExtension extension : description.getHeaderExtensions()) {
            final String id = extension.getId();
            final String uri = extension.getUri();
            if (Strings.isNullOrEmpty(id)) {
                throw new IllegalArgumentException("A header extension is missing id");
            }
            checkNoWhitespace(id, "header extension id must not contain whitespace");
            if (Strings.isNullOrEmpty(uri)) {
                throw new IllegalArgumentException("A header extension is missing uri");
            }
            checkNoWhitespace(uri, "feedback negotiation uri must not contain whitespace");
            mediaAttributes.put("extmap", id + " " + uri);
        }
        if (description.hasChild("extmap-allow-mixed", Namespace.JINGLE_RTP_HEADER_EXTENSIONS)) {
            mediaAttributes.put("extmap-allow-mixed", "");
        }
        for (final RtpDescription.SourceGroup sourceGroup : description.getSourceGroups()) {
            final String semantics = sourceGroup.getSemantics();
            final List<String> groups = sourceGroup.getSsrcs();
            if (Strings.isNullOrEmpty(semantics)) {
                throw new IllegalArgumentException("A SSRC group is missing semantics attribute");
            }
            checkNoWhitespace(semantics, "source group semantics must not contain whitespace");
            if (groups.size() == 0) {
                throw new IllegalArgumentException("A SSRC group is missing SSRC ids");
            }
            mediaAttributes.put("ssrc-group", String.format("%s %s", semantics, Joiner.on(' ').join(groups)));
        }
        for (final RtpDescription.Source source : description.getSources()) {
            for (final RtpDescription.Source.Parameter parameter : source.getParameters()) {
                final String id = source.getSsrcId();
                final String parameterName = parameter.getParameterName();
                final String parameterValue = parameter.getParameterValue();
                if (Strings.isNullOrEmpty(id)) {
                    throw new IllegalArgumentException("A source specific media attribute is missing the id");
                }
                checkNoWhitespace(id, "A source specific media attributes must not contain whitespaces");
                if (Strings.isNullOrEmpty(parameterName)) {
                    throw new IllegalArgumentException("A source specific media attribute is missing its name");
                }
                if (Strings.isNullOrEmpty(parameterValue)) {
                    throw new IllegalArgumentException("A source specific media attribute is missing its value");
                }
                mediaAttributes.put("ssrc", id + " " + parameterName + ":" + parameterValue);
            }
        }
        mediaAttributes.put("mid", name);
        // random additional attributes
        mediaAttributes.put("rtcp", "9 IN IP4 0.0.0.0");
        mediaAttributes.put("sendrecv", "");
        if (description.hasChild("rtcp-mux", Namespace.JINGLE_APPS_RTP)) {
            mediaAttributes.put("rtcp-mux", "");
        }
        final MediaBuilder mediaBuilder = new MediaBuilder();
        mediaBuilder.setMedia(description.getMedia().toString().toLowerCase(Locale.ROOT));
        mediaBuilder.setConnectionData(HARDCODED_CONNECTION);
        mediaBuilder.setPort(HARDCODED_MEDIA_PORT);
        mediaBuilder.setProtocol(HARDCODED_MEDIA_PROTOCOL);
        mediaBuilder.setAttributes(mediaAttributes);
        mediaBuilder.setFormats(formatBuilder.build());
        mediaListBuilder.add(mediaBuilder.createMedia());
    }
    sessionDescriptionBuilder.setVersion(0);
    sessionDescriptionBuilder.setName("-");
    sessionDescriptionBuilder.setMedia(mediaListBuilder.build());
    sessionDescriptionBuilder.setAttributes(attributeMap);
    return sessionDescriptionBuilder.createSessionDescription();
}
Also used : Group(eu.siacs.conversations.xmpp.jingle.stanzas.Group) ImmutableList(com.google.common.collect.ImmutableList) RtpDescription(eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription) IceUdpTransportInfo(eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo) Map(java.util.Map)

Example 3 with RtpDescription

use of eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription in project Conversations by siacs.

the class JingleConnectionManager method deliverMessage.

public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message, String remoteMsgId, String serverMsgId, long timestamp) {
    Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
    final String sessionId = message.getAttribute("id");
    if (sessionId == null) {
        return;
    }
    if ("accept".equals(message.getName())) {
        for (AbstractJingleConnection connection : connections.values()) {
            if (connection instanceof JingleRtpConnection) {
                final JingleRtpConnection rtpConnection = (JingleRtpConnection) connection;
                final AbstractJingleConnection.Id id = connection.getId();
                if (id.account == account && id.sessionId.equals(sessionId)) {
                    rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
                    return;
                }
            }
        }
        return;
    }
    final boolean fromSelf = from.asBareJid().equals(account.getJid().asBareJid());
    final boolean addressedDirectly = to != null && to.equals(account.getJid());
    final AbstractJingleConnection.Id id;
    if (fromSelf) {
        if (to != null && to.isFullJid()) {
            id = AbstractJingleConnection.Id.of(account, to, sessionId);
        } else {
            return;
        }
    } else {
        id = AbstractJingleConnection.Id.of(account, from, sessionId);
    }
    final AbstractJingleConnection existingJingleConnection = connections.get(id);
    if (existingJingleConnection != null) {
        if (existingJingleConnection instanceof JingleRtpConnection) {
            ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message, serverMsgId, timestamp);
        } else {
            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages");
        }
        return;
    }
    if (fromSelf) {
        if ("proceed".equals(message.getName())) {
            final Conversation c = mXmppConnectionService.findOrCreateConversation(account, id.with, false, false);
            final Message previousBusy = c.findRtpSession(sessionId, Message.STATUS_RECEIVED);
            if (previousBusy != null) {
                previousBusy.setBody(new RtpSessionStatus(true, 0).toString());
                if (serverMsgId != null) {
                    previousBusy.setServerMsgId(serverMsgId);
                }
                previousBusy.setTime(timestamp);
                mXmppConnectionService.updateMessage(previousBusy, true);
                Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": updated previous busy because call got picked up by another device");
                return;
            }
        }
        // TODO handle reject for cases where we don’t have carbon copies (normally reject is to be sent to own bare jid as well)
        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignore jingle message from self");
        return;
    }
    if ("propose".equals(message.getName())) {
        final Propose propose = Propose.upgrade(message);
        final List<GenericDescription> descriptions = propose.getDescriptions();
        final Collection<RtpDescription> rtpDescriptions = Collections2.transform(Collections2.filter(descriptions, d -> d instanceof RtpDescription), input -> (RtpDescription) input);
        if (rtpDescriptions.size() > 0 && rtpDescriptions.size() == descriptions.size() && isUsingClearNet(account)) {
            final Collection<Media> media = Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
            if (media.contains(Media.UNKNOWN)) {
                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered unknown media in session proposal. " + propose);
                return;
            }
            final Optional<RtpSessionProposal> matchingSessionProposal = findMatchingSessionProposal(account, id.with, ImmutableSet.copyOf(media));
            if (matchingSessionProposal.isPresent()) {
                final String ourSessionId = matchingSessionProposal.get().sessionId;
                final String theirSessionId = id.sessionId;
                if (ComparisonChain.start().compare(ourSessionId, theirSessionId).compare(account.getJid().toEscapedString(), id.with.toEscapedString()).result() > 0) {
                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session lost tie break. automatically accepting their session. winning Session=" + theirSessionId);
                    // TODO a retract for this reason should probably include some indication of tie break
                    retractSessionProposal(matchingSessionProposal.get());
                    final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
                    this.connections.put(id, rtpConnection);
                    rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
                    rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
                } else {
                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session won tie break. waiting for other party to accept. winningSession=" + ourSessionId);
                }
                return;
            }
            final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
            if (isBusy() || stranger) {
                writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
                if (stranger) {
                    Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring call proposal from stranger " + id.with);
                    return;
                }
                final int activeDevices = account.activeDevicesWithRtpCapability();
                Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
                if (activeDevices == 0) {
                    final MessagePacket reject = mXmppConnectionService.getMessageGenerator().sessionReject(from, sessionId);
                    mXmppConnectionService.sendMessagePacket(account, reject);
                } else {
                    Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring proposal because busy on this device but there are other devices");
                }
            } else {
                final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
                this.connections.put(id, rtpConnection);
                rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
                rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
            }
        } else {
            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed session with " + rtpDescriptions.size() + " rtp descriptions of " + descriptions.size() + " total descriptions");
        }
    } else if (addressedDirectly && "proceed".equals(message.getName())) {
        synchronized (rtpSessionProposals) {
            final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
            if (proposal != null) {
                rtpSessionProposals.remove(proposal);
                final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
                rtpConnection.setProposedMedia(proposal.media);
                this.connections.put(id, rtpConnection);
                rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
                rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
            } else {
                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver proceed");
                if (remoteMsgId == null) {
                    return;
                }
                final MessagePacket errorMessage = new MessagePacket();
                errorMessage.setTo(from);
                errorMessage.setId(remoteMsgId);
                errorMessage.setType(MessagePacket.TYPE_ERROR);
                final Element error = errorMessage.addChild("error");
                error.setAttribute("code", "404");
                error.setAttribute("type", "cancel");
                error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
                mXmppConnectionService.sendMessagePacket(account, errorMessage);
            }
        }
    } else if (addressedDirectly && "reject".equals(message.getName())) {
        final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
        synchronized (rtpSessionProposals) {
            if (proposal != null && rtpSessionProposals.remove(proposal) != null) {
                writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
                toneManager.transition(RtpEndUserState.DECLINED_OR_BUSY, proposal.media);
                mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY);
            } else {
                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver reject");
            }
        }
    } else {
        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message" + message);
    }
}
Also used : Conversational(eu.siacs.conversations.entities.Conversational) Content(eu.siacs.conversations.xmpp.jingle.stanzas.Content) RtpDescription(eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription) Config(eu.siacs.conversations.Config) ScheduledFuture(java.util.concurrent.ScheduledFuture) IqPacket(eu.siacs.conversations.xmpp.stanzas.IqPacket) Reason(eu.siacs.conversations.xmpp.jingle.stanzas.Reason) HashMap(java.util.HashMap) Collections2(com.google.common.collect.Collections2) Account(eu.siacs.conversations.entities.Account) Element(eu.siacs.conversations.xml.Element) Conversation(eu.siacs.conversations.entities.Conversation) SecureRandom(java.security.SecureRandom) GenericDescription(eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription) Optional(com.google.common.base.Optional) Map(java.util.Map) ScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) FileTransferDescription(eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription) Objects(com.google.common.base.Objects) WeakReference(java.lang.ref.WeakReference) Log(android.util.Log) XmppConnection(eu.siacs.conversations.xmpp.XmppConnection) MessagePacket(eu.siacs.conversations.xmpp.stanzas.MessagePacket) ImmutableSet(com.google.common.collect.ImmutableSet) Contact(eu.siacs.conversations.entities.Contact) Collection(java.util.Collection) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) Propose(eu.siacs.conversations.xmpp.jingle.stanzas.Propose) Set(java.util.Set) JinglePacket(eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket) ComparisonChain(com.google.common.collect.ComparisonChain) Executors(java.util.concurrent.Executors) TimeUnit(java.util.concurrent.TimeUnit) Transferable(eu.siacs.conversations.entities.Transferable) List(java.util.List) Message(eu.siacs.conversations.entities.Message) RtpSessionStatus(eu.siacs.conversations.entities.RtpSessionStatus) XmppConnectionService(eu.siacs.conversations.services.XmppConnectionService) Namespace(eu.siacs.conversations.xml.Namespace) OnIqPacketReceived(eu.siacs.conversations.xmpp.OnIqPacketReceived) Base64(android.util.Base64) Preconditions(com.google.common.base.Preconditions) CacheBuilder(com.google.common.cache.CacheBuilder) Cache(com.google.common.cache.Cache) AbstractConnectionManager(eu.siacs.conversations.services.AbstractConnectionManager) Collections(java.util.Collections) Jid(eu.siacs.conversations.xmpp.Jid) Message(eu.siacs.conversations.entities.Message) Element(eu.siacs.conversations.xml.Element) GenericDescription(eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription) Conversation(eu.siacs.conversations.entities.Conversation) RtpDescription(eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription) MessagePacket(eu.siacs.conversations.xmpp.stanzas.MessagePacket) Propose(eu.siacs.conversations.xmpp.jingle.stanzas.Propose) RtpSessionStatus(eu.siacs.conversations.entities.RtpSessionStatus)

Aggregations

RtpDescription (eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription)3 Map (java.util.Map)3 IceUdpTransportInfo (eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo)2 Base64 (android.util.Base64)1 Log (android.util.Log)1 Objects (com.google.common.base.Objects)1 Optional (com.google.common.base.Optional)1 Preconditions (com.google.common.base.Preconditions)1 Cache (com.google.common.cache.Cache)1 CacheBuilder (com.google.common.cache.CacheBuilder)1 Collections2 (com.google.common.collect.Collections2)1 ComparisonChain (com.google.common.collect.ComparisonChain)1 ImmutableList (com.google.common.collect.ImmutableList)1 ImmutableMap (com.google.common.collect.ImmutableMap)1 ImmutableSet (com.google.common.collect.ImmutableSet)1 Config (eu.siacs.conversations.Config)1 Account (eu.siacs.conversations.entities.Account)1 Contact (eu.siacs.conversations.entities.Contact)1 Conversation (eu.siacs.conversations.entities.Conversation)1 Conversational (eu.siacs.conversations.entities.Conversational)1