Home | History | Annotate | Download | only in base
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.location.cts.asn1.base;
     18 
     19 import com.google.common.annotations.VisibleForTesting;
     20 
     21 import java.math.BigInteger;
     22 import java.nio.ByteBuffer;
     23 
     24 import javax.annotation.Nullable;
     25 
     26 /**
     27  * Represents the tag of (class and number) of an ASN.1 type.
     28  */
     29 public class Asn1Tag {
     30   public static final Asn1Tag BOOLEAN = new Asn1Tag(Asn1TagClass.UNIVERSAL, 1);
     31   public static final Asn1Tag INTEGER = new Asn1Tag(Asn1TagClass.UNIVERSAL, 2);
     32   public static final Asn1Tag BIT_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 3);
     33   public static final Asn1Tag OCTET_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 4);
     34   public static final Asn1Tag NULL = new Asn1Tag(Asn1TagClass.UNIVERSAL, 5);
     35   public static final Asn1Tag OBJECT_IDENTIFIER = new Asn1Tag(Asn1TagClass.UNIVERSAL, 6);
     36   public static final Asn1Tag OBJECT_DESCRIPTOR = new Asn1Tag(Asn1TagClass.UNIVERSAL, 7);
     37   public static final Asn1Tag REAL = new Asn1Tag(Asn1TagClass.UNIVERSAL, 9);
     38   public static final Asn1Tag ENUMERATED = new Asn1Tag(Asn1TagClass.UNIVERSAL, 10);
     39   public static final Asn1Tag UTF8STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 12);
     40   public static final Asn1Tag SEQUENCE = new Asn1Tag(Asn1TagClass.UNIVERSAL, 16);
     41   public static final Asn1Tag SET = new Asn1Tag(Asn1TagClass.UNIVERSAL, 17);
     42   public static final Asn1Tag NUMERIC_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 18);
     43   public static final Asn1Tag PRINTABLE_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 19);
     44   public static final Asn1Tag TELETEX_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 20);
     45   public static final Asn1Tag VIDEOTEX_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 21);
     46   public static final Asn1Tag IA5_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 22);
     47   public static final Asn1Tag UTC_TIME = new Asn1Tag(Asn1TagClass.UNIVERSAL, 23);
     48   public static final Asn1Tag GENERALIZED_TIME = new Asn1Tag(Asn1TagClass.UNIVERSAL, 24);
     49   public static final Asn1Tag GRAPHIC_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 25);
     50   public static final Asn1Tag VISIBLE_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 26);
     51   public static final Asn1Tag GENERAL_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 27);
     52   public static final Asn1Tag UNIVERSAL_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 28);
     53   public static final Asn1Tag BMP_STRING = new Asn1Tag(Asn1TagClass.UNIVERSAL, 30);
     54   public static final int CONSTRUCTED_BIT = 0x20;
     55   public static final BigInteger MAX_INTEGER_VALUE = BigInteger.valueOf(Integer.MAX_VALUE);
     56 
     57   private final Asn1TagClass tagClass;
     58   private final int tagNumber;
     59 
     60   public Asn1Tag(Asn1TagClass tagClass, int tagNumber) {
     61     this.tagClass = tagClass;
     62     this.tagNumber = tagNumber;
     63   }
     64 
     65   public int getTaggedLength(int valueLength) {
     66     if (tagNumber > 30) {
     67       throw new IllegalArgumentException("Tags with value > 30 not supported");
     68     }
     69     return 1 + getLengthLength(valueLength) + valueLength;
     70   }
     71 
     72   /**
     73    * Returns the number of octets needed for the length field for a value
     74    * of the specified size. The returned number will be the smallest possible
     75    * number of octets needed for the length field.
     76    *
     77    * @param valueLength number of octets used to BER encode the value
     78    */
     79   static int getLengthLength(int valueLength) {
     80     if (valueLength < 0) {
     81       throw new IllegalArgumentException("valueLength=" + valueLength);
     82     }
     83     if (valueLength < (1 << 7)) {
     84       // short form
     85       return 1;
     86     } else if (valueLength < (1 << 8)) {
     87       return 2;
     88     } else if (valueLength < (1 << 16)) {
     89       return 3;
     90     } else if (valueLength < (1 << 24)) {
     91       return 4;
     92     } else {
     93       return 5;
     94     }
     95   }
     96 
     97   public void writeTagAndLength(ByteBuffer buf, boolean constructed, int valueLength) {
     98     this.writeTag(buf, constructed);
     99     writeLength(buf, valueLength);
    100   }
    101 
    102   private void writeTag(ByteBuffer buf, boolean constructed) {
    103     if (tagNumber > 30) {
    104       throw new IllegalArgumentException("Tags with value > 30 not supported");
    105     }
    106     if (constructed) {
    107       buf.put((byte) (getValue() | CONSTRUCTED_BIT));
    108     } else {
    109       buf.put((byte) getValue());
    110     }
    111   }
    112 
    113   static Asn1Tag readTag(ByteBuffer buf) {
    114     return Asn1Tag.fromValue(buf.get() & 0xFF);
    115   }
    116 
    117   /**
    118    * Returns the tag at the head of the buffer without advancing the position.
    119    */
    120   static Asn1Tag peekTag(ByteBuffer buf) {
    121     return Asn1Tag.fromValue(buf.get(buf.position()) & 0xFF);
    122   }
    123 
    124   /**
    125    * Returns the value of the tag octet - including class and tag ID but excluding
    126    * the 'C' (composed) bit.
    127    */
    128   int getValue() {
    129     return (tagClass.getValue() << 6) + tagNumber;
    130   }
    131 
    132   static void writeLength(ByteBuffer buf, int valueLength) {
    133     if (valueLength < 0) {
    134       throw new IllegalArgumentException("valueLength=" + valueLength);
    135     }
    136     if (valueLength < (1 << 7)) {
    137       buf.put((byte) valueLength);
    138     } else {
    139       // Long form: the low 7 bits of the first octet encodes the number of following
    140       // octets that holds the actual length. The top bit is 1 to indicate long form.
    141       byte[] lengthBytes = BigInteger.valueOf(valueLength).toByteArray();
    142       if (lengthBytes[0] == (byte) 0x00) {
    143         // the length bytes are unsigned so throw away the leading zero octet.
    144         // For example, 128 becomes { 0x00, 0x80 } and we drop the first 0x00.
    145         int len = lengthBytes.length - 1;
    146         byte[] newLengthBytes = new byte[len];
    147         System.arraycopy(lengthBytes, 1, newLengthBytes, 0, len);
    148         lengthBytes = newLengthBytes;
    149       }
    150       buf.put((byte) (lengthBytes.length | (1 << 7)));
    151       buf.put(lengthBytes);
    152     }
    153   }
    154 
    155   public static int readLength(ByteBuffer buf) {
    156     int n = buf.get() & 0xFF;
    157     if (n < (1 << 7)) {
    158       // short form of the length field - single octet with high order bit 0
    159       return n;
    160     } else {
    161       // long form - first octet contains number of subsequent octets used to encode the length
    162       n = n & 0x7F;
    163       if (n > 5) {
    164         throw new IllegalArgumentException("Length length too big: " + n + " octets");
    165       }
    166       byte[] val = new byte[n];
    167       for (int i = 0; i < n; i++) {
    168         val[i] = buf.get();
    169       }
    170       val = prependZeroByteIfHighBitSet(val);
    171       BigInteger bi = new BigInteger(val);
    172       if (bi.compareTo(MAX_INTEGER_VALUE) > 0) {
    173         throw new IllegalArgumentException("Lengths bigger than 2^^31-1 unsupported: " + bi);
    174       }
    175       return bi.intValue();
    176     }
    177   }
    178 
    179   private static byte[] prependZeroByteIfHighBitSet(byte[] ba) {
    180     if ((ba[0] & 0x80) != 0) {
    181       byte[] newba = new byte[ba.length + 1];
    182       System.arraycopy(ba, 0, newba, 1, ba.length);
    183       newba[0] = 0;
    184       ba = newba;
    185     }
    186     return ba;
    187   }
    188 
    189   /**
    190    * Returns the tag with the specified value (including tag and length, excluding "constructed"
    191    * bit).
    192    */
    193   private static Asn1Tag fromValue(int value) {
    194     Asn1Tag result = new Asn1Tag(Asn1TagClass.fromValue(value >> 6), value & 0x1F);
    195     if (result.tagNumber > 30) {
    196       throw new IllegalArgumentException("Tags with value > 30 not supported (" + result.tagNumber
    197           + ")");
    198     }
    199     return result;
    200   }
    201 
    202   /**
    203    * Returns the tag corresponding to the given class and number.
    204    *
    205    * <p>By convention, null is returned for impossible tag class < 0. Used in code generation.
    206    */
    207   @Nullable public static Asn1Tag fromClassAndNumber(int tagClass, int tagNumber) {
    208     if (tagClass < 0) {
    209       return null;
    210     }
    211     return new Asn1Tag(Asn1TagClass.fromValue(tagClass), tagNumber);
    212   }
    213 
    214   @Override
    215   public boolean equals(Object o) {
    216     if (!(o instanceof Asn1Tag)) {
    217       return false;
    218     }
    219     Asn1Tag tag = (Asn1Tag) o;
    220     return tagClass == tag.tagClass && tagNumber == tag.tagNumber;
    221   }
    222 
    223   @Override
    224   public int hashCode() {
    225     int result = 17;
    226     result = 31 * result + tagClass.hashCode();
    227     result = 31 * result + tagNumber;
    228     return result;
    229   }
    230 
    231   @Override
    232   public String toString() {
    233     return "Asn1Tag[" + tagClass + ", " + tagNumber + "]";
    234   }
    235 }
    236