View Javadoc

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 }