/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009, 2010, 2011 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * 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. */ package org.ccnx.ccn.impl.encoding; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.TreeMap; import java.util.logging.Level; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.protocol.CCNTime; /** * An implementation of XMLDecoder for the Binary (ccnb) codec. * * @see BinaryXMLCodec * @see XMLDecoder */ public class BinaryXMLDecoder extends GenericXMLDecoder implements XMLDecoder { protected static final int MARK_LEN = 512; // tag length in UTF-8 encoded bytes, plus length/val bytes protected static final int DEBUG_MAX_LEN = 32768; public BinaryXMLDecoder() { super(); } public BinaryXMLDecoder(XMLDictionary dictionary) { super(dictionary); } public void initializeDecoding() { if (!_istream.markSupported()) { throw new IllegalArgumentException(this.getClass().getName() + ": input stream must support marking!"); } } public void readStartDocument() throws ContentDecodingException { // Currently no start document in binary encoding. } public void readEndDocument() throws ContentDecodingException { // Currently no end document in binary encoding. } public void readStartElement(String startTag, TreeMap<String, String> attributes) throws ContentDecodingException { try { BinaryXMLCodec.TypeAndVal tv = BinaryXMLCodec.decodeTypeAndVal(_istream); if (null == tv) { throw new ContentDecodingException("Expected start element: " + startTag + " got something not a tag."); } String decodedTag = null; if (tv.type() == BinaryXMLCodec.XML_TAG) { Log.info(Log.FAC_ENCODING, "Unexpected: got tag in readStartElement; looking for tag " + startTag + " got length: " + (int)tv.val()+1); // Tag value represents length-1 as tags can never be empty. decodedTag = BinaryXMLCodec.decodeUString(_istream, (int)tv.val()+1); } else if (tv.type() == BinaryXMLCodec.XML_DTAG) { decodedTag = tagToString(tv.val()); } if ((null == decodedTag) || (!decodedTag.equals(startTag))) { throw new ContentDecodingException("Expected start element: " + startTag + " got: " + decodedTag + "(" + tv.val() + ")"); } // DKS: does not read attributes out of stream if caller doesn't // ask for them. Should possibly peek and skip over them regardless. // TODO: fix this if (null != attributes) { readAttributes(attributes); } } catch (IOException e) { throw new ContentDecodingException("readStartElement", e); } } public void readStartElement(long startTag, TreeMap<String, String> attributes) throws ContentDecodingException { try { BinaryXMLCodec.TypeAndVal tv = BinaryXMLCodec.decodeTypeAndVal(_istream); if (null == tv) { throw new ContentDecodingException("Expected start element: " + startTag + " got something not a tag."); } Long decodedTag = null; if (tv.type() == BinaryXMLCodec.XML_TAG) { Log.info(Log.FAC_ENCODING, "Unexpected: got tag in readStartElement; looking for tag " + startTag + " got length: " + (int)tv.val()+1); // Tag value represents length-1 as tags can never be empty. String strTag = BinaryXMLCodec.decodeUString(_istream, (int)tv.val()+1); decodedTag = stringToTag(strTag); } else if (tv.type() == BinaryXMLCodec.XML_DTAG) { decodedTag = tv.val(); } if ((null == decodedTag) || (decodedTag.longValue() != startTag)) { throw new ContentDecodingException("Expected start element: " + startTag + " got: " + decodedTag + "(" + tv.val() + ")"); } // DKS: does not read attributes out of stream if caller doesn't // ask for them. Should possibly peek and skip over them regardless. // TODO: fix this if (null != attributes) { readAttributes(attributes); } } catch (IOException e) { throw new ContentDecodingException("readStartElement", e); } } public void readAttributes(TreeMap<String,String> attributes) throws ContentDecodingException { if (null == attributes) { return; } try { // Now need to get attributes. BinaryXMLCodec.TypeAndVal nextTV = BinaryXMLCodec.peekTypeAndVal(_istream); while ((null != nextTV) && ((BinaryXMLCodec.XML_ATTR == nextTV.type()) || (BinaryXMLCodec.XML_DATTR == nextTV.type()))) { // Decode this attribute. First, really read the type and value. BinaryXMLCodec.TypeAndVal thisTV = BinaryXMLCodec.decodeTypeAndVal(_istream); String attributeName = null; if (BinaryXMLCodec.XML_ATTR == thisTV.type()) { // Tag value represents length-1 as attribute names cannot be empty. attributeName = BinaryXMLCodec.decodeUString(_istream, (int)thisTV.val()+1); } else if (BinaryXMLCodec.XML_DATTR == thisTV.type()) { // DKS TODO are attributes same or different dictionary? attributeName = tagToString(thisTV.val()); if (null == attributeName) { throw new ContentDecodingException("Unknown DATTR value" + thisTV.val()); } } // Attribute values are always UDATA String attributeValue = BinaryXMLCodec.decodeUString(_istream); attributes.put(attributeName, attributeValue); nextTV = BinaryXMLCodec.peekTypeAndVal(_istream); } } catch (IOException e) { Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("readStartElement", e); } } public String peekStartElementAsString() throws ContentDecodingException { _istream.mark(MARK_LEN); String decodedTag = null; try { // Have to distinguish genuine errors from wrong tags. Could either use // a special exception subtype, or redo the work here. BinaryXMLCodec.TypeAndVal tv = BinaryXMLCodec.decodeTypeAndVal(_istream); if (null != tv) { if (tv.type() == BinaryXMLCodec.XML_TAG) { if (tv.val()+1 > DEBUG_MAX_LEN) { throw new ContentDecodingException("Decoding error: length " + tv.val()+1 + " longer than expected maximum length!"); } // Tag value represents length-1 as tags can never be empty. decodedTag = BinaryXMLCodec.decodeUString(_istream, (int)tv.val()+1); Log.info(Log.FAC_ENCODING, "Unexpected: got text tag in peekStartElement; length: " + (int)tv.val()+1 + " decoded tag = " + decodedTag); } else if (tv.type() == BinaryXMLCodec.XML_DTAG) { decodedTag = tagToString(tv.val()); } } // else, not a type and val, probably an end element. rewind and return false. } catch (ContentDecodingException e) { try { _istream.reset(); _istream.mark(MARK_LEN); long ms = System.currentTimeMillis(); File tempFile = new File("data_" + Long.toString(ms) + ".ccnb"); FileOutputStream fos = new FileOutputStream(tempFile); try { byte buf[] = new byte[1024]; while (_istream.available() > 0) { int count = _istream.read(buf); fos.write(buf,0, count); } } finally { fos.close(); } _istream.reset(); Log.info(Log.FAC_ENCODING, "BinaryXMLDecoder: exception in peekStartElement, dumping offending object to file: " + tempFile.getAbsolutePath()); throw e; } catch (IOException ie) { Log.warning(Log.FAC_ENCODING, "IOException in BinaryXMLDecoder error handling: " + e.getMessage()); Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, ie); throw new ContentDecodingException("peekStartElement", e); } } catch (IOException e) { Log.warning(Log.FAC_ENCODING, "IOException in BinaryXMLDecoder: " + e.getMessage()); Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("peekStartElement", e); } finally { try { _istream.reset(); } catch (IOException e) { Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("Cannot reset stream! " + e.getMessage(), e); } } return decodedTag; } public Long peekStartElementAsLong() throws ContentDecodingException { _istream.mark(MARK_LEN); Long decodedTag = null; try { // Have to distinguish genuine errors from wrong tags. Could either use // a special exception subtype, or redo the work here. BinaryXMLCodec.TypeAndVal tv = BinaryXMLCodec.decodeTypeAndVal(_istream); if (null != tv) { if (tv.type() == BinaryXMLCodec.XML_TAG) { if (tv.val()+1 > DEBUG_MAX_LEN) { throw new ContentDecodingException("Decoding error: length " + tv.val()+1 + " longer than expected maximum length!"); } // Tag value represents length-1 as tags can never be empty. String strTag = BinaryXMLCodec.decodeUString(_istream, (int)tv.val()+1); decodedTag = stringToTag(strTag); Log.info(Log.FAC_ENCODING, "Unexpected: got text tag in peekStartElement; length: " + (int)tv.val()+1 + " decoded tag = " + decodedTag); } else if (tv.type() == BinaryXMLCodec.XML_DTAG) { decodedTag = tv.val(); } } // else, not a type and val, probably an end element. rewind and return false. } catch (ContentDecodingException e) { try { _istream.reset(); _istream.mark(MARK_LEN); long ms = System.currentTimeMillis(); File tempFile = new File("data_" + Long.toString(ms) + ".ccnb"); FileOutputStream fos = new FileOutputStream(tempFile); try { byte buf[] = new byte[1024]; while (_istream.available() > 0) { int count = _istream.read(buf); fos.write(buf,0, count); } } finally { fos.close(); } _istream.reset(); Log.info(Log.FAC_ENCODING, "BinaryXMLDecoder: exception in peekStartElement, dumping offending object to file: " + tempFile.getAbsolutePath()); throw e; } catch (IOException ie) { Log.warning(Log.FAC_ENCODING, "IOException in BinaryXMLDecoder error handling: " + e.getMessage()); Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("peekStartElement", e); } } catch (IOException e) { Log.warning(Log.FAC_ENCODING, "IOException in BinaryXMLDecoder peekStartElementAsLong: " + e.getMessage()); Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("peekStartElement", e); } finally { try { _istream.reset(); } catch (IOException e) { Log.logStackTrace(Log.FAC_ENCODING, Level.WARNING, e); throw new ContentDecodingException("Cannot reset stream! " + e.getMessage(), e); } } return decodedTag; } public void readEndElement() throws ContentDecodingException { try { int next = _istream.read(); if (next != BinaryXMLCodec.XML_CLOSE) { throw new ContentDecodingException("Expected end element, got: " + next); } } catch (IOException e) { throw new ContentDecodingException(e); } } /** * Read a UString. Force this to consume the end element to match the * behavior on the text side. */ public String readUString() throws ContentDecodingException { try { String ustring = BinaryXMLCodec.decodeUString(_istream); readEndElement(); return ustring; } catch (IOException e) { throw new ContentDecodingException(e.getMessage(),e); } } /** * Read a BLOB. Force this to consume the end element to match the * behavior on the text side. */ public byte [] readBlob() throws ContentDecodingException { try { byte [] blob = BinaryXMLCodec.decodeBlob(_istream); readEndElement(); return blob; } catch (IOException e) { throw new ContentDecodingException(e.getMessage(),e); } } public CCNTime readDateTime(String startTag) throws ContentDecodingException { byte [] byteTimestamp = readBinaryElement(startTag); CCNTime timestamp = new CCNTime(byteTimestamp); if (null == timestamp) { throw new ContentDecodingException("Cannot parse timestamp: " + DataUtils.printHexBytes(byteTimestamp)); } return timestamp; } public CCNTime readDateTime(long startTag) throws ContentDecodingException { byte [] byteTimestamp = readBinaryElement(startTag); CCNTime timestamp = new CCNTime(byteTimestamp); if (null == timestamp) { throw new ContentDecodingException("Cannot parse timestamp: " + DataUtils.printHexBytes(byteTimestamp)); } return timestamp; } }