/* * SCUBA smart card framework. * * Copyright (C) 2009 The SCUBA team. * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * $Id: BERTLVInputStream.java 1865 2011-09-27 13:22:17Z anatom $ */ /* * This file has been re-licensed under LGPL with permission from * Martijn Oostdijk, 2009-08-15. */ package net.sourceforge.scuba.tlv; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Stack; /** * TLV input stream. * * @author Martijn Oostdijk (martijn.oostdijk@gmail.com) */ public class BERTLVInputStream extends InputStream { /** Carrier. */ private DataInputStream in; private State state; private State markedState; /** * Constructs a new TLV stream based on another stream. * * @param in a TLV object */ public BERTLVInputStream(InputStream in) { this.in = new DataInputStream(in); state = new State(); markedState = null; } /** * Reads a tag. * * @return the tag just read * * @throws IOException if reading goes wrong */ public int readTag() throws IOException { int tag = -1; int bytesRead = 0; try { int b = in.readUnsignedByte(); bytesRead++; while (b == 0x00 || b == 0xFF) { b = in.readUnsignedByte(); bytesRead++; /* skip 00 and FF */ } switch (b & 0x1F) { case 0x1F: tag = b; /* We store the first byte including LHS nibble */ b = in.readUnsignedByte(); bytesRead++; while ((b & 0x80) == 0x80) { tag <<= 8; tag |= (b & 0x7F); b = in.readUnsignedByte(); bytesRead++; } tag <<= 8; tag |= (b & 0x7F); /* * Byte with MSB set is last byte of * tag... */ break; default: tag = b; break; } state.setTagRead(tag, bytesRead); return tag; } catch (IOException e) { throw e; } } /** * Reads a length. * * @return the length just read * * @throws IOException if reading goes wrong */ public int readLength() throws IOException { try { if (!state.isAtStartOfLength()) { throw new IllegalStateException("Not at start of length"); } int bytesRead = 0; int length = 0; int b = in.readUnsignedByte(); bytesRead++; if ((b & 0x80) == 0x00) { /* short form */ length = b; } else { /* long form */ int count = b & 0x7F; length = 0; for (int i = 0; i < count; i++) { b = in.readUnsignedByte(); bytesRead++; length <<= 8; length |= b; } } state.setLengthRead(length, bytesRead); return length; } catch (IOException e) { throw e; } } /** * Reads a value. * * @return the value just read * * @throws IOException if reading goes wrong */ public byte[] readValue() throws IOException { try { int length = state.getLength(); byte[] value = new byte[length]; in.readFully(value); state.updateValueBytesRead(length); return value; } catch (IOException e) { throw e; } } private long skipValue() throws IOException { if (state.isAtStartOfTag()) { return 0; } if (state.isAtStartOfLength()) { return 0; } int bytesLeft = state.getValueBytesLeft(); return skip(bytesLeft); } /** * Skips in this stream until a given tag is found (depth first). * The stream is positioned right after the first occurrence of the tag. * * @param searchTag the tag to search for * * @throws IOException */ public void skipToTag(int searchTag) throws IOException { while (true) { /* Get the next tag. */ int tag = -1; if (state.isAtStartOfTag()) { /* Nothing. */ } else if (state.isAtStartOfLength()) { readLength(); if (isPrimitive(state.getTag())) { skipValue(); } } else { if (isPrimitive(state.getTag())) { skipValue(); } } tag = readTag(); if (tag == searchTag) { return; } if (isPrimitive(tag)) { int length = readLength(); int skippedBytes = (int)skipValue(); if (skippedBytes >= length) { /* Now at next tag. */ continue; } else { /* Could only skip less than length bytes, * we're lost, probably at EOF. */ break; } } } } /** * Returns an estimate of the number of bytes that can be read (or * skipped over) from this input stream without blocking by the next * invocation of a method for this input stream. * * @return a number of bytes * * @throws IOException if something goes wrong */ public int available() throws IOException { return in.available(); } /** * Reads the next byte of data from the input stream. * * @return a byte * * @throws IOException if reading goes wrong */ public int read() throws IOException { int result = in.read(); if (result < 0) { return -1; } state.updateValueBytesRead(1); return result; } /** * Attempts to skip over n bytes. * * @return the actual number of bytes skipped * * @throws IOException if something goes wrong */ public long skip(long n) throws IOException { if (n <= 0) { return 0; } long result = in.skip(n); state.updateValueBytesRead((int)result); return result; } /** * Marks the underlying input stream if supported. * * @param readLimit limit for marking */ public synchronized void mark(int readLimit) { in.mark(readLimit); markedState = (State)state.clone(); } /** * Whether marking and resetting are supported. * We support this whenever the underlying input stream supports it. * * @return whether mark and reset are supported */ public boolean markSupported() { return in.markSupported(); } /** * Resets the underlying input stream if supported. * * @throws IOException if something goes wrong */ public synchronized void reset() throws IOException { if (!markSupported()) { throw new IOException("mark/reset not supported"); } in.reset(); state = markedState; markedState = null; } /** * Closes this input stream. * * @throws IOException if something goes wrong */ public void close() throws IOException { in.close(); } public String toString() { return state.toString(); } private static boolean isPrimitive(int tag) { int i = 3; for (; i >= 0; i--) { int mask = (0xFF << (8 * i)); if ((tag & mask) != 0x00) { break; } } int msByte = (((tag & (0xFF << (8 * i))) >> (8 * i)) & 0xFF); boolean result = ((msByte & 0x20) == 0x00); return result; } /** * State keeps track of where we are in the TLV stream. */ private class State implements Cloneable { /** Which tags have we seen thus far? */ private Stack state; /** FIXME: These are probably redundant... */ private boolean isAtStartOfTag, isAtStartOfLength, isReadingValue; /* * TFF: ^TLVVVVVV * FTF: T^LVVVVVV * FFT: TL^VVVVVV * FFT: TLVVVV^VV * TFF: ^ */ public State() { state = new Stack(); isAtStartOfTag = true; isAtStartOfLength = false; isReadingValue = false; } private State(Stack state, boolean isAtStartOfTag, boolean isAtStartOfLength, boolean isReadingValue) { this.state = state; this.isAtStartOfTag = isAtStartOfTag; this.isAtStartOfLength = isAtStartOfLength; this.isReadingValue = isReadingValue; } public boolean isAtStartOfTag() { return isAtStartOfTag; } public boolean isAtStartOfLength() { return isAtStartOfLength; } public boolean isReadingValue() { return isReadingValue; } public int getTag() { if (state.isEmpty()) { throw new IllegalStateException("Tag not yet read."); } TLStruct currentObject = state.peek(); return currentObject.getTag(); } public int getLength() { if (state.isEmpty()) { throw new IllegalStateException("Length not yet read."); } TLStruct currentObject = state.peek(); int length = currentObject.getLength(); if (length < 0) { throw new IllegalStateException("Length not yet read."); } return length; } public int getValueBytesLeft() { if (state.isEmpty()) { throw new IllegalStateException("Not yet reading value."); } TLStruct currentObject = state.peek(); int currentLength = currentObject.getLength(); if (currentLength < 0) { throw new IllegalStateException("Not yet reading value."); } int currentBytesRead = currentObject.getValueBytesRead(); return currentLength - currentBytesRead; } public void setTagRead(int tag, int bytesRead) { /* Length is set to -1, we will update it when we encounter it */ TLStruct obj = new TLStruct(tag, -1, 0); if (!state.isEmpty()) { TLStruct parent = state.peek(); parent.updateValueBytesRead(bytesRead); } state.push(obj); isAtStartOfTag = false; isAtStartOfLength = true; isReadingValue = false; } public void setLengthRead(int length, int bytesRead) { if (length < 0) { throw new IllegalArgumentException("Cannot set negative length (length = " + length + ")."); } TLStruct obj = state.pop(); if (!state.isEmpty()) { TLStruct parent = state.peek(); parent.updateValueBytesRead(bytesRead); } obj.setLength(length); state.push(obj); isAtStartOfTag = false; isAtStartOfLength = false; isReadingValue = true; } public void updateValueBytesRead(int n) { if (state.isEmpty()) { return; } TLStruct currentObject = state.peek(); int bytesLeft = currentObject.getLength() - currentObject.getValueBytesRead(); if (n > bytesLeft) { throw new IllegalArgumentException("Cannot read " + n + " bytes! Only " + bytesLeft + " bytes left in this TLV object " + currentObject); } currentObject.updateValueBytesRead(n); int currentLength = currentObject.getLength(); if (currentObject.getValueBytesRead() == currentLength) { state.pop(); /* Stand back! I'm going to try recursion! Update parent(s)... */ updateValueBytesRead(currentLength); isAtStartOfTag = true; isAtStartOfLength = false; isReadingValue = false; } else { isAtStartOfTag = false; isAtStartOfLength = false; isReadingValue = true; } } @SuppressWarnings("unchecked") public Object clone() { return new State((Stack)state.clone(), isAtStartOfTag, isAtStartOfLength, isReadingValue); } public String toString() { return state.toString(); } private class TLStruct implements Cloneable { private int tag, length, valueBytesRead; public TLStruct(int tag, int length, int valueBytesRead) { this.tag = tag; this.length = length; this.valueBytesRead = valueBytesRead; } public void setLength(int length) { this.length = length; } public int getTag() { return tag; } public int getLength() { return length; } public int getValueBytesRead() { return valueBytesRead; } public void updateValueBytesRead(int n) { this.valueBytesRead += n; } public Object clone() { return new TLStruct(tag, length, valueBytesRead); } public String toString() { return "[TLStruct " + Integer.toHexString(tag) + ", " + length + ", " + valueBytesRead + "]"; } } } }