001/*
002 *
003 * Copyright 2003-2005 Jive Software.
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 */
017
018package org.jivesoftware.smackx.jingleold.nat;
019
020import java.io.IOException;
021import java.net.InetAddress;
022import java.net.NetworkInterface;
023import java.net.SocketException;
024import java.util.Enumeration;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import org.jivesoftware.smack.SmackException.NoResponseException;
029import org.jivesoftware.smack.SmackException.NotConnectedException;
030import org.jivesoftware.smack.XMPPConnection;
031import org.jivesoftware.smack.XMPPException.XMPPErrorException;
032import org.jivesoftware.smack.packet.IQ;
033import org.jivesoftware.smack.packet.IqData;
034import org.jivesoftware.smack.packet.XmlEnvironment;
035import org.jivesoftware.smack.provider.IqProvider;
036import org.jivesoftware.smack.provider.ProviderManager;
037import org.jivesoftware.smack.xml.XmlPullParser;
038import org.jivesoftware.smack.xml.XmlPullParserException;
039
040import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
041import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
042
043import org.jxmpp.JxmppContext;
044import org.jxmpp.jid.DomainBareJid;
045import org.jxmpp.jid.impl.JidCreate;
046import org.jxmpp.stringprep.XmppStringprepException;
047
048/**
049 * RTPBridge IQ Stanza used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT.
050 * This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties.
051 * <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i>
052 *
053 * High Level Usage Example:
054 *
055 * RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID);
056 *
057 * @author Thiago Camargo
058 */
059public class RTPBridge extends IQ {
060
061    private static final Logger LOGGER = Logger.getLogger(RTPBridge.class.getName());
062
063    private String sid;
064    private String pass;
065    private String ip;
066    private String name;
067    private int portA = -1;
068    private int portB = -1;
069    private String hostA;
070    private String hostB;
071    private BridgeAction bridgeAction = BridgeAction.create;
072
073    private enum BridgeAction {
074
075        create, change, publicip
076    }
077
078    /**
079     * Element name of the stanza extension.
080     */
081    public static final String NAME = "rtpbridge";
082
083    /**
084     * Element name of the stanza extension.
085     */
086    public static final String ELEMENT_NAME = "rtpbridge";
087
088    /**
089     * Namespace of the stanza extension.
090     */
091    public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";
092
093    static {
094        ProviderManager.addIQProvider(NAME, NAMESPACE, new Provider());
095    }
096
097    /**
098     * Creates a RTPBridge Instance with defined Session ID.
099     *
100     * @param sid TODO javadoc me please
101     */
102    public RTPBridge(String sid) {
103        this();
104        this.sid = sid;
105    }
106
107    /**
108     * Creates a RTPBridge Instance with defined Session ID.
109     *
110     * @param action TODO javadoc me please
111     */
112    public RTPBridge(BridgeAction action) {
113        this();
114        this.bridgeAction = action;
115    }
116
117    /**
118     * Creates a RTPBridge Instance with defined Session ID.
119     *
120     * @param sid TODO javadoc me please
121     * @param bridgeAction TODO javadoc me please
122     */
123    public RTPBridge(String sid, BridgeAction bridgeAction) {
124        this();
125        this.sid = sid;
126        this.bridgeAction = bridgeAction;
127    }
128
129    /**
130     * Creates a RTPBridge Stanza without Session ID.
131     */
132    public RTPBridge() {
133        super(ELEMENT_NAME, NAMESPACE);
134    }
135
136    /**
137     * Get the attributes string.
138     *
139     * @return the attributes.
140     */
141    public String getAttributes() {
142        StringBuilder str = new StringBuilder();
143
144        if (getSid() != null)
145            str.append(" sid='").append(getSid()).append('\'');
146
147        if (getPass() != null)
148            str.append(" pass='").append(getPass()).append('\'');
149
150        if (getPortA() != -1)
151            str.append(" porta='").append(getPortA()).append('\'');
152
153        if (getPortB() != -1)
154            str.append(" portb='").append(getPortB()).append('\'');
155
156        if (getHostA() != null)
157            str.append(" hosta='").append(getHostA()).append('\'');
158
159        if (getHostB() != null)
160            str.append(" hostb='").append(getHostB()).append('\'');
161
162        return str.toString();
163    }
164
165    /**
166     * Get the Session ID of the Stanza (usually same as Jingle Session ID).
167     *
168     * @return the session ID
169     */
170    public String getSid() {
171        return sid;
172    }
173
174    /**
175     * Set the Session ID of the Stanza (usually same as Jingle Session ID).
176     *
177     * @param sid TODO javadoc me please
178     */
179    public void setSid(String sid) {
180        this.sid = sid;
181    }
182
183    /**
184     * Get the Host A IP Address.
185     *
186     * @return the Host A IP Address
187     */
188    public String getHostA() {
189        return hostA;
190    }
191
192    /**
193     * Set the Host A IP Address.
194     *
195     * @param hostA TODO javadoc me please
196     */
197    public void setHostA(String hostA) {
198        this.hostA = hostA;
199    }
200
201    /**
202     * Get the Host B IP Address.
203     *
204     * @return the Host B IP Address
205     */
206    public String getHostB() {
207        return hostB;
208    }
209
210    /**
211     * Set the Host B IP Address.
212     *
213     * @param hostB TODO javadoc me please
214     */
215    public void setHostB(String hostB) {
216        this.hostB = hostB;
217    }
218
219    /**
220     * Get Side A receive port.
221     *
222     * @return the side A receive port
223     */
224    public int getPortA() {
225        return portA;
226    }
227
228    /**
229     * Set Side A receive port.
230     *
231     * @param portA TODO javadoc me please
232     */
233    public void setPortA(int portA) {
234        this.portA = portA;
235    }
236
237    /**
238     * Get Side B receive port.
239     *
240     * @return the side B receive port
241     */
242    public int getPortB() {
243        return portB;
244    }
245
246    /**
247     * Set Side B receive port.
248     *
249     * @param portB TODO javadoc me please
250     */
251    public void setPortB(int portB) {
252        this.portB = portB;
253    }
254
255    /**
256     * Get the RTP Bridge IP.
257     *
258     * @return the RTP Bridge IP
259     */
260    public String getIp() {
261        return ip;
262    }
263
264    /**
265     * Set the RTP Bridge IP.
266     *
267     * @param ip TODO javadoc me please
268     */
269    public void setIp(String ip) {
270        this.ip = ip;
271    }
272
273    /**
274     * Get the RTP Agent Pass.
275     *
276     * @return the RTP Agent Pass
277     */
278    public String getPass() {
279        return pass;
280    }
281
282    /**
283     * Set the RTP Agent Pass.
284     *
285     * @param pass TODO javadoc me please
286     */
287    public void setPass(String pass) {
288        this.pass = pass;
289    }
290
291    /**
292     * Get the name of the Candidate.
293     *
294     * @return the name of the Candidate
295     */
296    public String getName() {
297        return name;
298    }
299
300    /**
301     * Set the name of the Candidate.
302     *
303     * @param name TODO javadoc me please
304     */
305    public void setName(String name) {
306        this.name = name;
307    }
308
309    /**
310     * Get the Child Element XML of the Packet
311     *
312     * @return the Child Element XML of the Packet
313     */
314    @Override
315    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder str) {
316        str.attribute("sid", sid);
317        str.rightAngleBracket();
318
319        if (bridgeAction.equals(BridgeAction.create))
320            str.append("<candidate/>");
321        else if (bridgeAction.equals(BridgeAction.change))
322            str.append("<relay ").append(getAttributes()).append(" />");
323        else
324            str.append("<publicip ").append(getAttributes()).append(" />");
325
326        return str;
327    }
328
329    /**
330     * IQProvider for RTP Bridge packets.
331     * Parse receive RTPBridge stanza to a RTPBridge instance
332     *
333     * @author Thiago Rocha
334     */
335    public static class Provider extends IqProvider<RTPBridge> {
336
337        @Override
338        public RTPBridge parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment, JxmppContext jxmppContext)
339                        throws XmlPullParserException,
340                        IOException {
341
342            boolean done = false;
343
344            XmlPullParser.Event eventType;
345
346            if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
347                // TODO: Should be SmackParseException.
348                throw new IOException("Not a RTP Bridge packet");
349
350            RTPBridge iq = new RTPBridge();
351
352            for (int i = 0; i < parser.getAttributeCount(); i++) {
353                if (parser.getAttributeName(i).equals("sid"))
354                    iq.setSid(parser.getAttributeValue(i));
355            }
356
357            // Start processing sub-elements
358            while (!done) {
359                eventType = parser.next();
360
361                if (eventType == XmlPullParser.Event.START_ELEMENT) {
362                    String elementName = parser.getName();
363                    if (elementName.equals("candidate")) {
364                        for (int i = 0; i < parser.getAttributeCount(); i++) {
365                            if (parser.getAttributeName(i).equals("ip"))
366                                iq.setIp(parser.getAttributeValue(i));
367                            else if (parser.getAttributeName(i).equals("pass"))
368                                iq.setPass(parser.getAttributeValue(i));
369                            else if (parser.getAttributeName(i).equals("name"))
370                                iq.setName(parser.getAttributeValue(i));
371                            else if (parser.getAttributeName(i).equals("porta"))
372                                iq.setPortA(Integer.parseInt(parser.getAttributeValue(i)));
373                            else if (parser.getAttributeName(i).equals("portb"))
374                                iq.setPortB(Integer.parseInt(parser.getAttributeValue(i)));
375                        }
376                    }
377                    else if (elementName.equals("publicip")) {
378                        for (int i = 0; i < parser.getAttributeCount(); i++) {
379                            if (parser.getAttributeName(i).equals("ip"))
380                                iq.setIp(parser.getAttributeValue(i));
381                        }
382                    }
383                }
384                else if (eventType == XmlPullParser.Event.END_ELEMENT) {
385                    if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
386                        done = true;
387                    }
388                }
389            }
390            return iq;
391        }
392    }
393
394    /**
395     * Get a new RTPBridge Candidate from the server.
396     * If a error occurs or the server don't support RTPBridge Service, null is returned.
397     *
398     * @param connection TODO javadoc me please
399     * @param sessionID TODO javadoc me please
400     * @return the new RTPBridge
401     * @throws NotConnectedException if the XMPP connection is not connected.
402     * @throws InterruptedException if the calling thread was interrupted.
403     * @throws XMPPErrorException if there was an XMPP error returned.
404     * @throws NoResponseException if there was no response from the remote entity.
405     */
406    public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
407
408        if (!connection.isConnected()) {
409            return null;
410        }
411
412        RTPBridge rtpPacket = new RTPBridge(sessionID);
413        DomainBareJid jid;
414        try {
415            jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
416        } catch (XmppStringprepException e) {
417            throw new AssertionError(e);
418        }
419        rtpPacket.setTo(jid);
420
421        RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket);
422
423        return response;
424    }
425
426    /**
427     * Check if the server support RTPBridge Service.
428     *
429     * @param connection TODO javadoc me please
430     * @return true if the server supports the RTPBridge service
431     * @throws XMPPErrorException if there was an XMPP error returned.
432     * @throws NoResponseException if there was no response from the remote entity.
433     * @throws NotConnectedException if the XMPP connection is not connected.
434     * @throws InterruptedException if the calling thread was interrupted.
435     */
436    public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException,
437                    XMPPErrorException, NotConnectedException, InterruptedException {
438
439        if (!connection.isConnected()) {
440            return false;
441        }
442
443        LOGGER.fine("Service listing");
444
445        ServiceDiscoveryManager disco = ServiceDiscoveryManager
446                .getInstanceFor(connection);
447//            DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain());
448//            Iterator iter = items.getItems();
449//            while (iter.hasNext()) {
450//                DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
451//                if (item.getEntityID().startsWith("rtpbridge.")) {
452//                    return true;
453//                }
454//            }
455
456        DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain());
457        for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) {
458            if (identity.getName() != null && identity.getName().startsWith("rtpbridge")) {
459                return true;
460            }
461        }
462
463        return false;
464    }
465
466    /**
467     * Check if the server support RTPBridge Service.
468     *
469     * @param connection TODO javadoc me please
470     * @param sessionID the session id.
471     * @param pass the password.
472     * @param proxyCandidate the proxy candidate.
473     * @param localCandidate the local candidate.
474     * @return the RTPBridge
475     * @throws NotConnectedException if the XMPP connection is not connected.
476     * @throws InterruptedException if the calling thread was interrupted.
477     * @throws XMPPErrorException if there was an XMPP error returned.
478     * @throws NoResponseException if there was no response from the remote entity.
479     */
480    public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
481
482        if (!connection.isConnected()) {
483            return null;
484        }
485
486        RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change);
487        DomainBareJid jid;
488        try {
489            jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
490        } catch (XmppStringprepException e) {
491            throw new AssertionError(e);
492        }
493        rtpPacket.setTo(jid);
494        rtpPacket.setType(Type.set);
495
496        rtpPacket.setPass(pass);
497        rtpPacket.setPortA(localCandidate.getPort());
498        rtpPacket.setPortB(proxyCandidate.getPort());
499        rtpPacket.setHostA(localCandidate.getIp());
500        rtpPacket.setHostB(proxyCandidate.getIp());
501
502        // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
503
504        RTPBridge response = connection.sendIqRequestAndWaitForResponse(rtpPacket);
505
506        return response;
507    }
508
509    /**
510     * Get Public Address from the Server.
511     *
512     * @param xmppConnection TODO javadoc me please
513     * @return public IP String or null if not found
514     * @throws NotConnectedException if the XMPP connection is not connected.
515     * @throws InterruptedException if the calling thread was interrupted.
516     * @throws XMPPErrorException if there was an XMPP error returned.
517     * @throws NoResponseException if there was no response from the remote entity.
518     */
519    public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
520
521        if (!xmppConnection.isConnected()) {
522            return null;
523        }
524
525        RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip);
526        DomainBareJid jid;
527        try {
528            jid = JidCreate.domainBareFrom(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain());
529        } catch (XmppStringprepException e) {
530            throw new AssertionError(e);
531        }
532        rtpPacket.setTo(jid);
533        rtpPacket.setType(Type.set);
534
535        // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
536
537        RTPBridge response = xmppConnection.sendIqRequestAndWaitForResponse(rtpPacket);
538
539        if (response == null) return null;
540
541        if (response.getIp() == null || response.getIp().equals("")) return null;
542
543        Enumeration<NetworkInterface> ifaces = null;
544        try {
545            ifaces = NetworkInterface.getNetworkInterfaces();
546        }
547        catch (SocketException e) {
548            LOGGER.log(Level.WARNING, "exception", e);
549        }
550        while (ifaces != null && ifaces.hasMoreElements()) {
551
552            NetworkInterface iface = ifaces.nextElement();
553            Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
554
555            while (iaddresses.hasMoreElements()) {
556                InetAddress iaddress = iaddresses.nextElement();
557                if (!iaddress.isLoopbackAddress())
558                    if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0)
559                        return null;
560
561            }
562        }
563
564        return response.getIp();
565    }
566
567}