/*
 * @(#)DnsInputStream.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
package com.jscape.inet.dns;

import com.jscape.inet.dns.record.DnsRecord;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;


/**
 * InputStream responsible for parsing byte[] data retrieved in communcations
 * with a Dns server.
 */
public class DnsInputStream
        extends ByteArrayInputStream {
    protected DataInputStream dataIn;

    /**
     * Constructs a new <code>DnsInputStream</code> instance.
     *
     * @param data the input buffer
     * @param off  the offset in the buffer of the first byte to read.
     * @param len  the maximum number of bytes to read from the buffer.
     */
    public DnsInputStream(byte[] data, int off, int len) {
        super(data, off, len);
        dataIn = new DataInputStream(this);
    }

    /**
     * Reads the next byte of data from this input stream.
     *
     * @return the 8-bit value read.
     *
     * @throws IOException if an I/O error occurs
     */
    public int readByte()
            throws IOException {
        return dataIn.readUnsignedByte();
    }

    /**
     * Reads two input bytes and returns a unsigned short value.
     *
     * @return the 16-bit value read.
     *
     * @throws IOException if an I/O error occurs
     */
    public int readShort()
            throws IOException {
        return dataIn.readUnsignedShort();
    }

    /**
     * Reads four input bytes and returns an int value.
     *
     * @return the int value read.
     *
     * @throws IOException if an I/O error occurs
     */
    public long readInt()
            throws IOException {
        return dataIn.readInt() & 0xffffffffL;
    }

    /**
     * Reads String from response.
     *
     * @return the String read.
     *
     * @throws IOException if an I/O error occurs
     */
    public String readString()
            throws IOException {
        int len = readByte();

        if (len == 0) {
            return "";
        } else {
            byte[] buffer = new byte[len];

            dataIn.readFully(buffer);

            return new String(buffer, "latin1");
        }
    }

    /**
     * Parses out domain name from response.
     *
     * @return domain name
     *
     * @throws IOException if an I/O error occurs
     */
    public String readDomainName()
            throws IOException {
        if (pos >= count) {
            throw new EOFException("EOF reading domain name");
        }

        if ((buf[pos] & 0xc0) == 0) {
            String label = readString();

            if (label.length() > 0) {
                String tail = readDomainName();

                if (tail.length() > 0) {
                    label = label + '.' + tail;
                }
            }

            return label;
        } else {
            if ((buf[pos] & 0xc0) != 0xc0) {
                throw new IOException("Invalid domain name compression offset");
            }

            int offset = readShort() & 0x3fff;
            DnsInputStream dnsIn = new DnsInputStream(buf, offset,
                                                      buf.length - offset);

            return dnsIn.readDomainName();
        }
    }

    /**
     * Reads a <code>DnsRecord</code> from response.
     *
     * @return the <code>DnsRecord</code> read.
     *
     * @throws IOException if an I/O error occurs
     * @see com.jscape.inet.dns.record.DnsRecord
     */
    public DnsRecord readRecord()
            throws IOException {
        String recordName = readDomainName();
        int recordType = readShort();
        int recordClass = readShort();
        long recordTTL = readInt();
        int recordLength = readShort();
        DnsInputStream inputStream = new DnsInputStream(buf, pos, recordLength);

        pos += recordLength;

        try {
            Class theClass = Dns.typeName(recordType);

            if (theClass == null) {
                return null;
            }

            DnsRecord record = (DnsRecord) theClass.newInstance();

            record.init(recordName, recordType, recordClass, recordTTL,
                        inputStream);

            return record;
        } catch (IllegalAccessException ex) {
            throw new IOException("Access error creating DnsRecord (type " + Dns.typeName(recordType) + ')');
        } catch (InstantiationException ex) {
            throw new IOException("Instantiation error creating DnsRecord (type " + Dns.typeName(recordType) + ')');
        }
    }
}
