From f466781bbec93635045ded718f61d479d61fd1cf Mon Sep 17 00:00:00 2001 From: Minji Park Date: Wed, 5 Apr 2017 13:13:39 +0900 Subject: [PATCH] add websocket feature and dashboard - added dashboard using websocket - modified websocket spec related to length 0 - added websocket client Change-Id: I14bf29702165448d364d7544544519b48866e1c0 Signed-off-by: Jung Seungho Signed-off-by: Minji Park Reviewed-on: https://gerrit.iotivity.org/gerrit/18519 Tested-by: jenkins-iotivity Reviewed-by: Yeonghun Nam Reviewed-by: Jee Hyeok Kim --- cloud/dashboard/.gitignore | 1 + cloud/dashboard/README | 13 + cloud/dashboard/package.json | 31 + cloud/dashboard/public/favicon.ico | Bin 0 -> 24838 bytes cloud/dashboard/public/index.html | 35 + cloud/dashboard/src/Client.js | 230 +++++ .../controlee_temperatureApp.js | 260 ++++++ .../controller_ForTemperature.js | 190 +++++ .../controller_forAirconControlee.js | 193 +++++ .../dashboard/src/components/CoapWebsocketCodec.js | 933 +++++++++++++++++++++ cloud/dashboard/src/components/ConnectDialog.js | 139 +++ cloud/dashboard/src/components/ErrorMessage.js | 57 ++ .../dashboard/src/components/FirmwareManagement.js | 170 ++++ cloud/dashboard/src/components/MainAppBar.js | 98 +++ cloud/dashboard/src/components/MainMenu.js | 128 +++ cloud/dashboard/src/components/ResourceList.js | 195 +++++ .../src/components/ResourceListToolbar.js | 108 +++ cloud/dashboard/src/components/SigninPage.js | 187 +++++ cloud/dashboard/src/components/SignupButton.js | 198 +++++ cloud/dashboard/src/index.css | 5 + cloud/dashboard/src/index.js | 171 ++++ .../cloud/base/protocols/coap/CoapDecoder.java | 11 +- .../cloud/base/protocols/coap/CoapEncoder.java | 11 +- .../coap/websocket/WebSocketFrameHandler.java | 39 +- 24 files changed, 3382 insertions(+), 21 deletions(-) create mode 100644 cloud/dashboard/.gitignore create mode 100644 cloud/dashboard/README create mode 100644 cloud/dashboard/package.json create mode 100644 cloud/dashboard/public/favicon.ico create mode 100644 cloud/dashboard/public/index.html create mode 100644 cloud/dashboard/src/Client.js create mode 100644 cloud/dashboard/src/components/CoapWebsocketClientSample/controlee_temperatureApp.js create mode 100644 cloud/dashboard/src/components/CoapWebsocketClientSample/controller_ForTemperature.js create mode 100644 cloud/dashboard/src/components/CoapWebsocketClientSample/controller_forAirconControlee.js create mode 100644 cloud/dashboard/src/components/CoapWebsocketCodec.js create mode 100644 cloud/dashboard/src/components/ConnectDialog.js create mode 100644 cloud/dashboard/src/components/ErrorMessage.js create mode 100644 cloud/dashboard/src/components/FirmwareManagement.js create mode 100644 cloud/dashboard/src/components/MainAppBar.js create mode 100644 cloud/dashboard/src/components/MainMenu.js create mode 100644 cloud/dashboard/src/components/ResourceList.js create mode 100644 cloud/dashboard/src/components/ResourceListToolbar.js create mode 100644 cloud/dashboard/src/components/SigninPage.js create mode 100644 cloud/dashboard/src/components/SignupButton.js create mode 100644 cloud/dashboard/src/index.css create mode 100644 cloud/dashboard/src/index.js diff --git a/cloud/dashboard/.gitignore b/cloud/dashboard/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/cloud/dashboard/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/cloud/dashboard/README b/cloud/dashboard/README new file mode 100644 index 0000000..8798448 --- /dev/null +++ b/cloud/dashboard/README @@ -0,0 +1,13 @@ +To Build and Run cloud dashboard + +1) Install Node.js if you don't have it + + https://nodejs.org/en/download/ + +2) Install packages that cloud dashboard depends on + + $ npm install + +3) Run dashboard with command below, and it will be launched on your browser + + $ npm start \ No newline at end of file diff --git a/cloud/dashboard/package.json b/cloud/dashboard/package.json new file mode 100644 index 0000000..aa6c289 --- /dev/null +++ b/cloud/dashboard/package.json @@ -0,0 +1,31 @@ +{ + "name": "dashboard", + "version": "0.1.0", + "private": true, + "devDependencies": { + "react-scripts": "0.9.0" + }, + "engines": { + "node": ">= 0.10" + }, + "dependencies": { + "blob-to-buffer": "^1.2.6", + "material-ui": "^0.17.0", + "node-localstorage": "^1.3.0", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-tap-event-plugin": "^2.0.1", + "websocket": "^1.0.24", + "hashmap": "^2.0.6", + "crypto": "0.0.3", + "express": "^4.14.1", + "json-buffer": "^2.0.11", + "system-sleep": "^1.3.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/cloud/dashboard/public/favicon.ico b/cloud/dashboard/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c125de5d897c1ff5692a656485b3216123dcd89 GIT binary patch literal 24838 zcmeI4X^>UL6@VY56)S&I{`6Nu0RscWCdj@GJHx(%?6_-;yKy1n;EEf9f}pr1CW5HA zYt$%U#C=}?jWH&%G@BaHBxsWAoUb3}&6%Ei@4Ii_JRa1`RQ23*yU)_wJ$?H0>6gj0 z${d_I^w5kvTW3xYEc?FvyP3>p$!py@`@T`|dVepIsjbbvR}af%KKy7YuQ%SDC^zmNWPYR^7avI5P-@dKev}UZ^aDAOyci9Nn zwR4qEz~tSvrp|#ACvWzo9`3B;`}^{t18dxaH;?xT7#hmJiKAaI;|O=$yxzXNOHGw~ z^!5pE^SW`av%t_$22LFPsM^l%=PSp!3r`>9w%s+^ZQYnnTQ*Ggd9-1~kj_o$YdW@b ztCkJ(ZGYjusqV5L4{^)R9Gt@gzU1t|?xhE&c^q(|(R#oa*}Sj5c({A$mhrB8*Y@tc zr)K#C{KOp-eHl35ZWJ1&zkmI>9DL%!KJE@_!=W?aH;i?ZDb0O1HPFy6 zcV0Kf)eZ0BHmz9vowF7EA{z*aue9M)iJP&Zd)qYlfJ-c^sS1qY^?>s)!!Ta@x zr@Lz|80r)7<{QVk9Z$}5SDaVtz*Rc?oH5~Wcjoc^eA&EdJ^h@aZ-BvL{K2s_7Cvfr zFL&(R?D&(9OxsS%z_BzI9^Ai^AOF$PUpGk~oO(=OpMc3@Zh&KH1a9>G%%0rC)t@oQ z4d~M`hX+g^Wf8P>A&&qjq|tZe*44Laq7qVPK#QIc)s*Qj34P`NL`Q{xBI`SnR!RC? zlGdTvC%oVZ@0BgcH>}qc!uzul@{i@sH}L0|=eZBJ9qF!HHaw?`s0(_DJj(v`(memI z6jH}=BfGlSlRV4)ouv#h*65yRR>G zo;I#~BVK&l&{+H=_~Nq$d%bFLh7GE5pS&>Fr{RMe>)MM19~z6F1oQo_y>vtlpEZF# zIc82TpMc3z9;{Q)=zG5B#4+96yHCvYy8p4;C%6x`%y$2HccC9|#vGVD)**C0xX|R| z%h)}ze!Tnrvvb@RZ!GX@2lMEq`=`08b`9$%FnN@*zJLo2wD5?MbE&LN)Z>Kty*;m= zt{Cn0>Q3nk)`bR^{dVf!3ECg6Yz4YcskI>$XH*L8E)MsudhnkP0B>+M(XEcErHUBKi~ z1`fEP&WPhp{@Ew?cPlR(ma9iw8NbJWHqp=btCtM*FnP*@ZwwlJ&-Y|LEjgvJzUtPc zz5CrWNBRV8d0-bpWAl<=zM1PU8lJseDxBK^QuuCj2fg{&2#*IG5ezf1B(o%lU+OZx7So4D?yi2*h zFBkr5pG3AJs83uy!~C3mQZLp~ss7-N9oAY>t)!eC#s)CrPukK!(!G*)H?v(~JCoj# zfvgTxMV{4?zL1neQ;ITVBAdFDf`1yG$o{g7^1sR_n{RZ7tnXio?tM%240}(z9xFY0 zlz{^-G*RET;-`7`>e0b{{`!2kM)t7Si9ZqD$~wh*hyGC>z~qs@0T&u*;h}hiKGEga zHkJ;%7aNc^o_0(>Z{Gp069H;TwPTUnvvX0SJ+kGGZ0lFBWocl>kaa)AoiMta+x_-J-?#KHFnJ*! zwD1V?)4s#|?O)DlMBhVv4IgZs?d>b<6%xK3<{o91H?-%8?PK!_fm#3d>{{gQ z?*8`b{G6?bZKdO{_9IVlz{R$PcGjeL|3*|@upby()_Lf^eQ&XQe)CjsbJ3Uolrgt< zweld3GH|fZpn(=1@PencO_a_)v6tU?WV-w8wfXLbOGae0{<*C?Ead$6v+> z|EQKThJTmwXK!c6AOD+FgtDv7i<48{-OPce!KDVkzR+XKOcREPha(;$}iUb!*)f-Fb}Y4@r9z-_{OIg z`xn^T#ZtEPv_T$M*Sr+=Z{q#~8$|7Y{0!*2u${D*Jj%dfOrS~FzpH*_|55J!7kl4w z?LT!7T(!3!632pmZh?dh`n-z$_ts42pn6;c`}hx;TSYd0idsqal5&0uGV=UM{c9xQ z1KK6&TS+a^H|6B_hPo1W3 zh+Dun!`UkP%H3}*@IE18q{7&MH2f3?T6o}Jf+xI@fh=SyUOArw`*w1_-PUlHZTHc@ z--yqIxPtI}IjPRzLIZ8cPv4P=>?A&=E~~0)>&J#V;TwAR*6}`01iu~U$@prtzW6YS ze}E>gUX+0YuF}B+Uhw2x7a7Q+oOzMNFHTNN<)40Rzg#`pABKF18@l}5A>RL`?Ri;Z zC8ExD$)im1@R{N7(wIog8$Yn(6%q$yd9(zKe};OnH%;mWBs7)>ls~T3Wi6!Xqw6+dpJLVS1P| z9qV%io-nE*rYcPxiS31>U_>mbPTXxkC*!?*zefr#2vF|qr8{|4|u^7-pD|f z&OPc->UKu)=iHgIpysp;Lsbyj}GJWoBkufOA={CRTUjr%af zc5pUH9{pg?M5%+)oN`q9yBbBt@+3xHV)qGm8b)Cp-w7~CwEhtBUk0rbjrqM zTb|tQ3-5-pw^cul`T+X&s?O;?V(FD!(Q9Qg@(LTCNz{0-vBM^SX5lti3|GpxFn4;Ax6pGc~t)R!Bo${lYH(* z!F&5X*?S&}YoDCyzwv1H+XI(+rL`;RN9}iLxlfr-r&vGG8OQa@=>+a)+Ij)sd_{wu z1Am(+3-RFr4&N8N6+hqo19S#;SA1-hG>07p3}&*j4CR+rqdV)^6n; z_vFr!(a%-=#=kb{pYmNL@6|DWkw~%E2V2jYl*e1}c{e$fib?(O+hs}eoBLRo&9(;J}YV}0Mi;LZAe{U$(s= zT<-IaV$Z+q-P!~3{HxN>Kbw30jXzM&I(S<6Ksx^}HvU2Vntb!etSsm0>)j}Me^+L5{2yz--)?W`Q?az z!WLG4UNP}+#C+NKH+ZG-Q=E>IPp%LuKLx$$8NAOGr(#~P>!EA zDYlpXDR=xM?Xv5(-qp74Cw3LzBeASHSBY`OezkbOyjP!G%WSymju_C$VBl--z + + + + + + + + Resource Manager + + +
+ +
+
+ + + diff --git a/cloud/dashboard/src/Client.js b/cloud/dashboard/src/Client.js new file mode 100644 index 0000000..2dc8b4e --- /dev/null +++ b/cloud/dashboard/src/Client.js @@ -0,0 +1,230 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +var EventEmitter = require('events').EventEmitter; +var WS = require('websocket').w3cwebsocket; +var convertBlob = require('blob-to-buffer'); +var HashMap = require('hashmap'); +var coapTokenMap = new HashMap(); +var localStorage = window.localStorage; +if (typeof localStorage === "undefined" || localStorage === null) { + var LocalStorage = require('node-localstorage').LocalStorage; + localStorage = new LocalStorage('./client'); +} + +const coap = require('./components/CoapWebsocketCodec').coap; +const parse = require('./components/CoapWebsocketCodec').parse; +const path = require('./components/CoapWebsocketCodec').path; + +const CONNECTED = 'connected'; +const DISCONNECTED = 'disconnected'; +const SIGNUP = 'signup'; +const SIGNIN = 'signin'; +const SIGNOUT = 'signout'; +const ERROR = 'error'; + +var Client = new function() { + + var ws; + this.event = new EventEmitter(); + + // coap websocket client initialize. + this.init = function(address) { + console.log('client init: ' + address); + + /* TODO websocket secured */ + var serverURL = "ws://" + address + "/.well-known/coap"; + this.ws = new WS(serverURL, 'coap'); + this.ws.onopen = function() { + console.debug('Connected to server ' + address); + Client.event.emit(CONNECTED); + }; + this.ws.onclose = function() { + console.debug('Disconnected from server ' + address); + Client.event.emit(DISCONNECTED); + }; + this.ws.onerror = function() { + console.error('Error occurs'); + Client.event.emit(ERROR, 'Error occurs while websocket connection'); + Client.event.emit(DISCONNECTED); + }; + + // response callback. + this.ws.onmessage = function(event) { + console.debug('Message received -'); + + convertBlob(event.data, function (err, buffer) { + if (err) throw err; + + var packet = parse(buffer); + console.debug(packet); + console.debug(packet.getPayloadObject); + + var func = coapTokenMap.get(packet.getToken.toString()); + func(packet); + if(packet.getSequenceNumber === -1){ + coapTokenMap.remove(packet.getToken.toString()); + } + }); + }; + } + + // coap websocket client close. + this.close = function() { + console.log('client close'); + this.ws.close(); + Client.event.emit(DISCONNECTED); + } + + // send sign-up request. + this.onSignUp = function(packet) { + if(packet.getCode === 68) { + Client.event.emit(SIGNUP, packet.getPayloadObject.uid, + packet.getPayloadObject.accesstoken); + } else { + Client.event.emit(ERROR, "SignUp Failed" + packet.getCode); + } + } + this.signUp = function(di, provider, authcode) { + console.log('client signUp'); + var payload = { + di: di, + authprovider: provider, + authcode: authcode, + devicetype: "device" + }; + this.ws.send(this.doRequest("POST", path.ACCOUNT_FULL_URI, null, payload, this.onSignUp)); + } + + // send sign-in request. + this.onSignIn = function(packet) { + if(packet.getCode === 68) { + Client.event.emit(SIGNIN, packet.getPayloadObject.uid, + packet.getPayloadObject.accesstoken); + } else { + Client.event.emit(ERROR, "SignIn Failed" + packet.getCode); + } + } + this.signIn = function(di, uid, accesstoken) { + console.log('client signIn'); + var payload = { + di: di, + uid: uid, + accesstoken: accesstoken, + login: true + }; + this.ws.send(this.doRequest("POST", path.ACCOUNT_SESSION_FULL_URI, null, payload, this.onSignIn)); + } + + // send sign-out request. + this.onSignOut = function(packet) { + if(packet.getCode === 68) { + Client.event.emit(SIGNOUT); + } else { + Client.event.emit(ERROR, "SignOut Failed" + packet.getCode); + } + } + this.signOut = function(di, accesstoken) { + console.log('client signOut'); + var payload = { + di: di, + accesstoken: accesstoken, + login: false + }; + this.ws.send(this.doRequest("POST", path.ACCOUNT_SESSION_FULL_URI, null, payload, this.onSignOut)); + } + + // send resource discovery request. + this.discoverResource = function(queries, response) { + console.log('client discoverResource ' + queries); + this.ws.send(this.doRequest("GET", path.WELL_KNOWN_FULL_URI, queries, null, response)); + } + + // send control message. + this.sendMessage = function(uri, method, payload, queries, response) { + console.log('client sendMessage'); + this.ws.send(this.doRequest(method, uri, queries, payload, response)); + } + + this.doRequest = function(method, uri, query, payload, response) + { + var newCoaptoken = require('crypto').randomBytes(8); + coapTokenMap.set(newCoaptoken.toString(), response); + return coap.createTokenRequest(newCoaptoken, method, uri, query, payload); + } + + + // erase data in local storage + this.removeClientData = function(keyArray) { + for (var i = 0; i < keyArray.length; i++) { + localStorage.removeItem(keyArray[i]); + } + }; + + this.writeClientData = function(keyValArray) { + for (var i = 0; i < keyValArray.length; i++) { + localStorage.setItem(keyValArray[i][0], keyValArray[i][1]); + } + }; + + this.readClientData = function(key) { + return localStorage.getItem(key); + }; + + + this.getResourceList = function(discoveryPayload) { + if (discoveryPayload === null) { + return []; + } + + var resourceList = []; + + console.debug("Discovered devices: " + discoveryPayload.length); + for (var i = 0; i < discoveryPayload.length; i++) { + for (var j = 0; j < discoveryPayload[i].links.length; j++) { + console.debug("[" + i + "] " + discoveryPayload[i].links[j].href); + resourceList.push({ + n: discoveryPayload[i].n, + di: discoveryPayload[i].di, + uri: discoveryPayload[i].links[j].href, + rts: this.buildArrayString(discoveryPayload[i].links[j].rt), + ifs: this.buildArrayString(discoveryPayload[i].links[j].if) }); + } + } + + return resourceList; + } + + this.buildArrayString = function(array) { + var result = ''; + var seperates = ', '; + + for (var i = 0; i < array.length; i++) { + // last data + if (i === array.length -1) seperates = ''; + result += array[i] + seperates; + } + + return result; + } +}(); + +module.exports = Client; diff --git a/cloud/dashboard/src/components/CoapWebsocketClientSample/controlee_temperatureApp.js b/cloud/dashboard/src/components/CoapWebsocketClientSample/controlee_temperatureApp.js new file mode 100644 index 0000000..9d20237 --- /dev/null +++ b/cloud/dashboard/src/components/CoapWebsocketClientSample/controlee_temperatureApp.js @@ -0,0 +1,260 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +const coap = require('../CoapWebsocketCodec').coap; +const parse = require('../CoapWebsocketCodec').parse; +const path = require('../CoapWebsocketCodec').path; +var WebSocketClient = require('websocket').client; +var client = new WebSocketClient(); +var sleep = require('system-sleep'); + +var readline = require('readline'); +var r = readline.createInterface({ + input : process.stdin, + output : process.stdout +}); + +var seqNum = 1; +var temperature = 0.0; +var observeNotify = true; +var arg = new Array(); +var deviceId = "61646d69-6e44-6576-6963-234231423441"; + +process.argv.forEach(function(val, index, array) { + arg[index] = val; +}); + +client.on('connectFailed', function(error) { + console.log('Connect Error: ' + error.toString()); +}); + +client.on('connect', function(connection) { + console.log('WebSocket Client Connected'); + + connection.on('error', function(error) { + console.log("Connection Error: " + error.toString()); + }); + connection.on('close', function() { + console.log('echo-protocol Connection Closed'); + }); + connection.on('message', function(message) { + if (message.type != 'binary') + console.log("message type is not binary!!"); + + var packet = parse(message.binaryData); + + // console.log(); + console.log('Received Binary Message of ' + message.binaryData.length + ' bytes'); + console.log('received Token : ' + packet.getToken); + console.log('received uriPath : ' + packet.getUriPath); + console.log('received UriQuery : ' + packet.getUriQuery); + console.log('received payload : ' + packet.getPayloadString); + console.log('received getSequenceNumber : ' + packet.getSequenceNumber); + console.log('received getMethod : ' + packet.getMethod); + console.log('received getCode : ' + packet.getCode); + console.log(); + + if (packet.getCode < 5) { + console.log("Received Request Message"); + switch(packet.getMethod) { + case "GET": + console.log("request GET"); + + var response = coap.createResponse(packet, "CONTENT", { + temperature : 0.0, + units : "C" + }); + connection.sendBytes(response); + + break; + case "GET OBSERVE": + console.log("request GET OBSERVE"); + while (temperature < 1.3) { + var response = coap.createNotifyResponse(packet, "CONTENT", { + temperature : temperature, + units : "C" + }, seqNum++); + connection.sendBytes(response); + sleep(100); + temperature += 0.1; + } + break; + + case "GET OBSERVE CANCEL": + console.log("request GET OBSERVE CANCEL"); + observeNotify = false; + var response = coap.createNotifyResponse(packet, "CONTENT", null); + connection.sendBytes(response); + break; + } + } else if (packet.getCode < 70) { + + switch(packet.getUriPath) { + + case path.ACCOUNT_FULL_URI: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : packet.getPayloadObject.uid, + accesstoken : packet.getPayloadObject.accesstoken, + login : true + }; + connection.sendBytes(signIn(payload)); + break; + + case path.ACCOUNT_SESSION_FULL_URI: + console.log('STEP 3 : publish Resource'); + connection.sendBytes(publishResource('rt=oic.wk.rdpub', { + di : "61646d69-6e44-6576-6963-234231423441", + n : "Air Conditioner", + lt : 86400, + links : [{ + href : "/oic/d", + rt : ["oic.wk.d", "oic.d.airconditioner"], + if : ["oic.if.baseline", "oic.if.r"], + ins : 0, + type : ["application/json"], + p : { + bm : 1 + } + }, { + href : "/oic/p", + rt : ["oic.wk.p"], + if : ["oic.if.baseline", "oic.if.r"], + ins : 0, + type : ["application/json"], + p : { + bm : 1 + } + }] + })); + + connection.sendBytes(publishResource('rt=oic.wk.rdpub', { + di : "61646d69-6e44-6576-6963-234231423441", + n : "Air Conditioner", + lt : 86400, + links : [{ + href : "/temperature", + rt : ["oic.r.temperature"], + if : ["oic.if.baseline"], + ins : 0, + type : ["application/json"], + p : { + bm : 3 + } + }] + })); + break; + case path.RD_FULL_URI: + break; + + case path.WELL_KNOWN_FULL_URI: + if (packet.getPayloadObject[0].links.length == 1) { + } else { + console.log('STEP 4 : Send Observe Presence'); + connection.sendBytes(observePresence('di=' + packet.getPayloadObject[0].di)); + console.log('STEP 5 : Send GET Observe Message'); + connection.sendBytes(observeMessage(packet.getPayloadObject[0].links[2].href, 'if=' + packet.getPayloadObject[0].links[2].if[0] + ';rt=' + packet.getPayloadObject[0].links[2].rt[0], null)); + + } + break; + + case path.DEVICE_PRESENCE_FULL_URI: + console.log('response for DEVICE_PRESENCE_FULL_URI'); + break; + } + + if (packet.getUriPath.indexOf(path.ROUTE_FULL_URI) > -1) { + console.log('temperature : ' + packet.getPayloadObject.temperature); + } + } else { + console.log("error : " + packet.getMethod); + } + }); + + function sendCoapPacketforSignUpIn() { + if (connection.connected) { + var count = process.argv.length; + switch(count) { + case 4: + console.log('STEP 1 : sign Up'); + var payload = { + di : deviceId, + authprovider : "github", + authcode : arg[3], + devicetype : "device" + }; + connection.sendBytes(signUp(payload)); + break; + case 5: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : arg[3], + accesstoken : arg[4], + login : true + }; + connection.sendBytes(signIn(payload)); + break; + default: + console.log("argument is not valid!!"); + break; + } + } + } + + sendCoapPacketforSignUpIn(); +}); + +function signUp(payload) { + return postMessage(path.ACCOUNT_FULL_URI, null, payload); +} + +function signIn(payload) { + return postMessage(path.ACCOUNT_SESSION_FULL_URI, null, payload); +} + +function findResource(uriQury) { + return getMessage(path.WELL_KNOWN_FULL_URI, uriQury, null); +} + +function publishResource(uriQury, payload) { + return postMessage(path.RD_FULL_URI, uriQury, payload); +} + +function observePresence(uriQury) { + return observeMessage(path.DEVICE_PRESENCE_FULL_URI, uriQury, null); +} + +function postMessage(uriPath, uriQury, payload) { + return coap.createRequest("POST", uriPath, uriQury, payload); +} + +function getMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET", uriPath, uriQury, payload); +} + +function observeMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET OBSERVE", uriPath, uriQury, payload); +}; + +//client.connect('ws://52.78.151.180:8080/.well-known/coap', 'coap'); +client.connect('ws://' + arg[2] + '/.well-known/coap', 'coap'); diff --git a/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_ForTemperature.js b/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_ForTemperature.js new file mode 100644 index 0000000..e851777 --- /dev/null +++ b/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_ForTemperature.js @@ -0,0 +1,190 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +const coap = require('../CoapWebsocketCodec').coap; +const parse = require('../CoapWebsocketCodec').parse; +const path = require('../CoapWebsocketCodec').path; +var WebSocketClient = require('websocket').client; +var client = new WebSocketClient(); + +var readline = require('readline'); +var r = readline.createInterface({ + input : process.stdin, + output : process.stdout +}); + +var arg = new Array(); +var deviceId = "ac59e41e-1111-bfd0-9292-20a95af72863"; + +process.argv.forEach(function(val, index, array) { + arg[index] = val; +}); + +client.on('connectFailed', function(error) { + console.log('Connect Error: ' + error.toString()); +}); + +client.on('connect', function(connection) { + console.log('WebSocket Client Connected'); + + connection.on('error', function(error) { + console.log("Connection Error: " + error.toString()); + }); + connection.on('close', function() { + console.log('echo-protocol Connection Closed'); + }); + connection.on('message', function(message) { + if (message.type != 'binary') + console.log("message type is not binary!!"); + + var packet = parse(message.binaryData); + + // console.log(); + // console.log('Received Binary Message of ' + message.binaryData.length + ' bytes'); + // console.log('received Token : ' + packet.getToken); + // console.log('received uriPath : ' + packet.getUriPath); + // console.log('received UriQuery : ' + packet.getUriQuery); + console.log('received payload : ' + packet.getPayloadString); + // console.log('received getMethod : ' + packet.getMethod); + // console.log('received getCode : ' + packet.getCode); + console.log('received getSequenceNumber : ' + packet.getSequenceNumber); + // console.log(); + + if (packet.getCode < 70) { + switch(packet.getUriPath) { + case path.ACCOUNT_FULL_URI: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : packet.getPayloadObject.uid, + accesstoken : packet.getPayloadObject.accesstoken, + login : true + }; + connection.sendBytes(signIn(payload)); + break; + + case path.ACCOUNT_SESSION_FULL_URI: + console.log('STEP 3 : findResource rt'); + + connection.sendBytes(findResource(null)); + //61646d69-6e44-6576-6963-234231423441 2aaad9a5-a034-62e7-9d39-efd7ec9203d9 ac59e41e-cf97-bfd0-9292-20a95af72863 + case path.RD_FULL_URI: + break; + + case path.WELL_KNOWN_FULL_URI: + if (packet.getPayloadObject[0].links.length == 1) { + } else { + console.log('STEP 4 : Send Observe Presence'); + connection.sendBytes(observePresence('di=' + packet.getPayloadObject[0].di)); + + console.log('STEP 5-2 : Send GET Observe Message'); + connection.sendBytes(observeMessage(packet.getPayloadObject[0].links[2].href, 'if=' + packet.getPayloadObject[0].links[2].if[0] + ';rt=' + packet.getPayloadObject[0].links[2].rt[0], null)); + + } + break; + + case path.DEVICE_PRESENCE_FULL_URI: + console.log('response for DEVICE_PRESENCE_FULL_URI'); + break; + } + + if (packet.getUriPath.indexOf(path.ROUTE_FULL_URI) > -1) { + console.log('temperature : ' + packet.getPayloadObject.temperature); + if(packet.getPayloadObject.temperature == 1.2) + connection.sendBytes(observeCancelMessage(packet.getToken, packet.getUriPath, packet.getUriQuery, null)); + } + } else { + console.log("error : " + packet.getMethod); + } + }); + + function sendCoapPacketforSignUpIn() { + if (connection.connected) { + var count = process.argv.length; + switch(count) { + case 4: + console.log('STEP 1 : sign Up'); + var payload = { + di : deviceId, + authprovider : "github", + authcode : arg[3], + devicetype : "device" + }; + connection.sendBytes(signUp(payload)); + break; + case 5: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : arg[3], + accesstoken : arg[4], + login : true + }; + connection.sendBytes(signIn(payload)); + break; + default: + console.log("argument is not valid!!"); + break; + } + } + } + + sendCoapPacketforSignUpIn(); +}); + +function signUp(payload) { + return postMessage(path.ACCOUNT_FULL_URI, null, payload); +} + +function signIn(payload) { + return postMessage(path.ACCOUNT_SESSION_FULL_URI, null, payload); +} + +function findResource(uriQury) { + return getMessage(path.WELL_KNOWN_FULL_URI, uriQury, null); +} + +function publishResource(uriQury, payload) { + return postMessage(path.RD_FULL_URI, uriQury, payload); +} + +function observePresence(uriQury) { + return observeMessage(path.DEVICE_PRESENCE_FULL_URI, uriQury, null); +} + +function postMessage(uriPath, uriQury, payload) { + return coap.createRequest("POST", uriPath, uriQury, payload); +} + +function getMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET", uriPath, uriQury, payload); +} + +function observeMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET OBSERVE", uriPath, uriQury, payload); +}; + +function observeCancelMessage(token, uriPath, uriQury, payload) { + return coap.createTokenRequest(token, "GET OBSERVE CANCEL", uriPath, uriQury, payload); +}; + +//client.connect('ws://52.78.151.180:8080/.well-known/coap', 'coap'); +client.connect('ws://' + arg[2] + '/.well-known/coap', 'coap'); diff --git a/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_forAirconControlee.js b/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_forAirconControlee.js new file mode 100644 index 0000000..d2b26ef --- /dev/null +++ b/cloud/dashboard/src/components/CoapWebsocketClientSample/controller_forAirconControlee.js @@ -0,0 +1,193 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +const coap = require('../CoapWebsocketCodec').coap; +const parse = require('../CoapWebsocketCodec').parse; +const path = require('../CoapWebsocketCodec').path; +var WebSocketClient = require('websocket').client; +var client = new WebSocketClient(); + +var readline = require('readline'); +var r = readline.createInterface({ + input : process.stdin, + output : process.stdout +}); + +var arg = new Array(); +var deviceId = "ac59e41e-4444-bfd0-9292-20a95af72863"; + +process.argv.forEach(function(val, index, array) { + arg[index] = val; +}); + +client.on('connectFailed', function(error) { + console.log('Connect Error: ' + error.toString()); +}); + +client.on('connect', function(connection) { + console.log('WebSocket Client Connected'); + + connection.on('error', function(error) { + console.log("Connection Error: " + error.toString()); + }); + connection.on('close', function() { + console.log('echo-protocol Connection Closed'); + }); + connection.on('message', function(message) { + if (message.type != 'binary') + console.log("message type is not binary!!"); + + var packet = parse(message.binaryData); + + // console.log(); + // console.log('Received Binary Message of ' + message.binaryData.length + ' bytes'); + // console.log('received Token : ' + packet.getToken); + console.log('received uriPath : ' + packet.getUriPath); + // console.log('received UriQuery : ' + packet.getUriQuery); + console.log('received payload : ' + packet.getPayloadString); + // console.log('received getMethod : ' + packet.getMethod); + // console.log('received getCode : ' + packet.getCode); + console.log('received getSequenceNumber : ' + packet.getSequenceNumber); + // console.log(); + + if (packet.getCode < 70) { + switch(packet.getUriPath) { + case path.ACCOUNT_FULL_URI: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : packet.getPayloadObject.uid, + accesstoken : packet.getPayloadObject.accesstoken, + login : true + }; + connection.sendBytes(signIn(payload)); + break; + + case path.ACCOUNT_SESSION_FULL_URI: + console.log('STEP 3 : findResource rt'); + + connection.sendBytes(findResource(null)); + //61646d69-6e44-6576-6963-234231423441 2aaad9a5-a034-62e7-9d39-efd7ec9203d9 ac59e41e-cf97-bfd0-9292-20a95af72863 + case path.RD_FULL_URI: + break; + + case path.WELL_KNOWN_FULL_URI: + if (packet.getPayloadObject[0].links.length == 1) { + } else { + console.log('STEP 4 : Send Observe Presence'); + connection.sendBytes(observePresence('di=' + packet.getPayloadObject[0].di)); + + console.log('STEP 5-1 : Send GET Message'); + connection.sendBytes(getMessage(packet.getPayloadObject[0].links[2].href, 'if=' + packet.getPayloadObject[0].links[2].if[2])); + //+ ';rt=' + packet.getPayloadObject[0].links[2].rt[0], null)); + + } + break; + + case path.DEVICE_PRESENCE_FULL_URI: + console.log('response for DEVICE_PRESENCE_FULL_URI'); + break; + } + + if (packet.getUriPath.indexOf(path.ROUTE_FULL_URI) > -1) { + if (packet.getUriPath.indexOf('aircon') > -1) { + connection.sendBytes(observeMessage(packet.getPayloadObject[1].href, 'if=' + packet.getPayloadObject[1].if[0] + ';rt=' + packet.getPayloadObject[1].rt[0], null)); + } else { + console.log("power value : " + packet.getPayloadObject.value); + } + } + } else { + console.log("error : " + packet.getMethod); + } + }); + + function sendCoapPacketforSignUpIn() { + if (connection.connected) { + var count = process.argv.length; + switch(count) { + case 4: + console.log('STEP 1 : sign Up'); + var payload = { + di : deviceId, + authprovider : "github", + authcode : arg[3], + devicetype : "device" + }; + connection.sendBytes(signUp(payload)); + break; + case 5: + console.log('STEP 2 : Sign in'); + var payload = { + di : deviceId, + uid : arg[3], + accesstoken : arg[4], + login : true + }; + connection.sendBytes(signIn(payload)); + break; + default: + console.log("argument is not valid!!"); + break; + } + } + } + + sendCoapPacketforSignUpIn(); +}); + +function signUp(payload) { + return postMessage(path.ACCOUNT_FULL_URI, null, payload); +} + +function signIn(payload) { + return postMessage(path.ACCOUNT_SESSION_FULL_URI, null, payload); +} + +function findResource(uriQury) { + return getMessage(path.WELL_KNOWN_FULL_URI, uriQury, null); +} + +function publishResource(uriQury, payload) { + return postMessage(path.RD_FULL_URI, uriQury, payload); +} + +function observePresence(uriQury) { + return observeMessage(path.DEVICE_PRESENCE_FULL_URI, uriQury, null); +} + +function postMessage(uriPath, uriQury, payload) { + return coap.createRequest("POST", uriPath, uriQury, payload); +} + +function getMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET", uriPath, uriQury, payload); +} + +function observeMessage(uriPath, uriQury, payload) { + return coap.createRequest("GET OBSERVE", uriPath, uriQury, payload); +}; + +function observeCancelMessage(token, uriPath, uriQury, payload) { + return coap.createTokenRequest(token, "GET OBSERVE CANCEL", uriPath, uriQury, payload); +}; + +//client.connect('ws://52.78.151.180:8080/.well-known/coap', 'coap'); +client.connect('ws://' + arg[2] + '/.well-known/coap', 'coap'); diff --git a/cloud/dashboard/src/components/CoapWebsocketCodec.js b/cloud/dashboard/src/components/CoapWebsocketCodec.js new file mode 100644 index 0000000..2bbf24a --- /dev/null +++ b/cloud/dashboard/src/components/CoapWebsocketCodec.js @@ -0,0 +1,933 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +var empty = new Buffer(0) + +// a global index for parsing the options and the payload +// we can do this as the parsing is a sync operation +, + index, + lengthfield + +// last five bits are 1 +// 31.toString(2) => '111111' +, + lowerCodeMask = 31, + nextMsgId = Math.floor(Math.random() * 65535), + codes; + +codes = { + 'GET' : 1, + 'GET OBSERVE' : 1, + 'GET OBSERVE CANCEL' : 1, + 'POST' : 2, + 'PUT' : 3, + 'DELETE' : 4, + + 'CREATED' : 65, + 'DELETED' : 66, + 'VALID' : 67, + 'CHANGED' : 68, + 'CONTENT' : 69, + + 'BAD_REQUEST' : 128, + 'UNAUTHORIZED' : 129, + 'BAD_OPTION' : 130, + 'FORBIDDEN' : 131, + 'NOT_FOUND' : 132, + 'METHOD_NOT_ALLOWED' : 133, + 'NOT_ACCEPTABLE' : 134, + + 'PRECONDITION_FAILED' : 140, + 'REQUEST_ENTITY_TOO_LARGE' : 141, + 'UNSUPPORTED_CONTENT_FORMAT' : 143, + + 'INTERNAL_SERVER_ERROR' : 160, + 'NOT_IMPLEMENTED' : 161, + 'BAD_GATEWAY' : 162, + 'SERVICE_UNAVAILABLE' : 163, + 'GATEWAY_TIMEOUT' : 164, + 'PROXY_NOT_SUPPORTED' : 165, +}; + +module.exports.path = { + 'ACCOUNT_FULL_URI' : '/oic/account', + 'ACCOUNT_SEARCH_FULL_URI' : '/oic/account/search', + 'ACCOUNT_SESSION_FULL_URI' : '/oic/account/session', + 'ACCOUNT_TOKENREFRESH_FULL_URI' : '/oic/account/tokenrefresh', + 'KEEP_ALIVE_FULL_URI' : '/oic/ping', + 'ROUTE_FULL_URI' : '/oic/route', + 'RD_FULL_URI' : '/oic/rd', + 'WELL_KNOWN_FULL_URI' : '/oic/res', + 'DEVICE_PRESENCE_FULL_URI' : '/oic/prs', +}; + +module.exports.coap = { + + createRequest : function(requestMethod, uriPath, uriQuery, payload) { + return createMessage(null, requestMethod, uriPath, uriQuery, payload, null); + }, + + createTokenRequest : function(token, requestMethod, uriPath, uriQuery, payload) { + return createMessage(token, requestMethod, uriPath, uriQuery, payload, null); + }, + + createResponse : function(request, responseMethod, payload) { + return createMessage(request.getToken, responseMethod, request.getUriPath, request.getUriQuery, payload, null); + }, + + createNotifyResponse : function(request, responseMethod, payload, seqNum) { + return createMessage(request.getToken, responseMethod, request.getUriPath, request.getUriQuery, payload, seqNum); + } +}; + +module.exports.parse = function parse(buffer) { + + index = 1; + var result = { + getCode : getCode(buffer), + getMethod : getMethod(buffer), + getToken : getToken(buffer), + getUriPath : getUriPath(buffer), + getUriPathSegment : getUriPathSegment(buffer), + getUriQuery : getUriQuery(buffer), + getUriQuerySegment : getUriQuerySegment(buffer), + getPayloadString : getPayloadString(buffer), + getPayloadObject : getPayloadObject(buffer), + getSequenceNumber : getSequenceNumber(buffer), + + }; + + return result; +}; + +function createMessage(token, requestMethod, uriPath, uriQuery, payload, seqNum) { + var tokenValue, + codeValue = (codes[requestMethod] > -1 ? codes[requestMethod] : toCode(requestMethod)), + optionsValue = new Array(), + payloadValue; + if (token != null) { + tokenValue = new Buffer(token); + } else { + tokenValue = require('crypto').randomBytes(8); + } + + if (payload != null) + payloadValue = new Buffer(JSON.stringify(payload)); + console.log(); + console.log("Token : " + tokenValue); + console.log("Method : " + requestMethod); + console.log("Request uri : " + uriPath); + console.log("Request query : " + uriQuery); + console.log("Request payload : " + payloadValue); + console.log("Request seqNum : " + seqNum); + + if (seqNum != null) { + var buf = Buffer.allocUnsafe(3); + buf.writeUInt8(seqNum & 0xFF, 2); + buf.writeUInt8((seqNum >> 8) & 0xFF, 1); + buf.writeUInt8((seqNum >> 16) & 0xFF, 0); + optionsValue.push({ + name : 'Observe', + value : buf + }); + + } else { + if (requestMethod.indexOf('OBSERVE CANCEL') > -1) { + optionsValue.push({ + name : 'Observe', + value : Buffer.alloc(1, 1) + }); + } else if (requestMethod.indexOf('OBSERVE') > -1) { + optionsValue.push({ + name : 'Observe', + value : Buffer.alloc(0) + }); + } + } + + if (uriPath != null) { + uriPath = setUriPath(uriPath); + while (uriPath.length != 0) { + var path = uriPath.shift(); + optionsValue.push(path); + } + } + + optionsValue.push({ + name : 'Content-Format', + value : Buffer.alloc(1, 50) + }); + + if (uriQuery != null) { + uriQuery = setUriQuery(uriQuery); + while (uriQuery.length != 0) { + var query = uriQuery.shift(); + optionsValue.push(query); + } + } + + var packet = encoder({ + token : tokenValue, + code : codeValue, + payload : payloadValue, + options : optionsValue + }); + + return packet; +} + +function getkey(value) { + var flag = false; + var keyVal; + for (var key in codes) { + if (codes[key] == value) { + flag = true; + keyVal = key; + break; + } + } + if (flag) { + return keyVal; + } else { + return false; + } +} + +function getMethod(packet) { + var value = toCode(decoder(packet).code); + if (value == 1) { + + var observeOption = getSequenceNumber(packet).pop(); + var observe = 0; + if(observeOption.length == 3) { + ; + } else { + observe += observeOption & 0xFF; + } + + if (observe == -1) + ; + else if (observe == 1) + return 'GET OBSERVE CANCEL'; + else + return 'GET OBSERVE'; + } + var str = getkey(toCode(decoder(packet).code)); + return str; +} + +function getCode(packet) { + return toCode(decoder(packet).code); +} + +function getToken(packet) { + return decoder(packet).token; +} + +function getUriPath(packet) { + var uriPath = new String(), + value = getOptionValue(packet, 'Uri-Path'); + + while (value.length != 0) { + uriPath += ("/" + value.pop()); + } + + return uriPath; +} + +function getUriPathSegment(packet) { + var uriPathSegment = new Array(), + value = getOptionValue(packet, 'Uri-Path'); + + while (value.length != 0) { + uriPathSegment.push(value.pop()); + } + return uriPathSegment; +} + +function getPayloadString(packet) { + var payload = decoder(packet).payload; + if (payload.length != 0) + return decoder(packet).payload; + return null; +} + +function getPayloadObject(packet) { + var payload = decoder(packet).payload; + if (payload.length != 0) + return JSON.parse(decoder(packet).payload); + return null; +} + +function getPayloadValue(packet, property) { + if (payload.length != 0) { + var payload = JSON.parse(decoder(packet).payload); + if (payload.property != null) { + return payload.property; + } + } + return null; +} + +function setUriPath(path) { + var options = new Array(); + var pathArray = path.split('/'); + for (var i = 0; i < pathArray.length; i++) { + var value = { + name : 'Uri-Path', + value : new Buffer(pathArray[i]) + }; + if (pathArray[i].length != 0) { + options.push(value); + } + } + return options; +} + +function setUriQuery(query) { + var options = new Array(); + var queryArray = query.split(';'); + for (var i = 0; i < queryArray.length; i++) { + var value = { + name : 'Uri-Query', + value : new Buffer(queryArray[i]) + }; + if (queryArray[i].length != 0 && queryArray[i] != null) { + options.push(value); + } + } + return options; +} + +function getUriQuery(packet) { + var uriQuery = new String(), + value = getOptionValue(packet, 'Uri-Query'); + + if (value.length == 0 || value == null) + return null; + + while (value.length != 0) { + uriQuery += value.pop(); + if (value.length != 0) + uriQuery += ";"; + } + return uriQuery; +} + +function getSequenceNumber(packet) { + var value = getOptionValue(packet, 'Observe'); + + if (value.length == 0) + return -1; + + return value; +} + +function getUriQuerySegment(packet) { + var uriQuerySegment = new Array(), + value = getOptionValue(packet, 'Uri-Path'); + + if (value.length == 0) + return null; + + while (value.length != 0) { + var valueArray = value.pop().toString().split('='); + uriQuerySegment.push(valueArray[1]); + } + return uriQuerySegment; +} + +function getOptionValue(packet, optionName) { + var value = new Array(), + options = decoder(packet).options; + while (options.length != 0) { + var option = options.pop(); + if (option.name == optionName) { + value.push(option.value); + } + } + return value; +} + +function encoder(packet) { + var buffer, + byte, + pos = 0, + options, + i, + bufferLength, + length; + + packet = fillGenDefaults(packet); + options = prepareOptions(packet); + length = calculateLength(packet, options); + bufferLength = calculateBuffer(packet, length); + + console.log("length for Option and Payload : " + length); + + if (bufferLength > 4294967295 + 65535 + 1 + 14) + throw new Error('Max packet size is ' + (4294967295 + 65535 + 1 + 14) + ': current is ' + bufferLength); + + buffer = new Buffer(bufferLength); + + byte = 0; + // The Length (Len) field MUST be set to zero + // because the WebSockets frame containsthe length. + // byte |= calulateExtendedLength(length) << 4; + // first four bits are Length + byte |= packet.token.length; + // last four bits are Token Length + buffer.writeUInt8(byte, pos++); + + // if (length < 13) { + // ; + // } else if (length < 256 + 13 + 1) { + // buffer.writeUInt8(length - 13, pos++); + // } else if (length < 65535 + 269 + 1) { + // buffer.writeUInt16BE(length - 269, pos); + // pos += 2; + // } else if (length < 4294967295 + 65535 + 1) { + // buffer.writeUInt8(length - 65805, pos); + // pos += 2; + // } else { + // console.log('Length must be less than 4GB, current is ' + length); + // } + + // code can be humized or not + if (codes[packet.code]) + buffer.writeUInt8(codes[packet.code], pos++); + else + buffer.writeUInt8(toCode(packet.code), pos++); + + // the token might be an empty buffer + packet.token.copy(buffer, pos); + pos += packet.token.length; + + // write the options + for ( i = 0; i < options.length; i++) { + options[i].copy(buffer, pos); + pos += options[i].length; + } + + if (packet.code !== '0.00' && packet.payload != '') { + + // payload separator + buffer.writeUInt8(255, pos++); + packet.payload.copy(buffer, pos); + } + + return buffer; +} + +module.exports.generater = function generater(packet) { + var buffer, + byte, + pos = 0, + options, + i, + bufferLength, + length; + + /* fillGenDefaults : add default values to generate packet */ + packet = fillGenDefaults(packet); + options = prepareOptions(packet); + length = calculateLength(packet, options); + bufferLength = calculateBuffer(packet, length); + + console.log("length for Option and Payload : " + length); + + if (bufferLength > 4294967295 + 65535 + 1 + 14) + throw new Error('Max packet size is ' + (4294967295 + 65535 + 1 + 14) + ': current is ' + bufferLength); + + buffer = new Buffer(bufferLength); + + // first byte + byte = 0; + // byte |= calulateExtendedLength(length) << 4; + // first four bits are Length + byte |= packet.token.length; + // last four bits are Token Length + buffer.writeUInt8(byte, pos++); + + // if (length < 13) { + // ; + // } else if (length < 256 + 13 + 1) { + // buffer.writeUInt8(length - 13, pos++); + // } else if (length < 65535 + 269 + 1) { + // buffer.writeUInt16BE(length - 269, pos); + // pos += 2; + // } else if (length < 4294967295 + 65535 + 1) { + // buffer.writeUInt8(length - 65805, pos); + // pos += 2; + // } else { + // console.log('Length must be less than 4GB, current is ' + length); + // } + + + // code can be humized or not + if (codes[packet.code]) + buffer.writeUInt8(codes[packet.code], pos++); + else + buffer.writeUInt8(toCode(packet.code), pos++); + + // the token might be an empty buffer + packet.token.copy(buffer, pos); + pos += packet.token.length; + + // write the options + for ( i = 0; i < options.length; i++) { + options[i].copy(buffer, pos); + pos += options[i].length; + } + + if (packet.code !== '0.00' && packet.payload != '') { + + // payload separator + buffer.writeUInt8(255, pos++); + packet.payload.copy(buffer, pos); + } + + return buffer; +}; + +// module.exports.decoder = function decoder(buffer) { +function decoder(buffer) { + index = 1; + lengthfield = parseLength(buffer); + + if (lengthfield >= 13) { + var extendedLength = parseExtendedLength(buffer, lengthfield); + } + + var result = { + code : parseCode(buffer), + token : parseToken(buffer), + options : null, + payload : null, + }; + + if (result.code !== '0.00') { + result.options = parseOptions(buffer); + result.payload = buffer.slice(index + 1); + } else { + if (buffer.length != 4) + throw new Error('Empty messages must be empty'); + + result.options = []; + result.payload = empty; + } + + return result; +}; + +function parseLength(buffer) { + var length = buffer.readUInt8(0) >> 4; + + // if (length == 0) + // throw new Error('Message Length is 0'); + + // console.log('length : ' + length); + + return length; +} + +function parseExtendedLength(buffer, lengthfield) { + var extendedLength; + + if (lengthfield == 13) { + extendedLength = buffer.slice(index, index++) + 13; + } else if (lengthfield == 14) { + extendedLength = buffer.slice(index, index + 2) + 269; + index += 2; + } else if (lengthfield == 15) { + extendedLength = buffer.slice(index, index + 4) + 65805; + index += 4; + } else + throw new Error('lengthfield is invalid : current lengthfield is ' + lengthfield); + + // console.log('extendedLength : ' + extendedLength); + + return extendedLength; +} + +function parseCode(buffer) { + var byte = buffer.readUInt8(index), + code = '' + (byte >> 5) + '.'; + + index++; + + byte = byte & lowerCodeMask; + + if (byte < 10) + code += '0' + byte; + else + code += byte; + + // console.log('code : ' + code); + + return code; +} + +function parseToken(buffer) { + var length = buffer.readUInt8(0) & 0x0F, + token; + + if (length > 8) { + throw new Error('Token length not allowed'); + } + + token = buffer.slice(index, index + length); + + index += length; + + // console.log('token : ' + token); + + return token; +} + +var numMap = { + '1' : 'If-Match', + '3' : 'Uri-Host', + '4' : 'ETag', + '5' : 'If-None-Match', + '6' : 'Observe', + '7' : 'Uri-Port', + '8' : 'Location-Path', + '11' : 'Uri-Path', + '12' : 'Content-Format', + '14' : 'Max-Age', + '15' : 'Uri-Query', + '17' : 'Accept', + '20' : 'Location-Query', + '35' : 'Proxy-Uri', + '39' : 'Proxy-Scheme', + '60' : 'Size1' +}; + +var optionNumberToString = (function genOptionParser() { + + var code = Object.keys(numMap).reduce(function(acc, key) { + + acc += 'case ' + key + ':\n'; + acc += ' return \'' + numMap[key] + '\'\n'; + + return acc; + }, 'switch(number) {\n'); + + code += 'default:\n'; + code += 'return \'\' + number'; + code += '}\n'; + + return new Function('number', code); +})(); + +function parseOptions(buffer) { + + var byte, + number = 0, + delta, + length, + nextOption = true, + options = [], + option; + + while (index < buffer.length) { + byte = buffer.readUInt8(index); + + if (byte === 255 || index > buffer.length) { + break; + } + + delta = byte >> 4; + length = byte & 15; + + index += 1; + + if (delta === 13) { + delta = buffer.readUInt8(index) + 13; + index += 1; + } else if (delta === 14) { + delta = buffer.readUInt16BE(index) + 269; + index += 2; + } else if (delta === 15) { + throw new Error('Wrong option delta'); + } + + if (length === 13) { + length = buffer.readUInt8(index) + 13; + index += 1; + } else if (length === 14) { + length = buffer.readUInt16BE(index) + 269; + index += 2; + } else if (length === 15) { + throw new Error('Wrong option length'); + } + + number += delta; + + var name = optionNumberToString(number), + value = buffer.slice(index, index + length); + + // console.log("name : " + name); + // console.log("value : " + value.toString()); + // console.log(); + + options.push({ + name : name, + value : value + }); + + index += length; + } + + return options; +} + +function toCode(code) { + var split = code.split && code.split('.'), + by = 0; + + if (split && split.length === 2) { + by |= parseInt(split[0]) << 5; + by |= parseInt(split[1]); + } else { + + if (!split) + code = parseInt(code); + + by |= (code / 100) << 5; + by |= (code % 100); + } + + return by; +} + +function fillGenDefaults(packet) { + if (!packet) + packet = {}; + + if (!packet.payload) + packet.payload = empty; + + if (!packet.token) + packet.token = empty; + + if (packet.token.length > 8) + throw new Error('Token too long'); + + if (!packet.code) + packet.code = '0.01'; + + if (!packet.options) + packet.options = []; + + return packet; +} + +function calculateBuffer(packet, length) { + var extendedLengthValue = calulateExtendedLength(length), + extendLength; + + if (extendedLengthValue < 13) { + extendLength = 0; + } else if (extendedLengthValue == 13) { + extendLength = 1; + } else if (extendedLengthValue == 14) { + extendLength = 2; + } else if (extendedLengthValue == 15) { + extendLength = 4; + } else { + console.log('Length must be less than 4GB, current is ' + length); + } + + length += (2 + packet.token.length); //+ extendLength); + + return length; +} + +function calculateLength(packet, options) { + + var length = packet.payload.length, + i, + lengthValue; + + if (packet.payload != '') + length += 1; + + for ( i = 0; i < options.length; i++) { + length += options[i].length; + } + + return length; +} + +function calulateExtendedLength(length) { + var lengthValue; + if (length < 13) { + lengthValue = length; + } else if (length < 256 + 13 + 1) { + lengthValue = 13; + } else if (length < 65535 + 269 + 1) { + lengthValue = 14; + } else if (length < 4294967295 + 65535 + 1) { + lengthValue = 15; + } else { + console.log('Length must be less than 4GB, current is ' + length); + } + return lengthValue; +} + +var optionStringToNumber = (function genOptionParser() { + + var code = Object.keys(numMap).reduce(function(acc, key) { + + acc += 'case \'' + numMap[key] + '\':\n'; + acc += ' return \'' + key + '\'\n'; + + return acc; + }, 'switch(string) {\n'); + + code += 'default:\n'; + code += 'return parseInt(string)'; + code += '}\n'; + + return new Function('string', code); +})(); + +var nameMap = Object.keys(numMap).reduce(function(acc, key) { + acc[numMap[key]] = key; + return acc; +}, {}); + +function optionSorter(a, b) { + a = a.name; + b = b.name; + + a = parseInt(nameMap[a] || a); + b = parseInt(nameMap[b] || b); + console.log("a : " + a + ", b : " + b); + if (a < b) + return -1; + if (a > b) + return 1; + + return 0; +} + +function prepareOptions(packet) { + var options = [], + total = 0, + delta, + buffer, + byte, + option, + i, + bufferSize, + pos, + value; + + + // packet.options.sort(optionSorter); + + //console.log("path in prepareOptions : " + getUriPath(packet)); + + /* add delta, length and value to buffer for each option in order */ + console.log(); + console.log('packet.options.length : ' + packet.options.length); + console.log(); + + for ( i = 0; i < packet.options.length; i++) { + pos = 0; + option = packet.options[i].name; + delta = optionStringToNumber(option) - total; + value = packet.options[i].value; + + // console.log('turn : ' + i); + // console.log('optionStringToNumber(option) : ' + optionStringToNumber(option)); + // console.log('delta : ' + delta); + // console.log('value.length : ' + value.length); + // console.log('value : ' + value); + + // max option length is 1 header, 2 ext numb, 2 ext length + buffer = new Buffer(value.length + 5); + + byte = 0; + + if (delta < 13) { + byte |= delta << 4; + } else if (delta < 255 + 13 + 1) { + byte |= 13 << 4; + } else if (delta < 65535 + 269 + 1) { + byte |= 14 << 4; + } else { + byte != 15 << 4; + console.log("Message Format Error"); + } + + if (value.length < 13) { + byte |= value.length; + } else if (value.length < 255 + 13 + 1) { + byte |= 13; + } else if (value.length < 65535 + 269 + 1) { + byte |= 14; + } else { + byte != 15 << 4; + console.log("Message Format Error"); + } + + // console.log("byte : " + byte); + buffer.writeUInt8(byte, pos++); + + if (delta < 13) { + ; + } else if (delta < 255 + 13 + 1) { + buffer.writeUInt8(delta - 13, pos++); + } else if (delta < 65535 + 269 + 1) { + buffer.writeUInt16BE(delta - 269, pos); + pos += 2; + } else { + console.log("Message Format Error"); + } + + if (value.length < 13) { + ; + } else if (value.length < 269) { + buffer.writeUInt8(value.length - 13, pos++); + } else if (value.length < 65535 + 269 + 1) { + buffer.writeUInt16BE(value.length - 269, pos); + pos += 2; + } else { + console.log("Message Format Error"); + } + + value.copy(buffer, pos); + pos += value.length; + total += delta; + options.push(buffer.slice(0, pos)); + // console.log('total : ' + total); + // console.log('pos : ' + pos); + // console.log('buffer : ' + buffer); + // console.log(); + } + + return options; +} \ No newline at end of file diff --git a/cloud/dashboard/src/components/ConnectDialog.js b/cloud/dashboard/src/components/ConnectDialog.js new file mode 100644 index 0000000..5f03c46 --- /dev/null +++ b/cloud/dashboard/src/components/ConnectDialog.js @@ -0,0 +1,139 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import TextField from 'material-ui/TextField'; +import Checkbox from 'material-ui/Checkbox'; +import RaisedButton from 'material-ui/RaisedButton'; +import Dialog from 'material-ui/Dialog'; + +const Client = require('../Client'); + +const style = { + dialog: { + width: '500px' + }, + + actions: { + textAlign: 'center', + }, + + actionCheckbox: { + marginLeft: '100px', + width: '200px' + }, + + error: { + textAlign: 'left', + } +} + +const errorText = 'This field is required'; + +class ConnectDialog extends React.Component { + + handleChange = (event) => { + if (event.target.value.length !== 0) { + this.setState({ error : '' }); + } else { + this.setState({ error : errorText }); + } + + this.setState({ + address: event.target.value, + }); + }; + + handleSubmit = () => { + if (this.state.address.length === 0) { + this.setState({ error : errorText }); + return; + } + + Client.writeClientData([['address', this.state.address]]); + + console.log('input address: ' + this.state.address); + Client.init(this.state.address); + }; + + onAutoConnectChecked = (event, isInputChecked) => { + this.setState({ autoconnect: isInputChecked }); + + Client.writeClientData([['autoconnect', isInputChecked]]); + }; + + constructor(props, context) { + super(props, context); + this.state = { + open: true, + autoconnect: props.autoconnect, + error: '', + address: props.address, + }; + + if(this.state.autoconnect === true) { + Client.init(this.state.address); + } + }; + + render() { + const actions = [ + ,
,
, + ,
,
, + , + ]; + + return ( + +
+ + +
+
+ ); + } +}; + +export default ConnectDialog; diff --git a/cloud/dashboard/src/components/ErrorMessage.js b/cloud/dashboard/src/components/ErrorMessage.js new file mode 100644 index 0000000..8b8da5b --- /dev/null +++ b/cloud/dashboard/src/components/ErrorMessage.js @@ -0,0 +1,57 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import Snackbar from 'material-ui/Snackbar'; + +class ErrorMessage extends React.Component { + + constructor(props, context) { + super(props, context); + var strMessage = 'ERROR, ' + props.message; + this.state = { + open: true, + message: strMessage + }; + } + + handleRequestClose = () => { + this.setState({ + open: false, + }); + }; + + render() { + return ( + + + + ); + } +} + +export default ErrorMessage; \ No newline at end of file diff --git a/cloud/dashboard/src/components/FirmwareManagement.js b/cloud/dashboard/src/components/FirmwareManagement.js new file mode 100644 index 0000000..4584831 --- /dev/null +++ b/cloud/dashboard/src/components/FirmwareManagement.js @@ -0,0 +1,170 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; + +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import {List, ListItem} from 'material-ui/List'; +import RaisedButton from 'material-ui/RaisedButton'; +import TextField from 'material-ui/TextField'; + +const Client = require('../Client'); + +const style = { + basic: { + padding: 30 + }, + + button: { + margin: 7, + }, +} + +class FirmwareHandler extends React.Component { + responseCallback = (packet) => { + console.debug("response received from " + this.state.uri); + var response = packet.getMethod + '\n'; + if (packet.getPayloadObject !== null) { + response += packet.getPayloadString; + } + this.setState({ response: response }); + } + + handleFirmwareChanged = (event, newValue) => { + this.setState({ firmwarelink: newValue }) + }; + + handleNewVersionChanged = (event, newValue) => { + this.setState({ newversion: newValue }) + }; + + handleButtonClicked = (method) => { + var payload = '{"packageuri":"' + this.state.firmwarelink + + '", "newversion": "' + this.state.newversion + '"}'; + Client.sendMessage(this.state.uri, method, JSON.parse(payload), null, this.responseCallback); + }; + + onFirmware = (packet) => { + console.debug("response received from " + this.state.uri); + var response = packet.getMethod + '\n'; + if (packet.getPayloadObject !== null) { + response += packet.getPayloadString; + } + this.setState({ response: response }); + } + + constructor(props, context) { + super(props, context); + this.state = { + uri: props.uri, + firmwarelink: null, + newversion: null, + currentversion: null, + response: '', + } + + Client.sendMessage(this.state.uri, "GET", null, null, this.onFirmware); + } + + render() { + return ( +
+ + + + +
+ ); + } +}; + +class FirmwareManagement extends React.Component { + + constructor(props, context) { + super(props, context); + this.state = { + devices: props.devices + }; + }; + + makeListItem = (uri) => { + var handler = + ; + + return handler; + } + + render() { + return ( + +
+ + Devices + {this.state.devices.map( (row, index) => { + if (row.size === 0) { + return (

No Device found

); + } + return ( uri: {row.uri}
di: {row.di}

} + secondaryTextLines={2} + nestedItems={[ + {this.makeListItem(row.uri)} ]} + nestedListStyle={{ textAlign:'right' }} primaryTogglesNestedList={true} + innerDivStyle={{ fontWeight: 'bold' }}/> + );}) + } +
+
+
+ ); + } +} + +export default FirmwareManagement; diff --git a/cloud/dashboard/src/components/MainAppBar.js b/cloud/dashboard/src/components/MainAppBar.js new file mode 100644 index 0000000..ede3c3e --- /dev/null +++ b/cloud/dashboard/src/components/MainAppBar.js @@ -0,0 +1,98 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import AppBar from 'material-ui/AppBar'; +import IconButton from 'material-ui/IconButton'; +import IconMenu from 'material-ui/IconMenu'; +import MenuItem from 'material-ui/MenuItem'; +import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; +import {orange700, white} from 'material-ui/styles/colors'; + +const Client = require('../Client'); + +const style = { + basic: { + backgroundColor: orange700 + }, + + font: { + fontWeight: 'bold' + } +} + +const handleSignout = () => { + console.debug("sign out clicked"); + Client.signOut(); +}; + +const handleDisconnect = () => { + console.debug("disconnect clicked"); + Client.close(); +}; + +const Menu = (props) => ( + + } + targetOrigin={{horizontal: 'right', vertical: 'top'}} + anchorOrigin={{horizontal: 'right', vertical: 'bottom'}} + > + + {props.signin ? : null} + + +); +Menu.muiName = 'IconMenu'; + +class MainAppBar extends React.Component { + + constructor(props, context) { + super(props, context); + + this.state = { + signin: props.signin + } + }; + + render() { + return ( + +
+ } + showMenuIconButton={false} + style={style.basic} + /> +
+
+ ); + } +}; + +export default MainAppBar; diff --git a/cloud/dashboard/src/components/MainMenu.js b/cloud/dashboard/src/components/MainMenu.js new file mode 100644 index 0000000..fdfeb7c --- /dev/null +++ b/cloud/dashboard/src/components/MainMenu.js @@ -0,0 +1,128 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import {List, ListItem} from 'material-ui/List'; +import Divider from 'material-ui/Divider'; +import {orange700, white} from 'material-ui/styles/colors'; + +import ResourceList from './ResourceList'; +import ResourceListToolbar from './ResourceListToolbar'; +import FirmwareManagement from './FirmwareManagement'; + +const Client = require('../Client'); + +const ERROR = 'error'; + +const style = { + list: { + backgroundColor: orange700, + height: '100%' + }, + + text: { + color: white, + } +} + +function appendChildren(id, numChildren) { + var parent = document.getElementById(id); + while (parent.firstChild) { + parent.removeChild(parent.firstChild); + } + + for (var i = 0; parent.childElementCount <= numChildren; i++) { + parent.appendChild(document.createElement('child' + i)); + } +}; + +class MainMenu extends React.Component { + + onDiscover = (packet) => { + if(packet.getCode !== 69) + { + Client.event.emit(ERROR, "Resource Discover Failed " + packet.getCode); + return; + } + + var resources = Client.getResourceList(packet.getPayloadObject); + + appendChildren('body', 2); + ReactDOM.render( + , + document.getElementById('body').children[0] + ); + ReactDOM.render( + , + document.getElementById('body').children[1] + ); + }; + + onFirmwareResources = (packet) =>{ + if(packet.getCode !== 69) + { + Client.event.emit(ERROR, "Firmware Discover Failed " + packet.getCode); + return; + } + + var resources = Client.getResourceList(packet.getPayloadObject); + + appendChildren('body', 1); + ReactDOM.render( + , + document.getElementById('body').children[0] + ); + }; + + handleDiscovery = () => { + Client.discoverResource(null, this.onDiscover); + }; + + handleFirmware = () => { + Client.discoverResource("rt=x.samsung.firmware", this.onFirmwareResources); + }; + + constructor(props, context) { + super(props, context); + this.state = { + signin: props.signin + } + }; + + render() { + return ( + /* TODO add log page */ + + + + + + + + + + ); + }; +}; + +export default MainMenu; diff --git a/cloud/dashboard/src/components/ResourceList.js b/cloud/dashboard/src/components/ResourceList.js new file mode 100644 index 0000000..a69b267 --- /dev/null +++ b/cloud/dashboard/src/components/ResourceList.js @@ -0,0 +1,195 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import {List, ListItem} from 'material-ui/List'; +import Subheader from 'material-ui/Subheader'; +import RaisedButton from 'material-ui/RaisedButton'; +import TextField from 'material-ui/TextField'; + +const Client = require('../Client'); + +const style = { + basic: { + padding: 30 + }, + + button: { + margin: 7, + }, +} + +/* TODO handle payload json syntax error */ +// Message handler & react component for each found resource. +class MessageHandler extends React.Component { + responseCallback = (packet) => { + console.debug("response received from " + this.state.uri); + var response = packet.getMethod + '\n'; + if (packet.getPayloadObject !== null) { + response += packet.getPayloadString; + } + this.setState({ response: response }); + } + + handleQueryChanged = (event, newValue) => { + this.setState({ queries: newValue }) + }; + + handlePayloadChanged = (event, newValue) => { + this.setState({ payload: newValue }) + }; + + handleButtonClicked = (method) => { + Client.sendMessage(this.state.uri, method, JSON.parse(this.state.payload), this.state.queries, this.responseCallback); + if(method === "GET OBSERVE"){ + this.setState({observeState : "GET OBSERVE CANCEL"}) + } + else if(method === "GET OBSERVE CANCEL"){ + this.setState({observeState : "GET OBSERVE"}) + } + }; + + constructor(props, context) { + super(props, context); + this.state = { + uri: props.uri, + payload: null, + queries: null, + response: '', + observeState: "GET OBSERVE" + } + } + + render() { + return ( +
+ + + + + + + + +
+ ); + } +}; + +// List of found resources. +class ResourceList extends React.Component { + + state = { + inputRT: '', + inputDI: '', + }; + + handleRTTextChanged = (event, newValue) => { + this.setState({ inputRT: newValue }); + } + + handleDITextChanged = (event, newValue) => { + this.setState({ inputDI: newValue }); + } + + constructor(props, context) { + super(props, context); + this.state = { + data: props.data, + inputRT: '', + inputDI: '', + }; + }; + + makeListItem = (uri) => { + var handler = + ; + + return handler; + } + + render() { + return ( + +
+ + Resources + {this.state.data.map( (row, index) => { + if (row.size === 0) { + return (

No Resource found

); + } + return ( rt: {row.rts}
if: {row.ifs}

} + secondaryTextLines={2} + nestedItems={[ + {this.makeListItem(row.uri)} ]} + nestedListStyle={{ textAlign:'right' }} primaryTogglesNestedList={true} + innerDivStyle={{ fontWeight: 'bold' }}/> + );}) + } +
+
+
+ ); + } +} + +export default ResourceList; diff --git a/cloud/dashboard/src/components/ResourceListToolbar.js b/cloud/dashboard/src/components/ResourceListToolbar.js new file mode 100644 index 0000000..50329e7 --- /dev/null +++ b/cloud/dashboard/src/components/ResourceListToolbar.js @@ -0,0 +1,108 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import RaisedButton from 'material-ui/RaisedButton'; +import TextField from 'material-ui/TextField'; +import {Toolbar, ToolbarGroup, ToolbarSeparator} from 'material-ui/Toolbar'; +import ResourceList from './ResourceList'; + +const Client = require('../Client'); + +const ERROR = 'error'; + +class ResourceListToolbar extends React.Component { + + state = { + inputRT: '', + inputDI: '', + }; + + handleRTTextChanged = (event, newValue) => { + this.setState({ inputRT: newValue }); + } + + handleDITextChanged = (event, newValue) => { + this.setState({ inputDI: newValue }); + } + + onDiscover = (packet) => { + if(packet.getCode !== 69) + { + Client.event.emit(ERROR, "Resource Discover Failed " + packet.getCode); + return; + } + + var resources = Client.getResourceList(packet.getPayloadObject); + + document.getElementById('body').children[1].innerHTML = ""; + ReactDOM.render( + , + document.getElementById('body').children[1] + ); + }; + + handleButtonClicked = () => { + var queries = ''; + if (this.state.inputRT.length !== 0) { + queries += 'rt='+ this.state.inputRT + ';'; + } + if (this.state.inputDI.length !== 0) { + queries += 'di='+ this.state.inputDI; + } + + Client.discoverResource(queries, this.onDiscover); + } + + render() { + return ( + +
+ + + + + + + + + + + +
+
+ ); + } +} + +export default ResourceListToolbar; diff --git a/cloud/dashboard/src/components/SigninPage.js b/cloud/dashboard/src/components/SigninPage.js new file mode 100644 index 0000000..ee20f53 --- /dev/null +++ b/cloud/dashboard/src/components/SigninPage.js @@ -0,0 +1,187 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import RaisedButton from 'material-ui/RaisedButton'; +import TextField from 'material-ui/TextField'; +import Checkbox from 'material-ui/Checkbox'; +import {white} from 'material-ui/styles/colors'; +import Paper from 'material-ui/Paper'; + +import SignupButton from './SignupButton'; + +const Client = require('../Client'); + +const style = { + basic: { + margin: 7 + }, + + button: { + color: white + }, + + checkbox: { + marginLeft: '100px', + width: '200px' + }, + + error: { + textAlign: 'left', + }, + + font: { + fontWeight: 'bold' + }, + + paper: { + textAlign: 'center', + margin: 'auto', + position: 'absolute', + top: '20%', + left: '38%', + } +}; + +const errorText = 'This field is required'; + +class SigninPage extends React.Component { + + handleChange = (event) => { + var currentUidError = this.state.uidError; + var currentAccesstokenError = this.state.accesstokenError; + + switch(event.target.id) { + case 'uid': + this.setState({ uid : event.target.value }); + if (event.target.value.length !== 0) { + currentUidError = ''; + } else { + currentUidError = errorText; + } + break; + case 'accesstoken': + this.setState({ accesstoken : event.target.value }); + if (event.target.value.length !== 0) { + currentAccesstokenError = ''; + } else { + currentAccesstokenError = errorText; + } + break; + default: + break; + } + this.setState({ uidError : currentUidError, accesstokenError : currentAccesstokenError }) + }; + + handleSignin = () => { + if (this.state.uid.length === 0 || this.state.accesstoken.length === 0) { + return; + } + + console.log("di: " + this.state.di); + console.log("uid: " + this.state.uid); + console.log("accesstoken: " + this.state.accesstoken); + Client.writeClientData([["uid", this.state.uid], ["accesstoken", this.state.accesstoken]]); + Client.signIn(this.state.di, this.state.uid, this.state.accesstoken); + }; + + onAutoSigninChecked = (event, isInputChecked) => { + this.setState({ autosignin: isInputChecked }); + + Client.writeClientData([['autosignin', isInputChecked]]); + }; + + constructor(props, context) { + super(props, context); + this.state = { + autosignin: props.autosignin, + di: props.di, + uid: props.uid, + accesstoken: props.accesstoken, + uidError: '', + accesstokenError: '', + }; + + if(this.state.autosignin === true) { + Client.signIn(this.state.di, this.state.uid, this.state.accesstoken); + } + }; + + render() { + const actions = ( +
+

Sign In

+
+
+
+

+
+ + +
+
+ ); + + return ( + +
+ + {actions} + +
+
+ ); + } +}; + +export default SigninPage; diff --git a/cloud/dashboard/src/components/SignupButton.js b/cloud/dashboard/src/components/SignupButton.js new file mode 100644 index 0000000..7b1b497 --- /dev/null +++ b/cloud/dashboard/src/components/SignupButton.js @@ -0,0 +1,198 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +import RaisedButton from 'material-ui/RaisedButton'; +import SelectField from 'material-ui/SelectField'; +import MenuItem from 'material-ui/MenuItem'; +import TextField from 'material-ui/TextField'; +import FlatButton from 'material-ui/FlatButton'; +import Dialog from 'material-ui/Dialog'; +import {white} from 'material-ui/styles/colors'; + +const Client = require('../Client'); + +const style = { + basic: { + margin: 7 + }, + + button: { + color: white + }, + + dialog: { + width: '500px' + }, + + dialogActions: { + textAlign: 'center' + }, + + selectfield: { + textAlign: 'left' + }, + + radiobutton: { + textAlign: 'center', + maxWidth: '250px', + marginBottom: '16px' + }, + + error: { + textAlign: 'left', + }, + + font: { + fontWeight: 'bold' + } +}; + +const errorText = 'This field is required'; + +class SignupButton extends React.Component { + + handleOpen = () => { + this.setState({ open: true }); + }; + + handleClose = () => { + this.setState({ open: false }); + }; + + handleAuthcodeButtonClicked = (value) => { + this.setState({ provider : value }); + }; + + handleSelectfieldChange = (event, index, value) => { + this.setState({ provider : value }); + }; + + handleTextChange = (event) => { + var currentCodeErr = this.state.codeError; + + this.setState({ code : event.target.value }); + if (event.target.value.length !== 0) { + currentCodeErr = ''; + } else { + currentCodeErr = errorText; + } + + this.setState({ codeError : currentCodeErr }); + }; + + handleSubmit = () => { + if (this.state.code.length === 0) { + return; + } + + Client.event.on('signup', this.signupCallback); + Client.signUp(this.state.di, this.state.provider, this.state.code); + }; + + signupCallback = (uid, accesstoken) => { + this.setState({ + dialogMessage: 'User UUID: ' + uid + '\n Accesstoken: ' + accesstoken, + signup: true, + }); + }; + + constructor(props, context) { + super(props, context); + this.state = { + di: props.di, + open: false, + provider: 'github', + code: '', + codeError: errorText, + dialogMessage: '', + signup: false + }; + }; + + render() { + var actionBeforeSignup = [ + ,
, + + + + ,
, + ,
, + ,
,
, + , + , + ]; + + var actionAfterSignup = [ + , + ]; + + return ( + +
+ + + {this.state.dialogMessage} + +
+
+ ); + } +}; + +export default SignupButton; diff --git a/cloud/dashboard/src/index.css b/cloud/dashboard/src/index.css new file mode 100644 index 0000000..b4cc725 --- /dev/null +++ b/cloud/dashboard/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/cloud/dashboard/src/index.js b/cloud/dashboard/src/index.js new file mode 100644 index 0000000..a3c8cb7 --- /dev/null +++ b/cloud/dashboard/src/index.js @@ -0,0 +1,171 @@ +/* + * //****************************************************************** + * // + * // Copyright 2017 Samsung Electronics All Rights Reserved. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + * // + * // Licensed under the Apache License, Version 2.0 (the "License"); + * // you may not use this file except in compliance with the License. + * // You may obtain a copy of the License at + * // + * // http://www.apache.org/licenses/LICENSE-2.0 + * // + * // Unless required by applicable law or agreed to in writing, software + * // distributed under the License is distributed on an "AS IS" BASIS, + * // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * // See the License for the specific language governing permissions and + * // limitations under the License. + * // + * //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import ConnectDialog from './components/ConnectDialog'; +import MainAppBar from './components/MainAppBar'; +import MainMenu from './components/MainMenu'; +import SigninPage from './components/SigninPage'; +import ErrorMessage from './components/ErrorMessage'; + +import './index.css'; + +import injectTapEventPlugin from 'react-tap-event-plugin'; +injectTapEventPlugin(); + +const Client = require('./Client'); + +const CONNECTED = 'connected'; +const DISCONNECTED = 'disconnected'; +const SIGNUP = 'signup'; +const SIGNIN = 'signin'; +const SIGNOUT = 'signout'; +const ERROR = 'error'; + +// #1 error cases: render error message. +Client.event.on(ERROR, function(message) { + ReactDOM.unmountComponentAtNode(document.getElementById('error')); + ReactDOM.render( + , + document.getElementById('error') + ); +}); + +// #2 websocket connected: default page +Client.event.on(CONNECTED, function() { + renderDefaultPage(false); + + ReactDOM.render( + , + document.getElementById('body') + ); +}); + +// #3 websocket disconnected: connect page +Client.event.on(DISCONNECTED, function() { + Client.removeClientData(['autoconnect', 'autosignin']); + + unmountDefaultPage(); + + ReactDOM.render( + , + document.getElementById('body') + ); +}); + +// #4 sign-in: request for resource discovery +Client.event.on(SIGNIN, function() { + unmountDefaultPage(); + + renderDefaultPage(true); + + ReactDOM.unmountComponentAtNode(document.getElementById('body')); +}); + +// #5 sign-out: default page +Client.event.on(SIGNOUT, function() { + Client.removeClientData(['accesstoken']); + + unmountDefaultPage(); + renderDefaultPage(false); + + ReactDOM.render( + , + document.getElementById('body') + ); +}); + +ReactDOM.render( + , + document.getElementById('body') +); + +function renderDefaultPage(signinStatue) { + ReactDOM.render( + , + document.getElementById('appbar') + ); + ReactDOM.render( + , + document.getElementById('menu') + ); +}; + +function unmountDefaultPage() { + ReactDOM.unmountComponentAtNode(document.getElementById('appbar')); + ReactDOM.unmountComponentAtNode(document.getElementById('menu')); +}; + +/** + * Private API. + */ + function generateUUID() { + var d = new Date().getTime(); + var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (d + Math.random()*16)%16 | 0; + d = Math.floor(d/16); + return (c ==='x' ? r : (r&0x3|0x8)).toString(16); + }); + return uuid; + }; + +// get device id from local storage if exists, or generate id. +function getDeviceId() { + var deviceId = Client.readClientData('deviceId'); + + if (deviceId === undefined || deviceId === null) { + deviceId = generateUUID(); + Client.writeClientData([['deviceId', deviceId]]); + } + + return deviceId; +}; + +function getAddress() { + var address = Client.readClientData('address'); + + return address; +}; + +function getUid() { + var uid = Client.readClientData('uid'); + + return uid; +}; + +function getAccesstoken() { + var accesstoken = Client.readClientData('accesstoken'); + + return accesstoken; +}; + +function getAutoconnect() { + var autoconnect = (Client.readClientData('autoconnect') === 'true'); + + return autoconnect; +}; + +function getAutosignin() { + var autosignin = (Client.readClientData('autosignin') === 'true'); + + return autosignin; +}; diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java index aef3355..9bf0332 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapDecoder.java @@ -43,6 +43,7 @@ public class CoapDecoder extends ByteToMessageDecoder { private int tokenLength = 0; private int optionPayloadLength = 0; private CoapMessage partialMsg = null; + private int websocketLength = -1; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, @@ -121,6 +122,12 @@ public class CoapDecoder extends ByteToMessageDecoder { partialMsg.setToken(token); } + if (websocketLength != -1) { + optionPayloadLength = websocketLength + - (bufferToRead + 1); // shimheader + code + + // tokenLength + } + if (optionPayloadLength > 0) { int optionLen = parseOptions(partialMsg, in, optionPayloadLength); @@ -166,7 +173,9 @@ public class CoapDecoder extends ByteToMessageDecoder { } } - public void decode(ByteBuf in, List out) throws Exception { + public void decode(ByteBuf in, List out, int length) + throws Exception { + websocketLength = length; decode(null, in, out); } diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapEncoder.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapEncoder.java index d65c02c..38c1414 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapEncoder.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/CoapEncoder.java @@ -30,6 +30,8 @@ import io.netty.handler.codec.MessageToByteEncoder; public class CoapEncoder extends MessageToByteEncoder { + private boolean isboolWebSocket = false; + @Override protected void encode(ChannelHandlerContext ctx, CoapMessage msg, ByteBuf out) throws Exception { @@ -42,6 +44,7 @@ public class CoapEncoder extends MessageToByteEncoder { * encode options */ encodeOptions(optBuf, coapMessage); + long length = optBuf.readableBytes(); if (coapMessage.getPayloadSize() > 0) { @@ -49,6 +52,10 @@ public class CoapEncoder extends MessageToByteEncoder { length += 1 + coapMessage.getPayloadSize(); } + if (isboolWebSocket) { + length = 0; + } + calcShimHeader(coapMessage, out, length); out.writeByte(coapMessage.getCode()); @@ -70,7 +77,9 @@ public class CoapEncoder extends MessageToByteEncoder { } } - public void encode(CoapMessage msg, ByteBuf out) throws Exception { + public void encode(CoapMessage msg, ByteBuf out, boolean isWebsocket) + throws Exception { + isboolWebSocket = isWebsocket; encode(null, msg, out); } diff --git a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/websocket/WebSocketFrameHandler.java b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/websocket/WebSocketFrameHandler.java index a87b4e0..f10efb2 100644 --- a/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/websocket/WebSocketFrameHandler.java +++ b/cloud/stack/src/main/java/org/iotivity/cloud/base/protocols/coap/websocket/WebSocketFrameHandler.java @@ -21,19 +21,6 @@ */ package org.iotivity.cloud.base.protocols.coap.websocket; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.HttpObjectAggregator; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrame; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -48,6 +35,19 @@ import org.iotivity.cloud.util.Cbor; import org.iotivity.cloud.util.JSONUtil; import org.iotivity.cloud.util.Log; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; + public class WebSocketFrameHandler extends ChannelDuplexHandler { @Override @@ -77,7 +77,8 @@ public class WebSocketFrameHandler extends ChannelDuplexHandler { List messages = new ArrayList<>(); new CoapDecoder().decode(((BinaryWebSocketFrame) msg).content(), - messages); + messages, + ((BinaryWebSocketFrame) msg).content().readableBytes()); for (Object message : messages) { if (message instanceof CoapMessage) { @@ -85,12 +86,12 @@ public class WebSocketFrameHandler extends ChannelDuplexHandler { // convert content format to cbor if content format is json. if (coapMessage.getPayloadSize() != 0 - && coapMessage.getContentFormat().equals( - ContentFormat.APPLICATION_JSON)) { + && coapMessage.getContentFormat() + .equals(ContentFormat.APPLICATION_JSON)) { byte[] payload = coapMessage.getPayload(); coapMessage.setPayload(convertJsonToCbor(payload)); - coapMessage - .setContentFormat(ContentFormat.APPLICATION_CBOR); + coapMessage.setContentFormat( + ContentFormat.APPLICATION_CBOR); } ctx.fireChannelRead(coapMessage); } @@ -138,7 +139,7 @@ public class WebSocketFrameHandler extends ChannelDuplexHandler { } ByteBuf encodedBytes = Unpooled.buffer(); - new CoapEncoder().encode((CoapMessage) msg, encodedBytes); + new CoapEncoder().encode((CoapMessage) msg, encodedBytes, true); WebSocketFrame frame = new BinaryWebSocketFrame(encodedBytes); newMsg = frame; } else { -- 2.7.4