org.jzkit.a2j.codec.runtime.BERInputStream.java Source code

Java tutorial

Introduction

Here is the source code for org.jzkit.a2j.codec.runtime.BERInputStream.java

Source

/**
 *
 * BERInputStream : An implementation of the SerializationManager class that takes
 *                  an InputStream and can then be used as a parameter to a codec instance.
 *                  The data from the input stream will then be decoded according to the 
 *                  basic encoding rules.
 *
 * @author Ian Ibbotson ( ibbo@k-int.com )
 * @version $Id: BERInputStream.java,v 1.8 2005/06/09 17:53:07 ibbo Exp $
 * @see    org.jzkit.a2j.codec.runtime.SerializationManager
 *
 * Copyright:   Copyright (C) 2000, Knowledge Integration Ltd.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the license, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite
 * 330, Boston, MA  02111-1307, USA.
 *   
 *
 */

package org.jzkit.a2j.codec.runtime;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Stack;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.StringWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import org.jzkit.a2j.codec.util.OIDRegister;
import org.apache.commons.logging.*;

public class BERInputStream implements SerializationManager {

    private Stack encoding_info = new Stack();
    public int tag_class = -1;
    public int tag_value = -1;
    public int next_tag_class = -1;
    public int next_tag_number = -1;
    public boolean next_is_constructed = false;
    public boolean next_is_indefinite = false;
    public int next_length = -1;
    public boolean is_constructed = false;
    private BufferedInputStream in = null;
    private base_codec codec_hint = null;
    public String encoding = "US-ASCII";
    private static Log log = LogFactory.getLog(BERInputStream.class);
    private OIDRegister oid_register;

    public BERInputStream(InputStream from, OIDRegister oid_register) {
        this.in = new BufferedInputStream(from);
        this.oid_register = oid_register;
    }

    public BERInputStream(InputStream from, String encoding, OIDRegister oid_register) {
        this.in = new BufferedInputStream(from);
        this.encoding = encoding;
        this.oid_register = oid_register;
    }

    public BERInputStream(InputStream from, String encoding, int buffsize, OIDRegister oid_register) {
        this.in = new BufferedInputStream(from, buffsize);
        this.encoding = encoding;
        this.oid_register = oid_register;
    }

    // Methods from SerializationManager

    public int getDirection() {
        return DIRECTION_DECODE;
    }

    // Return length of tag encoding
    public int tag_codec(boolean is_constructed) throws java.io.IOException {
        // We expect to find the tag (tag_class, tag_value) on the input stream, if so, decode the length
        // octets also, if not, return -1

        if (next_tag_class < 0) {
            // First thing to to is to check that there is actually some more data in this PDU
            // The problem now is that if the last thing in a constructed type is an ASN NULL, then
            // there will be no data left in the constructed type after we have read the next tag and
            // length....
            // So, if we are in the middle of decoding something.
            if (encoding_info.size() > 0) {
                // But there is no more data
                if (!moreData()) {
                    if (log.isDebugEnabled()) {
                        log.debug("tag_codec returning -1 whilst expecting (" + tag_class + "," + tag_value
                                + ") because there is no more data in the constructed type");
                    }
                    tag_class = -1;
                    return -1;
                }
            }

            // Peek at the next tag
            decodeNextTag();
        }

        if (log.isDebugEnabled())
            log.debug("Looking for " + tag_class + " " + tag_value + " next = " + next_tag_class + " "
                    + next_tag_number);

        if ((next_tag_class == tag_class) && (next_tag_number == tag_value)) {
            if (log.isDebugEnabled())
                log.debug("[" + next_tag_class + "," + next_tag_number + "] cons=" + next_is_constructed + " len="
                        + next_length + " is indef:" + next_is_indefinite);

            // Set up to read next tag class
            next_tag_class = -1;
            tag_class = -1;

            // We have a match, so return enc len
            return next_length;
        } else {
            // Did not find the expected tag, reset next tag
            tag_class = -1;
        }

        return -1;
    }

    private void decodeNextTag() throws java.io.IOException {
        byte c = (byte) read();

        c &= 0xFF;
        next_tag_class = c & 0xC0;
        next_is_constructed = (c & 0x20) != 0;

        // System.err.println("First byte of tag is "+c);
        next_tag_number = c & 0x1F;

        // If there are multiple octets to encode the tag
        if (next_tag_number == 0x1F) {
            next_tag_number = 0;
            do {
                c = (byte) read();

                // Shift value 7 bits left
                next_tag_number = next_tag_number << 7;

                // Merge with the octets we just got
                next_tag_number = (next_tag_number | (c & 0x7F));
            } while ((c & 0x80) != 0);
        }

        next_length = decodeLengthOctets();

        if (log.isDebugEnabled()) {
            log.debug("[class:" + next_tag_class + " tag:" + next_tag_number + " cons:" + next_is_constructed
                    + " stack:" + encoding_info.size() + " len:" + next_length + "]");
        }
    }

    private int decodeLengthOctets() throws java.io.IOException {
        int datalen;
        byte lenpart = (byte) read();

        // System.err.println("First len octet is "+lenpart);

        if ((lenpart & 0x80) == 0) // If bit 8 is 0
        {
            // Single octet length encoding
            // System.err.println("Single octet length encoding");
            datalen = lenpart;
            next_is_indefinite = false;
        } else if ((lenpart & 0x7F) == 0) // Otherwise we are multiple octets (Maybe 0, which = indefinite)
        {
            // System.err.println("Indefinite length encoding");
            next_is_indefinite = true;
            datalen = 0;
        } else {
            next_is_indefinite = false;
            // System.err.println("Multiple octet length encoding ("+(lenpart & 0x7F )+"octets)");
            lenpart &= 0x7F;

            datalen = 0;
            while (lenpart-- > 0)
                datalen = (datalen << 8) | ((byte) read() & 0xFF);
        }

        return datalen;
    }

    public byte[] octetstring_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        byte[] retval = null;

        // Indefinite length encoding only allowed for constructed types, primitive length 0 must mean 
        // 0 contents octets
        if ((next_length == 0) && (next_is_constructed)) {
            if (log.isDebugEnabled())
                log.debug("Indefinite length encoding of octetstring");

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // StringWriter w = new StringWriter();

            byte current_octet = (byte) read();
            byte next_octet = (byte) read();

            while ((current_octet != 0) && (next_octet != 0)) {
                baos.write(current_octet);

                current_octet = next_octet;
                next_octet = (byte) read();
            }

            retval = baos.toByteArray();
        } else {
            if (log.isDebugEnabled())
                log.debug("definite length encoding of octetstring (" + next_length + ")");

            byte[] data = new byte[next_length];
            int bytes_left_to_read = next_length;
            int offset = 0;

            // We may need to call read repeatedly until we have all the data.
            while (bytes_left_to_read > 0) {
                int bytes_read = read(data, offset, bytes_left_to_read);
                bytes_left_to_read -= bytes_read;
                offset += bytes_read;
                if (log.isDebugEnabled())
                    log.debug("Read " + bytes_read + " of " + next_length + " leaving " + bytes_left_to_read
                            + " Next bytes will be at " + offset);
            }

            retval = data;
        }

        if (log.isDebugEnabled())
            log.debug("octetstring_codec returns byte array of length=" + retval.length);

        return retval;
    }

    public Boolean boolean_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        Boolean retval = null;

        byte val = (byte) read();

        if (val != 0x00)
            retval = Boolean.TRUE;
        else
            retval = Boolean.FALSE;

        // debug("boolean_codec returns "+retval+" length="+next_length);

        return retval;
    }

    public BigInteger integer_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        byte[] data = new byte[next_length];

        int bytes_left_to_read = next_length;
        int offset = 0;

        // We may need to call read repeatedly until we have all the data.
        while (bytes_left_to_read > 0) {
            int bytes_read = read(data, offset, bytes_left_to_read);
            bytes_left_to_read -= bytes_read;
            offset += bytes_read;
        }

        return new BigInteger(data);
    }

    public int[] oid_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        // System.err.println("Decoding OID, length = "+next_length);

        int[] retval = new int[next_length + 1];
        byte[] decode_buffer = new byte[next_length];
        int pos = 2;

        int bytes_left_to_read = next_length;
        int offset = 0;

        // We may need to call read repeatedly until we have all the data.
        while (bytes_left_to_read > 0) {
            int bytes_read = read(decode_buffer, offset, bytes_left_to_read);
            bytes_left_to_read -= bytes_read;
            offset += bytes_read;
        }

        ByteArrayInputStream bais = new ByteArrayInputStream(decode_buffer);

        byte octet = (byte) bais.read();

        if (octet >= 80) {
            retval[0] = 2;
            retval[1] = octet - 80;
        } else if (octet >= 40) {
            retval[0] = 1;
            retval[1] = octet - 40;
        } else {
            retval[0] = 0;
            retval[1] = octet;
        }

        // Split first octet into first 2 elements of OID

        while (bais.available() > 0) {
            retval[pos++] = decodeBase128Int(bais);
        }

        int[] result = new int[pos];
        System.arraycopy(retval, 0, result, 0, pos);

        // debug("oid_codec returns "+result+" length="+next_length);

        return result;
    }

    public byte[] any_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        byte[] data = null;

        if ((next_length > 0) && (next_is_constructed)) {
            // debug("definite length encoding of octetstring ("+next_length+")");
            data = new byte[next_length];
            int bytes_left_to_read = next_length;
            int offset = 0;

            // We may need to call read repeatedly until we have all the data.
            while (bytes_left_to_read > 0) {
                int bytes_read = read(data, offset, bytes_left_to_read);
                bytes_left_to_read -= bytes_read;
                offset += bytes_read;
                // debug("Read "+bytes_read+" of "+next_length+" leaving "+bytes_left_to_read+" Next bytes will be at "+offset);
            }
        } else if (next_length == 0) {
            // Indefinite length encoding
            // debug("Indefinite length encoding of any data....");
            StringWriter w = new StringWriter();

            byte current_octet = (byte) read();
            byte next_octet = (byte) read();

            while ((current_octet != 0) && (next_octet != 0)) {
                w.write(current_octet);

                current_octet = next_octet;
                next_octet = (byte) read();
            }

            data = w.toString().getBytes();
        } else
            throw new java.io.IOException("Problem decoding any");

        // debug("any returns ... length="+next_length);

        return data;
    }

    public AsnBitString bitstring_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        AsnBitString abs = (AsnBitString) instance;

        // debug("Bitstring codec...");

        int unused = read();

        if (next_length > 0) {
            byte[] data = new byte[next_length - 1];

            data = new byte[next_length - 1];
            int bytes_left_to_read = next_length - 1;
            int offset = 0;

            // We may need to call read repeatedly until we have all the data.
            while (bytes_left_to_read > 0) {
                int bytes_read = read(data, offset, bytes_left_to_read);
                bytes_left_to_read -= bytes_read;
                offset += bytes_read;
            }

            abs = new AsnBitString(data, unused);

            // debug("got "+next_length+" bytes of bitstring with "+unused+" unused bits at end);
        }

        // debug("bitstring returns ... length="+next_length);

        return abs;
    }

    public AsnNull null_codec(Object instance, boolean is_constructed) throws java.io.IOException {
        AsnNull retval = null;

        // No contents octets allowed for null encoding
        if (next_length != 0) {
            throw new java.io.IOException("Unexpected length encoding of null");
        }

        return new AsnNull();
    }

    public Object choice(Object current_instance, Object[][] choice_info, String name) throws java.io.IOException {
        ChoiceType retval = (ChoiceType) current_instance;
        if (log.isDebugEnabled())
            log.debug("1.... choice_codec (" + name + ") current = " + retval + " number of options="
                    + choice_info.length);
        Object result = null;

        for (int i = 0; ((i < choice_info.length) && (result == null)); i++) {
            Integer tagmode = (Integer) (choice_info[i][0]);
            Integer tagclass = (Integer) (choice_info[i][1]);
            Integer tagnumber = (Integer) (choice_info[i][2]);
            base_codec codec_to_use = ((base_codec) (choice_info[i][3]));

            if (log.isDebugEnabled())
                log.debug("choice Trying [" + i + "] : " + tagmode + " " + tagclass + " " + tagnumber + " "
                        + codec_to_use);

            if (tagmode.equals(SerializationManager.TAGMODE_NONE)) {
                result = codec_to_use.serialize(this, result, true, ((String) (choice_info[i][4])));
            } else {
                if (tagmode.equals(SerializationManager.IMPLICIT)) {
                    // Implicit Tagging
                    if (log.isDebugEnabled())
                        log.debug(
                                "implicit  tagging, so simply calling codec for " + ((String) (choice_info[i][4])));

                    result = implicit_tag(codec_to_use, result, tagclass.intValue(), tagnumber.intValue(), true,
                            ((String) (choice_info[i][4])));
                } else {
                    if (log.isDebugEnabled())
                        log.debug(
                                "explicit tagging, so simply calling codec for " + ((String) (choice_info[i][4])));

                    if (constructedBegin(tagclass.intValue(), tagnumber.intValue())) {
                        result = codec_to_use.serialize(this, result, false, ((String) (choice_info[i][4])));
                        constructedEnd();
                    }
                }
            }

            if (result != null) {
                if (log.isDebugEnabled())
                    log.debug("Choice codec matched on choice " + i);
                retval.o = result;
                retval.which = i;
            }
        }

        if (result == null)
            retval = null;

        return retval;
    }

    public boolean sequenceBegin() throws java.io.IOException {
        if (tag_class < 0) {
            tag_class = SerializationManager.UNIVERSAL;
            tag_value = SerializationManager.SEQUENCE;
        }
        return constructedBegin(tag_class, tag_value);
    }

    public boolean sequenceEnd() throws java.io.IOException {
        return constructedEnd();
    }

    public boolean constructedBegin(int tagclass, int tagnumber) throws java.io.IOException {
        if (tag_class < 0) {
            tag_class = tagclass;
            tag_value = tagnumber;
        }

        if (tag_codec(true) >= 0) {
            // Entering a constructed block
            if (log.isDebugEnabled()) {
                log.debug("CONS [" + encoding_info.size() + "] (" + tag_class + "," + tag_value + ") " + tagclass
                        + " len=" + next_length);
                log.debug("{");
            }
            CodecStackInfo csi = new CodecStackInfo();
            csi.content_length = next_length;
            csi.bytes_processed = 0;
            csi.is_constructed = next_is_constructed;
            csi.is_indefinite_length = next_is_indefinite;
            encoding_info.push(csi);
            return true;
        } else if (log.isDebugEnabled()) {
            log.debug("tag_codec returned < 0");
        }

        return false;
    }

    public boolean constructedEnd() throws java.io.IOException {
        CodecStackInfo csi = (CodecStackInfo) encoding_info.pop();
        // We now need to add any bytes read on to the bytes processed total for the next 
        // constructed item in the stack

        if (log.isDebugEnabled())
            log.debug("}");

        // If we are closing an indefinite length encoding, consume the terminating octets
        // if ( csi.content_length == 0 )
        if (csi.is_indefinite_length) {
            // debug("Reading indefinite length terminating octets");
            byte b1 = (byte) read();
            byte b2 = (byte) read();

            if ((b1 == 0) && (b2 == 0)) {
                next_tag_class = -1;
                tag_class = -1;
            } else {
                throw new java.io.IOException(
                        "Expected indefinite length terminating octets for constructed type, found other values");
            }
        }

        if (encoding_info.size() > 0) {
            CodecStackInfo curr = (CodecStackInfo) encoding_info.peek();

            curr.bytes_processed += csi.bytes_processed;
        }

        if (log.isDebugEnabled())
            log.debug("Constructed End [" + encoding_info.size() + "] (" + csi.bytes_processed + " bytes)");

        return true;
    }

    public Object implicit_tag(base_codec c, Object current_instance, int tag_class, int tag_number,
            boolean is_optional, String name) throws java.io.IOException {
        Object retval = null;

        if (log.isDebugEnabled())
            log.debug("implicit_tag " + tag_class + "," + tag_number + " " + name);

        implicit_settag(tag_class, tag_number);
        retval = c.serialize(this, current_instance, is_optional, name);

        return retval;
    }

    public Object explicit_tag(base_codec c, Object current_instance, int tag_class, int tag_number,
            boolean is_optional, String name) throws java.io.IOException {
        Object retval = current_instance;

        if (log.isDebugEnabled())
            log.debug("explicit_tag " + tag_class + "," + tag_number + " " + name);

        if (constructedBegin(tag_class, tag_number)) {
            retval = c.serialize(this, retval, is_optional, name);
            constructedEnd();
        }

        return retval;
    }

    public ArrayList sequenceOf(ArrayList v, base_codec codec) throws java.io.IOException {
        if (v != null) {
            while (moreData()) {
                Object item_to_add = codec.serialize(this, null, true, "SequenceOf item");
                if (item_to_add == null)
                    throw new java.io.IOException("Error expecting member of sequenceOf");
                v.add(item_to_add);
            }
        }

        return v;
    }

    public void implicit_settag(int tagclass, int tagvalue) {
        if (tag_class < 0) {
            tag_class = tagclass;
            tag_value = tagvalue;
        }
    }

    // Override default read method with octet counting for constructed members
    public int read() throws java.io.IOException {
        int retval = in.read();

        if (retval == -1)
            throw new java.io.IOException("Connection Closed");

        if (encoding_info.size() > 0) {
            // Get head off stack and add 1 to bytes processed
            CodecStackInfo csi = (CodecStackInfo) encoding_info.peek();
            csi.bytes_processed += 1;
        }

        return retval;
    }

    public int read(byte[] buffer, int offset, int max) throws java.io.IOException {
        int retval = in.read(buffer, offset, max);

        if (retval == -1)
            throw new java.io.IOException("Connection Closed");

        if (encoding_info.size() > 0) {
            // Get head off stack and add retval to bytes processed
            CodecStackInfo csi = (CodecStackInfo) encoding_info.peek();
            csi.bytes_processed += retval;
        }

        return retval;
    }

    // This function should NEVER consume the 2 octets that terminate indefinite length encoding...
    // That should be left to the thing encoding the sequence... This func just takes a peek at the
    // next octets to see what's to come...
    public boolean moreData() throws java.io.IOException {
        // Get the current top of the encoding stack and compare it's content_length
        // with the current decode position
        // debug("moreData called, stack="+encoding_info.size());

        if (encoding_info.size() > 0) {
            CodecStackInfo csi = (CodecStackInfo) encoding_info.peek();

            if (log.isDebugEnabled())
                log.debug("moreData() Content length=" + csi.content_length + ", bytes_processed="
                        + csi.bytes_processed + ", constructed=" + csi.is_constructed
                        + ", indefinite length encoding =" + csi.is_indefinite_length);

            if (csi.content_length > 0) {
                // debug("MoreData comparing "+csi.bytes_processed+" < "+ csi.content_length);
                if (csi.bytes_processed < csi.content_length)
                    return true;
                else
                    return false;
            } else if (csi.is_indefinite_length) {
                // Indefinite length encodings are terminated by 00
                // debug("MoreData... Indefinite length encoding, so check for terminating octets");
                in.mark(5);
                int i1 = in.read();
                int i2 = in.read();
                in.reset();
                if ((i1 == 0) && (i2 == 0)) {
                    // debug("MoreData... false ( Next octets are 00 )");
                    // csi.bytes_processed += 2;
                    return false;
                } else {
                    // debug("MoreData... true because next 2 octets are not 00: "+i1+" and "+i2);
                    // in.reset();
                    return true;
                }
            }
        }

        return false;
    }

    private int decodeBase128Int(InputStream ins) throws java.io.IOException {
        int retval = 0;
        byte octet = (byte) 128;
        while ((octet & 128) == 128) {
            octet = (byte) ins.read();
            retval = ((retval << 7) | (octet & 127));
        }

        return retval;
    }

    public base_codec getHintCodec() {
        return codec_hint;
    }

    public void setHintCodec(base_codec c) {
        codec_hint = c;
    }

    public String getCharsetEncoding() {
        return encoding;
    }

    public OIDRegister getOIDRegister() {
        return oid_register;
    }

}