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&lt;value&gt;" 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}