├── .gitignore ├── package.json ├── example.js ├── README.md └── src ├── constants.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /logs 3 | npm-debug.log 4 | /node_modules 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "faux-mysql-server", 3 | "private": false, 4 | "version": "0.1.0", 5 | "description": "A library which implements the MySQL server protocol, giving you the ability to create MySQL-like services", 6 | "keywords": [ "mysql" ], 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/CloudQuote/faux-mysql-server.git" 10 | }, 11 | "author": "CloudQuote", 12 | "license": "gpl-3.0", 13 | "type": "module", 14 | "main": "./src/index.js", 15 | "exports": { 16 | ".": "./src/index.js", 17 | "./constants": "./src/constants.js" 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | import net from 'net'; 2 | import FMS, {consts} from './src/index.js'; 3 | 4 | net.createServer((so) => { 5 | let server = new FMS({ 6 | socket: so, 7 | banner: "Fake Mysql/1.0", 8 | onAuthorize: handleAuthorize, 9 | onCommand: handleCommand 10 | }); 11 | }).listen(3306); 12 | 13 | console.log("Started server on port 3306"); 14 | 15 | function handleAuthorize(param) { 16 | console.log("Auth Info:"); 17 | console.log(param); 18 | // Yup you are authorized 19 | return true; 20 | } 21 | 22 | function handleCommand({command, extra}) { 23 | // command is a numeric ID, extra is a Buffer 24 | switch (command) { 25 | case consts.COM_QUERY: 26 | handleQuery.call(this, extra.toString()); 27 | break; 28 | case consts.COM_PING: 29 | this.sendOK({message: "OK"}); 30 | break; 31 | case null: 32 | case undefined: 33 | case consts.COM_QUIT: 34 | console.log("Disconnecting"); 35 | this.end(); 36 | break; 37 | default: 38 | console.log("Unknown Command: " + command); 39 | this.sendError({ message: "Unknown Command"}); 40 | break; 41 | } 42 | } 43 | 44 | function handleQuery(query) { 45 | // Take the query, print it out 46 | console.log("Got Query: " + query); 47 | 48 | // Then send it back to the user in table format 49 | this.sendDefinitions([ 50 | this.newDefinition({ name: 'TheCommandYouSent', columnType: consts.MYSQL_TYPE_VAR_STRING }), 51 | this.newDefinition({ name: "foo", columnType: consts.MYSQL_TYPE_VAR_STRING }), 52 | this.newDefinition({ name: "A Number", columnType: consts.MYSQL_TYPE_LONG }), 53 | ]); 54 | this.sendRows([ 55 | [query,"bar", 123456789] 56 | ]); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # faux-mysql-server 2 | A library which implements the MySQL server protocol, giving you the ability to create MySQL-like services 3 | 4 | ## Synopsis 5 | 6 | This is a server-side implementation of the MySQL network protocol 7 | 8 | ## Description 9 | 10 | This module emulates the server side of the MySQL protocol. This allows you to run your own faux-MySQL servers which can accept commands and queries and reply accordingly. 11 | 12 | ## Example 13 | 14 | See example.js for an example server which repeats the commands back to you that you send 15 | 16 | ```javascript 17 | import net from 'net'; 18 | import FMS from 'faux-mysql'; 19 | import * as consts from 'faux-mysql/constants'; 20 | 21 | net.createServer((so) => { 22 | let server = new FMS({ 23 | socket: so, 24 | banner: "Fake Mysql/1.0", 25 | onAuthorize: handleAuthorize, 26 | onCommand: handleCommand 27 | }); 28 | }).listen(3306); 29 | 30 | console.log("Started server on port 3306"); 31 | 32 | function handleAuthorize(param) { 33 | console.log("Auth Info:"); 34 | console.log(param); 35 | // Yup you are authorized 36 | return true; 37 | } 38 | 39 | function handleCommand({command, extra}) { 40 | // command is a numeric ID, extra is a Buffer 41 | switch (command) { 42 | case consts.COM_QUERY: 43 | handleQuery.call(this, extra.toString()); 44 | break; 45 | case consts.COM_PING: 46 | this.sendOK({message: "OK"}); 47 | break; 48 | case null: 49 | case undefined: 50 | case consts.COM_QUIT: 51 | console.log("Disconnecting"); 52 | this.end(); 53 | break; 54 | default: 55 | console.log("Unknown Command: " + command); 56 | this.sendError({ message: "Unknown Command"}); 57 | break; 58 | } 59 | } 60 | 61 | function handleQuery(query) { 62 | // Take the query, print it out 63 | console.log("Got Query: " + query); 64 | 65 | // Then send it back to the user in table format 66 | this.sendDefinitions([this.newDefinition({ name: 'TheCommandYouSent'})]); 67 | this.sendRows([ 68 | [query] 69 | ]); 70 | } 71 | ``` 72 | 73 | ## Where it came from 74 | 75 | This module was written by Mark Dierolf , as a port of the GPL'd DBIx::MyServer perl module to NodeJS 76 | 77 | The original [DBIx::MyServer](https://metacpan.org/pod/DBIx::MyServer) module was written by Philip Stoev 78 | 79 | ## License 80 | 81 | GPL 82 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const MYSERVER_PACKET_COUNT = 0; 2 | export const MYSERVER_SOCKET= 1; 3 | export const MYSERVER_DATABASE= 4; 4 | export const MYSERVER_THREAD_ID= 5; 5 | export const MYSERVER_SCRAMBLE= 6; 6 | export const MYSERVER_DBH= 7; 7 | export const MYSERVER_PARSER= 8; 8 | export const MYSERVER_BANNER= 9; 9 | export const MYSERVER_SERVER_CHARSET = 10; 10 | export const MYSERVER_CLIENT_CHARSET = 11; 11 | export const MYSERVER_SALT= 12; 12 | 13 | export const FIELD_CATALOG= 0; 14 | export const FIELD_DB = 1; 15 | export const FIELD_TABLE= 2; 16 | export const FIELD_ORG_TABLE= 3; 17 | export const FIELD_NAME = 4; 18 | export const FIELD_ORG_NAME= 5; 19 | export const FIELD_LENGTH= 6; 20 | export const FIELD_TYPE = 7; 21 | export const FIELD_FLAGS= 8; 22 | export const FIELD_DECIMALS= 9; 23 | export const FIELD_DEFAULT= 10; 24 | 25 | 26 | // 27 | // This comes from include/mysql_com.h of the MySQL source 28 | // 29 | 30 | export const CLIENT_LONG_PASSWORD = 1; 31 | export const CLIENT_FOUND_ROWS= 2; 32 | export const CLIENT_LONG_FLAG= 4; 33 | export const CLIENT_CONNECT_WITH_DB = 8; 34 | export const CLIENT_NO_SCHEMA= 16; 35 | export const CLIENT_COMPRESS= 32;// Must implement that one 36 | export const CLIENT_ODBC= 64; 37 | export const CLIENT_LOCAL_FILES= 128; 38 | export const CLIENT_IGNORE_SPACE = 256; 39 | export const CLIENT_PROTOCOL_41= 512; 40 | export const CLIENT_INTERACTIVE= 1024; 41 | export const CLIENT_SSL = 2048; // Must implement that one 42 | export const CLIENT_IGNORE_SIGPIPE = 4096; 43 | export const CLIENT_TRANSACTIONS = 8192; 44 | export const CLIENT_RESERVED = 16384; 45 | export const CLIENT_SECURE_CONNECTION = 32768; 46 | export const CLIENT_MULTI_STATEMENTS = 1 << 16; 47 | export const CLIENT_MULTI_RESULTS = 1 << 17; 48 | export const CLIENT_SSL_VERIFY_SERVER_CERT = 1 << 30; 49 | export const CLIENT_REMEMBER_OPTIONS= 1 << 31; 50 | 51 | export const SERVER_STATUS_IN_TRANS= 1; 52 | export const SERVER_STATUS_AUTOCOMMIT= 2; 53 | export const SERVER_MORE_RESULTS_EXISTS= 8; 54 | export const SERVER_QUERY_NO_GOOD_INDEX_USED = 16; 55 | export const SERVER_QUERY_NO_INDEX_USED= 32; 56 | export const SERVER_STATUS_CURSOR_EXISTS = 64; 57 | export const SERVER_STATUS_LAST_ROW_SENT = 128; 58 | export const SERVER_STATUS_DB_DROPPED= 256; 59 | export const SERVER_STATUS_NO_BACKSLASH_ESCAPES = 512; 60 | 61 | export const COM_SLEEP = 0; 62 | export const COM_QUIT = 1; 63 | export const COM_INIT_DB= 2; 64 | export const COM_QUERY = 3; 65 | export const COM_FIELD_LIST= 4; 66 | export const COM_CREATE_DB= 5; 67 | export const COM_DROP_DB= 6; 68 | export const COM_REFRESH= 7; 69 | export const COM_SHUTDOWN= 8; 70 | export const COM_STATISTICS= 9; 71 | export const COM_PROCESS_INFO= 10; 72 | export const COM_CONNECT= 11; 73 | export const COM_PROCESS_KILL= 12; 74 | export const COM_DEBUG = 13; 75 | export const COM_PING = 14; 76 | export const COM_TIME = 15; 77 | export const COM_DELAYED_INSERT= 16; 78 | export const COM_CHANGE_USER= 17; 79 | export const COM_BINLOG_DUMP= 18; 80 | export const COM_TABLE_DUMP= 19; 81 | export const COM_CONNECT_OUT= 20; 82 | export const COM_REGISTER_SLAVE= 21; 83 | export const COM_STMT_PREPARE= 22; 84 | export const COM_STMT_EXECUTE= 23; 85 | export const COM_STMT_SEND_LONG_DATA = 24; 86 | export const COM_STMT_CLOSE= 25; 87 | export const COM_STMT_RESET= 26; 88 | export const COM_SET_OPTION= 27; 89 | export const COM_STMT_FETCH= 28; 90 | export const COM_END = 29; 91 | 92 | // This is taken from include/mysql_com.h 93 | 94 | export const MYSQL_TYPE_DECIMAL= 0; 95 | export const MYSQL_TYPE_TINY= 1; 96 | export const MYSQL_TYPE_SHORT= 2; 97 | export const MYSQL_TYPE_LONG= 3; 98 | export const MYSQL_TYPE_FLOAT= 4; 99 | export const MYSQL_TYPE_DOUBLE= 5; 100 | export const MYSQL_TYPE_NULL= 6; 101 | export const MYSQL_TYPE_TIMESTAMP = 7; 102 | export const MYSQL_TYPE_LONGLONG = 8; 103 | export const MYSQL_TYPE_INT24= 9; 104 | export const MYSQL_TYPE_DATE= 10; 105 | export const MYSQL_TYPE_TIME= 11; 106 | export const MYSQL_TYPE_DATETIME = 12; 107 | export const MYSQL_TYPE_YEAR= 13; 108 | export const MYSQL_TYPE_NEWDATE= 14; 109 | export const MYSQL_TYPE_VARCHAR= 15; 110 | export const MYSQL_TYPE_BIT= 16; 111 | export const MYSQL_TYPE_NEWDECIMAL = 246; 112 | export const MYSQL_TYPE_ENUM= 247; 113 | export const MYSQL_TYPE_SET= 248; 114 | export const MYSQL_TYPE_TINY_BLOB = 249; 115 | export const MYSQL_TYPE_MEDIUM_BLOB = 250; 116 | export const MYSQL_TYPE_LONG_BLOB = 251; 117 | export const MYSQL_TYPE_BLOB= 252; 118 | export const MYSQL_TYPE_VAR_STRING = 253; 119 | export const MYSQL_TYPE_STRING= 254; 120 | export const MYSQL_TYPE_GEOMETRY = 255; 121 | 122 | export const NOT_NULL_FLAG= 1; 123 | export const PRI_KEY_FLAG= 2; 124 | export const UNIQUE_KEY_FLAG= 4; 125 | export const MULTIPLE_KEY_FLAG= 8; 126 | export const BLOB_FLAG = 16; 127 | export const UNSIGNED_FLAG= 32; 128 | export const ZEROFILL_FLAG= 64; 129 | export const BINARY_FLAG= 128; 130 | export const ENUM_FLAG = 256; 131 | export const AUTO_INCREMENT_FLAG = 512; 132 | export const TIMESTAMP_FLAG= 1024; 133 | export const SET_FLAG = 2048; 134 | export const NO_DEFAULT_VALUE_FLAG = 4096; 135 | export const NUM_FLAG = 32768; 136 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as consts from './constants.js'; 2 | import crypto from 'crypto'; 3 | 4 | export * as consts from './constants.js'; 5 | export default class Server { 6 | constructor(opts) { 7 | Object.assign(this,opts); 8 | 9 | if (! this.banner) this.banner = "MyServer/1.0"; 10 | if (! this.salt) this.salt = crypto.randomBytes(20); 11 | this.sequence = 0; 12 | this.onPacket = this.helloPacketHandler; 13 | this.incoming = []; 14 | 15 | this.socket.on('data', this.handleData); 16 | if (this.handleDisconnect) this.socket.on('end', this.handleDisconnect); 17 | 18 | this.sendServerHello(); 19 | } 20 | 21 | end = () => { 22 | this.socket.end(); 23 | }; 24 | 25 | writeHeader(data,len) { 26 | data.writeUIntLE(len - 4,0,3); 27 | data.writeUInt8(this.sequence++ % 256, 3); 28 | } 29 | 30 | sendPacket(payload) { 31 | return this.socket.write(payload); 32 | } 33 | 34 | newDefinition(params) { 35 | return { 36 | catalog: params.catalog ? params.catalog : 'def', 37 | schema: params.db, 38 | table: params.table, 39 | orgTable: params.orgTable, 40 | name: params.name, 41 | orgName: params.orgName, 42 | length: params.length ? params.length : 0, 43 | type: params.type ? params.type : consts.MYSQL_TYPE_STRING, 44 | flags: params.flags ? params.flags : 0, 45 | decimals: params.decimals, 46 | 'default': params['default'], 47 | }; 48 | } 49 | 50 | sendDefinitions(definitions) { 51 | // Write Definition Header 52 | let payload = Buffer.alloc(1024); 53 | let len = 4; 54 | len = writeLengthCodedBinary(payload,len,definitions.length); 55 | this.writeHeader(payload,len); 56 | this.sendPacket(payload.slice(0,len)); 57 | 58 | // Write each definition 59 | for (let definition of definitions) { 60 | len = 4; 61 | for (let field of [ 'catalog','schema','table','orgTable','name','orgName' ]) { 62 | let val = definition[field] || ""; 63 | len = writeLengthCodedString(payload,len,val); 64 | } 65 | len = payload.writeUInt8(0x0C, len); 66 | len = payload.writeUInt16LE(11, len); // ASCII 67 | len = payload.writeUInt32LE(definition.columnLength || 0, len); 68 | len = payload.writeUInt8(definition.columnType != null ? definition.columnType : consts.MYSQL_TYPE_VAR_STRING , len); 69 | len = payload.writeUInt16LE(definition.flags != null ? definition.flags : 0, len); 70 | len = payload.writeUInt8(definition.decimals != null ? definition.decimals : 0, len); 71 | len = payload.writeUInt16LE(0,len); // \0\0 FILLER 72 | len = writeLengthCodedString(payload,len,definition['default']); 73 | this.writeHeader(payload,len); 74 | this.sendPacket(payload.slice(0,len)); 75 | //console.log(definition); 76 | //console.log(payload.slice(4,len)); 77 | } 78 | 79 | this.sendEOF(); 80 | } 81 | 82 | sendRow(row) { 83 | let payload = Buffer.alloc(1024); 84 | let len = 4; 85 | for (let cell of row) { 86 | if (cell == null) { 87 | len = payload.writeUInt8(0xFB,len); 88 | } else { 89 | len = writeLengthCodedString(payload,len,cell); 90 | } 91 | } 92 | this.writeHeader(payload,len); 93 | this.sendPacket(payload.slice(0,len)); 94 | } 95 | 96 | sendRows(rows = []) { 97 | for (let row of rows) { 98 | this.sendRow(row); 99 | } 100 | this.sendEOF(); 101 | } 102 | 103 | 104 | sendEOF({warningCount = 0, serverStatus = consts.SERVER_STATUS_AUTOCOMMIT} = {}) { 105 | // Write EOF 106 | let payload = Buffer.alloc(16); 107 | let len = 4; 108 | len = payload.writeUInt8(0xFE,len); 109 | len = payload.writeUInt16LE(warningCount,len); 110 | len = payload.writeUInt16LE(serverStatus,len); 111 | this.writeHeader(payload,len); 112 | this.sendPacket(payload.slice(0,len)); 113 | } 114 | 115 | sendServerHello = () => { 116 | //## Sending Server Hello... 117 | let payload = Buffer.alloc(128); 118 | let pos = 4; 119 | pos = payload.writeUInt8(10,pos); // Protocol version 120 | 121 | pos += payload.write(this.banner || "MyServer/1.0",pos); 122 | pos = payload.writeUInt8(0,pos); 123 | 124 | pos = payload.writeUInt32LE(process.pid,pos); 125 | 126 | pos += this.salt.copy(payload,pos, 0,8); 127 | pos = payload.writeUInt8(0,pos); 128 | 129 | pos = payload.writeUInt16LE( 130 | consts.CLIENT_LONG_PASSWORD | 131 | consts.CLIENT_CONNECT_WITH_DB | 132 | consts.CLIENT_PROTOCOL_41 | 133 | consts.CLIENT_SECURE_CONNECTION 134 | , pos); 135 | 136 | if (this.serverCharset) { 137 | pos = payload.writeUInt8(this.serverCharset,pos); 138 | } else { 139 | pos = payload.writeUInt8(0x21,pos); // latin1 140 | } 141 | pos = payload.writeUInt16LE(consts.SERVER_STATUS_AUTOCOMMIT,pos); 142 | payload.fill(0,pos,pos+13); 143 | pos += 13; 144 | 145 | pos += this.salt.copy(payload,pos,8); 146 | pos = payload.writeUInt8(0,pos); 147 | this.writeHeader(payload,pos); 148 | 149 | return this.sendPacket(payload.slice(0,pos)); 150 | } 151 | 152 | handleData = (data) => { 153 | if (data && data.length > 0) { 154 | this.incoming.push(data); 155 | } 156 | this.gatherIncoming(); 157 | if (data == null) { 158 | console.log("Connection closed"); 159 | this.socket.destroy(); 160 | } 161 | } 162 | 163 | gatherIncoming() { 164 | let incoming; 165 | if (this.incoming.length > 0) { 166 | let len = 0; 167 | for (let buf of this.incoming) { 168 | len += buf.length; 169 | } 170 | incoming = Buffer.alloc(len); 171 | len = 0; 172 | for (let buf of this.incoming) { 173 | len += buf.copy(incoming,len); 174 | } 175 | } else { 176 | incoming = this.incoming[0]; 177 | } 178 | let remaining = this.readPackets(incoming); 179 | this.incoming = [Buffer.from(remaining)]; 180 | } 181 | 182 | readPackets(buf) { 183 | let offset = 0; 184 | while (true) { 185 | let data = buf.slice(offset); 186 | if (data.length < 4) return data; 187 | 188 | let packetLength = data.readUIntLE(0,3); 189 | if (data.length < packetLength + 4) return data; 190 | 191 | //console.log({ data, offset }); 192 | 193 | this.sequence = data.readUIntLE(3,1) + 1; 194 | offset += packetLength + 4; 195 | let packet = data.slice(4,packetLength + 4); 196 | 197 | this.onPacket(packet); 198 | this.packetCount++; 199 | } 200 | } 201 | 202 | helloPacketHandler = (packet) => { 203 | //## Reading Client Hello... 204 | 205 | // http://dev.mysql.com/doc/internals/en/the-packet-header.html 206 | 207 | if (packet.length == 0) return this.sendError({ message: "Zero length hello packet" }); 208 | 209 | let ptr = 0; 210 | 211 | let clientFlags = packet.slice(ptr,ptr+4); 212 | ptr += 4; 213 | 214 | let maxPacketSize = packet.slice(ptr,ptr+4); 215 | ptr += 4; 216 | 217 | this.clientCharset = packet.readUInt8(ptr); 218 | ptr++; 219 | 220 | let filler1 = packet.slice(ptr,ptr+23); 221 | ptr += 23; 222 | 223 | let usernameEnd = packet.indexOf(0,ptr); 224 | let username = packet.toString('ascii',ptr,usernameEnd); 225 | ptr = usernameEnd + 1; 226 | 227 | let scrambleBuff; 228 | 229 | let scrambleLength = packet.readUInt8(ptr); 230 | ptr++; 231 | 232 | if (scrambleLength > 0) { 233 | this.scramble = packet.slice(ptr,ptr+scrambleLength); 234 | ptr += scrambleLength; 235 | } 236 | 237 | let database; 238 | 239 | let databaseEnd = packet.indexOf(0,ptr); 240 | if (databaseEnd >= 0) { 241 | database = packet.toString('ascii',ptr,databaseEnd); 242 | } 243 | this.onPacket = null; 244 | 245 | return Promise.resolve(this.onAuthorize({ clientFlags, maxPacketSize, username, database })) 246 | .then( (authorized) => { 247 | if (! authorized) throw "Not Authorized"; 248 | 249 | this.onPacket = this.normalPacketHandler; 250 | this.gatherIncoming(); 251 | this.sendOK({ message: "OK" }); 252 | }) 253 | .catch( (err) => { 254 | console.log(err); 255 | this.sendError( { message: "Authorization Failure" } ); 256 | this.socket.destroy(); 257 | }); 258 | } 259 | 260 | normalPacketHandler(packet) { 261 | if (packet == null) throw "Empty packet"; 262 | return this.onCommand({ 263 | command: packet.readUInt8(0), 264 | extra: packet.length > 1 ? packet.slice(1) : null 265 | }); 266 | } 267 | sendOK({ message, affectedRows = 0, insertId, warningCount = 0}) { 268 | let data = Buffer.alloc(message.length + 64); 269 | let len = 4; 270 | len = data.writeUInt8(0,len); 271 | len = writeLengthCodedBinary(data,len,affectedRows); 272 | len = writeLengthCodedBinary(data,len,insertId); 273 | len = data.writeUInt16LE(consts.SERVER_STATUS_AUTOCOMMIT,len); 274 | len = data.writeUInt16LE(warningCount,len); 275 | len = writeLengthCodedString(data,len,message); 276 | 277 | this.writeHeader(data,len); 278 | this.sendPacket(data.slice(0,len)); 279 | } 280 | 281 | sendError({ message = 'Unknown MySQL error',errno = 2000,sqlState = "HY000"}) { 282 | //## Sending Error ... 283 | console.log(message); 284 | let data = Buffer.alloc(message.length + 64); 285 | let len = 4; 286 | len = data.writeUInt8(0xFF,len); 287 | len = data.writeUInt16LE(errno,len); 288 | len += data.write("#",len); 289 | len += data.write(sqlState,len,5); 290 | len += data.write(message,len); 291 | len = data.writeUInt8(0,len); 292 | 293 | this.writeHeader(data,len); 294 | this.sendPacket(data.slice(0,len)); 295 | } 296 | } 297 | function writeLengthCodedString(buf,pos,str) { 298 | if (str == null) return buf.writeUInt8(0,pos); 299 | if (typeof str !== 'string') { 300 | //Mangle it 301 | str = str.toString(); 302 | } 303 | buf.writeUInt8(253,pos); 304 | buf.writeUIntLE(str.length,pos + 1, 3); 305 | buf.write(str,pos + 4); 306 | return pos + str.length + 4; 307 | } 308 | 309 | function writeLengthCodedBinary(buf,pos,number) { 310 | if (number == null) { 311 | return buf.writeUInt8(251,pos); 312 | } else if (number < 251) { 313 | return buf.writeUInt8(number,pos); 314 | } else if (number < 0x10000) { 315 | buf.writeUInt8(252,pos); 316 | buf.writeUInt16LE(number,pos + 1); 317 | return pos + 3; 318 | } else if (number < 0x1000000) { 319 | buf.writeUInt8(253,pos); 320 | buf.writeUIntLE(number,pos + 1,3); 321 | return pos + 4; 322 | } else { 323 | buf.writeUInt8(254,pos); 324 | buf.writeUIntLE(number,pos + 1,8); 325 | return pos + 9; 326 | } 327 | } 328 | --------------------------------------------------------------------------------