UNPKG

protodef

Version:

A simple yet powerful way to define binary protocols

248 lines (241 loc) 9.57 kB
module.exports = { Read: { pstring: ['parametrizable', (compiler, string) => { let code = '' if (string.countType) { code += 'const { value: count, size: countSize } = ' + compiler.callType(string.countType) + '\n' } else if (string.count) { code += 'const count = ' + string.count + '\n' code += 'const countSize = 0\n' } else { throw new Error('pstring must contain either count or countType') } code += 'offset += countSize\n' code += 'if (offset + count > buffer.length) {\n' code += ' throw new PartialReadError("Missing characters in string, found size is " + buffer.length + " expected size was " + (offset + count))\n' code += '}\n' code += `return { value: buffer.toString("${string.encoding || 'utf8'}", offset, offset + count), size: count + countSize }` return compiler.wrapCode(code) }], buffer: ['parametrizable', (compiler, buffer) => { let code = '' if (buffer.countType) { code += 'const { value: count, size: countSize } = ' + compiler.callType(buffer.countType) + '\n' } else if (buffer.count) { code += 'const count = ' + buffer.count + '\n' code += 'const countSize = 0\n' } else { throw new Error('buffer must contain either count or countType') } code += 'offset += countSize\n' code += 'if (offset + count > buffer.length) {\n' code += ' throw new PartialReadError()\n' code += '}\n' code += 'return { value: buffer.slice(offset, offset + count), size: count + countSize }' return compiler.wrapCode(code) }], bitfield: ['parametrizable', (compiler, values) => { let code = '' const totalBytes = Math.ceil(values.reduce((acc, { size }) => acc + size, 0) / 8) code += `if ( offset + ${totalBytes} > buffer.length) { throw new PartialReadError() }\n` const names = [] let totalSize = 8 code += 'let bits = buffer[offset++]\n' for (const i in values) { const { name, size, signed } = values[i] const trueName = compiler.getField(name) while (totalSize < size) { totalSize += 8 code += 'bits = (bits << 8) | buffer[offset++]\n' } code += `let ${trueName} = (bits >> ` + (totalSize - size) + ') & 0x' + ((1 << size) - 1).toString(16) + '\n' if (signed) code += `${trueName} -= (${trueName} & 0x` + (1 << (size - 1)).toString(16) + ') << 1\n' totalSize -= size if (name === trueName) names.push(name) else names.push(`${name}: ${trueName}`) } code += 'return { value: { ' + names.join(', ') + ` }, size: ${totalBytes} }` return compiler.wrapCode(code) }], bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { let fstr = JSON.stringify(flags) if (Array.isArray(flags)) { fstr = '{' for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') fstr += '}' } else if (shift) { fstr = '{' for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` const { value: _value, size } = ${compiler.callType(type, 'offset')} const value = { _value } const flags = ${fstr} for (const key in flags) { value[key] = (_value & flags[key]) == flags[key] } return { value, size } `.trim()) }], mapper: ['parametrizable', (compiler, mapper) => { let code = 'const { value, size } = ' + compiler.callType(mapper.type) + '\n' code += 'return { value: ' + JSON.stringify(sanitizeMappings(mapper.mappings)) + '[value] || value, size }' return compiler.wrapCode(code) }] }, Write: { pstring: ['parametrizable', (compiler, string) => { let code = `const length = Buffer.byteLength(value, "${string.encoding || 'utf8'}")\n` if (string.countType) { code += 'offset = ' + compiler.callType('length', string.countType) + '\n' } else if (string.count === null) { throw new Error('pstring must contain either count or countType') } code += `buffer.write(value, offset, length, "${string.encoding || 'utf8'}")\n` code += 'return offset + length' return compiler.wrapCode(code) }], buffer: ['parametrizable', (compiler, buffer) => { let code = 'if (!(value instanceof Buffer)) value = Buffer.from(value)\n' if (buffer.countType) { code += 'offset = ' + compiler.callType('value.length', buffer.countType) + '\n' } else if (buffer.count === null) { throw new Error('buffer must contain either count or countType') } code += 'value.copy(buffer, offset)\n' code += 'return offset + value.length' return compiler.wrapCode(code) }], bitfield: ['parametrizable', (compiler, values) => { let toWrite = '' let bits = 0 let code = '' for (const i in values) { let { name, size } = values[i] const trueName = compiler.getField(name) code += `let ${trueName} = value.${name}\n` while (size > 0) { const writeBits = Math.min(8 - bits, size) const mask = ((1 << writeBits) - 1) if (toWrite !== '') toWrite = `((${toWrite}) << ${writeBits}) | ` toWrite += `((${trueName} >> ` + (size - writeBits) + ') & 0x' + mask.toString(16) + ')' size -= writeBits bits += writeBits if (bits === 8) { code += 'buffer[offset++] = ' + toWrite + '\n' bits = 0 toWrite = '' } } } if (bits !== 0) { code += 'buffer[offset++] = (' + toWrite + ') << ' + (8 - bits) + '\n' } code += 'return offset' return compiler.wrapCode(code) }], bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { let fstr = JSON.stringify(flags) if (Array.isArray(flags)) { fstr = '{' for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') fstr += '}' } else if (shift) { fstr = '{' for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` const flags = ${fstr} let val = value._value ${big ? '|| 0n' : ''} for (const key in flags) { if (value[key]) val |= flags[key] } return (ctx.${type})(val, buffer, offset) `.trim()) }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) return compiler.wrapCode(code) }] }, SizeOf: { pstring: ['parametrizable', (compiler, string) => { let code = `let size = Buffer.byteLength(value, "${string.encoding || 'utf8'}")\n` if (string.countType) { code += 'size += ' + compiler.callType('size', string.countType) + '\n' } else if (string.count === null) { throw new Error('pstring must contain either count or countType') } code += 'return size' return compiler.wrapCode(code) }], buffer: ['parametrizable', (compiler, buffer) => { let code = 'let size = value instanceof Buffer ? value.length : Buffer.from(value).length\n' if (buffer.countType) { code += 'size += ' + compiler.callType('size', buffer.countType) + '\n' } else if (buffer.count === null) { throw new Error('buffer must contain either count or countType') } code += 'return size' return compiler.wrapCode(code) }], bitfield: ['parametrizable', (compiler, values) => { const totalBytes = Math.ceil(values.reduce((acc, { size }) => acc + size, 0) / 8) return `${totalBytes}` }], bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { let fstr = JSON.stringify(flags) if (Array.isArray(flags)) { fstr = '{' for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') fstr += '}' } else if (shift) { fstr = '{' for (const key in flags) fstr += `"${key}": ${1 << flags[key]}${big ? 'n,' : ','}` fstr += '}' } return compiler.wrapCode(` const flags = ${fstr} let val = value._value ${big ? '|| 0n' : ''} for (const key in flags) { if (value[key]) val |= flags[key] } return (ctx.${type})(val) `.trim()) }], mapper: ['parametrizable', (compiler, mapper) => { const mappings = JSON.stringify(swapMappings(mapper.mappings)) const code = 'return ' + compiler.callType(`${mappings}[value] || value`, mapper.type) return compiler.wrapCode(code) }] } } // Convert hexadecimal keys to decimal function sanitizeMappings (json) { const ret = {} for (let key in json) { let val = json[key] key = hex2dec(key) if (!isNaN(val)) val = Number(val) if (val === 'true') val = true if (val === 'false') val = false ret[key] = val } return ret } function swapMappings (json) { const ret = {} for (let key in json) { const val = json[key] key = hex2dec(key) ret[val] = (isNaN(key)) ? key : parseInt(key, 10) } return ret } function hex2dec (num) { if ((num.match(/^0x[0-9a-f]+$/i))) { return parseInt(num.substring(2), 16) } return num }