001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingleold;
018
019import java.util.logging.Level;
020import java.util.logging.Logger;
021
022import org.jivesoftware.smack.SmackException;
023import org.jivesoftware.smack.XMPPException;
024import org.jivesoftware.smack.packet.IQ;
025import org.jivesoftware.smackx.jingleold.media.JingleMediaManager;
026import org.jivesoftware.smackx.jingleold.media.MediaNegotiator;
027import org.jivesoftware.smackx.jingleold.media.PayloadType;
028import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager;
029import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator;
030import org.jivesoftware.smackx.jingleold.nat.TransportResolver;
031import org.jivesoftware.smackx.jingleold.packet.Jingle;
032import org.jivesoftware.smackx.jingleold.packet.JingleContent;
033import org.jivesoftware.smackx.jingleold.packet.JingleDescription;
034import org.jivesoftware.smackx.jingleold.packet.JingleError;
035import org.jivesoftware.smackx.jingleold.packet.JingleTransport;
036
037/**
038 * Jingle. 
039 *  @author Jeff Williams
040 *  @see JingleSessionState
041 */
042public class JingleSessionStateUnknown extends JingleSessionState {
043    private static final Logger LOGGER = Logger.getLogger(JingleSessionStateUnknown.class.getName());
044
045    private static JingleSessionStateUnknown INSTANCE = null;
046
047    protected JingleSessionStateUnknown() {
048        // Prevent instantiation of the class.
049    }
050
051    /**
052     *  A thread-safe means of getting the one instance of this class.
053     *  @return The singleton instance of this class.
054     */
055    public synchronized static JingleSessionState getInstance() {
056        if (INSTANCE == null) {
057            INSTANCE = new JingleSessionStateUnknown();
058        }
059        return INSTANCE;
060    }
061
062    @Override
063    public void enter() {
064        // TODO Auto-generated method stub
065
066    }
067
068    @Override
069    public void exit() {
070        // TODO Auto-generated method stub
071
072    }
073
074    @Override
075    public IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action) throws SmackException, InterruptedException {
076        IQ response = null;
077
078        switch (action) {
079            case SESSION_INITIATE:
080                response = receiveSessionInitiateAction(session, jingle);
081                break;
082
083            case SESSION_TERMINATE:
084                response = receiveSessionTerminateAction(session, jingle);
085                break;
086
087            default:
088                // Anything other than session-initiate is an error.
089                response = session.createJingleError(jingle, JingleError.MALFORMED_STANZA);
090                break;
091        }
092
093        return response;
094    }
095
096    /**
097     * In the UNKNOWN state we received a <session-initiate> action.
098     * This method processes that action.
099     * @throws SmackException 
100     * @throws InterruptedException 
101     */
102
103    private IQ receiveSessionInitiateAction(JingleSession session, Jingle inJingle) throws SmackException, InterruptedException {
104
105        IQ response = null;
106        boolean shouldAck = true;
107
108        // According to XEP-166 when we get a session-initiate we need to check for:
109        //      1. Initiator unknown
110        //      2. Receiver redirection
111        //      3. Does not support Jingle
112        //      4. Does not support any <description> formats
113        //      5. Does not support any <transport> formats
114        // If all of the above are OK then we send an IQ type = result to ACK the session-initiate.
115
116        // 1. Initiator unknown
117        // TODO
118
119        // 2. Receiver redirection
120        // TODO
121
122        // 3. Does not support Jingle
123        // Handled by Smack's lower layer.
124
125        // 4. Does not support any <description> formats
126        // TODO
127
128        // 5. Does not support any <transport> formats
129        // TODO
130
131        if (!shouldAck) {
132
133            response = session.createJingleError(inJingle, JingleError.NEGOTIATION_ERROR);
134
135        } else {
136
137            // Create the Ack
138            response = session.createAck(inJingle);
139
140            session.setSessionState(JingleSessionStatePending.getInstance());
141
142            // Now set up all of the initial content negotiators for the session.
143            for (JingleContent jingleContent : inJingle.getContentsList()) {
144                // First create the content negotiator for this <content> section.
145                ContentNegotiator contentNeg = new ContentNegotiator(session, jingleContent.getCreator(), jingleContent
146                        .getName());
147
148                // Get the media negotiator that goes with the <description> of this content.
149                JingleDescription jingleDescription = jingleContent.getDescription();
150
151                // Loop through each media manager looking for the ones that matches the incoming 
152                // session-initiate <content> choices.
153                // (Set the first media manager as the default, so that in case things don't match we can still negotiate.)
154                JingleMediaManager chosenMediaManager = session.getMediaManagers().get(0);
155                for (JingleMediaManager mediaManager : session.getMediaManagers()) {
156                    boolean matches = true;
157                    for (PayloadType mediaPayloadType : mediaManager.getPayloads()) {
158                        for (PayloadType descPayloadType2 : jingleDescription.getPayloadTypesList()) {
159                            if (mediaPayloadType.getId() != descPayloadType2.getId()) {
160                                matches = false;
161                            }
162                        }
163                        if (matches) {
164                            chosenMediaManager = mediaManager;
165                        }
166                    }
167                }
168
169                // Create the media negotiator for this content description.
170                contentNeg.setMediaNegotiator(new MediaNegotiator(session, chosenMediaManager, jingleDescription
171                        .getPayloadTypesList(), contentNeg));
172
173                // For each transport type in this content, try to find the corresponding transport manager.
174                // Then create a transport negotiator for that transport.
175                for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) {
176                    for (JingleMediaManager mediaManager : session.getMediaManagers()) {
177
178                        JingleTransportManager transportManager = mediaManager.getTransportManager();
179                        TransportResolver resolver = null;
180                        try {
181                            resolver = transportManager.getResolver(session);
182                        } catch (XMPPException e) {
183                            LOGGER.log(Level.WARNING, "exception", e);
184                        }
185
186                        if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
187                            contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(session, resolver, contentNeg));
188                        }
189                        if (resolver.getType().equals(TransportResolver.Type.ice)) {
190                            contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(session, resolver, contentNeg));
191                        }
192                    }
193                }
194
195                // Add the content negotiator to the session.
196                session.addContentNegotiator(contentNeg);
197            }
198
199            // Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
200            session.setupListeners();
201        }
202
203        return response;
204    }
205
206    /**
207     * Receive and process the <session-terminate> action.
208     */
209    private IQ receiveSessionTerminateAction(JingleSession session, Jingle jingle) {
210
211        // According to XEP-166 the only thing we can do is ack.
212        IQ response = session.createAck(jingle);
213
214        try {
215            session.terminate("Closed remotely");
216        } catch (Exception e) {
217            LOGGER.log(Level.WARNING, "exception", e);
218        }
219
220        return response;
221    }
222
223}