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.netty5.handler.codec.dns;
17  
18  import io.netty5.util.internal.PlatformDependent;
19  import io.netty5.util.internal.StringUtil;
20  import io.netty5.util.internal.UnstableApi;
21  
22  import java.net.IDN;
23  
24  import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero;
25  import static java.util.Objects.requireNonNull;
26  
27  /**
28   * A skeletal implementation of {@link DnsRecord}.
29   */
30  @UnstableApi
31  public abstract class AbstractDnsRecord implements DnsRecord {
32  
33      private final String name;
34      private final DnsRecordType type;
35      private final short dnsClass;
36      private final long timeToLive;
37      private int hashCode;
38  
39      /**
40       * Creates a new {@link #CLASS_IN IN-class} record.
41       *
42       * @param name the domain name
43       * @param type the type of the record
44       * @param timeToLive the TTL value of the record
45       */
46      protected AbstractDnsRecord(String name, DnsRecordType type, long timeToLive) {
47          this(name, type, CLASS_IN, timeToLive);
48      }
49  
50      /**
51       * Creates a new record.
52       *
53       * @param name the domain name
54       * @param type the type of the record
55       * @param dnsClass the class of the record, usually one of the following:
56       *                 <ul>
57       *                     <li>{@link #CLASS_IN}</li>
58       *                     <li>{@link #CLASS_CSNET}</li>
59       *                     <li>{@link #CLASS_CHAOS}</li>
60       *                     <li>{@link #CLASS_HESIOD}</li>
61       *                     <li>{@link #CLASS_NONE}</li>
62       *                     <li>{@link #CLASS_ANY}</li>
63       *                 </ul>
64       * @param timeToLive the TTL value of the record
65       */
66      protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long timeToLive) {
67          checkPositiveOrZero(timeToLive, "timeToLive");
68          // Convert to ASCII which will also check that the length is not too big.
69          // See:
70          //   - https://github.com/netty/netty/issues/4937
71          //   - https://github.com/netty/netty/issues/4935
72          this.name = appendTrailingDot(IDNtoASCII(name));
73          this.type = requireNonNull(type, "type");
74          this.dnsClass = (short) dnsClass;
75          this.timeToLive = timeToLive;
76      }
77  
78      private static String IDNtoASCII(String name) {
79          requireNonNull(name, "name");
80          if (PlatformDependent.isAndroid() && DefaultDnsRecordDecoder.ROOT.equals(name)) {
81              // Prior Android 10 there was a bug that did not correctly parse ".".
82              //
83              // See https://github.com/netty/netty/issues/10034
84              return name;
85          }
86          return IDN.toASCII(name);
87      }
88  
89      private static String appendTrailingDot(String name) {
90          if (name.length() > 0 && name.charAt(name.length() - 1) != '.') {
91              return name + '.';
92          }
93          return name;
94      }
95  
96      @Override
97      public String name() {
98          return name;
99      }
100 
101     @Override
102     public DnsRecordType type() {
103         return type;
104     }
105 
106     @Override
107     public int dnsClass() {
108         return dnsClass & 0xFFFF;
109     }
110 
111     @Override
112     public long timeToLive() {
113         return timeToLive;
114     }
115 
116     @Override
117     public boolean equals(Object obj) {
118         if (this == obj) {
119             return true;
120         }
121 
122         if (!(obj instanceof DnsRecord)) {
123             return false;
124         }
125 
126         final DnsRecord that = (DnsRecord) obj;
127         final int hashCode = this.hashCode;
128         if (hashCode != 0 && hashCode != that.hashCode()) {
129             return false;
130         }
131 
132         return type().intValue() == that.type().intValue() &&
133                dnsClass() == that.dnsClass() &&
134                name().equals(that.name());
135     }
136 
137     @Override
138     public int hashCode() {
139         final int hashCode = this.hashCode;
140         if (hashCode != 0) {
141             return hashCode;
142         }
143 
144         return this.hashCode = name.hashCode() * 31 + type().intValue() * 31 + dnsClass();
145     }
146 
147     @Override
148     public String toString() {
149         StringBuilder buf = new StringBuilder(64);
150 
151         buf.append(StringUtil.simpleClassName(this))
152            .append('(')
153            .append(name())
154            .append(' ')
155            .append(timeToLive())
156            .append(' ');
157 
158         DnsMessageUtil.appendRecordClass(buf, dnsClass())
159                       .append(' ')
160                       .append(type().name())
161                       .append(')');
162 
163         return buf.toString();
164     }
165 }