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