/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Vladimir N. Molotkov, Stepan M. Mishura
*/
package org.apache.harmony.security.asn1;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.apache.harmony.security.internal.nls.Messages;
/**
* This abstract class represents ASN.1 Choice type.
*
* To implement custom ASN.1 choice type an application class must provide
* implementation for the following methods: getIndex() getObjectToEncode()
*
* There are two ways to implement custom ASN.1 choice type: with application
* class that represents ASN.1 custom choice type or without. The key point is
* how a value of choice type is stored by application classes.
*
* For example, let's consider the following ASN.1 notations (see
* http://www.ietf.org/rfc/rfc3280.txt)
*
* Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime }
*
* Validity ::= SEQUENCE { notBefore Time, notAfter Time }
*
* 1)First approach: No application class to represent ASN.1 Time notation
*
* The Time notation is a choice of different time formats: UTC and Generalized.
* Both of them are mapped to java.util.Date object, so an application class
* that represents ASN.1 Validity notation may keep values as Date objects.
*
* So a custom ASN.1 Time choice type should map its notation to Date object.
*
* class Time {
*
* // custom ASN.1 choice class: maps Time to is notation public static final
* ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] { ASN1GeneralizedTime.asn1,
* ASN1UTCTime.asn1 }) {
*
* public int getIndex(java.lang.Object object) { return 0; // always encode as
* ASN1GeneralizedTime }
*
* public Object getObjectToEncode(Object object) {
*
* // A value to be encoded value is a Date object // pass it to custom time
* class return object; } }; }
*
* class Validity {
*
* private Date notBefore; // choice as Date private Date notAfter; // choice as
* Date
*
* ... // constructors and other methods go here
*
* // custom ASN.1 sequence class: maps Validity class to is notation public
* static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {Time.asn1,
* Time.asn1 }) {
*
* protected Object getObject(Object[] values) {
*
* // ASN.1 Time choice passed Data object - use it return new Validity((Date)
* values[0], (Date) values[1]); }
*
* protected void getValues(Object object, Object[] values) {
*
* Validity validity = (Validity) object;
*
* // pass Date objects to ASN.1 Time choice values[0] = validity.notBefore;
* values[1] = validity.notAfter; } } }
*
* 2)Second approach: There is an application class to represent ASN.1 Time
* notation
*
* If it is a matter what time format should be used to decode/encode Date
* objects a class to represent ASN.1 Time notation must be created.
*
* For example,
*
* class Time {
*
* private Date utcTime; private Date gTime;
*
* ... // constructors and other methods go here
*
* // custom ASN.1 choice class: maps Time to is notation public static final
* ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] { ASN1GeneralizedTime.asn1,
* ASN1UTCTime.asn1 }) {
*
* public Object getDecodedObject(BerInputStream in) {
*
* // create Time object to pass as decoded value Time time = new Time();
*
* if (in.choiceIndex==0) { // we decoded GeneralizedTime // store decoded Date
* value in corresponding field time.gTime = in.content; // return it return
* time; } else { // we decoded UTCTime // store decoded Date value in
* corresponding field time.utcTime = in.content; // return it return time; } }
*
* public int getIndex(java.lang.Object object) { Time time = (Time)object;
* if(time.utcTime!=null){ // encode Date as UTCTime return 1; } else { //
* otherwise encode Date as GeneralizedTime return 0; } }
*
* public Object getObjectToEncode(Object object) { Time time = (Time)object;
* if(time.utcTime!=null){ // encode Date as UTCTime return 1; } else { //
* otherwise encode Date as GeneralizedTime return 0; } } }; }
*
* So now Validity class must keep all values in Time object and its custom
* ASN.1 sequence class must handle this class of objects
*
* class Validity {
*
* private Time notBefore; // now it is a Time!!! private Time notAfter; // now
* it is a Time!!!
*
* ... // constructors and other methods go here
*
* // custom ASN.1 sequence class: maps Validity class to is notation public
* static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] {Time.asn1,
* Time.asn1 }) {
*
* protected Object getObject(Object[] values) {
*
* // We've gotten Time objects here !!! return new Validity((Time) values[0],
* (Time) values[1]); }
*
* protected void getValues(Object object, Object[] values) {
*
* Validity validity = (Validity) object;
*
* // pass Time objects to ASN.1 Time choice values[0] = validity.notBefore;
* values[1] = validity.notAfter; } } }
*
* @see http://asn1.elibel.tm.fr/en/standards/index.htm
*/
public abstract class ASN1Choice extends ASN1Type {
public final ASN1Type[] type;
// identifiers table: [2][number of distinct identifiers]
// identifiers[0]: stores identifiers (includes nested choices)
// identifiers[1]: stores identifiers' indexes in array of types
private final int[][] identifiers;
/**
* Constructs ASN.1 choice type.
*
* @param type
* - an array of one or more ASN.1 type alternatives.
* @throws IllegalArgumentException
* - type parameter is invalid
*/
public ASN1Choice(ASN1Type[] type) {
super(TAG_CHOICE); // has not tag number
if (type.length == 0) {
throw new IllegalArgumentException(Messages.getString(
"security.10E", //$NON-NLS-1$
getClass().getName()));
}
// create map of all identifiers
final TreeMap map = new TreeMap();
for (int index = 0; index < type.length; index++) {
final ASN1Type t = type[index];
if (t instanceof ASN1Any) {
// ASN.1 ANY is not allowed,
// even it is a single component (not good for nested choices)
throw new IllegalArgumentException(Messages.getString(
"security.10F", //$NON-NLS-1$
getClass().getName())); // FIXME name
} else if (t instanceof ASN1Choice) {
// add all choice's identifiers
final int[][] choiceToAdd = ((ASN1Choice) t).identifiers;
for (int j = 0; j < choiceToAdd[0].length; j++) {
addIdentifier(map, choiceToAdd[0][j], index);
}
continue;
}
// add primitive identifier
if (t.checkTag(t.id)) {
addIdentifier(map, t.id, index);
}
// add constructed identifier
if (t.checkTag(t.constrId)) {
addIdentifier(map, t.constrId, index);
}
}
// fill identifiers array
final int size = map.size();
identifiers = new int[2][size];
final Iterator it = map.entrySet().iterator();
for (int i = 0; i < size; i++) {
final Map.Entry entry = (Map.Entry) it.next();
final BigInteger identifier = (BigInteger) entry.getKey();
identifiers[0][i] = identifier.intValue();
identifiers[1][i] = ((BigInteger) entry.getValue()).intValue();
}
this.type = type;
}
private void addIdentifier(TreeMap map, int identifier, int index) {
if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) {
throw new IllegalArgumentException(Messages.getString(
"security.10F", //$NON-NLS-1$
getClass().getName())); // FIXME name
}
}
//
//
// DECODE
//
//
/**
* Tests whether one of choice alternatives has the same identifier or not.
*
* @param identifier
* - ASN.1 identifier to be verified
* @return - true if one of choice alternatives has the same identifier,
* otherwise false;
*/
@Override
public final boolean checkTag(int identifier) {
return Arrays.binarySearch(identifiers[0], identifier) >= 0;
}
@Override
public Object decode(BerInputStream in) throws IOException {
int index = Arrays.binarySearch(identifiers[0], in.tag);
if (index < 0) {
throw new ASN1Exception(Messages.getString("security.110", //$NON-NLS-1$
getClass().getName()));// FIXME message
}
index = identifiers[1][index];
in.content = type[index].decode(in);
// set index for getDecodedObject method
in.choiceIndex = index;
if (in.isVerify) {
return null;
}
return getDecodedObject(in);
}
//
//
// ENCODE
//
//
@Override
public void encodeASN(BerOutputStream out) {
encodeContent(out);
}
@Override
public final void encodeContent(BerOutputStream out) {
out.encodeChoice(this);
}
/**
* TODO Put method description here
*
* @param object
* - an object to be encoded
* @return
*/
public abstract int getIndex(Object object);
public abstract Object getObjectToEncode(Object object);
@Override
public final void setEncodingContent(BerOutputStream out) {
out.getChoiceLength(this);
}
}