1 // Copyright 2003-2005 Arthur van Hoff, Rick Blair 2 // Licensed under Apache License version 2.0 3 // Original license LGPL 4 5 package javax.jmdns.impl; 6 7 import java.io.ByteArrayOutputStream; 8 import java.io.DataOutputStream; 9 import java.io.IOException; 10 import java.util.Collections; 11 import java.util.Map; 12 13 import javax.jmdns.ServiceInfo.Fields; 14 import javax.jmdns.impl.constants.DNSRecordClass; 15 import javax.jmdns.impl.constants.DNSRecordType; 16 17 /** 18 * DNS entry with a name, type, and class. This is the base class for questions and records. 19 * 20 * @author Arthur van Hoff, Pierre Frisch, Rick Blair 21 */ 22 public abstract class DNSEntry { 23 // private static Logger logger = Logger.getLogger(DNSEntry.class.getName()); 24 private final String _key; 25 26 private final String _name; 27 28 private final String _type; 29 30 private final DNSRecordType _recordType; 31 32 private final DNSRecordClass _dnsClass; 33 34 private final boolean _unique; 35 36 final Map<Fields, String> _qualifiedNameMap; 37 38 /** 39 * Create an entry. 40 */ DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique)41 DNSEntry(String name, DNSRecordType type, DNSRecordClass recordClass, boolean unique) { 42 _name = name; 43 // _key = (name != null ? name.trim().toLowerCase() : null); 44 _recordType = type; 45 _dnsClass = recordClass; 46 _unique = unique; 47 _qualifiedNameMap = ServiceInfoImpl.decodeQualifiedNameMapForType(this.getName()); 48 String domain = _qualifiedNameMap.get(Fields.Domain); 49 String protocol = _qualifiedNameMap.get(Fields.Protocol); 50 String application = _qualifiedNameMap.get(Fields.Application); 51 String instance = _qualifiedNameMap.get(Fields.Instance).toLowerCase(); 52 _type = (application.length() > 0 ? "_" + application + "." : "") + (protocol.length() > 0 ? "_" + protocol + "." : "") + domain + "."; 53 _key = ((instance.length() > 0 ? instance + "." : "") + _type).toLowerCase(); 54 } 55 56 /* 57 * (non-Javadoc) 58 * @see java.lang.Object#equals(java.lang.Object) 59 */ 60 @Override equals(Object obj)61 public boolean equals(Object obj) { 62 boolean result = false; 63 if (obj instanceof DNSEntry) { 64 DNSEntry other = (DNSEntry) obj; 65 result = this.getKey().equals(other.getKey()) && this.getRecordType().equals(other.getRecordType()) && this.getRecordClass() == other.getRecordClass(); 66 } 67 return result; 68 } 69 70 /** 71 * Check if two entries have exactly the same name, type, and class. 72 * 73 * @param entry 74 * @return <code>true</code> if the two entries have are for the same record, <code>false</code> otherwise 75 */ isSameEntry(DNSEntry entry)76 public boolean isSameEntry(DNSEntry entry) { 77 return this.getKey().equals(entry.getKey()) && this.getRecordType().equals(entry.getRecordType()) && ((DNSRecordClass.CLASS_ANY == entry.getRecordClass()) || this.getRecordClass().equals(entry.getRecordClass())); 78 } 79 80 /** 81 * Check if two entries have the same subtype. 82 * 83 * @param other 84 * @return <code>true</code> if the two entries have are for the same subtype, <code>false</code> otherwise 85 */ sameSubtype(DNSEntry other)86 public boolean sameSubtype(DNSEntry other) { 87 return this.getSubtype().equals(other.getSubtype()); 88 } 89 90 /** 91 * Returns the subtype of this entry 92 * 93 * @return subtype of this entry 94 */ getSubtype()95 public String getSubtype() { 96 String subtype = this.getQualifiedNameMap().get(Fields.Subtype); 97 return (subtype != null ? subtype : ""); 98 } 99 100 /** 101 * Returns the name of this entry 102 * 103 * @return name of this entry 104 */ getName()105 public String getName() { 106 return (_name != null ? _name : ""); 107 } 108 109 /** 110 * @return the type 111 */ getType()112 public String getType() { 113 return (_type != null ? _type : ""); 114 } 115 116 /** 117 * Returns the key for this entry. The key is the lower case name. 118 * 119 * @return key for this entry 120 */ getKey()121 public String getKey() { 122 return (_key != null ? _key : ""); 123 } 124 125 /** 126 * @return record type 127 */ getRecordType()128 public DNSRecordType getRecordType() { 129 return (_recordType != null ? _recordType : DNSRecordType.TYPE_IGNORE); 130 } 131 132 /** 133 * @return record class 134 */ getRecordClass()135 public DNSRecordClass getRecordClass() { 136 return (_dnsClass != null ? _dnsClass : DNSRecordClass.CLASS_UNKNOWN); 137 } 138 139 /** 140 * @return true if unique 141 */ isUnique()142 public boolean isUnique() { 143 return _unique; 144 } 145 getQualifiedNameMap()146 public Map<Fields, String> getQualifiedNameMap() { 147 return Collections.unmodifiableMap(_qualifiedNameMap); 148 } 149 isServicesDiscoveryMetaQuery()150 public boolean isServicesDiscoveryMetaQuery() { 151 return _qualifiedNameMap.get(Fields.Application).equals("dns-sd") && _qualifiedNameMap.get(Fields.Instance).equals("_services"); 152 } 153 isDomainDiscoveryQuery()154 public boolean isDomainDiscoveryQuery() { 155 // b._dns-sd._udp.<domain>. 156 // db._dns-sd._udp.<domain>. 157 // r._dns-sd._udp.<domain>. 158 // dr._dns-sd._udp.<domain>. 159 // lb._dns-sd._udp.<domain>. 160 161 if (_qualifiedNameMap.get(Fields.Application).equals("dns-sd")) { 162 String name = _qualifiedNameMap.get(Fields.Instance); 163 return "b".equals(name) || "db".equals(name) || "r".equals(name) || "dr".equals(name) || "lb".equals(name); 164 } 165 return false; 166 } 167 isReverseLookup()168 public boolean isReverseLookup() { 169 return this.isV4ReverseLookup() || this.isV6ReverseLookup(); 170 } 171 isV4ReverseLookup()172 public boolean isV4ReverseLookup() { 173 return _qualifiedNameMap.get(Fields.Domain).endsWith("in-addr.arpa"); 174 } 175 isV6ReverseLookup()176 public boolean isV6ReverseLookup() { 177 return _qualifiedNameMap.get(Fields.Domain).endsWith("ip6.arpa"); 178 } 179 180 /** 181 * Check if the record is stale, i.e. it has outlived more than half of its TTL. 182 * 183 * @param now 184 * update date 185 * @return <code>true</code> is the record is stale, <code>false</code> otherwise. 186 */ isStale(long now)187 public abstract boolean isStale(long now); 188 189 /** 190 * Check if the record is expired. 191 * 192 * @param now 193 * update date 194 * @return <code>true</code> is the record is expired, <code>false</code> otherwise. 195 */ isExpired(long now)196 public abstract boolean isExpired(long now); 197 198 /** 199 * Check that 2 entries are of the same class. 200 * 201 * @param entry 202 * @return <code>true</code> is the two class are the same, <code>false</code> otherwise. 203 */ isSameRecordClass(DNSEntry entry)204 public boolean isSameRecordClass(DNSEntry entry) { 205 return (entry != null) && (entry.getRecordClass() == this.getRecordClass()); 206 } 207 208 /** 209 * Check that 2 entries are of the same type. 210 * 211 * @param entry 212 * @return <code>true</code> is the two type are the same, <code>false</code> otherwise. 213 */ isSameType(DNSEntry entry)214 public boolean isSameType(DNSEntry entry) { 215 return (entry != null) && (entry.getRecordType() == this.getRecordType()); 216 } 217 218 /** 219 * @param dout 220 * @exception IOException 221 */ toByteArray(DataOutputStream dout)222 protected void toByteArray(DataOutputStream dout) throws IOException { 223 dout.write(this.getName().getBytes("UTF8")); 224 dout.writeShort(this.getRecordType().indexValue()); 225 dout.writeShort(this.getRecordClass().indexValue()); 226 } 227 228 /** 229 * 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. 230 * 231 * @return byte array representation 232 */ toByteArray()233 protected byte[] toByteArray() { 234 try { 235 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 236 DataOutputStream dout = new DataOutputStream(bout); 237 this.toByteArray(dout); 238 dout.close(); 239 return bout.toByteArray(); 240 } catch (IOException e) { 241 throw new InternalError(); 242 } 243 } 244 245 /** 246 * 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. 247 * 248 * @param that 249 * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. 250 */ compareTo(DNSEntry that)251 public int compareTo(DNSEntry that) { 252 byte[] thisBytes = this.toByteArray(); 253 byte[] thatBytes = that.toByteArray(); 254 for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) { 255 if (thisBytes[i] > thatBytes[i]) { 256 return 1; 257 } else if (thisBytes[i] < thatBytes[i]) { 258 return -1; 259 } 260 } 261 return thisBytes.length - thatBytes.length; 262 } 263 264 /** 265 * Overriden, to return a value which is consistent with the value returned by equals(Object). 266 */ 267 @Override hashCode()268 public int hashCode() { 269 return this.getKey().hashCode() + this.getRecordType().indexValue() + this.getRecordClass().indexValue(); 270 } 271 272 /* 273 * (non-Javadoc) 274 * @see java.lang.Object#toString() 275 */ 276 @Override toString()277 public String toString() { 278 StringBuilder aLog = new StringBuilder(200); 279 aLog.append("[" + this.getClass().getSimpleName() + "@" + System.identityHashCode(this)); 280 aLog.append(" type: " + this.getRecordType()); 281 aLog.append(", class: " + this.getRecordClass()); 282 aLog.append((_unique ? "-unique," : ",")); 283 aLog.append(" name: " + _name); 284 this.toString(aLog); 285 aLog.append("]"); 286 return aLog.toString(); 287 } 288 289 /** 290 * @param aLog 291 */ toString(StringBuilder aLog)292 protected void toString(StringBuilder aLog) { 293 // Stub 294 } 295 296 } 297