www.pudn.com > cryptix-asn1-0.1.11.zip > XerDecoder.java
/* $Id: XerDecoder.java,v 1.1.1.1 2001/02/24 04:59:00 raif Exp $ * * Copyright (C) 1997-2001 The Cryptix Foundation Limited. All rights reserved. * * Use, modification, copying and distribution of this software is subject to * the terms and conditions of the Cryptix General Licence. You should have * received a copy of the Cryptix General Licence along with this library; if * not, you can download a copy from http://www.cryptix.org/ */ package cryptix.asn1.encoding; import cryptix.asn1.io.*; import cryptix.asn1.lang.*; import org.apache.log4j.Category; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.InputStream; import java.io.IOException; import java.math.BigInteger; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.TimeZone; /** * A class to decode ASN.1 specifications according to the XML Encoding Rules.* * @version $Revision: 1.1.1.1 $ * @author Raif S. Naffah */ public class XerDecoder extends ASNReader { // Constants and vars // ........................................................................ static Category cat = Category.getInstance(DerDecoder.class.getName()); /** * The underlying input stream that supports mark() and reset(). */ BufferedInputStream in; // Constructor(s) // ........................................................................ /** * Trivial constructor for use by the Factory. */ public XerDecoder() { super(); } /** * Private constructor for internal use. * * @param ba a byte array to become the underlying stream of the decoder. */ private XerDecoder(byte[] ba) { this.in = new BufferedInputStream(new ByteArrayInputStream(ba), 10240); } // Class method(s) // ........................................................................ /** * A class method to compare similarity between 2 Tag instances. The * comparison works on the Tag's class and value fields. * * @param tClass the tag's actual (read) class field. * @param tValue the tag's actual (read) value field. * @param xClass the expected tag's class field. * @param xValue the expected tag's value field. */ private static final boolean eval(int tClass, int xClass, int tValue, int xValue) { cat.info("Comparing ["+tClass+", "+tValue+"] to ["+xClass+", "+xValue+"]"); if (tClass != xClass) return (false); if (tClass == Tag.APPLICATION || tClass == Tag.PRIVATE) return (tValue == xValue); if (tValue == xValue) return (true); if (xValue > 0x20) // compare unstructured values xValue -= 0x20; // equate PrintableString, IA5String and T61_STRING if (xValue == Tag.PrintableString || xValue == Tag.IA5_STRING || xValue == Tag.T61_STRING) return (tValue == Tag.PrintableString || tValue == Tag.IA5_STRING || tValue == Tag.T61_STRING); // equate SEQUENCE, SEQUENCE OF, SET and SET OF if (xValue == Tag.SEQUENCE || xValue == Tag.SEQUENCE_OF || xValue == Tag.SET || xValue == Tag.SET_OF) return (tValue == Tag.SEQUENCE || tValue == Tag.SEQUENCE_OF || tValue == Tag.SET || tValue == Tag.SET_OF); return (false); } private static final Date toDate(byte[] buffer) throws DerFormatException { Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); int YY = (buffer[0]-'0')*10 + (buffer[1]-'0'); int MM = (buffer[2]-'0')*10 + (buffer[3]-'0') - 1; int DD = (buffer[4]-'0')*10 + (buffer[5]-'0'); int hh = (buffer[6]-'0')*10 + (buffer[7]-'0'); int mm = (buffer[8]-'0')*10 + (buffer[9]-'0'); int ss = 0; YY += YY <= 50 ? 2000 : 1900; // fails for 2051 and later if (buffer[10] != 'Z') { ss = (buffer[10]-'0')*10 + (buffer[11]-'0'); if (buffer[12] != 'Z') throw new DerFormatException(Tag.UTC_TIME); } cal.set(YY, MM, DD, hh, mm, ss); return (cal.getTime()); } private static final String toOID(byte[] buffer) { StringBuffer sb = new StringBuffer(); int length = buffer.length; int i = 0; if (--length >= 0) { // first byte is special int b = buffer[i++] & 0xFF; int first = (b < 40 ? 0 : (b < 80 ? 1 : 2)); int second = (b - first * 40); sb.append(first).append(".").append(second); } while (length > 0) { // handle the rest sb.append("."); int sid = 0; // subid int b; do { b = buffer[i++] & 0xFF; sid = sid << 7 | (b & 0x7F); } while (--length > 0 && (b & 0x80) == 0x80); sb.append(sid); } String result = sb.toString(); return (result); } private static final Boolean toBoolean(byte[] buffer) throws DerInvalidLengthException { int length = buffer.length; if (length != 1) throw new DerInvalidLengthException(Tag.BOOLEAN, length, 1); Boolean result = new Boolean(buffer[0] != 0x00); return (result); } private static final void toNull(byte[] buffer) throws DerInvalidLengthException { int length = buffer.length; if (length != 0) throw new DerInvalidLengthException(Tag.NULL, length, 0); } // ASN1InputStream abstract methods implementation // ........................................................................ /** * Initialises this instance to decode from the designated input stream. * * @param is the designated input stream to decode. * @exception IllegalStateException if this instance is already initialised * with an input stream. Caller should close the previous stream before * invoking this method again on a new input stream. */ public void open(InputStream is) { if (in != null) throw new IllegalStateException(); this.in = is instanceof BufferedInputStream ? (BufferedInputStream) is : new BufferedInputStream(is, 10240); } /** * Decodes an ANY from the input stream. * * @param obj the element to decode. * @return the concrete object decoded from the underlying input stream. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public IType decodeAny(IType obj) throws IOException { cat.debug("==> decodeAny()"); String name = obj.getName(); Tag tag = null; try { tag = readTag(); } catch (EOFException x) { throw new ASNElementNotFoundException("???"); } int length = readLength(); byte[] buffer = new byte[length]; int actualLength = read(buffer); if (actualLength == -1) throw new EOFException(); if (actualLength != length) throw new DerLengthMismatchException(actualLength, length); IType result = null; if (tag.getClazz() == Tag.UNIVERSAL) switch (tag.getValue()) { case Tag.BOOLEAN: result = new ASNBoolean(name, tag, toBoolean(buffer)); break; case Tag.INTEGER: result = new ASNInteger(name, tag, new BigInteger(1, buffer)); break; case Tag.BIT_STRING: result = new ASNBitString(name, tag, buffer); break; case Tag.OCTET_STRING: result = new ASNOctetString(name, tag, buffer); break; case Tag.NULL: toNull(buffer); // throws an exception if malformed result = new ASNNull(name, tag, new Object()); break; case Tag.OBJECT_IDENTIFIER: result = new ASNObjectIdentifier(name, tag, toOID(buffer)); break; case Tag.PrintableString: result = new ASNPrintableString(name, tag, new String(buffer, "US-ASCII")); break; case Tag.UTC_TIME: result = new ASNUTCTime(name, tag, toDate(buffer)); break; } else result = new ASNAny(name, tag, buffer); cat.debug("<== decodeAny() --> "+result); return result; } /** * Decodes an OBJECT IDENTIFIER from the input stream. * * @param obj the element to decode. * @return a String representation of the OID decoded from the stream. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public String decodeObjectIdentifier(IType obj) throws IOException { cat.debug("==> decodeObjectIdentifier()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.OBJECT_IDENTIFIER); String result = toOID(buffer); cat.debug("<== decodeObjectIdentifier() --> "+result); return result; } /** * Decodes a NULL from the input stream. * * @param obj the element to decode. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public void decodeNull(IType obj) throws IOException { cat.debug("==> decodeNull()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.NULL); toNull(buffer); cat.debug("<== decodeNull()"); } /** * Decodes a BOOLEAN from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Boolean decodeBoolean(IType obj) throws IOException { cat.debug("==> decodeBoolean("+tag+")"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.BOOLEAN); Boolean result = toBoolean(buffer); cat.debug("<== decodeBoolean() --> "+result); return result; } /** * Decodes an INTEGER from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public BigInteger decodeInteger(IType obj) throws IOException { cat.debug("==> decodeInteger()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.INTEGER); BigInteger result = new BigInteger(1, buffer); cat.debug("<== decodeInteger() --> "+result); return result; } /** * Decodes a PrintableString from the input stream. * * @param tagValue the value of a Tag constant to differentiate between * the different types of strings. * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public String decodeString(int tagValue, IType obj) throws IOException { cat.debug("==> decodeString()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, tagValue); String result = new String(buffer, "UTF8"); cat.debug("<== decodeString() --> \""+result+"\""); return result; } /** * Decodes a BIT STRING from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public byte[] decodeBitString(IType obj) throws IOException { cat.debug("==> decodeBitString()"); Tag tag = obj.getTag(); byte[] tmp = readRaw(tag, Tag.BIT_STRING); cat.warn("Truncating "+String.valueOf(tmp[0])+" unused leftmost bit(s) from a BIT STRING"); byte[] result = new byte[tmp.length-1]; System.arraycopy(tmp, 1, result, 0, result.length); cat.debug("<== decodeBitString() --> 0x"+(new BigInteger(1, result).toString(16))); return result; } /** * Decodes an OCTET STRING from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public byte[] decodeOctetString(IType obj) throws IOException { cat.debug("==> decodeOctetString()"); Tag tag = obj.getTag(); byte[] result = readRaw(tag, Tag.OCTET_STRING); cat.debug("<== decodeOctetString() --> 0x"+(new BigInteger(1, result).toString(16))); return result; } /** * Decodes a UTCTime from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Date decodeUTCTime(IType obj) throws IOException { cat.debug("==> decodeUTCTime()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.UTC_TIME); Date result = toDate(buffer); cat.debug("<== decodeUTCTime() --> "+result); return result; } /** * Decodes a GeneralizedTime from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Date decodeGeneralizedTime(IType obj) throws IOException { cat.debug("==> decodeGeneralizedTime()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.GENERALIZED_TIME); Date result = toDate(buffer); cat.debug("<== decodeGeneralizedTime() --> "+result); return result; } /** * Decodes a compund type (SEQUENCE/SET [OF]) from the input stream. * * @param obj the compound element to decode. * @return a Decoder that parses the input stream according to the same * encoding rules as this one. * @exception ASNTagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerLengthMismatchException if a mismatch between the expected * (parsed) and actual (read) size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public DerDecoder decodeStructure(IType obj) throws IOException { cat.debug("==> decodeStructure()"); Tag tag = obj.getTag(); byte[] buffer = readRaw(tag, Tag.SEQUENCE); DerDecoder result = new DerDecoder(buffer); cat.debug("<== decodeStructure() --> "+result); return result; } // InputStream methods implementation // ....................................................................... /** * Reads the next byte of data from the underlying input stream. The value * byte is returned as an int in the range 0 to 255. * Contrary to the normal java.io.InputStream contract, if no * byte is available because the end of the stream has been reached, this * method throws a java.io.EOFException. This method blocks until * input data is available, the end of the stream is detected, or another * exception is thrown. * * @return the next byte of data. * @exception java.io.EOFException the end-of-stream was detected. * @exception java.io.IOException if an I/O error occurs. */ public int read() throws IOException { int result = in.read(); if (result == -1) throw new EOFException(); return (result & 0xFF); } /** * Closes the underlying input stream and releases any system resources * associated with it. * * @exception java.io.IOException if an I/O error occurs. */ public void close() throws IOException { if (in != null) { try { in.close(); } catch (IOException ignored) { cat.warn("I/O exception while closing the stream: "+ignored.getMessage()); } in = null; } } /** * Marks the current position in the underlying input stream. A subsequent * call to the reset() method repositions that stream at the last * marked position so that subsequent reads re-read the same byte(s).
* * The readlimit arguments tells the underlying input stream to * allow that many bytes to be read before the mark position gets * invalidated.
* * The general contract of mark() is that, if the method * markSupported() returns true, the stream somehow * remembers all the bytes read after the call to mark() and * stands ready to supply those same bytes again if and whenever the method * reset() is called. However, the stream is not required to * remember any data at all if more than readlimit bytes are read * from the stream before reset() is called. * * @param readlimit the maximum limit of bytes that can be read before the * mark position becomes invalid. */ public void mark(int readlimit) { in.mark(readlimit); } /** * Repositions the underlying stream to the position at the time the * mark() method was last called on that input stream. * * The general contract of reset() is: *