// 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.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Collections; import java.util.Map; import javax.jmdns.ServiceInfo.Fields; import javax.jmdns.impl.constants.DNSRecordClass; import javax.jmdns.impl.constants.DNSRecordType; /** * DNS entry with a name, type, and class. This is the base class for questions and records. * * @author Arthur van Hoff, Pierre Frisch, Rick Blair */ public abstract class DNSEntry { // private static Logger logger = Logger.getLogger(DNSEntry.class.getName()); private final String _key; private final String _name; private final String _type; private final DNSRecordType _recordType; private final DNSRecordClass _dnsClass; private final boolean _unique; final Map<Fields, String> _qualifiedNameMap; /** * Create an entry. */ DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { _name = name; // _key = (name != null ? name.trim().toLowerCase() : null); _recordType = type; _dnsClass = recordClass; _unique = unique; _qualifiedNameMap = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getName()); String domain = _qualifiedNameMap.get(Fields.Domain); String protocol = _qualifiedNameMap.get(Fields.Protocol); String application = _qualifiedNameMap.get(Fields.Application); String instance = _qualifiedNameMap.get(Fields.Instance).toLowerCase(); _type = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; _key = ((instance.length() > 0 ? instance + "." : "") + _type).toLowerCase(); } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { boolean result = false; if (obj instanceof DNSEntry) { DNSEntry other = (DNSEntry) obj; result = this.getKey().equals(other.getKey()) && this.getRecordType().equals(other.getRecordType()) && this.getRecordClass() == other.getRecordClass(); } return result; } /** * Check if two entries have exactly the same name, type, and class. * * @param entry * @return <code>true</code> if the two entries have are for the same record, <code>false</code> otherwise */ public boolean isSameEntry(DNSEntry entry) { return this.getKey().equals(entry.getKey()) && this.getRecordType().equals(entry.getRecordType()) && ((DNSRecordClass.CLASS_ANY == entry.getRecordClass()) || this.getRecordClass().equals(entry.getRecordClass())); } /** * Check if two entries have the same subtype. * * @param other * @return <code>true</code> if the two entries have are for the same subtype, <code>false</code> otherwise */ public boolean sameSubtype(DNSEntry other) { return this.getSubtype().equals(other.getSubtype()); } /** * Returns the subtype of this entry * * @return subtype of this entry */ public String getSubtype() { String subtype = this.getQualifiedNameMap().get(Fields.Subtype); return (subtype != null ? subtype : ""); } /** * Returns the name of this entry * * @return name of this entry */ public String getName() { return (_name != null ? _name : ""); } /** * @return the type */ public String getType() { return (_type != null ? _type : ""); } /** * Returns the key for this entry. The key is the lower case name. * * @return key for this entry */ public String getKey() { return (_key != null ? _key : ""); } /** * @return record type */ public DNSRecordType getRecordType() { return (_recordType != null ? _recordType : DNSRecordType.TYPE_IGNORE); } /** * @return record class */ public DNSRecordClass getRecordClass() { return (_dnsClass != null ? _dnsClass : DNSRecordClass.CLASS_UNKNOWN); } /** * @return true if unique */ public boolean isUnique() { return _unique; } public Map<Fields, String> getQualifiedNameMap() { return Collections.unmodifiableMap(_qualifiedNameMap); } public boolean isServicesDiscoveryMetaQuery() { return _qualifiedNameMap.get(Fields.Application).equals("dns-sd") && _qualifiedNameMap.get(Fields.Instance).equals("_services"); } public boolean isDomainDiscoveryQuery() { // b._dns-sd._udp.<domain>. // db._dns-sd._udp.<domain>. // r._dns-sd._udp.<domain>. // dr._dns-sd._udp.<domain>. // lb._dns-sd._udp.<domain>. if (_qualifiedNameMap.get(Fields.Application).equals("dns-sd")) { String name = _qualifiedNameMap.get(Fields.Instance); return "b".equals(name) || "db".equals(name) || "r".equals(name) || "dr".equals(name) || "lb".equals(name); } return false; } public boolean isReverseLookup() { return this.isV4ReverseLookup() || this.isV6ReverseLookup(); } public boolean isV4ReverseLookup() { return _qualifiedNameMap.get(Fields.Domain).endsWith("in-addr.arpa"); } public boolean isV6ReverseLookup() { return _qualifiedNameMap.get(Fields.Domain).endsWith("ip6.arpa"); } /** * Check if the record is stale, i.e. it has outlived more than half of its TTL. * * @param now * update date * @return <code>true</code> is the record is stale, <code>false</code> otherwise. */ public abstract boolean isStale(long now); /** * Check if the record is expired. * * @param now * update date * @return <code>true</code> is the record is expired, <code>false</code> otherwise. */ public abstract boolean isExpired(long now); /** * Check that 2 entries are of the same class. * * @param entry * @return <code>true</code> is the two class are the same, <code>false</code> otherwise. */ public boolean isSameRecordClass(DNSEntry entry) { return (entry != null) && (entry.getRecordClass() == this.getRecordClass()); } /** * Check that 2 entries are of the same type. * * @param entry * @return <code>true</code> is the two type are the same, <code>false</code> otherwise. */ public boolean isSameType(DNSEntry entry) { return (entry != null) && (entry.getRecordType() == this.getRecordType()); } /** * @param dout * @exception IOException */ protected void toByteArray(DataOutputStream dout) throws IOException { dout.write(this.getName().getBytes("UTF8")); dout.writeShort(this.getRecordType().indexValue()); dout.writeShort(this.getRecordClass().indexValue()); } /** * 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. * * @return byte array representation */ protected byte[] toByteArray() { try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); this.toByteArray(dout); dout.close(); return bout.toByteArray(); } catch (IOException e) { throw new InternalError(); } } /** * Does a lexicographic comparison of the byte array representation of this record and that record. This is needed for tie-break tests according to draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. * * @param that * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. */ public int compareTo(DNSEntry that) { byte[] thisBytes = this.toByteArray(); byte[] thatBytes = that.toByteArray(); for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) { if (thisBytes[i] > thatBytes[i]) { return 1; } else if (thisBytes[i] < thatBytes[i]) { return -1; } } return thisBytes.length - thatBytes.length; } /** * Overriden, to return a value which is consistent with the value returned by equals(Object). */ @Override public int hashCode() { return this.getKey().hashCode() + this.getRecordType().indexValue() + this.getRecordClass().indexValue(); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder aLog = new StringBuilder(200); aLog.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this)); aLog.append(" type: " + this.getRecordType()); aLog.append(", class: " + this.getRecordClass()); aLog.append((_unique ? "-unique," : ",")); aLog.append(" name: " + _name); this.toString(aLog); aLog.append("]"); return aLog.toString(); } /** * @param aLog */ protected void toString(StringBuilder aLog) { // Stub } }