Added BinaryXMLStructureDecoder. Use it in NDNSocketTransportService to detect the end of the input (instead of the hack to try to decode a ContentObject).
diff --git a/js/ccnxProtocol/modules/ndn-js.jsm b/js/ccnxProtocol/modules/ndn-js.jsm
index a336031..e5f2837 100644
--- a/js/ccnxProtocol/modules/ndn-js.jsm
+++ b/js/ccnxProtocol/modules/ndn-js.jsm
@@ -220,6 +220,7 @@
var inStream = transport.openInputStream(0, 0, 0);
var dataListener = {
data: [],
+ structureDecoder: new BinaryXMLStructureDecoder(),
calledOnReceivedData: false,
debugNOnDataAvailable: 0,
@@ -234,28 +235,21 @@
}
},
onDataAvailable: function (request, context, _inputStream, offset, count) {
+ if (this.calledOnReceivedData)
+ // Already finished. Ignore extra data.
+ return;
+
try {
this.debugNOnDataAvailable += 1;
// Ignore _inputStream and use inStream.
// Use readInputStreamToString to handle binary data.
var rawData = NetUtil.readInputStreamToString(inStream, count);
- // Append to this.data.
this.data = this.data.concat(DataUtils.toNumbersFromString(rawData));
- // TODO: Need to parse the input to check if a whole ccnb object has been read, as in
- // CcnbObjectReader class: https://github.com/NDN-Routing/NDNLP/blob/master/ndnld.h#L256 .
- // For now as a hack, try to fully decode this.data as a ContentObject.
- try {
- var decoder = new BinaryXMLDecoder(this.data);
- var co = new ContentObject();
- co.from_ccnb(decoder);
- } catch (ex) {
- // Assume the exception is because the decoder only got partial data, so read moe.
- dump("Awaiting more data at onDataAvailable call # " + this.debugNOnDataAvailable);
- return;
- }
- // We were able to parse the ContentObject, so finish.
- this.onStopRequest();
+ // Scan the input to check if a whole ccnb object has been read.
+ if (this.structureDecoder.findElementEnd(this.data))
+ // Finish.
+ this.onStopRequest();
} catch (ex) {
dump("onDataAvailable exception: " + ex + "\n");
}
@@ -2101,7 +2095,7 @@
ForwardingEntry.prototype.getElementLabel = function() { return CCNProtocolDTags.ForwardingEntry; }
/*
- * This class is used to encode and decode binary elements ( blog, type/value pairs)
+ * This class is used to encode ccnb binary elements (blob, type/value pairs).
*
* @author: ucla-cs
* See COPYING for copyright and distribution information.
@@ -2545,7 +2539,7 @@
/*
- * This class is used to encode and decode binary elements ( blog, type/value pairs)
+ * This class is used to decode ccnb binary elements (blob, type/value pairs).
*
* @author: ucla-cs
* See COPYING for copyright and distribution information.
@@ -2617,49 +2611,9 @@
var DEBUG_MAX_LEN = 32768;
this.istream = istream;
- //console.log('istream is '+ this.istream);
this.offset = 0;
};
-
-BinaryXMLDecoder.prototype.readStartElement =function(
- //String
- startTag,
- //TreeMap<String, String>
- attributes) {
-
- try {
- //this.TypeAndVal
- tv = this.decodeTypeAndVal(this.istream);
-
- if (null == tv) {
- throw new Error("Expected start element: " + startTag + " got something not a tag.");
- }
-
- //String
- decodedTag = null;
-
- if (tv.type() == XML_TAG) {
-
- decodedTag = this.decodeUString(this.Istream, tv.val()+1);
-
- } else if (tv.type() == XML_DTAG) {
- decodedTag = tagToString(tv.val());
- }
-
- if ((null == decodedTag) || decodedTag != startTag) {
- throw new Error("Expected start element: " + startTag + " got: " + decodedTag + "(" + tv.val() + ")");
- }
-
- if (null != attributes) {
- readAttributes(attributes);
- }
-
- } catch (e) {
- throw new Error("readStartElement", e);
- }
-};
-
BinaryXMLDecoder.prototype.readAttributes = function(
//TreeMap<String,String>
attributes){
@@ -2671,18 +2625,18 @@
try {
//this.TypeAndVal
- nextTV = this.peekTypeAndVal(this.istream);
+ nextTV = this.peekTypeAndVal();
while ((null != nextTV) && ((XML_ATTR == nextTV.type()) ||
(XML_DATTR == nextTV.type()))) {
//this.TypeAndVal
- thisTV = this.decodeTypeAndVal(this.Istream);
+ thisTV = this.decodeTypeAndVal();
var attributeName = null;
if (XML_ATTR == thisTV.type()) {
- attributeName = this.decodeUString(this.istream, thisTV.val()+1);
+ attributeName = this.decodeUString(thisTV.val()+1);
} else if (XML_DATTR == thisTV.type()) {
// DKS TODO are attributes same or different dictionary?
@@ -2692,11 +2646,11 @@
}
}
- var attributeValue = this.decodeUString(this.istream);
+ var attributeValue = this.decodeUString();
attributes.put(attributeName, attributeValue);
- nextTV = this.peekTypeAndVal(this.istream);
+ nextTV = this.peekTypeAndVal();
}
} catch ( e) {
@@ -2733,7 +2687,7 @@
//try {
//TypeAndVal
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
if (null == tv) {
throw new Error("Expected start element: " + startTag + " got something not a tag.");
@@ -2758,7 +2712,7 @@
//console.log('valval is ' +valval);
- decodedTag = this.decodeUString(this.istream, valval);
+ decodedTag = this.decodeUString(valval);
} else if (tv.type() == XML_DTAG) {
//console.log('gothere');
@@ -2802,14 +2756,14 @@
try {
// Now need to get attributes.
//TypeAndVal
- nextTV = this.peekTypeAndVal(this.istream);
+ nextTV = this.peekTypeAndVal();
while ((null != nextTV) && ((XML_ATTR == nextTV.type()) ||
(XML_DATTR == nextTV.type()))) {
// Decode this attribute. First, really read the type and value.
//this.TypeAndVal
- thisTV = this.decodeTypeAndVal(this.istream);
+ thisTV = this.decodeTypeAndVal();
//String
attributeName = null;
@@ -2822,7 +2776,7 @@
else
valval = (tv.val())+ 1;
- attributeName = this.decodeUString(this.istream,valval);
+ attributeName = this.decodeUString(valval);
} else if (XML_DATTR == thisTV.type()) {
// DKS TODO are attributes same or different dictionary?
@@ -2833,12 +2787,12 @@
}
// Attribute values are always UDATA
//String
- attributeValue = this.decodeUString(this.istream);
+ attributeValue = this.decodeUString();
//
attributes.push([attributeName, attributeValue]);
- nextTV = this.peekTypeAndVal(this.istream);
+ nextTV = this.peekTypeAndVal();
}
} catch ( e) {
@@ -2858,7 +2812,7 @@
// Have to distinguish genuine errors from wrong tags. Could either use
// a special exception subtype, or redo the work here.
//this.TypeAndVal
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
if (null != tv) {
@@ -2875,7 +2829,7 @@
else
valval = (tv.val())+ 1;
- decodedTag = this.decodeUString(this.istream, valval);
+ decodedTag = this.decodeUString(valval);
//Log.info(Log.FAC_ENCODING, "Unexpected: got text tag in peekStartElement; length: " + valval + " decoded tag = " + decodedTag);
@@ -2934,7 +2888,7 @@
// Have to distinguish genuine errors from wrong tags. Could either use
// a special exception subtype, or redo the work here.
//this.TypeAndVal
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
if (null != tv) {
@@ -2952,7 +2906,7 @@
// Tag value represents length-1 as tags can never be empty.
//String
- strTag = this.decodeUString(this.istream, valval);
+ strTag = this.decodeUString(valval);
decodedTag = stringToTag(strTag);
@@ -3022,7 +2976,7 @@
//String
BinaryXMLDecoder.prototype.readUString = function(){
//String
- ustring = this.decodeUString(this.istream);
+ ustring = this.decodeUString();
this.readEndElement();
return ustring;
@@ -3033,7 +2987,7 @@
BinaryXMLDecoder.prototype.readBlob = function() {
//byte []
- blob = this.decodeBlob(this.istream);
+ blob = this.decodeBlob();
this.readEndElement();
return blob;
@@ -3073,9 +3027,7 @@
return timestamp;
};
-BinaryXMLDecoder.prototype.decodeTypeAndVal = function(
- /*InputStream*/
- istream) {
+BinaryXMLDecoder.prototype.decodeTypeAndVal = function() {
/*int*/next;
/*int*/type = -1;
@@ -3123,20 +3075,18 @@
//TypeAndVal
-BinaryXMLDecoder.peekTypeAndVal = function(
- //InputStream
- istream) {
+BinaryXMLDecoder.peekTypeAndVal = function() {
//TypeAndVal
tv = null;
- //istream.mark(LONG_BYTES*2);
+ //this.istream.mark(LONG_BYTES*2);
var previousOffset = this.offset;
try {
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
} finally {
- //istream.reset();
+ //this.istream.reset();
this.offset = previousOffset;
}
@@ -3146,15 +3096,13 @@
//byte[]
BinaryXMLDecoder.prototype.decodeBlob = function(
- //InputStream
- istream,
//int
blobLength) {
if(null == blobLength){
//TypeAndVal
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
var valval ;
@@ -3165,7 +3113,7 @@
valval = (tv.val());
//console.log('valval here is ' + valval);
- return this.decodeBlob(this.istream, valval);
+ return this.decodeBlob(valval);
}
//
@@ -3185,8 +3133,6 @@
//String
BinaryXMLDecoder.prototype.decodeUString = function(
- //InputStream
- istream,
//int
byteLength) {
@@ -3196,7 +3142,7 @@
count++;
if(null == byteLength|| undefined == byteLength){
console.log("!!!!");
- tv = this.decodeTypeAndVal(this.istream);
+ tv = this.decodeTypeAndVal();
var valval ;
if(typeof tv.val() == 'string'){
valval = (parseInt(tv.val()));
@@ -3205,7 +3151,7 @@
valval = (tv.val());
if(LOG>4) console.log('valval is ' + valval);
- byteLength= this.decodeUString(this.istream, valval);
+ byteLength= this.decodeUString(valval);
//if(LOG>4) console.log('byte Length found in type val is '+ byteLength.charCodeAt(0));
byteLength = parseInt(byteLength);
@@ -3217,7 +3163,7 @@
if(LOG>4)console.log('byteLength is '+byteLength);
if(LOG>4)console.log('type of byteLength is '+typeof byteLength);
- stringBytes = this.decodeBlob(this.istream, byteLength);
+ stringBytes = this.decodeBlob(byteLength);
//console.log('String bytes are '+ stringBytes);
//console.log('stringBytes);
@@ -3249,7 +3195,7 @@
var tempStreamPosition = this.offset;
//TypeAndVal
- tv = this.decodeTypeAndVal(istream);
+ tv = this.decodeTypeAndVal();
if(LOG>3)console.log('TV is '+tv);
if(LOG>3)console.log(tv);
@@ -3265,11 +3211,11 @@
return "";
}
- return this.decodeUString(istream, tv.val());
+ return this.decodeUString(tv.val());
}
else{
//byte []
- stringBytes = this.decodeBlob(istream, byteLength);
+ stringBytes = this.decodeBlob(byteLength);
//return DataUtils.getUTF8StringFromBytes(stringBytes);
return DataUtils.toString(stringBytes);
@@ -3334,6 +3280,137 @@
return strElementText;
};
+/*
+ * Set the offset into the input, used for the next read.
+ */
+BinaryXMLDecoder.prototype.seek = function(
+ //int
+ offset) {
+ this.offset = offset;
+}
+
+/*
+ * This class uses BinaryXMLDecoder to follow the structure of a ccnb binary element to
+ * determine its end.
+ *
+ * @author: ucla-cs
+ * See COPYING for copyright and distribution information.
+ */
+
+var BinaryXMLStructureDecoder = function BinaryXMLDecoder() {
+ this.gotElementEnd = false;
+ this.offset = 0;
+ this.level = 0;
+ this.state = BinaryXMLStructureDecoder.READ_HEADER_OR_CLOSE;
+ this.headerStartOffset = 0;
+ this.readBytesEndOffset = 0;
+};
+
+BinaryXMLStructureDecoder.READ_HEADER_OR_CLOSE = 0;
+BinaryXMLStructureDecoder.READ_BYTES = 1;
+
+/*
+ * Continue scanning input starting from this.offset. If found the end of the element
+ * which started at offset 0 then return true, else false.
+ * If this returns false, you should read more into input and call again.
+ * You have to pass in input each time because the array could be reallocated.
+ * This throws an exception for badly formed ccnb.
+ */
+BinaryXMLStructureDecoder.prototype.findElementEnd = function(
+ // byte array
+ input)
+{
+ if (this.gotElementEnd)
+ // Someone is calling when we already got the end.
+ return true;
+
+ var decoder = new BinaryXMLDecoder(input);
+
+ while (true) {
+ if (this.offset >= input.length)
+ // All the cases assume we have some input.
+ return false;
+
+ switch (this.state) {
+ case BinaryXMLStructureDecoder.READ_HEADER_OR_CLOSE:
+ // First check for XML_CLOSE.
+ if (this.offset == this.headerStartOffset && input[this.offset] == XML_CLOSE) {
+ ++this.offset;
+ // Close the level.
+ --this.level;
+ if (this.level == 0)
+ // Finished.
+ return true;
+ if (this.level < 0)
+ throw new Error("BinaryXMLStructureDecoder: Unexepected close tag at offset " +
+ (this.offset - 1));
+
+ // Get ready for the next header.
+ this.headerStartOffset = this.offset;
+ break;
+ }
+
+ while (true) {
+ if (this.offset >= input.length)
+ return false;
+ if (input[this.offset++] & XML_TT_NO_MORE)
+ // Break and read the header.
+ break;
+ }
+
+ decoder.seek(this.headerStartOffset);
+ var typeAndVal = decoder.decodeTypeAndVal();
+ if (typeAndVal == null)
+ throw new Error("BinaryXMLStructureDecoder: Can't read header starting at offset " +
+ this.headerStartOffset);
+
+ // Set the next state based on the type.
+ var type = typeAndVal.t;
+ if (type == XML_DATTR)
+ // We already consumed the item. READ_HEADER_OR_CLOSE again.
+ // ccnb has rules about what must follow an attribute, but we are just scanning.
+ this.headerStartOffset = this.offset;
+ else if (type == XML_DTAG || type == XML_EXT) {
+ // Start a new level and READ_HEADER_OR_CLOSE again.
+ ++this.level;
+ this.headerStartOffset = this.offset;
+ }
+ else if (type == XML_TAG || type == XML_ATTR) {
+ if (type == XML_TAG)
+ // Start a new level and read the tag.
+ ++this.level;
+ // Minimum tag or attribute length is 1.
+ this.readBytesEndOffset = this.offset + typeAndVal.v + 1;
+ this.state = BinaryXMLStructureDecoder.READ_BYTES;
+ // ccnb has rules about what must follow an attribute, but we are just scanning.
+ }
+ else if (type == XML_BLOB || type == XML_UDATA) {
+ this.readBytesEndOffset = this.offset + typeAndVal.v;
+ this.state = BinaryXMLStructureDecoder.READ_BYTES;
+ }
+ else
+ throw new Error("BinaryXMLStructureDecoder: Unrecognized header type " + type);
+ break;
+
+ case BinaryXMLStructureDecoder.READ_BYTES:
+ if (input.length < this.readBytesEndOffset) {
+ // Need more.
+ this.offset = input.length;
+ return false;
+ }
+ // Got the bytes. Read a new header or close.
+ this.offset = this.readBytesEndOffset;
+ this.headerStartOffset = this.offset;
+ this.state = BinaryXMLStructureDecoder.READ_HEADER_OR_CLOSE;
+ break;
+
+ default:
+ // We don't expect this to happen.
+ throw new Error("BinaryXMLStructureDecoder: Unrecognized state " + this.state);
+ }
+ }
+};
+
/*
* This class contains utilities to help parse the data
* author: ucla-cs