UNPKG

dat-replication-protocol

Version:

streaming implementation of the dat replication protocol

264 lines (210 loc) 5.48 kB
var stream = require('stream') var varint = require('varint') var util = require('util') var messages = require('./messages') var SIGNAL_FLUSH = new Buffer([0]) var BlobStream = function(parent) { stream.Readable.call(this) this.destroyed = false this._ondrain = null this._parent = parent this.on('end', this._read) } util.inherits(BlobStream, stream.Readable) BlobStream.prototype.destroy = function(err) { if (this.destroyed) return this.destroyed = true if (err) this.emit('error', err) this.emit('close') this._parent.destroy() } var compose = function(a,b) { return function() { a() b() } } BlobStream.prototype._push = function(data, cb) { if (this.push(data)) cb() else this._ondrain = this._ondrain ? compose(this._ondrain, cb) : cb } BlobStream.prototype._end = function() { this.push(null) } BlobStream.prototype._read = function() { var ondrain = this._ondrain this._ondrain = null if (ondrain) ondrain() } var defaultFinalize = function(cb) { cb() } var defaultChange = function(change, cb) { cb() } var defaultBlob = function(stream, cb) { stream.resume() cb() } var Decoder = function() { if (!(this instanceof Decoder)) return new Decoder() stream.Writable.call(this) this.destroyed = false this.bytes = 0 this.changes = 0 this.blobs = 0 this._pending = 0 this._onflush = null this._buffer = null this._blob = null this._header = new Buffer(50) this._ptr = 0 this._id = 0 this._missing = 0 this._onchange = defaultChange this._onblob = defaultBlob this._onfinalize = defaultFinalize var self = this this._up = function() { self._pending++ return self._down } this._down = function() { if (--self._pending > 0) return var onflush = self._onflush self._onflush = null if (onflush) self._consume(onflush) } } util.inherits(Decoder, stream.Writable) Decoder.prototype.destroy = function(err) { if (this.destroyed) return this.destroyed = true if (this._blob) this._blob.destroy() if (err) this.emit('error', err) this.emit('close') } Decoder.prototype.change = function(fn) { this._onchange = fn } Decoder.prototype.blob = function(fn) { this._onblob = fn } Decoder.prototype.finalize = function(fn) { this._onfinalize = fn } Decoder.prototype._write = function(data, enc, cb) { if (data === SIGNAL_FLUSH) { this._onfinalize(cb) return } this.bytes += data.length this._overflow = data this._consume(cb) } Decoder.prototype.end = function(data, enc, cb) { if (typeof data === 'function') return this.end(null, null, data) if (typeof enc === 'function') return this.end(data, null, enc) if (data) this.write(data) this.write(SIGNAL_FLUSH) stream.Writable.prototype.end.call(this, cb) } Decoder.prototype._consume = function(cb) { while (this._overflow && this._pending <= 0 && !this.destroyed) { switch (this._id) { case 0: this._overflow = this._onheader(this._overflow) break case 1: this._overflow = this._onchangedata(this._overflow) break case 2: this._overflow = this._onblobdata(this._overflow) break default: this.destroy(new Error('Protocol error, unknown type: '+this._id)) return } } if (this.destroyed) return if (this._pending <= 0) cb() else this._onflush = cb } Decoder.prototype._onblobend = function() { this._pending++ this._blob._end() this._blob = null this._id = 0 this._ptr = 0 } Decoder.prototype._onblobdata = function(data) { if (!this._blob) { this.blobs++ this._blob = new BlobStream(this) this._onblob(this._blob, this._down) } if (data.length === this._missing) { this._blob._push(data, this._up()) this._onblobend() return null } if (data.length < this._missing) { this._missing -= data.length this._blob._push(data, this._up()) return null } var overflow = data.slice(this._missing) this._blob._push(data.slice(0, this._missing), this._up()) this._onblobend() return overflow } Decoder.prototype._onchangeend = function(data) { this._id = 0 this._ptr = 0 this._buffer = null data = messages.Change.decode(data) this.changes++ this._onchange(data, this._up()) } Decoder.prototype._onchangedata = function(data) { if (!this._buffer) { // fast track if (data.length === this._missing) { this._onchangeend(data) return null } if (data.length > this._missing) { var overflow = data.slice(this._missing) this._onchangeend(data.slice(0, this._missing)) return overflow } this._buffer = new Buffer(this._missing) } if (data.length < this._missing) { data.copy(this._buffer, this._ptr) this._ptr += data.length this._missing -= data.length return null } if (data.length === this._missing) { data.copy(this._buffer, this._ptr) this._onchangeend(this._buffer) return null } var overflow = data.slice(this._missing) data.copy(this._buffer, this._ptr) this._onchangeend(this._buffer) return overflow } Decoder.prototype._onheader = function(data) { for (var i = 0; i < data.length; i++) { this._header[this._ptr++] = data[i] if (this._ptr > 1 && !(this._header[this._ptr-2] & 0x80)) { this._missing = varint.decode(this._header)-1 this._id = data[i] this._ptr = 0 return data.slice(i+1) } } return null } module.exports = Decoder