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 }