From ef8f484d5c5ef52d9fff54cba6f2850774da1e3b Mon Sep 17 00:00:00 2001 From: U9G Date: Wed, 2 Sep 2026 08:21:45 -0400 Subject: [PATCH] Compiled mapper: throw on a value not in the mappings instead of writing it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compiled write/sizeOf mapper fell through to the raw value when it wasn't in the mappings (`mappings[value] || value`), so an unmapped name reached the underlying numeric type, serialized as NaN -> 0, and went out on the wire as a bogus packet. A packet name that doesn't exist in the current protocol state serialized to a single 0x00 byte with no body — a real packet id the peer then fails to decode. The interpreted mapper already throws here; make the compiled one match. The `|| value` fallback also meant a value legitimately mapped to 0 only worked by accident (0 is falsy, so the name itself was passed to the numeric type and serialized as NaN -> 0). --- src/datatypes/compiler-utils.js | 8 ++++++-- test/misc.js | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/datatypes/compiler-utils.js b/src/datatypes/compiler-utils.js index 2c3d5a8..d60eda5 100644 --- a/src/datatypes/compiler-utils.js +++ b/src/datatypes/compiler-utils.js @@ -159,7 +159,9 @@ return (ctx.${type})(val, buffer, offset) }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) - const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) + let code = `const mapped = ${mappings}[value]\n` + code += 'if (mapped === undefined) throw new Error(value + \' is not in the mappings value\')\n' + code += 'return ' + compiler.callType('mapped', mapper.type) return compiler.wrapCode(code) }] }, @@ -211,7 +213,9 @@ return (ctx.${type})(val) }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) - const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) + let code = `const mapped = ${mappings}[value]\n` + code += 'if (mapped === undefined) throw new Error(value + \' is not in the mappings value\')\n' + code += 'return ' + compiler.callType('mapped', mapper.type) return compiler.wrapCode(code) }] } diff --git a/test/misc.js b/test/misc.js index 83200bc..dab6359 100644 --- a/test/misc.js +++ b/test/misc.js @@ -1,5 +1,27 @@ /* eslint-env mocha */ +const assert = require('assert') +const { ProtoDef } = require('../') +const { ProtoDefCompiler } = require('../').Compiler + it('example works', () => { require('../example') }) + +describe('mapper', () => { + const mapper = ['mapper', { type: 'varint', mappings: { '0x00': 'zero', '0x01': 'one' } }] + const proto = new ProtoDef() + proto.addType('name', mapper) + const compiler = new ProtoDefCompiler() + compiler.addTypesToCompile({ name: mapper }) + const compiled = compiler.compileProtoDefSync() + + for (const [label, p] of [['interpreted', proto], ['compiled', compiled]]) { + it(`writes a value mapped to 0 (${label})`, () => { + assert.deepStrictEqual(p.createPacketBuffer('name', 'zero'), Buffer.from([0])) + }) + it(`throws on a value not in the mappings instead of writing it (${label})`, () => { + assert.throws(() => p.createPacketBuffer('name', 'nope'), /nope is not in the mappings value/) + }) + } +})