/** * This file is part of SecureNIO. Copyright (C) 2014 K. Dermitzakis * <dermitza@gmail.com> * * SecureNIO is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * SecureNIO is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with SecureNIO. If not, see <http://www.gnu.org/licenses/>. */ package ch.dermitza.securenio.test.variablebyte; import ch.dermitza.securenio.AbstractSelector; import ch.dermitza.securenio.packet.PacketIF; import ch.dermitza.securenio.packet.worker.AbstractPacketWorker; import ch.dermitza.securenio.socket.PlainSocket; import ch.dermitza.securenio.socket.SocketIF; import ch.dermitza.securenio.socket.secure.SecureSocket; import ch.dermitza.securenio.util.PropertiesReader; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.StandardSocketOptions; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import javax.net.ssl.SSLEngine; /** * A client implementation with multiple sockets, used for testing. * * @author K. Dermitzakis * @version 0.19 * @since 0.19 */ public class MultiSocketClient extends AbstractSelector { private final int socketNo; private SocketIF[] sockets; private boolean connected = false; /** * Create a TCPClient instance * * @param address The address to connect to * @param port The port to connect to * @param packetWorker The instance of packet worker to use * @param usingSSL Whether we are using SSL/TLS * @param needClientAuth Whether this client should also authenticate with * the server. * @param socketNo the number of sockets this client will have */ public MultiSocketClient(InetAddress address, int port, AbstractPacketWorker packetWorker, boolean usingSSL, boolean needClientAuth, int socketNo) { super(address, port, packetWorker, usingSSL, true, needClientAuth); this.socketNo = socketNo; } /** * Send an {@link PacketIF} over the this client's {@link SocketIF}. Since * the client only has one socket, no socket parameter is necessary. * * @param packet The PacketIF to send through the associated SocketIF. * * @see AbstractSelector#send(ch.dermitza.securenio.socket.SocketIF, * java.nio.ByteBuffer) */ public void send(PacketIF packet) { // Sometimes during testing send is called before the socket is // even initialized. Does this happen on actual single client code? // Send through all sockets for (int i = 0; i < socketNo; i++) { if (sockets[i] != null) { send(sockets[i], packet.toBytes()); } } } /** * Invalidate the SSL/TLS session (if any) on the underlying * {@link SocketIF}. As this client implementation only has a single socket, * no parameter is needed. * * @see AbstractSelector#invalidateSession(SocketIF) */ @Override public void invalidateSession(SocketIF socket) { if (socket != null) { invalidateSession(socket); } } public boolean handshakesFinished() { for (int i = 0; i < socketNo; i++) { if (sockets[i].handshakePending()) { return false; } } return true; } /** * Initialize a client connection. This method initializes a * {@link SocketChannel}, configures it to non-blocking, and registers it * with the underlying {@link java.nio.channels.Selector} instance with an * OP_CONNECT {@link SelectionKey}. If this client implementation is using * SSL/TLS, it also sets up the {@link SSLEngine}, to be used. * * @throws IOException Propagates all underlying IOExceptions as thrown, to * be handled by the application layer. * * @see AbstractSelector#run() */ @Override protected void initConnection() throws IOException { SocketIF sc; sockets = new SocketIF[socketNo]; for (int i = 0; i < socketNo; i++) { SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(new InetSocketAddress(address, port)); // As part of the registration we'll register // an interest in connection events. These are raised when a channel // is ready to complete connection establishment. channel.register(selector, SelectionKey.OP_CONNECT); channel.setOption(StandardSocketOptions.SO_SNDBUF, PropertiesReader.getSoSndBuf()); channel.setOption(StandardSocketOptions.SO_RCVBUF, PropertiesReader.getSoRcvBuf()); channel.setOption(StandardSocketOptions.SO_KEEPALIVE, PropertiesReader.getKeepAlive()); channel.setOption(StandardSocketOptions.SO_REUSEADDR, PropertiesReader.getReuseAddress()); channel.setOption(StandardSocketOptions.IP_TOS, PropertiesReader.getIPTos()); // now wrap the channel if (usingSSL) { String peerHost = channel.socket().getInetAddress().getHostAddress(); int peerPort = channel.socket().getPort(); SSLEngine engine = setupEngine(peerHost, peerPort); sc = new SecureSocket(channel, engine, singleThreaded, taskWorker, toWorker, this, this); } else { sc = new PlainSocket(channel); } // add the socket to the container container.addSocket(sc.getSocket(), sc); sockets[i] = sc; } } /** * As this is the client implementation, it is NOT allowed to call this * method which is only useful for server implementations. This * implementation will throw a {@link NoSuchMethodError} if it is called and * do nothing else. * * @param key The selection key with the underlying {@link SocketChannel} to * be accepted * * @see AbstractSelector#run() */ @Override protected void accept(SelectionKey key) { // This is severe if it happens throw new NoSuchMethodError("accept() is never called in client"); } /** * Finish the connection to the server. This method also instantiates an * SSLEngine handshake if the underlying {@link SocketIF} is a secure * socket. Finally, after the connection has been established, the socket is * registered to the underlying {@link java.nio.channels.Selector}, with a * {@link SelectionKey} of OP_READ, signalling it is ready to read data. * * @param key The selection key with the underlying {@link SocketChannel} * that needs a connection finalization. */ @Override protected void connect(SelectionKey key) { // Finish the connection. If the connection operation failed // this will raise an IOException. try { container.getSocket(key.channel()).finishConnect(); } catch (IOException e) { // Cancel the channel's registration with our selector // since it faled to connect. At this point, there is no // reason to keep the client running. Perhaps we can issue // a reconnection attempt at a later stage, TODO. setRunning(false); System.out.println(e); key.cancel(); return; } // We are connected at this point connected = true; // Register an interest in writing on this channel key.interestOps(SelectionKey.OP_READ); } /** * Returns whether or not this client is connected, if and only if it is * running (returns false otherwise). * * @return whether or not this client is connected, if and only if it is * running (returns false otherwise) */ public boolean isConnected() { return isRunning() ? this.connected : false; } }