// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

/**
 * An class for parsing DNS messages.
 *
 * @author Brian Wellington
 */

public class DNSInput {

    private byte[] array;
    private int pos;
    private int end;
    private int saved_pos;
    private int saved_end;

    /**
     * Creates a new DNSInput
     *
     * @param input The byte array to read from
     */
    public DNSInput(byte[] input) {
        array = input;
        pos = 0;
        end = array.length;
        saved_pos = -1;
        saved_end = -1;
    }

    /**
     * Returns the current position.
     */
    public int
    current() {
        return pos;
    }

    /**
     * Returns the number of bytes that can be read from this stream before
     * reaching the end.
     */
    public int
    remaining() {
        return end - pos;
    }

    private void
    require(int n) throws WireParseException {
        if (n > remaining()) {
            throw new WireParseException("end of input");
        }
    }

    /**
     * Marks the following bytes in the stream as active.
     *
     * @param len The number of bytes in the active region.
     * @throws IllegalArgumentException The number of bytes in the active region
     *                                  is longer than the remainder of the input.
     */
    public void
    setActive(int len) {
        if (len > array.length - pos) {
            throw new IllegalArgumentException("cannot set active " +
                    "region past end of input");
        }
        end = pos + len;
    }

    /**
     * Clears the active region of the string.  Further operations are not
     * restricted to part of the input.
     */
    public void
    clearActive() {
        end = array.length;
    }

    /**
     * Returns the position of the end of the current active region.
     */
    public int
    saveActive() {
        return end;
    }

    /**
     * Restores the previously set active region.  This differs from setActive() in
     * that restoreActive() takes an absolute position, and setActive takes an
     * offset from the current location.
     *
     * @param pos The end of the active region.
     */
    public void
    restoreActive(int pos) {
        if (pos > array.length) {
            throw new IllegalArgumentException("cannot set active " +
                    "region past end of input");
        }
        end = pos;
    }

    /**
     * Resets the current position of the input stream to the specified index,
     * and clears the active region.
     *
     * @param index The position to continue parsing at.
     * @throws IllegalArgumentException The index is not within the input.
     */
    public void
    jump(int index) {
        if (index >= array.length) {
            throw new IllegalArgumentException("cannot jump past " +
                    "end of input");
        }
        pos = index;
        end = array.length;
    }

    /**
     * Saves the current state of the input stream.  Both the current position and
     * the end of the active region are saved.
     *
     * @throws IllegalArgumentException The index is not within the input.
     */
    public void
    save() {
        saved_pos = pos;
        saved_end = end;
    }

    /**
     * Restores the input stream to its state before the call to {@link #save}.
     */
    public void
    restore() {
        if (saved_pos < 0) {
            throw new IllegalStateException("no previous state");
        }
        pos = saved_pos;
        end = saved_end;
        saved_pos = -1;
        saved_end = -1;
    }

    /**
     * Reads an unsigned 8 bit value from the stream, as an int.
     *
     * @return An unsigned 8 bit value.
     * @throws WireParseException The end of the stream was reached.
     */
    public int
    readU8() throws WireParseException {
        require(1);
        return (array[pos++] & 0xFF);
    }

    /**
     * Reads an unsigned 16 bit value from the stream, as an int.
     *
     * @return An unsigned 16 bit value.
     * @throws WireParseException The end of the stream was reached.
     */
    public int
    readU16() throws WireParseException {
        require(2);
        int b1 = array[pos++] & 0xFF;
        int b2 = array[pos++] & 0xFF;
        return ((b1 << 8) + b2);
    }

    /**
     * Reads an unsigned 32 bit value from the stream, as a long.
     *
     * @return An unsigned 32 bit value.
     * @throws WireParseException The end of the stream was reached.
     */
    public long
    readU32() throws WireParseException {
        require(4);
        int b1 = array[pos++] & 0xFF;
        int b2 = array[pos++] & 0xFF;
        int b3 = array[pos++] & 0xFF;
        int b4 = array[pos++] & 0xFF;
        return (((long) b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
    }

    /**
     * Reads a byte array of a specified length from the stream into an existing
     * array.
     *
     * @param b   The array to read into.
     * @param off The offset of the array to start copying data into.
     * @param len The number of bytes to copy.
     * @throws WireParseException The end of the stream was reached.
     */
    public void
    readByteArray(byte[] b, int off, int len) throws WireParseException {
        require(len);
        System.arraycopy(array, pos, b, off, len);
        pos += len;
    }

    /**
     * Reads a byte array of a specified length from the stream.
     *
     * @return The byte array.
     * @throws WireParseException The end of the stream was reached.
     */
    public byte[]
    readByteArray(int len) throws WireParseException {
        require(len);
        byte[] out = new byte[len];
        System.arraycopy(array, pos, out, 0, len);
        pos += len;
        return out;
    }

    /**
     * Reads a byte array consisting of the remainder of the stream (or the
     * active region, if one is set.
     *
     * @return The byte array.
     */
    public byte[]
    readByteArray() {
        int len = remaining();
        byte[] out = new byte[len];
        System.arraycopy(array, pos, out, 0, len);
        pos += len;
        return out;
    }

    /**
     * Reads a counted string from the stream.  A counted string is a one byte
     * value indicating string length, followed by bytes of data.
     *
     * @return A byte array containing the string.
     * @throws WireParseException The end of the stream was reached.
     */
    public byte[]
    readCountedString() throws WireParseException {
        require(1);
        int len = array[pos++] & 0xFF;
        return readByteArray(len);
    }

}
