From 1d7ec1a764757b4ea16a5925d08c2040fac9b1f4 Mon Sep 17 00:00:00 2001 From: Miguel Angel Mulero Martinez Date: Mon, 4 Nov 2019 09:53:54 +0100 Subject: [PATCH 1/2] Add djv library to validate against JSON Schemas --- package.json | 1 + src/main.html | 1 + yarn.lock | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/package.json b/package.json index 79e5fafa..624907c8 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "dependencies": { "bluebird": "^3.5.5", + "djv": "^2.1.3-alpha.0", "i18next": "^18.0.1", "i18next-xhr-backend": "^3.1.1", "inflection": "1.12.0", diff --git a/src/main.html b/src/main.html index e18cdd0f..439e41bf 100644 --- a/src/main.html +++ b/src/main.html @@ -50,6 +50,7 @@ + diff --git a/yarn.lock b/yarn.lock index bad8270e..2c8b3606 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,11 @@ dependencies: regenerator-runtime "^0.13.2" +"@korzio/djv-draft-04@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@korzio/djv-draft-04/-/djv-draft-04-2.0.1.tgz#2984289426cac5ed622b26a58c3af8584aefbdfe" + integrity sha512-MeTVcNsfCIYxK6T7jW1sroC7dBAb4IfLmQe6RoCqlxHN5NFkzNpgdnBPR+/0D2wJDUJHM9s9NQv+ouhxKjvUjg== + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -1335,6 +1340,13 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +djv@^2.1.3-alpha.0: + version "2.1.3-alpha.0" + resolved "https://registry.yarnpkg.com/djv/-/djv-2.1.3-alpha.0.tgz#554daa91f22e0e8e9f48c9550766a6c07e8ce180" + integrity sha512-vrKAFr/wbhMjfqo+eoXZRIx/6YbN1DSZlxUsE3tHDPYB0zWRtOBhqmKbcgCjfP3+KevqidBGn2jNRnqRhjIzYg== + optionalDependencies: + "@korzio/djv-draft-04" "^2.0.1" + dom-serialize@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" From 510056b278f69e4ee38da7cd9c8d6866faac2974 Mon Sep 17 00:00:00 2001 From: Miguel Angel Mulero Martinez Date: Mon, 4 Nov 2019 09:54:37 +0100 Subject: [PATCH 2/2] Add VTX Config JSON Schema validation --- src/js/tabs/vtx.js | 144 ++++++++++++----- .../jsonschema/vtxconfig_schema-1.0.json | 148 ++++++++++++++++++ 2 files changed, 250 insertions(+), 42 deletions(-) create mode 100644 src/resources/jsonschema/vtxconfig_schema-1.0.json diff --git a/src/js/tabs/vtx.js b/src/js/tabs/vtx.js index 08501824..d1d05eef 100644 --- a/src/js/tabs/vtx.js +++ b/src/js/tabs/vtx.js @@ -10,6 +10,7 @@ TABS.vtx = { VTXTABLE_POWERLEVEL_LIST: [], analyticsChanges: {}, updating: true, + env: new djv(), }; TABS.vtx.initialize = function (callback) { @@ -51,9 +52,9 @@ TABS.vtx.initialize = function (callback) { function vtx_config() { MSP.send_message(MSPCodes.MSP_VTX_CONFIG, false, false, vtxtable_bands); } - + function vtxtable_bands() { - + // Simulation of static variable if (typeof vtxtable_bands.counter === 'undefined') { TABS.vtx.VTXTABLE_BAND_LIST = []; @@ -62,21 +63,21 @@ TABS.vtx.initialize = function (callback) { TABS.vtx.VTXTABLE_BAND_LIST.push(Object.assign({}, VTXTABLE_BAND)); vtxtable_bands.counter++; } - + let buffer = []; buffer.push8(vtxtable_bands.counter); - + if (vtxtable_bands.counter <= VTX_CONFIG.vtx_table_bands) { MSP.send_message(MSPCodes.MSP_VTXTABLE_BAND, buffer, false, vtxtable_bands); } else { vtxtable_bands.counter = undefined; vtxtable_powerlevels(); } - + } - + function vtxtable_powerlevels() { - + // Simulation of static variable if (typeof vtxtable_powerlevels.counter === 'undefined') { TABS.vtx.VTXTABLE_POWERLEVEL_LIST = []; @@ -85,10 +86,10 @@ TABS.vtx.initialize = function (callback) { TABS.vtx.VTXTABLE_POWERLEVEL_LIST.push(Object.assign({}, VTXTABLE_POWERLEVEL)); vtxtable_powerlevels.counter++; } - + let buffer = []; buffer.push8(vtxtable_powerlevels.counter); - + if (vtxtable_powerlevels.counter <= VTX_CONFIG.vtx_table_powerlevels) { MSP.send_message(MSPCodes.MSP_VTXTABLE_POWERLEVEL, buffer, false, vtxtable_powerlevels); } else { @@ -98,6 +99,35 @@ TABS.vtx.initialize = function (callback) { } } + // Validates the vtxConfig object against a JSON Schema + function validateVtxJson(vtxConfig, callback_valid, callback_error) { + + // At minimum the version must be defined + if (!vtxConfig.version) { + console.error("Validation against schema failed, version missing"); + callback_error(); + } + + // Load schema + const urlVtxSchema = chrome.runtime.getURL(`resources/jsonschema/vtxconfig_schema-${vtxConfig.version}.json`); + + fetch(urlVtxSchema) + .then(response => response.json()) + .catch(error => console.error('Error fetching VTX Schema:', error)) + .then(schemaJson => { + + let valid = false; + if (schemaJson !== undefined) { + // Validate + valid = (TABS.vtx.env.validate(schemaJson, vtxConfig) === undefined); + } + + console.log("Validation against schema result:", valid); + valid ? callback_valid() : callback_error(); + }); + + } + // Emulates the MSP read from a vtxConfig object (JSON) function read_vtx_config_json(vtxConfig, vtxcallback_after_read) { @@ -156,8 +186,8 @@ TABS.vtx.initialize = function (callback) { // Supported? let vtxSupported = VTX_CONFIG.vtx_type != 0 && VTX_CONFIG.vtx_type != 255; - let vtxTableNotConfigured = vtxSupported && VTX_CONFIG.vtx_table_available && (VTX_CONFIG.vtx_table_bands == 0 || - VTX_CONFIG.vtx_table_channels == 0 || + let vtxTableNotConfigured = vtxSupported && VTX_CONFIG.vtx_table_available && (VTX_CONFIG.vtx_table_bands == 0 || + VTX_CONFIG.vtx_table_channels == 0 || VTX_CONFIG.vtx_table_powerlevels == 0); $(".vtx_supported").toggle(vtxSupported); @@ -208,7 +238,7 @@ TABS.vtx.initialize = function (callback) { } $("#vtx_band_description").text(bandName); } else { - $("#vtx_band_description").text(VTX_CONFIG.vtx_band); + $("#vtx_band_description").text(VTX_CONFIG.vtx_band); } } @@ -222,7 +252,7 @@ TABS.vtx.initialize = function (callback) { } $("#vtx_power_description").text(powerLevel); } else { - let levelText = i18n.getMessage('vtxPower_X', {powerLevel: VTX_CONFIG.vtx_power}); + let levelText = i18n.getMessage('vtxPower_X', {powerLevel: VTX_CONFIG.vtx_power}); $("#vtx_power_description").text(levelText); } } @@ -326,7 +356,7 @@ TABS.vtx.initialize = function (callback) { let powerlevelstitle_e = $(".vtx_table_powerlevels_table .vtx_table_powerlevels_title"); for (let i = 1; i <= TABS.vtx.MAX_POWERLEVEL_VALUES; i++) { - powerlevelstitle_e.append("" + i + ""); + powerlevelstitle_e.append("" + i + ""); } // Power levels @@ -359,7 +389,7 @@ TABS.vtx.initialize = function (callback) { let title_e = $("#tab-vtx-templates #tab-vtx-bands-title tr"); for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) { - title_e.append("" + i + ""); + title_e.append("" + i + ""); } bandstable_e.append(title_e); @@ -400,11 +430,11 @@ TABS.vtx.initialize = function (callback) { bandName = i18n.getMessage('vtxBand_X', {bandName: i}); } selectBand.append(new Option(bandName, i)); - } + } } else { for (let i = 1; i <= TABS.vtx.MAX_BAND_VALUES; i++) { selectBand.append(new Option(i18n.getMessage('vtxBand_X', {bandName: i}), i)); - } + } } } @@ -424,11 +454,11 @@ TABS.vtx.initialize = function (callback) { selectChannel.append(new Option(i18n.getMessage('vtxChannel_X', {channelName: i}), i)); } } - } + } } else { for (let i = 1; i <= TABS.vtx.MAX_BAND_CHANNELS_VALUES; i++) { selectBand.append(new Option(i18n.getMessage('vtxChannel_X', {channelName: i}), i)); - } + } } } @@ -443,7 +473,7 @@ TABS.vtx.initialize = function (callback) { powerLevel = i18n.getMessage('vtxPower_X', {powerLevel: i}); } selectPower.append(new Option(powerLevel, i)); - } + } } else { let powerMaxMinValues = getPowerValues(VTX_CONFIG.vtx_type); for (let i = powerMaxMinValues.min; i <= powerMaxMinValues.max; i++) { @@ -452,19 +482,19 @@ TABS.vtx.initialize = function (callback) { } else { selectPower.append(new Option(i18n.getMessage('vtxPower_X', {bandName: i}), i)); } - } + } } } // Returns the power values min and max depending on the VTX Type function getPowerValues(vtxType) { - + let powerMinMax = {}; if (VTX_CONFIG.vtx_table_available) { powerMinMax = {min: 1, max: VTX_CONFIG.vtx_table_powerlevels} } else { - + switch (vtxType) { case 0: // Unsupported @@ -472,7 +502,7 @@ TABS.vtx.initialize = function (callback) { break; case 1: // RTC6705 - powerMinMax = {min: 1, max: 3}; + powerMinMax = {min: 1, max: 3}; break; case 3: // SmartAudio @@ -556,7 +586,7 @@ TABS.vtx.initialize = function (callback) { let vtxConfig = createVtxConfigInfo(); let text = creatLuaTables(vtxConfig); let data = new Blob([text], { type: "application/text" }); - + // we get here at the end of the truncate method, change to the new end writer.onwriteend = function() { analytics.sendEvent(analytics.EVENT_CATEGORIES.FLIGHT_CONTROLLER, 'VtxTableLuaSave', text.length); @@ -579,7 +609,7 @@ TABS.vtx.initialize = function (callback) { let suggestedName = 'vtxtable'; let suffix = 'json'; - var filename = generateFilename(suggestedName, suffix); + var filename = generateFilename(suggestedName, suffix); let accepts = [{ description: suffix.toUpperCase() + ' files', extensions: [suffix], @@ -658,16 +688,32 @@ TABS.vtx.initialize = function (callback) { let text = e.target.result; try { + let vtxConfig = JSON.parse(text); - read_vtx_config_json(vtxConfig, load_html); - TABS.vtx.vtxTableSavePending = true; + validateVtxJson( + vtxConfig, + function() { - self.analyticsChanges['VtxTableLoadFromClipboard'] = undefined; - self.analyticsChanges['VtxTableLoadFromFile'] = file.name; + // JSON is valid + read_vtx_config_json(vtxConfig, load_html); - console.log('Load VTX file end'); - GUI.log(i18n.getMessage('vtxLoadFileOk')); + TABS.vtx.vtxTableSavePending = true; + + self.analyticsChanges['VtxTableLoadFromClipboard'] = undefined; + self.analyticsChanges['VtxTableLoadFromFile'] = file.name; + + console.log('Load VTX file end'); + GUI.log(i18n.getMessage('vtxLoadFileOk')); + }, + function() { + + // JSON is NOT valid + console.error('VTX Config from file failed validation against schema'); + GUI.log(i18n.getMessage('vtxLoadFileKo')); + + } + ); } catch (err) { console.error('Failed loading VTX file config'); @@ -694,15 +740,29 @@ TABS.vtx.initialize = function (callback) { console.log('Pasted content: ', text); let vtxConfig = JSON.parse(text); - read_vtx_config_json(vtxConfig, load_html); - TABS.vtx.vtxTableSavePending = true; + validateVtxJson( + vtxConfig, + function() { - self.analyticsChanges['VtxTableLoadFromFile'] = undefined; - self.analyticsChanges['VtxTableLoadFromClipboard'] = text.length; + // JSON is valid + read_vtx_config_json(vtxConfig, load_html); - console.log('Load VTX clipboard end'); - GUI.log(i18n.getMessage('vtxLoadClipboardOk')); + TABS.vtx.vtxTableSavePending = true; + + self.analyticsChanges['VtxTableLoadFromFile'] = undefined; + self.analyticsChanges['VtxTableLoadFromClipboard'] = text.length; + + console.log('Load VTX clipboard end'); + GUI.log(i18n.getMessage('vtxLoadClipboardOk')); + }, + function() { + + // JSON is NOT valid + GUI.log(i18n.getMessage('vtxLoadClipboardKo')); + console.error('VTX Config from clipboard failed validation against schema'); + } + ); }, function(err) { GUI.log(i18n.getMessage('vtxLoadClipboardKo')); @@ -731,7 +791,7 @@ TABS.vtx.initialize = function (callback) { function save_vtx_config() { MSP.send_message(MSPCodes.MSP_SET_VTX_CONFIG, mspHelper.crunch(MSPCodes.MSP_SET_VTX_CONFIG), false, save_vtx_powerlevels); } - + function save_vtx_powerlevels() { // Simulation of static variable @@ -741,7 +801,7 @@ TABS.vtx.initialize = function (callback) { save_vtx_powerlevels.counter++; } - + if (save_vtx_powerlevels.counter < VTX_CONFIG.vtx_table_powerlevels) { VTXTABLE_POWERLEVEL = Object.assign({}, TABS.vtx.VTXTABLE_POWERLEVEL_LIST[save_vtx_powerlevels.counter]); MSP.send_message(MSPCodes.MSP_SET_VTXTABLE_POWERLEVEL, mspHelper.crunch(MSPCodes.MSP_SET_VTXTABLE_POWERLEVEL), false, save_vtx_powerlevels); @@ -760,7 +820,7 @@ TABS.vtx.initialize = function (callback) { save_vtx_bands.counter++; } - + if (save_vtx_bands.counter < VTX_CONFIG.vtx_table_bands) { VTXTABLE_BAND = Object.assign({}, TABS.vtx.VTXTABLE_BAND_LIST[save_vtx_bands.counter]); MSP.send_message(MSPCodes.MSP_SET_VTXTABLE_BAND, mspHelper.crunch(MSPCodes.MSP_SET_VTXTABLE_BAND), false, save_vtx_bands); @@ -788,7 +848,7 @@ TABS.vtx.initialize = function (callback) { TABS.vtx.initialize(); } } - + function dump_html_to_msp() { // General config diff --git a/src/resources/jsonschema/vtxconfig_schema-1.0.json b/src/resources/jsonschema/vtxconfig_schema-1.0.json new file mode 100644 index 00000000..35c03e61 --- /dev/null +++ b/src/resources/jsonschema/vtxconfig_schema-1.0.json @@ -0,0 +1,148 @@ +{ + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://example.com/root.json", + "type": "object", + "title": "The Root Schema", + "required": [ + "description", + "version", + "vtx_table" + ], + "properties": { + "description": { + "$id": "#/properties/description", + "type": "string", + "title": "The Description Schema", + "default": "", + "examples": [ + "Betaflight VTX Config file for SmartAudio 2.0 (US version)" + ], + "pattern": "^(.*)$" + }, + "version": { + "$id": "#/properties/version", + "type": "string", + "title": "The Version Schema", + "default": "", + "examples": [ + "1.0" + ], + "pattern": "^(.*)$" + }, + "vtx_table": { + "$id": "#/properties/vtx_table", + "type": "object", + "title": "The Vtx_table Schema", + "required": [ + "bands_list", + "powerlevels_list" + ], + "properties": { + "bands_list": { + "$id": "#/properties/vtx_table/properties/bands_list", + "type": "array", + "title": "The Bands_list Schema", + "items": { + "$id": "#/properties/vtx_table/properties/bands_list/items", + "type": "object", + "title": "The Items Schema", + "required": [ + "name", + "letter", + "is_factory_band", + "frequencies" + ], + "properties": { + "name": { + "$id": "#/properties/vtx_table/properties/bands_list/items/properties/name", + "type": "string", + "title": "The Name Schema", + "default": "", + "examples": [ + "BOSCAM_A" + ], + "pattern": "^(.*)$" + }, + "letter": { + "$id": "#/properties/vtx_table/properties/bands_list/items/properties/letter", + "type": "string", + "title": "The Letter Schema", + "default": "", + "examples": [ + "A" + ], + "pattern": "^(.*)$" + }, + "is_factory_band": { + "$id": "#/properties/vtx_table/properties/bands_list/items/properties/is_factory_band", + "type": "boolean", + "title": "The Is_factory_band Schema", + "default": false, + "examples": [ + true + ] + }, + "frequencies": { + "$id": "#/properties/vtx_table/properties/bands_list/items/properties/frequencies", + "type": "array", + "title": "The Frequencies Schema", + "items": { + "$id": "#/properties/vtx_table/properties/bands_list/items/properties/frequencies/items", + "type": "integer", + "title": "The Items Schema", + "default": 0, + "examples": [ + 5865, + 5845, + 5825, + 5805, + 5785, + 5765, + 5745, + 5725 + ] + } + } + } + } + }, + "powerlevels_list": { + "$id": "#/properties/vtx_table/properties/powerlevels_list", + "type": "array", + "title": "The Powerlevels_list Schema", + "items": { + "$id": "#/properties/vtx_table/properties/powerlevels_list/items", + "type": "object", + "title": "The Items Schema", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "$id": "#/properties/vtx_table/properties/powerlevels_list/items/properties/value", + "type": "integer", + "title": "The Value Schema", + "default": 0, + "examples": [ + 0 + ] + }, + "label": { + "$id": "#/properties/vtx_table/properties/powerlevels_list/items/properties/label", + "type": "string", + "title": "The Label Schema", + "default": "", + "examples": [ + "25 " + ], + "pattern": "^(.*)$" + } + } + } + } + } + } + } +} \ No newline at end of file