package eu.hgross.blaubot.ethernet; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import eu.hgross.blaubot.core.IBlaubotAdapter; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor; import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener; import eu.hgross.blaubot.core.acceptor.IBlaubotListeningStateListener; import eu.hgross.blaubot.core.acceptor.discovery.BeaconMessage; import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore; import eu.hgross.blaubot.util.Log; /** * Acceptor for ethernet * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ public class BlaubotEthernetAcceptor implements IBlaubotConnectionAcceptor { private final IBlaubotDevice ownDevice; private final int acceptorPort; private final IBlaubotAdapter adapter; private final InetAddress ipAddress; private volatile EthernetAcceptThread acceptThread; private volatile IBlaubotListeningStateListener listeningStateListener; private volatile IBlaubotIncomingConnectionListener incomingConnectionListener; private final Object startStopMonitor; /** * Monitor to avoid two EthernetAcceptThread are executing at the same time on this instance. * (could happen on fast activate/deactivate calls) */ private final Object acceptThreadLock = new Object(); private IBlaubotBeaconStore beaconStore; public BlaubotEthernetAcceptor(IBlaubotAdapter adapter, IBlaubotDevice ownDevice, InetAddress ipAddress, int acceptorPort) { // TODO: remove ipAddress dependency here and get it from the ServerSocket this.adapter = adapter; this.ownDevice = ownDevice; this.ipAddress = ipAddress; this.acceptorPort = acceptorPort; this.startStopMonitor = new Object(); } @Override public void setBeaconStore(IBlaubotBeaconStore beaconStore) { this.beaconStore = beaconStore; } @Override public IBlaubotAdapter getAdapter() { return adapter; } @Override public void startListening() { synchronized (startStopMonitor) { if (acceptThread != null) { // stop thread and create new acceptThread.interrupt(); acceptThread = null; } EthernetAcceptThread acceptThread = new EthernetAcceptThread(); this.acceptThread = acceptThread; acceptThread.start(); } } @Override public void stopListening() { synchronized (startStopMonitor) { if (acceptThread == null) { return; } this.acceptThread.interrupt(); try { this.acceptThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } this.acceptThread = null; } } @Override public boolean isStarted() { synchronized (startStopMonitor) { return acceptThread != null; } } @Override public void setListeningStateListener(IBlaubotListeningStateListener stateListener) { this.listeningStateListener = stateListener; } @Override public void setAcceptorListener(IBlaubotIncomingConnectionListener acceptorListener) { this.incomingConnectionListener = acceptorListener; } @Override public ConnectionMetaDataDTO getConnectionMetaData() { String ipStr = ipAddress.getHostAddress(); final ConnectionMetaDataDTO connectionMetaDataDTO = new EthernetConnectionMetaDataDTO(ipStr, acceptorPort); return connectionMetaDataDTO; } private class EthernetAcceptThread extends Thread { private static final String LOG_TAG = "EthernetAcceptThread"; private volatile ServerSocket serverSocket = null; public EthernetAcceptThread() { setName("ethernet-acceptor-accept-thread"); } @Override public void interrupt() { super.interrupt(); try { if(serverSocket != null) { serverSocket.close(); } } catch (IOException e) { // nothing interesting } } @Override public void run() { synchronized (acceptThreadLock) { try { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Accept thread started, waiting for incoming connections ..."); } ServerSocket serverSocket = new ServerSocket(); serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(acceptorPort)); this.serverSocket = serverSocket; if (acceptThread != null && this == acceptThread) { notify_listening_started(); } final int connectionTimeout = adapter.getBlaubotAdapterConfig().getConnectionTimeout(); while (!isInterrupted() && acceptThread == Thread.currentThread()) { Socket socket = serverSocket.accept(); socket.setSoTimeout(connectionTimeout); if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Got new client connection from " + socket.getInetAddress().toString()); } BlaubotEthernetConnection connection = BlaubotEthernetUtils.getEthernetConnectionFromSocket(socket); if (connection == null) { if (Log.logWarningMessages()) { Log.w(LOG_TAG, "Failed to create connection from incoming socket. Closing connection."); } continue; } // retrieve their beacon message with their state and most importantly their acceptor meta data final BeaconMessage theirBeaconMessage = BeaconMessage.fromBlaubotConnection(connection); beaconStore.putDiscoveryEvent(theirBeaconMessage, connection.getRemoteDevice()); if (incomingConnectionListener != null) { incomingConnectionListener.onConnectionEstablished(connection); } } serverSocket.close(); } catch (IOException e) { if (serverSocket != null && !serverSocket.isClosed()) { if (Log.logErrorMessages()) { Log.e(LOG_TAG, "Socket I/O failed (" + e.getMessage() + ")", e); } } } finally { if (this.serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { } finally { serverSocket = null; } } if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Acceptors accept thread stopped ..."); } // only notify, if we are the thread in action notify_listening_stopped(); } } } } private void notify_listening_stopped() { if (listeningStateListener != null) listeningStateListener.onListeningStopped(this); } private void notify_listening_started() { if (listeningStateListener != null) listeningStateListener.onListeningStarted(this); } }