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