1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18* @author Vladimir N. Molotkov, Stepan M. Mishura
19* @version $Revision$
20*/
21
22package org.apache.harmony.security.asn1;
23
24import java.io.IOException;
25import java.math.BigInteger;
26import java.util.Arrays;
27import java.util.Iterator;
28import java.util.Map;
29import java.util.TreeMap;
30
31import org.apache.harmony.security.internal.nls.Messages;
32
33
34/**
35 * This abstract class represents ASN.1 Choice type.
36 *
37 * To implement custom ASN.1 choice type an application class
38 * must provide implementation for the following methods:
39 *     getIndex()
40 *     getObjectToEncode()
41 *
42 * There are two ways to implement custom ASN.1 choice type:
43 * with application class that represents ASN.1 custom choice type or without.
44 * The key point is how a value of choice type is stored by application classes.
45 *
46 * For example, let's consider the following ASN.1 notations
47 * (see http://www.ietf.org/rfc/rfc3280.txt)
48 *
49 * Time ::= CHOICE {
50 *       utcTime        UTCTime,
51 *       generalTime    GeneralizedTime
52 * }
53 *
54 * Validity ::= SEQUENCE {
55 *       notBefore      Time,
56 *       notAfter       Time
57 *  }
58 *
59 * 1)First approach:
60 * No application class to represent ASN.1 Time notation
61 *
62 * The Time notation is a choice of different time formats: UTC and Generalized.
63 * Both of them are mapped to java.util.Date object, so an application
64 * class that represents ASN.1 Validity notation may keep values
65 * as Date objects.
66 *
67 * So a custom ASN.1 Time choice type should map its notation to Date object.
68 *
69 * class Time {
70 *
71 *     // custom ASN.1 choice class: maps Time to is notation
72 *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
73 *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
74 *
75 *         public int getIndex(java.lang.Object object) {
76 *             return 0; // always encode as ASN1GeneralizedTime
77 *         }
78 *
79 *         public Object getObjectToEncode(Object object) {
80 *
81 *             // A value to be encoded value is a Date object
82 *             // pass it to custom time class
83 *             return object;
84 *         }
85 *     };
86 * }
87 *
88 * class Validity {
89 *
90 *     private Date notBefore;    // choice as Date
91 *     private Date notAfter;     // choice as Date
92 *
93 *     ... // constructors and other methods go here
94 *
95 *     // custom ASN.1 sequence class: maps Validity class to is notation
96 *     public static final ASN1Sequence ASN1
97 *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
98 *
99 *         protected Object getObject(Object[] values) {
100 *
101 *             // ASN.1 Time choice passed Data object - use it
102 *             return new Validity((Date) values[0], (Date) values[1]);
103 *         }
104 *
105 *         protected void getValues(Object object, Object[] values) {
106 *
107 *             Validity validity = (Validity) object;
108 *
109 *             // pass Date objects to ASN.1 Time choice
110 *             values[0] = validity.notBefore;
111 *             values[1] = validity.notAfter;
112 *         }
113 *     }
114 * }
115 *
116 * 2)Second approach:
117 * There is an application class to represent ASN.1 Time notation
118 *
119 * If it is a matter what time format should be used to decode/encode
120 * Date objects a class to represent ASN.1 Time notation must be created.
121 *
122 * For example,
123 *
124 * class Time {
125 *
126 *     private Date utcTime;
127 *     private Date gTime;
128 *
129 *     ... // constructors and other methods go here
130 *
131 *     // custom ASN.1 choice class: maps Time to is notation
132 *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
133 *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
134 *
135 *         public Object getDecodedObject(BerInputStream in) {
136 *
137 *             // create Time object to pass as decoded value
138 *             Time time = new Time();
139 *
140 *             if (in.choiceIndex==0) {
141 *                 // we decoded GeneralizedTime
142 *                 // store decoded Date value in corresponding field
143 *                 time.gTime = in.content;
144 *                 // return it
145 *                 return time;
146 *             } else {
147 *                 // we decoded UTCTime
148 *                 // store decoded Date value in corresponding field
149 *                 time.utcTime = in.content;
150 *                 // return it
151 *                 return time;
152 *             }
153 *         }
154 *
155 *         public int getIndex(java.lang.Object object) {
156 *             Time time = (Time)object;
157 *             if(time.utcTime!=null){
158 *                 // encode Date as UTCTime
159 *                 return 1;
160 *             } else {
161 *                 // otherwise encode Date as GeneralizedTime
162 *                 return 0;
163 *             }
164 *         }
165 *
166 *         public Object getObjectToEncode(Object object) {
167 *             Time time = (Time)object;
168 *             if(time.utcTime!=null){
169 *                 // encode Date as UTCTime
170 *                 return 1;
171 *             } else {
172 *                 // otherwise encode Date as GeneralizedTime
173 *                 return 0;
174 *             }
175 *         }
176 *     };
177 * }
178 *
179 * So now Validity class must keep all values in Time object
180 * and its custom ASN.1 sequence class must handle this class of objects
181 *
182 * class Validity {
183 *
184 *     private Time notBefore;    // now it is a Time!!!
185 *     private Time notAfter;     // now it is a Time!!!
186 *
187 *     ... // constructors and other methods go here
188 *
189 *     // custom ASN.1 sequence class: maps Validity class to is notation
190 *     public static final ASN1Sequence ASN1
191 *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
192 *
193 *         protected Object getObject(Object[] values) {
194 *
195 *             // We've gotten Time objects here !!!
196 *             return new Validity((Time) values[0], (Time) values[1]);
197 *         }
198 *
199 *         protected void getValues(Object object, Object[] values) {
200 *
201 *             Validity validity = (Validity) object;
202 *
203 *             // pass Time objects to ASN.1 Time choice
204 *             values[0] = validity.notBefore;
205 *             values[1] = validity.notAfter;
206 *         }
207 *     }
208 * }
209 *
210 * @see <a href="http://asn1.elibel.tm.fr/en/standards/index.htm">ASN.1</a>
211 */
212
213public abstract class ASN1Choice extends ASN1Type {
214
215    public final ASN1Type[] type;
216
217    // identifiers table: [2][number of distinct identifiers]
218    // identifiers[0]: stores identifiers (includes nested choices)
219    // identifiers[1]: stores identifiers' indexes in array of types
220    private final int[][] identifiers;
221
222    /**
223     * Constructs ASN.1 choice type.
224     *
225     * @param type -
226     *            an array of one or more ASN.1 type alternatives.
227     * @throws IllegalArgumentException -
228     *             type parameter is invalid
229     */
230    public ASN1Choice(ASN1Type[] type) {
231        super(TAG_CHOICE); // has not tag number
232
233        if (type.length == 0) {
234            throw new IllegalArgumentException(Messages.getString("security.10E", //$NON-NLS-1$
235                    getClass().getName()));
236        }
237
238        // create map of all identifiers
239        TreeMap map = new TreeMap();
240        for (int index = 0; index < type.length; index++) {
241
242            ASN1Type t = type[index];
243
244            if (t instanceof ASN1Any) {
245                // ASN.1 ANY is not allowed,
246                // even it is a single component (not good for nested choices)
247                throw new IllegalArgumentException(Messages.getString("security.10F", //$NON-NLS-1$
248                        getClass().getName())); // FIXME name
249            } else if (t instanceof ASN1Choice) {
250
251                // add all choice's identifiers
252                int[][] choiceToAdd = ((ASN1Choice) t).identifiers;
253                for (int j = 0; j < choiceToAdd[0].length; j++) {
254                    addIdentifier(map, choiceToAdd[0][j], index);
255                }
256                continue;
257            }
258
259            // add primitive identifier
260            if (t.checkTag(t.id)) {
261                addIdentifier(map, t.id, index);
262            }
263
264            // add constructed identifier
265            if (t.checkTag(t.constrId)) {
266                addIdentifier(map, t.constrId, index);
267            }
268        }
269
270        // fill identifiers array
271        int size = map.size();
272        identifiers = new int[2][size];
273        Iterator it = map.entrySet().iterator();
274
275        for (int i = 0; i < size; i++) {
276        	Map.Entry entry = (Map.Entry) it.next();
277            BigInteger identifier = (BigInteger) entry.getKey();
278
279            identifiers[0][i] = identifier.intValue();
280            identifiers[1][i] = ((BigInteger) entry.getValue()).intValue();
281        }
282
283        this.type = type;
284    }
285
286    private void addIdentifier(TreeMap map, int identifier, int index){
287        if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) {
288            throw new IllegalArgumentException(Messages.getString("security.10F", //$NON-NLS-1$
289                    getClass().getName())); // FIXME name
290        }
291    }
292
293    //
294    //
295    // DECODE
296    //
297    //
298
299    /**
300     * Tests whether one of choice alternatives has the same identifier or not.
301     *
302     * @param identifier -
303     *            ASN.1 identifier to be verified
304     * @return - true if one of choice alternatives has the same identifier,
305     *         otherwise false;
306     */
307    public final boolean checkTag(int identifier) {
308        return Arrays.binarySearch(identifiers[0], identifier) >= 0;
309    }
310
311    public Object decode(BerInputStream in) throws IOException {
312
313        int index = Arrays.binarySearch(identifiers[0], in.tag);
314        if (index < 0) {
315            throw new ASN1Exception(Messages.getString("security.110", //$NON-NLS-1$
316                    getClass().getName()));// FIXME message
317        }
318
319        index = identifiers[1][index];
320
321        in.content = type[index].decode(in);
322
323        // set index for getDecodedObject method
324        in.choiceIndex = index;
325
326        if (in.isVerify) {
327            return null;
328        }
329        return getDecodedObject(in);
330    }
331
332    //
333    //
334    // ENCODE
335    //
336    //
337
338    public void encodeASN(BerOutputStream out) {
339        encodeContent(out);
340    }
341
342    public final void encodeContent(BerOutputStream out) {
343        out.encodeChoice(this);
344    }
345
346    /**
347     * TODO Put method description here
348     *
349     * @param object - an object to be encoded
350     * @return
351     */
352    public abstract int getIndex(Object object);
353
354    public abstract Object getObjectToEncode(Object object);
355
356    public final void setEncodingContent(BerOutputStream out) {
357        out.getChoiceLength(this);
358    }
359}
360