Validate all incoming payloads in Stream (#6)

This commit is contained in:
Powersource 2024-03-13 13:04:45 +01:00 committed by GitHub
parent 93f00dbd04
commit 5fef427ebb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 6 deletions

View File

@ -1,3 +1,14 @@
/**
* @param {any} range
* @return {range is Range}
*/
function isRange(range) {
if (!Array.isArray(range)) return false
if (range.length !== 2) return false
if (!Number.isInteger(range[0]) || !Number.isInteger(range[1])) return false
return true
}
/** /**
* @typedef {[number, number]} Range * @typedef {[number, number]} Range
*/ */
@ -26,7 +37,8 @@ function estimateMsgCount(range) {
const EMPTY_RANGE = /** @type {Range} */ ([1, 0]) const EMPTY_RANGE = /** @type {Range} */ ([1, 0])
module.exports = { module.exports = {
isRange,
isEmptyRange, isEmptyRange,
estimateMsgCount, estimateMsgCount,
EMPTY_RANGE EMPTY_RANGE,
} }

View File

@ -1,6 +1,7 @@
// @ts-ignore // @ts-ignore
const Pipeable = require('push-stream/pipeable') const Pipeable = require('push-stream/pipeable')
const { isEmptyRange } = require('./range') const { isRange, isEmptyRange } = require('./range')
const { isMsgId, isBloom, isMsgIds, isMsgs } = require('./util')
/** /**
* @typedef {ReturnType<import('ppppp-goals').init>} PPPPPGoals * @typedef {ReturnType<import('ppppp-goals').init>} PPPPPGoals
@ -449,18 +450,34 @@ class SyncStream extends Pipeable {
* @param {Data} data * @param {Data} data
*/ */
write(data) { write(data) {
if (!data) return this.#debug('Invalid data from remote peer: missing data')
// prettier-ignore
if (typeof data !== 'object') return this.#debug('Invalid data from remote peer: not an object')
// prettier-ignore
if (Array.isArray(data)) return this.#debug('Invalid data from remote peer: is an array')
const { id, phase, payload } = data const { id, phase, payload } = data
// prettier-ignore
if (typeof phase !== 'number') return this.#debug("Invalid data from remote peer: phase isn't a number")
// prettier-ignore
if (!isMsgId(id)) return this.#debug('Invalid data from remote peer: id is not a valid msg id')
// prettier-ignore
if (phase !== 0 && !payload) return this.#debug('Invalid data from remote peer: payload is missing')
// TODO: validate that each data objects has the exact correct shape
switch (phase) { switch (phase) {
case 0: { case 0: {
return this.#sendLocalHave(id) return this.#sendLocalHave(id)
} }
case 1: { case 1: {
// prettier-ignore
if (!isRange(payload)) return this.#debug('Invalid data from remote peer: payload is not a range in phase 1')
return this.#sendLocalHaveAndWant(id, payload) return this.#sendLocalHaveAndWant(id, payload)
} }
case 2: { case 2: {
const { haveRange, wantRange } = payload const { haveRange, wantRange } = payload
// prettier-ignore
if (!isRange(haveRange) || !isRange(wantRange)) return this.#debug('Invalid data from remote peer: haveRange or wantRange is not a range in phase 2')
if (isEmptyRange(haveRange)) { if (isEmptyRange(haveRange)) {
// prettier-ignore // prettier-ignore
this.#debug('%s Stream IN2: received remote have-range %o and want-range %o for %s', this.#myId, haveRange, wantRange, id) this.#debug('%s Stream IN2: received remote have-range %o and want-range %o for %s', this.#myId, haveRange, wantRange, id)
@ -471,6 +488,11 @@ class SyncStream extends Pipeable {
} }
case 3: { case 3: {
const { wantRange, bloom } = payload const { wantRange, bloom } = payload
// prettier-ignore
if (!isRange(wantRange)) return this.#debug('Invalid data from remote peer: wantRange is not a range in phase 3')
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 3')
const haveRange = this.#remoteHave.get(id) const haveRange = this.#remoteHave.get(id)
if (haveRange && isEmptyRange(haveRange)) { if (haveRange && isEmptyRange(haveRange)) {
// prettier-ignore // prettier-ignore
@ -482,32 +504,62 @@ class SyncStream extends Pipeable {
} }
case 4: { case 4: {
const { bloom, msgIDs } = payload const { bloom, msgIDs } = payload
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 4')
// prettier-ignore
if (!isMsgIds(msgIDs)) return this.#debug('Invalid data from remote peer: msgIDs is not an array of msg ids in phase 4')
return this.#sendBloomReq(id, phase + 1, 1, bloom, msgIDs) return this.#sendBloomReq(id, phase + 1, 1, bloom, msgIDs)
} }
case 5: { case 5: {
const { bloom, msgIDs } = payload const { bloom, msgIDs } = payload
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 5')
// prettier-ignore
if (!isMsgIds(msgIDs)) return this.#debug('Invalid data from remote peer: msgIDs is not an array of msg ids in phase 5')
return this.#sendBloomRes(id, phase + 1, 1, bloom, msgIDs) return this.#sendBloomRes(id, phase + 1, 1, bloom, msgIDs)
} }
case 6: { case 6: {
const { bloom, msgIDs } = payload const { bloom, msgIDs } = payload
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 6')
// prettier-ignore
if (!isMsgIds(msgIDs)) return this.#debug('Invalid data from remote peer: msgIDs is not an array of msg ids in phase 6')
return this.#sendBloomReq(id, phase + 1, 2, bloom, msgIDs) return this.#sendBloomReq(id, phase + 1, 2, bloom, msgIDs)
} }
case 7: { case 7: {
const { bloom, msgIDs } = payload const { bloom, msgIDs } = payload
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 7')
// prettier-ignore
if (!isMsgIds(msgIDs)) return this.#debug('Invalid data from remote peer: msgIDs is not an array of msg ids in phase 7')
return this.#sendMissingMsgsReq(id, 2, bloom, msgIDs) return this.#sendMissingMsgsReq(id, 2, bloom, msgIDs)
} }
case 8: { case 8: {
const { bloom, msgs } = payload const { bloom, msgs } = payload
// prettier-ignore
if (!isBloom(bloom)) return this.#debug('Invalid data from remote peer: bloom is not a bloom in phase 8')
// prettier-ignore
if (!isMsgs(msgs)) return this.#debug('Invalid data from remote peer: msgs is not an array of msgs in phase 8')
return this.#sendMissingMsgsRes(id, 2, bloom, msgs) return this.#sendMissingMsgsRes(id, 2, bloom, msgs)
} }
case 9: { case 9: {
// prettier-ignore
if (!isMsgs(payload)) return this.#debug('Invalid data from remote peer: payload is not an array of msgs in phase 9')
// prettier-ignore // prettier-ignore
this.#debug('%s Stream IN9: got %s msgs in %s', this.#myId, payload.length, id) this.#debug('%s Stream IN9: got %s msgs in %s', this.#myId, payload.length, id)
return this.#consumeMissingMsgs(id, payload) return this.#consumeMissingMsgs(id, payload)
} }
default: {
// prettier-ignore
return this.#debug('Invalid data from remote peer: phase is an invalid number')
}
} }
this.#debug('Stream IN: unknown %o', data)
} }
/** /**

94
lib/util.js Normal file
View File

@ -0,0 +1,94 @@
const bs58 = require('bs58')
/**
* @typedef {import('./range').Range} Range
* @typedef {import('ppppp-db/msg-v4').Msg} Msg
*/
/**
* @param {any} msgId
* @return {msgId is string}
*/
function isMsgId(msgId) {
try {
const d = bs58.decode(msgId)
return d.length === 32
} catch {
return false
}
}
/**
* @param {any} msgIds
* @return {msgIds is Array<string>}
*/
function isMsgIds(msgIds) {
if (!Array.isArray(msgIds)) return false
return msgIds.every(isMsgId)
}
/**
* @param {any} msgs
* @return {msgs is Array<Msg>}
*/
function isMsgs(msgs) {
if (!Array.isArray(msgs)) return false
return msgs.every(isMsg)
}
/**
* @param {any} bloom
* @return {bloom is string}
*/
function isBloom(bloom) {
// TODO: validate when blooming is stabilized
return !!bloom
}
/**
* @param {any} msg
* @returns {msg is Msg}
*/
function isMsg(msg) {
if (!msg || typeof msg !== 'object') {
return false
}
if (!('data' in msg)) {
return false
}
if (!msg.metadata || typeof msg.metadata !== 'object') {
return false
}
if (!('dataHash' in msg.metadata)) {
return false
}
if (!('dataSize' in msg.metadata)) {
return false
}
if (!('account' in msg.metadata)) {
return false
}
if (!('accountTips' in msg.metadata)) {
return false
}
if (!('tangles' in msg.metadata)) {
return false
}
if (!('domain' in msg.metadata)) {
return false
}
if (msg.metadata.v !== 4) {
return false
}
if (typeof msg.sig !== 'string') {
return false
}
return true
}
module.exports = {
isMsgId,
isMsgIds,
isMsgs,
isBloom,
isMsg,
}

View File

@ -24,6 +24,7 @@
}, },
"dependencies": { "dependencies": {
"bloom-filters": "^3.0.0", "bloom-filters": "^3.0.0",
"bs58": "^5.0.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"promisify-4loc": "^1.0.0", "promisify-4loc": "^1.0.0",
"pull-stream": "^3.7.0", "pull-stream": "^3.7.0",
@ -35,7 +36,6 @@
"@types/debug": "^4.1.9", "@types/debug": "^4.1.9",
"@types/pull-stream": "3.6.3", "@types/pull-stream": "3.6.3",
"@types/node": "16.x", "@types/node": "16.x",
"bs58": "^5.0.0",
"c8": "7", "c8": "7",
"ppppp-caps": "github:staltz/ppppp-caps#93fa810b9a40b78aef4872d4c2a8412cccb52929", "ppppp-caps": "github:staltz/ppppp-caps#93fa810b9a40b78aef4872d4c2a8412cccb52929",
"ppppp-db": "github:staltz/ppppp-db#cf1532965ea1d16929ed2291a9b737a4ce74caac", "ppppp-db": "github:staltz/ppppp-db#cf1532965ea1d16929ed2291a9b737a4ce74caac",