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}