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 
Asn1Tag(Asn1TagClass tagClass, int tagNumber)60   public Asn1Tag(Asn1TagClass tagClass, int tagNumber) {
61     this.tagClass = tagClass;
62     this.tagNumber = tagNumber;
63   }
64 
getTaggedLength(int valueLength)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    */
getLengthLength(int valueLength)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 
writeTagAndLength(ByteBuffer buf, boolean constructed, int valueLength)97   public void writeTagAndLength(ByteBuffer buf, boolean constructed, int valueLength) {
98     this.writeTag(buf, constructed);
99     writeLength(buf, valueLength);
100   }
101 
writeTag(ByteBuffer buf, boolean constructed)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 
readTag(ByteBuffer buf)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    */
peekTag(ByteBuffer buf)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    */
getValue()128   int getValue() {
129     return (tagClass.getValue() << 6) + tagNumber;
130   }
131 
writeLength(ByteBuffer buf, int valueLength)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 
readLength(ByteBuffer buf)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 
prependZeroByteIfHighBitSet(byte[] ba)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    */
fromValue(int value)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    */
fromClassAndNumber(int tagClass, int tagNumber)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
equals(Object o)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
hashCode()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
toString()232   public String toString() {
233     return "Asn1Tag[" + tagClass + ", " + tagNumber + "]";
234   }
235 }
236