diff --git a/chrome/content/Cbor.js b/chrome/content/Cbor.js new file mode 100644 index 0000000..6051e1a --- /dev/null +++ b/chrome/content/Cbor.js @@ -0,0 +1,404 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 Patrick Gansterer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +(function(global, undefined) { "use strict"; +var POW_2_24 = Math.pow(2, -24), + POW_2_32 = Math.pow(2, 32), + POW_2_53 = Math.pow(2, 53); + +function encode(value) { + var data = new ArrayBuffer(256); + var dataView = new DataView(data); + var lastLength; + var offset = 0; + + function ensureSpace(length) { + var newByteLength = data.byteLength; + var requiredLength = offset + length; + while (newByteLength < requiredLength) + newByteLength *= 2; + if (newByteLength !== data.byteLength) { + var oldDataView = dataView; + data = new ArrayBuffer(newByteLength); + dataView = new DataView(data); + var uint32count = (offset + 3) >> 2; + for (var i = 0; i < uint32count; ++i) + dataView.setUint32(i * 4, oldDataView.getUint32(i * 4)); + } + + lastLength = length; + return dataView; + } + function write() { + offset += lastLength; + } + function writeFloat64(value) { + write(ensureSpace(8).setFloat64(offset, value)); + } + function writeUint8(value) { + write(ensureSpace(1).setUint8(offset, value)); + } + function writeUint8Array(value) { + var dataView = ensureSpace(value.length); + for (var i = 0; i < value.length; ++i) + dataView.setUint8(offset + i, value[i]); + write(); + } + function writeUint16(value) { + write(ensureSpace(2).setUint16(offset, value)); + } + function writeUint32(value) { + write(ensureSpace(4).setUint32(offset, value)); + } + function writeUint64(value) { + var low = value % POW_2_32; + var high = (value - low) / POW_2_32; + var dataView = ensureSpace(8); + dataView.setUint32(offset, high); + dataView.setUint32(offset + 4, low); + write(); + } + function writeTypeAndLength(type, length) { + if (length < 24) { + writeUint8(type << 5 | length); + } else if (length < 0x100) { + writeUint8(type << 5 | 24); + writeUint8(length); + } else if (length < 0x10000) { + writeUint8(type << 5 | 25); + writeUint16(length); + } else if (length < 0x100000000) { + writeUint8(type << 5 | 26); + writeUint32(length); + } else { + writeUint8(type << 5 | 27); + writeUint64(length); + } + } + + function encodeItem(value) { + var i; + + if (value === false) + return writeUint8(0xf4); + if (value === true) + return writeUint8(0xf5); + if (value === null) + return writeUint8(0xf6); + if (value === undefined) + return writeUint8(0xf7); + + switch (typeof value) { + case "number": + if (Math.floor(value) === value) { + if (0 <= value && value <= POW_2_53) + return writeTypeAndLength(0, value); + if (-POW_2_53 <= value && value < 0) + return writeTypeAndLength(1, -(value + 1)); + } + writeUint8(0xfb); + return writeFloat64(value); + + case "string": + var utf8data = []; + for (i = 0; i < value.length; ++i) { + var charCode = value.charCodeAt(i); + if (charCode < 0x80) { + utf8data.push(charCode); + } else if (charCode < 0x800) { + utf8data.push(0xc0 | charCode >> 6); + utf8data.push(0x80 | charCode & 0x3f); + } else if (charCode < 0xd800) { + utf8data.push(0xe0 | charCode >> 12); + utf8data.push(0x80 | (charCode >> 6) & 0x3f); + utf8data.push(0x80 | charCode & 0x3f); + } else { + charCode = (charCode & 0x3ff) << 10; + charCode |= value.charCodeAt(++i) & 0x3ff; + charCode += 0x10000; + + utf8data.push(0xf0 | charCode >> 18); + utf8data.push(0x80 | (charCode >> 12) & 0x3f); + utf8data.push(0x80 | (charCode >> 6) & 0x3f); + utf8data.push(0x80 | charCode & 0x3f); + } + } + + writeTypeAndLength(3, utf8data.length); + return writeUint8Array(utf8data); + + default: + var length; + if (Array.isArray(value)) { + length = value.length; + writeTypeAndLength(4, length); + for (i = 0; i < length; ++i) + encodeItem(value[i]); + } else if (value instanceof Uint8Array) { + writeTypeAndLength(2, value.length); + writeUint8Array(value); + } else { + var keys = Object.keys(value); + length = keys.length; + writeTypeAndLength(5, length); + for (i = 0; i < length; ++i) { + var key = keys[i]; + encodeItem(key); + encodeItem(value[key]); + } + } + } + } + + encodeItem(value); + + if ("slice" in data) + return data.slice(0, offset); + + var ret = new ArrayBuffer(offset); + var retView = new DataView(ret); + for (var i = 0; i < offset; ++i) + retView.setUint8(i, dataView.getUint8(i)); + return ret; +} + +function decode(data, tagger, simpleValue) { + var dataView = new DataView(data); + var offset = 0; + + if (typeof tagger !== "function") + tagger = function(value) { return value; }; + if (typeof simpleValue !== "function") + simpleValue = function() { return undefined; }; + + function read(value, length) { + offset += length; + return value; + } + function readArrayBuffer(length) { + return read(new Uint8Array(data, offset, length), length); + } + function readFloat16() { + var tempArrayBuffer = new ArrayBuffer(4); + var tempDataView = new DataView(tempArrayBuffer); + var value = readUint16(); + + var sign = value & 0x8000; + var exponent = value & 0x7c00; + var fraction = value & 0x03ff; + + if (exponent === 0x7c00) + exponent = 0xff << 10; + else if (exponent !== 0) + exponent += (127 - 15) << 10; + else if (fraction !== 0) + return fraction * POW_2_24; + + tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13); + return tempDataView.getFloat32(0); + } + function readFloat32() { + return read(dataView.getFloat32(offset), 4); + } + function readFloat64() { + return read(dataView.getFloat64(offset), 8); + } + function readUint8() { + return read(dataView.getUint8(offset), 1); + } + function readUint16() { + return read(dataView.getUint16(offset), 2); + } + function readUint32() { + return read(dataView.getUint32(offset), 4); + } + function readUint64() { + return readUint32() * POW_2_32 + readUint32(); + } + function readBreak() { + if (dataView.getUint8(offset) !== 0xff) + return false; + offset += 1; + return true; + } + function readLength(additionalInformation) { + if (additionalInformation < 24) + return additionalInformation; + if (additionalInformation === 24) + return readUint8(); + if (additionalInformation === 25) + return readUint16(); + if (additionalInformation === 26) + return readUint32(); + if (additionalInformation === 27) + return readUint64(); + if (additionalInformation === 31) + return -1; + throw "Invalid length encoding"; + } + function readIndefiniteStringLength(majorType) { + var initialByte = readUint8(); + if (initialByte === 0xff) + return -1; + var length = readLength(initialByte & 0x1f); + if (length < 0 || (initialByte >> 5) !== majorType) + throw "Invalid indefinite length element"; + return length; + } + + function appendUtf16data(utf16data, length) { + for (var i = 0; i < length; ++i) { + var value = readUint8(); + if (value & 0x80) { + if (value < 0xe0) { + value = (value & 0x1f) << 6 + | (readUint8() & 0x3f); + length -= 1; + } else if (value < 0xf0) { + value = (value & 0x0f) << 12 + | (readUint8() & 0x3f) << 6 + | (readUint8() & 0x3f); + length -= 2; + } else { + value = (value & 0x0f) << 18 + | (readUint8() & 0x3f) << 12 + | (readUint8() & 0x3f) << 6 + | (readUint8() & 0x3f); + length -= 3; + } + } + + if (value < 0x10000) { + utf16data.push(value); + } else { + value -= 0x10000; + utf16data.push(0xd800 | (value >> 10)); + utf16data.push(0xdc00 | (value & 0x3ff)); + } + } + } + + function decodeItem() { + var initialByte = readUint8(); + var majorType = initialByte >> 5; + var additionalInformation = initialByte & 0x1f; + var i; + var length; + + if (majorType === 7) { + switch (additionalInformation) { + case 25: + return readFloat16(); + case 26: + return readFloat32(); + case 27: + return readFloat64(); + } + } + + length = readLength(additionalInformation); + if (length < 0 && (majorType < 2 || 6 < majorType)) + throw "Invalid length"; + + switch (majorType) { + case 0: + return length; + case 1: + return -1 - length; + case 2: + if (length < 0) { + var elements = []; + var fullArrayLength = 0; + while ((length = readIndefiniteStringLength(majorType)) >= 0) { + fullArrayLength += length; + elements.push(readArrayBuffer(length)); + } + var fullArray = new Uint8Array(fullArrayLength); + var fullArrayOffset = 0; + for (i = 0; i < elements.length; ++i) { + fullArray.set(elements[i], fullArrayOffset); + fullArrayOffset += elements[i].length; + } + return fullArray; + } + return readArrayBuffer(length); + case 3: + var utf16data = []; + if (length < 0) { + while ((length = readIndefiniteStringLength(majorType)) >= 0) + appendUtf16data(utf16data, length); + } else + appendUtf16data(utf16data, length); + return String.fromCharCode.apply(null, utf16data); + case 4: + var retArray; + if (length < 0) { + retArray = []; + while (!readBreak()) + retArray.push(decodeItem()); + } else { + retArray = new Array(length); + for (i = 0; i < length; ++i) + retArray[i] = decodeItem(); + } + return retArray; + case 5: + var retObject = {}; + for (i = 0; i < length || length < 0 && !readBreak(); ++i) { + var key = decodeItem(); + retObject[key] = decodeItem(); + } + return retObject; + case 6: + return tagger(decodeItem(), length); + case 7: + switch (length) { + case 20: + return false; + case 21: + return true; + case 22: + return null; + case 23: + return undefined; + default: + return simpleValue(length); + } + } + } + + var ret = decodeItem(); + if (offset !== data.byteLength) + throw "Remaining bytes"; + return ret; +} + +var obj = { encode: encode, decode: decode }; + +if (typeof define === "function" && define.amd) + define("cbor/cbor", obj); +else if (!global.CBOR) + global.cbor = obj; + +})(Copper); diff --git a/chrome/content/Handlers.js b/chrome/content/Handlers.js index 0f1705f..ce02901 100644 --- a/chrome/content/Handlers.js +++ b/chrome/content/Handlers.js @@ -54,9 +54,14 @@ Copper.defaultHandler = function(message) { if (message.getRTT) Copper.updateLabel('info_code', ' (RTT ' + message.getRTT() + ' ms)', true); - if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT) { + if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT) { Copper.updateResourceLinks( Copper.parseLinkFormat( document.getElementById('packet_payload').value ) ); - } + } else if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_CBOR && + (Copper.WELL_KNOWN_RESOURCES.indexOf(message.getOption(Copper.OPTION_URI_PATH)) > -1)) { + // FIXME: investigate why the code below triggers a parsing error +// Copper.updateResourceLinks( Copper.parseLinkFormat( Copper.parseCBORFormat(document.getElementById('packet_payload').value ) ) ); + } + }; //Handle ping responses @@ -182,53 +187,74 @@ Copper.observingHandler = function(message) { }; // Handle messages with link format payload -Copper.discoverCache = new String(); -Copper.discoverHandler = function(message) { - - Copper.logEvent('INFO: discoverHandler()'); - - if (message.getCode()!=Copper.CODE_2_05_CONTENT) return; - - if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT) { - - Copper.updateLabel('info_code', 'Discovering'); - - if (message.isOption(Copper.OPTION_BLOCK2)) { - - if (message.getBlock2More()) { +Copper.discoverCache = {}; +Copper.discoverHandler = function(message, stoppedListening) { - // block size negotiation - let size = Copper.negotiateBlockSize(message); - let offset = message.getBlock2Offset(); - let num = offset/size; - Copper.discover(num, size); - } - - if (message.getBlock2Number()==0) { - Copper.logEvent('INFO: Starting new discover cache'); - Copper.discoverCache = new String(); - } - - Copper.discoverCache += Copper.bytes2str( message.getPayload() ); - - if (!message.getBlock2More()) { - Copper.logEvent('INFO: Appending discover cache'); - // link-format - Copper.resourcesCached = false; - Copper.updateResourceLinks( Copper.parseLinkFormat( Copper.discoverCache ) ); + Copper.logEvent('INFO: discoverHandler()'); - document.getElementById('toolbar_discover').image = 'chrome://copper/skin/tool_discover.png'; - } - } else { - // link-format - Copper.resourcesCached = false; - Copper.updateResourceLinks( Copper.parseLinkFormat( Copper.bytes2str( message.getPayload() ) ) ); + if (message.getCode()!=Copper.CODE_2_05_CONTENT) return; - document.getElementById('toolbar_discover').image = 'chrome://copper/skin/tool_discover.png'; - } - } else { - Copper.logWarning(new Error("Discovery requires 'application/link-format', but received "+message.getContentFormat())); - } + let domain = "["+message.from.addrress+"]:"+ message.from.port; + + Copper.logEvent('INFO: discovery of ' + message.getOption(Copper.OPTION_URI_PATH) ); + + if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT + || (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_CBOR) ) { + + Copper.updateLabel('info_code', 'Discovering'); + + if (message.isOption(Copper.OPTION_BLOCK2)) { + + if (message.getBlock2Number()==0) { + Copper.logEvent('INFO: Starting new discover cache'); + Copper.discoverCache[domain] = {}; + Copper.discoverCache[domain].payloads = message.getPayload(); + Copper.discoverCache[domain].from = message.from; + } else { + Copper.logEvent('INFO: Appending '+message.getPayload().length +' to discover cache ' + Copper.discoverCache[domain].payloads.length); + Copper.discoverCache[domain].payloads = Copper.discoverCache[domain].payloads.concat( message.getPayload() ); + Copper.logEvent('INFO: discover cache is now ' + Copper.discoverCache[domain].payloads.length); + } + + if (message.getBlock2More()) { + // block size negotiation + let size = Copper.negotiateBlockSize(message); + let offset = message.getBlock2Offset(); + let num = offset/size; + Copper.startDiscovery(message.from, num, size); + } else { + // got all blocks + Copper.resourcesCached = false; + if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT) { + // link-format + Copper.discoverCache[domain].payloads = Copper.bytes2str(Copper.discoverCache[domain].payloads); + } else { + // oic link format + Copper.discoverCache[domain].payloads = Copper.parseCBORFormat( Copper.discoverCache[domain].payloads ); + } + Copper.updateResourceLinks( Copper.parseLinkFormat( Copper.discoverCache[domain]) ); + } + } else { + // link-format + Copper.resourcesCached = false; + Copper.discoverCache[domain] = {}; + Copper.discoverCache[domain].from = message.from; + if (message.getContentFormat()==Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT) { + Copper.discoverCache[domain].payloads = Copper.bytes2str( message.getPayload() ); + } else { + Copper.discoverCache[domain].payloads = Copper.parseCBORFormat(message.getPayload()); + } + Copper.updateResourceLinks( Copper.parseLinkFormat( Copper.discoverCache[domain] ) ); + } + if(stoppedListening) { + document.getElementById('toolbar_discover').image = 'chrome://copper/skin/tool_discover.png'; + Copper.displayPayload(message); + } + Copper.displayMessageInfo(message); + + } else { + Copper.logWarning(new Error("Discovery requires 'application/link-format', but received "+message.getContentFormat())); + } }; Copper.errorHandler = function(message) { diff --git a/chrome/content/Helpers.js b/chrome/content/Helpers.js index 22eec77..f3c840a 100644 --- a/chrome/content/Helpers.js +++ b/chrome/content/Helpers.js @@ -47,6 +47,7 @@ Copper.getRequestType = function() { Copper.loadBehavior = function() { Copper.behavior.requests = Copper.prefManager.getCharPref('extensions.copper.behavior.requests'); Copper.behavior.retransmissions = Copper.prefManager.getBoolPref('extensions.copper.behavior.retransmissions'); + Copper.behavior.oic = Copper.prefManager.getBoolPref('extensions.copper.behavior.oic'); Copper.behavior.sendDuplicates = Copper.prefManager.getBoolPref('extensions.copper.behavior.send-duplicates'); Copper.behavior.showUnknown = Copper.prefManager.getBoolPref('extensions.copper.behavior.show-unknown'); Copper.behavior.rejectUnknown = Copper.prefManager.getBoolPref('extensions.copper.behavior.reject-unknown'); @@ -62,6 +63,7 @@ Copper.loadBehavior = function() { // sync XUL menu with behavior object Copper.updateBehavior = function() { document.getElementById('menu_behavior_requests_' + Copper.behavior.requests).setAttribute('checked', 'true'); + document.getElementById('menu_behavior_oic').setAttribute('checked', Copper.behavior.oic); document.getElementById('menu_behavior_retransmissions').setAttribute('checked', Copper.behavior.retransmissions); document.getElementById('menu_behavior_send_duplicates').setAttribute('checked', Copper.behavior.sendDuplicates); document.getElementById('menu_behavior_show_unknown').setAttribute('checked', Copper.behavior.showUnknown); @@ -78,6 +80,10 @@ Copper.updateBehavior = function() { Copper.behaviorUpdate = function(target) { if (target.id.substr(0,22)=='menu_behavior_requests') { Copper.behavior.requests = target.value; + } else if (target.id=='menu_behavior_oic') { + Copper.behavior.oic = target.getAttribute('checked')=='true'; + Copper.resources = new Object(); + Copper.updateResourceLinks(); } else if (target.id=='menu_behavior_retransmissions') { Copper.behavior.retransmissions = target.getAttribute('checked')=='true'; Copper.endpoint.setRetransmissions(Copper.behavior.retransmissions); @@ -113,6 +119,7 @@ Copper.behaviorUpdate = function(target) { Copper.saveBehavior = function() { Copper.prefManager.setCharPref('extensions.copper.behavior.requests', Copper.behavior.requests); Copper.prefManager.setBoolPref('extensions.copper.behavior.retransmissions', Copper.behavior.retransmissions); + Copper.prefManager.setBoolPref('extensions.copper.behavior.oic', Copper.behavior.oic); Copper.prefManager.setBoolPref('extensions.copper.behavior.send-duplicates', Copper.behavior.sendDuplicates); Copper.prefManager.setBoolPref('extensions.copper.behavior.show-unknown', Copper.behavior.showUnknown); Copper.prefManager.setBoolPref('extensions.copper.behavior.reject-unknown', Copper.behavior.rejectUnknown); @@ -331,10 +338,69 @@ Copper.checkUri = function(uri, caller) { } }; -Copper.parseLinkFormat = function(data) { - - var links = new Object(); +Copper.parseCBORFormat = function(data) { + let abs = data.length; + let ab = new ArrayBuffer(abs); + let abv = new DataView(ab); + for(var i=0; i < abs; i++) + abv.setUint8(i, data[i]); + return Copper.cbor.decode(ab); +}; + +Copper.createCBORFormat = function (obj) { + let ab = Copper.cbor.encode(obj); + let abv = new DataView(ab); + let abs = abv.byteLength; + let a = new Array(abs); + for(var i=0; i < abs; i++) + a[i] = abv.getUint8(i, abv[i]); + return a; +} + +Copper.parseLinkFormat = function(cache) { + // This should really be based on rt=oic.rt.res + // but iotivity doesn't set this yet. + let data = cache.payloads; + let from = cache.from; + if ( data instanceof Array ) { + var baseURL = "coap://"+ from.address + ':' + from.port; + var links = new Object(); + for (var i = 0; i < data.length; i++) { + if((data[i].links) instanceof Array) + { + Copper.parseOICLinkFormat(data[i], baseURL, links); + } + } + delete data[0]['links']; + links[baseURL + Copper.WELL_KNOWN_RESOURCES] = data[0]; + return links; + } + return Copper.parseRawLinkFormat (data); + +}; + +Copper.parseOICLinkFormat = function(data, baseURL, olinks) { + var elm = data.links; + + // add all links + for (var i = 0; i]+>\s*(;\s*\w+\s*(=\s*(\w+|"([^"\\]*(\\.[^"\\]*)*)")\s*)?)*)/g); Copper.logEvent('-parsing link-format----------------------------'); @@ -391,6 +457,21 @@ Copper.parseLinkFormat = function(data) { return links; }; +Copper.stringifyReplacer = function(key, value) { + var ua = this[key]; + if(value instanceof Uint8Array) { + // Copper.logEvent('replacestringify ' + (typeof value) + ' ['+key+'] = ' + value); + var h=''; + for (var i = 0; i - */ - -// Constants -//////////////////////////////////////////////////////////////////////////////// - -Copper.__defineGetter__("VERSION", function() { return 1; }); -Copper.__defineGetter__("DEFAULT_PORT", function() { return 5683; }); -Copper.__defineGetter__("RESPONSE_TIMEOUT", function() { return 2000; }); // ms -Copper.__defineGetter__("RESPONSE_RANDOM_FACTOR", function() { return 1.5; }); // ms -Copper.__defineGetter__("MAX_RETRANSMIT", function() { return 4; }); -Copper.__defineGetter__("ETAG_LENGTH", function() { return 8; }); -Copper.__defineGetter__("TOKEN_LENGTH", function() { return 8; }); - -Copper.__defineGetter__("MSG_TYPE_CON", function() { return 0; }); -Copper.__defineGetter__("MSG_TYPE_NON", function() { return 1; }); -Copper.__defineGetter__("MSG_TYPE_ACK", function() { return 2; }); -Copper.__defineGetter__("MSG_TYPE_RST", function() { return 3; }); - -Copper.__defineGetter__("OPTION_IF_MATCH", function() { return 1; }); -Copper.__defineGetter__("OPTION_URI_HOST", function() { return 3; }); -Copper.__defineGetter__("OPTION_ETAG", function() { return 4; }); -Copper.__defineGetter__("OPTION_IF_NONE_MATCH", function() { return 5; }); -Copper.__defineGetter__("OPTION_URI_PORT", function() { return 7; }); -Copper.__defineGetter__("OPTION_LOCATION_PATH", function() { return 8; }); -Copper.__defineGetter__("OPTION_URI_PATH", function() { return 11; }); -Copper.__defineGetter__("OPTION_CONTENT_FORMAT", function() { return 12; }); -Copper.__defineGetter__("OPTION_MAX_AGE", function() { return 14; }); -Copper.__defineGetter__("OPTION_URI_QUERY", function() { return 15; }); -Copper.__defineGetter__("OPTION_ACCEPT", function() { return 17; }); -Copper.__defineGetter__("OPTION_LOCATION_QUERY", function() { return 20; }); -Copper.__defineGetter__("OPTION_PROXY_URI", function() { return 35; }); -Copper.__defineGetter__("OPTION_PROXY_SCHEME", function() { return 39; }); -Copper.__defineGetter__("OPTION_SIZE1", function() { return 60; }); - -Copper.__defineGetter__("OPTION_OBSERVE", function() { return 6; }); - -Copper.__defineGetter__("OPTION_BLOCK2", function() { return 23; }); -Copper.__defineGetter__("OPTION_BLOCK1", function() { return 27; }); -Copper.__defineGetter__("OPTION_SIZE2", function() { return 28; }); - -Copper.__defineGetter__("EMPTY", function() { return 0; }); - -Copper.__defineGetter__("GET", function() { return 1; }); -Copper.__defineGetter__("POST", function() { return 2; }); -Copper.__defineGetter__("PUT", function() { return 3; }); -Copper.__defineGetter__("DELETE", function() { return 4; }); - -Copper.__defineGetter__("CODE_2_01_CREATED", function() { return 65; }); -Copper.__defineGetter__("CODE_2_02_DELETED", function() { return 66; }); -Copper.__defineGetter__("CODE_2_03_VALID", function() { return 67; }); -Copper.__defineGetter__("CODE_2_04_CHANGED", function() { return 68; }); -Copper.__defineGetter__("CODE_2_05_CONTENT", function() { return 69; }); -Copper.__defineGetter__("CODE_2_31_CONTINUE", function() { return 95; }); - -Copper.__defineGetter__("CODE_4_00_BAD_REQUEST", function() { return 128; }); -Copper.__defineGetter__("CODE_4_01_UNAUTHORIZED", function() { return 129; }); -Copper.__defineGetter__("CODE_4_02_BAD_OPTION", function() { return 130; }); -Copper.__defineGetter__("CODE_4_03_FORBIDDEN", function() { return 131; }); -Copper.__defineGetter__("CODE_4_04_NOT_FOUND", function() { return 132; }); -Copper.__defineGetter__("CODE_4_05_METHOD_NOT_ALLOWED", function() { return 133; }); -Copper.__defineGetter__("CODE_4_06_NOT_ACCEPTABLE", function() { return 134; }); -Copper.__defineGetter__("CODE_4_08_REQUEST_ENTITY_INCOMPLETE", function() { return 136; }); -Copper.__defineGetter__("CODE_4_12_PRECONDITION_FAILED", function() { return 140; }); -Copper.__defineGetter__("CODE_4_13_REQUEST_ENTITY_TOO_LARGE", function() { return 141; }); -Copper.__defineGetter__("CODE_4_15_UNSUPPORTED_MEDIA_TYPE", function() { return 143; }); - -Copper.__defineGetter__("CODE_5_00_INTERNAL_SERVER_ERROR", function() { return 160; }); -Copper.__defineGetter__("CODE_5_01_NOT_IMPLEMENTED", function() { return 161; }); -Copper.__defineGetter__("CODE_5_02_BAD_GATEWAY", function() { return 162; }); -Copper.__defineGetter__("CODE_5_03_SERVICE_UNAVAILABLE", function() { return 163; }); -Copper.__defineGetter__("CODE_5_04_GATEWAY_TIMEOUT", function() { return 164; }); -Copper.__defineGetter__("CODE_5_05_PROXYING_NOT_SUPPORTED", function() { return 165; }); - -Copper.__defineGetter__("CONTENT_TYPE_TEXT_PLAIN", function() { return 0; }); -Copper.__defineGetter__("CONTENT_TYPE_TEXT_XML", function() { return 1; }); -Copper.__defineGetter__("CONTENT_TYPE_TEXT_CSV", function() { return 2; }); -Copper.__defineGetter__("CONTENT_TYPE_TEXT_HTML", function() { return 3; }); -Copper.__defineGetter__("CONTENT_TYPE_IMAGE_GIF", function() { return 21; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_IMAGE_JPEG", function() { return 22; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_IMAGE_PNG", function() { return 23; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_IMAGE_TIFF", function() { return 24; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_AUDIO_RAW", function() { return 25; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_VIDEO_RAW", function() { return 26; }); // 03 -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_LINK_FORMAT", function() { return 40; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_XML", function() { return 41; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_OCTET_STREAM", function() { return 42; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_RDF_XML", function() { return 43; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_SOAP_XML", function() { return 44; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_ATOM_XML", function() { return 45; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_XMPP_XML", function() { return 46; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_EXI", function() { return 47; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_FASTINFOSET", function() { return 48; }); // 04 -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_SOAP_FASTINFOSET", function() { return 49; }); // 04 -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_JSON", function() { return 50; }); // 04 -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_X_OBIX_BINARY", function() { return 51; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_CBOR", function() { return 60; }); // 04 -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TEXT", function() { return 1541; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TLV", function() { return 1542; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_JSON", function() { return 1543; }); -Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_OPAQUE", function() { return 1544; }); - -Copper.__defineGetter__("WELL_KNOWN_RESOURCES", function() { return '/.well-known/core'; }); - -//Registries -//////////////////////////////////////////////////////////////////////////////// - -Copper.getCodeName = function(code) { - switch (parseInt(code)) { - // empty - case 0: return 'EMPTY'; - // methods - case Copper.GET: return 'GET'; - case Copper.POST: return 'POST'; - case Copper.PUT: return 'PUT'; - case Copper.DELETE: return 'DELETE'; - // response codes - case Copper.CODE_2_01_CREATED: return '2.01 Created'; - case Copper.CODE_2_02_DELETED: return '2.02 Deleted'; - case Copper.CODE_2_03_VALID: return '2.03 Valid'; - case Copper.CODE_2_04_CHANGED: return '2.04 Changed'; - case Copper.CODE_2_05_CONTENT: return '2.05 Content'; - case Copper.CODE_2_31_CONTINUE: return '2.31 Continue'; - case Copper.CODE_4_00_BAD_REQUEST: return '4.00 Bad Request'; - case Copper.CODE_4_01_UNAUTHORIZED: return '4.01 Unauthorized'; - case Copper.CODE_4_02_BAD_OPTION: return '4.02 Bad Option'; - case Copper.CODE_4_03_FORBIDDEN: return '4.03 Forbidden'; - case Copper.CODE_4_04_NOT_FOUND: return '4.04 Not Found'; - case Copper.CODE_4_05_METHOD_NOT_ALLOWED: return '4.05 Method Not Allowed'; - case Copper.CODE_4_06_NOT_ACCEPTABLE: return '4.06 Not Acceptable'; - case Copper.CODE_4_08_REQUEST_ENTITY_INCOMPLETE: return '4.08 Request Entity Incomplete'; - case Copper.CODE_4_12_PRECONDITION_FAILED: return '4.12 Precondition Failed'; - case Copper.CODE_4_13_REQUEST_ENTITY_TOO_LARGE: return '4.13 Request Entity Too Large'; - case Copper.CODE_4_15_UNSUPPORTED_MEDIA_TYPE: return '4.15 Unsupported Content-Format'; - case Copper.CODE_5_00_INTERNAL_SERVER_ERROR: return '5.00 Internal Server Error'; - case Copper.CODE_5_01_NOT_IMPLEMENTED: return '5.01 Not Implemented'; - case Copper.CODE_5_02_BAD_GATEWAY: return '5.02 Bad Gateway'; - case Copper.CODE_5_03_SERVICE_UNAVAILABLE: return '5.03 Service Unavailable'; - case Copper.CODE_5_04_GATEWAY_TIMEOUT: return '5.04 Gateway Timeout'; - case Copper.CODE_5_05_PROXYING_NOT_SUPPORTED: return '5.05 Proxying Not Supported'; - // ... - default: return Math.floor(code/32)+'.'+(code % 32)+' Unknown by Copper'; - } -}; - -Copper.getOptionName = function(number) { - switch (parseInt(number)) { - case Copper.OPTION_CONTENT_FORMAT: return 'Content-Format'; - case Copper.OPTION_MAX_AGE: return 'Max-Age'; - case Copper.OPTION_ACCEPT: return 'Accept'; - - case Copper.OPTION_URI_HOST: return 'Uri-Host'; - case Copper.OPTION_URI_PORT: return 'Uri-Port'; - case Copper.OPTION_URI_PATH: return 'Uri-Path'; - case Copper.OPTION_URI_QUERY: return 'Uri-Query'; - - case Copper.OPTION_LOCATION_PATH: return 'Location-Path'; - case Copper.OPTION_LOCATION_QUERY: return 'Location-Query'; - - case Copper.OPTION_PROXY_URI: return 'Proxy-Uri'; - case Copper.OPTION_PROXY_SCHEME: return 'Proxy-Scheme'; - - case Copper.OPTION_IF_MATCH: return 'If-Match'; - case Copper.OPTION_IF_NONE_MATCH: return 'If-None-Match'; - case Copper.OPTION_ETAG: return 'ETag'; - - case Copper.OPTION_OBSERVE: return 'Observe'; - - case Copper.OPTION_BLOCK2: return 'Block2'; - case Copper.OPTION_BLOCK1: return 'Block1'; - - case Copper.OPTION_SIZE2: return 'Size2'; - case Copper.OPTION_SIZE1: return 'Size1'; - - default: return 'Unknown '+number; - } -}; - -Copper.getContentFormatName = function(type) { - switch (type) { - case Copper.CONTENT_TYPE_TEXT_PLAIN: return 'text/plain'; break; - case Copper.CONTENT_TYPE_TEXT_XML: return 'text/xml'; break; - case Copper.CONTENT_TYPE_TEXT_CSV: return 'text/csv'; break; - case Copper.CONTENT_TYPE_TEXT_HTML: return 'text/html'; break; - case Copper.CONTENT_TYPE_IMAGE_GIF: return 'image/gif'; break; - case Copper.CONTENT_TYPE_IMAGE_JPEG: return 'image/jpeg'; break; - case Copper.CONTENT_TYPE_IMAGE_PNG: return 'image/png'; break; - case Copper.CONTENT_TYPE_IMAGE_TIFF: return 'image/tiff'; break; - case Copper.CONTENT_TYPE_AUDIO_RAW: return 'audio/raw'; break; - case Copper.CONTENT_TYPE_VIDEO_RAW: return 'video/raw'; break; - case Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT: return 'application/link-format'; break; - case Copper.CONTENT_TYPE_APPLICATION_XML: return 'application/xml'; break; - case Copper.CONTENT_TYPE_APPLICATION_OCTET_STREAM: return 'application/octet-stream'; break; - case Copper.CONTENT_TYPE_APPLICATION_RDF_XML: return 'application/rdf+xml'; break; - case Copper.CONTENT_TYPE_APPLICATION_SOAP_XML: return 'application/soap+xml'; break; - case Copper.CONTENT_TYPE_APPLICATION_ATOM_XML: return 'application/atom+xml'; break; - case Copper.CONTENT_TYPE_APPLICATION_XMPP_XML: return 'application/xmpp+xml'; break; - case Copper.CONTENT_TYPE_APPLICATION_EXI: return 'application/exi'; break; - case Copper.CONTENT_TYPE_APPLICATION_FASTINFOSET: return 'application/fastinfoset'; break; - case Copper.CONTENT_TYPE_APPLICATION_SOAP_FASTINFOSET: return 'application/soap+fastinfoset'; break; - case Copper.CONTENT_TYPE_APPLICATION_JSON: return 'application/json'; break; - case Copper.CONTENT_TYPE_APPLICATION_X_OBIX_BINARY: return 'application/x-obix-binary'; break; - case Copper.CONTENT_TYPE_APPLICATION_CBOR: return 'application/cbor'; break; - case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TEXT: return 'application/vnd.oma.lwm2m+text'; break; - case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TLV: return 'application/vnd.oma.lwm2m+tlv'; break; - case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_JSON: return 'application/vnd.oma.lwm2m+json'; break; - case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_OPAQUE: return 'application/vnd.oma.lwm2m+opaque'; break; - default: return 'unknown/unknown'; - } -}; - -// CoAP RFC 7252 implementation -//////////////////////////////////////////////////////////////////////////////// - -Copper.serialize = function(message) { - let byteArray = new Array(); - let tempByte = 0x00; - - // first byte: version, type, and token length - tempByte = (0x03 & Copper.VERSION) << 6; // using const for sending packets - tempByte |= (0x03 & message.type) << 4; - tempByte |= (0x0F & message.token.length); - - byteArray.push(tempByte); - - // second byte: method or response code - byteArray.push(0xFF & message.code); - - // third and forth byte: message ID (MID) - byteArray.push(0xFF & (message.mid >>> 8)); - byteArray.push(0xFF & message.mid); - - for (let i in message.token) { - byteArray.push(0xFF & message.token[i]); - } - - // options - message.optionCount = 0; - let optNumber = 0; - - for (let optTypeIt in message.options) { - - if (!Array.isArray(message.options[optTypeIt][1])) { - continue; - } else { - - Copper.logEvent("SERIALIZE: Option "+Copper.getOptionName(optTypeIt)); - - let splitOption = new Array(); - if (optTypeIt==Copper.OPTION_LOCATION_PATH || - optTypeIt==Copper.OPTION_LOCATION_QUERY || - optTypeIt==Copper.OPTION_URI_PATH || - optTypeIt==Copper.OPTION_URI_QUERY) { - - let separator = '/'; // 0x002F - if (optTypeIt==Copper.OPTION_LOCATION_QUERY || optTypeIt==Copper.OPTION_URI_QUERY) { - separator = '&'; // 0x0026 - } - - if (Copper.bytes2str(message.options[optTypeIt][1])!="") { - let splitString = Copper.bytes2str(message.options[optTypeIt][1]).split(separator); - for (let s in splitString) { - splitOption.push(Copper.str2bytes(splitString[s])); - } - } - } else { - splitOption.push(message.options[optTypeIt][1]); - } - - while ((opt = splitOption.shift())) { - - let optDelta = optTypeIt - optNumber; - - let delta = Copper.optionNibble(optDelta); - let len = Copper.optionNibble(opt.length); - - byteArray.push(0xFF & (delta<<4 | len)); - - if (delta==13) { - byteArray.push(optDelta-13); - } else if (delta==14) { - byteArray.push( (optDelta-269)>>>8 ); - byteArray.push( 0xFF & (optDelta-269) ); - } - if (len==13) { - byteArray.push(opt.length-13); - } else if (len==14) { - byteArray.push( (opt.length)>>>8 ); - byteArray.push( 0xFF & (opt.length-269) ); - } - - // add option value - for (let i in opt) byteArray.push(opt[i]); - - message.optionCount++; - - optNumber = optTypeIt; - } - } - } - - // option terminator - if (message.payload.length>0) { - byteArray.push(0xFF); - } - - // serialize as string - let packet = Copper.bytes2data(byteArray); - - // payload - packet += Copper.bytes2data(message.payload); - - // finished - return packet; -}; - -Copper.parse = function(packet) { - - Copper.logEvent('PACKET (hex): ' + packet.map(function(x){return x.toString(16).toUpperCase();})); - - // first byte: version, type, and option count - let tempByte = packet.shift(); - let tokenLength = parseInt(0x0F & tempByte); - let version = 0xFF & ((tempByte & 0xC0) >>> 6); - if (version != Copper.VERSION) { - throw new Error('Cannot parse CoAP version '+version); - } - - // create the message - var message = new Copper.CoapMessage( 0x03 & ((tempByte) >>> 4), packet.shift() ); - - // third and forth byte: message ID (MID) - message.mid = packet.shift() << 8; - message.mid |= packet.shift(); - - Copper.logEvent("PARSE: Token length = "+tokenLength); - for (let i=0; i0) { - optLen += 1 + message.options[optNumber][0]; - opt = message.options[optNumber][1].concat(separator).concat(opt); - } - } - - message.options[optNumber] = new Array(optLen, opt); - - } else { - message.payload = packet; - break; - } - } - - return message; -}; - - -//Protocol helper functions -//////////////////////////////////////////////////////////////////////////////// - -Copper.optionNibble = function(value) { - if (value < 13) { - return (0xFF & value); - } else if (value <= 0xFF+13) { - return 13; - } else if (value <= 0xFFFF+269) { - return 14; - } else { - throw new Error('Option delta/length larger than 65804 not allowed'); - } -}; - -Copper.isPowerOfTwo = function(i) { - return ((i & (i-1))==0); -}; - -Copper.leadingZero = function(num, len) { - if (!len) len = 2; - num = ''+num; - while (num.length 127) && (c < 2048)) { - b.push(0xFF & ((c >> 6) | 192)); - b.push(0xFF & ((c & 63) | 128)); - } else { - b.push(0xFF & ((c >> 12) | 224)); - b.push(0xFF & (((c >> 6) & 63) | 128)); - b.push(0xFF & ((c & 63) | 128)); - } - } - } - - return b; -}; - -Copper.bytes2str = function(b) { - - let str = ''; - for (let i=0; i= 32 && c < 127)) { - str += String.fromCharCode(c); - } else if(Copper.utf8 && c >= 192 && c < 224 && (i+1 < b.length) && (b[i+1] & 0xc0) == 0x80) { - let c1 = c & 0x1f; - let c2 = b[i+1] & 0x3F; - str += String.fromCharCode((c1 << 6) | c2); - i += 1; - } else if (Copper.utf8 && c >= 224 && c < 240 && (i+2 < b.length) && (b[i+1] & 0xc0) == 0x80 && (b[i+2] & 0xc0) == 0x80) { - let c1 = c & 0x0f; - let c2 = b[i+1] & 0x3F; - let c3 = b[i+2] & 0x3F; - str += String.fromCharCode((c1 << 12) | (c2 << 6) | c3); - i += 2; - } else if (Copper.utf8 && c >= 240 && i+3 < b.length) { - Copper.logEvent('4-byte UTF-8'); - str += String.fromCharCode(0xFFFD); // char '�' - i += 3; - } else if (Copper.utf8 && c >= 128) { - Copper.logEvent('Incomplete UTF-8 encoding'); - str += String.fromCharCode(0xFFFD); // char '�' - } else { - if (c < 32) - str += String.fromCharCode(0x2400 + c); // replacement character block - else - str += String.fromCharCode(0xFFFD); // char '�' -// str += "\\x" + (c < 16 ? "0" : "") + c.toString(16); - } - } - return str; -}; - -Copper.int2bytes = function(i) { - var b = new Array(0); - while (i>0) { - b.unshift(0xFF & i); - i >>>= 8; - } - return b; -}; - -Copper.bytes2int = function(b) { - var i = 0; - for (let k in b) { - i = (i << 8) | b[k]; - } - //convert to unsigned int - return i>>>0; -}; - -Copper.hex2bytes = function(h) { - var b = new Array(); - for (let i=h.length-2; i>0; i-=2) { - b.unshift(parseInt(('0x'+h.substr(i,2)).replace(/xx/, 'x'))); - } - return b; -}; - -Copper.bytes2hex = function(b) { - - if (!b || !Array.isArray(b) || b.length==0) { - return 'empty'; - } - - var hex = '0x'; - for (let k in b) { - if (b[k]!==undefined) { - hex += Copper.leadingZero(b[k].toString(16).toUpperCase()); - } else { - hex += '--'; - } - } - - return hex; -}; - -Copper.str2hex = function(s) { - var temp; - if (s.substr(0,2)=='0x') { - temp = Copper.hex2bytes(s); - } else { - temp = Copper.str2bytes(s); - } - - return Copper.bytes2hex(temp); -}; - -Copper.float2bytes = function(value) { - var bytes = 0; - switch (value) { - case Number.POSITIVE_INFINITY: bytes = 0x7F800000; break; - case Number.NEGATIVE_INFINITY: bytes = 0xFF800000; break; - case +0.0: bytes = 0x40000000; break; - case -0.0: bytes = 0xC0000000; break; - default: - if (Number.isNaN(value)) { bytes = 0x7FC00000; break; } - - if (value <= -0.0) { - bytes = 0x80000000; - value = -value; - } - - var exponent = Math.floor(Math.log(value) / Math.log(2)); - var significand = ((value / Math.pow(2, exponent)) * 0x00800000) | 0; - - exponent += 127; - if (exponent >= 0xFF) { - exponent = 0xFF; - significand = 0; - } else if (exponent < 0) exponent = 0; - - bytes = bytes | (exponent << 23); - bytes = bytes | (significand & ~(-1 << 23)); - break; - } - return bytes; -}; - -Copper.double2bytes = function(value) { - - var hiWord = 0, loWord = 0; - switch (value) { - case Number.POSITIVE_INFINITY: hiWord = 0x7FF00000; break; - case Number.NEGATIVE_INFINITY: hiWord = 0xFFF00000; break; - case +0.0: hiWord = 0x40000000; break; - case -0.0: hiWord = 0xC0000000; break; - default: - if (Number.isNaN(value)) { hiWord = 0x7FF80000; break; } - - if (value <= -0.0) { - hiWord = 0x80000000; - value = -value; - } - - var exponent = Math.floor(Math.log(value) / Math.log(2)); - var significand = Math.floor((value / Math.pow(2, exponent)) * Math.pow(2, 52)); - - loWord = significand & 0xFFFFFFFF; - significand /= Math.pow(2, 32); - - exponent += 1023; - if (exponent >= 0x7FF) { - exponent = 0x7FF; - significand = 0; - } else if (exponent < 0) exponent = 0; - - hiWord = hiWord | (exponent << 20); - hiWord = hiWord | (significand & ~(-1 << 20)); - break; - } - - let bytes = new Array(0); - - bytes.unshift( loWord>>24 & 0xFF ); - bytes.unshift( loWord>>16 & 0xFF ); - bytes.unshift( loWord>>8 & 0xFF ); - bytes.unshift( loWord>>0 & 0xFF ); - bytes.unshift( hiWord>>24 & 0xFF ); - bytes.unshift( hiWord>>16 & 0xFF ); - bytes.unshift( hiWord>>8 & 0xFF ); - bytes.unshift( hiWord>>0 & 0xFF ); - - return bytes; -}; +/******************************************************************************* + * Copyright (c) 2015, Institute for Pervasive Computing, ETH Zurich. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Copper (Cu) CoAP user-agent. + *******************************************************************************/ +/** + * \file Implementation of RFC 7252 + * + * \author Matthias Kovatsch + */ + +// Constants +//////////////////////////////////////////////////////////////////////////////// + +Copper.__defineGetter__("VERSION", function() { return 1; }); +Copper.__defineGetter__("DEFAULT_PORT", function() { return 5683; }); +Copper.__defineGetter__("RESPONSE_TIMEOUT", function() { return 2000; }); // ms +Copper.__defineGetter__("RESPONSE_RANDOM_FACTOR", function() { return 1.5; }); // ms +Copper.__defineGetter__("MAX_RETRANSMIT", function() { return 4; }); +Copper.__defineGetter__("ETAG_LENGTH", function() { return 8; }); +Copper.__defineGetter__("TOKEN_LENGTH", function() { return 8; }); + +Copper.__defineGetter__("MSG_TYPE_CON", function() { return 0; }); +Copper.__defineGetter__("MSG_TYPE_NON", function() { return 1; }); +Copper.__defineGetter__("MSG_TYPE_ACK", function() { return 2; }); +Copper.__defineGetter__("MSG_TYPE_RST", function() { return 3; }); + +Copper.__defineGetter__("OPTION_IF_MATCH", function() { return 1; }); +Copper.__defineGetter__("OPTION_URI_HOST", function() { return 3; }); +Copper.__defineGetter__("OPTION_ETAG", function() { return 4; }); +Copper.__defineGetter__("OPTION_IF_NONE_MATCH", function() { return 5; }); +Copper.__defineGetter__("OPTION_URI_PORT", function() { return 7; }); +Copper.__defineGetter__("OPTION_LOCATION_PATH", function() { return 8; }); +Copper.__defineGetter__("OPTION_URI_PATH", function() { return 11; }); +Copper.__defineGetter__("OPTION_CONTENT_FORMAT", function() { return 12; }); +Copper.__defineGetter__("OPTION_MAX_AGE", function() { return 14; }); +Copper.__defineGetter__("OPTION_URI_QUERY", function() { return 15; }); +Copper.__defineGetter__("OPTION_ACCEPT", function() { return 17; }); +Copper.__defineGetter__("OPTION_LOCATION_QUERY", function() { return 20; }); +Copper.__defineGetter__("OPTION_PROXY_URI", function() { return 35; }); +Copper.__defineGetter__("OPTION_PROXY_SCHEME", function() { return 39; }); +Copper.__defineGetter__("OPTION_SIZE1", function() { return 60; }); + +Copper.__defineGetter__("OPTION_OBSERVE", function() { return 6; }); + +Copper.__defineGetter__("OPTION_BLOCK2", function() { return 23; }); +Copper.__defineGetter__("OPTION_BLOCK1", function() { return 27; }); +Copper.__defineGetter__("OPTION_SIZE2", function() { return 28; }); + +Copper.__defineGetter__("EMPTY", function() { return 0; }); + +Copper.__defineGetter__("GET", function() { return 1; }); +Copper.__defineGetter__("POST", function() { return 2; }); +Copper.__defineGetter__("PUT", function() { return 3; }); +Copper.__defineGetter__("DELETE", function() { return 4; }); + +Copper.__defineGetter__("CODE_2_01_CREATED", function() { return 65; }); +Copper.__defineGetter__("CODE_2_02_DELETED", function() { return 66; }); +Copper.__defineGetter__("CODE_2_03_VALID", function() { return 67; }); +Copper.__defineGetter__("CODE_2_04_CHANGED", function() { return 68; }); +Copper.__defineGetter__("CODE_2_05_CONTENT", function() { return 69; }); +Copper.__defineGetter__("CODE_2_31_CONTINUE", function() { return 95; }); + +Copper.__defineGetter__("CODE_4_00_BAD_REQUEST", function() { return 128; }); +Copper.__defineGetter__("CODE_4_01_UNAUTHORIZED", function() { return 129; }); +Copper.__defineGetter__("CODE_4_02_BAD_OPTION", function() { return 130; }); +Copper.__defineGetter__("CODE_4_03_FORBIDDEN", function() { return 131; }); +Copper.__defineGetter__("CODE_4_04_NOT_FOUND", function() { return 132; }); +Copper.__defineGetter__("CODE_4_05_METHOD_NOT_ALLOWED", function() { return 133; }); +Copper.__defineGetter__("CODE_4_06_NOT_ACCEPTABLE", function() { return 134; }); +Copper.__defineGetter__("CODE_4_08_REQUEST_ENTITY_INCOMPLETE", function() { return 136; }); +Copper.__defineGetter__("CODE_4_12_PRECONDITION_FAILED", function() { return 140; }); +Copper.__defineGetter__("CODE_4_13_REQUEST_ENTITY_TOO_LARGE", function() { return 141; }); +Copper.__defineGetter__("CODE_4_15_UNSUPPORTED_MEDIA_TYPE", function() { return 143; }); + +Copper.__defineGetter__("CODE_5_00_INTERNAL_SERVER_ERROR", function() { return 160; }); +Copper.__defineGetter__("CODE_5_01_NOT_IMPLEMENTED", function() { return 161; }); +Copper.__defineGetter__("CODE_5_02_BAD_GATEWAY", function() { return 162; }); +Copper.__defineGetter__("CODE_5_03_SERVICE_UNAVAILABLE", function() { return 163; }); +Copper.__defineGetter__("CODE_5_04_GATEWAY_TIMEOUT", function() { return 164; }); +Copper.__defineGetter__("CODE_5_05_PROXYING_NOT_SUPPORTED", function() { return 165; }); + +Copper.__defineGetter__("CONTENT_TYPE_TEXT_PLAIN", function() { return 0; }); +Copper.__defineGetter__("CONTENT_TYPE_TEXT_XML", function() { return 1; }); +Copper.__defineGetter__("CONTENT_TYPE_TEXT_CSV", function() { return 2; }); +Copper.__defineGetter__("CONTENT_TYPE_TEXT_HTML", function() { return 3; }); +Copper.__defineGetter__("CONTENT_TYPE_IMAGE_GIF", function() { return 21; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_IMAGE_JPEG", function() { return 22; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_IMAGE_PNG", function() { return 23; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_IMAGE_TIFF", function() { return 24; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_AUDIO_RAW", function() { return 25; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_VIDEO_RAW", function() { return 26; }); // 03 +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_LINK_FORMAT", function() { return 40; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_XML", function() { return 41; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_OCTET_STREAM", function() { return 42; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_RDF_XML", function() { return 43; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_SOAP_XML", function() { return 44; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_ATOM_XML", function() { return 45; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_XMPP_XML", function() { return 46; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_EXI", function() { return 47; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_FASTINFOSET", function() { return 48; }); // 04 +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_SOAP_FASTINFOSET", function() { return 49; }); // 04 +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_JSON", function() { return 50; }); // 04 +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_X_OBIX_BINARY", function() { return 51; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_CBOR", function() { return 60; }); // 04 +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TEXT", function() { return 1541; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TLV", function() { return 1542; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_JSON", function() { return 1543; }); +Copper.__defineGetter__("CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_OPAQUE", function() { return 1544; }); + +Copper.__defineGetter__("WELL_KNOWN_RESOURCES", function() { + if(Copper.behavior.oic) return '/oic/res'; + return '/.well-known/core'; +}); + +Copper.__defineGetter__("WELL_KNOWN_PATH", function() { + if(Copper.behavior.oic) return '/oic'; + return '/.well-known'; +}); + + + +//Registries +//////////////////////////////////////////////////////////////////////////////// + +Copper.getCodeName = function(code) { + switch (parseInt(code)) { + // empty + case 0: return 'EMPTY'; + // methods + case Copper.GET: return 'GET'; + case Copper.POST: return 'POST'; + case Copper.PUT: return 'PUT'; + case Copper.DELETE: return 'DELETE'; + // response codes + case Copper.CODE_2_01_CREATED: return '2.01 Created'; + case Copper.CODE_2_02_DELETED: return '2.02 Deleted'; + case Copper.CODE_2_03_VALID: return '2.03 Valid'; + case Copper.CODE_2_04_CHANGED: return '2.04 Changed'; + case Copper.CODE_2_05_CONTENT: return '2.05 Content'; + case Copper.CODE_2_31_CONTINUE: return '2.31 Continue'; + case Copper.CODE_4_00_BAD_REQUEST: return '4.00 Bad Request'; + case Copper.CODE_4_01_UNAUTHORIZED: return '4.01 Unauthorized'; + case Copper.CODE_4_02_BAD_OPTION: return '4.02 Bad Option'; + case Copper.CODE_4_03_FORBIDDEN: return '4.03 Forbidden'; + case Copper.CODE_4_04_NOT_FOUND: return '4.04 Not Found'; + case Copper.CODE_4_05_METHOD_NOT_ALLOWED: return '4.05 Method Not Allowed'; + case Copper.CODE_4_06_NOT_ACCEPTABLE: return '4.06 Not Acceptable'; + case Copper.CODE_4_08_REQUEST_ENTITY_INCOMPLETE: return '4.08 Request Entity Incomplete'; + case Copper.CODE_4_12_PRECONDITION_FAILED: return '4.12 Precondition Failed'; + case Copper.CODE_4_13_REQUEST_ENTITY_TOO_LARGE: return '4.13 Request Entity Too Large'; + case Copper.CODE_4_15_UNSUPPORTED_MEDIA_TYPE: return '4.15 Unsupported Content-Format'; + case Copper.CODE_5_00_INTERNAL_SERVER_ERROR: return '5.00 Internal Server Error'; + case Copper.CODE_5_01_NOT_IMPLEMENTED: return '5.01 Not Implemented'; + case Copper.CODE_5_02_BAD_GATEWAY: return '5.02 Bad Gateway'; + case Copper.CODE_5_03_SERVICE_UNAVAILABLE: return '5.03 Service Unavailable'; + case Copper.CODE_5_04_GATEWAY_TIMEOUT: return '5.04 Gateway Timeout'; + case Copper.CODE_5_05_PROXYING_NOT_SUPPORTED: return '5.05 Proxying Not Supported'; + // ... + default: return Math.floor(code/32)+'.'+(code % 32)+' Unknown by Copper'; + } +}; + +Copper.getOptionName = function(number) { + switch (parseInt(number)) { + case Copper.OPTION_CONTENT_FORMAT: return 'Content-Format'; + case Copper.OPTION_MAX_AGE: return 'Max-Age'; + case Copper.OPTION_ACCEPT: return 'Accept'; + + case Copper.OPTION_URI_HOST: return 'Uri-Host'; + case Copper.OPTION_URI_PORT: return 'Uri-Port'; + case Copper.OPTION_URI_PATH: return 'Uri-Path'; + case Copper.OPTION_URI_QUERY: return 'Uri-Query'; + + case Copper.OPTION_LOCATION_PATH: return 'Location-Path'; + case Copper.OPTION_LOCATION_QUERY: return 'Location-Query'; + + case Copper.OPTION_PROXY_URI: return 'Proxy-Uri'; + case Copper.OPTION_PROXY_SCHEME: return 'Proxy-Scheme'; + + case Copper.OPTION_IF_MATCH: return 'If-Match'; + case Copper.OPTION_IF_NONE_MATCH: return 'If-None-Match'; + case Copper.OPTION_ETAG: return 'ETag'; + + case Copper.OPTION_OBSERVE: return 'Observe'; + + case Copper.OPTION_BLOCK2: return 'Block2'; + case Copper.OPTION_BLOCK1: return 'Block1'; + + case Copper.OPTION_SIZE2: return 'Size2'; + case Copper.OPTION_SIZE1: return 'Size1'; + + default: return 'Unknown '+number; + } +}; + +Copper.getContentFormatName = function(type) { + switch (type) { + case Copper.CONTENT_TYPE_TEXT_PLAIN: return 'text/plain'; break; + case Copper.CONTENT_TYPE_TEXT_XML: return 'text/xml'; break; + case Copper.CONTENT_TYPE_TEXT_CSV: return 'text/csv'; break; + case Copper.CONTENT_TYPE_TEXT_HTML: return 'text/html'; break; + case Copper.CONTENT_TYPE_IMAGE_GIF: return 'image/gif'; break; + case Copper.CONTENT_TYPE_IMAGE_JPEG: return 'image/jpeg'; break; + case Copper.CONTENT_TYPE_IMAGE_PNG: return 'image/png'; break; + case Copper.CONTENT_TYPE_IMAGE_TIFF: return 'image/tiff'; break; + case Copper.CONTENT_TYPE_AUDIO_RAW: return 'audio/raw'; break; + case Copper.CONTENT_TYPE_VIDEO_RAW: return 'video/raw'; break; + case Copper.CONTENT_TYPE_APPLICATION_LINK_FORMAT: return 'application/link-format'; break; + case Copper.CONTENT_TYPE_APPLICATION_XML: return 'application/xml'; break; + case Copper.CONTENT_TYPE_APPLICATION_OCTET_STREAM: return 'application/octet-stream'; break; + case Copper.CONTENT_TYPE_APPLICATION_RDF_XML: return 'application/rdf+xml'; break; + case Copper.CONTENT_TYPE_APPLICATION_SOAP_XML: return 'application/soap+xml'; break; + case Copper.CONTENT_TYPE_APPLICATION_ATOM_XML: return 'application/atom+xml'; break; + case Copper.CONTENT_TYPE_APPLICATION_XMPP_XML: return 'application/xmpp+xml'; break; + case Copper.CONTENT_TYPE_APPLICATION_EXI: return 'application/exi'; break; + case Copper.CONTENT_TYPE_APPLICATION_FASTINFOSET: return 'application/fastinfoset'; break; + case Copper.CONTENT_TYPE_APPLICATION_SOAP_FASTINFOSET: return 'application/soap+fastinfoset'; break; + case Copper.CONTENT_TYPE_APPLICATION_JSON: return 'application/json'; break; + case Copper.CONTENT_TYPE_APPLICATION_X_OBIX_BINARY: return 'application/x-obix-binary'; break; + case Copper.CONTENT_TYPE_APPLICATION_CBOR: return 'application/cbor'; break; + case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TEXT: return 'application/vnd.oma.lwm2m+text'; break; + case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_TLV: return 'application/vnd.oma.lwm2m+tlv'; break; + case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_JSON: return 'application/vnd.oma.lwm2m+json'; break; + case Copper.CONTENT_TYPE_APPLICATION_VND_OMA_LWM2M_OPAQUE: return 'application/vnd.oma.lwm2m+opaque'; break; + default: return 'unknown/unknown'; + } +}; + +// CoAP RFC 7252 implementation +//////////////////////////////////////////////////////////////////////////////// + +Copper.serialize = function(message) { + let byteArray = new Array(); + let tempByte = 0x00; + + // first byte: version, type, and token length + tempByte = (0x03 & Copper.VERSION) << 6; // using const for sending packets + tempByte |= (0x03 & message.type) << 4; + tempByte |= (0x0F & message.token.length); + + byteArray.push(tempByte); + + // second byte: method or response code + byteArray.push(0xFF & message.code); + + // third and forth byte: message ID (MID) + byteArray.push(0xFF & (message.mid >>> 8)); + byteArray.push(0xFF & message.mid); + + for (let i in message.token) { + byteArray.push(0xFF & message.token[i]); + } + + // options + message.optionCount = 0; + let optNumber = 0; + + for (let optTypeIt in message.options) { + + if (!Array.isArray(message.options[optTypeIt][1])) { + continue; + } else { + + Copper.logEvent("SERIALIZE: Option "+Copper.getOptionName(optTypeIt)); + + let splitOption = new Array(); + if (optTypeIt==Copper.OPTION_LOCATION_PATH || + optTypeIt==Copper.OPTION_LOCATION_QUERY || + optTypeIt==Copper.OPTION_URI_PATH || + optTypeIt==Copper.OPTION_URI_QUERY) { + + let separator = '/'; // 0x002F + if (optTypeIt==Copper.OPTION_LOCATION_QUERY || optTypeIt==Copper.OPTION_URI_QUERY) { + separator = '&'; // 0x0026 + } + + if (Copper.bytes2str(message.options[optTypeIt][1])!="") { + let splitString = Copper.bytes2str(message.options[optTypeIt][1]).split(separator); + for (let s in splitString) { + splitOption.push(Copper.str2bytes(splitString[s])); + } + } + } else { + splitOption.push(message.options[optTypeIt][1]); + } + + let opt; + while ((opt = splitOption.shift())) { + + let optDelta = optTypeIt - optNumber; + + let delta = Copper.optionNibble(optDelta); + let len = Copper.optionNibble(opt.length); + + byteArray.push(0xFF & (delta<<4 | len)); + + if (delta==13) { + byteArray.push(optDelta-13); + } else if (delta==14) { + byteArray.push( (optDelta-269)>>>8 ); + byteArray.push( 0xFF & (optDelta-269) ); + } + if (len==13) { + byteArray.push(opt.length-13); + } else if (len==14) { + byteArray.push( (opt.length)>>>8 ); + byteArray.push( 0xFF & (opt.length-269) ); + } + + // add option value + for (let i in opt) byteArray.push(opt[i]); + + message.optionCount++; + + optNumber = optTypeIt; + } + } + } + + // option terminator + if (message.payload.length>0) { + byteArray.push(0xFF); + } + + // serialize as string + let packet = Copper.bytes2data(byteArray); + + // payload + packet += Copper.bytes2data(message.payload); + + // finished + return packet; +}; + +Copper.parse = function(packet) { + + //Copper.logEvent('PACKET (hex): ' + packet.map(function(x){return x.toString(16).toUpperCase();})); + + // first byte: version, type, and option count + let tempByte = packet.shift(); + let tokenLength = parseInt(0x0F & tempByte); + let version = 0xFF & ((tempByte & 0xC0) >>> 6); + if (version != Copper.VERSION) { + throw new Error('Cannot parse CoAP version '+version); + } + + // create the message + var message = new Copper.CoapMessage( 0x03 & ((tempByte) >>> 4), packet.shift() ); + + // third and forth byte: message ID (MID) + message.mid = packet.shift() << 8; + message.mid |= packet.shift(); + + Copper.logEvent("PARSE: Token length = "+tokenLength); + for (let i=0; i0) { + optLen += 1 + message.options[optNumber][0]; + opt = message.options[optNumber][1].concat(separator).concat(opt); + } + } + + message.options[optNumber] = new Array(optLen, opt); + + } else { + message.payload = packet; + break; + } + } + + return message; +}; + + +//Protocol helper functions +//////////////////////////////////////////////////////////////////////////////// + +Copper.optionNibble = function(value) { + if (value < 13) { + return (0xFF & value); + } else if (value <= 0xFF+13) { + return 13; + } else if (value <= 0xFFFF+269) { + return 14; + } else { + throw new Error('Option delta/length larger than 65804 not allowed'); + } +}; + +Copper.isPowerOfTwo = function(i) { + return ((i & (i-1))==0); +}; + +Copper.leadingZero = function(num, len) { + if (!len) len = 2; + num = ''+num; + while (num.length 127) && (c < 2048)) { + b.push(0xFF & ((c >> 6) | 192)); + b.push(0xFF & ((c & 63) | 128)); + } else { + b.push(0xFF & ((c >> 12) | 224)); + b.push(0xFF & (((c >> 6) & 63) | 128)); + b.push(0xFF & ((c & 63) | 128)); + } + } + } + + return b; +}; + +Copper.bytes2str = function(b) { + + let str = ''; + for (let i=0; i= 32 && c < 127)) { + str += String.fromCharCode(c); + } else if(Copper.utf8 && c >= 192 && c < 224 && (i+1 < b.length) && (b[i+1] & 0xc0) == 0x80) { + let c1 = c & 0x1f; + let c2 = b[i+1] & 0x3F; + str += String.fromCharCode((c1 << 6) | c2); + i += 1; + } else if (Copper.utf8 && c >= 224 && c < 240 && (i+2 < b.length) && (b[i+1] & 0xc0) == 0x80 && (b[i+2] & 0xc0) == 0x80) { + let c1 = c & 0x0f; + let c2 = b[i+1] & 0x3F; + let c3 = b[i+2] & 0x3F; + str += String.fromCharCode((c1 << 12) | (c2 << 6) | c3); + i += 2; + } else if (Copper.utf8 && c >= 240 && i+3 < b.length) { + //Copper.logEvent('4-byte UTF-8'); + str += String.fromCharCode(0xFFFD); // char '�' + i += 3; + } else if (Copper.utf8 && c >= 128) { + //Copper.logEvent('Incomplete UTF-8 encoding'); + str += String.fromCharCode(0xFFFD); // char '�' + } else { + if (c < 32) + str += String.fromCharCode(0x2400 + c); // replacement character block + else + str += String.fromCharCode(0xFFFD); // char '�' +// str += "\\x" + (c < 16 ? "0" : "") + c.toString(16); + } + } + return str; +}; + +Copper.int2bytes = function(i) { + var b = new Array(0); + while (i>0) { + b.unshift(0xFF & i); + i >>>= 8; + } + return b; +}; + +Copper.bytes2int = function(b) { + var i = 0; + for (let k in b) { + i = (i << 8) | b[k]; + } + //convert to unsigned int + return i>>>0; +}; + +Copper.hex2bytes = function(h) { + var b = new Array(); + for (let i=h.length-2; i>0; i-=2) { + b.unshift(parseInt(('0x'+h.substr(i,2)).replace(/xx/, 'x'))); + } + return b; +}; + +Copper.bytes2hex = function(b) { + + if (!b || !Array.isArray(b) || b.length==0) { + return 'empty'; + } + + var hex = '0x'; + for (let k in b) { + if (b[k]!==undefined) { + hex += Copper.leadingZero(b[k].toString(16).toUpperCase()); + } else { + hex += '--'; + } + } + + return hex; +}; + +Copper.str2hex = function(s) { + var temp; + if (s.substr(0,2)=='0x') { + temp = Copper.hex2bytes(s); + } else { + temp = Copper.str2bytes(s); + } + + return Copper.bytes2hex(temp); +}; + +Copper.float2bytes = function(value) { + var bytes = 0; + switch (value) { + case Number.POSITIVE_INFINITY: bytes = 0x7F800000; break; + case Number.NEGATIVE_INFINITY: bytes = 0xFF800000; break; + case +0.0: bytes = 0x40000000; break; + case -0.0: bytes = 0xC0000000; break; + default: + if (Number.isNaN(value)) { bytes = 0x7FC00000; break; } + + if (value <= -0.0) { + bytes = 0x80000000; + value = -value; + } + + var exponent = Math.floor(Math.log(value) / Math.log(2)); + var significand = ((value / Math.pow(2, exponent)) * 0x00800000) | 0; + + exponent += 127; + if (exponent >= 0xFF) { + exponent = 0xFF; + significand = 0; + } else if (exponent < 0) exponent = 0; + + bytes = bytes | (exponent << 23); + bytes = bytes | (significand & ~(-1 << 23)); + break; + } + return bytes; +}; + +Copper.double2bytes = function(value) { + + var hiWord = 0, loWord = 0; + switch (value) { + case Number.POSITIVE_INFINITY: hiWord = 0x7FF00000; break; + case Number.NEGATIVE_INFINITY: hiWord = 0xFFF00000; break; + case +0.0: hiWord = 0x40000000; break; + case -0.0: hiWord = 0xC0000000; break; + default: + if (Number.isNaN(value)) { hiWord = 0x7FF80000; break; } + + if (value <= -0.0) { + hiWord = 0x80000000; + value = -value; + } + + var exponent = Math.floor(Math.log(value) / Math.log(2)); + var significand = Math.floor((value / Math.pow(2, exponent)) * Math.pow(2, 52)); + + loWord = significand & 0xFFFFFFFF; + significand /= Math.pow(2, 32); + + exponent += 1023; + if (exponent >= 0x7FF) { + exponent = 0x7FF; + significand = 0; + } else if (exponent < 0) exponent = 0; + + hiWord = hiWord | (exponent << 20); + hiWord = hiWord | (significand & ~(-1 << 20)); + break; + } + + let bytes = new Array(0); + + bytes.unshift( loWord>>24 & 0xFF ); + bytes.unshift( loWord>>16 & 0xFF ); + bytes.unshift( loWord>>8 & 0xFF ); + bytes.unshift( loWord>>0 & 0xFF ); + bytes.unshift( hiWord>>24 & 0xFF ); + bytes.unshift( hiWord>>16 & 0xFF ); + bytes.unshift( hiWord>>8 & 0xFF ); + bytes.unshift( hiWord>>0 & 0xFF ); + + return bytes; +}; diff --git a/chrome/content/coap/TransactionHandler.js b/chrome/content/coap/TransactionHandler.js index 7dd6c83..87e6898 100644 --- a/chrome/content/coap/TransactionHandler.js +++ b/chrome/content/coap/TransactionHandler.js @@ -143,6 +143,14 @@ Copper.TransactionHandler.prototype = { send : function(message, reqCB) { if (this.client.ended) return; + + if(null != reqCB) { + if(0 == Copper.hostname.indexOf('[ff0') || 0 == Copper.hostname.indexOf('224')) + reqCB.isMulticast = true; + else + reqCB.isMulticast = false; + } + // set MID for message if (message.getMID()==-1) { @@ -191,6 +199,11 @@ Copper.TransactionHandler.prototype = { this.client.send( Copper.serialize(message) ); }, + unregister(mid, tid) { + delete this.requests[tid]; + delete this.registeredMIDs[mid]; + }, + resend : function(mid) { if (Copper.behavior.retransmissions && this.transactions[mid]!==undefined && (this.transactions[mid].message.getRetries() < Copper.MAX_RETRANSMIT)) { @@ -213,9 +226,12 @@ Copper.TransactionHandler.prototype = { } }, - handle : function(datagram) { + handle : function(datagram, from) { // parse byte message to CoAP message let message = Copper.parse(datagram); + // Common case is to stop listening except when using multicast + let stoppedListening = true; + message.from = from; Copper.logMessage(message, false); @@ -233,7 +249,10 @@ Copper.TransactionHandler.prototype = { delete this.transactions[message.getMID()]; // filter duplicates - } else if (this.dupFilter.indexOf(message.getMID()) != -1) { + } else if (this.dupFilter.findIndex(function (element, index, array) { + return (element.mid == message.getMID() && + element.addr == message.from.address && + element.port == message.from.port);}) != -1) { if (message.getType()==Copper.MSG_TYPE_CON) { var reply = this.dupCache[message.getMID()]; @@ -277,9 +296,14 @@ Copper.TransactionHandler.prototype = { } callback = this.requests[message.getToken()]; - - delete this.requests[message.getToken()]; - delete this.registeredMIDs[message.getMID()]; + if(callback.isMulticast === undefined || callback.isMulticast === false) { + // multicast may receive multiple messages from different servers with same mid + // so we will not delete the callbacks just yet + delete this.requests[message.getToken()]; + delete this.registeredMIDs[message.getMID()]; + } else { + stoppedListening = false; + } // check registered Tokens, e.g., subscriptions } else if (this.registeredTokens[message.getToken()]) { @@ -318,7 +342,7 @@ Copper.TransactionHandler.prototype = { // callback might set reply for message used by deduplication if (callback) { try { - callback(message); + callback(message, stoppedListening); } catch (ex) { ex.message = 'Message callback failed:\n' + ex.message; Copper.logError(ex); @@ -334,14 +358,14 @@ Copper.TransactionHandler.prototype = { // add to duplicates filter if (message.getType()!=Copper.MSG_TYPE_RST) { - this.dupFilter.unshift(message.getMID()); + this.dupFilter.unshift({mid:message.getMID(), addr:message.from.address, port: message.from.port}); if (message.reply) this.dupCache[message.getMID()] = message.reply; if (this.dupFilter.length>10) { delete this.dupCache[this.dupFilter.pop()]; } } }, - + ack : function(mid) { var ack = new Copper.CoapMessage(Copper.MSG_TYPE_ACK); ack.setMID( mid ); diff --git a/chrome/content/coap/UdpClient.js b/chrome/content/coap/UdpClient.js index 08653ce..2f84a94 100644 --- a/chrome/content/coap/UdpClient.js +++ b/chrome/content/coap/UdpClient.js @@ -1,167 +1,99 @@ -/******************************************************************************* - * Copyright (c) 2015, Institute for Pervasive Computing, ETH Zurich. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the Institute nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * This file is part of the Copper (Cu) CoAP user-agent. - *******************************************************************************/ -/** - * \file - * Code handling the UDP communication for the CoAP protocol - * - * \author Matthias Kovatsch \author - */ +Copper.UdpClient = function (remoteHost, remotePort) { -/* - * FIXME: inputStream does not separate single datagrams. - * Thus, increased traffic results in merged datagrams, i.e., one or more datagrams are added to the payload of the first one. - * A workaround will probably need native code to provide a datagram handler (transport-service) that pipes them to/from Firefox. - */ + try { + Components.utils.import("resource://gre/modules/Services.jsm"); + this.socket = Components.classes["@mozilla.org/network/udp-socket;1"].createInstance(Components.interfaces.nsIUDPSocket); + this.socket.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal()); + this.socket.asyncListen(this); -Copper.UdpClient = function(remoteHost, remotePort) { + this.socketf = Components.classes["@mozilla.org/network/udp-socket;1"].createInstance(Components.interfaces.nsIUDPSocket); + this.socketf.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal()); + this.socketf.asyncListen(this); + } catch(e) { + Copper.logEvent("Failed to start new socket: " + e); + } - // createTransport requires plain IPv6 address - this.host = remoteHost.replace(/\[/,'').replace(/\]/,''); - this.port = remotePort; - - this.transportService = Components.classes["@mozilla.org/network/socket-transport-service;1"].getService(Components.interfaces.nsISocketTransportService); - this.pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump); - - this.socket = this.transportService.createTransport(["udp"], 1, this.host, this.port, null); - - this.outputStream = this.socket.openOutputStream(0, 0, 0); - this.inputStream = this.socket.openInputStream(0, 0, 0); // 1,0,0 = OPEN_BLOCKING - - this.pump.init(this.inputStream, -1, -1, 0, 0, false); - this.pump.asyncRead(this, null); - - this.localAddr = null; - this.callback = null; - this.lastSend = null; - this.ended = false; - - return this; + this.callback = null; + this.lastSend = null; + this.ended = false; + Copper.logEvent("localAddr" + this.socket.localAddr); + return this; }; Copper.UdpClient.prototype = { - - register : function(myCB) { - this.callback = myCB; - }, - - // stream observer functions - onStartRequest : function(request, context) { - // do nothing - }, - - onStopRequest : function(request, context, status) { - if (!this.ended) { - this.shutdown(); - throw new Error('Host/network unreachable'); - } else { - Copper.logWarning('Illegal UdpClient state'); - } - }, - - onDataAvailable : function(request, context, inputStream, offset, count) { - try { - let sis = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream); - sis.init(inputStream); - - var byteArray = new Array(); - for (let i=0; i4) { - Copper.logEvent('UDP: Concatenated 4 + ' + (count-4) + ' bytes'); - if (this.callback) this.callback(byteArray); - // reset for next datagram - i = -1; // i++ still coming - count -= 4; - byteArray = new Array(); - } - } - - Copper.logEvent('UDP: Received ' + byteArray.length + ' bytes'); - - if (this.callback) this.callback(byteArray); - - - } catch (ex) { - Copper.logError(ex); - } - }, - - // UdpClient functions - shutdown : function() { - // will also trigger onStopRequest() - this.ended = true; - this.outputStream.close(); - this.inputStream.close(); - this.socket.close(0); - }, - - send : function(datagram) { - - if (this.ended) return; - - // the transport API also concatenates outgoing datagrams when sent too quickly - let since = new Date() - this.lastSend; - if (since<30) { - var that = this; - window.setTimeout( - function() { Copper.myBind(that,that.send(datagram)); }, - 30-since); - return; - } - - this.lastSend = new Date(); - - try { - this.outputStream.write(datagram, datagram.length); - Copper.logEvent('UDP: Sent ' + datagram.length + ' bytes'); - } catch (ex) { - Copper.logError(ex); - } - }, - - getAddr : function() { + // + // nsIUDPSocketListener + // + onPacketReceived: function(socket, message) { + Copper.logEvent("onPacketReceived"); + let messageData = message.data; + let from = message.fromAddr.address; + let port = message.fromAddr.port; + if (port == this.socket.port) { + Copper.logEvent("Ignoring looped message"); + return; + } + try { + if (this.callback) { + this.callback(Copper.data2bytes(message.data), message.fromAddr); + } + } catch (ex) { + Copper.logError(ex); + }; + }, + + onStopListening: function() { + Copper.logEvent('onStopListening'); + this.socket.close(); + this.socketf.close(); + }, + + + // + // UdpClient functions + // + register : function(myCB) { + this.callback = myCB; + }, + + shutdown : function() { + // will also trigger onStopRequest() + this.ended = true; + this.socket.close(0); + this.socketf.close(0); + }, + + send : function(message) { + Copper.logEvent('send'); + if (this.ended) return; + + // the transport API also concatenates outgoing datagrams when sent too quickly + let since = new Date() - this.lastSend; + if (since<30) { + var that = this; + window.setTimeout( + function() { Copper.myBind(that,that.send(message)); }, + 30-since); + return; + } + + this.lastSend = new Date(); + + try { + let rawMessage = Copper.data2bytes(message);//this.converter.convertToByteArray(message); + let host = Copper.hostname.replace(/\[/,'').replace(/\]/,''); + Copper.logEvent('UDP: Sent ' + message.length + ' bytes to ' + host +' on port '+ Copper.port); + Copper.logEvent("send " +this.socket.send(host, Copper.port, rawMessage, rawMessage.length)); + if('ff05:0:0:0:0:0:0:fd' === host || 'ff05:0:0:0:0:0:0:158' === host) { + Copper.logEvent('UDP: Sent ' + message.length + ' bytes to ' + "224.0.1.187" +' on port '+ Copper.port); + Copper.logEvent("also send v4 multicast"+this.socketf.send("224.0.1.187", Copper.port, rawMessage, rawMessage.length)); + } + + //Copper.logEvent("send localAddr" + this.socket.localAddr); + } catch (ex) { + Copper.logError(ex); + } + }, - try { - this.localAddr = this.socket.getScriptableSelfAddr(); - return this.localAddr; - } catch (ex) { - return null; - } - }, - - setTimeout : function(time) { - this.socket.setTimeout(this.socket.TIMEOUT_READ_WRITE, time); - } }; diff --git a/chrome/content/copper.xul b/chrome/content/copper.xul index 0025342..0cb041b 100644 --- a/chrome/content/copper.xul +++ b/chrome/content/copper.xul @@ -49,6 +49,9 @@