From 165735159874441949d6c417dc0226fb1e3f0f9d Mon Sep 17 00:00:00 2001 From: Richard Benkovsky Date: Wed, 19 Apr 2023 12:44:05 -0700 Subject: [PATCH] Add a few unit tests for MspHelper class (#3425) * Add a few unit tests for MSPHelper * Simplify encode_message_v1 * Simplify MSP_BOARD_INFO handling --- src/js/msp.js | 45 ++++------ src/js/msp/MSPHelper.js | 26 +----- test/js/msp/MSPHelper.test.js | 165 ++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 test/js/msp/MSPHelper.test.js diff --git a/src/js/msp.js b/src/js/msp.js index 28d4e3e0f7..5b6cc9d8a3 100644 --- a/src/js/msp.js +++ b/src/js/msp.js @@ -262,41 +262,26 @@ const MSP = { return crc; }, encode_message_v1: function(code, data) { - let bufferOut; + const dataLength = data ? data.length : 0; // always reserve 6 bytes for protocol overhead ! - if (data) { - const size = data.length + 6; - let checksum = 0; - - bufferOut = new ArrayBuffer(size); - let bufView = new Uint8Array(bufferOut); - - bufView[0] = 36; // $ - bufView[1] = 77; // M - bufView[2] = 60; // < - bufView[3] = data.length; - bufView[4] = code; - - checksum = bufView[3] ^ bufView[4]; - - for (let i = 0; i < data.length; i++) { - bufView[i + 5] = data[i]; + const bufferSize = dataLength + 6; + let bufferOut = new ArrayBuffer(bufferSize); + let bufView = new Uint8Array(bufferOut); - checksum ^= bufView[i + 5]; - } + bufView[0] = 36; // $ + bufView[1] = 77; // M + bufView[2] = 60; // < + bufView[3] = dataLength; + bufView[4] = code; - bufView[5 + data.length] = checksum; - } else { - bufferOut = new ArrayBuffer(6); - let bufView = new Uint8Array(bufferOut); + let checksum = bufView[3] ^ bufView[4]; - bufView[0] = 36; // $ - bufView[1] = 77; // M - bufView[2] = 60; // < - bufView[3] = 0; // data length - bufView[4] = code; // code - bufView[5] = bufView[3] ^ bufView[4]; // checksum + for (let i = 0; i < dataLength; i++) { + bufView[i + 5] = data[i]; + checksum ^= bufView[i + 5]; } + + bufView[5 + dataLength] = checksum; return bufferOut; }, encode_message_v2: function (code, data) { diff --git a/src/js/msp/MSPHelper.js b/src/js/msp/MSPHelper.js index 77109063cf..4c1ce77700 100644 --- a/src/js/msp/MSPHelper.js +++ b/src/js/msp/MSPHelper.js @@ -787,35 +787,15 @@ MspHelper.prototype.process_data = function(dataHandler) { } FC.CONFIG.boardVersion = data.readU16(); - FC.CONFIG.boardType = 0; - FC.CONFIG.boardType = data.readU8(); - FC.CONFIG.targetCapabilities = 0; - FC.CONFIG.targetName = ''; - FC.CONFIG.targetCapabilities = data.readU8(); - let length = data.readU8(); - for (let i = 0; i < length; i++) { - FC.CONFIG.targetName += String.fromCharCode(data.readU8()); - } + FC.CONFIG.targetName = this.getText(data); - FC.CONFIG.boardName = ''; - FC.CONFIG.manufacturerId = ''; + FC.CONFIG.boardName = this.getText(data); + FC.CONFIG.manufacturerId = this.getText(data); FC.CONFIG.signature = []; - length = data.readU8(); - - for (let i = 0; i < length; i++) { - FC.CONFIG.boardName += String.fromCharCode(data.readU8()); - } - - length = data.readU8(); - - for (let i = 0; i < length; i++) { - FC.CONFIG.manufacturerId += String.fromCharCode(data.readU8()); - } - for (let i = 0; i < self.SIGNATURE_LENGTH; i++) { FC.CONFIG.signature.push(data.readU8()); } diff --git a/test/js/msp/MSPHelper.test.js b/test/js/msp/MSPHelper.test.js new file mode 100644 index 0000000000..cd21acf634 --- /dev/null +++ b/test/js/msp/MSPHelper.test.js @@ -0,0 +1,165 @@ +import {beforeEach, describe, expect, it} from "vitest"; +import MspHelper from "../../../src/js/msp/MSPHelper"; +import MSPCodes from "../../../src/js/msp/MSPCodes"; +import '../../../src/js/injected_methods'; +import FC from "../../../src/js/fc"; +import {API_VERSION_1_46} from "../../../src/js/data_storage"; + +describe("MspHelper", () => { + const mspHelper = new MspHelper(); + beforeEach(() => { + FC.resetState(); + }); + describe("process_data", () => { + it("refuses to process data with crc-error", () => { + let callbackCalled = false; + + let callbackFunction = (item) => { + callbackCalled = true; + expect(item['crcError']).toEqual(true); + expect(item['command']).toEqual(MSPCodes.MSP_BOARD_INFO); + expect(item['length']).toEqual(0); + }; + + mspHelper.process_data({ + code: MSPCodes.MSP_BOARD_INFO, + dataView: new DataView(new Uint8Array([]).buffer), + crcError: true, + callbacks: [{ + callback: callbackFunction, + callbackOnError: true, + code: MSPCodes.MSP_BOARD_INFO, + }], + }); + + expect(callbackCalled).toEqual(true); + }); + it("handles MSP_API_VERSION correctly", () => { + let randomValues = crypto.getRandomValues(new Uint8Array(3)); + const [mspProtocolVersion, apiVersionMajor, apiVersionMinor] = randomValues; + mspHelper.process_data({ + code: MSPCodes.MSP_API_VERSION, + dataView: new DataView(randomValues.buffer), + crcError: false, + callbacks: [], + }); + + expect(FC.CONFIG.mspProtocolVersion).toEqual(mspProtocolVersion); + expect(FC.CONFIG.apiVersion).toEqual(`${apiVersionMajor}.${apiVersionMinor}.0`); + }); + it("handles MSP_PIDNAMES correctly", () => { + let pidNamesCount = 1 + crypto.getRandomValues(new Uint8Array(1))[0]; + let expectedNames = Array.from({length: pidNamesCount}).map(_ => generateRandomString()); + + let lowLevelData = []; + appendStringToArray(lowLevelData, `${expectedNames.join(';')};`); + + mspHelper.process_data({ + code: MSPCodes.MSP_PIDNAMES, + dataView: new DataView(new Uint8Array(lowLevelData).buffer), + crcError: false, + callbacks: [], + }); + + expect(FC.PID_NAMES).toEqual(expectedNames); + }); + it("handles MSP_MOTOR correctly", () => { + let motorCount = crypto.getRandomValues(new Uint8Array(1))[0] % 8; + let motorBytes = crypto.getRandomValues(new Uint16Array(motorCount)); + + mspHelper.process_data({ + code: MSPCodes.MSP_MOTOR, + dataView: new DataView(new Uint16Array(motorBytes).buffer), + crcError: false, + callbacks: [], + }); + expect(new Uint16Array(FC.MOTOR_DATA).slice(0, motorCount)).toEqual(motorBytes); + expect(FC.MOTOR_DATA.slice(motorCount, 8)).toContain(undefined); + }); + it("handles MSP_BOARD_INFO correctly for API version", () => { + FC.CONFIG.apiVersion = API_VERSION_1_46; + let infoBuffer = []; + + const boardIdentifier = appendStringToArray(infoBuffer, generateRandomString(4)); // set board-identifier + + infoBuffer.push16(0xDEAD); // set board version + infoBuffer.push8(0x12); // set board type + infoBuffer.push8(0x32); // set target capabilities + + const targetName = appendStringToArray(infoBuffer, generateRandomString(), true); // set target name + const boardName = appendStringToArray(infoBuffer, generateRandomString(), true); // set board name + const manufacturerId = appendStringToArray(infoBuffer, generateRandomString(), true); // set board name + const signature = crypto.getRandomValues(new Uint8Array(32)); + + signature.forEach(element => infoBuffer.push8(element)); + infoBuffer.push8(0xFA); // mcu type id + infoBuffer.push8(0xBB); // configuration state + infoBuffer.push16(0xBAAB); // sample rate + infoBuffer.push32(0xDEADBEEF); // configuration problems + + mspHelper.process_data({ + code: MSPCodes.MSP_BOARD_INFO, + dataView: new DataView(new Uint8Array(infoBuffer).buffer), + crcError: false, + callbacks: [], + }); + + expect(FC.CONFIG.boardIdentifier).toEqual(boardIdentifier); + expect(FC.CONFIG.boardVersion).toEqual(0xDEAD); + expect(FC.CONFIG.boardType).toEqual(0x12); + expect(FC.CONFIG.targetCapabilities).toEqual(0x32); + expect(FC.CONFIG.targetName).toEqual(targetName); + expect(FC.CONFIG.boardName).toEqual(boardName); + expect(FC.CONFIG.manufacturerId).toEqual(manufacturerId); + expect(new Uint8Array(FC.CONFIG.signature)).toEqual(signature); + expect(FC.CONFIG.mcuTypeId).toEqual(0xFA); + + expect(FC.CONFIG.configurationState).toEqual(0xBB); + expect(FC.CONFIG.sampleRateHz).toEqual(0xBAAB); + expect(FC.CONFIG.configurationProblems).toEqual(0xDEADBEEF); + }); + }); +}); + +/** + * Appends given string to an array. If required, it will append length of the string by length. + * @param destination array to which we append given string (and length if required) + * @param source string to append to an array + * @param prefixWithLength should we prefix the string by its length in the array + * @returns {*} string that was requested to be inserted to the array + */ +function appendStringToArray(destination, source, prefixWithLength = false) { + const size = source.length; + + if (prefixWithLength) { + destination.push8(source.length); + } + + for (let i = 0; i < size; i++) { + destination.push8(source.charCodeAt(i)); + } + + return source; +} + +/** + * Generates a random string of required length. If required length is -1, it will generate a random string of a random length. + * @param length required random string length. If lower than 0, it will generate a string of random length. + * @returns {string} random string (composed of letters [A-Za-z0-9]) + */ +function generateRandomString(length = -1) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + + if (length < 0) { + length = crypto.getRandomValues(new Uint8Array(1))[0]; + } + + const signature = crypto.getRandomValues(new Uint8Array(length)); + for (let i = 0; i < length; i++) { + result += characters.charAt(signature[i] % charactersLength); + } + + return result; +}