1 /*
2  * Copyright (C) 2022 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.net.DnsResolver;
20 import android.text.TextUtils;
21 
22 import com.android.net.module.util.CollectionUtils;
23 import com.android.server.connectivity.mdns.util.MdnsUtils;
24 
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Objects;
29 
30 /**
31  * A mDNS "NSEC" record, used in particular for negative responses (RFC6762 6.1).
32  */
33 public class MdnsNsecRecord extends MdnsRecord {
34     private String[] mNextDomain;
35     private int[] mTypes;
36 
MdnsNsecRecord(String[] name, MdnsPacketReader reader)37     public MdnsNsecRecord(String[] name, MdnsPacketReader reader) throws IOException {
38         this(name, reader, false);
39     }
40 
MdnsNsecRecord(String[] name, MdnsPacketReader reader, boolean isQuestion)41     public MdnsNsecRecord(String[] name, MdnsPacketReader reader, boolean isQuestion)
42             throws IOException {
43         super(name, TYPE_NSEC, reader, isQuestion);
44     }
45 
MdnsNsecRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis, String[] nextDomain, int[] types)46     public MdnsNsecRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis,
47             String[] nextDomain, int[] types) {
48         super(name, TYPE_NSEC, DnsResolver.CLASS_IN, receiptTimeMillis, cacheFlush, ttlMillis);
49         mNextDomain = nextDomain;
50         final int[] sortedTypes = Arrays.copyOf(types, types.length);
51         Arrays.sort(sortedTypes);
52         mTypes = sortedTypes;
53     }
54 
getNextDomain()55     public String[] getNextDomain() {
56         return mNextDomain;
57     }
58 
getTypes()59     public int[] getTypes() {
60         return mTypes;
61     }
62 
63     @Override
readData(MdnsPacketReader reader)64     protected void readData(MdnsPacketReader reader) throws IOException {
65         mNextDomain = reader.readLabels();
66         mTypes = readTypes(reader);
67     }
68 
readTypes(MdnsPacketReader reader)69     private int[] readTypes(MdnsPacketReader reader) throws IOException {
70         // See RFC3845 #2.1.2
71         final ArrayList<Integer> types = new ArrayList<>();
72         int prevBlockNumber = -1;
73         while (reader.getRemaining() > 0) {
74             final int blockNumber = reader.readUInt8();
75             if (blockNumber <= prevBlockNumber) {
76                 throw new IOException(
77                         "Unordered block number: " + blockNumber + " after " + prevBlockNumber);
78             }
79             prevBlockNumber = blockNumber;
80             final int bitmapLength = reader.readUInt8();
81             if (bitmapLength > 32 || bitmapLength <= 0) {
82                 throw new IOException("Invalid bitmap length: " + bitmapLength);
83             }
84             final byte[] bitmap = new byte[bitmapLength];
85             reader.readBytes(bitmap);
86 
87             for (int bitmapIndex = 0; bitmapIndex < bitmap.length; bitmapIndex++) {
88                 final byte bitmapByte = bitmap[bitmapIndex];
89                 for (int bit = 0; bit < 8; bit++) {
90                     if ((bitmapByte & (1 << (7 - bit))) != 0) {
91                         types.add(blockNumber * 256 + bitmapIndex * 8 + bit);
92                     }
93                 }
94             }
95         }
96 
97         return CollectionUtils.toIntArray(types);
98     }
99 
100     @Override
writeData(MdnsPacketWriter writer)101     protected void writeData(MdnsPacketWriter writer) throws IOException {
102         // Standard NSEC records should use no compression for the Next Domain Name field as per
103         // RFC3845 2.1.1, but for mDNS RFC6762 18.14 specifies that compression should be used.
104         writer.writeLabels(mNextDomain);
105 
106         // type bitmaps: RFC3845 2.1.2
107         int typesBlockStart = 0;
108         int pendingBlockNumber = -1;
109         int blockLength = 0;
110         // Loop on types (which are sorted in increasing order) to find each block and determine
111         // their length; use writeTypeBlock once the length of each block has been found.
112         for (int i = 0; i < mTypes.length; i++) {
113             final int blockNumber = mTypes[i] / 256;
114             final int typeLowOrder = mTypes[i] % 256;
115             // If the low-order 8 bits are e.g. 0x10, bit number 16 (=0x10) will be set in the
116             // bitmap; this is the first bit of byte 2 (byte 0 is 0-7, 1 is 8-15, etc.)
117             final int byteIndex = typeLowOrder / 8;
118 
119             if (pendingBlockNumber >= 0 && blockNumber != pendingBlockNumber) {
120                 // Just reached a new block; write the previous one
121                 writeTypeBlock(writer, typesBlockStart, i - 1, blockLength);
122                 typesBlockStart = i;
123                 blockLength = 0;
124             }
125             blockLength = Math.max(blockLength, byteIndex + 1);
126             pendingBlockNumber = blockNumber;
127         }
128 
129         if (pendingBlockNumber >= 0) {
130             writeTypeBlock(writer, typesBlockStart, mTypes.length - 1, blockLength);
131         }
132     }
133 
writeTypeBlock(MdnsPacketWriter writer, int typesStart, int typesEnd, int bytesInBlock)134     private void writeTypeBlock(MdnsPacketWriter writer,
135             int typesStart, int typesEnd, int bytesInBlock) throws IOException {
136         final int blockNumber = mTypes[typesStart] / 256;
137         final byte[] bytes = new byte[bytesInBlock];
138         for (int i = typesStart; i <= typesEnd; i++) {
139             final int typeLowOrder = mTypes[i] % 256;
140             bytes[typeLowOrder / 8] |= 1 << (7 - (typeLowOrder % 8));
141         }
142         writer.writeUInt8(blockNumber);
143         writer.writeUInt8(bytesInBlock);
144         writer.writeBytes(bytes);
145     }
146 
147     @Override
toString()148     public String toString() {
149         return "NSEC: NextDomain: " + TextUtils.join(".", mNextDomain)
150                 + " Types " + Arrays.toString(mTypes);
151     }
152 
153     @Override
hashCode()154     public int hashCode() {
155         return Objects.hash(super.hashCode(),
156                 Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(mNextDomain)),
157                 Arrays.hashCode(mTypes));
158     }
159 
160     @Override
equals(Object other)161     public boolean equals(Object other) {
162         if (this == other) {
163             return true;
164         }
165         if (!(other instanceof MdnsNsecRecord)) {
166             return false;
167         }
168 
169         return super.equals(other)
170                 && MdnsUtils.equalsDnsLabelIgnoreDnsCase(mNextDomain,
171                 ((MdnsNsecRecord) other).mNextDomain)
172                 && Arrays.equals(mTypes, ((MdnsNsecRecord) other).mTypes);
173     }
174 }
175