001/* 002 * Copyright 2015-2024 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.record; 012 013import org.minidns.dnsname.DnsName; 014import org.minidns.record.Record.TYPE; 015 016import java.io.ByteArrayInputStream; 017import java.io.ByteArrayOutputStream; 018import java.io.DataInputStream; 019import java.io.DataOutputStream; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.List; 025 026/** 027 * NSEC record payload. 028 */ 029public class NSEC extends Data { 030 031 /** 032 * The next owner name that contains a authoritative data or a delegation point. 033 */ 034 public final DnsName next; 035 036 /** 037 * The type bitmap in wire format. If this NSEC was parsed, then those are the exact bytes as received. This is 038 * important, as re-creating the bitmap from {@link #types} would lose the types unknown to MiniDNS, which would 039 * cause the RRSIG covering this NSEC to fail to verify. 040 */ 041 private final byte[] typeBitmap; 042 043 /** 044 * The RR types existing at the owner name. This only contains the types known to MiniDNS, see 045 * {@link #typeValues} for all types. 046 */ 047 public final List<TYPE> types; 048 049 /** 050 * The numeric values of all RR types existing at the owner name, including the ones unknown to MiniDNS. 051 */ 052 public final List<Integer> typeValues; 053 054 public static NSEC parse(DataInputStream dis, byte[] data, int length) throws IOException { 055 DnsName next = DnsName.parse(dis, data); 056 057 byte[] typeBitmap = new byte[length - next.size()]; 058 if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException(); 059 List<Integer> typeValues = readTypeBitMap(typeBitmap); 060 return new NSEC(next, typeBitmap, typeValues); 061 } 062 063 public NSEC(String next, List<TYPE> types) { 064 this(DnsName.from(next), types); 065 } 066 067 public NSEC(String next, TYPE... types) { 068 this(DnsName.from(next), Arrays.asList(types)); 069 } 070 071 public NSEC(DnsName next, List<TYPE> types) { 072 this(next, createTypeBitMap(types), toTypeValues(types)); 073 } 074 075 private NSEC(DnsName next, byte[] typeBitmap, List<Integer> typeValues) { 076 this.next = next; 077 this.typeBitmap = typeBitmap; 078 this.typeValues = Collections.unmodifiableList(typeValues); 079 this.types = Collections.unmodifiableList(toKnownTypes(typeValues)); 080 } 081 082 @Override 083 public TYPE getType() { 084 return TYPE.NSEC; 085 } 086 087 @Override 088 public void serialize(DataOutputStream dos) throws IOException { 089 next.writeToStream(dos); 090 dos.write(typeBitmap); 091 } 092 093 @Override 094 public String toString() { 095 StringBuilder sb = new StringBuilder() 096 .append(next).append('.'); 097 appendTypes(sb, typeValues); 098 return sb.toString(); 099 } 100 101 static List<Integer> toTypeValues(List<TYPE> types) { 102 List<Integer> typeValues = new ArrayList<>(types.size()); 103 for (TYPE type : types) { 104 typeValues.add(type.getValue()); 105 } 106 return typeValues; 107 } 108 109 static List<TYPE> toKnownTypes(List<Integer> typeValues) { 110 List<TYPE> types = new ArrayList<>(typeValues.size()); 111 for (int typeValue : typeValues) { 112 TYPE type = TYPE.getType(typeValue); 113 if (type == TYPE.UNKNOWN) { 114 continue; 115 } 116 types.add(type); 117 } 118 return types; 119 } 120 121 /** 122 * Append the given types to the given string builder. Types unknown to MiniDNS are written in the generic 123 * "TYPE<value>" notation as per RFC 3597 ยง 5. 124 * 125 * @param sb the string builder. 126 * @param typeValues the numeric values of the types. 127 */ 128 static void appendTypes(StringBuilder sb, List<Integer> typeValues) { 129 for (int typeValue : typeValues) { 130 sb.append(' '); 131 TYPE type = TYPE.getType(typeValue); 132 if (type == TYPE.UNKNOWN) { 133 sb.append("TYPE").append(typeValue); 134 } else { 135 sb.append(type); 136 } 137 } 138 } 139 140 @SuppressWarnings("NarrowingCompoundAssignment") 141 static byte[] createTypeBitMap(List<TYPE> types) { 142 List<Integer> typeList = toTypeValues(types); 143 Collections.sort(typeList); 144 145 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 146 DataOutputStream dos = new DataOutputStream(baos); 147 148 try { 149 int windowBlock = -1; 150 byte[] bitmap = null; 151 for (Integer type : typeList) { 152 if (windowBlock == -1 || (type >> 8) != windowBlock) { 153 if (windowBlock != -1) writeOutBlock(bitmap, dos); 154 windowBlock = type >> 8; 155 dos.writeByte(windowBlock); 156 bitmap = new byte[32]; 157 } 158 int a = (type >> 3) % 32; 159 int b = type % 8; 160 bitmap[a] |= (byte) (128 >> b); 161 } 162 if (windowBlock != -1) writeOutBlock(bitmap, dos); 163 } catch (IOException e) { 164 // Should never happen. 165 throw new RuntimeException(e); 166 } 167 168 return baos.toByteArray(); 169 } 170 171 private static void writeOutBlock(byte[] values, DataOutputStream dos) throws IOException { 172 int n = 0; 173 for (int i = 0; i < values.length; i++) { 174 if (values[i] != 0) n = i + 1; 175 } 176 dos.writeByte(n); 177 for (int i = 0; i < n; i++) { 178 dos.writeByte(values[i]); 179 } 180 } 181 182 /** 183 * Read the numeric values of all types in the given type bitmap, in ascending order. 184 * 185 * @param typeBitmap the type bitmap in wire format. 186 * @return the numeric values of the types, including the ones unknown to MiniDNS. 187 * @throws IOException if the type bitmap is malformed. 188 */ 189 static List<Integer> readTypeBitMap(byte[] typeBitmap) throws IOException { 190 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(typeBitmap)); 191 int read = 0; 192 List<Integer> typeList = new ArrayList<>(); 193 while (typeBitmap.length > read) { 194 int windowBlock = dis.readUnsignedByte(); 195 int bitmapLength = dis.readUnsignedByte(); 196 for (int i = 0; i < bitmapLength; i++) { 197 int b = dis.readUnsignedByte(); 198 for (int j = 7; j >= 0; j--) { 199 if (((b >> j) & 0x1) > 0) { 200 int typeInt = (windowBlock << 8) + (i * 8) + (7 - j); 201 typeList.add(typeInt); 202 } 203 } 204 } 205 read += bitmapLength + 2; 206 } 207 return typeList; 208 } 209}