• 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 android.annotation.Nullable;
20 import android.util.SparseArray;
21 
22 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
23 
24 import java.io.EOFException;
25 import java.io.IOException;
26 import java.net.DatagramPacket;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Locale;
30 
31 /** Simple decoder for mDNS packets. */
32 public class MdnsPacketReader {
33     private final byte[] buf;
34     private final int count;
35     private final SparseArray<LabelEntry> labelDictionary;
36     private int pos;
37     private int limit;
38 
39     /** Constructs a reader for the given packet. */
MdnsPacketReader(DatagramPacket packet)40     public MdnsPacketReader(DatagramPacket packet) {
41         this(packet.getData(), packet.getLength());
42     }
43 
44     /** Constructs a reader for the given packet. */
MdnsPacketReader(byte[] buffer, int length)45     public MdnsPacketReader(byte[] buffer, int length) {
46         buf = buffer;
47         count = length;
48         pos = 0;
49         limit = -1;
50         labelDictionary = new SparseArray<>(16);
51     }
52 
53     /**
54      * Sets a temporary limit (from the current read position) for subsequent reads. Any attempt to
55      * read past this limit will result in an EOFException.
56      *
57      * @param limit The new limit.
58      * @throws IOException If there is insufficient data for the new limit.
59      */
setLimit(int limit)60     public void setLimit(int limit) throws IOException {
61         if (limit >= 0) {
62             if (pos + limit <= count) {
63                 this.limit = pos + limit;
64             } else {
65                 throw new IOException(
66                         String.format(
67                                 Locale.ROOT,
68                                 "attempt to set limit beyond available data: %d exceeds %d",
69                                 pos + limit,
70                                 count));
71             }
72         }
73     }
74 
75     /** Clears the limit set by {@link #setLimit}. */
clearLimit()76     public void clearLimit() {
77         limit = -1;
78     }
79 
80     /**
81      * Returns the number of bytes left to read, between the current read position and either the
82      * limit (if set) or the end of the packet.
83      */
getRemaining()84     public int getRemaining() {
85         return (limit >= 0 ? limit : count) - pos;
86     }
87 
88     /**
89      * Reads an unsigned 8-bit integer.
90      *
91      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
92      *                      read.
93      */
readUInt8()94     public int readUInt8() throws EOFException {
95         checkRemaining(1);
96         byte val = buf[pos++];
97         return val & 0xFF;
98     }
99 
100     /**
101      * Reads an unsigned 16-bit integer.
102      *
103      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
104      *                      read.
105      */
readUInt16()106     public int readUInt16() throws EOFException {
107         checkRemaining(2);
108         int val = (buf[pos++] & 0xFF) << 8;
109         val |= (buf[pos++]) & 0xFF;
110         return val;
111     }
112 
113     /**
114      * Reads an unsigned 32-bit integer.
115      *
116      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
117      *                      read.
118      */
readUInt32()119     public long readUInt32() throws EOFException {
120         checkRemaining(4);
121         long val = (long) (buf[pos++] & 0xFF) << 24;
122         val |= (long) (buf[pos++] & 0xFF) << 16;
123         val |= (long) (buf[pos++] & 0xFF) << 8;
124         val |= buf[pos++] & 0xFF;
125         return val;
126     }
127 
128     /**
129      * Reads a sequence of labels and returns them as an array of strings. A sequence of labels is
130      * either a sequence of strings terminated by a NUL byte, a sequence of strings terminated by a
131      * pointer, or a pointer.
132      *
133      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
134      *                      read.
135      * @throws IOException  If invalid data is read.
136      */
readLabels()137     public String[] readLabels() throws IOException {
138         List<String> result = new ArrayList<>(5);
139         LabelEntry previousEntry = null;
140 
141         while (getRemaining() > 0) {
142             byte nextByte = peekByte();
143 
144             if (nextByte == 0) {
145                 // A NUL byte terminates a sequence of labels.
146                 skip(1);
147                 break;
148             }
149 
150             int currentOffset = pos;
151 
152             boolean isLabelPointer = (nextByte & 0xC0) == 0xC0;
153             if (isLabelPointer) {
154                 // A pointer terminates a sequence of labels. Store the pointer value in the
155                 // previous label entry.
156                 int labelOffset = ((readUInt8() & 0x3F) << 8) | (readUInt8() & 0xFF);
157                 if (previousEntry != null) {
158                     previousEntry.nextOffset = labelOffset;
159                 }
160 
161                 // Follow the chain of labels starting at this pointer, adding all of them onto the
162                 // result.
163                 while (labelOffset != 0) {
164                     LabelEntry entry = labelDictionary.get(labelOffset);
165                     if (entry == null) {
166                         throw new IOException(
167                                 String.format(Locale.ROOT, "Invalid label pointer: %04X",
168                                         labelOffset));
169                     }
170                     result.add(entry.label);
171                     labelOffset = entry.nextOffset;
172                 }
173                 break;
174             } else {
175                 // It's an ordinary label. Chain it onto the previous label entry (if any), and add
176                 // it onto the result.
177                 String val = readString();
178                 LabelEntry newEntry = new LabelEntry(val);
179                 labelDictionary.put(currentOffset, newEntry);
180 
181                 if (previousEntry != null) {
182                     previousEntry.nextOffset = currentOffset;
183                 }
184                 previousEntry = newEntry;
185                 result.add(val);
186             }
187         }
188 
189         return result.toArray(new String[result.size()]);
190     }
191 
192     /**
193      * Reads a length-prefixed string.
194      *
195      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
196      *                      read.
197      */
readString()198     public String readString() throws EOFException {
199         int len = readUInt8();
200         checkRemaining(len);
201         String val = new String(buf, pos, len, MdnsConstants.getUtf8Charset());
202         pos += len;
203         return val;
204     }
205 
206     @Nullable
readTextEntry()207     public TextEntry readTextEntry() throws EOFException {
208         int len = readUInt8();
209         checkRemaining(len);
210         byte[] bytes = new byte[len];
211         System.arraycopy(buf, pos, bytes, 0, bytes.length);
212         pos += len;
213         return TextEntry.fromBytes(bytes);
214     }
215 
216     /**
217      * Reads a specific number of bytes.
218      *
219      * @param bytes The array to fill.
220      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
221      *                      read.
222      */
readBytes(byte[] bytes)223     public void readBytes(byte[] bytes) throws EOFException {
224         checkRemaining(bytes.length);
225         System.arraycopy(buf, pos, bytes, 0, bytes.length);
226         pos += bytes.length;
227     }
228 
229     /**
230      * Skips over the given number of bytes.
231      *
232      * @param count The number of bytes to read and discard.
233      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
234      *                      read.
235      */
skip(int count)236     public void skip(int count) throws EOFException {
237         checkRemaining(count);
238         pos += count;
239     }
240 
241     /**
242      * Peeks at and returns the next byte in the packet, without advancing the read position.
243      *
244      * @throws EOFException If there are not enough bytes remaining in the packet to satisfy the
245      *                      read.
246      */
peekByte()247     public byte peekByte() throws EOFException {
248         checkRemaining(1);
249         return buf[pos];
250     }
251 
252     /** Returns the current byte position of the reader for the data packet. */
getPosition()253     public int getPosition() {
254         return pos;
255     }
256 
257     // Checks if the number of remaining bytes to be read in the packet is at least |count|.
checkRemaining(int count)258     private void checkRemaining(int count) throws EOFException {
259         if (getRemaining() < count) {
260             throw new EOFException();
261         }
262     }
263 
264     private static class LabelEntry {
265         public final String label;
266         public int nextOffset = 0;
267 
LabelEntry(String label)268         public LabelEntry(String label) {
269             this.label = label;
270         }
271     }
272 }