/*
 * Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The names of the authors may not be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jcraft.jsch.asn1;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ASN1 {
  byte[] buf;
  int start;
  int length;

  public ASN1(byte[] buf) throws ASN1Exception {
    this(buf, 0, buf.length);
  }

  public ASN1(byte[] buf, int start, int length) throws ASN1Exception {
    this.buf = buf;
    this.start = start;
    this.length = length;
    if (start + length > buf.length)
      throw new ASN1Exception();
  }

  public int getType() {
    return buf[start] & 0xff;
  }

  public boolean isSEQUENCE() {
    return getType() == (0x30 & 0xff);
  }

  public boolean isINTEGER() {
    return getType() == (0x02 & 0xff);
  }

  public boolean isOBJECT() {
    return getType() == (0x06 & 0xff);
  }

  public boolean isOCTETSTRING() {
    return getType() == (0x04 & 0xff);
  }

  public boolean isNULL() {
    return getType() == (0x05 & 0xff);
  }

  public boolean isBITSTRING() {
    return getType() == (0x03 & 0xff);
  }

  public boolean isCONTEXTPRIMITIVE(int tag) {
    if ((tag & ~0xff) != 0 || (tag & 0x60) != 0) {
      throw new IllegalArgumentException();
    }
    return getType() == ((tag | 0x80) & 0xff);
  }

  public boolean isCONTEXTCONSTRUCTED(int tag) {
    if ((tag & ~0xff) != 0 || (tag & 0x40) != 0) {
      throw new IllegalArgumentException();
    }
    return getType() == ((tag | 0xa0) & 0xff);
  }

  private int getLength(int[] indexp) {
    int index = indexp[0];
    int length = buf[index++] & 0xff;
    if ((length & 0x80) != 0) {
      int foo = length & 0x7f;
      length = 0;
      while (foo-- > 0) {
        length = (length << 8) + (buf[index++] & 0xff);
      }
    }
    indexp[0] = index;
    return length;
  }

  public byte[] getContent() {
    int[] indexp = new int[1];
    indexp[0] = start + 1;
    int length = getLength(indexp);
    int index = indexp[0];
    byte[] tmp = new byte[length];
    System.arraycopy(buf, index, tmp, 0, tmp.length);
    return tmp;
  }

  public ASN1[] getContents() throws ASN1Exception {
    int typ = buf[start];
    int[] indexp = new int[1];
    indexp[0] = start + 1;
    int length = getLength(indexp);
    if (typ == 0x05) {
      return new ASN1[0];
    }
    int index = indexp[0];
    List<ASN1> values = new ArrayList<>();
    while (length > 0) {
      index++;
      length--;
      int tmp = index;
      indexp[0] = index;
      int l = getLength(indexp);
      index = indexp[0];
      length -= (index - tmp);
      values.add(new ASN1(buf, tmp - 1, 1 + (index - tmp) + l));
      index += l;
      length -= l;
    }
    ASN1[] result = new ASN1[values.size()];
    values.toArray(result);
    return result;
  }

  public byte[] getRaw() {
    return Arrays.copyOfRange(buf, start, start + length);
  }

  public static int parseASN1IntegerAsInt(byte[] content) throws ArithmeticException {
    BigInteger b = new BigInteger(content);
    // https://github.com/mwiede/jsch/issues/392 not using intValueExact() because of Android
    // incompatibility.
    if (b.bitLength() <= 31) {
      return b.intValue();
    } else {
      throw new ArithmeticException("BigInteger out of int range");
    }
  }
}
