Index: work/remote/web/protocol/LICENSE =================================================================== --- work/remote/web/protocol/LICENSE (nonexistent) +++ work/remote/web/protocol/LICENSE (revision 4691) @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Kalman Keri + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. Index: work/remote/web/protocol/README =================================================================== --- work/remote/web/protocol/README (nonexistent) +++ work/remote/web/protocol/README (revision 4691) @@ -0,0 +1,3 @@ +# protocol + + Index: work/remote/web/protocol/package.json =================================================================== --- work/remote/web/protocol/package.json (nonexistent) +++ work/remote/web/protocol/package.json (revision 4691) @@ -0,0 +1,25 @@ +{ + "name": "pcb-rnd-hid-protocol", + "version": "0.0.1", + "description": "Implementation of the pcb-rnd remote HID protocol", + "homepage": "http://repo.hu/projects/pcb-rnd/", + "repository": { + "type": "svn", + "url": "svn://repo.hu/pcb-rnd" + }, + "license": "MIT", + "author": { + "name": "Kalman Keri", + "email": "kk@metamaya.org" + }, + "main": "protocol.js", + "dependencies": { + }, + "devDependencies": { + "jshint": "^2.9.3", + "tape": "^4.6.0", + }, + "scripts": { + "test": "jshint protocol.js test/test.js && node ./test/test.js", + } +} Index: work/remote/web/protocol/protocol.js =================================================================== --- work/remote/web/protocol/protocol.js (nonexistent) +++ work/remote/web/protocol/protocol.js (revision 4691) @@ -0,0 +1,226 @@ +"use strict"; + + +// parser states +const MSG = 0; +const COMMAND = 1; +const ARGUMENT = 2; +const BINARY_LENGTH = 3; +const BINARY_STRING = 4; +const TEXT_STRING = 5; +const COMMENT = 8; +const EOL = 9; + + +// list types +const GENERIC = 0; +const BINARY = 1; + + +/** + * Constructs a HID protocol parser. + */ +function ProtocolParser() { + this.state = MSG; +} +/** + * Parses an array of octets. + * + * @param buffer - An array-like indexable object whose elements are octets. + */ +ProtocolParser.prototype.onreceive = function (buffer) { + for (let i = 0; i < buffer.length; ++i) { + let b = buffer[i]; + while (true) { + switch (this.state) { + + case MSG: + if (b === 9 || b === 10 || b === 13 || b === 32) { + // empty message or leading space + } else if (b === 35) { // '#' + this.state = STATE.COMMENT; + } else { + this.state = STATE.COMMAND; + this.command = ''; + break; // pass the same character to COMMAND + } + continue; + + case COMMAND: + if (this.command.length >= 16) { + this.onError("command too long"); + this.state = COMMENT; + break; // process the same character again so we won't miss EOL + } else if (b >= 65 && b <= 90 || b >= 97 && b <= 122 || b === 95 + || b === 43 || b === 45 || b === 46 || b === 35) { + this.command += String.fromCharCode(b); + } else { + this.STATE = ARGUMENT; + this.list = null; + this.args = null; + } + continue; + + case ARGUMENT: + if (b === 9 || b === 32) { + // skip whitespace + } else if (b === 40) { // '(' + if (this.list && this.list.type === BINARY) { + this.onError("generic list inside binary list"); + this.state = COMMENT; + break; + } else { + let list = { + elements: [], + type: GENERIC, + parent: this.list + }; + this.list.elements.push(list); + this.list = list; + } + } else if (b === 123) { // '{' + let list = { + elements: [], + type: BINARY, + parent: this.list + }; + this.list.elements.push(list); + this.list = list; + } else if (b === 41 || b === 125) { // ')' || '}' + if (!this.list) { + this.onError("list terminator outside of list"); + this.state = COMMENT; + break; + } else if (b === 41 && this.list.type === BINARY) { + this.onError("generic list terminator in binary list"); + this.state = COMMENT; + break; + } else if (b === 125 && this.list.type === GENERIC) { + this.onError("binary list terminator in generic list"); + this.state = COMMENT; + break; + } else { + if (this.list.parent) { + this.list = this.list.parent; + } else { + this.args = compactArguments(this.list); + this.list = null; + this.state = EOL; + } + } + } else { // string + if (this.list.type === BINARY) { + this.state = BINARY_LENGTH; + this.strLen = 0; + this.strLenSize = 0; + this.str = ''; + break; + } else { + this.state = TEXT_STRING; + this.str = ''; + break; + } + } + continue; + + case TEXT_STRING: + if (this.str.length >= 16) { + this.onError("text string too long"); + this.state = COMMENT; + break; + } else if (b >= 65 && b <= 90 || b >= 97 && b <= 122 || b === 95 + || b === 43 || b === 45 || b === 46 || b === 35) { + this.str += String.fromCharCode(b); + } else { + if (this.list) { + this.list.elements.push(this.str); + this.STATE = ARGUMENT; + } else { + this.onError("string outside of list"); + this.state = COMMENT; + break; + } + } + continue; + + case BINARY_LENGTH: + if (this.strLenSize >= 5) { + this.onError("binary string length is too long"); + this.state = COMMENT; + break; + } else if (b >= 65 && b <= 90) { // A-Z + this.strLenSize++; + let code = b - 65; + this.strLen += code << 6; + } else if (b >= 97 && b <= 122) { // a-z + this.strLenSize++; + let code = b - 97 + 26; + this.strLen += code << 6; + } else if (b >= 48 && b <= 57) { // 0-9 + this.strLenSize++; + let code = b - 48 + 52; + this.strLen += code << 6; + } else if (b === 42) { // '+' + this.strLenSize++; + this.strLen += 62 << 6; + } else if (b === 47) { // '/' + this.strLenSize++; + this.strLen += 63 << 6; + } else if (b === 61) { // '=' + this.state = BINARY_STRING; + } else { + this.onError("invalid character in binary string length"); + this.state = COMMENT; + break; + } + continue; + + case BINARY_STRING: + if (this.strLen <= 0) { + if (this.list) { + this.list.elements.push(this.str); + this.STATE = ARGUMENT; + } else { + this.onError("string outside of list"); + this.state = COMMENT; + break; + } + } else { + this.str += String.fromCharCode(b); + this.strLen--; + } + continue; + + case COMMENT: + if (b === 10 || b === 13) { + this.state = MSG; + } + continue; + + case EOL: + if (b === 9 || b === 32) { + // skip whitespace + } else if (b === 10 || b === 13) { + this.onCommand(this.args); + this.state = MSG; + } else { + this.onError("extra characters after argument list"); + this.state = COMMENT; + break; + } + continue; + + } // end switch + } // end while + } // end for +} +/** + * Override this to execute a command. + */ +ProtocolParser.prototype.onCommand = function (command) { +} +/** + * Override this to respond to errors. + */ +ProtocolParser.prototype.onError = function () { +}