Home | History | Annotate | Download | only in impl
      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