All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lastbamboo.common.ice.sdp.IceCandidateSdpDecoderImpl Maven / Gradle / Ivy

package org.lastbamboo.common.ice.sdp;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

import org.lastbamboo.common.ice.IceTransportProtocol;
import org.lastbamboo.common.ice.candidate.IceCandidate;
import org.lastbamboo.common.ice.candidate.IceCandidateType;
import org.lastbamboo.common.ice.candidate.IceTcpActiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpHostPassiveCandidate;
import org.lastbamboo.common.ice.candidate.IceTcpRelayPassiveCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpHostCandidate;
import org.lastbamboo.common.ice.candidate.IceUdpServerReflexiveCandidate;
import org.lastbamboo.common.sdp.api.Attribute;
import org.lastbamboo.common.sdp.api.MediaDescription;
import org.lastbamboo.common.sdp.api.SdpException;
import org.lastbamboo.common.sdp.api.SdpFactory;
import org.lastbamboo.common.sdp.api.SdpParseException;
import org.lastbamboo.common.sdp.api.SessionDescription;
import org.littleshoot.mina.common.ByteBuffer;
import org.littleshoot.util.IoExceptionWithCause;
import org.littleshoot.util.mina.MinaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Factory class for creating ICE candidates from offer/answer data.
 * 
 * TODO: This currently decodes candidates assuming there's only one media
 * stream. That's incorrect. It should create collections of candidates for 
 * each media stream.
 */
public final class IceCandidateSdpDecoderImpl 
    implements IceCandidateSdpDecoder {
    
    /**
     * Logger for this class.
     */
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    private static final String CANDIDATE_KEY = "candidate";

    private final SdpFactory sdpFactory = new SdpFactory();
    
    public Collection decode(final ByteBuffer buf,
            final boolean controlling) throws IOException {
        final String responseBodyString = MinaUtils.toAsciiString(buf);

        try {
            final SessionDescription sdp = this.sdpFactory
                    .createSessionDescription(responseBodyString);
            final Collection mediaDescriptions = 
                sdp.getMediaDescriptions(true);
            log.debug("Creating candidates from media descs:\n"
                    + mediaDescriptions);
            return createCandidates(mediaDescriptions, controlling);
        } catch (final SdpException e) {
            log.warn("Could not parse SDP: " + MinaUtils.toAsciiString(buf));
            throw new IoExceptionWithCause("Could not parse SDP", e);
        }
    }
    
    /**
     * Sends TURN "Send Request"s to the collection of candidate connections 
     * from the peer's SDP.
     * 
     * @param remoteMediaDescriptions The Collection of media
     * description's from the peer's SDP.  Each media description will contain
     * some number of candidates for exchanging media.
     * @param controlling Whether or not to create controlling candidates.
     */
    private Collection createCandidates(
        final Collection remoteMediaDescriptions, 
        final boolean controlling) {
        final Collection candidates = 
            new ArrayList();
        for (final MediaDescription mediaDesc : remoteMediaDescriptions) {
            final Collection attributes = 
                mediaDesc.getAttributes(true);
            for (final Attribute attribute : attributes) {
                try {
                    if (attribute.getName().equals(CANDIDATE_KEY)) {
                        final String attributeValue = attribute.getValue();
                        final Collection newCandidates = createCandidates(
                                attributeValue, controlling);
                        candidates.addAll(newCandidates);
                    }
                } catch (final UnknownHostException e) {
                    log.warn("Could not parse SDP", e);
                    // Go to the next candidate.
                    continue;
                } catch (final SdpParseException e) {
                    log.warn("Could not parse SDP", e);
                    // Go to the next candidate.
                    continue;
                }
            }
        }
        return candidates;
    }

    /**
     * Sends a TURN send request to a TURN server to enable incoming data
     * for a specific candidate for a specific media.
     * 
     * @param tracker The tracker for the status of the available ICE 
     * connection candidates.
     * @param attribute The attribute string to parse in order to send a
     * TURN send request to the server in the candidate.
     * @throws UnknownHostException If we could not parse the host 
     */
    private Collection createCandidates(final String attribute,
            final boolean controlling) throws UnknownHostException {
        log.trace("Parsing attribute: " + attribute);
        final List candidates = new LinkedList();
        final Scanner scanner = new Scanner(attribute);
        scanner.useDelimiter(" ");
        while (scanner.hasNext()) {
            final IceCandidate candidate = createIceCandidate(scanner,
                    controlling);
            candidates.add(candidate);
        }
        return candidates;
    }

    private IceCandidate createIceCandidate(final Scanner scanner,
            final boolean controlling) throws UnknownHostException {
        final String foundation = scanner.next();
        final int componentId = Integer.parseInt(scanner.next());
        final String transportString = scanner.next();
        final IceTransportProtocol transportProtocol = IceTransportProtocol
                .toTransport(transportString);
        if (transportProtocol == null) {
            log.warn("Null transport");
            throw new NullPointerException("Null transport");
        }

        final int priority = Integer.parseInt(scanner.next());
        final InetAddress address = InetAddress.getByName(scanner.next());
        final int port = Integer.parseInt(scanner.next());
        final InetSocketAddress socketAddress = new InetSocketAddress(address,
                port);

        final String typeToken = scanner.next();
        if (!typeToken.equals("typ")) {
            log.error("Unexpected type token: " + typeToken);
            throw new IllegalArgumentException("Unexpected type token: "
                    + typeToken);
        }

        final String typeString = scanner.next();
        final IceCandidateType type = IceCandidateType.toType(typeString);
        if (type == null) {
            log.warn("Unrecognized type: " + typeString);
            throw new IllegalArgumentException("Unrecognized type: "
                    + typeString);
        }

        switch (transportProtocol) {
        case UDP:
            switch (type) {
            case HOST:
                return new IceUdpHostCandidate(socketAddress, foundation,
                        priority, controlling, componentId);
            case RELAYED:
                break;
            case PEER_REFLEXIVE:
                break;
            case SERVER_REFLEXIVE:
                final InetSocketAddress related = parseRelated(scanner);
                return new IceUdpServerReflexiveCandidate(socketAddress,
                        foundation, related.getAddress(), related.getPort(),
                        controlling, priority, componentId);
            }
            break;
        case TCP_PASS:
            switch (type) {
            case HOST:
                return new IceTcpHostPassiveCandidate(socketAddress,
                        foundation, controlling, priority, componentId);
            case RELAYED:
                log.debug("Received a TCP relay passive candidate");
                final InetSocketAddress related = parseRelated(scanner);
                return new IceTcpRelayPassiveCandidate(socketAddress,
                        foundation, related.getAddress(), related.getPort(),
                        controlling, priority, componentId);
            case PEER_REFLEXIVE:
                break;
            case SERVER_REFLEXIVE:
                break;
            }
            break;
        case TCP_SO:
            // Not currently used. Awaiting progress on the ICE TCP
            // draft before implementing things that could change.
            switch (type) {
            case HOST:
                break;
            case RELAYED:
                break;
            case PEER_REFLEXIVE:
                break;
            case SERVER_REFLEXIVE:
                break;
            }
            break;
        case TCP_ACT:
            // Not currently used. Awaiting progress on the ICE TCP
            // draft before implementing things that could change.
            switch (type) {
            case HOST:
                return new IceTcpActiveCandidate(socketAddress, controlling);
            case RELAYED:
                break;
            case PEER_REFLEXIVE:
                break;
            case SERVER_REFLEXIVE:
                break;
            }
            break;
        }

        log.warn("Returning null candidate for type: " + type
                + " and protocol " + transportProtocol);
        return null;
    }

    private InetSocketAddress parseRelated(final Scanner scanner)
            throws UnknownHostException {
        final String raddr = scanner.next();
        if (!raddr.equals("raddr")) {
            log.error("Bad related address: " + raddr);
        }
        final InetAddress relatedAddress = InetAddress
                .getByName(scanner.next());

        final String rport = scanner.next();
        if (!rport.equals("rport")) {
            log.error("Bad related port: " + rport);
        }
        final int relatedPort = Integer.parseInt(scanner.next());

        return new InetSocketAddress(relatedAddress, relatedPort);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy