summaryrefslogtreecommitdiff
path: root/src/javax/jmdns/impl/DNSRecord.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/javax/jmdns/impl/DNSRecord.java')
-rw-r--r--src/javax/jmdns/impl/DNSRecord.java1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/src/javax/jmdns/impl/DNSRecord.java b/src/javax/jmdns/impl/DNSRecord.java
new file mode 100644
index 0000000..d0ff6f3
--- /dev/null
+++ b/src/javax/jmdns/impl/DNSRecord.java
@@ -0,0 +1,1025 @@
+// Copyright 2003-2005 Arthur van Hoff, Rick Blair
+// Licensed under Apache License version 2.0
+// Original license LGPL
+
+package javax.jmdns.impl;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceInfo;
+import javax.jmdns.ServiceInfo.Fields;
+import javax.jmdns.impl.DNSOutgoing.MessageOutputStream;
+import javax.jmdns.impl.constants.DNSConstants;
+import javax.jmdns.impl.constants.DNSRecordClass;
+import javax.jmdns.impl.constants.DNSRecordType;
+
+/**
+ * DNS record
+ *
+ * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch
+ */
+public abstract class DNSRecord extends DNSEntry {
+ private static Logger logger = Logger.getLogger(DNSRecord.class.getName());
+ private int _ttl;
+ private long _created;
+
+ /**
+ * This source is mainly for debugging purposes, should be the address that sent this record.
+ */
+ private InetAddress _source;
+
+ /**
+ * Create a DNSRecord with a name, type, class, and ttl.
+ */
+ DNSRecord(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl) {
+ super(name, type, recordClass, unique);
+ this._ttl = ttl;
+ this._created = System.currentTimeMillis();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSEntry#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof DNSRecord) && super.equals(other) && sameValue((DNSRecord) other);
+ }
+
+ /**
+ * True if this record has the same value as some other record.
+ */
+ abstract boolean sameValue(DNSRecord other);
+
+ /**
+ * True if this record has the same type as some other record.
+ */
+ boolean sameType(DNSRecord other) {
+ return this.getRecordType() == other.getRecordType();
+ }
+
+ /**
+ * Handles a query represented by this record.
+ *
+ * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured.
+ */
+ abstract boolean handleQuery(JmDNSImpl dns, long expirationTime);
+
+ /**
+ * Handles a response represented by this record.
+ *
+ * @return Returns true if a conflict with one of the services registered with JmDNS or with the hostname occured.
+ */
+ abstract boolean handleResponse(JmDNSImpl dns);
+
+ /**
+ * Adds this as an answer to the provided outgoing datagram.
+ */
+ abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException;
+
+ /**
+ * True if this record is suppressed by the answers in a message.
+ */
+ boolean suppressedBy(DNSIncoming msg) {
+ try {
+ for (DNSRecord answer : msg.getAllAnswers()) {
+ if (suppressedBy(answer)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e);
+ // msg.print(true);
+ return false;
+ }
+ }
+
+ /**
+ * True if this record would be suppressed by an answer. This is the case if this record would not have a significantly longer TTL.
+ */
+ boolean suppressedBy(DNSRecord other) {
+ if (this.equals(other) && (other._ttl > _ttl / 2)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the expiration time of this record.
+ */
+ long getExpirationTime(int percent) {
+ // ttl is in seconds the constant 10 is 1000 ms / 100 %
+ return _created + (percent * _ttl * 10L);
+ }
+
+ /**
+ * Get the remaining TTL for this record.
+ */
+ int getRemainingTTL(long now) {
+ return (int) Math.max(0, (getExpirationTime(100) - now) / 1000);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSEntry#isExpired(long)
+ */
+ @Override
+ public boolean isExpired(long now) {
+ return getExpirationTime(100) <= now;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSEntry#isStale(long)
+ */
+ @Override
+ public boolean isStale(long now) {
+ return getExpirationTime(50) <= now;
+ }
+
+ /**
+ * Reset the TTL of a record. This avoids having to update the entire record in the cache.
+ */
+ void resetTTL(DNSRecord other) {
+ _created = other._created;
+ _ttl = other._ttl;
+ }
+
+ /**
+ * When a record flushed we don't remove it immediately, but mark it for rapid decay.
+ */
+ void setWillExpireSoon(long now) {
+ _created = now;
+ _ttl = DNSConstants.RECORD_EXPIRY_DELAY;
+ }
+
+ /**
+ * Write this record into an outgoing message.
+ */
+ abstract void write(MessageOutputStream out);
+
+ public static class IPv4Address extends Address {
+
+ IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
+ super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, addr);
+ }
+
+ IPv4Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
+ super(name, DNSRecordType.TYPE_A, recordClass, unique, ttl, rawAddress);
+ }
+
+ @Override
+ void write(MessageOutputStream out) {
+ if (_addr != null) {
+ byte[] buffer = _addr.getAddress();
+ // If we have a type A records we should answer with a IPv4 address
+ if (_addr instanceof Inet4Address) {
+ // All is good
+ } else {
+ // Get the last four bytes
+ byte[] tempbuffer = buffer;
+ buffer = new byte[4];
+ System.arraycopy(tempbuffer, 12, buffer, 0, 4);
+ }
+ int length = buffer.length;
+ out.writeBytes(buffer, 0, length);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+
+ ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent);
+ info.addAddress((Inet4Address) _addr);
+ return info;
+ }
+
+ }
+
+ public static class IPv6Address extends Address {
+
+ IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
+ super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, addr);
+ }
+
+ IPv6Address(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
+ super(name, DNSRecordType.TYPE_AAAA, recordClass, unique, ttl, rawAddress);
+ }
+
+ @Override
+ void write(MessageOutputStream out) {
+ if (_addr != null) {
+ byte[] buffer = _addr.getAddress();
+ // If we have a type AAAA records we should answer with a IPv6 address
+ if (_addr instanceof Inet4Address) {
+ byte[] tempbuffer = buffer;
+ buffer = new byte[16];
+ for (int i = 0; i < 16; i++) {
+ if (i < 11) {
+ buffer[i] = tempbuffer[i - 12];
+ } else {
+ buffer[i] = 0;
+ }
+ }
+ }
+ int length = buffer.length;
+ out.writeBytes(buffer, 0, length);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+
+ ServiceInfoImpl info = (ServiceInfoImpl) super.getServiceInfo(persistent);
+ info.addAddress((Inet6Address) _addr);
+ return info;
+ }
+
+ }
+
+ /**
+ * Address record.
+ */
+ public static abstract class Address extends DNSRecord {
+ private static Logger logger1 = Logger.getLogger(Address.class.getName());
+
+ InetAddress _addr;
+
+ protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, InetAddress addr) {
+ super(name, type, recordClass, unique, ttl);
+ this._addr = addr;
+ }
+
+ protected Address(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique, int ttl, byte[] rawAddress) {
+ super(name, type, recordClass, unique, ttl);
+ try {
+ this._addr = InetAddress.getByAddress(rawAddress);
+ } catch (UnknownHostException exception) {
+ logger1.log(Level.WARNING, "Address() exception ", exception);
+ }
+ }
+
+ boolean same(DNSRecord other) {
+ if (! (other instanceof Address) ) {
+ return false;
+ }
+ return ((sameName(other)) && ((sameValue(other))));
+ }
+
+ boolean sameName(DNSRecord other) {
+ return this.getName().equalsIgnoreCase(other.getName());
+ }
+
+ @Override
+ boolean sameValue(DNSRecord other) {
+ if (! (other instanceof Address) ) {
+ return false;
+ }
+ Address address = (Address) other;
+ if ((this.getAddress() == null) && (address.getAddress() != null)) {
+ return false;
+ }
+ return this.getAddress().equals(address.getAddress());
+ }
+
+ @Override
+ public boolean isSingleValued() {
+ return false;
+ }
+
+ InetAddress getAddress() {
+ return _addr;
+ }
+
+ /**
+ * Creates a byte array representation of this record. This is needed for tie-break tests according to draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2.
+ */
+ @Override
+ protected void toByteArray(DataOutputStream dout) throws IOException {
+ super.toByteArray(dout);
+ byte[] buffer = this.getAddress().getAddress();
+ for (int i = 0; i < buffer.length; i++) {
+ dout.writeByte(buffer[i]);
+ }
+ }
+
+ /**
+ * Does the necessary actions, when this as a query.
+ */
+ @Override
+ boolean handleQuery(JmDNSImpl dns, long expirationTime) {
+ if (dns.getLocalHost().conflictWithRecord(this)) {
+ DNSRecord.Address localAddress = dns.getLocalHost().getDNSAddressRecord(this.getRecordType(), this.isUnique(), DNSConstants.DNS_TTL);
+ int comparison = this.compareTo(localAddress);
+
+ if (comparison == 0) {
+ // the 2 records are identical this probably means we are seeing our own record.
+ // With multiple interfaces on a single computer it is possible to see our
+ // own records come in on different interfaces than the ones they were sent on.
+ // see section "10. Conflict Resolution" of mdns draft spec.
+ logger1.finer("handleQuery() Ignoring an identical address query");
+ return false;
+ }
+
+ logger1.finer("handleQuery() Conflicting query detected.");
+ // Tie breaker test
+ if (dns.isProbing() && comparison > 0) {
+ // We lost the tie-break. We have to choose a different name.
+ dns.getLocalHost().incrementHostName();
+ dns.getCache().clear();
+ for (ServiceInfo serviceInfo : dns.getServices().values()) {
+ ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo;
+ info.revertState();
+ }
+ }
+ dns.revertState();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Does the necessary actions, when this as a response.
+ */
+ @Override
+ boolean handleResponse(JmDNSImpl dns) {
+ if (dns.getLocalHost().conflictWithRecord(this)) {
+ logger1.finer("handleResponse() Denial detected");
+
+ if (dns.isProbing()) {
+ dns.getLocalHost().incrementHostName();
+ dns.getCache().clear();
+ for (ServiceInfo serviceInfo : dns.getServices().values()) {
+ ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo;
+ info.revertState();
+ }
+ }
+ dns.revertState();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
+ return out;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+ ServiceInfoImpl info = new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
+ // info.setAddress(_addr); This is done in the sub class so we don't have to test for class type
+ return info;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ public ServiceEvent getServiceEvent(JmDNSImpl dns) {
+ ServiceInfo info = this.getServiceInfo(false);
+ ((ServiceInfoImpl) info).setDns(dns);
+ return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" address: '" + (this.getAddress() != null ? this.getAddress().getHostAddress() : "null") + "'");
+ }
+
+ }
+
+ /**
+ * Pointer record.
+ */
+ public static class Pointer extends DNSRecord {
+ // private static Logger logger = Logger.getLogger(Pointer.class.getName());
+ private final String _alias;
+
+ public Pointer(String name, DNSRecordClass recordClass, boolean unique, int ttl, String alias) {
+ super(name, DNSRecordType.TYPE_PTR, recordClass, unique, ttl);
+ this._alias = alias;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSEntry#isSameEntry(javax.jmdns.impl.DNSEntry)
+ */
+ @Override
+ public boolean isSameEntry(DNSEntry entry) {
+ return super.isSameEntry(entry) && (entry instanceof Pointer) && this.sameValue((Pointer) entry);
+ }
+
+ @Override
+ void write(MessageOutputStream out) {
+ out.writeName(_alias);
+ }
+
+ @Override
+ boolean sameValue(DNSRecord other) {
+ if (! (other instanceof Pointer) ) {
+ return false;
+ }
+ Pointer pointer = (Pointer) other;
+ if ((_alias == null) && (pointer._alias != null)) {
+ return false;
+ }
+ return _alias.equals(pointer._alias);
+ }
+
+ @Override
+ public boolean isSingleValued() {
+ return false;
+ }
+
+ @Override
+ boolean handleQuery(JmDNSImpl dns, long expirationTime) {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ @Override
+ boolean handleResponse(JmDNSImpl dns) {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ String getAlias() {
+ return _alias;
+ }
+
+ @Override
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
+ return out;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+ if (this.isServicesDiscoveryMetaQuery()) {
+ // The service name is in the alias
+ Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias());
+ return new ServiceInfoImpl(map, 0, 0, 0, persistent, (byte[]) null);
+ } else if (this.isReverseLookup()) {
+ return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
+ } else if (this.isDomainDiscoveryQuery()) {
+ // FIXME [PJYF Nov 16 2010] We do not currently support domain discovery
+ return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, (byte[]) null);
+ }
+ Map<Fields, String> map = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getAlias());
+ map.put(Fields.Subtype, this.getQualifiedNameMap().get(Fields.Subtype));
+ return new ServiceInfoImpl(map, 0, 0, 0, persistent, this.getAlias());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ public ServiceEvent getServiceEvent(JmDNSImpl dns) {
+ ServiceInfo info = this.getServiceInfo(false);
+ ((ServiceInfoImpl) info).setDns(dns);
+ String domainName = info.getType();
+ String serviceName = JmDNSImpl.toUnqualifiedName(domainName, this.getAlias());
+ return new ServiceEventImpl(dns, domainName, serviceName, info);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" alias: '" + (_alias != null ? _alias.toString() : "null") + "'");
+ }
+
+ }
+
+ public final static byte[] EMPTY_TXT = new byte[] { 0 };
+
+ public static class Text extends DNSRecord {
+ // private static Logger logger = Logger.getLogger(Text.class.getName());
+ private final byte[] _text;
+
+ public Text(String name, DNSRecordClass recordClass, boolean unique, int ttl, byte text[]) {
+ super(name, DNSRecordType.TYPE_TXT, recordClass, unique, ttl);
+ this._text = (text != null && text.length > 0 ? text : EMPTY_TXT);
+ }
+
+ /**
+ * @return the text
+ */
+ byte[] getText() {
+ return this._text;
+ }
+
+ @Override
+ void write(MessageOutputStream out) {
+ out.writeBytes(_text, 0, _text.length);
+ }
+
+ @Override
+ boolean sameValue(DNSRecord other) {
+ if (! (other instanceof Text) ) {
+ return false;
+ }
+ Text txt = (Text) other;
+ if ((_text == null) && (txt._text != null)) {
+ return false;
+ }
+ if (txt._text.length != _text.length) {
+ return false;
+ }
+ for (int i = _text.length; i-- > 0;) {
+ if (txt._text[i] != _text[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isSingleValued() {
+ return true;
+ }
+
+ @Override
+ boolean handleQuery(JmDNSImpl dns, long expirationTime) {
+ // Nothing to do (?)
+ // I think there is no possibility for conflicts for this record type?
+ return false;
+ }
+
+ @Override
+ boolean handleResponse(JmDNSImpl dns) {
+ // Nothing to do (?)
+ // Shouldn't we care if we get a conflict at this level?
+ /*
+ * ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); if (info != null) { if (! Arrays.equals(text,info.text)) { info.revertState(); return true; } }
+ */
+ return false;
+ }
+
+ @Override
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
+ return out;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+ return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, _text);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ public ServiceEvent getServiceEvent(JmDNSImpl dns) {
+ ServiceInfo info = this.getServiceInfo(false);
+ ((ServiceInfoImpl) info).setDns(dns);
+ return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" text: '" + ((_text.length > 20) ? new String(_text, 0, 17) + "..." : new String(_text)) + "'");
+ }
+
+ }
+
+ /**
+ * Service record.
+ */
+ public static class Service extends DNSRecord {
+ private static Logger logger1 = Logger.getLogger(Service.class.getName());
+ private final int _priority;
+ private final int _weight;
+ private final int _port;
+ private final String _server;
+
+ public Service(String name, DNSRecordClass recordClass, boolean unique, int ttl, int priority, int weight, int port, String server) {
+ super(name, DNSRecordType.TYPE_SRV, recordClass, unique, ttl);
+ this._priority = priority;
+ this._weight = weight;
+ this._port = port;
+ this._server = server;
+ }
+
+ @Override
+ void write(MessageOutputStream out) {
+ out.writeShort(_priority);
+ out.writeShort(_weight);
+ out.writeShort(_port);
+ if (DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET) {
+ out.writeName(_server);
+ } else {
+ // [PJYF Nov 13 2010] Do we still need this? This looks really bad. All label are supposed to start by a length.
+ out.writeUTF(_server, 0, _server.length());
+
+ // add a zero byte to the end just to be safe, this is the strange form
+ // used by the BonjourConformanceTest
+ out.writeByte(0);
+ }
+ }
+
+ @Override
+ protected void toByteArray(DataOutputStream dout) throws IOException {
+ super.toByteArray(dout);
+ dout.writeShort(_priority);
+ dout.writeShort(_weight);
+ dout.writeShort(_port);
+ try {
+ dout.write(_server.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException exception) {
+ /* UTF-8 is always present */
+ }
+ }
+
+ String getServer() {
+ return _server;
+ }
+
+ /**
+ * @return the priority
+ */
+ public int getPriority() {
+ return this._priority;
+ }
+
+ /**
+ * @return the weight
+ */
+ public int getWeight() {
+ return this._weight;
+ }
+
+ /**
+ * @return the port
+ */
+ public int getPort() {
+ return this._port;
+ }
+
+ @Override
+ boolean sameValue(DNSRecord other) {
+ if (! (other instanceof Service) ) {
+ return false;
+ }
+ Service s = (Service) other;
+ return (_priority == s._priority) && (_weight == s._weight) && (_port == s._port) && _server.equals(s._server);
+ }
+
+ @Override
+ public boolean isSingleValued() {
+ return true;
+ }
+
+ @Override
+ boolean handleQuery(JmDNSImpl dns, long expirationTime) {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
+ if (info != null && (info.isAnnouncing() || info.isAnnounced()) && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) {
+ logger1.finer("handleQuery() Conflicting probe detected from: " + getRecordSource());
+ DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns.getLocalHost().getName());
+
+ // This block is useful for debugging race conditions when jmdns is responding to itself.
+ try {
+ if (dns.getInetAddress().equals(getRecordSource())) {
+ logger1.warning("Got conflicting probe from ourselves\n" + "incoming: " + this.toString() + "\n" + "local : " + localService.toString());
+ }
+ } catch (IOException e) {
+ logger1.log(Level.WARNING, "IOException", e);
+ }
+
+ int comparison = this.compareTo(localService);
+
+ if (comparison == 0) {
+ // the 2 records are identical this probably means we are seeing our own record.
+ // With multiple interfaces on a single computer it is possible to see our
+ // own records come in on different interfaces than the ones they were sent on.
+ // see section "10. Conflict Resolution" of mdns draft spec.
+ logger1.finer("handleQuery() Ignoring a identical service query");
+ return false;
+ }
+
+ // Tie breaker test
+ if (info.isProbing() && comparison > 0) {
+ // We lost the tie break
+ String oldName = info.getQualifiedName().toLowerCase();
+ info.setName(dns.incrementName(info.getName()));
+ dns.getServices().remove(oldName);
+ dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
+ logger1.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName());
+
+ // We revert the state to start probing again with the new name
+ info.revertState();
+ } else {
+ // We won the tie break, so this conflicting probe should be ignored
+ // See paragraph 3 of section 9.2 in mdns draft spec
+ return false;
+ }
+
+ return true;
+
+ }
+ return false;
+ }
+
+ @Override
+ boolean handleResponse(JmDNSImpl dns) {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
+ if (info != null && (_port != info.getPort() || !_server.equalsIgnoreCase(dns.getLocalHost().getName()))) {
+ logger1.finer("handleResponse() Denial detected");
+
+ if (info.isProbing()) {
+ String oldName = info.getQualifiedName().toLowerCase();
+ info.setName(dns.incrementName(info.getName()));
+ dns.getServices().remove(oldName);
+ dns.getServices().put(info.getQualifiedName().toLowerCase(), info);
+ logger1.finer("handleResponse() New unique name chose:" + info.getName());
+
+ }
+ info.revertState();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
+ ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(this.getKey());
+ if (info != null) {
+ if (this._port == info.getPort() != _server.equals(dns.getLocalHost().getName())) {
+ return dns.addAnswer(in, addr, port, out, new DNSRecord.Service(info.getQualifiedName(), DNSRecordClass.CLASS_IN, DNSRecordClass.UNIQUE, DNSConstants.DNS_TTL, info.getPriority(), info.getWeight(), info.getPort(), dns
+ .getLocalHost().getName()));
+ }
+ }
+ return out;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+ return new ServiceInfoImpl(this.getQualifiedNameMap(), _port, _weight, _priority, persistent, _server);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ public ServiceEvent getServiceEvent(JmDNSImpl dns) {
+ ServiceInfo info = this.getServiceInfo(false);
+ ((ServiceInfoImpl) info).setDns(dns);
+ // String domainName = "";
+ // String serviceName = this.getServer();
+ // int index = serviceName.indexOf('.');
+ // if (index > 0)
+ // {
+ // serviceName = this.getServer().substring(0, index);
+ // if (index + 1 < this.getServer().length())
+ // domainName = this.getServer().substring(index + 1);
+ // }
+ // return new ServiceEventImpl(dns, domainName, serviceName, info);
+ return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" server: '" + _server + ":" + _port + "'");
+ }
+
+ }
+
+ public static class HostInformation extends DNSRecord {
+ String _os;
+ String _cpu;
+
+ /**
+ * @param name
+ * @param recordClass
+ * @param unique
+ * @param ttl
+ * @param cpu
+ * @param os
+ */
+ public HostInformation(String name, DNSRecordClass recordClass, boolean unique, int ttl, String cpu, String os) {
+ super(name, DNSRecordType.TYPE_HINFO, recordClass, unique, ttl);
+ _cpu = cpu;
+ _os = os;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#addAnswer(javax.jmdns.impl.JmDNSImpl, javax.jmdns.impl.DNSIncoming, java.net.InetAddress, int, javax.jmdns.impl.DNSOutgoing)
+ */
+ @Override
+ DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException {
+ return out;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#handleQuery(javax.jmdns.impl.JmDNSImpl, long)
+ */
+ @Override
+ boolean handleQuery(JmDNSImpl dns, long expirationTime) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#handleResponse(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ boolean handleResponse(JmDNSImpl dns) {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#sameValue(javax.jmdns.impl.DNSRecord)
+ */
+ @Override
+ boolean sameValue(DNSRecord other) {
+ if (! (other instanceof HostInformation) ) {
+ return false;
+ }
+ HostInformation hinfo = (HostInformation) other;
+ if ((_cpu == null) && (hinfo._cpu != null)) {
+ return false;
+ }
+ if ((_os == null) && (hinfo._os != null)) {
+ return false;
+ }
+ return _cpu.equals(hinfo._cpu) && _os.equals(hinfo._os);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#isSingleValued()
+ */
+ @Override
+ public boolean isSingleValued() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#write(javax.jmdns.impl.DNSOutgoing)
+ */
+ @Override
+ void write(MessageOutputStream out) {
+ String hostInfo = _cpu + " " + _os;
+ out.writeUTF(hostInfo, 0, hostInfo.length());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceInfo(boolean)
+ */
+ @Override
+ public ServiceInfo getServiceInfo(boolean persistent) {
+ Map<String, String> hinfo = new HashMap<String, String>(2);
+ hinfo.put("cpu", _cpu);
+ hinfo.put("os", _os);
+ return new ServiceInfoImpl(this.getQualifiedNameMap(), 0, 0, 0, persistent, hinfo);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.jmdns.impl.DNSRecord#getServiceEvent(javax.jmdns.impl.JmDNSImpl)
+ */
+ @Override
+ public ServiceEvent getServiceEvent(JmDNSImpl dns) {
+ ServiceInfo info = this.getServiceInfo(false);
+ ((ServiceInfoImpl) info).setDns(dns);
+ return new ServiceEventImpl(dns, info.getType(), info.getName(), info);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" cpu: '" + _cpu + "' os: '" + _os + "'");
+ }
+
+ }
+
+ /**
+ * Determine if a record can have multiple values in the cache.
+ *
+ * @return <code>false</code> if this record can have multiple values in the cache, <code>true</code> otherwise.
+ */
+ public abstract boolean isSingleValued();
+
+ /**
+ * Return a service information associated with that record if appropriate.
+ *
+ * @return service information
+ */
+ public ServiceInfo getServiceInfo() {
+ return this.getServiceInfo(false);
+ }
+
+ /**
+ * Return a service information associated with that record if appropriate.
+ *
+ * @param persistent
+ * if <code>true</code> ServiceListener.resolveService will be called whenever new new information is received.
+ * @return service information
+ */
+ public abstract ServiceInfo getServiceInfo(boolean persistent);
+
+ /**
+ * Creates and return a service event for this record.
+ *
+ * @param dns
+ * DNS serviced by this event
+ * @return service event
+ */
+ public abstract ServiceEvent getServiceEvent(JmDNSImpl dns);
+
+ public void setRecordSource(InetAddress source) {
+ this._source = source;
+ }
+
+ public InetAddress getRecordSource() {
+ return _source;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.webobjects.discoveryservices.DNSRecord#toString(java.lang.StringBuilder)
+ */
+ @Override
+ protected void toString(StringBuilder aLog) {
+ super.toString(aLog);
+ aLog.append(" ttl: '" + getRemainingTTL(System.currentTimeMillis()) + "/" + _ttl + "'");
+ }
+
+ public void setTTL(int ttl) {
+ this._ttl = ttl;
+ }
+
+ public int getTTL() {
+ return _ttl;
+ }
+}