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