UNPKG

nimcodec

Version:

Encoder/decoder for satellite IoT using Non-IP Messages

125 lines (119 loc) 3.9 kB
/** * An integer bitmask maps to an array of strings to identify each bit, * followed by an array of length (rows) equal to the bits set. * The fields defined in the array represent columns for each row. * * Example: `["bit0": {"kpi1": 1, "kpi2": 2}, "bit3": {"kpi4": 1, "kpi5": 2}]` * @namespace field.bitkeylist */ const { int2bits, extractBits } = require('../../../bitman'); const { decodeField, encodeField, } = require('./common'); const Types = require('../types'); /** @type {Types.Field} */ /** * Validate the field configuration for bitkeylist * @memberof field.bitkeylist * @private * @param {Field} field */ function validateField(field) { if (!Array.isArray(field.items)) { throw new Error('Field items must be an Array'); } if (field.items.length > field.size) { throw new Error('Field items must be equal or smaller length than size'); } const checkSet = new Set(field.items); if (checkSet.size != field.items.length || !field.items.every(i => typeof i === 'string')) { throw new Error('Field items must contain unique bit-indexed strings'); } } /** * Decode a bitmask-defined array field * @memberof field.bitkeylist * @private * @param {Field} field The field definition * @param {number} field.size The bitmask size in bits * @param {String[]} [field.items] The bit/tag definitions * @param {Field[]} field.fields The fields of the array entries * @param {Buffer} buffer The raw payload buffer * @param {number} offset The start bit of the field in the buffer * @returns The decoded field value and the offset for the next read operation */ function decode(field, buffer, offset) { validateField(field); const bitmask = extractBits(buffer, offset, field.size); const bits = int2bits(bitmask, field.size).reverse(); offset += field.size; const value = []; field.items.forEach((tag, i) => { if (bits[i] === 1) { const row = {}; row[tag] = {}; for (const f of field.fields) { let decoded; ({decoded, offset} = decodeField(f, buffer, offset)); row[tag][decoded.name] = decoded.value; } value.push(row); } }); return { value: value, offset: offset }; } /** * Returns a subfield for the bitmask as an integer * @memberof field.bitkeylist * @private * @param {Field} field Field definition * @returns name, type and size in an object */ function subField(field) { return { name: 'subField', type: 'uint', size: field.size, }; } /** * Encode a Bit Keyed List field * @memberof field.bitkeylist * @private * @param {Field} field The field definition * @param {Array} value The value to encode * @param {Buffer} buffer The buffer to append to * @param {number} offset The bit offset within the buffer to append at * @returns The encoded buffer and the offset for the next field within it */ function encode(field, value, buffer, offset) { validateField(field); if (!Array.isArray(value)) throw new Error('Invalid bitkeylist'); value.forEach((row) => { for (const key of Object.keys(row)) { if (!field.items.includes(key)) throw new Error('Invalid bitkey value'); } }); if (value.length > field.size) throw new Error('Array to large for field size'); // calculate bitmask and encode as uint of size field.size let bitmask = 0; value.forEach(row => { const bitName = Object.keys(row)[0]; const iBit = field.items.indexOf(bitName); bitmask += 2**iBit; }); ({buffer, offset} = encodeField(subField(field), bitmask, buffer, offset)); value.forEach((row) => { for (const [k, v] of Object.entries(Object.values(row)[0])) { field.fields.forEach((f) => { if (f.name === k) { ({buffer, offset} = encodeField(f, v, buffer, offset)); } }); } }); return { buffer: buffer, offset: offset }; } module.exports = { decode, encode };