View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.dns;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.handler.codec.UnsupportedMessageTypeException;
20  
21  /**
22   * The default {@link DnsRecordEncoder} implementation.
23   *
24   * @see DefaultDnsRecordDecoder
25   */
26  public class DefaultDnsRecordEncoder implements DnsRecordEncoder {
27      private static final int PREFIX_MASK = Byte.SIZE - 1;
28  
29      /**
30       * Creates a new instance.
31       */
32      protected DefaultDnsRecordEncoder() { }
33  
34      @Override
35      public final void encodeQuestion(DnsQuestion question, ByteBuf out) throws Exception {
36          encodeName(question.name(), out);
37          out.writeShort(question.type().intValue());
38          out.writeShort(question.dnsClass());
39      }
40  
41      private static final Class<?>[] SUPPORTED_MESSAGES = new Class<?>[] {
42              DnsQuestion.class, DnsPtrRecord.class,
43              DnsOptEcsRecord.class, DnsOptPseudoRecord.class, DnsRawRecord.class };
44  
45      @Override
46      public void encodeRecord(DnsRecord record, ByteBuf out) throws Exception {
47          if (record instanceof DnsQuestion) {
48              encodeQuestion((DnsQuestion) record, out);
49          } else if (record instanceof DnsPtrRecord) {
50              encodePtrRecord((DnsPtrRecord) record, out);
51          } else if (record instanceof DnsOptEcsRecord) {
52              encodeOptEcsRecord((DnsOptEcsRecord) record, out);
53          } else if (record instanceof DnsOptPseudoRecord) {
54              encodeOptPseudoRecord((DnsOptPseudoRecord) record, out);
55          } else if (record instanceof DnsRawRecord) {
56              encodeRawRecord((DnsRawRecord) record, out);
57          } else {
58              throw new UnsupportedMessageTypeException(record, SUPPORTED_MESSAGES);
59          }
60      }
61  
62      private void encodeRecord0(DnsRecord record, ByteBuf out) throws Exception {
63          encodeName(record.name(), out);
64          out.writeShort(record.type().intValue());
65          out.writeShort(record.dnsClass());
66          out.writeInt((int) record.timeToLive());
67      }
68  
69      private void encodePtrRecord(DnsPtrRecord record, ByteBuf out) throws Exception {
70          encodeRecord0(record, out);
71          encodeName(record.hostname(), out);
72      }
73  
74      private void encodeOptPseudoRecord(DnsOptPseudoRecord record, ByteBuf out) throws Exception {
75          encodeRecord0(record, out);
76          out.writeShort(0);
77      }
78  
79      private void encodeOptEcsRecord(DnsOptEcsRecord record, ByteBuf out) throws Exception {
80          encodeRecord0(record, out);
81  
82          int sourcePrefixLength = record.sourcePrefixLength();
83          int scopePrefixLength = record.scopePrefixLength();
84          int lowOrderBitsToPreserve = sourcePrefixLength & PREFIX_MASK;
85  
86          byte[] bytes = record.address();
87          int addressBits = bytes.length << 3;
88          if (addressBits < sourcePrefixLength || sourcePrefixLength < 0) {
89              throw new IllegalArgumentException(sourcePrefixLength + ": " +
90                      sourcePrefixLength + " (expected: 0 >= " + addressBits + ')');
91          }
92  
93          // See https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml
94          final short addressNumber = (short) (bytes.length == 4 ? 1 : 2);
95          int payloadLength = calculateEcsAddressLength(sourcePrefixLength, lowOrderBitsToPreserve);
96  
97          int fullPayloadLength = 2 + // OPTION-CODE
98                  2 + // OPTION-LENGTH
99                  2 + // FAMILY
100                 1 + // SOURCE PREFIX-LENGTH
101                 1 + // SCOPE PREFIX-LENGTH
102                 payloadLength; //  ADDRESS...
103 
104         out.writeShort(fullPayloadLength);
105         out.writeShort(8); // This is the defined type for ECS.
106 
107         out.writeShort(fullPayloadLength - 4); // Not include OPTION-CODE and OPTION-LENGTH
108         out.writeShort(addressNumber);
109         out.writeByte(sourcePrefixLength);
110         out.writeByte(scopePrefixLength); // Must be 0 in queries.
111 
112         if (lowOrderBitsToPreserve > 0) {
113             int bytesLength = payloadLength - 1;
114             out.writeBytes(bytes, 0, bytesLength);
115 
116             // Pad the leftover of the last byte with zeros.
117             out.writeByte(padWithZeros(bytes[bytesLength], lowOrderBitsToPreserve));
118         } else {
119             // The sourcePrefixLength align with Byte so just copy in the bytes directly.
120             out.writeBytes(bytes, 0, payloadLength);
121         }
122     }
123 
124     // Package-Private for testing
125     static int calculateEcsAddressLength(int sourcePrefixLength, int lowOrderBitsToPreserve) {
126         return (sourcePrefixLength >>> 3) + (lowOrderBitsToPreserve != 0 ? 1 : 0);
127     }
128 
129     private void encodeRawRecord(DnsRawRecord record, ByteBuf out) throws Exception {
130         encodeRecord0(record, out);
131 
132         ByteBuf content = record.content();
133         int contentLen = content.readableBytes();
134 
135         out.writeShort(contentLen);
136         out.writeBytes(content, content.readerIndex(), contentLen);
137     }
138 
139     protected void encodeName(String name, ByteBuf buf) throws Exception {
140         DnsCodecUtil.encodeDomainName(name, buf);
141     }
142 
143     private static byte padWithZeros(byte b, int lowOrderBitsToPreserve) {
144         switch (lowOrderBitsToPreserve) {
145         case 0:
146             return 0;
147         case 1:
148             return (byte) (0x80 & b);
149         case 2:
150             return (byte) (0xC0 & b);
151         case 3:
152             return (byte) (0xE0 & b);
153         case 4:
154             return (byte) (0xF0 & b);
155         case 5:
156             return (byte) (0xF8 & b);
157         case 6:
158             return (byte) (0xFC & b);
159         case 7:
160             return (byte) (0xFE & b);
161         case 8:
162             return b;
163         default:
164             throw new IllegalArgumentException("lowOrderBitsToPreserve: " + lowOrderBitsToPreserve);
165         }
166     }
167 }