datoms-protocol
Version:
An integrated implementation of hypercore protocol with both stream and state machine
109 lines (92 loc) • 3.23 kB
JavaScript
const SH = require('simple-handshake')
const crypto = require('hypercore-crypto')
const varint = require('varint')
const b4a = require('b4a')
module.exports = class ProtocolHandshake {
constructor (initiator, payload, opts, done) {
this.options = opts
this.ondone = done
this.buffer = null
this.length = 0
this.remotePayload = null
this.payload = payload
this.keyPair = opts.keyPair || ProtocolHandshake.keyPair()
this.remotePublicKey = null
this.onrecv = onrecv.bind(this)
this.onsend = onsend.bind(this)
this.destroyed = false
const staticKeyPair = {
publicKey: this.keyPair.publicKey,
secretKey: this.keyPair.secretKey.subarray(0, 32)
}
this.noise = SH(initiator, {
pattern: 'XX',
onhandshake,
staticKeyPair: staticKeyPair,
onstatickey: onstatickey.bind(this)
})
const self = this
if (this.noise.waiting === false) process.nextTick(start, this)
function onhandshake (state, cb) {
process.nextTick(finish, self)
cb(null)
}
}
recv (data) {
if (this.destroyed) return
if (this.buffer) this.buffer = Buffer.concat([this.buffer, data])
else this.buffer = data
while (!this.destroyed && !this.noise.finished) {
if (!this.buffer || this.buffer.length < 3) return
if (this.length) {
if (this.buffer.length < this.length) return
const message = this.buffer.slice(0, this.length)
this.buffer = this.length < this.buffer.length ? this.buffer.slice(this.length) : null
this.length = 0
this.noise.recv(message, this.onrecv)
} else {
this.length = varint.decode(this.buffer, 0)
this.buffer = this.buffer.slice(varint.decode.bytes)
}
}
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
if (!this.noise.finished) this.noise.destroy()
this.ondone(err || new Error('Handshake destroyed'), null, null, null, null, null)
}
static keyPair (seed) {
return crypto.keyPair(seed)
}
}
function finish (self) {
if (self.destroyed) return
self.destroyed = true
const split = { rx: Buffer.from(self.noise.split.rx), tx: Buffer.from(self.noise.split.tx) }
self.ondone(null, self.remotePayload, split, self.buffer, self.remotePublicKey, self.noise.handshakeHash)
}
function start (self) {
if (self.destroyed) return
self.noise.send(self.payload, self.onsend)
}
function onsend (err, data) {
if (err) return this.destroy(err)
const buf = Buffer.allocUnsafe(varint.encodingLength(data.length) + data.length)
varint.encode(data.length, buf, 0)
data.copy(buf, varint.encode.bytes)
this.options.send(buf)
}
function onrecv (err, data) { // data is reused so we need to copy it if we use it
if (err) return this.destroy(err)
if (data && data.length) this.remotePayload = Buffer.from(data)
if (this.destroyed || this.noise.finished) return
if (this.noise.waiting === false) {
this.noise.send(this.payload, this.onsend)
}
}
function onstatickey (remoteKey, done) {
this.remotePublicKey = Buffer.from(remoteKey)
if (this.options.onauthenticate) this.options.onauthenticate(this.remotePublicKey, done)
else done(null)
}