diff --git a/moonlight-common/.classpath b/moonlight-common/.classpath index db1e0181..0ac3498e 100644 --- a/moonlight-common/.classpath +++ b/moonlight-common/.classpath @@ -4,5 +4,6 @@ + diff --git a/moonlight-common/libs/jmdns-fixed.jar b/moonlight-common/libs/jmdns-fixed.jar new file mode 100644 index 00000000..b003773c Binary files /dev/null and b/moonlight-common/libs/jmdns-fixed.jar differ diff --git a/moonlight-common/patches/jmdns-3.4.1-changes.patch b/moonlight-common/patches/jmdns-3.4.1-changes.patch new file mode 100644 index 00000000..1f757312 --- /dev/null +++ b/moonlight-common/patches/jmdns-3.4.1-changes.patch @@ -0,0 +1,31 @@ +Index: build.xml +=================================================================== +--- build.xml (revision 353) ++++ build.xml (working copy) +@@ -58,7 +58,7 @@ + + + +- ++ + + + +Index: src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java +=================================================================== +--- src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java (revision 353) ++++ src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java (working copy) +@@ -80,12 +80,7 @@ + protected DNSOutgoing addQuestions(DNSOutgoing out) throws IOException { + DNSOutgoing newOut = out; + if (!_info.hasData()) { +- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); +- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); +- if (_info.getServer().length() > 0) { +- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); +- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); +- } ++ newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_PTR, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE)); + } + return newOut; + } diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java new file mode 100644 index 00000000..6c61414e --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java @@ -0,0 +1,52 @@ +package com.limelight.nvstream.mdns; + +import java.net.InetAddress; +import java.util.UUID; + +public class MdnsComputer { + private InetAddress ipAddr; + private UUID uniqueId; + private String name; + + public MdnsComputer(String name, UUID uniqueId, InetAddress addr) { + this.name = name; + this.uniqueId = uniqueId; + this.ipAddr = addr; + } + + public String getName() { + return name; + } + + public UUID getUniqueId() { + return uniqueId; + } + + public InetAddress getAddress() { + return ipAddr; + } + + @Override + public int hashCode() { + return uniqueId.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof MdnsComputer) { + MdnsComputer other = (MdnsComputer)o; + if (other.uniqueId.equals(uniqueId) && + other.ipAddr.equals(ipAddr) && + other.name.equals(name)) { + return true; + } + } + + return false; + } + + @Override + public String toString() { + return "["+name+" - "+uniqueId+" - "+ipAddr+"]"; + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java new file mode 100644 index 00000000..56ea11f2 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java @@ -0,0 +1,173 @@ +package com.limelight.nvstream.mdns; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import com.jmdns.JmDNS; +import com.jmdns.ServiceEvent; +import com.jmdns.ServiceListener; + +import com.limelight.LimeLog; + +public class MdnsDiscoveryAgent { + public static final String SERVICE_TYPE = "_nvstream._tcp.local."; + + private JmDNS resolver; + private HashMap computers; + private Timer discoveryTimer; + private MdnsDiscoveryListener listener; + private ArrayList pendingResolution; + private ServiceListener nvstreamListener = new ServiceListener() { + public void serviceAdded(ServiceEvent event) { + LimeLog.info("mDNS: Machine appeared: "+event.getInfo().getName()); + + // This machine is pending resolution until we get the serviceResolved callback + pendingResolution.add(event.getInfo().getName()); + + // We call this to kick the resolver + resolver.getServiceInfo(SERVICE_TYPE, event.getInfo().getName()); + } + + public void serviceRemoved(ServiceEvent event) { + LimeLog.info("mDNS: Machine disappeared: "+event.getInfo().getName()); + + Inet4Address addrs[] = event.getInfo().getInet4Addresses(); + for (Inet4Address addr : addrs) { + synchronized (computers) { + MdnsComputer computer = computers.remove(addr); + if (computer != null) { + listener.notifyComputerRemoved(computer); + break; + } + } + } + } + + public void serviceResolved(ServiceEvent event) { + MdnsComputer computer; + + LimeLog.info("mDNS: Machine resolved: "+event.getInfo().getName()); + + pendingResolution.remove(event.getInfo().getName()); + + try { + computer = parseMdnsResponse(event); + } catch (UnsupportedEncodingException e) { + // Invalid DNS response + return; + } + + synchronized (computers) { + if (computers.put(computer.getAddress(), computer) == null) { + // This was a new entry + listener.notifyComputerAdded(computer); + } + } + } + }; + + private static ArrayList parseTxtBytes(byte[] txtBytes) throws UnsupportedEncodingException { + int i = 0; + ArrayList strings = new ArrayList(); + + while (i < txtBytes.length) { + int length = txtBytes[i++]; + + byte[] stringData = Arrays.copyOfRange(txtBytes, i, i+length); + strings.add(new String(stringData, "UTF-8")); + + i += length; + } + + return strings; + } + + private static MdnsComputer parseMdnsResponse(ServiceEvent event) throws UnsupportedEncodingException { + Inet4Address addrs[] = event.getInfo().getInet4Addresses(); + if (addrs.length == 0) { + return null; + } + + Inet4Address address = addrs[0]; + String name = event.getInfo().getName(); + ArrayList txtStrs = parseTxtBytes(event.getInfo().getTextBytes()); + String uniqueId = null; + for (String txtStr : txtStrs) { + if (txtStr.startsWith("SERVICE_UNIQUEID=")) { + uniqueId = txtStr.substring(txtStr.indexOf("=")+1); + break; + } + } + + if (uniqueId == null) { + return null; + } + + UUID uuid; + try { + // fromString() throws an exception for a + // malformed string + uuid = UUID.fromString(uniqueId); + } catch (IllegalArgumentException e) { + // UUID must be properly formed + return null; + } + + return new MdnsComputer(name, uuid, address); + } + + private MdnsDiscoveryAgent(MdnsDiscoveryListener listener) { + computers = new HashMap(); + discoveryTimer = new Timer(); + pendingResolution = new ArrayList(); + this.listener = listener; + } + + public static MdnsDiscoveryAgent createDiscoveryAgent(MdnsDiscoveryListener listener) throws IOException { + MdnsDiscoveryAgent agent = new MdnsDiscoveryAgent(listener); + + agent.resolver = JmDNS.create(); + + return agent; + } + + public void startDiscovery(final int discoveryIntervalMs) { + resolver.addServiceListener(SERVICE_TYPE, nvstreamListener); + + discoveryTimer.schedule(new TimerTask() { + @Override + public void run() { + // Send another mDNS query + resolver.requestServiceInfo(SERVICE_TYPE, null, discoveryIntervalMs); + + // Run service resolution again for pending machines + ArrayList pendingNames = new ArrayList(pendingResolution); + for (String name : pendingNames) { + LimeLog.info("mDNS: Retrying service resolution for machine: "+name); + resolver.getServiceInfo(SERVICE_TYPE, name); + } + } + }, 0, discoveryIntervalMs); + } + + public void stopDiscovery() { + resolver.removeServiceListener(SERVICE_TYPE, nvstreamListener); + + discoveryTimer.cancel(); + } + + public List getComputerSet() { + synchronized (computers) { + return new ArrayList(computers.values()); + } + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java new file mode 100644 index 00000000..057849ca --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java @@ -0,0 +1,7 @@ +package com.limelight.nvstream.mdns; + +public interface MdnsDiscoveryListener { + public void notifyComputerAdded(MdnsComputer computer); + public void notifyComputerRemoved(MdnsComputer computer); + public void notifyDiscoveryFailure(Exception e); +}