| /* |
| * 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 |
| * @version $Revision$ |
| */ |
| |
| package org.apache.harmony.security.asn1; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| |
| |
| /** |
| * Decodes ASN.1 types encoded with BER (X.690) |
| * |
| * @see <a href="http://asn1.elibel.tm.fr/en/standards/index.htm">ASN.1</a> |
| */ |
| |
| public class BerInputStream { |
| |
| /** |
| * Associated <code>InputStream</code> |
| */ |
| protected InputStream in; |
| |
| /** |
| * Internal buffer for storing encoded array |
| */ |
| protected byte[] buffer; |
| |
| /** |
| * The position in the buffer. |
| * |
| * Next read must place data into the buffer from this offset |
| */ |
| protected int offset = 0; |
| |
| // The buffer increment size. |
| // Must be reasonable big to reallocate memory not to often. |
| // Primary is used for decoding indefinite length encoding |
| private static final int BUF_INCREASE_SIZE = 1024 * 16; |
| |
| /** |
| * Indicates indefinite length of the current type |
| */ |
| protected static final int INDEFINIT_LENGTH = -1; |
| |
| /** |
| * Creates stream for decoding. |
| * |
| * @param encoded - bytes array to be decoded |
| * @throws IOException - if an error occurs |
| */ |
| public BerInputStream(byte[] encoded) throws IOException { |
| this(encoded, 0, encoded.length); |
| } |
| |
| /** |
| * Creates stream for decoding. |
| * |
| * @param encoded - |
| * bytes array to be decoded |
| * @param offset - |
| * the encoding offset |
| * @param expectedLength - |
| * expected length of full encoding, this includes identifier, |
| * length an content octets |
| * @throws IOException - |
| * if an error occurs |
| */ |
| public BerInputStream(byte[] encoded, int offset, int expectedLength) |
| throws IOException { |
| |
| this.buffer = encoded; |
| this.offset = offset; |
| |
| next(); |
| |
| // compare expected and decoded length |
| if (length != INDEFINIT_LENGTH |
| && (offset + expectedLength) != (this.offset + this.length)) { |
| throw new ASN1Exception("Wrong content length"); |
| } |
| } |
| |
| /** |
| * Creates stream for decoding. |
| * |
| * Allocates initial buffer of default size |
| * |
| * @param is associated <code>InputStream</code> |
| */ |
| public BerInputStream(InputStream in) throws IOException { |
| this(in, BUF_INCREASE_SIZE); |
| } |
| |
| /** |
| * Creates stream for decoding. |
| * |
| * Allocates initial buffer of <code>initialSize</code> size |
| * |
| * @param initialSize the internal buffer initial size |
| * @param is associated <code>InputStream</code> |
| */ |
| public BerInputStream(InputStream in, int initialSize) throws IOException { |
| |
| this.in = in; |
| buffer = new byte[initialSize]; |
| |
| next(); |
| |
| if (length != INDEFINIT_LENGTH) { |
| // input stream has definite length encoding |
| // check allocated length to avoid further reallocations |
| if (buffer.length < (length + offset)) { |
| byte[] newBuffer = new byte[length + offset]; |
| System.arraycopy(buffer, 0, newBuffer, 0, offset); |
| buffer = newBuffer; |
| } |
| } else { |
| isIndefinedLength = true; |
| throw new ASN1Exception("Decoding indefinite length encoding is not supported"); |
| } |
| } |
| |
| /** |
| * Resets this stream to initial state. |
| * |
| * @param encoded - a new bytes array to be decoded |
| * @throws IOException - if an error occurs |
| */ |
| public final void reset(byte[] encoded) throws IOException { |
| buffer = encoded; |
| |
| next(); |
| } |
| |
| /** |
| * Current decoded tag |
| */ |
| public int tag; |
| |
| /** |
| * Current decoded length |
| */ |
| protected int length; |
| |
| /** |
| * Current decoded content |
| */ |
| public Object content; |
| |
| /** |
| * Current decoded tag offset |
| */ |
| protected int tagOffset; |
| |
| /** |
| * Current decoded content offset |
| */ |
| protected int contentOffset; |
| |
| /** |
| * Decodes next encoded type. |
| * Initializes tag, length, tagOffset and contentOffset variables |
| * |
| * @return next decoded tag |
| * @throws IOException - if error occured |
| */ |
| public int next() throws IOException { |
| |
| tagOffset = offset; |
| |
| // read tag |
| tag = read(); |
| |
| // read length |
| length = read(); |
| if (length != 0x80) { // definite form |
| // long or short length form |
| if ((length & 0x80) != 0) { // long form |
| int numOctets = length & 0x7F; |
| |
| if (numOctets > 5) { |
| throw new ASN1Exception("Too long encoding at [" + tagOffset + "]"); //FIXME message |
| } |
| |
| // collect this value length |
| length = read(); |
| for (int i = 1; i < numOctets; i++) { |
| int ch = read(); |
| length = (length << 8) + ch;//read(); |
| } |
| |
| if (length > 0xFFFFFF) { |
| throw new ASN1Exception("Too long encoding at [" + tagOffset + "]"); //FIXME message |
| } |
| } |
| } else { //indefinite form |
| length = INDEFINIT_LENGTH; |
| } |
| contentOffset = offset; |
| |
| return tag; |
| } |
| |
| /** |
| * Returns the length of the encoding |
| */ |
| public static int getLength(byte[] encoding) { |
| int length = encoding[1] & 0xFF; |
| int numOctets = 0; |
| if ((length & 0x80) != 0) { // long form |
| numOctets = length & 0x7F; |
| |
| // collect this value length |
| length = encoding[2] & 0xFF; |
| for (int i = 3; i < numOctets + 2; i++) { |
| length = (length << 8) + (encoding[i] & 0xFF); |
| } |
| } |
| // tag length long_form content |
| return 1 + 1 + numOctets + length; |
| } |
| |
| /** |
| * Decodes ASN.1 bitstring type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readBitString() throws IOException { |
| |
| if (tag == ASN1Constants.TAG_BITSTRING) { |
| |
| if (length == 0) { |
| throw new ASN1Exception("ASN.1 Bitstring: wrong length. Tag at [" + tagOffset + "]"); |
| } |
| |
| readContent(); |
| |
| // content: check unused bits |
| if (buffer[contentOffset] > 7) { |
| throw new ASN1Exception("ASN.1 Bitstring: wrong content at [" + contentOffset + "]. A number of unused bits MUST be in range 0 to 7"); |
| } |
| |
| if (length == 1 && buffer[contentOffset] != 0) { |
| throw new ASN1Exception("ASN.1 Bitstring: wrong content at [" + contentOffset + "]. For empty string unused bits MUST be 0"); |
| } |
| |
| } else if (tag == ASN1Constants.TAG_C_BITSTRING) { |
| throw new ASN1Exception("Decoding constructed ASN.1 bitstring type is not provided"); |
| } else { |
| throw expected("bitstring"); |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 Enumerated type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readEnumerated() throws IOException { |
| |
| if (tag != ASN1Constants.TAG_ENUM) { |
| throw expected("enumerated"); |
| } |
| |
| // |
| // all checks are the same as for ASN.1 integer type |
| // |
| |
| // check encoded length |
| if (length == 0) { |
| throw new ASN1Exception("ASN.1 enumerated: wrong length for identifier at [" + tagOffset + "]"); |
| } |
| |
| readContent(); |
| |
| // check encoded content |
| if (length > 1) { |
| |
| int bits = buffer[contentOffset] & 0xFF; |
| if (buffer[contentOffset + 1] < 0) { |
| bits += 0x100; |
| } |
| |
| if (bits == 0 || bits == 0x1FF) { |
| throw new ASN1Exception("ASN.1 enumerated: wrong content at [" + contentOffset + "]. An integer MUST be encoded in minimum number of octets"); |
| } |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 boolean type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readBoolean() throws IOException { |
| |
| if (tag != ASN1Constants.TAG_BOOLEAN) { |
| throw expected("boolean"); |
| } |
| |
| // check encoded length |
| if (length != 1) { |
| throw new ASN1Exception("Wrong length for ASN.1 boolean at [" + tagOffset + "]"); |
| } |
| |
| readContent(); |
| } |
| |
| /** |
| * The last choice index |
| */ |
| public int choiceIndex; |
| |
| /** |
| * Keeps last decoded: year, month, day, hour, minute, second, millisecond |
| */ |
| public int[] times; |
| |
| /** |
| * Decodes ASN.1 GeneralizedTime type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readGeneralizedTime() throws IOException { |
| |
| if (tag == ASN1Constants.TAG_GENERALIZEDTIME) { |
| |
| // FIXME: any other optimizations? |
| readContent(); |
| // FIXME store string somewhere to allow a custom time type perform |
| // additional checks |
| |
| // check syntax: the last char MUST be Z |
| if (buffer[offset - 1] != 'Z') { |
| // FIXME support only format that is acceptable for DER |
| throw new ASN1Exception("ASN.1 GeneralizedTime: encoded format is not implemented"); |
| } |
| |
| // check syntax: MUST be YYYYMMDDHHMMSS[(./,)DDD]'Z' |
| if (length != 15 && (length < 17 || length > 19)) // invalid |
| // length |
| { |
| throw new ASN1Exception("ASN.1 GeneralizedTime wrongly encoded at [" + contentOffset + "]"); |
| } |
| |
| // check content: milliseconds |
| if (length > 16) { |
| byte char14 = buffer[contentOffset + 14]; |
| if (char14 != '.' && char14 != ',') { |
| throw new ASN1Exception("ASN.1 GeneralizedTime wrongly encoded at [" + contentOffset + "]"); |
| } |
| } |
| |
| if (times == null) { |
| times = new int[7]; |
| } |
| times[0] = strToInt(contentOffset, 4); // year |
| times[1] = strToInt(contentOffset + 4, 2); // month |
| times[2] = strToInt(contentOffset + 6, 2); // day |
| times[3] = strToInt(contentOffset + 8, 2); // hour |
| times[4] = strToInt(contentOffset + 10, 2); // minute |
| times[5] = strToInt(contentOffset + 12, 2); // second |
| |
| if (length > 16) { |
| // FIXME optimize me |
| times[6] = strToInt(contentOffset + 15, length - 16); |
| |
| if (length == 17) { |
| times[6] = times[6] * 100; |
| } else if (length == 18) { |
| times[6] = times[6] * 10; |
| } |
| } |
| |
| // FIXME check all values for valid numbers!!! |
| } else if (tag == ASN1Constants.TAG_C_GENERALIZEDTIME) { |
| throw new ASN1Exception("Decoding constructed ASN.1 GeneralizedTime type is not supported"); |
| } else { |
| throw expected("GeneralizedTime"); |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 UTCTime type |
| * |
| * @throws IOException - if an I/O error occurs or the end of the stream is reached |
| */ |
| public void readUTCTime() throws IOException { |
| |
| if (tag == ASN1Constants.TAG_UTCTIME) { |
| |
| switch (length) { |
| case ASN1UTCTime.UTC_HM: |
| case ASN1UTCTime.UTC_HMS: |
| break; |
| case ASN1UTCTime.UTC_LOCAL_HM: |
| case ASN1UTCTime.UTC_LOCAL_HMS: |
| // FIXME only coordinated universal time formats are supported |
| throw new ASN1Exception("ASN.1 UTCTime: local time format is not supported"); |
| default: |
| throw new ASN1Exception("ASN.1 UTCTime: wrong length, identifier at " + tagOffset); |
| } |
| |
| // FIXME: any other optimizations? |
| readContent(); |
| |
| // FIXME store string somewhere to allow a custom time type perform |
| // additional checks |
| |
| // check syntax: the last char MUST be Z |
| if (buffer[offset - 1] != 'Z') { |
| throw new ASN1Exception("ASN.1 UTCTime wrongly encoded at [" |
| + contentOffset + ']'); |
| } |
| |
| if (times == null) { |
| times = new int[7]; |
| } |
| |
| times[0] = strToInt(contentOffset, 2); // year |
| if (times[0] > 49) { |
| times[0] += 1900; |
| } else { |
| times[0] += 2000; |
| } |
| |
| times[1] = strToInt(contentOffset + 2, 2); // month |
| times[2] = strToInt(contentOffset + 4, 2); // day |
| times[3] = strToInt(contentOffset + 6, 2); // hour |
| times[4] = strToInt(contentOffset + 8, 2); // minute |
| |
| if (length == ASN1UTCTime.UTC_HMS) { |
| times[5] = strToInt(contentOffset + 10, 2); // second |
| } |
| |
| // FIXME check all time values for valid numbers!!! |
| } else if (tag == ASN1Constants.TAG_C_UTCTIME) { |
| throw new ASN1Exception("Decoding constructed ASN.1 UTCTime type is not supported"); |
| } else { |
| throw expected("UTCTime"); |
| } |
| } |
| |
| //TODO comment me |
| private int strToInt(int off, int count) throws ASN1Exception { |
| |
| //FIXME works only with buffer |
| |
| int c; |
| int result = 0; |
| for (int i = off, end = off + count; i < end; i++) { |
| c = buffer[i] - 48; |
| if (c < 0 || c > 9) { |
| throw new ASN1Exception("Time encoding has invalid char"); |
| } |
| result = result * 10 + c; |
| } |
| return result; |
| } |
| |
| /** |
| * Decodes ASN.1 Integer type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readInteger() throws IOException { |
| |
| if (tag != ASN1Constants.TAG_INTEGER) { |
| throw expected("integer"); |
| } |
| |
| // check encoded length |
| if (length < 1) { |
| throw new ASN1Exception("Wrong length for ASN.1 integer at [" + tagOffset + "]"); |
| } |
| |
| readContent(); |
| |
| // check encoded content |
| if (length > 1) { |
| |
| byte firstByte = buffer[offset - length]; |
| byte secondByte = (byte) (buffer[offset - length + 1] & 0x80); |
| |
| if (firstByte == 0 && secondByte == 0 || firstByte == (byte) 0xFF |
| && secondByte == (byte) 0x80) { |
| throw new ASN1Exception("Wrong content for ASN.1 integer at [" + (offset - length) + "]. An integer MUST be encoded in minimum number of octets"); |
| } |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 Octetstring type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readOctetString() throws IOException { |
| |
| if (tag == ASN1Constants.TAG_OCTETSTRING) { |
| readContent(); |
| } else if (tag == ASN1Constants.TAG_C_OCTETSTRING) { |
| throw new ASN1Exception("Decoding constructed ASN.1 octet string type is not supported"); |
| } else { |
| throw expected("octetstring"); |
| } |
| } |
| |
| private ASN1Exception expected(String what) throws ASN1Exception { |
| throw new ASN1Exception("ASN.1 " + what + " identifier expected at [" + tagOffset + "], got " + Integer.toHexString(tag)); |
| } |
| |
| //FIXME comment me |
| public int oidElement; |
| |
| /** |
| * Decodes ASN.1 ObjectIdentifier type |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readOID() throws IOException { |
| |
| if (tag != ASN1Constants.TAG_OID) { |
| throw expected("OID"); |
| } |
| |
| // check encoded length |
| if (length < 1) { |
| throw new ASN1Exception("Wrong length for ASN.1 object identifier at [" + tagOffset + "]"); |
| } |
| |
| readContent(); |
| |
| // check content: last encoded byte (8th bit MUST be zero) |
| if ((buffer[offset - 1] & 0x80) != 0) { |
| throw new ASN1Exception("Wrong encoding at [" + (offset - 1) + "]"); |
| } |
| |
| oidElement = 1; |
| for (int i = 0; i < length; i++, ++oidElement) { |
| |
| // According to ASN.1 BER spec: |
| // leading octet of subidentifier MUST not be 0x80 |
| // This assertion is not verified |
| // |
| //if (buffer[contentOffset + i] == (byte)0x80) { |
| // throw new ASN1Exception( |
| // "Wrong content for ASN.1 object identifier at [" |
| // + contentOffset |
| // + "]. Subidentifier MUST be encoded in minimum number of octets"); |
| //} |
| |
| while ((buffer[contentOffset + i] & 0x80) == 0x80) { |
| i++; |
| } |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 Sequence type |
| * |
| * @param sequence - ASN.1 sequence to be decoded |
| * @throws IOException - if error occured |
| */ |
| public void readSequence(ASN1Sequence sequence) throws IOException { |
| |
| if (tag != ASN1Constants.TAG_C_SEQUENCE) { |
| throw expected("sequence"); |
| } |
| |
| int begOffset = offset; |
| int endOffset = begOffset + length; |
| |
| ASN1Type[] type = sequence.type; |
| |
| int i = 0; |
| |
| if (isVerify) { |
| |
| for (; (offset < endOffset) && (i < type.length); i++) { |
| |
| next(); |
| while (!type[i].checkTag(tag)) { |
| // check whether it is optional component or not |
| if (!sequence.OPTIONAL[i] || (i == type.length - 1)) { |
| throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]"); |
| } |
| i++; |
| } |
| |
| type[i].decode(this); |
| } |
| |
| // check the rest of components |
| for (; i < type.length; i++) { |
| if (!sequence.OPTIONAL[i]) { |
| throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]"); |
| } |
| } |
| |
| } else { |
| |
| int seqTagOffset = tagOffset; //store tag offset |
| |
| Object[] values = new Object[type.length]; |
| for (; (offset < endOffset) && (i < type.length); i++) { |
| |
| next(); |
| while (!type[i].checkTag(tag)) { |
| // check whether it is optional component or not |
| if (!sequence.OPTIONAL[i] || (i == type.length - 1)) { |
| throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]"); |
| } |
| |
| // sets default value |
| if (sequence.DEFAULT[i] != null) { |
| values[i] = sequence.DEFAULT[i]; |
| } |
| i++; |
| } |
| values[i] = type[i].decode(this); |
| } |
| |
| // check the rest of components |
| for (; i < type.length; i++) { |
| if (!sequence.OPTIONAL[i]) { |
| throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]"); |
| } |
| if (sequence.DEFAULT[i] != null) { |
| values[i] = sequence.DEFAULT[i]; |
| } |
| } |
| content = values; |
| |
| tagOffset = seqTagOffset; //retrieve tag offset |
| } |
| |
| if (offset != endOffset) { |
| throw new ASN1Exception("Wrong encoding at [" + begOffset + "]. Content's length and encoded length are not the same"); |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 SequenceOf type |
| * |
| * @param sequenceOf - ASN.1 sequence to be decoded |
| * @throws IOException - if error occured |
| */ |
| public void readSequenceOf(ASN1SequenceOf sequenceOf) throws IOException { |
| |
| if (tag != ASN1Constants.TAG_C_SEQUENCEOF) { |
| throw expected("sequenceOf"); |
| } |
| |
| decodeValueCollection(sequenceOf); |
| } |
| |
| /** |
| * Decodes ASN.1 Set type |
| * |
| * @param set - ASN.1 set to be decoded |
| * @throws IOException - if error occured |
| */ |
| public void readSet(ASN1Set set) throws IOException { |
| |
| if (tag != ASN1Constants.TAG_C_SET) { |
| throw expected("set"); |
| } |
| |
| throw new ASN1Exception("Decoding ASN.1 Set type is not supported"); |
| } |
| |
| /** |
| * Decodes ASN.1 SetOf type |
| * |
| * @param set - ASN.1 set to be decoded |
| * @throws IOException - if error occured |
| */ |
| public void readSetOf(ASN1SetOf setOf) throws IOException { |
| |
| if (tag != ASN1Constants.TAG_C_SETOF) { |
| throw expected("setOf"); |
| } |
| |
| decodeValueCollection(setOf); |
| } |
| |
| private final void decodeValueCollection(ASN1ValueCollection collection) |
| throws IOException { |
| |
| int begOffset = offset; |
| int endOffset = begOffset + length; |
| |
| ASN1Type type = collection.type; |
| |
| if (isVerify) { |
| while (endOffset > offset) { |
| next(); |
| type.decode(this); |
| } |
| } else { |
| |
| int seqTagOffset = tagOffset; //store tag offset |
| |
| ArrayList values = new ArrayList(); |
| while (endOffset > offset) { |
| next(); |
| values.add(type.decode(this)); |
| } |
| |
| content = values; |
| |
| tagOffset = seqTagOffset; //retrieve tag offset |
| } |
| |
| if (offset != endOffset) { |
| throw new ASN1Exception("Wrong encoding at [" + begOffset + "]. Content's length and encoded length are not the same"); |
| } |
| } |
| |
| /** |
| * Decodes ASN.1 String type |
| * |
| * @throws IOException - if an I/O error occurs or the end of the stream is reached |
| */ |
| public void readString(ASN1StringType type) throws IOException { |
| |
| //FIXME check string content |
| if (tag == type.id) { |
| readContent(); |
| } else if (tag == type.constrId) { |
| throw new ASN1Exception("Decoding constructed ASN.1 string type is not provided"); |
| } else { |
| throw expected("string"); |
| } |
| } |
| |
| /** |
| * Returns encoded array. |
| * |
| * MUST be invoked after decoding corresponding ASN.1 notation |
| */ |
| public byte[] getEncoded() { |
| byte[] encoded = new byte[offset - tagOffset]; |
| System.arraycopy(buffer, tagOffset, encoded, 0, encoded.length); |
| return encoded; |
| } |
| |
| /** |
| * Returns internal buffer used for decoding |
| * |
| * @return - buffer |
| */ |
| public final byte[] getBuffer() { |
| return buffer; |
| } |
| |
| /** |
| * Returns length of the current content for decoding |
| * |
| * @return - length of content |
| */ |
| public final int getLength() { |
| return length; |
| } |
| |
| /** |
| * Returns the current offset |
| * |
| * @return - offset |
| */ |
| public final int getOffset() { |
| return offset; |
| } |
| |
| /** |
| * Returns end offset for the current encoded type |
| * |
| * @return - offset |
| */ |
| public final int getEndOffset() { |
| return offset + length; |
| } |
| |
| /** |
| * Returns start offset for the current encoded type |
| * |
| * @return - offset |
| */ |
| public final int getTagOffset() { |
| return tagOffset; |
| } |
| |
| public final int getContentOffset() { |
| return contentOffset; |
| } |
| |
| /** |
| * Indicates verify or store mode. |
| * |
| * In store mode a decoded content is stored in a newly allocated |
| * appropriate object. The <code>content</code> variable holds |
| * a reference to the last created object. |
| * |
| * In verify mode a decoded content is not stored. |
| */ |
| // FIXME it is used only for one case |
| // decoding PCKS#8 Private Key Info notation |
| // remove this option because it does decoding more complex |
| protected boolean isVerify; |
| |
| /** |
| * Sets verify mode. |
| */ |
| public final void setVerify() { |
| isVerify = true; |
| } |
| |
| /** |
| * Indicates defined or indefined reading mode for associated InputStream. |
| * |
| * This mode is defined by reading a length |
| * for a first ASN.1 type from InputStream. |
| */ |
| protected boolean isIndefinedLength; |
| |
| /** |
| * Reads the next encoded byte from the encoded input stream. |
| * |
| * @return the next encoded byte |
| * @throws IOException - if error occured |
| */ |
| protected int read() throws IOException { |
| |
| if (offset == buffer.length) { |
| throw new ASN1Exception("Unexpected end of encoding"); |
| } |
| |
| if (in == null) { |
| return buffer[offset++] & 0xFF; |
| } else { |
| int octet = in.read(); |
| if (octet == -1) { |
| throw new ASN1Exception("Unexpected end of encoding"); |
| } |
| |
| buffer[offset++] = (byte) octet; |
| |
| return octet; |
| } |
| } |
| |
| /** |
| * Reads the next encoded content from the encoded input stream. |
| * The method MUST be used for reading a primitive encoded content. |
| * |
| * @throws IOException - if error occured |
| */ |
| public void readContent() throws IOException { |
| if (offset + length > buffer.length) { |
| throw new ASN1Exception("Unexpected end of encoding"); |
| } |
| |
| if (in == null) { |
| offset += length; |
| } else { |
| int bytesRead = in.read(buffer, offset, length); |
| |
| if (bytesRead != length) { |
| // if input stream didn't return all data at once |
| // try to read it in several blocks |
| int c = bytesRead; |
| do { |
| if (c < 1 || bytesRead > length) { |
| throw new ASN1Exception("Failed to read encoded content"); |
| } |
| c = in.read(buffer, offset + bytesRead, length - bytesRead); |
| bytesRead += c; |
| } while (bytesRead != length); |
| } |
| |
| offset += length; |
| } |
| } |
| |
| // // reallocates internal buffer for indefined reading mode |
| // private void reallocateBuffer(int n) { |
| // int newSize; |
| // for (newSize = buffer.length * 2; newSize < buffer.length + n; newSize = newSize * 2) |
| // ; |
| // byte[] newBuffer = new byte[newSize]; |
| // System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); |
| // buffer = newBuffer; |
| // } |
| |
| /** |
| * Reallocates the buffer in order to make it |
| * exactly the size of data it contains |
| */ |
| public void compactBuffer() { |
| if (offset != buffer.length) { |
| byte[] newBuffer = new byte[offset]; |
| // restore buffer content |
| System.arraycopy(buffer, 0, newBuffer, 0, offset); |
| // set new buffer |
| buffer = newBuffer; |
| } |
| } |
| |
| // |
| // |
| // |
| // |
| // |
| |
| private Object[][] pool; |
| |
| public void put(Object key, Object entry) { |
| |
| if (pool == null) { |
| pool = new Object[2][10]; |
| } |
| |
| int i = 0; |
| for (; i < pool[0].length && pool[0][i] != null; i++) { |
| if (pool[0][i] == key) { |
| pool[1][i] = entry; |
| return; |
| } |
| } |
| |
| if (i == pool[0].length) { |
| Object[][] newPool = new Object[pool[0].length * 2][2]; |
| System.arraycopy(pool[0], 0, newPool[0], 0, pool[0].length); |
| System.arraycopy(pool[1], 0, newPool[1], 0, pool[0].length); |
| pool = newPool; |
| } else { |
| pool[0][i] = key; |
| pool[1][i] = entry; |
| } |
| } |
| |
| public Object get(Object key) { |
| |
| if (pool == null) { |
| return null; |
| } |
| |
| for (int i = 0; i < pool[0].length; i++) { |
| if (pool[0][i] == key) { |
| return pool[1][i]; |
| } |
| } |
| return null; |
| } |
| } |