var util = require('util') var EventEmitter = require('eventemitter3').EventEmitter var debug = require('debug')('vnc:connection') var PixelFormat = require('./pixelformat') function VncConnection(conn, options) { this.options = options this._bound = { _errorListener: this._errorListener.bind(this) , _readableListener: this._readableListener.bind(this) , _endListener: this._endListener.bind(this) , _closeListener: this._closeListener.bind(this) } this._buffer = null this._state = 0 this._changeState(VncConnection.STATE_NEED_CLIENT_VERSION) this._serverVersion = VncConnection.V3_008 this._serverSupportedSecurity = [VncConnection.SECURITY_NONE] this._serverWidth = 1080 this._serverHeight = 1920 this._serverPixelFormat = new PixelFormat({ bitsPerPixel: 24 , depth: 24 , bigEndianFlag: 0 , trueColorFlag: 1 , redMax: 255 , greenMax: 255 , blueMax: 255 , redShift: 16 , greenShift: 8 , blueShift: 0 }) this._requireServerPixelFormat = true this._serverName = this.options.name this._clientVersion = null this._clientShare = false this._clientPixelFormat = this._serverPixelFormat this._clientEncodingCount = 0 this._clientEncodings = [] this._clientCutTextLength = 0 this.conn = conn .on('error', this._bound._errorListener) .on('readable', this._bound._readableListener) .on('end', this._bound._endListener) .on('close', this._bound._closeListener) this._writeServerVersion() this._read() } util.inherits(VncConnection, EventEmitter) VncConnection.V3_003 = 3003 VncConnection.V3_007 = 3007 VncConnection.V3_008 = 3008 VncConnection.SECURITY_NONE = 1 VncConnection.SECURITY_VNC = 2 VncConnection.SECURITYRESULT_OK = 0 VncConnection.SECURITYRESULT_FAIL = 1 VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT = 0 VncConnection.CLIENT_MESSAGE_SETENCODINGS = 2 VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST = 3 VncConnection.CLIENT_MESSAGE_KEYEVENT = 4 VncConnection.CLIENT_MESSAGE_POINTEREVENT = 5 VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT = 6 VncConnection.SERVER_MESSAGE_FBUPDATE = 0 var StateReverse = Object.create(null), State = { STATE_NEED_CLIENT_VERSION: 10 , STATE_NEED_CLIENT_SECURITY: 20 , STATE_NEED_CLIENT_INIT: 30 , STATE_NEED_CLIENT_MESSAGE: 40 , STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: 50 , STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: 60 , STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: 61 , STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: 70 , STATE_NEED_CLIENT_MESSAGE_KEYEVENT: 80 , STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: 90 , STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: 100 , STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: 101 } VncConnection.ENCODING_RAW = 0 VncConnection.ENCODING_DESKTOPSIZE = -223 Object.keys(State).map(function(name) { VncConnection[name] = State[name] StateReverse[State[name]] = name }) VncConnection.prototype.end = function() { this.conn.end() } VncConnection.prototype.writeFramebufferUpdate = function(rectangles) { var chunk = new Buffer(4) chunk[0] = VncConnection.SERVER_MESSAGE_FBUPDATE chunk[1] = 0 chunk.writeUInt16BE(rectangles.length, 2) this._write(chunk) rectangles.forEach(function(rect) { var chunk = new Buffer(12) chunk.writeUInt16BE(rect.xPosition, 0) chunk.writeUInt16BE(rect.yPosition, 2) chunk.writeUInt16BE(rect.width, 4) chunk.writeUInt16BE(rect.height, 6) chunk.writeInt32BE(rect.encodingType, 8) this._write(chunk) switch (rect.encodingType) { case VncConnection.ENCODING_RAW: this._write(rect.data) break case VncConnection.ENCODING_DESKTOPSIZE: break default: throw new Error(util.format( 'Unsupported encoding type', rect.encodingType)) } }, this) } VncConnection.prototype._error = function(err) { this.emit('error', err) this.end() } VncConnection.prototype._errorListener = function(err) { this._error(err) } VncConnection.prototype._endListener = function() { this.emit('end') } VncConnection.prototype._closeListener = function() { this.emit('close') } VncConnection.prototype._writeServerVersion = function() { // Yes, we could just format the string instead. Didn't feel like it. switch (this._serverVersion) { case VncConnection.V3_003: this._write(new Buffer('RFB 003.003\n')) break case VncConnection.V3_007: this._write(new Buffer('RFB 003.007\n')) break case VncConnection.V3_008: this._write(new Buffer('RFB 003.008\n')) break } } VncConnection.prototype._writeSupportedSecurity = function() { var chunk = new Buffer(1 + this._serverSupportedSecurity.length) chunk[0] = this._serverSupportedSecurity.length this._serverSupportedSecurity.forEach(function(security, i) { chunk[1 + i] = security }) this._write(chunk) } VncConnection.prototype._writeSelectedSecurity = function() { var chunk = new Buffer(4) chunk.writeUInt32BE(VncConnection.SECURITY_NONE, 0) this._write(chunk) } VncConnection.prototype._writeSecurityResult = function(result, reason) { var chunk switch (result) { case VncConnection.SECURITYRESULT_OK: chunk = new Buffer(4) chunk.writeUInt32BE(result, 0) this._write(chunk) break case VncConnection.SECURITYRESULT_FAIL: chunk = new Buffer(4 + 4 + reason.length) chunk.writeUInt32BE(result, 0) chunk.writeUInt32BE(reason.length, 4) chunk.write(reason, 8, reason.length) this._write(chunk) break } } VncConnection.prototype._writeServerInit = function() { var chunk = new Buffer(2 + 2 + 16 + 4 + this._serverName.length) chunk.writeUInt16BE(this._serverWidth, 0) chunk.writeUInt16BE(this._serverHeight, 2) chunk[4] = this._serverPixelFormat.bitsPerPixel chunk[5] = this._serverPixelFormat.depth chunk[6] = this._serverPixelFormat.bigEndianFlag chunk[7] = this._serverPixelFormat.trueColorFlag chunk.writeUInt16BE(this._serverPixelFormat.redMax, 8) chunk.writeUInt16BE(this._serverPixelFormat.greenMax, 10) chunk.writeUInt16BE(this._serverPixelFormat.blueMax, 12) chunk[14] = this._serverPixelFormat.redShift chunk[15] = this._serverPixelFormat.greenShift chunk[16] = this._serverPixelFormat.blueShift chunk[17] = 0 // padding chunk[18] = 0 // padding chunk[19] = 0 // padding chunk.writeUInt32BE(this._serverName.length, 20) chunk.write(this._serverName, 24, this._serverName.length) this._write(chunk) } VncConnection.prototype._readableListener = function() { this._read() } VncConnection.prototype._read = function() { var chunk, lo, hi while (this._append(this.conn.read())) { do { debug('state', StateReverse[this._state]) chunk = null switch (this._state) { case VncConnection.STATE_NEED_CLIENT_VERSION: if ((chunk = this._consume(12))) { if ((this._clientVersion = this._parseVersion(chunk)) === null) { this.end() return } debug('client version', this._clientVersion) this._writeSupportedSecurity() this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY) } break case VncConnection.STATE_NEED_CLIENT_SECURITY: if ((chunk = this._consume(1))) { if ((this._clientSecurity = this._parseSecurity(chunk)) === null) { this._writeSecurityResult( VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type') this.end() return } debug('client security', this._clientSecurity) this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) this.emit('authenticated') this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) } break case VncConnection.STATE_NEED_CLIENT_INIT: if ((chunk = this._consume(1))) { this._clientShare = chunk[0] debug('client shareFlag', this._clientShare) this._writeServerInit() this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE: if ((chunk = this._consume(1))) { switch (chunk[0]) { case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT) break case VncConnection.CLIENT_MESSAGE_SETENCODINGS: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS) break case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST) break case VncConnection.CLIENT_MESSAGE_KEYEVENT: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT) break case VncConnection.CLIENT_MESSAGE_POINTEREVENT: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT) break case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT: this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT) break default: this._error(new Error(util.format( 'Unsupported message type %d', chunk[0]))) return } } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: if ((chunk = this._consume(19))) { // [0b, 3b) padding this._clientPixelFormat = new PixelFormat({ bitsPerPixel: chunk[3] , depth: chunk[4] , bigEndianFlag: chunk[5] , trueColorFlag: chunk[6] , redMax: chunk.readUInt16BE(7, true) , greenMax: chunk.readUInt16BE(9, true) , blueMax: chunk.readUInt16BE(11, true) , redShift: chunk[13] , greenShift: chunk[14] , blueShift: chunk[15] }) // [16b, 19b) padding debug('client pixel format', this._clientPixelFormat) if (this._requireServerPixelFormat && this._clientPixelFormat.bitsPerPixel < this._serverPixelFormat.bitsPerPixel) { this.end() return } this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: if ((chunk = this._consume(3))) { // [0b, 1b) padding this._clientEncodingCount = chunk.readUInt16BE(1, true) this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: lo = 0 hi = 4 * this._clientEncodingCount if ((chunk = this._consume(hi))) { this._clientEncodings = [] while (lo < hi) { this._clientEncodings.push(chunk.readInt32BE(lo, true)) lo += 4 } debug('client encodings', this._clientEncodings) this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: if ((chunk = this._consume(9))) { this.emit('fbupdaterequest', { incremental: chunk[0] , xPosition: chunk.readUInt16BE(1, true) , yPosition: chunk.readUInt16BE(3, true) , width: chunk.readUInt16BE(5, true) , height: chunk.readUInt16BE(7, true) }) this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT: if ((chunk = this._consume(7))) { // downFlag = chunk[0] // [1b, 3b) padding // key = chunk.readUInt32BE(3, true) this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: if ((chunk = this._consume(5))) { this.emit('pointer', { buttonMask: chunk[0] , xPosition: chunk.readUInt16BE(1, true) / this._serverWidth , yPosition: chunk.readUInt16BE(3, true) / this._serverHeight }) this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: if ((chunk = this._consume(7))) { // [0b, 3b) padding this._clientCutTextLength = chunk.readUInt32BE(3) this._changeState( VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE) } break case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: if ((chunk = this._consume(this._clientCutTextLength))) { // value = chunk this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) } break default: throw new Error(util.format('Impossible state %d', this._state)) } } while (chunk) } } VncConnection.prototype._parseVersion = function(chunk) { if (chunk.equals(new Buffer('RFB 003.008\n'))) { return VncConnection.V3_008 } if (chunk.equals(new Buffer('RFB 003.007\n'))) { return VncConnection.V3_007 } if (chunk.equals(new Buffer('RFB 003.003\n'))) { return VncConnection.V3_003 } return null } VncConnection.prototype._parseSecurity = function(chunk) { switch (chunk[0]) { case VncConnection.SECURITY_NONE: case VncConnection.SECURITY_VNC: return chunk[0] default: return null } } VncConnection.prototype._changeState = function(state) { this._state = state } VncConnection.prototype._append = function(chunk) { if (!chunk) { return false } debug('in', chunk) if (this._buffer) { this._buffer = Buffer.concat( [this._buffer, chunk], this._buffer.length + chunk.length) } else { this._buffer = chunk } return true } VncConnection.prototype._consume = function(n) { var chunk if (!this._buffer) { return null } if (n < this._buffer.length) { chunk = this._buffer.slice(0, n) this._buffer = this._buffer.slice(n) return chunk } if (n === this._buffer.length) { chunk = this._buffer this._buffer = null return chunk } return null } VncConnection.prototype._write = function(chunk) { debug('out', chunk) this.conn.write(chunk) } module.exports = VncConnection