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; 025import java.util.logging.Logger; 026 027/** 028 * NSEC record payload. 029 */ 030public class NSEC extends Data { 031 032 private static final Logger LOGGER = Logger.getLogger(NSEC.class.getName()); 033 034 /** 035 * The next owner name that contains a authoritative data or a delegation point. 036 */ 037 public final DnsName next; 038 039 private final byte[] typeBitmap; 040 041 /** 042 * The RR types existing at the owner name. 043 */ 044 public final List<TYPE> types; 045 046 public static NSEC parse(DataInputStream dis, byte[] data, int length) throws IOException { 047 DnsName next = DnsName.parse(dis, data); 048 049 byte[] typeBitmap = new byte[length - next.size()]; 050 if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException(); 051 List<TYPE> types = readTypeBitMap(typeBitmap); 052 return new NSEC(next, types); 053 } 054 055 public NSEC(String next, List<TYPE> types) { 056 this(DnsName.from(next), types); 057 } 058 059 public NSEC(String next, TYPE... types) { 060 this(DnsName.from(next), Arrays.asList(types)); 061 } 062 063 public NSEC(DnsName next, List<TYPE> types) { 064 this.next = next; 065 this.types = Collections.unmodifiableList(types); 066 this.typeBitmap = createTypeBitMap(types); 067 } 068 069 @Override 070 public TYPE getType() { 071 return TYPE.NSEC; 072 } 073 074 @Override 075 public void serialize(DataOutputStream dos) throws IOException { 076 next.writeToStream(dos); 077 dos.write(typeBitmap); 078 } 079 080 @Override 081 public String toString() { 082 StringBuilder sb = new StringBuilder() 083 .append(next).append('.'); 084 for (TYPE type : types) { 085 sb.append(' ').append(type); 086 } 087 return sb.toString(); 088 } 089 090 @SuppressWarnings("NarrowingCompoundAssignment") 091 static byte[] createTypeBitMap(List<TYPE> types) { 092 List<Integer> typeList = new ArrayList<Integer>(types.size()); 093 for (TYPE type : types) { 094 typeList.add(type.getValue()); 095 } 096 Collections.sort(typeList); 097 098 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 099 DataOutputStream dos = new DataOutputStream(baos); 100 101 try { 102 int windowBlock = -1; 103 byte[] bitmap = null; 104 for (Integer type : typeList) { 105 if (windowBlock == -1 || (type >> 8) != windowBlock) { 106 if (windowBlock != -1) writeOutBlock(bitmap, dos); 107 windowBlock = type >> 8; 108 dos.writeByte(windowBlock); 109 bitmap = new byte[32]; 110 } 111 int a = (type >> 3) % 32; 112 int b = type % 8; 113 bitmap[a] |= (byte) (128 >> b); 114 } 115 if (windowBlock != -1) writeOutBlock(bitmap, dos); 116 } catch (IOException e) { 117 // Should never happen. 118 throw new RuntimeException(e); 119 } 120 121 return baos.toByteArray(); 122 } 123 124 private static void writeOutBlock(byte[] values, DataOutputStream dos) throws IOException { 125 int n = 0; 126 for (int i = 0; i < values.length; i++) { 127 if (values[i] != 0) n = i + 1; 128 } 129 dos.writeByte(n); 130 for (int i = 0; i < n; i++) { 131 dos.writeByte(values[i]); 132 } 133 } 134 135 // TODO: This method should probably just return List<Integer> so that unknown types can be act on later. 136 static List<TYPE> readTypeBitMap(byte[] typeBitmap) throws IOException { 137 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(typeBitmap)); 138 int read = 0; 139 ArrayList<TYPE> typeList = new ArrayList<TYPE>(); 140 while (typeBitmap.length > read) { 141 int windowBlock = dis.readUnsignedByte(); 142 int bitmapLength = dis.readUnsignedByte(); 143 for (int i = 0; i < bitmapLength; i++) { 144 int b = dis.readUnsignedByte(); 145 for (int j = 0; j < 8; j++) { 146 if (((b >> j) & 0x1) > 0) { 147 int typeInt = (windowBlock << 8) + (i * 8) + (7 - j); 148 TYPE type = TYPE.getType(typeInt); 149 if (type == TYPE.UNKNOWN) { 150 LOGGER.warning("Skipping unknown type in type bitmap: " + typeInt); 151 continue; 152 } 153 typeList.add(type); 154 } 155 } 156 } 157 read += bitmapLength + 2; 158 } 159 return typeList; 160 } 161}