diff --git a/docs/dlt-logs/docs/filterReference.mdx b/docs/dlt-logs/docs/filterReference.mdx index 7d68d8e8..ccc22304 100644 --- a/docs/dlt-logs/docs/filterReference.mdx +++ b/docs/dlt-logs/docs/filterReference.mdx @@ -42,8 +42,11 @@ attribute name | expected type | default value | description `type`| number | none | Mandatory type. Use `0` for positive, `1` for negative, `2` for marker and `3` for event based filter. `mstp`| number | undefined | Message type. `0` for `TYPE_LOG`, `1`for `TYPE_APP_TRACE`, `2` for `TYPE_NW_TRACE`, `3` for `TYPE_CONTROL`. `ecu`| string | undefined| ECU identifier / ECU. Up to 4 characters. +`ecuIsRegex` | boolean | no value = 'autodetect' | Optional. See `apidIsRegex`. `apid`| string | undefined| Application identifier / APID . Up to 4 characters. +`apidIsRegex` | boolean | no value = 'autodetect' | Optional. If provided the apid is treated according to the value as true:regex, false:no regex. If not provided the apid is treated as regex if it contains any special regex characters: `^$*+?()[]{}|.-\=!<,` `ctid`| string | undefined| Context identifier / CTID. Up to 4 characters. +`ctidIsRegex` | boolean | no value = 'autodetect' | Optional. See `apidIsRegex`. `logLevelMin`| number | undefined| Minimum log-level. I.e. log-level of the message has to be >= to match. If specified `mstp`is automatically set to 0. See [Log levels](#log-levels) for values and examples. `logLevelMax`| number | undefined| Same as `logLevelMin`but for the maximum log-level, i.e. log-level of the message has to be <= `logLevelMax` to match. See [Log levels](#log-levels) for values and examples. `verbose`| boolean | undefined| `verbose` or `non-verbose` messages. Verbose flag is part of the extended header of a DLT msg and defaults to false if the ext. header doesn't exist. @@ -64,6 +67,8 @@ To speed up the match-comparision it's usually a good practice to specify as man `ecu` should only be used in cases when you deal with DLT-files that have multiple ECUs logs in the same file. See [configs](configsReference) for an alternative way to quickly disable filters not relevant for the current ECU. +For `apid` and `ctid` the regular expressions are roughly 6 times slower (e.g. 35ns vs 6ns) and should only be used if necessary. + Payload text checks should use `payloadRegex` instead of `payload` except for the cases where you really do want to search simply for substring somewhere in the payload. The amount of necessary steps during regex comparision are nicely shown on pages like [regex101](https://regex101.com). E.g. to match for messages with @@ -73,6 +78,16 @@ E.g. to match for messages with `^Operation .*? failed` (53 steps) is faster than `Operation .* failed` (70 steps for 3 test strings). ::: +:::tip ecu, apid, ctid regular expressions +In general the filter for ecu, apid, ctid expect 4 characters. If less characters are used the regex search is different that the default/non-regex ones: +e.g. +version | example | description +------- | ------- | ----------- +default | `"apid":"ECU"` | default/non-regex matches against `apid ECU\0`. So starting with ECU and then (null byte). +`"apidIsRegex":true`| `"apid":"ECU"` | regex matches against 'ECU' within apid. So e.g. "ECU1" or "AECU" or "ECU" all match. +regex | `"apid":"^ECU"` | autodetected regex matches against starting with 'ECU'. So e.g. "ECU" or "ECU1" match but not "AECU". +::: + :::warning Especially the `payloadRegex` consists frequently of characters that need to be escaped in JSON. If using the DLT filter assistant to create filters this is done automatically. If you modify the JSON settings manually please keep this in mind and escape the strings properly. @@ -173,4 +188,4 @@ Example: "overviewRulerLane":1, // left "overviewRulerColor":"yellow" } -``` \ No newline at end of file +``` diff --git a/package-lock.json b/package-lock.json index 65a6d774..9363d0bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "vscode": "^1.67.0" }, "optionalDependencies": { - "node-adlt": "0.42.1" + "node-adlt": "0.43.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -10384,9 +10384,9 @@ "optional": true }, "node_modules/node-adlt": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/node-adlt/-/node-adlt-0.42.1.tgz", - "integrity": "sha512-OfBNtG6omlCwg8nXTu1B3hXiNajLeQ6j9ejCyIYBNJbXMNmvo8bmS5Wku4VZkkA6qWUpNJSSh0NsvHTBlLxYKg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/node-adlt/-/node-adlt-0.43.0.tgz", + "integrity": "sha512-cbf56K18Hhxgl2LDtonPrRWaE226e9oIKtoAMyEmTVGKH38fyQBC5F/F3e89A6kHZ3rCkm72BIQolpeoKbF/uA==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -26042,9 +26042,9 @@ "optional": true }, "node-adlt": { - "version": "0.42.1", - "resolved": "https://registry.npmjs.org/node-adlt/-/node-adlt-0.42.1.tgz", - "integrity": "sha512-OfBNtG6omlCwg8nXTu1B3hXiNajLeQ6j9ejCyIYBNJbXMNmvo8bmS5Wku4VZkkA6qWUpNJSSh0NsvHTBlLxYKg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/node-adlt/-/node-adlt-0.43.0.tgz", + "integrity": "sha512-cbf56K18Hhxgl2LDtonPrRWaE226e9oIKtoAMyEmTVGKH38fyQBC5F/F3e89A6kHZ3rCkm72BIQolpeoKbF/uA==", "optional": true, "requires": { "https-proxy-agent": "^5.0.0", diff --git a/package.json b/package.json index b2e14550..2cfe4be4 100644 --- a/package.json +++ b/package.json @@ -943,7 +943,7 @@ "ws": "^8.13.0" }, "optionalDependencies": { - "node-adlt": "0.42.1" + "node-adlt": "0.43.0" }, "commitlint": { "extends": [ diff --git a/src/dltAddEditFilter.ts b/src/dltAddEditFilter.ts index 9b036656..1be0ea5d 100644 --- a/src/dltAddEditFilter.ts +++ b/src/dltAddEditFilter.ts @@ -189,9 +189,9 @@ export function editFilter(doc: FilterableDocument & ReportDocument, newFilter: } let stepInput = new MultiStepInput(`${isAdd ? 'add' : 'edit'} filter...`, [ - { title: `filter on ECU?`, items: ecus, initialValue: () => { return newFilter.ecu; }, placeholder: 'enter or select the ECU to filter (if any)', onValue: (v) => { newFilter.ecu = v.length ? v : undefined; }, isValid: (v => (v.length <= 4)) }, - { title: `filter on APID?`, items: apids, initialValue: () => { return newFilter.apid; }, onValue: (v) => { newFilter.apid = v.length ? v : undefined; }, isValid: (v => (v.length <= 4)) }, - { title: `filter on CTID?`, items: () => ctids.filter(v => { return newFilter.apid !== undefined ? v.data.apids.includes(newFilter.apid) : true; }), initialValue: () => { return newFilter.ctid; }, onValue: (v) => { newFilter.ctid = v.length ? v : undefined; }, isValid: (v => (v.length <= 4)) }, + { title: `filter on ECU?`, items: ecus, initialValue: () => { return newFilter.ecu instanceof RegExp ? newFilter.ecu.source : newFilter.ecu; }, placeholder: 'enter or select the ECU to filter (if any)', onValue: (v) => { newFilter.ecu = onValueChar4OrRegex(newFilter.ecu, v); }, isValid: (v => (v.length <= 4 || util.containsRegexChars(v))) }, + { title: `filter on APID?`, items: apids, initialValue: () => { return newFilter.apid instanceof RegExp ? newFilter.apid.source : newFilter.apid; }, onValue: (v) => { newFilter.apid = onValueChar4OrRegex(newFilter.apid, v); }, isValid: (v => (v.length <= 4 || util.containsRegexChars(v))) }, + { title: `filter on CTID?`, items: () => ctids.filter(v => { return ctidFilter(newFilter.apid, v.data.apids); }), initialValue: () => { return newFilter.ctid instanceof RegExp ? newFilter.ctid.source : newFilter.ctid; }, onValue: (v) => { newFilter.ctid = onValueChar4OrRegex(newFilter.ctid, v); }, isValid: (v => (v.length <= 4 || util.containsRegexChars(v))) }, { title: `filter on payload?`, items: optArgs !== undefined && optArgs.payload !== undefined ? [new PickItem(optArgs.payload)] : [], initialValue: () => { return newFilter.payload; }, onValue: (v) => { newFilter.payload = v.length ? v : undefined; } }, { title: `filter on payloadRegex?`, items: optArgs !== undefined && optArgs.payload !== undefined ? [new PickItem(optArgs.payload)] : [], initialValue: () => { return newFilter.payloadRegex?.source; }, onValue: (v) => { newFilter.payloadRegex = v.length ? new RegExp(v) : undefined; }, isValid: (v => { try { let r = new RegExp(v); return true; } catch (err) { return false; } }) }, { title: `filter type?`, items: [new PickItem(filterTypesByNumber.get(0)!), new PickItem(filterTypesByNumber.get(1)!), new PickItem(filterTypesByNumber.get(2)!)], initialValue: () => { return filterTypesByNumber.get(newFilter.type); }, onValue: (v) => { let t = filterTypesByName.get(v); if (t !== undefined) { newFilter.type = t; } }, isValid: (v => (filterTypesByName.has(v))) }, @@ -211,6 +211,31 @@ export function editFilter(doc: FilterableDocument & ReportDocument, newFilter: }); } +function ctidFilter(apid: string|RegExp|undefined, apids:string[]):boolean { + if (apid === undefined){ + return true; // no apid -> all ctids + } + if (apid instanceof RegExp){ + return apids.some(a => apid.test(a)); + } else { + return apids.includes(apid); + } +} + +function onValueChar4OrRegex(oldValue: string | RegExp | undefined, newValue: string): string | RegExp | undefined { + if (newValue.length) { + const oldWasRegEx = oldValue instanceof RegExp; + const lastValue = oldWasRegEx ? (oldValue as RegExp).source : oldValue; + if (newValue === lastValue) { + return oldValue; // we keep the type + } else { + return util.containsRegexChars(newValue) ? new RegExp(newValue) : newValue; + } + } else { + return undefined; + } +} + // from npm color-names: (import of 2.0 module does export as single elements and not as one object with all keys) const colors = { aliceblue: [240, 248, 255], diff --git a/src/dltFilter.ts b/src/dltFilter.ts index 2ec036dd..5faf1b3c 100644 --- a/src/dltFilter.ts +++ b/src/dltFilter.ts @@ -8,501 +8,663 @@ import { FilterableDltMsg, MSTP, MTIN_LOG, MTIN_CTRL, MSTP_strs, MTIN_LOG_strs } import * as util from './util'; import { v4 as uuidv4 } from 'uuid'; import * as fastXmlParser from 'fast-xml-parser'; +import { containsRegexChars } from './util'; -export enum DltFilterType { POSITIVE, NEGATIVE, MARKER, EVENT }; +export enum DltFilterType { + POSITIVE, + NEGATIVE, + MARKER, + EVENT, +} export class DltFilter { - filterName: string | undefined; // maps to "name" from config - type: DltFilterType; - enabled: boolean = true; - atLoadTime: boolean = false; // this filter gets used a load/opening the dlt file already (thus can't be deactivated later). Not possible with MARKER. - beforePositive: boolean = false; // for neg. (todo later for marker?): match this before the pos. filters. mainly used for plugins like FileTransfer - negateMatch: boolean = false; // perform a "not"/! on the match result. As pos and neg. Filters are or'd this allows to create e.g. a pos filter that all messages have to match e.g. via NEGATIVE with NOT. - - // what to match for: - mstp: number | undefined; - ecu: string | undefined; - apid: string | undefined; - ctid: string | undefined; - logLevelMin: number | undefined; - logLevelMax: number | undefined; - verbose: boolean | undefined; - payload: string | undefined; - payloadToUpper: string | undefined; // will be set if ignoreCasePayload is used, internal speedup - payloadRegex: RegExp | undefined; - ignoreCasePayload: boolean = false; // for both payload and payloadRegex, default to false - lifecycles: number[] | undefined; // array with persistentIds from lifecycles - - // marker decorations: - filterColour: string | object | undefined; - decorationId: string | undefined; - - // time sync: - timeSyncId: string | undefined; - timeSyncPrio: number | undefined; - - // report options: - reportOptions: any | undefined; - - // configs: - private _configs: string[] = []; - - // the options used to create the object. - // asConfiguration() modifies this one based on current values - configOptions: any | undefined; - - constructor(options: any, readonly allowEdit = true) { // we do need at least the type - if ('type' in options) { - this.type = options["type"]; - } else { - throw Error("type missing for DltFilter"); - } - // we create a deep copy (ignoring functions....) and don't keep reference to the options - // passed... otherwise changes on a filter in one document reflect the other as well. - try { - this.configOptions = JSON.parse(JSON.stringify(options)); - } catch (e) { - throw Error(`can't JSON parse the options: ${e}`); - } - // and we assign a id/uuid if it's not there yet: - // todo: check if id represents a valid uuid? - if (!('id' in this.configOptions)) { - this.configOptions.id = uuidv4(); - //console.log(`DltFilter.constructor created id=${this.configOptions.id}`); - } + filterName: string | undefined; // maps to "name" from config + type: DltFilterType; + enabled: boolean = true; + atLoadTime: boolean = false; // this filter gets used a load/opening the dlt file already (thus can't be deactivated later). Not possible with MARKER. + beforePositive: boolean = false; // for neg. (todo later for marker?): match this before the pos. filters. mainly used for plugins like FileTransfer + negateMatch: boolean = false; // perform a "not"/! on the match result. As pos and neg. Filters are or'd this allows to create e.g. a pos filter that all messages have to match e.g. via NEGATIVE with NOT. + + // what to match for: + mstp: number | undefined; + ecu: string | RegExp | undefined; + apid: string | RegExp | undefined; + ctid: string | RegExp | undefined; + logLevelMin: number | undefined; + logLevelMax: number | undefined; + verbose: boolean | undefined; + payload: string | undefined; + payloadToUpper: string | undefined; // will be set if ignoreCasePayload is used, internal speedup + payloadRegex: RegExp | undefined; + ignoreCasePayload: boolean = false; // for both payload and payloadRegex, default to false + lifecycles: number[] | undefined; // array with persistentIds from lifecycles + + // marker decorations: + filterColour: string | object | undefined; + decorationId: string | undefined; + + // time sync: + timeSyncId: string | undefined; + timeSyncPrio: number | undefined; + + // report options: + reportOptions: any | undefined; + + // configs: + private _configs: string[] = []; + + // the options used to create the object. + // asConfiguration() modifies this one based on current values + configOptions: any | undefined; + + constructor(options: any, readonly allowEdit = true) { + // we do need at least the type + if ('type' in options) { + this.type = options['type']; + } else { + throw Error('type missing for DltFilter'); + } + // we create a deep copy (ignoring functions....) and don't keep reference to the options + // passed... otherwise changes on a filter in one document reflect the other as well. + try { + this.configOptions = JSON.parse(JSON.stringify(options)); + } catch (e) { + throw Error(`can't JSON parse the options: ${e}`); + } + // and we assign a id/uuid if it's not there yet: + // todo: check if id represents a valid uuid? + if (!('id' in this.configOptions)) { + this.configOptions.id = uuidv4(); + //console.log(`DltFilter.constructor created id=${this.configOptions.id}`); + } - this.reInitFromConfiguration(); - } - - asConfiguration() { // to persist new Filters into configuration setting - if (this.configOptions === undefined) { this.configOptions = { type: this.type, id: uuidv4() }; } - const obj = this.configOptions; - obj.type = this.type; - // we don't store/change enabled. As we do use configs for runtime changes. - // obj.enabled = this.enabled ? undefined : false; // default to true. don't store to make the config small, readable - obj.name = this.filterName; - obj.atLoadTime = this.atLoadTime ? true : undefined; // default to false - obj.not = this.negateMatch ? true : undefined; // default to false - obj.mstp = this.mstp; - obj.ecu = this.ecu; - obj.apid = this.apid; - obj.ctid = this.ctid; - obj.logLevelMin = this.logLevelMin; - obj.logLevelMax = this.logLevelMax; - obj.verbose = this.verbose; - obj.payload = this.payload; - obj.payloadRegex = this.payloadRegex !== undefined ? this.payloadRegex.source : undefined; - obj.ignoreCasePayload = this.ignoreCasePayload ? true : undefined; // default to false - obj.lifecycles = this.lifecycles; - obj.timeSyncId = this.timeSyncId; - obj.timeSyncPrio = this.timeSyncPrio; - obj.decorationId = this.decorationId; - obj.filterColour = this.filterColour; // or remove blue? - obj.reportOptions = this.reportOptions; - obj.configs = this._configs.length > 0 ? this._configs : undefined; // we report it even if property later hides it - - return obj; - } - - /** - * Re-initializes the internal variables from the configOptions object. - * Allows to update the filter from outside e.g. via filter.configOptions[key] = ... - * and then reflect those values as well. - * Take care: some values can't be changed! (e.g. type) - */ - reInitFromConfiguration() { - const options = this.configOptions; - if (!options) { return; } - - this.filterName = 'name' in options ? options.name : undefined; - - this.enabled = 'enabled' in options ? options.enabled : true; - - this.atLoadTime = 'atLoadTime' in options ? options.atLoadTime : false; - - if ('not' in options) { - this.negateMatch = options.not ? true : false; - } else { this.negateMatch = false; } - - this.mstp = 'mstp' in options ? options.mstp : undefined; - - this.ecu = 'ecu' in options ? options.ecu : undefined; - - this.apid = 'apid' in options ? options.apid : undefined; - - this.ctid = 'ctid' in options ? options.ctid : undefined; - - if ('logLevelMin' in options) { - this.mstp = 0; - this.logLevelMin = options.logLevelMin; - } else { this.logLevelMin = undefined; } - - if ('logLevelMax' in options) { - this.mstp = 0; - this.logLevelMax = options.logLevelMax; - } else { this.logLevelMax = undefined; } - - this.verbose = 'verbose' in options ? options.verbose : undefined; - - this.ignoreCasePayload = 'ignoreCasePayload' in options ? options.ignoreCasePayload === true : false; - this.payload = 'payload' in options ? options.payload : undefined; - if (this.ignoreCasePayload && this.payload !== undefined) { - this.payloadToUpper = this.payload.toUpperCase(); - } else { - this.payloadToUpper = undefined; - } + this.reInitFromConfiguration(); + } - if ('payloadRegex' in options) { - this.payload = undefined; - this.payloadToUpper = undefined; - this.payloadRegex = new RegExp(options.payloadRegex, this.ignoreCasePayload ? "i" : undefined); + asConfiguration() { + // to persist new Filters into configuration setting + if (this.configOptions === undefined) { + this.configOptions = { type: this.type, id: uuidv4() }; + } + const obj = this.configOptions; + obj.type = this.type; + // we don't store/change enabled. As we do use configs for runtime changes. + // obj.enabled = this.enabled ? undefined : false; // default to true. don't store to make the config small, readable + obj.name = this.filterName; + obj.atLoadTime = this.atLoadTime ? true : undefined; // default to false + obj.not = this.negateMatch ? true : undefined; // default to false + obj.mstp = this.mstp; + if (this.ecu instanceof RegExp) { + obj.ecu = this.ecu.source; + obj.ecuIsRegex = true; + } else { + obj.ecu = this.ecu; + if (this.ecu !== undefined) { + obj.ecuIsRegex = false; + } + } + if (this.apid instanceof RegExp) { + obj.apid = this.apid.source; + obj.apidIsRegex = true; + } else { + obj.apid = this.apid; + if (this.apid !== undefined) { + obj.apidIsRegex = false; + } + } + obj.ctid = this.ctid; + if (this.ctid instanceof RegExp) { + obj.ctid = this.ctid.source; + obj.ctidIsRegex = true; + } else { + obj.ctid = this.ctid; + if (this.ctid !== undefined) { + obj.ctidIsRegex = false; + } + } + obj.logLevelMin = this.logLevelMin; + obj.logLevelMax = this.logLevelMax; + obj.verbose = this.verbose; + obj.payload = this.payload; + obj.payloadRegex = this.payloadRegex !== undefined ? this.payloadRegex.source : undefined; + obj.ignoreCasePayload = this.ignoreCasePayload ? true : undefined; // default to false + obj.lifecycles = this.lifecycles; + obj.timeSyncId = this.timeSyncId; + obj.timeSyncPrio = this.timeSyncPrio; + obj.decorationId = this.decorationId; + obj.filterColour = this.filterColour; // or remove blue? + obj.reportOptions = this.reportOptions; + obj.configs = this._configs.length > 0 ? this._configs : undefined; // we report it even if property later hides it + + return obj; + } + + /** + * Re-initializes the internal variables from the configOptions object. + * Allows to update the filter from outside e.g. via filter.configOptions[key] = ... + * and then reflect those values as well. + * Take care: some values can't be changed! (e.g. type) + */ + reInitFromConfiguration() { + const options = this.configOptions; + if (!options) { + return; + } - // needs payloadRegex - if ('timeSyncId' in options && 'timeSyncPrio' in options) { - this.type = DltFilterType.EVENT; - this.timeSyncId = options.timeSyncId; - this.timeSyncPrio = options.timeSyncPrio; - } - } else { // on update those might have been set prev. - this.payloadRegex = undefined; - this.timeSyncId = undefined; - this.timeSyncPrio = undefined; - } + this.filterName = 'name' in options ? options.name : undefined; - this.lifecycles = 'lifecycles' in options && Array.isArray(options.lifecycles) ? options.lifecycles : undefined; + this.enabled = 'enabled' in options ? options.enabled : true; - this.decorationId = undefined; - this.filterColour = undefined; - if (this.type === DltFilterType.MARKER || this.type === DltFilterType.POSITIVE) { - if ('decorationId' in options) { // has preference wrt filterColour - this.decorationId = options.decorationId; - } else if ('filterColour' in options) { - this.filterColour = options.filterColour; - } else { - if (this.type === DltFilterType.MARKER) { - this.filterColour = "blue"; // default to blue - } - } - } + this.atLoadTime = 'atLoadTime' in options ? options.atLoadTime : false; - this.reportOptions = undefined; - if (this.isReport) { - if ('reportOptions' in options) { - this.reportOptions = options.reportOptions; - } - } + if ('not' in options) { + this.negateMatch = options.not ? true : false; + } else { + this.negateMatch = false; + } - this._configs = []; - if ('configs' in options && Array.isArray(options.configs)) { - this._configs.push(...options.configs); - } + this.mstp = 'mstp' in options ? options.mstp : undefined; + + if ('ecu' in options) { + const isRegex = 'ecuIsRegex' in options ? !!options.ecuIsRegex : containsRegexChars(options.ecu); + if (isRegex) { + this.ecu = new RegExp(options.ecu); + } else { + this.ecu = options.ecu; + } + } else { + this.ecu = undefined; + } + if ('apid' in options) { + const isRegex = 'apidIsRegex' in options ? !!options.apidIsRegex : containsRegexChars(options.apid); + if (isRegex) { + this.apid = new RegExp(options.apid); + } else { + this.apid = options.apid; + } + } else { + this.apid = undefined; + } + if ('ctid' in options) { + const isRegex = 'ctidIsRegex' in options ? !!options.ctidIsRegex : containsRegexChars(options.ctid); + if (isRegex) { + this.ctid = new RegExp(options.ctid); + } else { + this.ctid = options.ctid; + } + } else { + this.ctid = undefined; + } + if ('logLevelMin' in options) { + this.mstp = 0; + this.logLevelMin = options.logLevelMin; + } else { + this.logLevelMin = undefined; + } + if ('logLevelMax' in options) { + this.mstp = 0; + this.logLevelMax = options.logLevelMax; + } else { + this.logLevelMax = undefined; } - matches(msg: FilterableDltMsg): boolean { - if (!this.enabled) { - return false; // negateMatch doesn't negate this! - } + this.verbose = 'verbose' in options ? options.verbose : undefined; - const negated = this.negateMatch; - - if (this.mstp !== undefined && msg.mstp !== this.mstp) { return negated; } - if (this.logLevelMax && msg.mtin > this.logLevelMax) { return negated; } // mstp already checked - if (this.logLevelMin && msg.mtin < this.logLevelMin) { return negated; } // mstp already checked - if (this.ecu && msg.ecu !== this.ecu) { return negated; } - if (this.apid && msg.apid !== this.apid) { return negated; } - if (this.ctid && msg.ctid !== this.ctid) { return negated; } - if (this.verbose !== undefined && msg.verbose !== this.verbose) { return negated; } - if (this.payload) { - if (!this.ignoreCasePayload) { - if (!msg.payloadString.includes(this.payload)) { return negated; } - } else { - if (!msg.payloadString.toUpperCase().includes(this.payloadToUpper!)) { return negated; } - } - } - if (this.payloadRegex !== undefined && !this.payloadRegex.test(msg.payloadString)) { return negated; } - if (this.lifecycles !== undefined && this.lifecycles.length > 0) { - // we treat an empty array as always matching (that's why we skip this check if length<=0) - // otherwise the msg lifecycle needs to be within the array: - // msgs without lifecycle are not matched - const lc = msg.lifecycle; - if (!lc) { return negated; } - const msgLcPeristentId = lc.persistentId; - let foundLc: boolean = false; - const lcArray = this.lifecycles; - const lcLength = lcArray.length; - for (let i = 0; i < lcLength; ++i) { - if (msgLcPeristentId === lcArray[i]) { foundLc = true; break; } - } - if (!foundLc) { return negated; } - } + this.ignoreCasePayload = 'ignoreCasePayload' in options ? options.ignoreCasePayload === true : false; + this.payload = 'payload' in options ? options.payload : undefined; + if (this.ignoreCasePayload && this.payload !== undefined) { + this.payloadToUpper = this.payload.toUpperCase(); + } else { + this.payloadToUpper = undefined; + } - // if we reach here all defined criteria match - return !negated; + if ('payloadRegex' in options) { + this.payload = undefined; + this.payloadToUpper = undefined; + this.payloadRegex = new RegExp(options.payloadRegex, this.ignoreCasePayload ? 'i' : undefined); + + // needs payloadRegex + if ('timeSyncId' in options && 'timeSyncPrio' in options) { + this.type = DltFilterType.EVENT; + this.timeSyncId = options.timeSyncId; + this.timeSyncPrio = options.timeSyncPrio; + } + } else { + // on update those might have been set prev. + this.payloadRegex = undefined; + this.timeSyncId = undefined; + this.timeSyncPrio = undefined; } - get iconPath(): ThemeIcon | undefined { - if (this.isReport) { - return new ThemeIcon('graph'); - } else if (!this.enabled) { - return new ThemeIcon('stop-circle'); - } else { - return new ThemeIcon('play'); + this.lifecycles = 'lifecycles' in options && Array.isArray(options.lifecycles) ? options.lifecycles : undefined; + + this.decorationId = undefined; + this.filterColour = undefined; + if (this.type === DltFilterType.MARKER || this.type === DltFilterType.POSITIVE) { + if ('decorationId' in options) { + // has preference wrt filterColour + this.decorationId = options.decorationId; + } else if ('filterColour' in options) { + this.filterColour = options.filterColour; + } else { + if (this.type === DltFilterType.MARKER) { + this.filterColour = 'blue'; // default to blue } - return undefined; + } + } + + this.reportOptions = undefined; + if (this.isReport) { + if ('reportOptions' in options) { + this.reportOptions = options.reportOptions; + } + } + + this._configs = []; + if ('configs' in options && Array.isArray(options.configs)) { + this._configs.push(...options.configs); } + } - get id(): string { - return this.configOptions.id; + matches(msg: FilterableDltMsg): boolean { + if (!this.enabled) { + return false; // negateMatch doesn't negate this! } - get name(): string { - let enabled: string = this.enabled ? "" : "disabled: "; - if (this.filterName) { - enabled += this.filterName + ' '; + const negated = this.negateMatch; + + if (this.mstp !== undefined && msg.mstp !== this.mstp) { + return negated; + } + if (this.logLevelMax && msg.mtin > this.logLevelMax) { + return negated; + } // mstp already checked + if (this.logLevelMin && msg.mtin < this.logLevelMin) { + return negated; + } // mstp already checked + if (this.ecu !== undefined) { + if (this.ecu instanceof RegExp) { + if (!this.ecu.test(msg.ecu)) { + return negated; } - let type: string; - switch (this.type) { - case DltFilterType.POSITIVE: type = "+"; break; - case DltFilterType.NEGATIVE: type = "-"; break; - case DltFilterType.MARKER: type = "*"; break; - case DltFilterType.EVENT: type = "@"; break; - }; - if (this.atLoadTime) { - type = "(load time) " + type; + } else if (msg.ecu !== this.ecu) { + return negated; + } + } + if (this.apid !== undefined) { + if (this.apid instanceof RegExp) { + if (!this.apid.test(msg.apid)) { + return negated; } - if (this.negateMatch) { - type += '!'; + } else if (msg.apid !== this.apid) { + return negated; + } + } + if (this.ctid !== undefined) { + if (this.ctid instanceof RegExp) { + if (!this.ctid.test(msg.ctid)) { + return negated; } - let nameStr: string = ""; - if (this.mstp !== undefined) { - nameStr += MSTP_strs[this.mstp]; - nameStr += ' '; + } else if (msg.ctid !== this.ctid) { + return negated; + } + } + if (this.verbose !== undefined && msg.verbose !== this.verbose) { + return negated; + } + if (this.payload) { + if (!this.ignoreCasePayload) { + if (!msg.payloadString.includes(this.payload)) { + return negated; } - if (this.logLevelMin) { // we ignore 0 values here - nameStr += `>=${MTIN_LOG_strs[this.logLevelMin]} `; + } else { + if (!msg.payloadString.toUpperCase().includes(this.payloadToUpper!)) { + return negated; } - if (this.logLevelMax) { // we ignore 0 value here - nameStr += `<=${MTIN_LOG_strs[this.logLevelMax]} `; + } + } + if (this.payloadRegex !== undefined && !this.payloadRegex.test(msg.payloadString)) { + return negated; + } + if (this.lifecycles !== undefined && this.lifecycles.length > 0) { + // we treat an empty array as always matching (that's why we skip this check if length<=0) + // otherwise the msg lifecycle needs to be within the array: + // msgs without lifecycle are not matched + const lc = msg.lifecycle; + if (!lc) { + return negated; + } + const msgLcPeristentId = lc.persistentId; + let foundLc: boolean = false; + const lcArray = this.lifecycles; + const lcLength = lcArray.length; + for (let i = 0; i < lcLength; ++i) { + if (msgLcPeristentId === lcArray[i]) { + foundLc = true; + break; } - if (this.ecu) { nameStr += `ECU:${this.ecu} `; } // we ignore empty strings - if (this.apid) { nameStr += `APID:${this.apid} `; } - if (this.ctid) { nameStr += `CTID:${this.ctid} `; } - if (this.verbose !== undefined) { nameStr += this.verbose ? 'VERB ' : 'NON-VERB '; } - if (this.payload) { nameStr += `payload contains ${this.ignoreCasePayload ? 'ignoring case ' : ''}'${this.payload}' `; } - if (this.payloadRegex !== undefined) { nameStr += `payload matches ${this.ignoreCasePayload ? 'ignoring case ' : ''}'${this.payloadRegex.source}'`; } - if (this.lifecycles !== undefined) { nameStr += ` in ${this.lifecycles.length} LCs`; } - if (this.timeSyncId !== undefined) { nameStr += ` timeSyncId:${this.timeSyncId} prio:${this.timeSyncPrio}`; } - - return `${enabled}${type}${nameStr}`; - } - - get isReport(): boolean { - // a report filter is a type EVENT filter that has a payloadRegex and no timeSyncId - return this.type === DltFilterType.EVENT && (this.payloadRegex !== undefined) && (this.timeSyncId === undefined); - } - - /** - * array of config names/paths this filter belongs to. - * The property returns empty if the filter is a load time filter - * as configs don't make sense then. - */ - get configs(): string[] { - return this.atLoadTime ? [] : this._configs; - } - - set configs(newCfgs: string[]) { - // we do allow setting it even for load time filters - this._configs = newCfgs; - } - - asRestObject(idHint: number): util.RestObject { - return { - id: this.id, - type: 'filter', - attributes: this.asConfiguration() // inludes id again... - }; - } - - private static similarFiltersKeysToIgnore = ['name', 'reportOptions', 'filterColour', 'decorationId']; - - /** - * return a list of filters "similar" to the filterFragment provided. - * - * Compares from the provided filters (allFilters) which ones are similar to the filterFragment. - * A filter is consideres "similar" if it contains the same key/value pairs. - * The parameter `lessRestrictive`determines whether less or more restrictive filters are returned. - * E.g. - * - lessRestrictive=true: (ctid='ct1') is less restrictive than (apid='ap1', ctid='ct1'). - * - * @param lessRestrictive less restrictive or completely matching/more restrictive - * @param includeDisabled shall disabled filters be included if they are similar - * @param filterFragment (needle) fragements (keys/values) that all have to match - * @param allFilters (haystack) list of filters to search within - * @returns list of filters that are similar - */ - public static getSimilarFilters(lessRestrictive: boolean, includeDisabled: boolean = false, filterFragment: any, allFilters: DltFilter[]): DltFilter[] { - // we check allFilters whether any is "similar": - const activeFilters: DltFilter[] = []; - // all keys that are not to be ignored - const keys = Object.keys(filterFragment).filter((key) => !DltFilter.similarFiltersKeysToIgnore.includes(key)); - let minMatchingFragementKeys = lessRestrictive ? 1 : keys.length; - - for (let i = 0; i < allFilters.length; ++i) { - const filter = allFilters[i]; - if (!filter.atLoadTime && (includeDisabled || filter.enabled) && (filter.type === DltFilterType.POSITIVE || filter.type === DltFilterType.NEGATIVE)) { // todo marker support? - const filterConfigOptions = filter.configOptions; // todo double check: we compare against configOptions from filter but it's not nec. up-to-date? check edit use-cases... - // a filter is similar if all specified filterFragment keys match - // the filter can be less restrictive (e.g. miss the ecu key but have apid for a filterFragment {ecu: ..., apid: ...}) - let allFragmentKeysMatch = true; - let fragmentKeysMatching = 0; - for (let k = 0; k < keys.length && allFragmentKeysMatch; ++k) { - const key = keys[k]; - const keyValue = filterFragment[key]; - const filterValue = filterConfigOptions[key]; - if (filterValue !== undefined) { - if (filterValue === keyValue) { fragmentKeysMatching++; } else { allFragmentKeysMatch = false; } - } else if (keyValue === null) { fragmentKeysMatching++; } - } - if (allFragmentKeysMatch && fragmentKeysMatching >= minMatchingFragementKeys) { // for less restrictive there shouldn't be any other keys??? - activeFilters.push(filter); - } + } + if (!foundLc) { + return negated; + } + } + + // if we reach here all defined criteria match + return !negated; + } + + get iconPath(): ThemeIcon | undefined { + if (this.isReport) { + return new ThemeIcon('graph'); + } else if (!this.enabled) { + return new ThemeIcon('stop-circle'); + } else { + return new ThemeIcon('play'); + } + return undefined; + } + + get id(): string { + return this.configOptions.id; + } + + get name(): string { + let enabled: string = this.enabled ? '' : 'disabled: '; + if (this.filterName) { + enabled += this.filterName + ' '; + } + let type: string; + switch (this.type) { + case DltFilterType.POSITIVE: + type = '+'; + break; + case DltFilterType.NEGATIVE: + type = '-'; + break; + case DltFilterType.MARKER: + type = '*'; + break; + case DltFilterType.EVENT: + type = '@'; + break; + } + if (this.atLoadTime) { + type = '(load time) ' + type; + } + if (this.negateMatch) { + type += '!'; + } + let nameStr: string = ''; + if (this.mstp !== undefined) { + nameStr += MSTP_strs[this.mstp]; + nameStr += ' '; + } + if (this.logLevelMin) { + // we ignore 0 values here + nameStr += `>=${MTIN_LOG_strs[this.logLevelMin]} `; + } + if (this.logLevelMax) { + // we ignore 0 value here + nameStr += `<=${MTIN_LOG_strs[this.logLevelMax]} `; + } + if (this.ecu) { + nameStr += this.ecu instanceof RegExp ? `ECU*:${this.ecu.source} ` : `ECU:${this.ecu} `; + } // we ignore empty strings + if (this.apid) { + nameStr += this.apid instanceof RegExp ? `APID*:${this.apid.source} ` : `APID:${this.apid} `; + } + if (this.ctid) { + nameStr += this.ctid instanceof RegExp ? `CTID*:${this.ctid.source} ` : `CTID:${this.ctid} `; + } + if (this.verbose !== undefined) { + nameStr += this.verbose ? 'VERB ' : 'NON-VERB '; + } + if (this.payload) { + nameStr += `payload contains ${this.ignoreCasePayload ? 'ignoring case ' : ''}'${this.payload}' `; + } + if (this.payloadRegex !== undefined) { + nameStr += `payload matches ${this.ignoreCasePayload ? 'ignoring case ' : ''}'${this.payloadRegex.source}'`; + } + if (this.lifecycles !== undefined) { + nameStr += ` in ${this.lifecycles.length} LCs`; + } + if (this.timeSyncId !== undefined) { + nameStr += ` timeSyncId:${this.timeSyncId} prio:${this.timeSyncPrio}`; + } + + return `${enabled}${type}${nameStr}`; + } + + get isReport(): boolean { + // a report filter is a type EVENT filter that has a payloadRegex and no timeSyncId + return this.type === DltFilterType.EVENT && this.payloadRegex !== undefined && this.timeSyncId === undefined; + } + + /** + * array of config names/paths this filter belongs to. + * The property returns empty if the filter is a load time filter + * as configs don't make sense then. + */ + get configs(): string[] { + return this.atLoadTime ? [] : this._configs; + } + + set configs(newCfgs: string[]) { + // we do allow setting it even for load time filters + this._configs = newCfgs; + } + + asRestObject(idHint: number): util.RestObject { + return { + id: this.id, + type: 'filter', + attributes: this.asConfiguration(), // inludes id again... + }; + } + + private static similarFiltersKeysToIgnore = ['name', 'reportOptions', 'filterColour', 'decorationId']; + + /** + * return a list of filters "similar" to the filterFragment provided. + * + * Compares from the provided filters (allFilters) which ones are similar to the filterFragment. + * A filter is consideres "similar" if it contains the same key/value pairs. + * The parameter `lessRestrictive`determines whether less or more restrictive filters are returned. + * E.g. + * - lessRestrictive=true: (ctid='ct1') is less restrictive than (apid='ap1', ctid='ct1'). + * + * @param lessRestrictive less restrictive or completely matching/more restrictive + * @param includeDisabled shall disabled filters be included if they are similar + * @param filterFragment (needle) fragements (keys/values) that all have to match + * @param allFilters (haystack) list of filters to search within + * @returns list of filters that are similar + */ + public static getSimilarFilters( + lessRestrictive: boolean, + includeDisabled: boolean = false, + filterFragment: any, + allFilters: DltFilter[] + ): DltFilter[] { + // we check allFilters whether any is "similar": + const activeFilters: DltFilter[] = []; + // all keys that are not to be ignored + const keys = Object.keys(filterFragment).filter((key) => !DltFilter.similarFiltersKeysToIgnore.includes(key)); + let minMatchingFragementKeys = lessRestrictive ? 1 : keys.length; + + for (let i = 0; i < allFilters.length; ++i) { + const filter = allFilters[i]; + if ( + !filter.atLoadTime && + (includeDisabled || filter.enabled) && + (filter.type === DltFilterType.POSITIVE || filter.type === DltFilterType.NEGATIVE) + ) { + // todo marker support? + const filterConfigOptions = filter.configOptions; // todo double check: we compare against configOptions from filter but it's not nec. up-to-date? check edit use-cases... + // a filter is similar if all specified filterFragment keys match + // the filter can be less restrictive (e.g. miss the ecu key but have apid for a filterFragment {ecu: ..., apid: ...}) + let allFragmentKeysMatch = true; + let fragmentKeysMatching = 0; + for (let k = 0; k < keys.length && allFragmentKeysMatch; ++k) { + const key = keys[k]; + const keyValue = filterFragment[key]; + const filterValue = filterConfigOptions[key]; + if (filterValue !== undefined) { + if (filterValue === keyValue) { + fragmentKeysMatching++; + } else { + allFragmentKeysMatch = false; } + } else if (keyValue === null) { + fragmentKeysMatching++; + } + } + if (allFragmentKeysMatch && fragmentKeysMatching >= minMatchingFragementKeys) { + // for less restrictive there shouldn't be any other keys??? + activeFilters.push(filter); } - return activeFilters; - } - - /** - * parse dlt-viewer filter files in xml/dlf format - * - * It returns an array with a filter config/fragment that can be passed to the constructor or a string with the warning/error - * that occurred during parsing that filter. - * It does not directly return a DltFilter so that `getSimilarFilters` can be used first to return already existing filters - * instead of newly created ones. - * @param dlfString filter file as string (e.g. from fs.readFileSync(...{encoding: 'utf8'})) - * @returns array of either a filter config/fragment or a string with the warning/error during parsing that filter - */ - public static filtersFromXmlDlf(dlfString: string): (any | string)[] { - let filters: (any | string)[] = []; - try { - const filtersJson = fastXmlParser.parse(dlfString, { - arrayMode: (tagName, parentTagName) => { - switch (tagName) { - case 'dltfilter': - return true; - case 'filter': - return true; - default: - return false; + } + } + return activeFilters; + } + + /** + * parse dlt-viewer filter files in xml/dlf format + * + * It returns an array with a filter config/fragment that can be passed to the constructor or a string with the warning/error + * that occurred during parsing that filter. + * It does not directly return a DltFilter so that `getSimilarFilters` can be used first to return already existing filters + * instead of newly created ones. + * @param dlfString filter file as string (e.g. from fs.readFileSync(...{encoding: 'utf8'})) + * @returns array of either a filter config/fragment or a string with the warning/error during parsing that filter + */ + public static filtersFromXmlDlf(dlfString: string): (any | string)[] { + let filters: (any | string)[] = []; + try { + const filtersJson = fastXmlParser.parse(dlfString, { + arrayMode: (tagName, parentTagName) => { + switch (tagName) { + case 'dltfilter': + return true; + case 'filter': + return true; + default: + return false; + } + }, + }); + if ('dltfilter' in filtersJson) { + const dltfilters = filtersJson['dltfilter']; + if (Array.isArray(dltfilters)) { + for (const filterElems of dltfilters) { + // we expect as elements only 'filter' + if ('filter' in filterElems && Array.isArray(filterElems.filter)) { + for (const dltFilter of filterElems.filter) { + // check for mandatory entries: (only type for now) + if ('type' in dltFilter) { + // map from dlf to our naming convention + let filterFrag: any = { type: dltFilter.type }; + if ('name' in dltFilter) { + const name = dltFilter.name; + if (typeof name === 'string' && name.length > 0) { + filterFrag.name = name; + } + } + if ('enableecuid' in dltFilter && dltFilter.enableecuid === 1) { + if ('ecuid' in dltFilter && dltFilter.ecuid.length > 0) { + filterFrag.ecu = dltFilter.ecuid; + // todo enable autodetection? for now not. treat same as adlt + filterFrag.ecuIsRegex = false; + } + } + if ('enableapplicationid' in dltFilter && dltFilter.enableapplicationid === 1) { + if ('enableregexp_Appid' in dltFilter) { + filterFrag.apidIsRegex = dltFilter.enableregexp_Appid === 1; } - }, - }); - if ('dltfilter' in filtersJson) { - const dltfilters = filtersJson['dltfilter']; - if (Array.isArray(dltfilters)) { - for (const filterElems of dltfilters) { - // we expect as elements only 'filter' - if ('filter' in filterElems && Array.isArray(filterElems.filter)) { - for (const dltFilter of filterElems.filter) { - // check for mandatory entries: (only type for now) - if ('type' in dltFilter) { - // map from dlf to our naming convention - let filterFrag: any = { type: dltFilter.type }; - if ('name' in dltFilter) { - const name = dltFilter.name; - if (typeof name === 'string' && name.length > 0) { - filterFrag.name = name; - } - } - if ('enableecuid' in dltFilter && dltFilter.enableecuid === 1) { - if ('ecuid' in dltFilter && dltFilter.ecuid.length > 0) { - filterFrag.ecu = dltFilter.ecuid; - } - } - if ('enableapplicationid' in dltFilter && dltFilter.enableapplicationid === 1) { - if ('enableregexp_Appid' in dltFilter && dltFilter.enableregexp_Appid === 1) { - filters.push(`regexp for apid not supported yet! ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); - continue; - } else - if ('applicationid' in dltFilter && dltFilter.applicationid.length > 0) { - filterFrag.apid = dltFilter.applicationid; - } - } - if ('enablecontextid' in dltFilter && dltFilter.enablecontextid === 1) { - if ('enableregexp_Context' in dltFilter && dltFilter.enableregexp_Context === 1) { - filters.push(`regexp for ctid not supported yet! ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); - continue; - } else - if ('contextid' in dltFilter && dltFilter.contextid.length > 0) { - filterFrag.ctid = dltFilter.contextid; - } - } - // headertext, enableregexp_Header, ignoreCase_Header - if ('enableheadertext' in dltFilter && dltFilter.enableheadertext === 1) { - filters.push(`filter for headertext not supported yet! ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); - continue; - } - if ('enablepayloadtext' in dltFilter && dltFilter.enablepayloadtext === 1) { - if ('ignoreCase_Payload' in dltFilter && dltFilter.ignoreCase_Payload === 1) { - filterFrag.ignoreCasePayload = true; - } - if (('enableregexp_Payload' in dltFilter && dltFilter.enableregexp_Payload === 1) || - ('enableregexp' in dltFilter && dltFilter.enableregexp === 1)) { - if ('payloadtext' in dltFilter && dltFilter.payloadtext.length > 0) { - filterFrag.payloadRegex = dltFilter.payloadtext; - } - } else { - if ('payloadtext' in dltFilter && dltFilter.payloadtext.length > 0) { - filterFrag.payload = dltFilter.payloadtext; - } - } - } - // regex_search/_replace not supported! - if ('enableRegexSearchReplace' in dltFilter && dltFilter.enableRegexSearchReplace !== 0) { - filters.push(`regex_search/_replace not supported! Please use plugin 'rewrite'! Ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); - continue; - } - if ('enablefilter' in dltFilter && dltFilter.enablefilter === 0) { - filterFrag.enabled = false; // we default to true and dont include in configuration - } - if ('enablectrlmsgs' in dltFilter && dltFilter.enablectrlmsgs === 1) { - filterFrag.mstp = MSTP.TYPE_CONTROL; - } - // messageIdMax/Min not supported! - if ('enableMessageId' in dltFilter && dltFilter.enableMessageId !== 0) { - filters.push(`messageIdMin/Max not supported yet! Ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); - continue; - } - if ('enableLogLevelMax' in dltFilter && dltFilter.enableLogLevelMax === 1 && 'logLevelMax' in dltFilter) { - filterFrag.logLevelMax = dltFilter.logLevelMax; - } - if ('enableLogLevelMin' in dltFilter && dltFilter.enableLogLevelMin === 1 && 'logLevelMin' in dltFilter) { - filterFrag.logLevelMin = dltFilter.logLevelMin; - } - if ('enableMarker' in dltFilter && dltFilter.enableMarker === 1 && 'filterColour' in dltFilter) { - filterFrag.filterColour = dltFilter.filterColour; - } - - filters.push(filterFrag); - } else { - filters.push(`type missing in dlf filter ('${JSON.stringify(dltFilter)}')`); - } - } - } else { - filters.push(`unexpected object (no filter) in dlf ('${JSON.stringify(filterElems)}')`); - } + if ('applicationid' in dltFilter && dltFilter.applicationid.length > 0) { + filterFrag.apid = dltFilter.applicationid; } + } + if ('enablecontextid' in dltFilter && dltFilter.enablecontextid === 1) { + if ('enableregexp_Context' in dltFilter) { + filterFrag.ctidIsRegex = dltFilter.enableregexp_Context === 1; + } + if ('contextid' in dltFilter && dltFilter.contextid.length > 0) { + filterFrag.ctid = dltFilter.contextid; + } + } + // headertext, enableregexp_Header, ignoreCase_Header + if ('enableheadertext' in dltFilter && dltFilter.enableheadertext === 1) { + filters.push(`filter for headertext not supported yet! ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); + continue; + } + if ('enablepayloadtext' in dltFilter && dltFilter.enablepayloadtext === 1) { + if ('ignoreCase_Payload' in dltFilter && dltFilter.ignoreCase_Payload === 1) { + filterFrag.ignoreCasePayload = true; + } + if ( + ('enableregexp_Payload' in dltFilter && dltFilter.enableregexp_Payload === 1) || + ('enableregexp' in dltFilter && dltFilter.enableregexp === 1) + ) { + if ('payloadtext' in dltFilter && dltFilter.payloadtext.length > 0) { + filterFrag.payloadRegex = dltFilter.payloadtext; + } + } else { + if ('payloadtext' in dltFilter && dltFilter.payloadtext.length > 0) { + filterFrag.payload = dltFilter.payloadtext; + } + } + } + // regex_search/_replace not supported! + if ('enableRegexSearchReplace' in dltFilter && dltFilter.enableRegexSearchReplace !== 0) { + filters.push( + `regex_search/_replace not supported! Please use plugin 'rewrite'! Ignoring dlf filter ('${JSON.stringify( + dltFilter + )}')` + ); + continue; + } + if ('enablefilter' in dltFilter && dltFilter.enablefilter === 0) { + filterFrag.enabled = false; // we default to true and dont include in configuration + } + if ('enablectrlmsgs' in dltFilter && dltFilter.enablectrlmsgs === 1) { + filterFrag.mstp = MSTP.TYPE_CONTROL; + } + // messageIdMax/Min not supported! + if ('enableMessageId' in dltFilter && dltFilter.enableMessageId !== 0) { + filters.push(`messageIdMin/Max not supported yet! Ignoring dlf filter ('${JSON.stringify(dltFilter)}')`); + continue; + } + if ('enableLogLevelMax' in dltFilter && dltFilter.enableLogLevelMax === 1 && 'logLevelMax' in dltFilter) { + filterFrag.logLevelMax = dltFilter.logLevelMax; + } + if ('enableLogLevelMin' in dltFilter && dltFilter.enableLogLevelMin === 1 && 'logLevelMin' in dltFilter) { + filterFrag.logLevelMin = dltFilter.logLevelMin; + } + if ('enableMarker' in dltFilter && dltFilter.enableMarker === 1 && 'filterColour' in dltFilter) { + filterFrag.filterColour = dltFilter.filterColour; + } + + filters.push(filterFrag); } else { - filters.push("dltfilter wrong type (no array) in dlf"); + filters.push(`type missing in dlf filter ('${JSON.stringify(dltFilter)}')`); } + } } else { - filters.push("dltfilter missing in dlf"); + filters.push(`unexpected object (no filter) in dlf ('${JSON.stringify(filterElems)}')`); } - } catch (e) { - console.warn(`filtersFromXmlDlf got e=${e}`); - filters.push(`exception: ${e}`); + } + } else { + filters.push('dltfilter wrong type (no array) in dlf'); } - return filters; + } else { + filters.push('dltfilter missing in dlf'); + } + } catch (e) { + console.warn(`filtersFromXmlDlf got e=${e}`); + filters.push(`exception: ${e}`); } + return filters; + } } diff --git a/src/test/suite/dltFilter.test.ts b/src/test/suite/dltFilter.test.ts new file mode 100644 index 00000000..02572603 --- /dev/null +++ b/src/test/suite/dltFilter.test.ts @@ -0,0 +1,286 @@ +import * as assert from 'assert'; + +import { DltFilter, DltFilterType } from '../../dltFilter'; +import { FilterableDltMsg, MSTP } from '../../dltParser'; +import { containsRegexChars } from '../../util'; + +suite('DltFilter class test suite', () => { + test('containsRegexChars', () => { + assert(containsRegexChars('') === false); + assert(containsRegexChars('ECU') === false); + assert(containsRegexChars('abc') === false); + assert(containsRegexChars('^abc') === true); + assert(containsRegexChars('abc$') === true); + assert(containsRegexChars('abc*') === true); + assert(containsRegexChars('abc+') === true); + assert(containsRegexChars('abc?') === true); + assert(containsRegexChars('abc(') === true); + assert(containsRegexChars('abc)') === true); + assert(containsRegexChars('abc[') === true); + assert(containsRegexChars('abc]') === true); + assert(containsRegexChars('abc{') === true); + assert(containsRegexChars('abc}') === true); + assert(containsRegexChars('abc|foo') === true); + assert(containsRegexChars('abc.') === true); + assert(containsRegexChars('abc-') === true); + assert(containsRegexChars('abc\\') === true); + assert(containsRegexChars('abc=') === true); + assert(containsRegexChars('abc!') === true); + assert(containsRegexChars('abc<') === true); + }); + + test('filter from object', () => { + const dltFilter = new DltFilter({ type: 0, ecu: 'ECU', apid: 'APID', ctid: 'CTID' }); + assert(!(dltFilter.ecu instanceof RegExp)); + assert(!(dltFilter.apid instanceof RegExp)); + assert(!(dltFilter.ctid instanceof RegExp)); + assert.equal(dltFilter.apid, 'APID'); + }); + + test('parse dlf ex1 sw version', () => { + const ex1 = ` + + + + 0 + Get Software Version + + + + + get_software_version + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + 0 + 0 + #f0f0f0 + 0 + 0 + + `; + + let filters = DltFilter.filtersFromXmlDlf(ex1); + assert(Array.isArray(filters)); + assert.equal(1, filters.length); + let filter = filters[0]; + assert.equal(typeof filter, 'object'); + //console.warn(`filter='${JSON.stringify(filter, undefined, 2)}'`); + // ensure that we can pass this to DltFilter constructor: + const dltFilter = new DltFilter(filter); + assert.equal(DltFilterType.POSITIVE, dltFilter.type); + assert.equal(MSTP.TYPE_CONTROL, dltFilter.mstp, 'mismatching mstp'); + }); + test('parse dlf ex2_3', () => { + const ex1 = ` + + + + 0 + Error/Fatal Messages + + + + + + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + #f0f0f0 + 2 + 1 + + + 0 + Message Buffer overflow + + + + 2013/05/31 20:34:10.092828 3381.3386 0 ENAT DA1 DC1 control response non-verbose 1 + message_buffer_overflow ok + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + 0 + 0 + #f0f0f0 + 0 + 0 + + `; + + let filters = DltFilter.filtersFromXmlDlf(ex1); + assert(Array.isArray(filters)); + assert.equal(2, filters.length, 'mismatching number of filters'); + let filter = filters[0]; + //console.warn(`filter='${JSON.stringify(filter, undefined, 2)}'`); + // ensure that we can pass this to DltFilter constructor: + const dltFilter = new DltFilter(filter); + assert.equal(DltFilterType.POSITIVE, dltFilter.type); + assert.equal(2, dltFilter.logLevelMax, 'mismatching logLevelMax'); + assert.equal(1, dltFilter.logLevelMin, 'mismatching logLevelMin'); + assert.equal(dltFilter.ignoreCasePayload, false); + filter = filters[1]; + console.warn(`filter[1]='${JSON.stringify(filter, undefined, 2)}'`); + }); + + test('check case sensitive payload', () => { + const ex = ` + + + + 0 + get_software_version + 0 + 0 + 1 + 1 + + `; + let filters = DltFilter.filtersFromXmlDlf(ex); + const dltFilter = new DltFilter(filters[0]); + assert.equal(dltFilter.ignoreCasePayload, false); + let msg: FilterableDltMsg = { + timeStamp: 0, + mstp: 0, + ecu: '', + apid: '', + ctid: '', + mtin: 0, + verbose: true, + payloadString: 'I have get_software_version in my payload', + asRestObject: (i) => { + return { id: i, type: '' }; + }, + }; + assert(dltFilter.matches(msg), 'failed to match same case'); + msg.payloadString = 'I have get_Software_Version in my payload'; + assert(!dltFilter.matches(msg), 'failed to not match ignoring case'); + }); + + test('check case insensitive payload', () => { + const ex = ` + + + + 0 + get_software_version + 1 + 0 + 1 + 1 + + `; + let filters = DltFilter.filtersFromXmlDlf(ex); + const dltFilter = new DltFilter(filters[0]); + assert.equal(dltFilter.ignoreCasePayload, true); + let msg: FilterableDltMsg = { + timeStamp: 0, + mstp: 0, + ecu: '', + apid: '', + ctid: '', + mtin: 0, + verbose: true, + payloadString: 'I have get_software_version in my payload', + asRestObject: (i) => { + return { id: i, type: '' }; + }, + }; + assert(dltFilter.matches(msg), 'failed to match same case'); + msg.payloadString = 'I have get_Software_Version in my payload'; + assert(dltFilter.matches(msg), 'failed to match ignoring case'); + }); + + test('check case sensitive payload regex', () => { + const ex = ` + + + + 0 + ^get_software_version + 0 + 1 + 1 + 1 + + `; + let filters = DltFilter.filtersFromXmlDlf(ex); + const dltFilter = new DltFilter(filters[0]); + assert.equal(dltFilter.ignoreCasePayload, false); + let msg: FilterableDltMsg = { + timeStamp: 0, + mstp: 0, + ecu: '', + apid: '', + ctid: '', + mtin: 0, + verbose: true, + payloadString: 'I have get_software_version in my payload', + asRestObject: (i) => { + return { id: i, type: '' }; + }, + }; + assert(!dltFilter.matches(msg), 'failed to not match same case'); + msg.payloadString = 'get_software_version in my payload'; + assert(dltFilter.matches(msg), 'failed to match same case'); + msg.payloadString = 'get_Software_version in my payload'; + assert(!dltFilter.matches(msg), 'failed to not match same case'); + }); + + test('check case insensitive payload regex', () => { + const ex = ` + + + + 0 + ^get_software_version + 1 + 1 + 1 + 1 + + `; + let filters = DltFilter.filtersFromXmlDlf(ex); + const dltFilter = new DltFilter(filters[0]); + assert.equal(dltFilter.ignoreCasePayload, true); + let msg: FilterableDltMsg = { + timeStamp: 0, + mstp: 0, + ecu: '', + apid: '', + ctid: '', + mtin: 0, + verbose: true, + payloadString: 'I have get_software_version in my payload', + asRestObject: (i) => { + return { id: i, type: '' }; + }, + }; + assert(!dltFilter.matches(msg), 'failed to not match same case'); + msg.payloadString = 'get_software_version in my payload'; + assert(dltFilter.matches(msg), 'failed to match same case'); + msg.payloadString = 'Get_Software_Version in my payload'; + assert(dltFilter.matches(msg), 'failed to match different case'); + }); +}); diff --git a/src/util.ts b/src/util.ts index 0e48623d..5c58063b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -238,3 +238,14 @@ export function getNonce() { export function getUri(webview: vscode.Webview, extensionUri: vscode.Uri, pathList: string[]) { return webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, ...pathList)); } + +/** + * check whether the given string contains any regex chars ^$*+?()[]{}|.-\=!<, + * @param s string to search for regex chars + * @returns whether the string contains any regex chars + */ +export function containsRegexChars(s: string): boolean { + let pos= s.search(/[\^\$\*\+\?\(\)\[\]\{\}\|\.\-\\\=\!\<]/); + // console.log(`containsRegexChars('${s}') pos=${pos}`); + return pos >= 0; +}