blob: d8f7e93afbd0e9a97f38e5361cffc3a0decabd36 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed 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.
*/
package android.location.cts.asn1.base;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer;
import javax.annotation.Nullable;
/**
* Implements ASN.1 functionality.
*
*/
public abstract class Asn1Choice extends Asn1Object {
/**
* Dummy non-tag used as default tag for CHOICE, which reflects the BER encoding idea
* that CHOICE is not an object in itself, it just gets coded as its value. This means
* that it doesn't matter whether the choice object tag is designated as IMPLICIT or
* EXPLICIT, it gets coded the same way anyway.
*/
private static final Asn1Tag DUMMY_DEFAULT_TAG = new Asn1Tag(null, Integer.MIN_VALUE) {
@Override
public int getTaggedLength(int valueLength) {
return valueLength;
}
@Override
public void writeTagAndLength(ByteBuffer buf, boolean constructed, int valueLength) {
// Do nothing.
}
@Override
int getValue() {
throw new UnsupportedOperationException("This is not a real tag.");
}
};
/**
* Returns true if the type has an extension marker.
*/
protected abstract boolean isExtensible();
/**
* Returns true if the value set is an extension value.
*/
protected abstract boolean hasExtensionValue();
/**
* Returns the ordinal of the chosen value (extension values re-start at 0),
* or null if no value is set.
*/
@Nullable protected abstract Integer getSelectionOrdinal();
@Nullable protected abstract ChoiceComponent getSelectedComponent();
/**
* Returns the chosen value.
*/
protected abstract Asn1Object getValue();
/**
* Returns the number of choices available for the current value of
* {@code hasExtensionValue()}. If no value has been set, the number of
* choices for a non-extension value is returned.
*/
protected abstract int getOptionCount();
/**
* Creates a new instance of the type specified by the parameters, sets
* the current value of this instance to it and returns the newly created
* value.
*/
protected abstract Asn1Object createAndSetValue(boolean isExtensionValue,
int ordinal);
/**
* Given an Asn1Tag, creates and sets the value of this enumeration to a newly created object.
* Used in BER decoding.
*
* @param tag the tag read from the input stream (less the "constructed" bit)
* @return the newly created enumeration component
* @throws IllegalArgumentException if the tag is not recognized. This currently includes
* extension values.
*/
protected abstract ChoiceComponent createAndSetValue(Asn1Tag tag);
@Override Asn1Tag getDefaultTag() {
return DUMMY_DEFAULT_TAG;
}
@Override boolean isConstructed() {
return true;
}
@Override
public int getBerValueLength() {
if (hasExtensionValue()) {
throw new UnsupportedOperationException("BER for extension values is unsupported");
}
ChoiceComponent select = getSelectedComponent();
if (select == null) {
throw new NullPointerException("No component set");
}
if (select.getTag() != null) {
int valueLen;
if (select.isImplicitTagging()) {
valueLen = getValue().getBerValueLength();
} else {
valueLen = getValue().getBerLength();
}
return select.getTag().getTaggedLength(valueLen);
}
return getValue().getBerLength();
}
@Override
public void encodeBerValue(ByteBuffer buf) {
if (hasExtensionValue()) {
throw new UnsupportedOperationException("BER for extension values is unsupported");
}
ChoiceComponent select = getSelectedComponent();
if (select == null) {
throw new NullPointerException("No component set");
}
Asn1Object value = getValue();
if (select.getTag() != null) {
if (select.isImplicitTagging()) {
select.getTag().writeTagAndLength(buf, value.isConstructed(), value.getBerValueLength());
value.encodeBerValue(buf);
} else {
select.getTag().writeTagAndLength(buf, true, value.getBerLength());
value.encodeBer(buf);
}
} else {
value.encodeBer(buf);
}
}
@Override
public void decodeBerValue(ByteBuffer buf) {
Asn1Tag tag = Asn1Tag.peekTag(buf);
ChoiceComponent select = createAndSetValue(tag);
if (select == null) {
throw new IllegalArgumentException("Unknown selection tag: " + tag);
}
Asn1Object element = getValue();
if (select.getTag() != null) {
Asn1Tag.readTag(buf); // Read the tag off.
int valueLen = Asn1Tag.readLength(buf);
if (valueLen != buf.remaining()) {
throw new IllegalArgumentException("Length mismatch: expected=" + buf.remaining()
+ ", actual=" + valueLen);
}
if (select.isImplicitTagging()) {
element.decodeBerValue(buf);
return;
}
}
element.decodeBer(buf);
}
@Override
public void decodeBer(ByteBuffer buf) {
if (getTag() != null) {
// This CHOICE is tagged
Asn1Tag tag = Asn1Tag.readTag(buf);
int len = Asn1Tag.readLength(buf);
checkTag(tag, getTag());
}
decodeBerValue(buf);
}
private Iterable<BitStream> encodePerImpl(boolean aligned) {
ImmutableList.Builder<BitStream> listBuilder = ImmutableList.builder();
if (isExtensible()) {
BitStream extensionMarker = new BitStream();
extensionMarker.appendBit(hasExtensionValue());
listBuilder.add(extensionMarker);
}
int optionCount = getOptionCount();
Integer selectionOrdinal = getSelectionOrdinal();
Preconditions.checkState(optionCount == 0 || selectionOrdinal != null,
"No value set.");
if (hasExtensionValue()) {
if (aligned) {
listBuilder.addAll(
PerAlignedUtils.encodeNormallySmallWholeNumber(selectionOrdinal));
} else {
listBuilder.addAll(
PerUnalignedUtils.encodeNormallySmallWholeNumber(
selectionOrdinal));
}
} else if (optionCount > 1) {
if (aligned) {
listBuilder.add(
PerAlignedUtils.encodeSmallConstrainedWholeNumber(
selectionOrdinal, 0, optionCount - 1));
} else {
listBuilder.add(
PerUnalignedUtils.encodeConstrainedWholeNumber(
selectionOrdinal, 0, optionCount - 1));
}
}
if (optionCount > 0) {
Asn1Object value = getValue();
if (hasExtensionValue()) {
if (aligned) {
listBuilder.addAll(PerAlignedUtils.encodeOpenTypeField(value));
} else {
listBuilder.addAll(
PerUnalignedUtils.encodeOpenTypeField(value));
}
} else {
if (aligned) {
listBuilder.addAll(value.encodePerAligned());
} else {
listBuilder.addAll(value.encodePerUnaligned());
}
}
}
return listBuilder.build();
}
@Override public Iterable<BitStream> encodePerUnaligned() {
return encodePerImpl(false);
}
@Override public Iterable<BitStream> encodePerAligned() {
return encodePerImpl(true);
}
private void decodePerImpl(BitStreamReader reader, boolean aligned) {
boolean extensionValued = false;
Integer selectionOrdinal = 0;
if (isExtensible()) {
extensionValued = reader.readBit();
}
if (extensionValued) {
if (aligned) {
selectionOrdinal = PerAlignedUtils.decodeNormallySmallWholeNumber(reader);
} else {
selectionOrdinal = PerUnalignedUtils.decodeNormallySmallWholeNumber(reader);
}
} else if (getOptionCount() > 1) {
if (aligned) {
selectionOrdinal = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
reader, 0, getOptionCount() - 1);
} else {
selectionOrdinal = PerUnalignedUtils.decodeConstrainedWholeNumber(
reader, 0, getOptionCount() - 1);
}
}
if (extensionValued) {
Asn1Object element = createAndSetValue(extensionValued, selectionOrdinal);
if (aligned) {
PerAlignedUtils.decodeOpenTypeField(reader, element);
} else {
PerUnalignedUtils.decodeOpenTypeField(reader, element);
}
} else if (getOptionCount() > 0) {
Asn1Object element = createAndSetValue(extensionValued, selectionOrdinal);
if (aligned) {
element.decodePerAligned(reader);
} else {
element.decodePerUnaligned(reader);
}
}
}
@Override public void decodePerUnaligned(BitStreamReader reader) {
decodePerImpl(reader, false);
}
@Override public void decodePerAligned(BitStreamReader reader) {
decodePerImpl(reader, true);
}
}