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