-
Notifications
You must be signed in to change notification settings - Fork 18
Changes from all commits
b8de80d
4775651
5c1fc3b
bdc239f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
'use strict' | ||
|
||
const handshake = require('pull-handshake') | ||
const lp = require('pull-length-prefixed') | ||
const pull = require('pull-stream') | ||
const Connection = require('interface-connection').Connection | ||
const debug = require('debug') | ||
const log = debug('multistream:agreement') | ||
log.error = debug('multistream:agreement:error') | ||
|
||
exports.select = (multicodec, callback) => { | ||
const stream = handshake({ | ||
timeout: 60 * 1000 | ||
}, callback) | ||
|
||
const shake = stream.handshake | ||
|
||
log('writing multicodec %s', multicodec) | ||
writeEncoded(shake, new Buffer(multicodec + '\n'), callback) | ||
|
||
lp.decodeFromReader(shake, (err, data) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
const protocol = data.toString().slice(0, -1) | ||
|
||
if (protocol !== multicodec) { | ||
return callback(new Error(`"${multicodec}" not supported`), shake.rest()) | ||
} | ||
|
||
log('multicodec ack') | ||
callback(null, shake.rest()) | ||
}) | ||
|
||
return stream | ||
} | ||
|
||
exports.handlerSelector = (rawConn, handlersMap) => { | ||
const cb = (err) => { | ||
// incoming errors are irrelevant for the app | ||
log.error(err) | ||
} | ||
|
||
const stream = handshake({ | ||
timeout: 60 * 1000 | ||
}, cb) | ||
|
||
const shake = stream.handshake | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bug 🚨 https://github.com/multiformats/multistream the dialer side does the right thing https://github.com/multiformats/js-multistream/pull/19/files#r77424842 Detected this while testing interop with go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed this further. It also expects that the dialer writes the multistream header first, while it should just write it away |
||
next() | ||
|
||
function next () { | ||
lp.decodeFromReader(shake, (err, data) => { | ||
if (err) { | ||
return cb(err) | ||
} | ||
log('received: %s', data.toString()) | ||
const protocol = data.toString().slice(0, -1) | ||
const result = Object.keys(handlersMap).filter((id) => id === protocol) | ||
const key = result && result[0] | ||
|
||
if (key) { | ||
log('ack: %s', protocol) | ||
writeEncoded(shake, data, cb) | ||
handlersMap[key](new Connection(shake.rest(), rawConn)) | ||
} else { | ||
log('received multicodec of not supported protocol: %s', protocol) | ||
writeEncoded(shake, new Buffer('na\n')) | ||
next() | ||
} | ||
}) | ||
} | ||
|
||
return stream | ||
} | ||
|
||
// prefixes a message with a varint | ||
function encode (msg, cb) { | ||
const values = Buffer.isBuffer(msg) ? [msg] : [new Buffer(msg)] | ||
|
||
pull( | ||
pull.values(values), | ||
lp.encode(), | ||
pull.collect((err, encoded) => { | ||
if (err) { | ||
return cb(err) | ||
} | ||
cb(null, encoded[0]) | ||
}) | ||
) | ||
} | ||
|
||
function writeEncoded (writer, msg, cb) { | ||
encode(msg, (err, msg) => { | ||
if (err) { | ||
return cb(err) | ||
} | ||
writer.write(msg) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
'use strict' | ||
|
||
module.exports = { | ||
PROTOCOL_ID: '/multistream/1.0.0' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,100 @@ | ||
'use strict' | ||
|
||
const lps = require('length-prefixed-stream') | ||
const PROTOCOL_ID = require('./protocol-id') | ||
const lp = require('pull-length-prefixed') | ||
const varint = require('varint') | ||
const range = require('lodash.range') | ||
const series = require('run-series') | ||
const pull = require('pull-stream') | ||
const Connection = require('interface-connection').Connection | ||
const debug = require('debug') | ||
const log = debug('multistream:dialer') | ||
|
||
exports = module.exports = Dialer | ||
const PROTOCOL_ID = require('./constants').PROTOCOL_ID | ||
const agrmt = require('./agreement') | ||
|
||
function Dialer () { | ||
if (!(this instanceof Dialer)) { | ||
return new Dialer() | ||
module.exports = class Dialer { | ||
constructor () { | ||
this.conn = null | ||
} | ||
|
||
const encode = lps.encode() | ||
const decode = lps.decode() | ||
let conn | ||
|
||
// perform the multistream handshake | ||
this.handle = (_conn, callback) => { | ||
encode.pipe(_conn) | ||
_conn.pipe(decode) | ||
|
||
decode.once('data', (buffer) => { | ||
const msg = buffer.toString().slice(0, -1) | ||
if (msg === PROTOCOL_ID) { | ||
encode.write(new Buffer(PROTOCOL_ID + '\n')) | ||
conn = _conn | ||
callback() | ||
} else { | ||
callback(new Error('Incompatible multistream')) | ||
handle (rawConn, callback) { | ||
log('handling connection') | ||
const ms = agrmt.select(PROTOCOL_ID, (err, conn) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
log('handshake success') | ||
|
||
this.conn = new Connection(conn, rawConn) | ||
|
||
callback() | ||
}) | ||
pull(rawConn, ms, rawConn) | ||
} | ||
|
||
this.select = (protocol, callback) => { | ||
if (!conn) { | ||
select (protocol, callback) { | ||
log('dialer select %s', protocol) | ||
if (!this.conn) { | ||
return callback(new Error('multistream handshake has not finalized yet')) | ||
} | ||
|
||
encode.write(new Buffer(protocol + '\n')) | ||
decode.once('data', function (msgBuffer) { | ||
const msg = msgBuffer.toString().slice(0, -1) | ||
if (msg === protocol) { | ||
return callback(null, conn) | ||
} | ||
if (msg === 'na') { | ||
return callback(new Error(protocol + ' not supported')) | ||
const selectStream = agrmt.select(protocol, (err, conn) => { | ||
if (err) { | ||
this.conn = new Connection(conn, this.conn) | ||
return callback(err) | ||
} | ||
callback(null, new Connection(conn, this.conn)) | ||
}) | ||
|
||
pull( | ||
this.conn, | ||
selectStream, | ||
this.conn | ||
) | ||
} | ||
|
||
this.ls = (callback) => { | ||
encode.write(new Buffer('ls' + '\n')) | ||
let protos = [] | ||
decode.once('data', function (msgBuffer) { | ||
const size = varint.decode(msgBuffer) // eslint-disable-line | ||
const nProtos = varint.decode(msgBuffer, varint.decode.bytes) | ||
|
||
timesSeries(nProtos, (n, next) => { | ||
decode.once('data', function (msgBuffer) { | ||
protos.push(msgBuffer.toString().slice(0, -1)) | ||
next() | ||
ls (callback) { | ||
const lsStream = agrmt.select('ls', (err, conn) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
|
||
pull( | ||
conn, | ||
lp.decode(), | ||
collectLs(conn), | ||
pull.map(stringify), | ||
pull.collect((err, list) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
callback(null, list.slice(1)) | ||
}) | ||
}, (err) => { | ||
if (err) { | ||
return callback(err) | ||
} | ||
callback(null, protos) | ||
}) | ||
) | ||
}) | ||
|
||
pull( | ||
this.conn, | ||
lsStream, | ||
this.conn | ||
) | ||
} | ||
} | ||
|
||
function timesSeries (i, work, callback) { | ||
series(range(i).map((i) => (callback) => work(i, callback)), callback) | ||
function stringify (buf) { | ||
return buf.toString().slice(0, -1) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need a func to stringify a buffer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm, got it it is for the pull stuff There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and for dropping the trailing |
||
|
||
function collectLs (conn) { | ||
let first = true | ||
let counter = 0 | ||
|
||
return pull.take((msg) => { | ||
if (first) { | ||
varint.decode(msg) | ||
counter = varint.decode(msg, varint.decode.bytes) | ||
return true | ||
} | ||
|
||
return counter-- > 0 | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't find this method on https://github.com/dignifiedquire/pull-length-prefixed#usage docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a secret method ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it documented now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's released but there is still no api docs on pull-length-prefixed