package com.door43.translationstudio.service; import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.Nullable; import com.door43.tools.reporting.Logger; import com.door43.translationstudio.R; import com.door43.translationstudio.device2device.SocketMessages; import com.door43.translationstudio.network.Connection; import com.door43.translationstudio.network.Peer; import com.door43.util.RSAEncryption; import com.door43.util.StringUtilities; import java.net.InetAddress; import java.net.Socket; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * Handles communication between peers on the network */ @Deprecated public class PeerService extends NetworkService { private static final String PARAM_PUBLIC_KEY = "param_public_key"; private static final String PARAM_PRIVATE_KEY = "param_private_key"; private final IBinder binder = new LocalBinder(); private OnServiceEventListener listener; private Map<String, Connection> peerConnections = new HashMap<>(); private PrivateKey privateKey; private String publicKey; private static Boolean isRunning = false; /** * Sets whether or not the service is running * @param running */ protected void setRunning(Boolean running) { isRunning = running; } /** * Checks if the service is currently running * @return */ public static boolean isRunning() { return isRunning; } @Nullable @Override public IBinder onBind(Intent intent) { return binder; } /** * Notifies the listener about events with the service and peers * @param listener */ public void setOnServiceEventListener(OnServiceEventListener listener) { this.listener = listener; if(isRunning() && this.listener != null) { this.listener.onReady(); } } @Override public int onStartCommand(Intent intent, int flags, int startid) { if(intent != null) { Bundle args = intent.getExtras(); if (args != null && args.containsKey(PARAM_PRIVATE_KEY) && args.containsKey(PARAM_PUBLIC_KEY)) { privateKey = (PrivateKey) args.get(PARAM_PRIVATE_KEY); publicKey = args.getString(PARAM_PUBLIC_KEY); if (listener != null) { listener.onReady(); } setRunning(true); return START_STICKY; } } Logger.e(this.getClass().getName(), "Peer service requires arguments"); stopService(); return START_NOT_STICKY; } /** * Establishes a TCP connection with a peer. * Once this connection has been made the cleanup thread won't identify the peer as lost unless the tcp connection is also disconnected. * @param peer */ public void connectToPeer(Peer peer) { if(!peerConnections.containsKey(peer.getIpAddress())) { PeerThread peerThread = new PeerThread(peer); new Thread(peerThread).start(); } } /** * Stops the service */ public void stopService() { Logger.i(this.getClass().getName(), "Stopping peer service"); // close sockets for(String key: peerConnections.keySet()) { peerConnections.get(key).close(); } setRunning(false); } @Override public void onDestroy() { stopService(); } /** * Sends a message to the peer * @param peer * @param message the message being sent to the peer */ private void sendMessage(Peer peer, String message) { if (peerConnections.containsKey(peer.getIpAddress())) { if(peer.isSecure()) { // encrypt message PublicKey key = RSAEncryption.getPublicKeyFromString(peer.keyStore.getString(PeerStatusKeys.PUBLIC_KEY)); if(key != null) { message = encryptMessage(key, message); } else { Logger.w(this.getClass().getName(), "Missing the peer's public key"); message = SocketMessages.MSG_EXCEPTION; } } peerConnections.get(peer.getIpAddress()).write(message); } } /** * Handles the initial handshake and authorization with the peer. * @param peer * @param message */ private void onMessageReceived(Peer peer, String message) { if(peer.isSecure()) { message = decryptMessage(privateKey, message); if(message != null) { String[] data = StringUtilities.chunk(message, ":"); onCommandReceived(peer, data[0], Arrays.copyOfRange(data, 1, data.length)); } else if(listener != null) { listener.onError(new Exception("Message descryption failed")); } } else { handshake(peer, message); } } /** * Performs the handshake with the peer * @param peer * @param message */ private void handshake(Peer peer, String message) { String[] data = StringUtilities.chunk(message, ":"); switch(data[0]) { case SocketMessages.MSG_PUBLIC_KEY: Logger.i(this.getClass().getName(), "connected to server " + peer.getIpAddress()); // receive the server's public key peer.keyStore.add(PeerStatusKeys.PUBLIC_KEY, data[1]); peer.keyStore.add(PeerStatusKeys.WAITING, false); peer.keyStore.add(PeerStatusKeys.CONTROL_TEXT, getResources().getString(R.string.browse)); peer.setIsSecure(true); if(listener != null) { listener.onPeerChanged(peer); } break; case SocketMessages.MSG_OK: Logger.i(this.getClass().getName(), "accepted by server " + peer.getIpAddress()); // we are authorized to access the server // send public key to server sendMessage(peer, SocketMessages.MSG_PUBLIC_KEY + ":" + publicKey); break; default: Logger.w(this.getClass().getName(), "Invalid request: " + message); sendMessage(peer, SocketMessages.MSG_INVALID_REQUEST); } } /** * Handles commands sent from the server * @param server * @param command * @param data */ private void onCommandReceived(final Peer server, String command, String[] data) { // TODO: 11/20/2015 place command management in another class switch(command) { case SocketMessages.MSG_INVALID_REQUEST: // TODO: do something about this. if(listener != null) { listener.onError(new Throwable("Invalid request")); } break; default: Logger.i(this.getClass().getName(), "received invalid request from " + server.getIpAddress() + ": " + command); sendMessage(server, SocketMessages.MSG_INVALID_REQUEST); } } public interface OnServiceEventListener { void onReady(); void onPeerLost(Peer peer); void onPeerChanged(Peer peer); void onError(Throwable e); } /** * Class to retrieve instance of service */ public class LocalBinder extends Binder { public PeerService getServiceInstance() { return PeerService.this; } } /** * Manages a single peer connection on it's own thread */ private class PeerThread implements Runnable { private Connection connection; private Peer peer; public PeerThread(Peer peer) { this.peer = peer; } @Override public void run() { // set up sockets try { InetAddress serverAddr = InetAddress.getByName(peer.getIpAddress()); connection = new Connection(new Socket(serverAddr, peer.getPort())); connection.setOnCloseListener(new Connection.OnCloseListener() { @Override public void onClose() { Thread.currentThread().interrupt(); } }); // we store references to all connections so we can access them later peerConnections.put(connection.getIpAddress(), connection); } catch (Exception e) { if(listener != null) { listener.onError(e); } Thread.currentThread().interrupt(); return; } // begin listening to peer while (!Thread.currentThread().isInterrupted()) { String message = connection.readLine(); if(message == null) { Thread.currentThread().interrupt(); } else { onMessageReceived(peer, message); } } // close the connection connection.close(); // remove all instances of the peer if(peerConnections.containsKey(connection.getIpAddress())) { peerConnections.remove(connection.getIpAddress()); } removePeer(peer); if(listener != null) { listener.onPeerLost(peer); } } } }