├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── handshake.js └── xor.js ├── messages.js ├── package.json ├── schema.proto └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-hypercore-protocol 2 | 3 | Hypercore protocol state machine 4 | 5 | ``` 6 | npm install simple-hypercore-protocol 7 | ``` 8 | 9 | Includes a Noise handshake, and is not backwards compatible with Hypercore <= 7 10 | 11 | ## Usage 12 | 13 | ``` js 14 | const Protocol = require('simple-hypercore-protocol') 15 | 16 | const a = new Protocol(true, { 17 | send (data) { // send hook should send data 18 | b.recv(data) 19 | } 20 | }) 21 | 22 | const b = new Protocol(false, { 23 | onrequest (channel, message) { 24 | console.log('got request message', message, 'on channel', channel) 25 | }, 26 | send (data) { 27 | a.recv(data) 28 | } 29 | }) 30 | 31 | // send a request message on channel 10 32 | a.request(10, { 33 | index: 42 34 | }) 35 | ``` 36 | 37 | ## API 38 | 39 | This is still a work in progress, so that messages supported might change. 40 | See the schema.proto file for the schema for each message. 41 | 42 | #### `p = new Protocol(isInitator, handlers)` 43 | 44 | Create a new protocol state machine. 45 | 46 | * `isInitator` is a boolean indicating if you are a client or server 47 | * `handlers` is a series of functions handling incoming messages 48 | 49 | Everytime a binary message should be sent to another peer, 50 | `handlers.send(data)` is invoked. 51 | 52 | If there is a critical error, `handlers.destroy(err)` is called. 53 | 54 | After the initial handshake transport encryption is enabled, 55 | to ensure your stream is private. 56 | 57 | To disable transport encryption set `handlers.encrypted = false`. 58 | 59 | To disable the NOISE handshake set `handlers.noise = false` (works only when `encrypted` is also set to false). 60 | 61 | #### `p.recv(data)` 62 | 63 | Call this with incoming data. 64 | 65 | #### `buf = p.remoteCapability(key)` 66 | 67 | Create a remote capability for a key. Use this to verify 68 | if a remote indeed had a key when you get an `open` message. 69 | 70 | #### `buf = p.capability(key)` 71 | 72 | Create a local capability. 73 | 74 | #### `p.destroy(err)` 75 | 76 | Destroy the protocol state machine. 77 | 78 | #### `p.publicKey` 79 | 80 | The local public key used for authentication. 81 | 82 | #### `p.remotePublicKey` 83 | 84 | The remotes public key. 85 | 86 | #### `p.handshakeHash` 87 | 88 | The noise handshake hash which uniquely identifies the noise session. 89 | 90 | http://noiseprotocol.org/noise.html#channel-binding 91 | 92 | #### `handlers.onauthenticate(remotePublicKey, done)` 93 | 94 | Called when you should authenticate a remote public key. 95 | 96 | #### `handlers.onhandshake()` 97 | 98 | Called when the initial protocol handshake has finished. 99 | 100 | #### `p.open(channel, message)` 101 | 102 | Send an open message on a channel. 103 | 104 | Note that if you message.key the protocol, will turn that into a capability that is sent instead of the key. 105 | 106 | Receiving an open message triggers `handlers.onopen(channel, message)` 107 | 108 | #### `p.options(channel, message)` 109 | 110 | Send a options message on a channel. 111 | 112 | Receiving a handshake message triggers `handlers.onoptions(channel, message)` 113 | 114 | #### `p.status(channel, message)` 115 | 116 | Send a status message on a channel. 117 | 118 | Receiving a info message triggers `handlers.onstatus(channel, message)` 119 | 120 | #### `p.have(channel, message)` 121 | 122 | Send a have message on a channel. 123 | 124 | Receiving a have message triggers `handlers.onhave(channel, message)` 125 | 126 | #### `p.unhave(channel, message)` 127 | 128 | Send an unhave message on a channel. 129 | 130 | Receiving an unhave message triggers `handlers.onunhave(channel, message)` 131 | 132 | #### `p.want(channel, message)` 133 | 134 | Send a want message on a channel. 135 | 136 | Receiving a want message triggers `handlers.onwant(channel, message)` 137 | 138 | #### `p.unwant(channel, message)` 139 | 140 | Send an unwant message on a channel. 141 | 142 | Receiving an unwant message triggers `handlers.onunwant(channel, message)` 143 | 144 | #### `p.request(channel, message)` 145 | 146 | Send a request message on a channel. 147 | 148 | Receiving a request message triggers `handlers.onrequest(channel, message)` 149 | 150 | #### `p.cancel(channel, message)` 151 | 152 | Send a cancel message on a channel. 153 | 154 | Receiving a cancel message triggers `handlers.oncancel(channel, message)` 155 | 156 | #### `p.data(channel, message)` 157 | 158 | Send a data message on a channel. 159 | 160 | Receiving a data message triggers `handlers.ondata(channel, message)` 161 | 162 | #### `keyPair = Protocol.keyPair([seed])` 163 | 164 | Static function to generate a Noise key pair, optionally from a seed. 165 | 166 | ## License 167 | 168 | MIT 169 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Handshake = require('./lib/handshake') 2 | const messages = require('./messages') 3 | const XOR = require('./lib/xor') 4 | const SMC = require('simple-message-channels') 5 | const crypto = require('hypercore-crypto') 6 | const varint = require('varint') 7 | 8 | module.exports = class SimpleProtocol { 9 | constructor (initiator, handlers) { 10 | const payload = { nonce: XOR.nonce() } 11 | 12 | this.handlers = handlers || {} 13 | this.remotePayload = null 14 | this.remotePublicKey = null 15 | this.publicKey = null 16 | this.handshakeHash = null 17 | this.destroyed = false 18 | 19 | this._initiator = initiator 20 | this._payload = payload 21 | this._pending = [] 22 | this._handshake = null 23 | this._split = null 24 | this._encryption = null 25 | this._noise = !(handlers.encrypted === false && handlers.noise === false) 26 | this._buffering = null 27 | this._handshaking = false 28 | 29 | this._messages = new SMC({ 30 | onmessage, 31 | onmissing, 32 | context: this, 33 | types: [ 34 | { context: this, onmessage: onopen, encoding: messages.Open }, 35 | { context: this, onmessage: onoptions, encoding: messages.Options }, 36 | { context: this, onmessage: onstatus, encoding: messages.Status }, 37 | { context: this, onmessage: onhave, encoding: messages.Have }, 38 | { context: this, onmessage: onunhave, encoding: messages.Unhave }, 39 | { context: this, onmessage: onwant, encoding: messages.Want }, 40 | { context: this, onmessage: onunwant, encoding: messages.Unwant }, 41 | { context: this, onmessage: onrequest, encoding: messages.Request }, 42 | { context: this, onmessage: oncancel, encoding: messages.Cancel }, 43 | { context: this, onmessage: ondata, encoding: messages.Data }, 44 | { context: this, onmessage: onclose, encoding: messages.Close } 45 | ] 46 | }) 47 | 48 | if (handlers.encrypted !== false || handlers.noise !== false) { 49 | this._handshaking = true 50 | if (typeof this.handlers.keyPair !== 'function') { 51 | this._onkeypair(null, this.handlers.keyPair || null) 52 | } else { 53 | this._buffering = [] 54 | this.handlers.keyPair(this._onkeypair.bind(this)) 55 | } 56 | } 57 | } 58 | 59 | _onkeypair (err, keyPair) { 60 | if (err) return this.destroy(err) 61 | if (this._handshake !== null) return 62 | 63 | this.handlers.keyPair = keyPair 64 | const handshake = new Handshake(this._initiator, messages.NoisePayload.encode(this._payload), this.handlers, this._onhandshake.bind(this)) 65 | 66 | this.publicKey = handshake.keyPair.publicKey 67 | this._handshake = handshake 68 | 69 | if (this._buffering) { 70 | while (this._buffering.length) this._recv(this._buffering.shift()) 71 | } 72 | 73 | this._buffering = null 74 | } 75 | 76 | open (ch, message) { 77 | return this._send(ch, 0, message) 78 | } 79 | 80 | options (ch, message) { 81 | return this._send(ch, 1, message) 82 | } 83 | 84 | status (ch, message) { 85 | return this._send(ch, 2, message) 86 | } 87 | 88 | have (ch, message) { 89 | return this._send(ch, 3, message) 90 | } 91 | 92 | unhave (ch, message) { 93 | return this._send(ch, 4, message) 94 | } 95 | 96 | want (ch, message) { 97 | return this._send(ch, 5, message) 98 | } 99 | 100 | unwant (ch, message) { 101 | return this._send(ch, 6, message) 102 | } 103 | 104 | request (ch, message) { 105 | return this._send(ch, 7, message) 106 | } 107 | 108 | cancel (ch, message) { 109 | return this._send(ch, 8, message) 110 | } 111 | 112 | data (ch, message) { 113 | return this._send(ch, 9, message) 114 | } 115 | 116 | close (ch, message) { 117 | return this._send(ch, 10, message || {}) 118 | } 119 | 120 | extension (ch, id, message) { 121 | const buf = Buffer.allocUnsafe(varint.encodingLength(id) + message.length) 122 | 123 | varint.encode(id, buf, 0) 124 | message.copy(buf, varint.encode.bytes) 125 | 126 | return this._send(ch, 15, buf) 127 | } 128 | 129 | ping () { 130 | if (this._handshaking || this._pending.length) return 131 | 132 | let ping = Buffer.from([0]) 133 | if (this._encryption !== null) { 134 | ping = this._encryption.encrypt(ping) 135 | } 136 | 137 | return this.handlers.send(ping) 138 | } 139 | 140 | _onhandshake (err, remotePayload, split, overflow, remotePublicKey, handshakeHash) { 141 | if (err) return this.destroy(new Error('Noise handshake error')) // workaround for https://github.com/emilbayes/noise-protocol/issues/5 142 | if (!remotePayload) return this.destroy(new Error('Remote did not include a handshake payload')) 143 | 144 | this.remotePublicKey = remotePublicKey 145 | this.handshakeHash = handshakeHash 146 | 147 | try { 148 | remotePayload = messages.NoisePayload.decode(remotePayload) 149 | } catch (_) { 150 | return this.destroy(new Error('Could not parse remote payload')) 151 | } 152 | 153 | this._handshake = null 154 | this._handshaking = false 155 | this._split = split 156 | this._encryption = this.handlers.encrypted === false 157 | ? null 158 | : new XOR({ rnonce: remotePayload.nonce, tnonce: this._payload.nonce }, split) 159 | 160 | this.remotePayload = remotePayload 161 | 162 | if (this.handlers.onhandshake) this.handlers.onhandshake() 163 | if (this.destroyed) return 164 | 165 | if (overflow) this.recv(overflow) 166 | while (this._pending.length && !this.destroyed) { 167 | this._sendNow(...this._pending.shift()) 168 | } 169 | } 170 | 171 | _send (channel, type, message) { 172 | if (this._handshaking || this._pending.length) { 173 | this._pending.push([channel, type, message]) 174 | return false 175 | } 176 | 177 | return this._sendNow(channel, type, message) 178 | } 179 | 180 | _sendNow (channel, type, message) { 181 | if (type === 0 && message.key && !message.capability) { 182 | message.capability = this.capability(message.key) 183 | message.key = null 184 | } 185 | 186 | let data = this._messages.send(channel, type, message) 187 | 188 | if (this._encryption !== null) { 189 | data = this._encryption.encrypt(data) 190 | } 191 | 192 | return this.handlers.send(data) 193 | } 194 | 195 | capability (key) { 196 | return crypto.capability(key, this._split) 197 | } 198 | 199 | remoteCapability (key) { 200 | return crypto.remoteCapability(key, this._split) 201 | } 202 | 203 | recv (data) { 204 | if (this._buffering !== null) this._buffering.push(data) 205 | else this._recv(data) 206 | } 207 | 208 | _recv (data) { 209 | if (this.destroyed) return 210 | 211 | if (this._handshaking) { 212 | this._handshake.recv(data) 213 | return 214 | } 215 | 216 | if (this._encryption !== null) { 217 | data = this._encryption.decrypt(data) 218 | } 219 | 220 | if (!this._messages.recv(data)) { 221 | this.destroy(this._messages.error) 222 | } 223 | } 224 | 225 | destroy (err) { 226 | if (this.destroyed) return 227 | this.destroyed = true 228 | if (this._handshake) this._handshake.destroy() 229 | if (this._encryption) this._encryption.destroy() 230 | if (this.handlers.destroy) this.handlers.destroy(err) 231 | } 232 | 233 | static keyPair (seed) { 234 | return Handshake.keyPair(seed) 235 | } 236 | } 237 | 238 | function onopen (ch, message, self) { 239 | if (self.handlers.onopen) self.handlers.onopen(ch, message) 240 | } 241 | 242 | function onoptions (ch, message, self) { 243 | if (self.handlers.onoptions) self.handlers.onoptions(ch, message) 244 | } 245 | 246 | function onstatus (ch, message, self) { 247 | if (self.handlers.onstatus) self.handlers.onstatus(ch, message) 248 | } 249 | 250 | function onhave (ch, message, self) { 251 | if (self.handlers.onhave) self.handlers.onhave(ch, message) 252 | } 253 | 254 | function onunhave (ch, message, self) { 255 | if (self.handlers.onunhave) self.handlers.onunhave(ch, message) 256 | } 257 | 258 | function onwant (ch, message, self) { 259 | if (self.handlers.onwant) self.handlers.onwant(ch, message) 260 | } 261 | 262 | function onunwant (ch, message, self) { 263 | if (self.handlers.onunwant) self.handlers.onunwant(ch, message) 264 | } 265 | 266 | function onrequest (ch, message, self) { 267 | if (self.handlers.onrequest) self.handlers.onrequest(ch, message) 268 | } 269 | 270 | function oncancel (ch, message, self) { 271 | if (self.handlers.oncancel) self.handlers.oncancel(ch, message) 272 | } 273 | 274 | function ondata (ch, message, self) { 275 | if (self.handlers.ondata) self.handlers.ondata(ch, message) 276 | } 277 | 278 | function onclose (ch, message, self) { 279 | if (self.handlers.onclose) self.handlers.onclose(ch, message) 280 | } 281 | 282 | function onmessage (ch, type, message, self) { 283 | if (type !== 15) return 284 | const id = varint.decode(message) 285 | const m = message.slice(varint.decode.bytes) 286 | if (self.handlers.onextension) self.handlers.onextension(ch, id, m) 287 | } 288 | 289 | function onmissing (bytes, self) { 290 | if (self.handlers.onmissing) self.handlers.onmissing(bytes) 291 | } 292 | -------------------------------------------------------------------------------- /lib/handshake.js: -------------------------------------------------------------------------------- 1 | const SH = require('simple-handshake') 2 | const DH = require('noise-protocol/dh') 3 | const crypto = require('hypercore-crypto') 4 | const varint = require('varint') 5 | 6 | module.exports = class ProtocolHandshake { 7 | constructor (initiator, payload, opts, done) { 8 | this.options = opts 9 | this.ondone = done 10 | this.buffer = null 11 | this.length = 0 12 | this.remotePayload = null 13 | this.payload = payload 14 | this.keyPair = opts.keyPair || ProtocolHandshake.keyPair() 15 | this.remotePublicKey = null 16 | this.onrecv = onrecv.bind(this) 17 | this.onsend = onsend.bind(this) 18 | this.destroyed = false 19 | this.noise = SH(initiator, { 20 | pattern: 'XX', 21 | onhandshake, 22 | staticKeyPair: this.keyPair, 23 | onstatickey: onstatickey.bind(this) 24 | }) 25 | 26 | const self = this 27 | if (this.noise.waiting === false) process.nextTick(start, this) 28 | 29 | function onhandshake (state, cb) { 30 | process.nextTick(finish, self) 31 | cb(null) 32 | } 33 | } 34 | 35 | recv (data) { 36 | if (this.destroyed) return 37 | 38 | if (this.buffer) this.buffer = Buffer.concat([this.buffer, data]) 39 | else this.buffer = data 40 | 41 | while (!this.destroyed && !this.noise.finished) { 42 | if (!this.buffer || this.buffer.length < 3) return 43 | if (this.length) { 44 | if (this.buffer.length < this.length) return 45 | const message = this.buffer.slice(0, this.length) 46 | this.buffer = this.length < this.buffer.length ? this.buffer.slice(this.length) : null 47 | this.length = 0 48 | this.noise.recv(message, this.onrecv) 49 | } else { 50 | this.length = varint.decode(this.buffer, 0) 51 | this.buffer = this.buffer.slice(varint.decode.bytes) 52 | } 53 | } 54 | } 55 | 56 | destroy (err) { 57 | if (this.destroyed) return 58 | this.destroyed = true 59 | if (!this.noise.finished) this.noise.destroy() 60 | this.ondone(err) 61 | } 62 | 63 | static keyPair (seed) { 64 | const obj = { 65 | // suboptimal but to reduce secure memory overhead on linux with default settings 66 | // better fix is to batch mallocs in noise-protocol 67 | publicKey: Buffer.alloc(DH.PKLEN), 68 | secretKey: Buffer.alloc(DH.SKLEN) 69 | } 70 | 71 | if (seed) DH.generateSeedKeypair(obj.publicKey, obj.secretKey, seed) 72 | else DH.generateKeypair(obj.publicKey, obj.secretKey) 73 | 74 | return obj 75 | } 76 | } 77 | 78 | function finish (self) { 79 | if (self.destroyed) return 80 | self.destroyed = true 81 | // suboptimal but to reduce secure memory overhead on linux with default settings 82 | // better fix is to batch mallocs in noise-protocol 83 | const split = { rx: Buffer.from(self.noise.split.rx), tx: Buffer.from(self.noise.split.tx) } 84 | crypto.free(self.noise.split.rx) 85 | crypto.free(self.noise.split.tx) 86 | self.ondone(null, self.remotePayload, split, self.buffer, self.remotePublicKey, self.noise.handshakeHash) 87 | } 88 | 89 | function start (self) { 90 | if (self.destroyed) return 91 | self.noise.send(self.payload, self.onsend) 92 | } 93 | 94 | function onsend (err, data) { 95 | if (err) return this.destroy(err) 96 | const buf = Buffer.allocUnsafe(varint.encodingLength(data.length) + data.length) 97 | varint.encode(data.length, buf, 0) 98 | data.copy(buf, varint.encode.bytes) 99 | this.options.send(buf) 100 | } 101 | 102 | function onrecv (err, data) { // data is reused so we need to copy it if we use it 103 | if (err) return this.destroy(err) 104 | if (data && data.length) this.remotePayload = Buffer.from(data) 105 | if (this.destroyed || this.noise.finished) return 106 | 107 | if (this.noise.waiting === false) { 108 | this.noise.send(this.payload, this.onsend) 109 | } 110 | } 111 | 112 | function onstatickey (remoteKey, done) { 113 | this.remotePublicKey = Buffer.from(remoteKey) 114 | if (this.options.onauthenticate) this.options.onauthenticate(this.remotePublicKey, done) 115 | else done(null) 116 | } 117 | -------------------------------------------------------------------------------- /lib/xor.js: -------------------------------------------------------------------------------- 1 | const XSalsa20 = require('xsalsa20-universal') 2 | const crypto = require('hypercore-crypto') 3 | 4 | module.exports = class XOR { 5 | constructor (nonces, split) { 6 | this.rnonce = nonces.rnonce 7 | this.tnonce = nonces.tnonce 8 | this.rx = new XSalsa20(this.rnonce, split.rx.slice(0, 32)) 9 | this.tx = new XSalsa20(this.tnonce, split.tx.slice(0, 32)) 10 | } 11 | 12 | encrypt (data) { 13 | this.tx.update(data, data) 14 | return data 15 | } 16 | 17 | decrypt (data) { 18 | this.rx.update(data, data) 19 | return data 20 | } 21 | 22 | destroy () { 23 | this.tx.final() 24 | this.rx.final() 25 | } 26 | 27 | static nonce () { 28 | return crypto.randomBytes(24) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /messages.js: -------------------------------------------------------------------------------- 1 | // This file is auto generated by the protocol-buffers compiler 2 | 3 | /* eslint-disable quotes */ 4 | /* eslint-disable indent */ 5 | /* eslint-disable no-redeclare */ 6 | /* eslint-disable camelcase */ 7 | 8 | // Remember to `npm install --save protocol-buffers-encodings` 9 | var encodings = require('protocol-buffers-encodings') 10 | var varint = encodings.varint 11 | var skip = encodings.skip 12 | 13 | var NoisePayload = exports.NoisePayload = { 14 | buffer: true, 15 | encodingLength: null, 16 | encode: null, 17 | decode: null 18 | } 19 | 20 | var Open = exports.Open = { 21 | buffer: true, 22 | encodingLength: null, 23 | encode: null, 24 | decode: null 25 | } 26 | 27 | var Options = exports.Options = { 28 | buffer: true, 29 | encodingLength: null, 30 | encode: null, 31 | decode: null 32 | } 33 | 34 | var Status = exports.Status = { 35 | buffer: true, 36 | encodingLength: null, 37 | encode: null, 38 | decode: null 39 | } 40 | 41 | var Have = exports.Have = { 42 | buffer: true, 43 | encodingLength: null, 44 | encode: null, 45 | decode: null 46 | } 47 | 48 | var Unhave = exports.Unhave = { 49 | buffer: true, 50 | encodingLength: null, 51 | encode: null, 52 | decode: null 53 | } 54 | 55 | var Want = exports.Want = { 56 | buffer: true, 57 | encodingLength: null, 58 | encode: null, 59 | decode: null 60 | } 61 | 62 | var Unwant = exports.Unwant = { 63 | buffer: true, 64 | encodingLength: null, 65 | encode: null, 66 | decode: null 67 | } 68 | 69 | var Request = exports.Request = { 70 | buffer: true, 71 | encodingLength: null, 72 | encode: null, 73 | decode: null 74 | } 75 | 76 | var Cancel = exports.Cancel = { 77 | buffer: true, 78 | encodingLength: null, 79 | encode: null, 80 | decode: null 81 | } 82 | 83 | var Data = exports.Data = { 84 | buffer: true, 85 | encodingLength: null, 86 | encode: null, 87 | decode: null 88 | } 89 | 90 | var Close = exports.Close = { 91 | buffer: true, 92 | encodingLength: null, 93 | encode: null, 94 | decode: null 95 | } 96 | 97 | defineNoisePayload() 98 | defineOpen() 99 | defineOptions() 100 | defineStatus() 101 | defineHave() 102 | defineUnhave() 103 | defineWant() 104 | defineUnwant() 105 | defineRequest() 106 | defineCancel() 107 | defineData() 108 | defineClose() 109 | 110 | function defineNoisePayload () { 111 | var enc = [ 112 | encodings.bytes 113 | ] 114 | 115 | NoisePayload.encodingLength = encodingLength 116 | NoisePayload.encode = encode 117 | NoisePayload.decode = decode 118 | 119 | function encodingLength (obj) { 120 | var length = 0 121 | if (!defined(obj.nonce)) throw new Error("nonce is required") 122 | var len = enc[0].encodingLength(obj.nonce) 123 | length += 1 + len 124 | return length 125 | } 126 | 127 | function encode (obj, buf, offset) { 128 | if (!offset) offset = 0 129 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 130 | var oldOffset = offset 131 | if (!defined(obj.nonce)) throw new Error("nonce is required") 132 | buf[offset++] = 10 133 | enc[0].encode(obj.nonce, buf, offset) 134 | offset += enc[0].encode.bytes 135 | encode.bytes = offset - oldOffset 136 | return buf 137 | } 138 | 139 | function decode (buf, offset, end) { 140 | if (!offset) offset = 0 141 | if (!end) end = buf.length 142 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 143 | var oldOffset = offset 144 | var obj = { 145 | nonce: null 146 | } 147 | var found0 = false 148 | while (true) { 149 | if (end <= offset) { 150 | if (!found0) throw new Error("Decoded message is not valid") 151 | decode.bytes = offset - oldOffset 152 | return obj 153 | } 154 | var prefix = varint.decode(buf, offset) 155 | offset += varint.decode.bytes 156 | var tag = prefix >> 3 157 | switch (tag) { 158 | case 1: 159 | obj.nonce = enc[0].decode(buf, offset) 160 | offset += enc[0].decode.bytes 161 | found0 = true 162 | break 163 | default: 164 | offset = skip(prefix & 7, buf, offset) 165 | } 166 | } 167 | } 168 | } 169 | 170 | function defineOpen () { 171 | var enc = [ 172 | encodings.bytes 173 | ] 174 | 175 | Open.encodingLength = encodingLength 176 | Open.encode = encode 177 | Open.decode = decode 178 | 179 | function encodingLength (obj) { 180 | var length = 0 181 | if (!defined(obj.discoveryKey)) throw new Error("discoveryKey is required") 182 | var len = enc[0].encodingLength(obj.discoveryKey) 183 | length += 1 + len 184 | if (defined(obj.capability)) { 185 | var len = enc[0].encodingLength(obj.capability) 186 | length += 1 + len 187 | } 188 | return length 189 | } 190 | 191 | function encode (obj, buf, offset) { 192 | if (!offset) offset = 0 193 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 194 | var oldOffset = offset 195 | if (!defined(obj.discoveryKey)) throw new Error("discoveryKey is required") 196 | buf[offset++] = 10 197 | enc[0].encode(obj.discoveryKey, buf, offset) 198 | offset += enc[0].encode.bytes 199 | if (defined(obj.capability)) { 200 | buf[offset++] = 18 201 | enc[0].encode(obj.capability, buf, offset) 202 | offset += enc[0].encode.bytes 203 | } 204 | encode.bytes = offset - oldOffset 205 | return buf 206 | } 207 | 208 | function decode (buf, offset, end) { 209 | if (!offset) offset = 0 210 | if (!end) end = buf.length 211 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 212 | var oldOffset = offset 213 | var obj = { 214 | discoveryKey: null, 215 | capability: null 216 | } 217 | var found0 = false 218 | while (true) { 219 | if (end <= offset) { 220 | if (!found0) throw new Error("Decoded message is not valid") 221 | decode.bytes = offset - oldOffset 222 | return obj 223 | } 224 | var prefix = varint.decode(buf, offset) 225 | offset += varint.decode.bytes 226 | var tag = prefix >> 3 227 | switch (tag) { 228 | case 1: 229 | obj.discoveryKey = enc[0].decode(buf, offset) 230 | offset += enc[0].decode.bytes 231 | found0 = true 232 | break 233 | case 2: 234 | obj.capability = enc[0].decode(buf, offset) 235 | offset += enc[0].decode.bytes 236 | break 237 | default: 238 | offset = skip(prefix & 7, buf, offset) 239 | } 240 | } 241 | } 242 | } 243 | 244 | function defineOptions () { 245 | var enc = [ 246 | encodings.string, 247 | encodings.bool 248 | ] 249 | 250 | Options.encodingLength = encodingLength 251 | Options.encode = encode 252 | Options.decode = decode 253 | 254 | function encodingLength (obj) { 255 | var length = 0 256 | if (defined(obj.extensions)) { 257 | for (var i = 0; i < obj.extensions.length; i++) { 258 | if (!defined(obj.extensions[i])) continue 259 | var len = enc[0].encodingLength(obj.extensions[i]) 260 | length += 1 + len 261 | } 262 | } 263 | if (defined(obj.ack)) { 264 | var len = enc[1].encodingLength(obj.ack) 265 | length += 1 + len 266 | } 267 | return length 268 | } 269 | 270 | function encode (obj, buf, offset) { 271 | if (!offset) offset = 0 272 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 273 | var oldOffset = offset 274 | if (defined(obj.extensions)) { 275 | for (var i = 0; i < obj.extensions.length; i++) { 276 | if (!defined(obj.extensions[i])) continue 277 | buf[offset++] = 10 278 | enc[0].encode(obj.extensions[i], buf, offset) 279 | offset += enc[0].encode.bytes 280 | } 281 | } 282 | if (defined(obj.ack)) { 283 | buf[offset++] = 16 284 | enc[1].encode(obj.ack, buf, offset) 285 | offset += enc[1].encode.bytes 286 | } 287 | encode.bytes = offset - oldOffset 288 | return buf 289 | } 290 | 291 | function decode (buf, offset, end) { 292 | if (!offset) offset = 0 293 | if (!end) end = buf.length 294 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 295 | var oldOffset = offset 296 | var obj = { 297 | extensions: [], 298 | ack: false 299 | } 300 | while (true) { 301 | if (end <= offset) { 302 | decode.bytes = offset - oldOffset 303 | return obj 304 | } 305 | var prefix = varint.decode(buf, offset) 306 | offset += varint.decode.bytes 307 | var tag = prefix >> 3 308 | switch (tag) { 309 | case 1: 310 | obj.extensions.push(enc[0].decode(buf, offset)) 311 | offset += enc[0].decode.bytes 312 | break 313 | case 2: 314 | obj.ack = enc[1].decode(buf, offset) 315 | offset += enc[1].decode.bytes 316 | break 317 | default: 318 | offset = skip(prefix & 7, buf, offset) 319 | } 320 | } 321 | } 322 | } 323 | 324 | function defineStatus () { 325 | var enc = [ 326 | encodings.bool 327 | ] 328 | 329 | Status.encodingLength = encodingLength 330 | Status.encode = encode 331 | Status.decode = decode 332 | 333 | function encodingLength (obj) { 334 | var length = 0 335 | if (defined(obj.uploading)) { 336 | var len = enc[0].encodingLength(obj.uploading) 337 | length += 1 + len 338 | } 339 | if (defined(obj.downloading)) { 340 | var len = enc[0].encodingLength(obj.downloading) 341 | length += 1 + len 342 | } 343 | return length 344 | } 345 | 346 | function encode (obj, buf, offset) { 347 | if (!offset) offset = 0 348 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 349 | var oldOffset = offset 350 | if (defined(obj.uploading)) { 351 | buf[offset++] = 8 352 | enc[0].encode(obj.uploading, buf, offset) 353 | offset += enc[0].encode.bytes 354 | } 355 | if (defined(obj.downloading)) { 356 | buf[offset++] = 16 357 | enc[0].encode(obj.downloading, buf, offset) 358 | offset += enc[0].encode.bytes 359 | } 360 | encode.bytes = offset - oldOffset 361 | return buf 362 | } 363 | 364 | function decode (buf, offset, end) { 365 | if (!offset) offset = 0 366 | if (!end) end = buf.length 367 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 368 | var oldOffset = offset 369 | var obj = { 370 | uploading: false, 371 | downloading: false 372 | } 373 | while (true) { 374 | if (end <= offset) { 375 | decode.bytes = offset - oldOffset 376 | return obj 377 | } 378 | var prefix = varint.decode(buf, offset) 379 | offset += varint.decode.bytes 380 | var tag = prefix >> 3 381 | switch (tag) { 382 | case 1: 383 | obj.uploading = enc[0].decode(buf, offset) 384 | offset += enc[0].decode.bytes 385 | break 386 | case 2: 387 | obj.downloading = enc[0].decode(buf, offset) 388 | offset += enc[0].decode.bytes 389 | break 390 | default: 391 | offset = skip(prefix & 7, buf, offset) 392 | } 393 | } 394 | } 395 | } 396 | 397 | function defineHave () { 398 | var enc = [ 399 | encodings.varint, 400 | encodings.bytes, 401 | encodings.bool 402 | ] 403 | 404 | Have.encodingLength = encodingLength 405 | Have.encode = encode 406 | Have.decode = decode 407 | 408 | function encodingLength (obj) { 409 | var length = 0 410 | if (!defined(obj.start)) throw new Error("start is required") 411 | var len = enc[0].encodingLength(obj.start) 412 | length += 1 + len 413 | if (defined(obj.length)) { 414 | var len = enc[0].encodingLength(obj.length) 415 | length += 1 + len 416 | } 417 | if (defined(obj.bitfield)) { 418 | var len = enc[1].encodingLength(obj.bitfield) 419 | length += 1 + len 420 | } 421 | if (defined(obj.ack)) { 422 | var len = enc[2].encodingLength(obj.ack) 423 | length += 1 + len 424 | } 425 | return length 426 | } 427 | 428 | function encode (obj, buf, offset) { 429 | if (!offset) offset = 0 430 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 431 | var oldOffset = offset 432 | if (!defined(obj.start)) throw new Error("start is required") 433 | buf[offset++] = 8 434 | enc[0].encode(obj.start, buf, offset) 435 | offset += enc[0].encode.bytes 436 | if (defined(obj.length)) { 437 | buf[offset++] = 16 438 | enc[0].encode(obj.length, buf, offset) 439 | offset += enc[0].encode.bytes 440 | } 441 | if (defined(obj.bitfield)) { 442 | buf[offset++] = 26 443 | enc[1].encode(obj.bitfield, buf, offset) 444 | offset += enc[1].encode.bytes 445 | } 446 | if (defined(obj.ack)) { 447 | buf[offset++] = 32 448 | enc[2].encode(obj.ack, buf, offset) 449 | offset += enc[2].encode.bytes 450 | } 451 | encode.bytes = offset - oldOffset 452 | return buf 453 | } 454 | 455 | function decode (buf, offset, end) { 456 | if (!offset) offset = 0 457 | if (!end) end = buf.length 458 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 459 | var oldOffset = offset 460 | var obj = { 461 | start: 0, 462 | length: 1, 463 | bitfield: null, 464 | ack: false 465 | } 466 | var found0 = false 467 | while (true) { 468 | if (end <= offset) { 469 | if (!found0) throw new Error("Decoded message is not valid") 470 | decode.bytes = offset - oldOffset 471 | return obj 472 | } 473 | var prefix = varint.decode(buf, offset) 474 | offset += varint.decode.bytes 475 | var tag = prefix >> 3 476 | switch (tag) { 477 | case 1: 478 | obj.start = enc[0].decode(buf, offset) 479 | offset += enc[0].decode.bytes 480 | found0 = true 481 | break 482 | case 2: 483 | obj.length = enc[0].decode(buf, offset) 484 | offset += enc[0].decode.bytes 485 | break 486 | case 3: 487 | obj.bitfield = enc[1].decode(buf, offset) 488 | offset += enc[1].decode.bytes 489 | break 490 | case 4: 491 | obj.ack = enc[2].decode(buf, offset) 492 | offset += enc[2].decode.bytes 493 | break 494 | default: 495 | offset = skip(prefix & 7, buf, offset) 496 | } 497 | } 498 | } 499 | } 500 | 501 | function defineUnhave () { 502 | var enc = [ 503 | encodings.varint 504 | ] 505 | 506 | Unhave.encodingLength = encodingLength 507 | Unhave.encode = encode 508 | Unhave.decode = decode 509 | 510 | function encodingLength (obj) { 511 | var length = 0 512 | if (!defined(obj.start)) throw new Error("start is required") 513 | var len = enc[0].encodingLength(obj.start) 514 | length += 1 + len 515 | if (defined(obj.length)) { 516 | var len = enc[0].encodingLength(obj.length) 517 | length += 1 + len 518 | } 519 | return length 520 | } 521 | 522 | function encode (obj, buf, offset) { 523 | if (!offset) offset = 0 524 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 525 | var oldOffset = offset 526 | if (!defined(obj.start)) throw new Error("start is required") 527 | buf[offset++] = 8 528 | enc[0].encode(obj.start, buf, offset) 529 | offset += enc[0].encode.bytes 530 | if (defined(obj.length)) { 531 | buf[offset++] = 16 532 | enc[0].encode(obj.length, buf, offset) 533 | offset += enc[0].encode.bytes 534 | } 535 | encode.bytes = offset - oldOffset 536 | return buf 537 | } 538 | 539 | function decode (buf, offset, end) { 540 | if (!offset) offset = 0 541 | if (!end) end = buf.length 542 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 543 | var oldOffset = offset 544 | var obj = { 545 | start: 0, 546 | length: 1 547 | } 548 | var found0 = false 549 | while (true) { 550 | if (end <= offset) { 551 | if (!found0) throw new Error("Decoded message is not valid") 552 | decode.bytes = offset - oldOffset 553 | return obj 554 | } 555 | var prefix = varint.decode(buf, offset) 556 | offset += varint.decode.bytes 557 | var tag = prefix >> 3 558 | switch (tag) { 559 | case 1: 560 | obj.start = enc[0].decode(buf, offset) 561 | offset += enc[0].decode.bytes 562 | found0 = true 563 | break 564 | case 2: 565 | obj.length = enc[0].decode(buf, offset) 566 | offset += enc[0].decode.bytes 567 | break 568 | default: 569 | offset = skip(prefix & 7, buf, offset) 570 | } 571 | } 572 | } 573 | } 574 | 575 | function defineWant () { 576 | var enc = [ 577 | encodings.varint 578 | ] 579 | 580 | Want.encodingLength = encodingLength 581 | Want.encode = encode 582 | Want.decode = decode 583 | 584 | function encodingLength (obj) { 585 | var length = 0 586 | if (!defined(obj.start)) throw new Error("start is required") 587 | var len = enc[0].encodingLength(obj.start) 588 | length += 1 + len 589 | if (defined(obj.length)) { 590 | var len = enc[0].encodingLength(obj.length) 591 | length += 1 + len 592 | } 593 | return length 594 | } 595 | 596 | function encode (obj, buf, offset) { 597 | if (!offset) offset = 0 598 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 599 | var oldOffset = offset 600 | if (!defined(obj.start)) throw new Error("start is required") 601 | buf[offset++] = 8 602 | enc[0].encode(obj.start, buf, offset) 603 | offset += enc[0].encode.bytes 604 | if (defined(obj.length)) { 605 | buf[offset++] = 16 606 | enc[0].encode(obj.length, buf, offset) 607 | offset += enc[0].encode.bytes 608 | } 609 | encode.bytes = offset - oldOffset 610 | return buf 611 | } 612 | 613 | function decode (buf, offset, end) { 614 | if (!offset) offset = 0 615 | if (!end) end = buf.length 616 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 617 | var oldOffset = offset 618 | var obj = { 619 | start: 0, 620 | length: 0 621 | } 622 | var found0 = false 623 | while (true) { 624 | if (end <= offset) { 625 | if (!found0) throw new Error("Decoded message is not valid") 626 | decode.bytes = offset - oldOffset 627 | return obj 628 | } 629 | var prefix = varint.decode(buf, offset) 630 | offset += varint.decode.bytes 631 | var tag = prefix >> 3 632 | switch (tag) { 633 | case 1: 634 | obj.start = enc[0].decode(buf, offset) 635 | offset += enc[0].decode.bytes 636 | found0 = true 637 | break 638 | case 2: 639 | obj.length = enc[0].decode(buf, offset) 640 | offset += enc[0].decode.bytes 641 | break 642 | default: 643 | offset = skip(prefix & 7, buf, offset) 644 | } 645 | } 646 | } 647 | } 648 | 649 | function defineUnwant () { 650 | var enc = [ 651 | encodings.varint 652 | ] 653 | 654 | Unwant.encodingLength = encodingLength 655 | Unwant.encode = encode 656 | Unwant.decode = decode 657 | 658 | function encodingLength (obj) { 659 | var length = 0 660 | if (!defined(obj.start)) throw new Error("start is required") 661 | var len = enc[0].encodingLength(obj.start) 662 | length += 1 + len 663 | if (defined(obj.length)) { 664 | var len = enc[0].encodingLength(obj.length) 665 | length += 1 + len 666 | } 667 | return length 668 | } 669 | 670 | function encode (obj, buf, offset) { 671 | if (!offset) offset = 0 672 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 673 | var oldOffset = offset 674 | if (!defined(obj.start)) throw new Error("start is required") 675 | buf[offset++] = 8 676 | enc[0].encode(obj.start, buf, offset) 677 | offset += enc[0].encode.bytes 678 | if (defined(obj.length)) { 679 | buf[offset++] = 16 680 | enc[0].encode(obj.length, buf, offset) 681 | offset += enc[0].encode.bytes 682 | } 683 | encode.bytes = offset - oldOffset 684 | return buf 685 | } 686 | 687 | function decode (buf, offset, end) { 688 | if (!offset) offset = 0 689 | if (!end) end = buf.length 690 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 691 | var oldOffset = offset 692 | var obj = { 693 | start: 0, 694 | length: 0 695 | } 696 | var found0 = false 697 | while (true) { 698 | if (end <= offset) { 699 | if (!found0) throw new Error("Decoded message is not valid") 700 | decode.bytes = offset - oldOffset 701 | return obj 702 | } 703 | var prefix = varint.decode(buf, offset) 704 | offset += varint.decode.bytes 705 | var tag = prefix >> 3 706 | switch (tag) { 707 | case 1: 708 | obj.start = enc[0].decode(buf, offset) 709 | offset += enc[0].decode.bytes 710 | found0 = true 711 | break 712 | case 2: 713 | obj.length = enc[0].decode(buf, offset) 714 | offset += enc[0].decode.bytes 715 | break 716 | default: 717 | offset = skip(prefix & 7, buf, offset) 718 | } 719 | } 720 | } 721 | } 722 | 723 | function defineRequest () { 724 | var enc = [ 725 | encodings.varint, 726 | encodings.bool 727 | ] 728 | 729 | Request.encodingLength = encodingLength 730 | Request.encode = encode 731 | Request.decode = decode 732 | 733 | function encodingLength (obj) { 734 | var length = 0 735 | if (!defined(obj.index)) throw new Error("index is required") 736 | var len = enc[0].encodingLength(obj.index) 737 | length += 1 + len 738 | if (defined(obj.bytes)) { 739 | var len = enc[0].encodingLength(obj.bytes) 740 | length += 1 + len 741 | } 742 | if (defined(obj.hash)) { 743 | var len = enc[1].encodingLength(obj.hash) 744 | length += 1 + len 745 | } 746 | if (defined(obj.nodes)) { 747 | var len = enc[0].encodingLength(obj.nodes) 748 | length += 1 + len 749 | } 750 | return length 751 | } 752 | 753 | function encode (obj, buf, offset) { 754 | if (!offset) offset = 0 755 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 756 | var oldOffset = offset 757 | if (!defined(obj.index)) throw new Error("index is required") 758 | buf[offset++] = 8 759 | enc[0].encode(obj.index, buf, offset) 760 | offset += enc[0].encode.bytes 761 | if (defined(obj.bytes)) { 762 | buf[offset++] = 16 763 | enc[0].encode(obj.bytes, buf, offset) 764 | offset += enc[0].encode.bytes 765 | } 766 | if (defined(obj.hash)) { 767 | buf[offset++] = 24 768 | enc[1].encode(obj.hash, buf, offset) 769 | offset += enc[1].encode.bytes 770 | } 771 | if (defined(obj.nodes)) { 772 | buf[offset++] = 32 773 | enc[0].encode(obj.nodes, buf, offset) 774 | offset += enc[0].encode.bytes 775 | } 776 | encode.bytes = offset - oldOffset 777 | return buf 778 | } 779 | 780 | function decode (buf, offset, end) { 781 | if (!offset) offset = 0 782 | if (!end) end = buf.length 783 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 784 | var oldOffset = offset 785 | var obj = { 786 | index: 0, 787 | bytes: 0, 788 | hash: false, 789 | nodes: 0 790 | } 791 | var found0 = false 792 | while (true) { 793 | if (end <= offset) { 794 | if (!found0) throw new Error("Decoded message is not valid") 795 | decode.bytes = offset - oldOffset 796 | return obj 797 | } 798 | var prefix = varint.decode(buf, offset) 799 | offset += varint.decode.bytes 800 | var tag = prefix >> 3 801 | switch (tag) { 802 | case 1: 803 | obj.index = enc[0].decode(buf, offset) 804 | offset += enc[0].decode.bytes 805 | found0 = true 806 | break 807 | case 2: 808 | obj.bytes = enc[0].decode(buf, offset) 809 | offset += enc[0].decode.bytes 810 | break 811 | case 3: 812 | obj.hash = enc[1].decode(buf, offset) 813 | offset += enc[1].decode.bytes 814 | break 815 | case 4: 816 | obj.nodes = enc[0].decode(buf, offset) 817 | offset += enc[0].decode.bytes 818 | break 819 | default: 820 | offset = skip(prefix & 7, buf, offset) 821 | } 822 | } 823 | } 824 | } 825 | 826 | function defineCancel () { 827 | var enc = [ 828 | encodings.varint, 829 | encodings.bool 830 | ] 831 | 832 | Cancel.encodingLength = encodingLength 833 | Cancel.encode = encode 834 | Cancel.decode = decode 835 | 836 | function encodingLength (obj) { 837 | var length = 0 838 | if (!defined(obj.index)) throw new Error("index is required") 839 | var len = enc[0].encodingLength(obj.index) 840 | length += 1 + len 841 | if (defined(obj.bytes)) { 842 | var len = enc[0].encodingLength(obj.bytes) 843 | length += 1 + len 844 | } 845 | if (defined(obj.hash)) { 846 | var len = enc[1].encodingLength(obj.hash) 847 | length += 1 + len 848 | } 849 | return length 850 | } 851 | 852 | function encode (obj, buf, offset) { 853 | if (!offset) offset = 0 854 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 855 | var oldOffset = offset 856 | if (!defined(obj.index)) throw new Error("index is required") 857 | buf[offset++] = 8 858 | enc[0].encode(obj.index, buf, offset) 859 | offset += enc[0].encode.bytes 860 | if (defined(obj.bytes)) { 861 | buf[offset++] = 16 862 | enc[0].encode(obj.bytes, buf, offset) 863 | offset += enc[0].encode.bytes 864 | } 865 | if (defined(obj.hash)) { 866 | buf[offset++] = 24 867 | enc[1].encode(obj.hash, buf, offset) 868 | offset += enc[1].encode.bytes 869 | } 870 | encode.bytes = offset - oldOffset 871 | return buf 872 | } 873 | 874 | function decode (buf, offset, end) { 875 | if (!offset) offset = 0 876 | if (!end) end = buf.length 877 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 878 | var oldOffset = offset 879 | var obj = { 880 | index: 0, 881 | bytes: 0, 882 | hash: false 883 | } 884 | var found0 = false 885 | while (true) { 886 | if (end <= offset) { 887 | if (!found0) throw new Error("Decoded message is not valid") 888 | decode.bytes = offset - oldOffset 889 | return obj 890 | } 891 | var prefix = varint.decode(buf, offset) 892 | offset += varint.decode.bytes 893 | var tag = prefix >> 3 894 | switch (tag) { 895 | case 1: 896 | obj.index = enc[0].decode(buf, offset) 897 | offset += enc[0].decode.bytes 898 | found0 = true 899 | break 900 | case 2: 901 | obj.bytes = enc[0].decode(buf, offset) 902 | offset += enc[0].decode.bytes 903 | break 904 | case 3: 905 | obj.hash = enc[1].decode(buf, offset) 906 | offset += enc[1].decode.bytes 907 | break 908 | default: 909 | offset = skip(prefix & 7, buf, offset) 910 | } 911 | } 912 | } 913 | } 914 | 915 | function defineData () { 916 | var Node = Data.Node = { 917 | buffer: true, 918 | encodingLength: null, 919 | encode: null, 920 | decode: null 921 | } 922 | 923 | defineNode() 924 | 925 | function defineNode () { 926 | var enc = [ 927 | encodings.varint, 928 | encodings.bytes 929 | ] 930 | 931 | Node.encodingLength = encodingLength 932 | Node.encode = encode 933 | Node.decode = decode 934 | 935 | function encodingLength (obj) { 936 | var length = 0 937 | if (!defined(obj.index)) throw new Error("index is required") 938 | var len = enc[0].encodingLength(obj.index) 939 | length += 1 + len 940 | if (!defined(obj.hash)) throw new Error("hash is required") 941 | var len = enc[1].encodingLength(obj.hash) 942 | length += 1 + len 943 | if (!defined(obj.size)) throw new Error("size is required") 944 | var len = enc[0].encodingLength(obj.size) 945 | length += 1 + len 946 | return length 947 | } 948 | 949 | function encode (obj, buf, offset) { 950 | if (!offset) offset = 0 951 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 952 | var oldOffset = offset 953 | if (!defined(obj.index)) throw new Error("index is required") 954 | buf[offset++] = 8 955 | enc[0].encode(obj.index, buf, offset) 956 | offset += enc[0].encode.bytes 957 | if (!defined(obj.hash)) throw new Error("hash is required") 958 | buf[offset++] = 18 959 | enc[1].encode(obj.hash, buf, offset) 960 | offset += enc[1].encode.bytes 961 | if (!defined(obj.size)) throw new Error("size is required") 962 | buf[offset++] = 24 963 | enc[0].encode(obj.size, buf, offset) 964 | offset += enc[0].encode.bytes 965 | encode.bytes = offset - oldOffset 966 | return buf 967 | } 968 | 969 | function decode (buf, offset, end) { 970 | if (!offset) offset = 0 971 | if (!end) end = buf.length 972 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 973 | var oldOffset = offset 974 | var obj = { 975 | index: 0, 976 | hash: null, 977 | size: 0 978 | } 979 | var found0 = false 980 | var found1 = false 981 | var found2 = false 982 | while (true) { 983 | if (end <= offset) { 984 | if (!found0 || !found1 || !found2) throw new Error("Decoded message is not valid") 985 | decode.bytes = offset - oldOffset 986 | return obj 987 | } 988 | var prefix = varint.decode(buf, offset) 989 | offset += varint.decode.bytes 990 | var tag = prefix >> 3 991 | switch (tag) { 992 | case 1: 993 | obj.index = enc[0].decode(buf, offset) 994 | offset += enc[0].decode.bytes 995 | found0 = true 996 | break 997 | case 2: 998 | obj.hash = enc[1].decode(buf, offset) 999 | offset += enc[1].decode.bytes 1000 | found1 = true 1001 | break 1002 | case 3: 1003 | obj.size = enc[0].decode(buf, offset) 1004 | offset += enc[0].decode.bytes 1005 | found2 = true 1006 | break 1007 | default: 1008 | offset = skip(prefix & 7, buf, offset) 1009 | } 1010 | } 1011 | } 1012 | } 1013 | 1014 | var enc = [ 1015 | encodings.varint, 1016 | encodings.bytes, 1017 | Node 1018 | ] 1019 | 1020 | Data.encodingLength = encodingLength 1021 | Data.encode = encode 1022 | Data.decode = decode 1023 | 1024 | function encodingLength (obj) { 1025 | var length = 0 1026 | if (!defined(obj.index)) throw new Error("index is required") 1027 | var len = enc[0].encodingLength(obj.index) 1028 | length += 1 + len 1029 | if (defined(obj.value)) { 1030 | var len = enc[1].encodingLength(obj.value) 1031 | length += 1 + len 1032 | } 1033 | if (defined(obj.nodes)) { 1034 | for (var i = 0; i < obj.nodes.length; i++) { 1035 | if (!defined(obj.nodes[i])) continue 1036 | var len = enc[2].encodingLength(obj.nodes[i]) 1037 | length += varint.encodingLength(len) 1038 | length += 1 + len 1039 | } 1040 | } 1041 | if (defined(obj.signature)) { 1042 | var len = enc[1].encodingLength(obj.signature) 1043 | length += 1 + len 1044 | } 1045 | return length 1046 | } 1047 | 1048 | function encode (obj, buf, offset) { 1049 | if (!offset) offset = 0 1050 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 1051 | var oldOffset = offset 1052 | if (!defined(obj.index)) throw new Error("index is required") 1053 | buf[offset++] = 8 1054 | enc[0].encode(obj.index, buf, offset) 1055 | offset += enc[0].encode.bytes 1056 | if (defined(obj.value)) { 1057 | buf[offset++] = 18 1058 | enc[1].encode(obj.value, buf, offset) 1059 | offset += enc[1].encode.bytes 1060 | } 1061 | if (defined(obj.nodes)) { 1062 | for (var i = 0; i < obj.nodes.length; i++) { 1063 | if (!defined(obj.nodes[i])) continue 1064 | buf[offset++] = 26 1065 | varint.encode(enc[2].encodingLength(obj.nodes[i]), buf, offset) 1066 | offset += varint.encode.bytes 1067 | enc[2].encode(obj.nodes[i], buf, offset) 1068 | offset += enc[2].encode.bytes 1069 | } 1070 | } 1071 | if (defined(obj.signature)) { 1072 | buf[offset++] = 34 1073 | enc[1].encode(obj.signature, buf, offset) 1074 | offset += enc[1].encode.bytes 1075 | } 1076 | encode.bytes = offset - oldOffset 1077 | return buf 1078 | } 1079 | 1080 | function decode (buf, offset, end) { 1081 | if (!offset) offset = 0 1082 | if (!end) end = buf.length 1083 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 1084 | var oldOffset = offset 1085 | var obj = { 1086 | index: 0, 1087 | value: null, 1088 | nodes: [], 1089 | signature: null 1090 | } 1091 | var found0 = false 1092 | while (true) { 1093 | if (end <= offset) { 1094 | if (!found0) throw new Error("Decoded message is not valid") 1095 | decode.bytes = offset - oldOffset 1096 | return obj 1097 | } 1098 | var prefix = varint.decode(buf, offset) 1099 | offset += varint.decode.bytes 1100 | var tag = prefix >> 3 1101 | switch (tag) { 1102 | case 1: 1103 | obj.index = enc[0].decode(buf, offset) 1104 | offset += enc[0].decode.bytes 1105 | found0 = true 1106 | break 1107 | case 2: 1108 | obj.value = enc[1].decode(buf, offset) 1109 | offset += enc[1].decode.bytes 1110 | break 1111 | case 3: 1112 | var len = varint.decode(buf, offset) 1113 | offset += varint.decode.bytes 1114 | obj.nodes.push(enc[2].decode(buf, offset, offset + len)) 1115 | offset += enc[2].decode.bytes 1116 | break 1117 | case 4: 1118 | obj.signature = enc[1].decode(buf, offset) 1119 | offset += enc[1].decode.bytes 1120 | break 1121 | default: 1122 | offset = skip(prefix & 7, buf, offset) 1123 | } 1124 | } 1125 | } 1126 | } 1127 | 1128 | function defineClose () { 1129 | var enc = [ 1130 | encodings.bytes 1131 | ] 1132 | 1133 | Close.encodingLength = encodingLength 1134 | Close.encode = encode 1135 | Close.decode = decode 1136 | 1137 | function encodingLength (obj) { 1138 | var length = 0 1139 | if (defined(obj.discoveryKey)) { 1140 | var len = enc[0].encodingLength(obj.discoveryKey) 1141 | length += 1 + len 1142 | } 1143 | return length 1144 | } 1145 | 1146 | function encode (obj, buf, offset) { 1147 | if (!offset) offset = 0 1148 | if (!buf) buf = Buffer.allocUnsafe(encodingLength(obj)) 1149 | var oldOffset = offset 1150 | if (defined(obj.discoveryKey)) { 1151 | buf[offset++] = 10 1152 | enc[0].encode(obj.discoveryKey, buf, offset) 1153 | offset += enc[0].encode.bytes 1154 | } 1155 | encode.bytes = offset - oldOffset 1156 | return buf 1157 | } 1158 | 1159 | function decode (buf, offset, end) { 1160 | if (!offset) offset = 0 1161 | if (!end) end = buf.length 1162 | if (!(end <= buf.length && offset <= buf.length)) throw new Error("Decoded message is not valid") 1163 | var oldOffset = offset 1164 | var obj = { 1165 | discoveryKey: null 1166 | } 1167 | while (true) { 1168 | if (end <= offset) { 1169 | decode.bytes = offset - oldOffset 1170 | return obj 1171 | } 1172 | var prefix = varint.decode(buf, offset) 1173 | offset += varint.decode.bytes 1174 | var tag = prefix >> 3 1175 | switch (tag) { 1176 | case 1: 1177 | obj.discoveryKey = enc[0].decode(buf, offset) 1178 | offset += enc[0].decode.bytes 1179 | break 1180 | default: 1181 | offset = skip(prefix & 7, buf, offset) 1182 | } 1183 | } 1184 | } 1185 | } 1186 | 1187 | function defined (val) { 1188 | return val !== null && val !== undefined && (typeof val !== 'number' || !isNaN(val)) 1189 | } 1190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-hypercore-protocol", 3 | "version": "2.1.2", 4 | "description": "Hypercore protocol state machine", 5 | "main": "index.js", 6 | "dependencies": { 7 | "hypercore-crypto": "^2.1.0", 8 | "noise-protocol": "^3.0.1", 9 | "protocol-buffers-encodings": "^1.1.0", 10 | "simple-handshake": "^3.0.0", 11 | "simple-message-channels": "^1.2.1", 12 | "varint": "^5.0.0", 13 | "xsalsa20-universal": "^1.0.0" 14 | }, 15 | "devDependencies": { 16 | "protocol-buffers": "^4.1.0", 17 | "standard": "^14.1.0", 18 | "tape": "^4.11.0" 19 | }, 20 | "scripts": { 21 | "test": "standard && tape test.js", 22 | "protobuf": "protocol-buffers schema.proto -o messages.js" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/mafintosh/simple-hypercore-protocol.git" 27 | }, 28 | "author": "Mathias Buus (@mafintosh)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/mafintosh/simple-hypercore-protocol/issues" 32 | }, 33 | "homepage": "https://github.com/mafintosh/simple-hypercore-protocol" 34 | } 35 | -------------------------------------------------------------------------------- /schema.proto: -------------------------------------------------------------------------------- 1 | // Sent as part of the noise protocol. 2 | message NoisePayload { 3 | required bytes nonce = 1; 4 | } 5 | 6 | // type=0 7 | message Open { 8 | required bytes discoveryKey = 1; 9 | optional bytes capability = 2; 10 | } 11 | 12 | // type=1, overall feed options. can be sent multiple times 13 | message Options { 14 | repeated string extensions = 1; // Should be sorted lexicographically 15 | optional bool ack = 2; // Should all blocks be explicitly acknowledged? 16 | } 17 | 18 | // type=2, message indicating state changes etc. 19 | // initial state for uploading/downloading is true 20 | message Status { 21 | optional bool uploading = 1; 22 | optional bool downloading = 2; 23 | } 24 | 25 | // type=3, what do we have? 26 | message Have { 27 | required uint64 start = 1; 28 | optional uint64 length = 2 [default = 1]; // defaults to 1 29 | optional bytes bitfield = 3; 30 | optional bool ack = 4; // when true, this Have message is an acknowledgement 31 | } 32 | 33 | // type=4, what did we lose? 34 | message Unhave { 35 | required uint64 start = 1; 36 | optional uint64 length = 2 [default = 1]; // defaults to 1 37 | } 38 | 39 | // type=5, what do we want? remote should start sending have messages in this range 40 | message Want { 41 | required uint64 start = 1; 42 | optional uint64 length = 2; // defaults to Infinity or feed.length (if not live) 43 | } 44 | 45 | // type=6, what don't we want anymore? 46 | message Unwant { 47 | required uint64 start = 1; 48 | optional uint64 length = 2; // defaults to Infinity or feed.length (if not live) 49 | } 50 | 51 | // type=7, ask for data 52 | message Request { 53 | required uint64 index = 1; 54 | optional uint64 bytes = 2; 55 | optional bool hash = 3; 56 | optional uint64 nodes = 4; 57 | } 58 | 59 | // type=8, cancel a request 60 | message Cancel { 61 | required uint64 index = 1; 62 | optional uint64 bytes = 2; 63 | optional bool hash = 3; 64 | } 65 | 66 | // type=9, get some data 67 | message Data { 68 | message Node { 69 | required uint64 index = 1; 70 | required bytes hash = 2; 71 | required uint64 size = 3; 72 | } 73 | 74 | required uint64 index = 1; 75 | optional bytes value = 2; 76 | repeated Node nodes = 3; 77 | optional bytes signature = 4; 78 | } 79 | 80 | // type=10, explicitly close a channel. 81 | message Close { 82 | optional bytes discoveryKey = 1; // only send this if you did not do an open 83 | } 84 | 85 | // type=15, extension message 86 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const SHP = require('./') 3 | 4 | const key = Buffer.alloc(32).fill('key') 5 | const discoveryKey = Buffer.alloc(32).fill('discovery') 6 | 7 | tape('single open', function (t) { 8 | const a = new SHP(true, { 9 | send (data) { 10 | b.recv(data) 11 | } 12 | }) 13 | 14 | const b = new SHP(false, { 15 | onopen (ch, message) { 16 | t.same(ch, 0) 17 | t.same(message.discoveryKey, discoveryKey) 18 | t.notOk(message.key) 19 | t.end() 20 | }, 21 | send (data) { 22 | a.recv(data) 23 | } 24 | }) 25 | 26 | a.open(0, { key, discoveryKey }) 27 | }) 28 | 29 | tape('single close', function (t) { 30 | const a = new SHP(true, { 31 | send (data) { 32 | b.recv(data) 33 | } 34 | }) 35 | 36 | const b = new SHP(false, { 37 | onclose (ch, message) { 38 | t.same(ch, 0) 39 | t.same(message.discoveryKey, discoveryKey) 40 | t.end() 41 | }, 42 | send (data) { 43 | a.recv(data) 44 | } 45 | }) 46 | 47 | a.close(0, { discoveryKey }) 48 | }) 49 | 50 | tape('back and fourth', function (t) { 51 | const a = new SHP(true, { 52 | ondata (ch, message) { 53 | t.same(ch, 0) 54 | t.same(message, { 55 | index: 42, 56 | value: Buffer.from('data'), 57 | nodes: [], 58 | signature: null 59 | }) 60 | a.close(ch) 61 | }, 62 | send (data) { 63 | process.nextTick(() => b.recv(data)) 64 | } 65 | }) 66 | 67 | const b = new SHP(false, { 68 | onopen (ch, message) { 69 | t.same(ch, 0) 70 | t.same(message.discoveryKey, discoveryKey) 71 | t.notOk(message.key) 72 | }, 73 | onrequest (ch, message) { 74 | t.same(ch, 0) 75 | t.same(message, { index: 42, bytes: 0, hash: false, nodes: 0 }) 76 | b.data(0, { 77 | index: 42, 78 | value: Buffer.from('data') 79 | }) 80 | }, 81 | onclose (ch) { 82 | t.same(ch, 0) 83 | t.end() 84 | }, 85 | send (data) { 86 | process.nextTick(() => a.recv(data)) 87 | } 88 | }) 89 | 90 | a.open(0, { key, discoveryKey }) 91 | a.request(0, { 92 | index: 42 93 | }) 94 | }) 95 | 96 | tape('various messages', function (t) { 97 | t.plan(9) 98 | 99 | const a = new SHP(true, { 100 | send (data) { 101 | process.nextTick(() => b.recv(data)) 102 | } 103 | }) 104 | 105 | const b = new SHP(false, { 106 | onhandshake () { 107 | t.pass('handshook') 108 | }, 109 | onextension (ch, id, data) { 110 | t.same(ch, 0) 111 | t.same(id, 42) 112 | t.same(data, Buffer.from('binary!')) 113 | }, 114 | onhave (ch, message) { 115 | t.same(ch, 0) 116 | t.same(message.start, 42) 117 | t.same(message.length, 10) 118 | }, 119 | onoptions (ch, message) { 120 | t.same(ch, 0) 121 | t.same(message.ack, true) 122 | }, 123 | send (data) { 124 | process.nextTick(() => a.recv(data)) 125 | } 126 | }) 127 | 128 | a.extension(0, 42, Buffer.from('binary!')) 129 | a.options(0, { ack: true }) 130 | a.have(0, { start: 42, length: 10 }) 131 | }) 132 | 133 | tape('auth', function (t) { 134 | t.plan(4) 135 | 136 | const a = new SHP(true, { 137 | onauthenticate (remotePublicKey, done) { 138 | t.pass('authenticated b') 139 | if (remotePublicKey.equals(b.publicKey)) return done(null) 140 | t.fail('bad public key') 141 | done(new Error('Nope')) 142 | }, 143 | onhandshake () { 144 | t.pass('handshook b') 145 | }, 146 | send (data) { 147 | process.nextTick(() => b.recv(data)) 148 | } 149 | }) 150 | 151 | const b = new SHP(false, { 152 | onauthenticate (remotePublicKey, done) { 153 | t.pass('authenticated a') 154 | if (remotePublicKey.equals(a.publicKey)) return done(null) 155 | t.fail('bad public key') 156 | done(new Error('Nope')) 157 | }, 158 | onhandshake () { 159 | t.pass('handshook a') 160 | }, 161 | send (data) { 162 | process.nextTick(() => a.recv(data)) 163 | } 164 | }) 165 | }) 166 | 167 | tape('send ping', function (t) { 168 | let pinging = false 169 | let sent = 0 170 | 171 | const a = new SHP(true, { 172 | send (data) { 173 | if (pinging) sent++ 174 | b.recv(data) 175 | }, 176 | onhandshake () { 177 | process.nextTick(function () { 178 | pinging = true 179 | for (let i = 0; i < 100; i++) a.ping() 180 | t.same(sent, 100, 'sent a hundred pings') 181 | t.end() 182 | }) 183 | } 184 | }) 185 | 186 | const b = new SHP(false, { 187 | send (data) { 188 | a.recv(data) 189 | } 190 | }) 191 | 192 | a.ping() // should not fail 193 | }) 194 | 195 | tape('set key pair later', function (t) { 196 | let later = null 197 | 198 | const a = new SHP(false, { 199 | keyPair (done) { 200 | setImmediate(function () { 201 | later = SHP.keyPair() 202 | done(null, later) 203 | }) 204 | }, 205 | send (data) { 206 | b.recv(data) 207 | } 208 | }) 209 | 210 | const b = new SHP(true, { 211 | send (data) { 212 | a.recv(data) 213 | }, 214 | onauthenticate (remotePublicKey, done) { 215 | t.same(remotePublicKey, later.publicKey) 216 | t.same(a.publicKey, later.publicKey) 217 | done() 218 | }, 219 | onhandshake () { 220 | t.end() 221 | } 222 | }) 223 | }) 224 | 225 | tape('disable noise', function (t) { 226 | const a = new SHP(true, { 227 | noise: false, 228 | encrypted: false, 229 | onhandshake () { 230 | t.fail('onhandshake may not be called with noise: false') 231 | }, 232 | send (data) { 233 | b.recv(data) 234 | } 235 | }) 236 | 237 | const b = new SHP(false, { 238 | noise: false, 239 | encrypted: false, 240 | onhandshake () { 241 | t.fail('onhandshake may not be called with noise: false') 242 | }, 243 | onopen (ch, message) { 244 | t.same(ch, 0) 245 | t.same(message.discoveryKey, discoveryKey) 246 | t.notOk(message.key) 247 | t.end() 248 | }, 249 | send (data) { 250 | a.recv(data) 251 | } 252 | }) 253 | 254 | a.open(0, { key, discoveryKey }) 255 | }) 256 | 257 | tape('handshakeHash', function (t) { 258 | t.plan(3) 259 | 260 | var pending = 3 261 | const a = new SHP(true, { 262 | onhandshake () { 263 | if (--pending === 0) process.nextTick(check) 264 | }, 265 | send (data) { 266 | process.nextTick(() => b.recv(data)) 267 | } 268 | }) 269 | 270 | const b = new SHP(false, { 271 | onhandshake () { 272 | if (--pending === 0) process.nextTick(check) 273 | }, 274 | send (data) { 275 | process.nextTick(() => a.recv(data)) 276 | } 277 | }) 278 | 279 | if (--pending === 0) check() 280 | function check () { 281 | t.ok(a.handshakeHash) 282 | t.ok(b.handshakeHash) 283 | t.deepEqual(a.handshakeHash, b.handshakeHash) 284 | } 285 | }) 286 | --------------------------------------------------------------------------------