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