1 /* 2 * Copyright (C) 2013 Glyptodon LLC 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23 var Guacamole = Guacamole || {}; 24 25 /** 26 * Simple Guacamole protocol parser that invokes an oninstruction event when 27 * full instructions are available from data received via receive(). 28 * 29 * @constructor 30 */ 31 Guacamole.Parser = function() { 32 33 /** 34 * Reference to this parser. 35 * @private 36 */ 37 var parser = this; 38 39 /** 40 * Current buffer of received data. This buffer grows until a full 41 * element is available. After a full element is available, that element 42 * is flushed into the element buffer. 43 * 44 * @private 45 */ 46 var buffer = ""; 47 48 /** 49 * Buffer of all received, complete elements. After an entire instruction 50 * is read, this buffer is flushed, and a new instruction begins. 51 * 52 * @private 53 */ 54 var element_buffer = []; 55 56 // The location of the last element's terminator 57 var element_end = -1; 58 59 // Where to start the next length search or the next element 60 var start_index = 0; 61 62 /** 63 * Appends the given instruction data packet to the internal buffer of 64 * this Guacamole.Parser, executing all completed instructions at 65 * the beginning of this buffer, if any. 66 * 67 * @param {String} packet The instruction data to receive. 68 */ 69 this.receive = function(packet) { 70 71 // Truncate buffer as necessary 72 if (start_index > 4096 && element_end >= start_index) { 73 74 buffer = buffer.substring(start_index); 75 76 // Reset parse relative to truncation 77 element_end -= start_index; 78 start_index = 0; 79 80 } 81 82 // Append data to buffer 83 buffer += packet; 84 85 // While search is within currently received data 86 while (element_end < buffer.length) { 87 88 // If we are waiting for element data 89 if (element_end >= start_index) { 90 91 // We now have enough data for the element. Parse. 92 var element = buffer.substring(start_index, element_end); 93 var terminator = buffer.substring(element_end, element_end+1); 94 95 // Add element to array 96 element_buffer.push(element); 97 98 // If last element, handle instruction 99 if (terminator == ";") { 100 101 // Get opcode 102 var opcode = element_buffer.shift(); 103 104 // Call instruction handler. 105 if (parser.oninstruction != null) 106 parser.oninstruction(opcode, element_buffer); 107 108 // Clear elements 109 element_buffer.length = 0; 110 111 } 112 else if (terminator != ',') 113 throw new Error("Illegal terminator."); 114 115 // Start searching for length at character after 116 // element terminator 117 start_index = element_end + 1; 118 119 } 120 121 // Search for end of length 122 var length_end = buffer.indexOf(".", start_index); 123 if (length_end != -1) { 124 125 // Parse length 126 var length = parseInt(buffer.substring(element_end+1, length_end)); 127 if (length == NaN) 128 throw new Error("Non-numeric character in element length."); 129 130 // Calculate start of element 131 start_index = length_end + 1; 132 133 // Calculate location of element terminator 134 element_end = start_index + length; 135 136 } 137 138 // If no period yet, continue search when more data 139 // is received 140 else { 141 start_index = buffer.length; 142 break; 143 } 144 145 } // end parse loop 146 147 }; 148 149 /** 150 * Fired once for every complete Guacamole instruction received, in order. 151 * 152 * @event 153 * @param {String} opcode The Guacamole instruction opcode. 154 * @param {Array} parameters The parameters provided for the instruction, 155 * if any. 156 */ 157 this.oninstruction = null; 158 159 }; 160