/* Protocol Definition Language Copyright (C) 2003-2006 Marcus Andersson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU 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 net.sf.nmedit.jpdl2; import java.util.ArrayList; import java.util.List; import net.sf.nmedit.jpdl2.dom.PDLBlock; import net.sf.nmedit.jpdl2.dom.PDLChoice; import net.sf.nmedit.jpdl2.dom.PDLConditional; import net.sf.nmedit.jpdl2.dom.PDLConstant; import net.sf.nmedit.jpdl2.dom.PDLDocument; import net.sf.nmedit.jpdl2.dom.PDLFunction; import net.sf.nmedit.jpdl2.dom.PDLInstruction; import net.sf.nmedit.jpdl2.dom.PDLItem; import net.sf.nmedit.jpdl2.dom.PDLMultiplicity; import net.sf.nmedit.jpdl2.dom.PDLOptional; import net.sf.nmedit.jpdl2.dom.PDLPacketDecl; import net.sf.nmedit.jpdl2.dom.PDLPacketRef; import net.sf.nmedit.jpdl2.dom.PDLSwitchStatement; import net.sf.nmedit.jpdl2.dom.PDLVariable; import net.sf.nmedit.jpdl2.impl.PDLMessageImpl; import net.sf.nmedit.jpdl2.impl.PDLPacketImpl; import net.sf.nmedit.jpdl2.impl.PDLParseContextImpl; import net.sf.nmedit.jpdl2.stream.BitStream; import net.sf.nmedit.jpdl2.stream.IntStream; import net.sf.nmedit.jpdl2.stream.PDLDataSource; import net.sf.nmedit.jpdl2.utils.PDLUtils; public class PDLPacketParser { private static final boolean DEBUG = false; private static final boolean LOG_ONLY_ERRORS = true; private PDLDataSource input; private BitStream bitstream; protected int reserved; private String messageId; private boolean generate; // == int stream source private StringBuilder logString = new StringBuilder(); private PDLParseContextImpl context = new PDLParseContextImpl(); private PDLDocument doc; public PDLPacketParser() { this(null); } public PDLPacketParser(PDLDocument doc) { this.doc = doc; } public PDLDocument getDocument() { return doc; } public String getLatestMessageId() { return messageId; } public BitStream getBitStream() { return bitstream; } public PDLMessage parseMessage(PDLDataSource input) throws PDLException { return parseMessage(input, doc); } public PDLMessage parseMessage(PDLDataSource input, String packetName) throws PDLException { return parseMessage(input, doc, packetName); } public PDLPacket parse(PDLDataSource input) throws PDLException { return parse(input, doc); } public PDLPacket parse(PDLDataSource input, String packetName) throws PDLException { return parse(input, doc, packetName); } public PDLMessage parseMessage(PDLDataSource input, PDLDocument document) throws PDLException { PDLPacket packet = parse(input, document); return new PDLMessageImpl(packet, messageId); } public PDLMessage parseMessage(PDLDataSource input, PDLDocument document, String packetName) throws PDLException { PDLPacket packet = parse(input, document, packetName); return new PDLMessageImpl(packet, messageId); } public PDLPacket parse(PDLDataSource input, PDLDocument document) throws PDLException { if (document.getStartPacketName() == null) throw new PDLException("start packet not defined"); return parse(input, document, document.getStartPacketName()); } public PDLPacket parse(PDLDataSource input, PDLDocument document, String packetName) throws PDLException { this.bitstream = null; this.input = null; PDLPacketDecl packetDecl = document.getPacketDecl(packetName); if (packetDecl == null) throw new PDLException("undefined packet: "+packetName); if (packetDecl.isInlined()) throw new PDLException("packet can only be used inline: "+packetName); if (DEBUG) { logString.setLength(0); tabs=0; println("*************************************"); println("parse new message, start="+packetName); if (input instanceof BitStream) println("bitstream: "+PDLUtils.toHexadecimal(((BitStream)input).toByteArray())); else { println("intstream: "+PDLUtils.toHexadecimal(((IntStream)input).toArray())); } } pstack.clear(); this.input = input; this.reserved = 0; this.messageId = null; context.input = input; if (input instanceof BitStream) { bitstream = (BitStream) input; generate = false; } else if (input instanceof IntStream) { bitstream = new BitStream(); generate = true; } else { throw new PDLException("input must be IntStream or BitStream: "+input); } println("generate:"+generate); if (!isAvailable(getMinSize(packetDecl))) throw new PDLException(ensureBitsAvailableMessage(getMinSize(packetDecl)), packetDecl); this.reserved = getMinSize(packetDecl); context.stream = bitstream; context.clearLabels(); try { try { int packetStart = getPaddingStartValue(); PDLPacketImpl packet = new PDLPacketImpl(packetDecl, null); parseBlock(packet, packetDecl, 0); padding(packetDecl, packetStart); if (DEBUG && (!LOG_ONLY_ERRORS)) { if (generate) println("generated bitstream: "+PDLUtils.toHexadecimal(bitstream.toByteArray())); println("parsing successful"); println("+++++++++++++++++++++++++++++++++++++"); System.out.println(logString); } return packet; } catch (Throwable t) { if (DEBUG) { println("parsing failed"); input.setPosition(0); StringBuilder sb = new StringBuilder(); int steps = generate ? 1 : 8; while (input.isAvailable(steps)) sb.append(Integer.toHexString(input.getInt(steps))+" "); println("message: "+sb.toString()); if (generate) println("generated bitstream (incomplete): "+PDLUtils.toHexadecimal(bitstream.toByteArray())); println("error: "+toString(t)); println("+++++++++++++++++++++++++++++++++++++"); System.out.println(logString); } PDLException pe = new PDLException(toString(t), packetDecl); // pe.initCause(t); throw pe; } } finally { this.input = null; } } private void padding(PDLPacketDecl packetDecl, int startPos) throws PDLException { if (packetDecl.getPadding()>1) { int packetSize = (generate?bitstream.getSize():bitstream.getPosition())-startPos; int tail = packetSize % packetDecl.getPadding(); if (tail>0) { int extraBits = packetDecl.getPadding() - tail; if (generate) { if (DEBUG) println(">>> adding padding bits in packet "+packetDecl+": 0:"+extraBits); while (extraBits>0) { bitstream.append(0, extraBits); extraBits-=32; } } else { if (DEBUG) println(">>> skipping padding bits in packet "+packetDecl+": 0:"+extraBits); while (extraBits>0) { int bits = input.getInt(extraBits); //if (bits != 0) // throw new PDLException("padding bits must be zero: "+bits, packetDecl); extraBits-=32; } } } } } private static String toString(Throwable t) { StringBuilder msg = new StringBuilder(); while (t != null) { StackTraceElement[] aste = t.getStackTrace(); StackTraceElement ste = aste[0]; msg.append("\tat "+t+" ("+ste.getFileName()+":"+ste.getLineNumber()+")"); t=t.getCause(); if (t != null) msg.append('\n'); } return msg.toString(); } private void addReserved(int value) throws PDLException { reserved+=value; if (reserved < 0) throw new InternalError("reserved negative: "+reserved); } private int getStreamPosition() { return generate ? bitstream.getSize() : bitstream.getPosition(); } private void printItem(PDLItem item, String suffix) { println("--- item "+item+" \t(reserved:"+reserved+", pos:"+input.getPosition()+", size:"+input.getSize()+") "+suffix); } private void printItem(PDLItem item) { printItem(item, ""); } private class ParseState { PDLPacketImpl packet; PDLBlock block; int index; public ParseState(PDLPacketImpl packet, PDLBlock block) { this.packet = packet; this.block = block; this.index = 0; } boolean recover() { return false; } void complete() throws PDLException {}; } private class BlockState extends ParseState { public BlockState(PDLPacketImpl packet, PDLBlock block) { super(packet, block); } } private class PacketState extends ParseState { private PDLPacketImpl insertInto; private int packetStart; private String binding; public PacketState(PDLPacketImpl packet, PDLPacketRef ref, PDLPacketDecl pdecl, PDLPacketImpl insertInto, int packetStart) { super(packet, pdecl); this.binding = ref.getBinding(); this.insertInto = insertInto; this.packetStart = packetStart; } void complete() throws PDLException { padding((PDLPacketDecl)block, packetStart); if (insertInto != null) insertInto.setPacket(binding, packet); } } private class RestorableParseState extends ParseState { int st_reserved; int st_age; int st_inputPos; int st_streamSize; int st_tabs; String st_messageId; int stackSize; public RestorableParseState(PDLPacketImpl packet) { super(packet, null); init(); } void init() { this.st_age = packet.getCurrentAge(); this.st_reserved = PDLPacketParser.this.reserved; this.st_inputPos = PDLPacketParser.this.input.getPosition(); this.st_streamSize = PDLPacketParser.this.bitstream.getSize(); this.st_tabs = PDLPacketParser.this.tabs; this.st_messageId = PDLPacketParser.this.messageId; this.stackSize = pstack.size(); } void restore() { PDLPacketParser.this.tabs = st_tabs; PDLPacketParser.this.reserved = st_reserved; // restore this.packet.removeItemsOlderThan(st_age); PDLPacketParser.this.input.setPosition(st_inputPos); PDLPacketParser.this.messageId = st_messageId; PDLPacketParser.this.context.deleteLabelsOlderThan(st_age); PDLPacketParser.this.bitstream.setSize(st_streamSize); while (stackSize<pstack.size()) pop(); } } List<ParseState> pstack = new ArrayList<ParseState>(1000); private void push(ParseState state) { pstack.add(state); } private ParseState pop() { if (pstack.isEmpty()) throw new IllegalStateException("stack is empty"); return pstack.remove(pstack.size()-1); } private ParseState peek() { if (pstack.isEmpty()) throw new IllegalStateException("stack is empty"); return pstack.get(pstack.size()-1); } void parseState(int stackSizeThreshold) throws PDLException { while (pstack.size()>=stackSizeThreshold) { ParseState state = peek(); PDLBlock block = state.block; if (state.index>=block.getItemCount()) { // block completed state.complete(); pop(); continue; } context.packet = state.packet; // set packet if not done already, TODO no need to set this every time int index = state.index; PDLItem item = block.getItem(index); boolean internallyHandled = parseItem(item, state.packet, state.block, index); if (internallyHandled) { state.complete(); pop(); } state.index++; } } void parseBlock(PDLPacketImpl packet, PDLBlock block, int startIndex) throws PDLException { tabs++; /* for (int index=startIndex;index<block.getItemCount();index++) { PDLItem item = block.getItem(index); context.packet = packet; boolean internallyHandled = parseItem(item, packet, block, index); if (internallyHandled) return; } */ /* We are using both a recursive and an iterative (using a stack) approach. * TODO completely eliminate recursion to avoid possible StackOverflow errors * TODO cache instances of ParseState and it's subclasses */ insertBlock(packet, block, startIndex); parseState(pstack.size()); tabs--; } void insertBlock(PDLPacketImpl packet, PDLBlock block) { insertBlock(packet, block, 0); } void insertBlock(PDLPacketImpl packet, PDLBlock block, int startIndex) { BlockState state = new BlockState(packet, block); state.index = startIndex; push(state); } void insertState(PacketState state) { push(state); } private int tabs = 0; boolean parseItem(final PDLItem item, final PDLPacketImpl packet, final PDLBlock block, final int index) throws PDLException { // returns true if block is internally handled if (DEBUG) { switch (item.getType()) { case Variable: break; case Conditional: break; default: printItem(item); break; } } switch (item.getType()) { case Fail: { throw new PDLException(item, "parsing failed"); } case SwitchStatement: { addReserved(-getMinSize(item)); PDLSwitchStatement sw = item.asSwitchStatement(); int value = sw.getFunction().compute(context); PDLBlock bi = sw.getItemForCase(value); if (bi != null) { if (DEBUG) { tabs++; println("--- case '0x"+Integer.toHexString(value)+"' ("+value+" decimal)"); } ensureBitsAvailable(bi, getMinSize(bi)); addReserved(+getMinSize(bi)); // parseBlock(packet, bi); insertBlock(packet, bi); if (DEBUG) tabs--; } break; } case MessageId: { messageId = item.asInstruction().getString(); break; } case StringDef: { PDLInstruction ins = item.asInstruction(); packet.setString(ins.getString(), ins.getString2()); break; } case Label: { context.setLabel(item.asInstruction().getString(), packet.incrementAge(), getStreamPosition()); break; } case Constant: { addReserved(-getMinSize(item)); PDLConstant constant = item.asConstant(); int multiplicity = PDLUtils.getMultiplicity(packet, constant.getMultiplicity()); if (generate) { for (int i=0;i<multiplicity;i++) bitstream.append(constant.getValue(), constant.getSize()); } else { final int bitcount = constant.getSize()*multiplicity; ensureBitsAvailable(item, bitcount); while (multiplicity>0) { int value = input.getInt(constant.getSize()); if (value != constant.getValue()) { throw new PDLException(constant, "constant mismatch: "+value); } multiplicity--; } } break; } case ImplicitVariable: { addReserved(-getMinSize(item)); PDLVariable variable = item.asVariable(); int value; if (generate) { value = computeChecksum(variable, context); bitstream.append(value, variable.getSize()); if (DEBUG) println("generate checksum: "+value); } else { ensureBitsAvailable(item, generate?1:variable.getSize()); value = input.getInt(variable.getSize()); context.packet = packet; // set packet if not done already checksum(variable, context, value); } packet.setVariable(variable.getName(), value); break; } case AnonymousVariable: { context.packet = packet; // set packet if not done already // only add value to packet, not part of stream PDLVariable variable = item.asVariable(); int value = computeChecksum(variable, context); packet.setVariable(variable.getName(), value); break; } case Variable: { addReserved(-getMinSize(item)); PDLVariable variable = item.asVariable(); ensureBitsAvailable(item, generate?1:variable.getSize()); int value = input.getInt(variable.getSize()); if (DEBUG) printItem(item, "\t=0x"+Integer.toHexString(value)+" (decimal: "+value+")"); if (generate) { bitstream.append(value, variable.getSize()); } packet.setVariable(variable.getName(), value); break; } case VariableList: { addReserved(-getMinSize(item)); PDLVariable variable = item.asVariable(); // this is a hack and will be removed later final String HACK = "HACK"; boolean hack = false; int multiplicity; PDLMultiplicity vm = variable.getMultiplicity(); if (vm != null && vm.getType() == PDLMultiplicity.Type.Variable && HACK.equals(vm.getVariable())) { hack = true; int hackedValue; if (generate) { hackedValue = input.getSize()-input.getPosition(); // remaining stream } else { hackedValue = (input.getSize()-input.getPosition())/8-2; // remaining stream with out last two byte } packet.setVariable(HACK, hackedValue); if (DEBUG) println("HACKED VALUE="+hackedValue); } multiplicity = PDLUtils.getMultiplicity(packet, variable.getMultiplicity()); final int bitcount = (generate?1:variable.getSize()) * multiplicity; if (!variable.hasTerminal()) ensureBitsAvailable(item, bitcount); int[] values = new int[multiplicity]; if (!variable.hasTerminal()) { for (int i=0;i<multiplicity;i++) values[i] = input.getInt(variable.getSize()); } else { for (int i=0;i<multiplicity;i++) { int sz = generate ? 1 : variable.getSize(); if (!isAvailable(sz)) throw new PDLException(variable, ensureBitsAvailableMessage(sz)); int value = input.getInt(variable.getSize()); if (value == variable.getTerminal()) { // shrink array int[] array = new int[i]; System.arraycopy(values, 0, array, 0, array.length); values = array; break; } values[i] = value; } } if (generate) { int size = variable.getSize(); for (int i=0;i<values.length;i++) bitstream.append(values[i], size); if (size<multiplicity && variable.hasTerminal()) bitstream.append(variable.getTerminal(), size); } packet.setVariableList(variable.getName(), values); break; } case InlinePacketRef: { // do not modify 'reserved' PDLPacketRef packetRef = item.asPacketRef(); PDLPacketDecl packetDecl = packetRef.getReferencedPacket(); //int bitcount = packetDecl.getMinimumSize(); //ensureBitsAvailable(item, bitcount); int packetStart = getPaddingStartValue(); /*try { // use the same packet rather then a new one parseBlock(packet, packetDecl); } catch (PDLException pdle) { throw new PDLException(pdle, packetRef); } padding(packetDecl, packetStart);*/ PacketState state = new PacketState(packet, packetRef, packetDecl, null, packetStart); insertState(state); break; } case PacketRef: { // do not modify 'reserved' PDLPacketRef packetRef = item.asPacketRef(); PDLPacketDecl packetDecl = packetRef.getReferencedPacket(); //int bitcount = packetDecl.getMinimumSize(); //ensureBitsAvailable(item, bitcount); int packetStart = getPaddingStartValue(); PDLPacketImpl context2 = new PDLPacketImpl(packetDecl, packetRef.getBinding()); /*try { parseBlock(context2, packetDecl); } catch (PDLException pdle) { throw new PDLException(pdle, packetRef); }*/ PacketState state = new PacketState(context2, packetRef, packetDecl, packet, packetStart); insertState(state); /* packet.setPacket(packetRef.getBinding(), context2); padding(packetDecl, packetStart);*/ break; } case PacketRefList: { addReserved(-getMinSize(item)); PDLPacketRef packetRefList = item.asPacketRef(); PDLPacketDecl packetDecl = packetRefList.getReferencedPacket(); int multiplicity = PDLUtils.getMultiplicity(packet, packetRefList.getMultiplicity()); int bitcount = getMinSize(packetDecl)*multiplicity; ensureBitsAvailable(item, bitcount); addReserved(bitcount); PDLPacketImpl[] packetList = new PDLPacketImpl[multiplicity]; try { tabs++; if (DEBUG) println("--- packet reference list "+packetRefList+" (reserved:"+reserved+",pos:"+input.getPosition()+",size:"+input.getSize()+")"); for (int i=0;i<multiplicity;i++) { int packetStart = getPaddingStartValue(); PDLPacketImpl context2 = new PDLPacketImpl(packetDecl, packetRefList.getBinding()); parseBlock(context2, packetDecl, 0); packetList[i] = context2; padding(packetDecl, packetStart); } tabs--; } catch (PDLException pdle) { throw new PDLException(pdle, packetRefList); } packet.setPacketList(packetRefList.getBinding(), packetList); break; } case Conditional: { PDLConditional conditional = item.asConditional(); context.packet = packet; // set the packet field if (conditional.getCondition().isConditionTrue(context)) { if (DEBUG) printItem(item); ensureBitsAvailable(item, getMinSize(conditional)); addReserved(+getMinSize(item)); insertBlock(packet, conditional); //parseBlock(packet, conditional); } break; } case Optional: { PDLOptional optional = item.asOptional(); if (!isAvailable(getMinSize(optional))) { if (DEBUG) println(">>> optional ignored: not enough data in stream. minsize:"+getMinSize(item) +"+"+reserved+" (reserved)" +" input(position:"+input.getPosition()+",size:"+input.getSize()+")"); // necessary number of bits unavailable break; // case statement } else { if (DEBUG) println(">>> trying optional block. minsize:"+getMinSize(item) +"+"+reserved+" (reserved)" +" input(position:"+input.getPosition()+",size:"+input.getSize()+")"); } RestorableParseState state = new RestorableParseState(packet); addReserved(+getMinSize(item)); try { parseBlock(packet, optional, 0); // try to parse remaining items in this block parseBlock(packet, block, index+1// start at next index ); // try to parse the tails /* if (input.getPosition()<input.getSize()) throw new PDLException(item, "parsing message incomplete in this branch " +" input(position:"+input.getPosition()+","+input.getSize()+")"); */ // remaining message successfully parsed return true; } catch (PDLException pdle) { state.restore(); if (DEBUG) println("optional failed: "+toString(pdle)); // continue parsing } break; } case Choice: { addReserved(-getMinSize(item)); PDLChoice mchoice = item.asChoice(); ensureBitsAvailable(mchoice, getMinSize(mchoice)); RestorableParseState state = new RestorableParseState(packet); for (PDLBlock choice: mchoice) { if (isAvailable(getMinSize(choice))) { addReserved(+getMinSize(choice)); try { parseBlock(packet, choice, 0); // try to parse remaining items in this block parseBlock(packet, block, index+1// start at next index ); // accepted return true; } catch (PDLException pdle) { state.restore(); if (DEBUG) println("not chosen ("+choice+"), reason:"+toString(pdle)); } } } throw new PDLException(mchoice, "no elements were chosen"); } case Block: { PDLBlock b = item.asBlock(); insertBlock(packet, b); //parseBlock(packet, b); break; } default: { throw new InternalError("unsupported item: "+item.getType()); } } return false; } private int getPaddingStartValue() { return generate ? bitstream.getSize() : bitstream.getPosition(); } private void println(String string) { for (int i=0;i<tabs;i++) logString.append(' '); logString.append(string); logString.append("\n"); } private final int getMinSize(PDLPacketDecl packet) { return generate ? packet.getMinimumCount() : packet.getMinimumSize(); } private final int getMinSize(PDLItem item) { return generate ? item.getMinimumCount() : item.getMinimumSize(); } private int computeChecksum(PDLVariable item, PDLParseContext context) throws PDLException { PDLFunction function = item.getFunction(); int savePos = bitstream.getPosition(); int expected; try { expected = function.compute(context); } catch (PDLException pdle) { throw new PDLException(pdle); } finally { bitstream.setPosition(savePos); } return expected; } private void checksum(PDLVariable item, PDLParseContext context, final int value) throws PDLException { int expected = computeChecksum(item, context); if (value != expected) { throw new PDLException(item, "implicit value different from stream value: "+ value+" (stream), "+expected+" (expected)"); } } protected final boolean isAvailable(int size) { return input.isAvailable(reserved+size); } private String ensureBitsAvailableMessage(int size) throws PDLException { return "data unavailable: " +(size+reserved)+"="+size+" + "+reserved+" (reserved) (remaining:"+ (input.getSize()-input.getPosition())+")"; } private void ensureBitsAvailable(PDLItem item, int size) throws PDLException { if (!isAvailable(size)) throw new PDLException(item, ensureBitsAvailableMessage(size)); } }