diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java index 21d5e1a..c81eda1 100644 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/MainActivity.java @@ -198,11 +198,12 @@ public class MainActivity extends AppCompatActivity implements NfcAdapter.Reader } private void vpcdConnect(SCReader scReader) { - SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(this); - int port = Integer.parseInt(SP.getString("port", Integer.toString(VPCDWorker.DEFAULT_PORT))); - String hostname = SP.getString("hostname", VPCDWorker.DEFAULT_HOSTNAME); + final SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(this); + final int port = Integer.parseInt(SP.getString("port", Integer.toString(VPCDWorker.DEFAULT_PORT))); + final String hostname = SP.getString("hostname", VPCDWorker.DEFAULT_HOSTNAME); + final boolean listen = SP.getBoolean("listen", VPCDWorker.DEFAULT_LISTEN); vpcdTest = new VPCDWorker(); - vpcdTest.execute(new VPCDWorker.VPCDWorkerParams(hostname, port, scReader)); + vpcdTest.execute(new VPCDWorker.VPCDWorkerParams(hostname, port, scReader, listen)); } private void vpcdDisconnect() { diff --git a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java index 2de77cc..d0e6b73 100644 --- a/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java +++ b/remote-reader/app/src/main/java/com/vsmartcard/remotesmartcardreader/app/VPCDWorker.java @@ -29,8 +29,17 @@ import com.vsmartcard.remotesmartcardreader.app.screaders.SCReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.ServerSocket; import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; class VPCDWorker extends AsyncTask { @@ -38,18 +47,22 @@ class VPCDWorker extends AsyncTask { final String hostname; final int port; final SCReader reader; - VPCDWorkerParams(String hostname, int port, SCReader reader) { + final boolean listen; + VPCDWorkerParams(String hostname, int port, SCReader reader, boolean listen) { this.hostname = hostname; this.port = port; this.reader = reader; + this.listen = listen; } } public static final int DEFAULT_PORT = 35963; // default URI when used in emulator public static final String DEFAULT_HOSTNAME = "10.0.2.2"; + public static final boolean DEFAULT_LISTEN = false; private SCReader reader; + private ServerSocket listenSocket; private Socket socket; private InputStream inputStream; private OutputStream outputStream; @@ -75,15 +88,22 @@ class VPCDWorker extends AsyncTask { public Void doInBackground(VPCDWorkerParams... params) { try { reader = params[0].reader; - Log.i(this.getClass().getName(), "Connecting to " + params[0].hostname + ":" + Integer.toString(params[0].port) + "..."); - vpcdConnect(params[0].hostname, params[0].port); - Log.i(this.getClass().getName(), "Connected to VPCD"); + vpcdConnection(params[0]); while (!isCancelled()) { + vpcdAccept(); byte[] out = null; byte[] in = receiveFromVPCD(); - if (in == null) - break; + if (in == null) { + if (listenSocket == null) { + Log.i(this.getClass().getName(), "End of stream, finishing"); + break; + } else { + Log.i(this.getClass().getName(), "End of stream, closing connection"); + vpcdCloseClient(); + continue; // back to accept + } + } if (in.length == VPCD_CTRL_LEN) { switch (in[0]) { @@ -169,7 +189,79 @@ class VPCDWorker extends AsyncTask { outputStream.flush(); } + private void vpcdConnection(VPCDWorkerParams params) throws IOException { + if (params.listen){ + vpcdListen(params.port); + return; + } + + Log.i(this.getClass().getName(), "Connecting to " + params.hostname + ":" + Integer.toString(params.port) + "..."); + vpcdConnect(params.hostname, params.port); + Log.i(this.getClass().getName(), "Connected to VPCD"); + } + + private void vpcdListen(int port) throws IOException { + listenSocket = new ServerSocket(port); + + final List ifaceAddresses = new LinkedList<>(); + final Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); + while(ifaces.hasMoreElements()){ + final NetworkInterface iface = ifaces.nextElement(); + if (!iface.isUp() || iface.isLoopback() || iface.isVirtual()) { + continue; + } + for(InterfaceAddress addr : iface.getInterfaceAddresses()){ + final InetAddress inetAddr = addr.getAddress(); + ifaceAddresses.add(inetAddr.getHostAddress()); + } + } + + Log.i(this.getClass().getName(), "Listening on port " + port + ". Local addresses: " + join(", ", ifaceAddresses)); + } + + private void vpcdAccept() throws IOException { + if(listenSocket == null){ + return; + } + + if (socket != null){ + return; // Already accepted, only one client allowed + } + + Log.i(this.getClass().getName(),"Waiting for connections..."); + while(!isCancelled()) { + listenSocket.setSoTimeout(1000); + try { + socket = listenSocket.accept(); + } catch (SocketTimeoutException ignored){} + if (socket != null){ + break; + } + } + + Log.i(this.getClass().getName(),"Connected, " + socket.getInetAddress()); + listenSocket.setSoTimeout(0); + outputStream = socket.getOutputStream(); + inputStream = socket.getInputStream(); + } + + private void vpcdCloseClient(){ + try { + outputStream.close(); + } catch (IOException ignored) { } + try { + inputStream.close(); + } catch (IOException ignored) { } + try { + socket.close(); + } catch (IOException ignored) { } + outputStream = null; + inputStream = null; + socket = null; + } + private void vpcdConnect(String hostname, int port) throws IOException { + listenSocket = null; socket = new Socket(InetAddress.getByName(hostname), port); outputStream = socket.getOutputStream(); inputStream = socket.getInputStream(); @@ -183,5 +275,25 @@ class VPCDWorker extends AsyncTask { socket.close(); Log.i(this.getClass().getName(), "Disconnected from VPCD"); } + if (listenSocket != null) { + Log.i(this.getClass().getName(), "Closing listening socket"); + listenSocket.close(); + } + } + + /** + * Usage of API level 24+ would allow streams(), join can be removed. + */ + private static String join(String separator, List input) { + if (input == null || input.size() <= 0) return ""; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < input.size(); i++) { + sb.append(input.get(i)); + if (i != input.size() - 1) { + sb.append(separator); + } + } + return sb.toString(); + } } \ No newline at end of file diff --git a/remote-reader/app/src/main/res/values/strings.xml b/remote-reader/app/src/main/res/values/strings.xml index b4534e6..dc2e46f 100644 --- a/remote-reader/app/src/main/res/values/strings.xml +++ b/remote-reader/app/src/main/res/values/strings.xml @@ -55,5 +55,6 @@ Clear Log Settings + Listen for connections diff --git a/remote-reader/app/src/main/res/xml/settings.xml b/remote-reader/app/src/main/res/xml/settings.xml index 41c6e0a..5d72274 100644 --- a/remote-reader/app/src/main/res/xml/settings.xml +++ b/remote-reader/app/src/main/res/xml/settings.xml @@ -32,6 +32,12 @@ android:inputType="number" android:hint="Default: 35963" /> + +