All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.synergy.nio.SocketConnection Maven / Gradle / Ivy

package com.sshtools.synergy.nio;

/*-
 * #%L
 * Common API
 * %%
 * Copyright (C) 2002 - 2024 JADAPTIVE Limited
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;

import com.sshtools.common.logger.Log;
import com.sshtools.common.nio.IdleStateManager;
import com.sshtools.common.ssh.ConnectionAwareTask;
import com.sshtools.common.ssh.ExecutorOperationQueues;
import com.sshtools.synergy.ssh.Connection;
import com.sshtools.synergy.ssh.SshContext;

/**
 * This class implements a {@link SocketHandler} using a standard socket.
 */
public class SocketConnection implements SocketHandler {
	
    private static final Integer SOCKET_QUEUE = ExecutorOperationQueues.generateUniqueQueue("SocketConnection.in");
    
	protected SocketChannel socketChannel;
    protected ProtocolEngine protocolEngine;
    protected SshEngineContext daemonContext;
    protected SelectorThread selectorThread;
    protected SelectionKey key;
    protected SshEngine daemon;

    protected ByteBuffer socketDataIn;
    protected ByteBuffer socketDataOut;

    protected boolean closed;

    boolean hasInterestedOps = false;

    int currentInterestedOps = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
    int pendingRemoveOps = 0;
    int pendingIncludeOps = 0;

    Object opsLock = new Object();
    LinkedList socketWriteCallbacks = new LinkedList();

	private SocketAddress remoteAddress;

	private int remotePort;

	private int localPort;

	private SocketAddress localAddress;

    /**
     * Construct the socket.
     */
    public SocketConnection() {
    }

    /**
     * Initialize the socket with the protocol engine and the daemon process.
     *
     * @param protocolEngine ProtocolEngine
     * @param daemon Daemon
     * @throws IOException 
     */
    public void initialize(ProtocolEngine protocolEngine, SshEngine daemon, SelectableChannel channel) throws IOException {
        this.protocolEngine = protocolEngine;
        this.daemon = daemon;
        this.daemonContext = daemon.getContext();
        this.socketChannel = (SocketChannel)channel;
        this.localAddress = socketChannel.getLocalAddress();
        this.localPort = socketChannel.socket().getLocalPort();
        this.remoteAddress = socketChannel.getRemoteAddress();
        this.remotePort = socketChannel.socket().getPort();
        
    }

    /**
     * The sockets channel has completed registration.
     *
     * @param channel SelectableChannel
     * @param key SelectionKey
     * @param selectorThread SelectorThread
     * @throws IOException 
     */
    public void registrationCompleted(SelectableChannel channel,
                                      SelectionKey key,
                                      SelectorThread selectorThread) throws IOException {
          
          this.selectorThread = selectorThread;
          this.key = key;
          protocolEngine.onSocketConnect(this);
    }
    
    public void setSelectionKey(SelectionKey key) {
    	this.key = key;
    	hasInterestedOps = false;
    }
    
    /**
     * Set a new ProtocolEngine to handle this sockets data.
     * @param protocolEngine
     */
    public void setProtocolEngine(ProtocolEngine protocolEngine) {
    	this.protocolEngine = protocolEngine;
    }

    /**
     * Close this socket connection.
     */
    public void closeConnection() {
    	 closeConnection(true);
    }
    
    public void closeConnection(boolean closeProtocol) {

        if(!closed) {
            if (socketChannel != null && socketChannel.isOpen()) {
                try {
                    if(Log.isTraceEnabled()) {
                    	Log.trace("Closing socket");
                    }
                    socketChannel.close();
                } catch (IOException ex) {
                }
            }

            if(closeProtocol) {
	            if(Log.isTraceEnabled()) {
	            	Log.trace("Closing protocol engine");
	            }
	            protocolEngine.onSocketClose();
            }
            closed = true;
        }
    }

    /**
     * Get the protocol engine for this socket.
     * @return ProtocolEngine
     */
    public ProtocolEngine getProtocolEngine() {
        return protocolEngine;
    }

    /**
     * Get the daemon process for this socket.
     * @return DaemonContext
     */
    public SshEngineContext getDaemonContext() {
        return daemonContext;
    }

    /**
     * Returns the local address to which the remote socket is connected.
     * @return
     */
    public SocketAddress getLocalAddress(){
      return localAddress;
    }

    /**
     * Returns the local port to which the remote socket is connected.
     * @return
     */
    public int getLocalPort() {
      return localPort;
    }

    /**
     * Returns the local port to which the remote socket is connected.
     * @return
     */
    public int getPort() {
    	return remotePort;
    }
    
    /**
     * Returns the address of the remote socket.
     * @return
     */
    public SocketAddress getRemoteAddress() {
      return remoteAddress;
    }

    /**
     * Get the SocketChannel for this socket.
     * @return SocketChannel
     */
    public SocketChannel getSocketChannel() {
            return socketChannel;
    }

    /**
     * Get the idle state manager.
     * @return IdleStateManager
     */
    public IdleStateManager getIdleStates() {
        return selectorThread.getIdleStates();
    }

    /**
     * Is the current thread this sockets {@link SelectorThread}?
     * @return boolean
     */
    public boolean isSelectorThread() {
        return Thread.currentThread().equals(selectorThread);
    }

  /**
   * Is the socket still connected?
   * @return boolean
   */
  protected boolean isConnected() {
        return socketChannel!=null && socketChannel.isOpen() && protocolEngine.isConnected();
    }

    /**
    * Get the selector thread for this connection
    * @return SelectorThread
    */
    public SelectorThread getThread() {
	    return selectorThread;
    }
    
    /**
     * Get the initial interested ops for this socket.
     * @return int
     */
    public int getInitialOps() {
        return SelectionKey.OP_READ | SelectionKey.OP_WRITE;
    }

    /**
     * The selector is ready to be read.
     *
     * @return boolean
     */
    public synchronized boolean processReadEvent() {

    	if(Log.isTraceEnabled()) {
        	Log.trace("Processing socket READ event");
        }

        try {

          if (!isConnected()) {
            return true;
          }

          // Get a byte buffer if needed
          if(socketDataIn==null) {
              socketDataIn = daemonContext.getBufferPool().get();
          }

          int  numBytesRead = socketChannel.read(socketDataIn);
          //flip buffer so that any remaining data can be read
          socketDataIn.flip();

          if(Log.isTraceEnabled()) {
        	  Log.trace("Read " + numBytesRead + " bytes from socket");
          }
         

          if (numBytesRead == -1) {
              if(Log.isTraceEnabled()) {
            	  Log.trace("Received EOF from remote host");
              }
              closeConnection();
              return true;
          } 
              
          if(socketDataIn.hasRemaining()) {
          	protocolEngine.onSocketRead(socketDataIn);
          }
          
          if(socketDataIn!= null && Log.isTraceEnabled()) {
        	  Log.trace("There is " +  socketDataIn.remaining() + " bytes left to process on socket");
          }

           if (!isConnected()) {
        	   if(Log.isTraceEnabled()) {
        		   Log.trace("Connection is closed, cancelling selectors");
        	   }
          }

          return !isConnected();
        } catch (Throwable ex) {
        	if(Log.isDebugEnabled()) {
        		Log.debug("Connection closed on socket read: " + ex.getMessage());
        	}
        	if(Log.isTraceEnabled()) {
        		Log.trace("Trace: ", ex);
        	}
        	closeConnection();
        	return true;
        } finally {
        	
            if(socketDataIn!=null) {
                if (!socketDataIn.hasRemaining()) {
                	daemonContext.getBufferPool().add(socketDataIn);
                    socketDataIn = null;
                } else {
                	socketDataIn.compact();
                }
            }
        }
    }

    /**
     * The selector is ready to be written to.
     *
     * @return boolean
     */
    public synchronized boolean processWriteEvent() {

        if(Log.isTraceEnabled()) {
        	Log.trace("Processing socket WRITE event");
        }
        if(socketChannel==null || !socketChannel.isOpen()) {
            return true;
        }

        if (socketDataOut == null) {
            socketDataOut = daemonContext.getBufferPool().get();
        }

        try {
        	
        	// Make sure we have sent all buffered data before getting more from protocol engine
             if(socketDataOut.remaining() == socketDataOut.capacity()
            		 && protocolEngine.isConnected()) {
        		SocketWriteCallback c = protocolEngine.onSocketWrite(socketDataOut);
        		if(c!=null)
            	 socketWriteCallbacks.addLast(c);
             }

            socketDataOut.flip();

            // Check before we send that the connection hasn't been closed
            if(!socketChannel.isOpen())
                return true;

            if(socketDataOut.hasRemaining()) {
            	int written = socketChannel.write(socketDataOut);
            	if(Log.isTraceEnabled()) {
            		Log.trace("Written " + written + " bytes to socket");
            	}
            }
            
            // Make sure any unprocessed read data is processed
            if(socketDataIn!=null) {
            	socketDataIn.flip();
            	if(socketDataIn.hasRemaining())
            		protocolEngine.onSocketRead(socketDataIn);
            }
            
            return !isConnected();

        } catch (Throwable ex) {
        	ex.printStackTrace();
            if(Log.isTraceEnabled()) {
            	Log.trace("Connection closed on socket write");
            }
            if(Log.isTraceEnabled()) {
            	Log.trace("Connection error", ex);
            }
            
            closeConnection();
            return true;
        } finally {
        	
            if(socketDataOut!=null) {
                if (!socketDataOut.hasRemaining()) {
                    daemonContext.getBufferPool().add(socketDataOut);
                    socketDataOut = null;
                    
                    for(Iterator it = socketWriteCallbacks.iterator(); it.hasNext() ;) {
                    	it.next().completedWrite();
                    }
                    socketWriteCallbacks.clear();
                } else
                    socketDataOut.compact();
            }
            
            if(socketDataIn!=null) {
                if (!socketDataIn.hasRemaining()) {
                	daemonContext.getBufferPool().add(socketDataIn);
                    socketDataIn = null;
                } else {
                	socketDataIn.compact();
                }
            }
        }
    }

    /**
     * Set the selector thread for this connection
     * 
     * @param thread SelectorThread
     */
	public void setThread(SelectorThread thread) {
		this.selectorThread = thread;
	}
	
	
	public void addTask(ConnectionAwareTask task) {
		protocolEngine.getExecutor().addTask(SOCKET_QUEUE, task);
	}

	@Override
	public synchronized boolean wantsWrite() {
		return (socketDataOut!=null && socketDataOut.hasRemaining()) || (protocolEngine!=null && protocolEngine.wantsToWrite());
	}

	@Override
	public SelectorThread getSelectorThread() {
		return selectorThread;
	}

	public void flagWrite() {
		selectorThread.addSelectorOperation(new Runnable() {
			public void run() {
				if(key.isValid()) {
					if(Log.isTraceEnabled()) {
						Log.trace("Flag selector as READ/WRITE");
					}
					key.interestOps(SelectionKey.OP_WRITE | SelectionKey.OP_READ);
				}
			}
		});
	}
	
	public String getName() {
		return protocolEngine.getName();
	}

	@Override
	public SshContext getContext() {
		return protocolEngine.getContext();
	}

	@Override
	public Connection getConnection() {
		return protocolEngine.getConnection();
	}

	@Override
	public boolean wantsRead() {
		return true;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy