Package com.brewtab.irc.impl

Source Code of com.brewtab.irc.impl.ClientImpl

/*
* Copyright (c) 2013 Christopher Thunes
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.brewtab.irc.impl;

import java.net.SocketAddress;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.ssl.SslHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.brewtab.irc.Connection;
import com.brewtab.irc.ConnectionException;
import com.brewtab.irc.ConnectionStateListener;
import com.brewtab.irc.NotConnectedException;
import com.brewtab.irc.User;
import com.brewtab.irc.client.Channel;
import com.brewtab.irc.client.Client;
import com.brewtab.irc.client.NickNameInUseException;
import com.brewtab.irc.messages.Message;
import com.brewtab.irc.messages.MessageListener;
import com.brewtab.irc.messages.MessageType;
import com.brewtab.irc.messages.filter.MessageFilters;

class ClientImpl implements Client {
    private static final Logger log = LoggerFactory.getLogger(ClientImpl.class);

    /* Default SSL context, initialized lazily */
    private static SSLContext defaultSSLContext = null;

    /* SSLContext used by this client, may be specified externally */
    private SSLContext sslContext = null;

    /* Netty objects */
    private ClientBootstrap bootstrap;

    /* Connection information */
    private String nick;
    private String username;
    private String hostname;
    private String realName;

    private ConnectionImpl connection;

    private boolean connected;

    /**
     * Construct a new IRCClient connected to the given address
     *
     * @param address The address to connect to
     */
    ClientImpl() {
        this.connection = new ConnectionImpl();

        this.nick = null;
        this.username = null;
        this.hostname = null;
        this.realName = null;

        this.connected = false;

        this.connection.addMessageListener(
            MessageFilters.message(MessageType.PING, (String) null),
            new MessageListener() {
                @Override
                public void onMessage(Message message) {
                    connection.send(new Message(MessageType.PONG, message.getArgs()));
                }
            });
    }

    void setSSLContext(SSLContext sslContext) {
        this.sslContext = sslContext;
    }

    SSLContext getSSLContext() {
        if (sslContext == null) {
            sslContext = getDefaultSSLContext();
        }

        return sslContext;
    }

    private static TrustManager createNonValidatingTrustManager() {
        return new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Pass everything
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Pass everything
            }
        };
    }

    private static SSLContext getDefaultSSLContext() {
        if (defaultSSLContext == null) {
            try {
                defaultSSLContext = SSLContext.getInstance("TLS");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("runtime does not support TLS", e);
            }

            try {
                defaultSSLContext.init(null, new TrustManager[] { createNonValidatingTrustManager() }, null);
            } catch (KeyManagementException e) {
                throw new RuntimeException("failed to initialize default SSLContext", e);
            }
        }

        return defaultSSLContext;
    }

    private SslHandler getClientSSLHandler() {
        SSLContext context = getSSLContext();
        SSLEngine engine = context.createSSLEngine();
        engine.setUseClientMode(true);

        return new SslHandler(engine);
    }

    private void doSSLHandshake(SslHandler sslHandler) {
        log.debug("performing SSL handshake");
        ChannelFuture handshakeFuture = sslHandler.handshake();

        try {
            handshakeFuture.await();
        } catch (InterruptedException e) {
            throw new ConnectionException("Interrupted while performing SSL handshake");
        }

        if (!handshakeFuture.isSuccess()) {
            throw new ConnectionException("error performing SSL handshake", handshakeFuture.getCause());
        }
    }

    /**
     * Connect to the remote server and perform the SSL handshake if SSL is being used.
     */
    void connect(SocketAddress socketAddress, boolean useSSL) {
        ChannelFactory channelFactory = new NioClientSocketChannelFactory(
            Executors.newCachedThreadPool(),
            Executors.newCachedThreadPool());

        ChannelPipeline clientPipeline = NettyChannelPipeline.newPipeline(connection);
        SslHandler sslHandler = null;

        if (useSSL) {
            sslHandler = getClientSSLHandler();
            clientPipeline.addFirst("ssl", sslHandler);
        }

        bootstrap = new ClientBootstrap(channelFactory);
        bootstrap.setOption("tcpNoDelay", true);
        bootstrap.setPipeline(clientPipeline);

        /*
         * The connection future and the connection itself aren't perfectly
         * synchronized so we have to wait for both to acknowledge that the
         * connection has been made
         */
        final CountDownLatch connectedLatch = new CountDownLatch(1);
        ConnectionStateListener connectedListener = new ConnectionStateListener() {
            @Override
            public void onConnectionConnected() {
                connectedLatch.countDown();
            }

            @Override
            public void onConnectionClosing() {
            }

            @Override
            public void onConnectionClosed() {
            }
        };

        connection.addConnectionStateListener(connectedListener);

        try {
            /* Perform connection */
            ChannelFuture future = bootstrap.connect(socketAddress);

            log.debug("connecting");

            future.await();

            if (future.isSuccess()) {
                /* This await should be very brief */
                connectedLatch.await();

                log.debug("connected successfully");

                if (useSSL) {
                    doSSLHandshake(sslHandler);
                }

                connected = true;
            } else {
                log.debug("connection failed");
                throw new ConnectionException(future.getCause());
            }
        } catch (InterruptedException e) {
            throw new ConnectionException("Interrupted while connecting");
        } finally {
            connection.removeConnectionStateListener(connectedListener);
        }
    }

    void registerConnection(String nick, String username, String hostname, String realName, String password) {
        log.debug("registering connection");

        Message nickMessage = new Message(MessageType.NICK, nick);
        Message userMessage = new Message(MessageType.USER, username, hostname, hostname, realName);

        if (password != null) {
            /* Send a PASS message first */
            connection.send(new Message(MessageType.PASS, password));
        }

        Message response;

        try {
            response = connection.request(
                null,
                MessageFilters.any(
                    MessageFilters.message(MessageType.RPL_ENDOFMOTD),
                    MessageFilters.message(MessageType.RPL_LUSERME),
                    MessageFilters.message(MessageType.RPL_LUSERCHANNELS),
                    MessageFilters.message(MessageType.RPL_LUSERCLIENT),
                    MessageFilters.message(MessageType.RPL_LUSEROP),
                    MessageFilters.message(MessageType.RPL_LUSERUNKNOWN),
                    MessageFilters.message(MessageType.ERR_NICKNAMEINUSE)),
                nickMessage, userMessage).get(0);
        } catch (InterruptedException e) {
            throw new ConnectionException("Interrupted while awaiting connection registration response");
        }

        switch (response.getType()) {
        case ERR_NICKNAMEINUSE:
            throw new NickNameInUseException();
        default:
            log.debug("connection registered");
            break;
        }

        this.nick = nick;
        this.username = username;
        this.hostname = hostname;
        this.realName = realName;
    }

    /**
     * Quit from the server and close all network connections
     *
     * @param reason The reason for quitting as given in the QUIT message
     */
    @Override
    public void quit(String reason) {
        if (!connected) {
            throw new NotConnectedException();
        }

        Message quit = new Message(MessageType.QUIT, reason);
        ChannelFuture future = connection.send(quit);

        /* Wait for message to be sent and then close the underlying connection */
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                connection.close();
            }
        });
    }

    @Override
    public void quit() {
        quit("Brewtab IRC client leaving");
    }

    /**
     * Join the given channel. Create a new channel object for this connection,
     * join the channel and return the IRCChannel object.
     *
     * @param channelName The channel to join
     * @return the new IRCChannel object or null if the channel could not be
     *         joined
     */
    @Override
    public Channel join(String channelName) {
        if (!connected) {
            throw new NotConnectedException();
        }

        ChannelImpl channel = new ChannelImpl(this, channelName);

        /* Attempt to join */
        if (channel.join()) {
            return channel;
        }

        /* Error while joining */
        return null;
    }

    @Override
    public void sendMessage(User user, String message) {
        if (!connected) {
            throw new NotConnectedException();
        }

        connection.send(new Message(MessageType.PRIVMSG, user.getNick(), message));
    }

    @Override
    public String getNick() {
        return nick;
    }

    @Override
    public void setNick(String nick) {
        if (connected) {
            try {
                Message response = connection.request(
                    null,
                    MessageFilters.any(
                        MessageFilters.message(MessageType.ERR_NICKNAMEINUSE),
                        MessageFilters.message(MessageType.NICK, nick)),
                    new Message(MessageType.NICK, nick)).get(0);

                if (response.getType() == MessageType.ERR_NICKNAMEINUSE) {
                    throw new NickNameInUseException();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("request interrupted");
            }
        }
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getHostname() {
        return hostname;
    }

    @Override
    public String getRealName() {
        return realName;
    }

    @Override
    public Connection getConnection() {
        return connection;
    }
}
TOP

Related Classes of com.brewtab.irc.impl.ClientImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.