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