From 82aa5730bde3e9c1d2a9ab36924ea152a5a95267 Mon Sep 17 00:00:00 2001 From: Andrew Hennessy Date: Mon, 4 Nov 2013 19:34:40 -0500 Subject: [PATCH] Finished the rewrite of mDNS --- src/com/limelight/nvstream/NvComputer.java | 22 +-- src/com/limelight/nvstream/NvmDNS.java | 212 ++++++++++++--------- 2 files changed, 133 insertions(+), 101 deletions(-) diff --git a/src/com/limelight/nvstream/NvComputer.java b/src/com/limelight/nvstream/NvComputer.java index e60ff5bf..8a495cdb 100644 --- a/src/com/limelight/nvstream/NvComputer.java +++ b/src/com/limelight/nvstream/NvComputer.java @@ -5,7 +5,7 @@ import java.util.Locale; import java.util.UUID; public class NvComputer { - private String mDNSResponse; + private String hostname; private InetAddress ipAddress; private String ipAddressString; private int state; @@ -14,15 +14,15 @@ public class NvComputer { private String mac; private UUID uniqueID; + private int sessionID; private boolean paired; private boolean isBusy; - - public NvComputer(String mDNSResponse, InetAddress ipAddress, String ipAddressString, int state, int numOfApps, String gpuType, String mac, UUID uniqueID) { - this.mDNSResponse = mDNSResponse; + public NvComputer(String hostname, InetAddress ipAddress, int state, int numOfApps, String gpuType, String mac, UUID uniqueID) { + this.hostname = hostname; this.ipAddress = ipAddress; - this.ipAddressString = ipAddressString; + this.ipAddressString = this.ipAddress.getHostAddress(); this.state = state; this.numOfApps = numOfApps; this.gpuType = gpuType; @@ -30,8 +30,8 @@ public class NvComputer { this.uniqueID = uniqueID; } - public String getmDNSResponse() { - return this.mDNSResponse; + public String getHostname() { + return this.hostname; } public InetAddress getIpAddress() { @@ -42,10 +42,6 @@ public class NvComputer { return this.ipAddressString; } - public String getIPAddressString() { - return this.ipAddress.getCanonicalHostName().toLowerCase(Locale.getDefault()); - } - public int getState() { return this.state; } @@ -97,8 +93,8 @@ public class NvComputer { StringBuilder returnStringBuilder = new StringBuilder(); returnStringBuilder.append("NvComputer 0x"); returnStringBuilder.append(Integer.toHexString(this.hashCode()).toUpperCase(Locale.getDefault())); - returnStringBuilder.append("\n|- mDNS Hostname: "); - returnStringBuilder.append(this.mDNSResponse); + returnStringBuilder.append("\n|- Hostname: "); + returnStringBuilder.append(this.hostname); returnStringBuilder.append("\n|- IP Address: "); returnStringBuilder.append(this.ipAddress.toString()); returnStringBuilder.append("\n|- Computer State: "); diff --git a/src/com/limelight/nvstream/NvmDNS.java b/src/com/limelight/nvstream/NvmDNS.java index 5efd2f55..767f9858 100644 --- a/src/com/limelight/nvstream/NvmDNS.java +++ b/src/com/limelight/nvstream/NvmDNS.java @@ -33,8 +33,8 @@ public class NvmDNS extends AsyncTask { public static InetAddress MDNS_MULTICAST_ADDRESS; public static final short MDNS_PORT = 5353; - public static final int WAIT_MS = 5000; - + public static final int WAIT_MS = 100; + private HashSet responses; private MulticastSocket socket; @@ -45,10 +45,10 @@ public class NvmDNS extends AsyncTask { MDNS_MULTICAST_ADDRESS = null; } } - + public NvmDNS() { this.responses = new HashSet(); - + // Create our Socket Connection try { this.socket = new MulticastSocket(NvmDNS.MDNS_PORT); @@ -64,10 +64,10 @@ public class NvmDNS extends AsyncTask { public Set getComputers() { return Collections.unmodifiableSet(this.responses); } - + private void sendQuery() { Header queryHeader = new Header(); - + // If we set the RA (Recursion Available) flag and our message ID to 0 // then the packet matches the real mDNS query packet as displayed in Wireshark queryHeader.setFlag(org.xbill.DNS.Flags.RA); @@ -90,8 +90,8 @@ public class NvmDNS extends AsyncTask { // Convert the message into Network Byte Order byte[] wireQuery = query.toWire(); - Log.v("NvmDNS Query", query.toString()); - + Log.i("NvmDNS Query", query.toString()); + // Convert our byte array into a Packet DatagramPacket transmitPacket = new DatagramPacket(wireQuery, wireQuery.length); transmitPacket.setAddress(NvmDNS.MDNS_MULTICAST_ADDRESS); @@ -99,9 +99,9 @@ public class NvmDNS extends AsyncTask { // And (attempt) to send the packet try { - Log.v("NvmDNS Query", "Blocking on this.nvstream_socket.send(transmitPacket)"); + Log.d("NvmDNS Query", "Blocking on this.nvstream_socket.send(transmitPacket)"); this.socket.send(transmitPacket); - Log.v("NvmDNS Query", "Passed this.nvstream_socket.send(transmitPacket)"); + Log.d("NvmDNS Query", "Passed this.nvstream_socket.send(transmitPacket)"); } catch (IOException e) { Log.e("NvmDNS Query", "There was an error sending the DNS query."); Log.e("NvmDNS Query", e.getMessage()); @@ -110,11 +110,11 @@ public class NvmDNS extends AsyncTask { public void waitForResponses() { Log.v("NvmDNS Response", "mDNS Loop Started"); - + // We support up to 1500 byte packets byte[] data = new byte[1500]; DatagramPacket packet = new DatagramPacket(data, data.length); - + Message message = null; while (!this.socket.isClosed()) { @@ -124,6 +124,7 @@ public class NvmDNS extends AsyncTask { this.socket.receive(packet); Log.d("NvmDNS Response", "Blocking passed on this.nvstream_query_socket.recieve()"); message = new Message(packet.getData()); + this.parseRecord(message, packet.getAddress()); } catch (IOException e) { if (this.socket.isClosed()) { Log.e("NvmDNS Response", "The socket was closed on us. The timer must have been reached."); @@ -134,78 +135,115 @@ public class NvmDNS extends AsyncTask { continue; } } - - // We really only care about the ADDITIONAL section (specifically the text records) - Record[] responses = message.getSectionArray(Section.ADDITIONAL); - - // We only want to process records that actually have a length, have an ANSWER - // section that has stuff in it and that the ANSWER to our query is what we sent - if (responses.length != 0 && + } + } + + private void parseRecord(Message message, InetAddress address) { + // We really only care about the ADDITIONAL section (specifically the text records) + Record[] responses = message.getSectionArray(Section.ADDITIONAL); + // We only want to process records that actually have a length, have an ANSWER + // section that has stuff in it and that the ANSWER to our query is what we sent + if (responses.length != 0 && message.getSectionArray(Section.ANSWER).length != 0 && message.getSectionArray(Section.ANSWER)[0].getName().toString().equals(NvmDNS.MDNS_QUERY)) { - - Log.v("NvmDNS Response", "Got a packet from " + packet.getAddress().getCanonicalHostName()); - Log.v("NvmDNS Response", "Question: " + message.getSectionArray(Section.ANSWER)[0].getName().toString()); - Log.v("NvmDNS Response", "Response: " + responses[0].getName().toString()); - // The DNS library we are using does not use inferred generics :( - - TXTRecord txtRecord = null; - - - for (Record record : responses) { - Log.v("NvmDNS Response", "We recieved a DNS repsonse with a " + record.getClass().getName() + " record."); - if (record instanceof TXTRecord) { - txtRecord = (TXTRecord)record; - } - } - - if (txtRecord == null) { - Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse with no TXTRecord"); - continue; - } - - @SuppressWarnings("unchecked") - ArrayList txtRecordStringList = new ArrayList(txtRecord.getStrings()); - - if (txtRecordStringList.size() != 5) { - Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse with the improper amount of TXTRecord Entries."); - continue; - } - - // The general format of the text records is: - // SERVICE_STATE=1 - // SERVICE_NUMOFAPPS=5 - // SERVICE_GPUTYPE=GeForce GTX 760 x2 - // SERVICE_MAC=DE:AD:BE:EF:CA:FE - // SERVICE_UNIQUEID={A Wild UUID Appeared!} - // Every single record I've seen so far has been in this format - try { - int serviceState = Integer.parseInt(txtRecordStringList.get(0).toString().split("=")[1]); - int numberOfApps = Integer.parseInt(txtRecordStringList.get(1).toString().split("=")[1]); - String gpuType = txtRecordStringList.get(2).toString().split("=")[1]; - String mac = txtRecordStringList.get(3).toString().split("=")[1]; - UUID uniqueID = UUID.fromString(txtRecordStringList.get(4).toString().split("=")[1]); + Log.v("NvmDNS Response", "Got a packet from " + address.getCanonicalHostName()); + Log.v("NvmDNS Response", "Question: " + message.getSectionArray(Section.ANSWER)[0].getName().toString()); + Log.v("NvmDNS Response", "Response: " + responses[0].getName().toString()); + - // We need to resolve the hostname in this thread so that we can use it in the GUI - packet.getAddress().getCanonicalHostName(); - - NvComputer computer = new NvComputer(responses[0].getName().toString(), packet.getAddress(), packet.getAddress().getCanonicalHostName(), serviceState, numberOfApps, gpuType, mac, uniqueID); - this.responses.add(computer); - } catch (ArrayIndexOutOfBoundsException e) { - Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse."); + // TODO: The DNS entry we get is "XENITH._nvstream._tcp.local." + // And the .'s in there are not actually periods. Or something. + String hostname = responses[0].getName().toString(); + + // The records can be returned in any order, so we need to figure out which one is the TXTRecord + // We get three records back: A TXTRecord, a SRVRecord and an ARecord + TXTRecord txtRecord = null; + + for (Record record : responses) { + Log.v("NvmDNS Response", "We recieved a DNS repsonse with a " + record.getClass().getName() + " record."); + if (record instanceof TXTRecord) { + txtRecord = (TXTRecord)record; } } - } + + if (txtRecord == null) { + Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse with no TXTRecord"); + return; + } + + this.parseTXTRecord(txtRecord, address, hostname); + } } + + private void parseTXTRecord(TXTRecord txtRecord, InetAddress address, String hostname) { + // The DNS library we are using does not use inferred generics :( + @SuppressWarnings("unchecked") + ArrayList txtRecordStringList = new ArrayList(txtRecord.getStrings()); + + if (txtRecordStringList.size() != 5) { + Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse with the improper amount of TXTRecord Entries."); + return; + } + + // The general format of the text records is: + // SERVICE_STATE=1 + // SERVICE_NUMOFAPPS=5 + // SERVICE_GPUTYPE=GeForce GTX 760 x2 + // SERVICE_MAC=DE:AD:BE:EF:CA:FE + // SERVICE_UNIQUEID={A Wild UUID Appeared!} + // Every single record I've seen so far has been in this format + try { + int serviceState = Integer.parseInt(this.parseTXTRecordField(txtRecordStringList.get(0), "SERVICE_STATE")); + int numberOfApps = Integer.parseInt(this.parseTXTRecordField(txtRecordStringList.get(1), "SERVICE_NUMOFAPPS")); + String gpuType = this.parseTXTRecordField(txtRecordStringList.get(2), "SERVICE_GPUTYPE"); + String mac = this.parseTXTRecordField(txtRecordStringList.get(3), "SERVICE_MAC"); + UUID uniqueID = UUID.fromString(this.parseTXTRecordField(txtRecordStringList.get(4), "SERVICE_UNIQUEID")); + + // We need to resolve the hostname in this thread so that we can use it in the GUI + address.getCanonicalHostName(); + + NvComputer computer = new NvComputer(hostname, address, serviceState, numberOfApps, gpuType, mac, uniqueID); + this.responses.add(computer); + } catch (ArrayIndexOutOfBoundsException e) { + Log.e("NvmDNS Response", "We recieved a malformed DNS repsonse."); + } + } + + private String parseTXTRecordField(String field, String key) { + // Make sure that our key=value pair actually has our key in it + if (!field.contains(key)) { + return ""; + } + + // Make sure that our key=value pair only has one "=" in it. + if (field.indexOf('=') != field.lastIndexOf('=')) { + return ""; + } + + String[] split = field.split("="); + + if (split.length != 2) { + return ""; + } + + return split[1]; + } + + // What follows is an implementation of Android's AsyncTask. + // The first step is to send our query, then we start our + // RX thread to parse responses. However we only want to accept + // responses for a limited amount of time so we start a new thread + // to kill the socket after a set amount of time + // Then we return control to the foreground thread @Override protected Void doInBackground(Void... thisParameterIsUseless) { Log.v("NvmDNS ASync", "doInBackground entered"); - + this.sendQuery(); - - + + // We want to run our wait thread for an amount of time then close the socket. new Thread(new Runnable() { @Override @@ -222,25 +260,23 @@ public class NvmDNS extends AsyncTask { Log.v("NvmDNS Wait", "Socket Closed"); } }).start(); - - this.waitForResponses(); - + Log.v("NvmDNS ASync", "doInBackground exit"); return null; - } - - @Override - protected void onProgressUpdate(Integer... progress) { - Log.v("NvmDNS ASync", "onProgressUpdate"); - } + } - @Override - protected void onPostExecute(Void moreUselessParameters) { - Log.v("NvmDNS ASync", "onPostExecute"); - for (NvComputer computer : this.responses) { - Log.i("NvmDNS NvComputer", computer.toString()); - } - } + @Override + protected void onProgressUpdate(Integer... progress) { + Log.v("NvmDNS ASync", "onProgressUpdate"); + } + + @Override + protected void onPostExecute(Void moreUselessParameters) { + Log.v("NvmDNS ASync", "onPostExecute"); + for (NvComputer computer : this.responses) { + Log.i("NvmDNS NvComputer", computer.toString()); + } + } }