// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib.asn1;

import krb5.lib.Asn1Exception;
import krb5.lib.Krb5;

/**
 * Encode reference
 */
public class EncodeRef {

	/**
	 * Data
	 */
	public byte[] data;

	/**
	 * Index
	 */
	public int index;

	/**
	 * Class number
	 */
	public int class_num;

	/**
	 * Method number
	 */
	public int method_num;

	/**
	 * Tag number
	 */
	public int tag_num;

	/**
	 * Tag length
	 */
	public int tagLength;

	/**
	 * Length length
	 */
	public int lengthLength;

	/**
	 * Length
	 */
	public int length; //no meaning for indefinite encodings

	/**
	 * First byte of data portion
	 */
	public int start; //first byte of data portion

	/**
	 * One past last byte of this element
	 */
	public int end; //one past last byte of this element; no meaning for indefinite encodings

	/**
	 * Set when exiting of sequence
	 */
	public int endOfSequence; //set when entering a sequence only

	/**
	 * Set when entering sequence
	 */
	public boolean inSequence; //set when entering a sequence only

	/**
	 * Indefinite
	 */
	public boolean indefinite; //no meaning outside of a sequence

	/**
	 * End of contents
	 */
	public boolean endOfContents; //no meaning outside of a sequence

	/**
	 * Class constructor
	 *
	 * @param data_ref is of type byte[]
	 * @param curr_index is of type int
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef(byte[] data_ref, int curr_index)
		throws Asn1Exception {
		if (data_ref == null)
			throw new Asn1Exception(Krb5.ASN1_BAD_FORMAT);
		if (curr_index > data_ref.length)
			throw new Asn1Exception(Krb5.ASN1_OVERRUN);
		data = data_ref;
		index = curr_index;
		class_num = decode.Class(data, index);
		method_num = decode.Method(data, index);
		tag_num = decode.Tag(data, index);
		tagLength = decode.TagLength(data, index);
		lengthLength = decode.LengthLength(data, index + tagLength);
		start = index + tagLength + lengthLength;
		if (method_num == asn1Method.CONSTRUCTED &&
			data[index + tagLength] == (byte)0x80) {
			if (class_num == asn1Class.CONTEXT_SPECIFIC) {
				EncodeRef subRef = new EncodeRef(data, start);
				length = subRef.end + 2 - start; //extra 2 for endOfContents
			}
			else {
				//scan for length
				EncodeRef subRef = new EncodeRef(data, start);
				subRef.inSequence = true;
				subRef.indefinite = true;
				while (!subRef.endOfContents) {
					subRef.next();
				}
				length = subRef.end - start;
			}
		}
		else {
			length = decode.Length(data, index + tagLength);
		}
		end = index + tagLength + lengthLength + length;
	}

	/**
	 * Class constructor
	 *
	 * @param data_ref is of type byte[]
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef(byte[] data_ref) throws Asn1Exception {
		this(data_ref, 0);
	}

	/**
	 * Gets bytes
	 *
	 * @return byte[] is a result
	 */
	public byte[] getBytes() {
		byte[] result = new byte[length];
		System.arraycopy(data, start, result, 0, length);
		return result;
	}

	/**
	 * Is more elements
	 *
	 * @return boolean is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public boolean isMore() throws Asn1Exception {
		if (index > data.length)
			throw new Asn1Exception(Krb5.ASN1_OVERRUN);
		if (index < data.length)
			return true;
		return false;
	}

	/**
	 * Is there more in sequence
	 *
	 * @return boolean is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public boolean isMoreInSequence() throws Asn1Exception {
		if (index > data.length)
			throw new Asn1Exception(Krb5.ASN1_OVERRUN);
		if (!inSequence)
			return false;
		if (!indefinite) {
			if (index > endOfSequence)
				throw new Asn1Exception(Krb5.ASN1_OVERRUN);
			if (index < endOfSequence)
				return true;
		}
		else {
			if (endOfContents)
				return false;
			else
				return true;
		}
		return false;
	}

	/**
	 * Copies the encoding reference
	 *
	 * @return EncodeRef is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef copy() throws Asn1Exception {
		return new EncodeRef(data, index);
	}

	/**
	 * Sets end of sequence
	 *
	 * @param ref is of type EncodeRef
	 * @see krb5.lib.asn1.EncodeRef
	 */
	public void setEndOfSequence(EncodeRef ref) {
		inSequence = true;
		if (ref.method_num == asn1Method.CONSTRUCTED &&
			ref.data[ref.index + ref.tagLength] == (byte)0x80)
			indefinite = true;
		else
			endOfSequence = ref.end;
	}

	/**
	 * Returns start of the sequence
	 *
	 * @return EncodeRef is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef startOfSequence() throws Asn1Exception {
		if (data == null)
			throw new Asn1Exception(Krb5.ASN1_BAD_FORMAT);
		EncodeRef ref = new EncodeRef(data, start);
		ref.setEndOfSequence(this);
		return ref;
	}

	/**
	 * Returns start of data
	 *
	 * @return EncodeRef is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef startOfData() throws Asn1Exception {
		if (data == null)
			throw new Asn1Exception(Krb5.ASN1_BAD_FORMAT);
		return new EncodeRef(data, start);
	}

	//Idempotent at end of sequence
	/**
	 * Returns next encoding reference
	 *
	 * @return EncodeRef is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 */
	public EncodeRef next() throws Asn1Exception {
		if (data == null)
			throw new Asn1Exception(Krb5.ASN1_BAD_FORMAT);
		if (inSequence) {
			if (!indefinite) {
				if (index == endOfSequence)
					return this;
			}
			else {
				if (endOfContents)
					return this;
			}
		}
		index = end;
		if (inSequence && indefinite) {
			if (data[index] == 0 && data[index + 1] == 0)
				endOfContents = true;
		}
		if (inSequence) {
			if (!indefinite) {
				if (index == endOfSequence)
					return this;
			}
			else {
				//if (endOfContents)
				//	return this;
			}
		}
		class_num = decode.Class(data, index);
		method_num = decode.Method(data, index);
		tag_num = decode.Tag(data, index);
		tagLength = decode.TagLength(data, index);
		lengthLength = decode.LengthLength(data, index + tagLength);
		start = index + tagLength + lengthLength;
		if (method_num == asn1Method.CONSTRUCTED &&
			data[index + tagLength] == (byte)0x80) {
			if (class_num == asn1Class.CONTEXT_SPECIFIC) {
				EncodeRef subRef = new EncodeRef(data, start);
				length = subRef.end + 2 - start; //extra 2 for endOfContents
			}
			else {
				//scan for length
				EncodeRef subRef = new EncodeRef(data, start);
				subRef.inSequence = true;
				subRef.indefinite = true;
				while (!subRef.endOfContents) {
					subRef.next();
				}
				length = subRef.end - start;
			}
		}
		else {
			length = decode.Length(data, index + tagLength);
		}
		end = index + tagLength + lengthLength + length;
		return this;
	}

	/**
	 * Main
	 *
	 * @param args is of type String[]
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public void main(String[] args) throws Asn1Exception {
	/*
	//Sample 1: Decode SEQUENCE OF XXXX
	EncodeRef ref = new EncodeRef(data, 0);
	decodeSequence(ref);
	EncodeRef subRef = ref.startOfSequence();
	Vector tempXxxx = new Vector();
	while (subRef.isMoreInSequence()) {
		tempXxxx.addElement(new XXXX(subRef));
		subRef.next();
	}

	//Sample 2: Decode SEQUENCE (all subfields tagged in order/required)
	EncodeRef ref = new EncodeRef(data, 0);
	decodeSequence(ref);
	EncodeRef subRef = ref.startOfSequence();
	if (decodeTag(subRef) != 0) //error
		;
	xxxx = new XXXX(subRef.startOfData());
	subRef.next();
	if (decodeTag(subRef) != 1) //error
		;
	yyyy = new YYYY(subRef.startOfData());
	subRef.next();
	if (decodeTag(subRef) != 2) //error
		;
	zzzz = new ZZZZ(subRef.startOfData());
	subRef.next();
	if (subRef.isMoreInSequence()) //error
		;

	//Sample 3: Decode SEQUENCE (all subfields tagged and ordered; some optional)
	EncodeRef ref = new EncodeRef(data, 0);
	decodeSequence(ref);
	EncodeRef subRef = ref.startOfSequence();
	if (decodeTag(subRef) != 0) //error
		;
	xxxx = new XXXX(subRef.startOfData());
	subRef.next();
	if (decodeTag(subRef) == 1) {  //non-destructive/idempotent
		yyyy = new YYYY(subRef.startOfData());  //could be rolled
		subRef.next();                          //together
	}
	if (decodeTag(subRef) == 2) {
		zzzz = new ZZZZ(subRef.startOfData());
		subRef.next();
	}
	if (decodeTag(subRef) != 3) //error
		;
	yyyy = new YYYY(subRef.startOfData());
	subRef.next();                    //to see if there are any more
	if (subRef.isMoreInSequence() && decodeTag(subRef) == 4) {
		yyyy = new YYYY(subRef.startOfData());
		subRef.next();
	}
	if (subRef.isMoreInSequence() && decodeTag(subRef) == 5) {
		zzzz = new ZZZZ(subRef.startOfData());
		subRef.next();
	}
	if (subRef.isMoreInSequence()) //error
		;

	//Sample 4: Decode SEQUENCE (all subfields tagged but unordered; some optional)
	EncodeRef ref = new EncodeRef(data, 0);
	decodeSequence(ref);
	EncodeRef subRef = ref.startOfSequence();
	while (subRef.isMoreInSequence())
		switch (decodeTag(subRef)) {
		case 0:
			xxxx = new XXXX(subRef.startOfData());
			subRef.next();
			break;
		case 1:
			yyyy = new YYYY(subRef.startOfData());
			subRef.next();
			break;
		case 2:
			zzzz = new ZZZZ(subRef.startOfData());
			subRef.next();
			break;
		default: //error
			;
		}
	}

	//Ultra-condensed versions

	//Sample 1: Decode SEQUENCE OF XXXX
	EncodeRef ref = new EncodeRef(data, 0);
	EncodeRef subRef = decodeSequence(ref); //returns ref.startOfSequence();
	Vector tempXxxx = new Vector();
	while (subRef.isMoreInSequence()) {
		tempXxxx.addElement(new XXXX(subRef)); //subRef->subRef.next()
	                                           //XXXX acts on only on subRef internally
	}

	//Sample 2: Decode SEQUENCE (all subfields tagged in order/required)
	EncodeRef ref = new EncodeRef(data, 0);
	EncodeRef subRef = decodeSequence(ref); //returns ref.startOfSequence();
	if (decodeTag(subRef) != 0) //error; subRef->subRef
		;
	xxxx = new XXXX(subRef);    //subRef->subRef.next();
	                            //XXXX acts on subRef.startOfSequence() internally
	if (decodeTag(subRef) != 1) //error; subRef->subRef
		;
	yyyy = new YYYY(subRef);    //subRef->subRef.next()
	if (decodeTag(subRef) != 2) //error; subRef->subRef
		;
	zzzz = new ZZZZ(subRef);    //subRef->subRef.next()
	if (subRef.isMoreInSequence()) //error
		;

	//Sample 3: Decode SEQUENCE (all subfields tagged and ordered; some optional)
	EncodeRef ref = new EncodeRef(data, 0);
	EncodeRef subRef = decodeSequence(ref); //returns ref.startOfSequence();
	if (decodeTag(subRef) != 0)    //error; subRef->subRef
		;
	xxxx = new XXXX(subRef);       //subRef->subRef.next()
	                               //XXXX acts on subRef.startOfSequence() internally
	if (decodeTag(subRef) == 1)    //subRef->subRef
		yyyy = new YYYY(subRef);   //subRef->subRef.next()
	if (decodeTag(subRef) == 2)    //subRef->subRef
		zzzz = new ZZZZ(subRef);   //subRef->subRef.next()
	if (decodeTag(subRef) != 3)    //error; subRef->subRef
		;
	yyyy = new YYYY(subRef);       //subRef->subRef.next()
	if (subRef.isMoreInSequence() && decodeTag(subRef) == 4) //subRef->subRef
		yyyy = new YYYY(subRef);   //subRef->subRef.next()
	if (subRef.isMoreInSequence() && decodeTag(subRef) == 5) //subRef->subRef
		zzzz = new ZZZZ(subRef);   //subRef->subRef.next()
	if (subRef.isMoreInSequence()) //error
		;

	//Sample 4: Decode SEQUENCE (all subfields tagged but unordered; some optional)
	EncodeRef ref = new EncodeRef(data, 0);
	EncodeRef subRef = decodeSequence(ref); //returns ref.startOfSequence();
	while (subRef.isMoreInSequence()) {
		switch (decodeTag(subRef)) {   //subRef->subRef
		case 0:
			xxxx = new XXXX(subRef);   //subRef->subRef.next()
	                                   //XXXX acts on subRef.startOfSequence() internally
			break;
		case 1:
			yyyy = new YYYY(subRef);   //subRef->subRef.next()
			break;
		case 2:
			zzzz = new ZZZZ(subRef);   //subRef->subRef.next()
			break;
		default: //error
			;
		}
	}
	*/
	}

}