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.IOException; 9 import java.util.HashMap; 10 import java.util.Map; 11 12 import javax.jmdns.impl.constants.DNSConstants; 13 import javax.jmdns.impl.constants.DNSRecordClass; 14 15 /** 16 * An outgoing DNS message. 17 * 18 * @author Arthur van Hoff, Rick Blair, Werner Randelshofer 19 */ 20 public final class DNSOutgoing extends DNSMessage { 21 22 public static class MessageOutputStream extends ByteArrayOutputStream { 23 private final DNSOutgoing _out; 24 25 private final int _offset; 26 27 /** 28 * Creates a new message stream, with a buffer capacity of the specified size, in bytes. 29 * 30 * @param size 31 * the initial size. 32 * @exception IllegalArgumentException 33 * if size is negative. 34 */ 35 MessageOutputStream(int size, DNSOutgoing out) { 36 this(size, out, 0); 37 } 38 39 MessageOutputStream(int size, DNSOutgoing out, int offset) { 40 super(size); 41 _out = out; 42 _offset = offset; 43 } 44 45 void writeByte(int value) { 46 this.write(value & 0xFF); 47 } 48 49 void writeBytes(String str, int off, int len) { 50 for (int i = 0; i < len; i++) { 51 writeByte(str.charAt(off + i)); 52 } 53 } 54 55 void writeBytes(byte data[]) { 56 if (data != null) { 57 writeBytes(data, 0, data.length); 58 } 59 } 60 61 void writeBytes(byte data[], int off, int len) { 62 for (int i = 0; i < len; i++) { 63 writeByte(data[off + i]); 64 } 65 } 66 67 void writeShort(int value) { 68 writeByte(value >> 8); 69 writeByte(value); 70 } 71 72 void writeInt(int value) { 73 writeShort(value >> 16); 74 writeShort(value); 75 } 76 77 void writeUTF(String str, int off, int len) { 78 // compute utf length 79 int utflen = 0; 80 for (int i = 0; i < len; i++) { 81 int ch = str.charAt(off + i); 82 if ((ch >= 0x0001) && (ch <= 0x007F)) { 83 utflen += 1; 84 } else { 85 if (ch > 0x07FF) { 86 utflen += 3; 87 } else { 88 utflen += 2; 89 } 90 } 91 } 92 // write utf length 93 writeByte(utflen); 94 // write utf data 95 for (int i = 0; i < len; i++) { 96 int ch = str.charAt(off + i); 97 if ((ch >= 0x0001) && (ch <= 0x007F)) { 98 writeByte(ch); 99 } else { 100 if (ch > 0x07FF) { 101 writeByte(0xE0 | ((ch >> 12) & 0x0F)); 102 writeByte(0x80 | ((ch >> 6) & 0x3F)); 103 writeByte(0x80 | ((ch >> 0) & 0x3F)); 104 } else { 105 writeByte(0xC0 | ((ch >> 6) & 0x1F)); 106 writeByte(0x80 | ((ch >> 0) & 0x3F)); 107 } 108 } 109 } 110 } 111 112 void writeName(String name) { 113 writeName(name, true); 114 } 115 116 void writeName(String name, boolean useCompression) { 117 String aName = name; 118 while (true) { 119 int n = aName.indexOf('.'); 120 if (n < 0) { 121 n = aName.length(); 122 } 123 if (n <= 0) { 124 writeByte(0); 125 return; 126 } 127 String label = aName.substring(0, n); 128 if (useCompression && USE_DOMAIN_NAME_COMPRESSION) { 129 Integer offset = _out._names.get(aName); 130 if (offset != null) { 131 int val = offset.intValue(); 132 writeByte((val >> 8) | 0xC0); 133 writeByte(val & 0xFF); 134 return; 135 } 136 _out._names.put(aName, Integer.valueOf(this.size() + _offset)); 137 writeUTF(label, 0, label.length()); 138 } else { 139 writeUTF(label, 0, label.length()); 140 } 141 aName = aName.substring(n); 142 if (aName.startsWith(".")) { 143 aName = aName.substring(1); 144 } 145 } 146 } 147 148 void writeQuestion(DNSQuestion question) { 149 writeName(question.getName()); 150 writeShort(question.getRecordType().indexValue()); 151 writeShort(question.getRecordClass().indexValue()); 152 } 153 154 void writeRecord(DNSRecord rec, long now) { 155 writeName(rec.getName()); 156 writeShort(rec.getRecordType().indexValue()); 157 writeShort(rec.getRecordClass().indexValue() | ((rec.isUnique() && _out.isMulticast()) ? DNSRecordClass.CLASS_UNIQUE : 0)); 158 writeInt((now == 0) ? rec.getTTL() : rec.getRemainingTTL(now)); 159 160 // We need to take into account the 2 size bytes 161 MessageOutputStream record = new MessageOutputStream(512, _out, _offset + this.size() + 2); 162 rec.write(record); 163 byte[] byteArray = record.toByteArray(); 164 165 writeShort(byteArray.length); 166 write(byteArray, 0, byteArray.length); 167 } 168 169 } 170 171 /** 172 * This can be used to turn off domain name compression. This was helpful for tracking problems interacting with other mdns implementations. 173 */ 174 public static boolean USE_DOMAIN_NAME_COMPRESSION = true; 175 176 Map<String, Integer> _names; 177 178 private int _maxUDPPayload; 179 180 private final MessageOutputStream _questionsBytes; 181 182 private final MessageOutputStream _answersBytes; 183 184 private final MessageOutputStream _authoritativeAnswersBytes; 185 186 private final MessageOutputStream _additionalsAnswersBytes; 187 188 private final static int HEADER_SIZE = 12; 189 190 /** 191 * Create an outgoing multicast query or response. 192 * 193 * @param flags 194 */ 195 public DNSOutgoing(int flags) { 196 this(flags, true, DNSConstants.MAX_MSG_TYPICAL); 197 } 198 199 /** 200 * Create an outgoing query or response. 201 * 202 * @param flags 203 * @param multicast 204 */ 205 public DNSOutgoing(int flags, boolean multicast) { 206 this(flags, multicast, DNSConstants.MAX_MSG_TYPICAL); 207 } 208 209 /** 210 * Create an outgoing query or response. 211 * 212 * @param flags 213 * @param multicast 214 * @param senderUDPPayload 215 * The sender's UDP payload size is the number of bytes of the largest UDP payload that can be reassembled and delivered in the sender's network stack. 216 */ 217 public DNSOutgoing(int flags, boolean multicast, int senderUDPPayload) { 218 super(flags, 0, multicast); 219 _names = new HashMap<String, Integer>(); 220 _maxUDPPayload = (senderUDPPayload > 0 ? senderUDPPayload : DNSConstants.MAX_MSG_TYPICAL); 221 _questionsBytes = new MessageOutputStream(senderUDPPayload, this); 222 _answersBytes = new MessageOutputStream(senderUDPPayload, this); 223 _authoritativeAnswersBytes = new MessageOutputStream(senderUDPPayload, this); 224 _additionalsAnswersBytes = new MessageOutputStream(senderUDPPayload, this); 225 } 226 227 /** 228 * Return the number of byte available in the message. 229 * 230 * @return available space 231 */ 232 public int availableSpace() { 233 return _maxUDPPayload - HEADER_SIZE - _questionsBytes.size() - _answersBytes.size() - _authoritativeAnswersBytes.size() - _additionalsAnswersBytes.size(); 234 } 235 236 /** 237 * Add a question to the message. 238 * 239 * @param rec 240 * @exception IOException 241 */ 242 public void addQuestion(DNSQuestion rec) throws IOException { 243 MessageOutputStream record = new MessageOutputStream(512, this); 244 record.writeQuestion(rec); 245 byte[] byteArray = record.toByteArray(); 246 if (byteArray.length < this.availableSpace()) { 247 _questions.add(rec); 248 _questionsBytes.write(byteArray, 0, byteArray.length); 249 } else { 250 throw new IOException("message full"); 251 } 252 } 253 254 /** 255 * Add an answer if it is not suppressed. 256 * 257 * @param in 258 * @param rec 259 * @exception IOException 260 */ 261 public void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException { 262 if ((in == null) || !rec.suppressedBy(in)) { 263 this.addAnswer(rec, 0); 264 } 265 } 266 267 /** 268 * Add an answer to the message. 269 * 270 * @param rec 271 * @param now 272 * @exception IOException 273 */ 274 public void addAnswer(DNSRecord rec, long now) throws IOException { 275 if (rec != null) { 276 if ((now == 0) || !rec.isExpired(now)) { 277 MessageOutputStream record = new MessageOutputStream(512, this); 278 record.writeRecord(rec, now); 279 byte[] byteArray = record.toByteArray(); 280 if (byteArray.length < this.availableSpace()) { 281 _answers.add(rec); 282 _answersBytes.write(byteArray, 0, byteArray.length); 283 } else { 284 throw new IOException("message full"); 285 } 286 } 287 } 288 } 289 290 /** 291 * Add an authoritative answer to the message. 292 * 293 * @param rec 294 * @exception IOException 295 */ 296 public void addAuthorativeAnswer(DNSRecord rec) throws IOException { 297 MessageOutputStream record = new MessageOutputStream(512, this); 298 record.writeRecord(rec, 0); 299 byte[] byteArray = record.toByteArray(); 300 if (byteArray.length < this.availableSpace()) { 301 _authoritativeAnswers.add(rec); 302 _authoritativeAnswersBytes.write(byteArray, 0, byteArray.length); 303 } else { 304 throw new IOException("message full"); 305 } 306 } 307 308 /** 309 * Add an additional answer to the record. Omit if there is no room. 310 * 311 * @param in 312 * @param rec 313 * @exception IOException 314 */ 315 public void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException { 316 MessageOutputStream record = new MessageOutputStream(512, this); 317 record.writeRecord(rec, 0); 318 byte[] byteArray = record.toByteArray(); 319 if (byteArray.length < this.availableSpace()) { 320 _additionals.add(rec); 321 _additionalsAnswersBytes.write(byteArray, 0, byteArray.length); 322 } else { 323 throw new IOException("message full"); 324 } 325 } 326 327 /** 328 * Builds the final message buffer to be send and returns it. 329 * 330 * @return bytes to send. 331 */ 332 public byte[] data() { 333 long now = System.currentTimeMillis(); // System.currentTimeMillis() 334 _names.clear(); 335 336 MessageOutputStream message = new MessageOutputStream(_maxUDPPayload, this); 337 message.writeShort(_multicast ? 0 : this.getId()); 338 message.writeShort(this.getFlags()); 339 message.writeShort(this.getNumberOfQuestions()); 340 message.writeShort(this.getNumberOfAnswers()); 341 message.writeShort(this.getNumberOfAuthorities()); 342 message.writeShort(this.getNumberOfAdditionals()); 343 for (DNSQuestion question : _questions) { 344 message.writeQuestion(question); 345 } 346 for (DNSRecord record : _answers) { 347 message.writeRecord(record, now); 348 } 349 for (DNSRecord record : _authoritativeAnswers) { 350 message.writeRecord(record, now); 351 } 352 for (DNSRecord record : _additionals) { 353 message.writeRecord(record, now); 354 } 355 return message.toByteArray(); 356 } 357 358 @Override 359 public boolean isQuery() { 360 return (this.getFlags() & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; 361 } 362 363 /** 364 * Debugging. 365 */ 366 String print(boolean dump) { 367 StringBuilder buf = new StringBuilder(); 368 buf.append(this.print()); 369 if (dump) { 370 buf.append(this.print(this.data())); 371 } 372 return buf.toString(); 373 } 374 375 @Override 376 public String toString() { 377 StringBuffer buf = new StringBuffer(); 378 buf.append(isQuery() ? "dns[query:" : "dns[response:"); 379 buf.append(" id=0x"); 380 buf.append(Integer.toHexString(this.getId())); 381 if (this.getFlags() != 0) { 382 buf.append(", flags=0x"); 383 buf.append(Integer.toHexString(this.getFlags())); 384 if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0) { 385 buf.append(":r"); 386 } 387 if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0) { 388 buf.append(":aa"); 389 } 390 if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0) { 391 buf.append(":tc"); 392 } 393 } 394 if (this.getNumberOfQuestions() > 0) { 395 buf.append(", questions="); 396 buf.append(this.getNumberOfQuestions()); 397 } 398 if (this.getNumberOfAnswers() > 0) { 399 buf.append(", answers="); 400 buf.append(this.getNumberOfAnswers()); 401 } 402 if (this.getNumberOfAuthorities() > 0) { 403 buf.append(", authorities="); 404 buf.append(this.getNumberOfAuthorities()); 405 } 406 if (this.getNumberOfAdditionals() > 0) { 407 buf.append(", additionals="); 408 buf.append(this.getNumberOfAdditionals()); 409 } 410 if (this.getNumberOfQuestions() > 0) { 411 buf.append("\nquestions:"); 412 for (DNSQuestion question : _questions) { 413 buf.append("\n\t"); 414 buf.append(question); 415 } 416 } 417 if (this.getNumberOfAnswers() > 0) { 418 buf.append("\nanswers:"); 419 for (DNSRecord record : _answers) { 420 buf.append("\n\t"); 421 buf.append(record); 422 } 423 } 424 if (this.getNumberOfAuthorities() > 0) { 425 buf.append("\nauthorities:"); 426 for (DNSRecord record : _authoritativeAnswers) { 427 buf.append("\n\t"); 428 buf.append(record); 429 } 430 } 431 if (this.getNumberOfAdditionals() > 0) { 432 buf.append("\nadditionals:"); 433 for (DNSRecord record : _additionals) { 434 buf.append("\n\t"); 435 buf.append(record); 436 } 437 } 438 buf.append("\nnames="); 439 buf.append(_names); 440 buf.append("]"); 441 return buf.toString(); 442 } 443 444 /** 445 * @return the maxUDPPayload 446 */ 447 public int getMaxUDPPayload() { 448 return this._maxUDPPayload; 449 } 450 451 } 452