• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.connectivity.mdns;
18 
19 import static java.util.concurrent.TimeUnit.MILLISECONDS;
20 import static java.util.concurrent.TimeUnit.SECONDS;
21 
22 import android.annotation.Nullable;
23 import android.os.SystemClock;
24 import android.text.TextUtils;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.io.IOException;
29 import java.util.Arrays;
30 import java.util.Objects;
31 
32 /**
33  * Abstract base class for mDNS records. Stores the header fields and provides methods for reading
34  * the record from and writing it to a packet.
35  */
36 public abstract class MdnsRecord {
37     public static final int TYPE_A = 0x0001;
38     public static final int TYPE_AAAA = 0x001C;
39     public static final int TYPE_PTR = 0x000C;
40     public static final int TYPE_SRV = 0x0021;
41     public static final int TYPE_TXT = 0x0010;
42     public static final int TYPE_NSEC = 0x002f;
43     public static final int TYPE_ANY = 0x00ff;
44 
45     private static final int FLAG_CACHE_FLUSH = 0x8000;
46 
47     public static final long RECEIPT_TIME_NOT_SENT = 0L;
48     public static final int CLASS_ANY = 0x00ff;
49     /** Max label length as per RFC 1034/1035 */
50     public static final int MAX_LABEL_LENGTH = 63;
51 
52     /** Status indicating that the record is current. */
53     public static final int STATUS_OK = 0;
54     /** Status indicating that the record has expired (TTL reached 0). */
55     public static final int STATUS_EXPIRED = 1;
56     /** Status indicating that the record should be refreshed (Less than half of TTL remains.) */
57     public static final int STATUS_NEEDS_REFRESH = 2;
58 
59     protected final String[] name;
60     private final int type;
61     private final int cls;
62     private final long receiptTimeMillis;
63     private final long ttlMillis;
64     private Object key;
65 
66     /**
67      * Constructs a new record with the given name and type.
68      *
69      * @param reader The reader to read the record from.
70      * @param isQuestion Whether the record was included in the questions part of the message.
71      * @throws IOException If an error occurs while reading the packet.
72      */
MdnsRecord(String[] name, int type, MdnsPacketReader reader, boolean isQuestion)73     protected MdnsRecord(String[] name, int type, MdnsPacketReader reader, boolean isQuestion)
74             throws IOException {
75         this.name = name;
76         this.type = type;
77         cls = reader.readUInt16();
78         receiptTimeMillis = SystemClock.elapsedRealtime();
79 
80         if (isQuestion) {
81             // Questions do not have TTL or data
82             ttlMillis = 0L;
83         } else {
84             ttlMillis = SECONDS.toMillis(reader.readUInt32());
85             int dataLength = reader.readUInt16();
86 
87             reader.setLimit(dataLength);
88             readData(reader);
89             reader.clearLimit();
90         }
91     }
92 
93     /**
94      * Constructs a new record with the given name and type.
95      *
96      * @param reader The reader to read the record from.
97      * @throws IOException If an error occurs while reading the packet.
98      */
99     // call to readData(com.android.server.connectivity.mdns.MdnsPacketReader) not allowed on given
100     // receiver.
101     @SuppressWarnings("nullness:method.invocation.invalid")
MdnsRecord(String[] name, int type, MdnsPacketReader reader)102     protected MdnsRecord(String[] name, int type, MdnsPacketReader reader) throws IOException {
103         this(name, type, reader, false);
104     }
105 
106     /**
107      * Constructs a new record with the given properties.
108      */
MdnsRecord(String[] name, int type, int cls, long receiptTimeMillis, boolean cacheFlush, long ttlMillis)109     protected MdnsRecord(String[] name, int type, int cls, long receiptTimeMillis,
110             boolean cacheFlush, long ttlMillis) {
111         this.name = name;
112         this.type = type;
113         this.cls = cls | (cacheFlush ? FLAG_CACHE_FLUSH : 0);
114         this.receiptTimeMillis = receiptTimeMillis;
115         this.ttlMillis = ttlMillis;
116     }
117 
118     /**
119      * Converts an array of labels into their dot-separated string representation. This method
120      * should
121      * be used for logging purposes only.
122      */
labelsToString(String[] labels)123     public static String labelsToString(String[] labels) {
124         if (labels == null) {
125             return null;
126         }
127         return TextUtils.join(".", labels);
128     }
129 
130     /** Tests if |list1| is a suffix of |list2|. */
labelsAreSuffix(String[] list1, String[] list2)131     public static boolean labelsAreSuffix(String[] list1, String[] list2) {
132         int offset = list2.length - list1.length;
133 
134         if (offset < 1) {
135             return false;
136         }
137 
138         for (int i = 0; i < list1.length; ++i) {
139             if (!list1[i].equals(list2[i + offset])) {
140                 return false;
141             }
142         }
143 
144         return true;
145     }
146 
147     /** Returns the record's receipt (creation) time. */
getReceiptTime()148     public final long getReceiptTime() {
149         return receiptTimeMillis;
150     }
151 
152     /** Returns the record's name. */
getName()153     public String[] getName() {
154         return name;
155     }
156 
157     /** Returns the record's original TTL, in milliseconds. */
getTtl()158     public final long getTtl() {
159         return ttlMillis;
160     }
161 
162     /** Returns the record's type. */
getType()163     public final int getType() {
164         return type;
165     }
166 
167     /** Return the record's class. */
getRecordClass()168     public final int getRecordClass() {
169         return cls & ~FLAG_CACHE_FLUSH;
170     }
171 
172     /** Return whether the cache flush flag is set. */
getCacheFlush()173     public final boolean getCacheFlush() {
174         return (cls & FLAG_CACHE_FLUSH) != 0;
175     }
176 
177     /**
178      * Returns the record's remaining TTL.
179      *
180      * If the record was not sent yet (receipt time {@link #RECEIPT_TIME_NOT_SENT}), this is the
181      * original TTL of the record.
182      * @param now The current system time.
183      * @return The remaning TTL, in milliseconds.
184      */
getRemainingTTL(final long now)185     public long getRemainingTTL(final long now) {
186         if (receiptTimeMillis == RECEIPT_TIME_NOT_SENT) {
187             return ttlMillis;
188         }
189 
190         long age = now - receiptTimeMillis;
191         if (age > ttlMillis) {
192             return 0;
193         }
194 
195         return ttlMillis - age;
196     }
197 
198     /**
199      * Reads the record's payload from a packet.
200      *
201      * @param reader The reader to use.
202      * @throws IOException If an I/O error occurs.
203      */
readData(MdnsPacketReader reader)204     protected abstract void readData(MdnsPacketReader reader) throws IOException;
205 
206     /**
207      * Write the first fields of the record, which are common fields for questions and answers.
208      *
209      * @param writer The writer to use.
210      */
writeHeaderFields(MdnsPacketWriter writer)211     public final void writeHeaderFields(MdnsPacketWriter writer) throws IOException {
212         writer.writeLabels(name);
213         writer.writeUInt16(type);
214         writer.writeUInt16(cls);
215     }
216 
217     /**
218      * Writes the record to a packet.
219      *
220      * @param writer The writer to use.
221      * @param now    The current system time. This is used when writing the updated TTL.
222      */
223     @VisibleForTesting
write(MdnsPacketWriter writer, long now)224     public final void write(MdnsPacketWriter writer, long now) throws IOException {
225         writeHeaderFields(writer);
226 
227         writer.writeUInt32(MILLISECONDS.toSeconds(getRemainingTTL(now)));
228 
229         int dataLengthPos = writer.getWritePosition();
230         writer.writeUInt16(0); // data length
231         int dataPos = writer.getWritePosition();
232 
233         writeData(writer);
234 
235         // Calculate amount of data written, and overwrite the data field earlier in the packet.
236         int endPos = writer.getWritePosition();
237         int dataLength = endPos - dataPos;
238         writer.rewind(dataLengthPos);
239         writer.writeUInt16(dataLength);
240         writer.unrewind();
241     }
242 
243     /**
244      * Writes the record's payload to a packet.
245      *
246      * @param writer The writer to use.
247      * @throws IOException If an I/O error occurs.
248      */
writeData(MdnsPacketWriter writer)249     protected abstract void writeData(MdnsPacketWriter writer) throws IOException;
250 
251     /** Gets the status of the record. */
getStatus(final long now)252     public int getStatus(final long now) {
253         if (receiptTimeMillis == RECEIPT_TIME_NOT_SENT) {
254             return STATUS_OK;
255         }
256         final long age = now - receiptTimeMillis;
257         if (age > ttlMillis) {
258             return STATUS_EXPIRED;
259         }
260         if (age > (ttlMillis / 2)) {
261             return STATUS_NEEDS_REFRESH;
262         }
263         return STATUS_OK;
264     }
265 
266     @Override
equals(@ullable Object other)267     public boolean equals(@Nullable Object other) {
268         if (!(other instanceof MdnsRecord)) {
269             return false;
270         }
271 
272         MdnsRecord otherRecord = (MdnsRecord) other;
273 
274         return Arrays.equals(name, otherRecord.name) && (type == otherRecord.type);
275     }
276 
277     @Override
hashCode()278     public int hashCode() {
279         return Objects.hash(Arrays.hashCode(name), type);
280     }
281 
282     /**
283      * Returns an opaque object that uniquely identifies this record through a combination of its
284      * type
285      * and name. Suitable for use as a key in caches.
286      */
getKey()287     public final Object getKey() {
288         if (key == null) {
289             key = new Key(type, name);
290         }
291         return key;
292     }
293 
294     private static final class Key {
295         private final int recordType;
296         private final String[] recordName;
297 
Key(int recordType, String[] recordName)298         public Key(int recordType, String[] recordName) {
299             this.recordType = recordType;
300             this.recordName = recordName;
301         }
302 
303         @Override
equals(@ullable Object other)304         public boolean equals(@Nullable Object other) {
305             if (this == other) {
306                 return true;
307             }
308             if (!(other instanceof Key)) {
309                 return false;
310             }
311 
312             Key otherKey = (Key) other;
313 
314             return (recordType == otherKey.recordType) && Arrays.equals(recordName,
315                     otherKey.recordName);
316         }
317 
318         @Override
hashCode()319         public int hashCode() {
320             return (recordType * 31) + Arrays.hashCode(recordName);
321         }
322     }
323 }
324