1 /* 2 * Copyright (C) 2023 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.tv.mdnsoffloadmanager; 18 19 import androidx.annotation.NonNull; 20 21 import java.nio.charset.StandardCharsets; 22 import java.util.ArrayList; 23 import java.util.List; 24 import java.util.Objects; 25 26 import device.google.atv.mdns_offload.IMdnsOffload.MdnsProtocolData.MatchCriteria; 27 28 /** 29 * Tool class to help read mdns data from a fully formed mDNS response packet. 30 */ 31 public final class MdnsPacketParser { 32 33 private static final int OFFSET_QUERIES_COUNT = 4; 34 private static final int OFFSET_ANSWERS_COUNT = 6; 35 private static final int OFFSET_AUTHORITY_COUNT = 8; 36 private static final int OFFSET_ADDITIONAL_COUNT = 10; 37 private static final int OFFSET_DATA_SECTION_START = 12; 38 39 private final byte[] mMdnsData; 40 private int mCursorIndex; 41 MdnsPacketParser(@onNull byte[] mDNSData)42 private MdnsPacketParser(@NonNull byte[] mDNSData) { 43 this.mMdnsData = mDNSData; 44 } 45 46 /** 47 * Extracts a label starting at offset and then follows RFC1035-4.1.4 The offset should start 48 * either at a data length value or at a pointer value. 49 */ extractFullName(@onNull byte[] array, int offset)50 public static String extractFullName(@NonNull byte[] array, int offset) { 51 MdnsPacketParser parser = new MdnsPacketParser(array); 52 parser.setCursor(offset); 53 StringBuilder builder = new StringBuilder(); 54 55 while (!parser.isCursorOnRootLabel()) { 56 if (parser.isCursorOnPointer()) { 57 parser.setCursor(parser.pollPointerOffset()); 58 } else if (parser.isCursorOnLabel()) { 59 builder.append(parser.pollLabel()); 60 builder.append('.'); 61 } else { 62 throw new IllegalArgumentException("mDNS response packet is badly formed."); 63 } 64 } 65 return builder.toString(); 66 } 67 68 /** 69 * Finds all the RRNAMEs ans RRTYPEs in the mdns response packet provided. Expects a packet only 70 * with responses. 71 */ extractMatchCriteria(@onNull byte[] mdnsResponsePacket)72 public static List<MatchCriteria> extractMatchCriteria(@NonNull byte[] mdnsResponsePacket) { 73 Objects.requireNonNull(mdnsResponsePacket); 74 75 // Parse MdnsPacket and read labels and find 76 List<MatchCriteria> criteriaList = new ArrayList<>(); 77 MdnsPacketParser parser = new MdnsPacketParser(mdnsResponsePacket); 78 79 if (parser.getQueriesCount() != 0 80 || parser.getAuthorityCount() != 0 81 || parser.getAdditionalCount() != 0) { 82 throw new IllegalArgumentException( 83 "mDNS response packet contains data that is not answers"); 84 } 85 int answersToRead = parser.getAnswersCount(); 86 87 parser.moveToDataSection(); 88 while (answersToRead > 0) { 89 // Each record starts with the RRNAME, so the offset is correct for the criteria. 90 MatchCriteria criteria = new MatchCriteria(); 91 criteria.nameOffset = parser.getCursorOffset(); 92 93 /// Skip labels first 94 while (parser.isCursorOnLabel()) { 95 parser.pollLabel(); 96 } 97 // We can be on a root label or on a pointer. Skip both. 98 if (parser.isCursorOnRootLabel()) { 99 parser.skipBytes(1); 100 } else if (parser.isCursorOnPointer()) { 101 parser.pollPointerOffset(); 102 } 103 104 // The cursor must be on the RRTYPE. 105 criteria.type = parser.pollUint16(); 106 107 // The next 6 bytes point to cache flush, rrclass, and ttl 108 parser.skipBytes(6); 109 110 // Now the index points to the data length on 2 bytes 111 int dataLength = parser.pollUint16(); 112 113 // Then we can skip those data bytes. 114 parser.skipBytes(dataLength); 115 116 // Criteria is complete, it can be added. 117 criteriaList.add(criteria); 118 answersToRead--; 119 } 120 if (parser.hasContent()) { 121 // The packet is badly formed. All answers where read successfully, but data remains 122 // available. 123 throw new IllegalArgumentException( 124 "mDNS response packet is badly formed. Too much data."); 125 } 126 127 return criteriaList; 128 } 129 hasContent()130 private boolean hasContent() { 131 return mCursorIndex < mMdnsData.length; 132 } 133 setCursor(int position)134 private void setCursor(int position) { 135 if (position < 0) { 136 throw new IllegalArgumentException("Setting cursor on negative offset is not allowed."); 137 } 138 mCursorIndex = position; 139 } 140 getCursorOffset()141 private int getCursorOffset() { 142 return mCursorIndex; 143 } 144 skipBytes(int bytesToSkip)145 private void skipBytes(int bytesToSkip) { 146 mCursorIndex += bytesToSkip; 147 } 148 getQueriesCount()149 private int getQueriesCount() { 150 return readUint16(OFFSET_QUERIES_COUNT); 151 } 152 getAnswersCount()153 private int getAnswersCount() { 154 return readUint16(OFFSET_ANSWERS_COUNT); 155 } 156 getAuthorityCount()157 private int getAuthorityCount() { 158 return readUint16(OFFSET_AUTHORITY_COUNT); 159 } 160 getAdditionalCount()161 private int getAdditionalCount() { 162 return readUint16(OFFSET_ADDITIONAL_COUNT); 163 } 164 moveToDataSection()165 private void moveToDataSection() { 166 mCursorIndex = OFFSET_DATA_SECTION_START; 167 } 168 pollLabel()169 private String pollLabel() { 170 int labelSize = readUint8(mCursorIndex); 171 mCursorIndex++; 172 if (mCursorIndex + labelSize > mMdnsData.length) { 173 throw new IllegalArgumentException( 174 "mDNS response packet is badly formed. Not enough data."); 175 } 176 String value = new String(mMdnsData, mCursorIndex, labelSize, StandardCharsets.UTF_8); 177 mCursorIndex += labelSize; 178 return value; 179 } 180 isCursorOnLabel()181 private boolean isCursorOnLabel() { 182 return !isCursorOnRootLabel() && (readUint8(mCursorIndex) & 0b11000000) == 0b00000000; 183 } 184 isCursorOnPointer()185 private boolean isCursorOnPointer() { 186 return (readUint8(mCursorIndex) & 0b11000000) == 0b11000000; 187 } 188 isCursorOnRootLabel()189 private boolean isCursorOnRootLabel() { 190 return readUint8(mCursorIndex) == 0; 191 } 192 pollPointerOffset()193 private int pollPointerOffset() { 194 int value = readUint16(mCursorIndex) & 0b0011111111111111; 195 mCursorIndex += 2; 196 return value; 197 } 198 readUint8(int offset)199 private int readUint8(int offset) { 200 if (offset >= mMdnsData.length) { 201 throw new IllegalArgumentException( 202 "mDNS response packet is badly formed. Not enough data."); 203 } 204 return ((int) mMdnsData[offset]) & 0xff; 205 } 206 readUint16(int offset)207 private int readUint16(int offset) { 208 if (offset + 1 >= mMdnsData.length) { 209 throw new IllegalArgumentException( 210 "mDNS response packet is badly formed. Not enough data."); 211 } 212 return (readUint8(offset) << 8) + readUint8(offset + 1); 213 } 214 pollUint16()215 private int pollUint16() { 216 int value = readUint16(mCursorIndex); 217 mCursorIndex += 2; 218 return value; 219 } 220 } 221