node-red-contrib-prib-functions 0.14.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '35 18 * * 1'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ language: [ 'javascript' ]
32
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33
+ # Learn more:
34
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v2
39
+
40
+ # Initializes the CodeQL tools for scanning.
41
+ - name: Initialize CodeQL
42
+ uses: github/codeql-action/init@v1
43
+ with:
44
+ languages: ${{ matrix.language }}
45
+ # If you wish to specify custom queries, you can do so here or in a config file.
46
+ # By default, queries listed here will override any specified in a config file.
47
+ # Prefix the list here with "+" to use these queries and those in the config file.
48
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
49
+
50
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51
+ # If this step fails, then you should remove it and run the build manually (see below)
52
+ - name: Autobuild
53
+ uses: github/codeql-action/autobuild@v1
54
+
55
+ # ℹ️ Command-line programs to run using the OS shell.
56
+ # 📚 https://git.io/JvXDl
57
+
58
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59
+ # and modify them (or add more) to build your code if your project
60
+ # uses a compiled language
61
+
62
+ #- run: |
63
+ # make bootstrap
64
+ # make release
65
+
66
+ - name: Perform CodeQL Analysis
67
+ uses: github/codeql-action/analyze@v1
@@ -0,0 +1,6 @@
1
+ {
2
+ "recommendations": [
3
+ "bibhasdn.git-easy",
4
+ "github.vscode-pull-request-github"
5
+ ]
6
+ }
package/README.md CHANGED
@@ -50,6 +50,11 @@ Array metrics:
50
50
  * Normalise
51
51
  * Standardization (Z-score Normalization)
52
52
 
53
+ Array data
54
+ * distances
55
+ * minimum distance(s) between points
56
+ * maximum distance(s) between points
57
+
53
58
  ![Data Analysis](documentation/DataAnalysis.JPG "Data Analysis")
54
59
 
55
60
  example:
@@ -154,7 +159,7 @@ Test example:
154
159
 
155
160
  The levenshtein distance between two character strings.
156
161
 
157
- ![Levenshtein Distance](documentation/levenshteinDistance.JPG "Levenshtein Distance")
162
+ ![Levenshtein Distance](documentation/levenshteinDistance.jpg "Levenshtein Distance")
158
163
 
159
164
  ------------------------------------------------------------
160
165
 
@@ -242,6 +247,12 @@ Test/example flow in test/generalTest.json
242
247
 
243
248
  # Version
244
249
 
250
+ 0.17.0 Add finished wire to load injector
251
+
252
+ 0.16.10 data analysis add eulcidean distance functions. Add array pairs
253
+
254
+ 0.16.0 fix data analysis variance and stddev, add sample, add tests
255
+
245
256
  0.14.2 fix general test flows. Add icon for data analysis
246
257
 
247
258
  0.14.1 fix capitalization issue with levenshtein Distance
@@ -256,40 +267,6 @@ Test/example flow in test/generalTest.json
256
267
 
257
268
  0.11.0 Transform for AVRO and snappy. Add JSON to CSV
258
269
 
259
- 0.10.2 Transform validate for array source, bug fixes on transform and add improvements to array to messages. Added node for levenshtein distance.
260
-
261
- 0.10.1 Real time weighted moving average, levenshtein Distance, for test allow testing of "infinity","-infinity" and "NaN" in JSON.
262
-
263
- 0.10.0 Many fixes to transform. Array and csv to various forms work. Added test to validate.
264
- Improved test to allow for escape to put special characters into a string.
265
-
266
- 0.9.6 Enhance transform with csv ignore lead or trailing lines.
267
- Add Array and CSV to Messages. Add in topic override
268
-
269
- 0.9.5 Enhance transform with path and setting source and target.
270
- Outlier allowed to set number of deviations if median and reset or set stats
271
- Add outlier detection and Pearson R realtime metrics.
272
-
273
- 0.8.1 Add realtime metrics to data analysis.
274
-
275
- 0.7.1
276
- * fix json to table html and minor code improvements.
277
- turn off debug mode on spawn process.
278
- clear down timer on close for host available
279
- * add Host Available
280
-
281
- 0.6.0
282
- * add Spawn Process
283
- * improve experimental transform with json to table html
284
-
285
- 0.5.0
286
- * test node add select property tested for result
287
- * dataAnalysis add property analysed
288
- * add experimental transform
289
-
290
- 0.4.0 Add test, monitor flow, data analysis
291
-
292
- 0.0.1 base
293
270
 
294
271
  # Author
295
272
 
@@ -0,0 +1,8 @@
1
+ if(Array.prototype.last==null)
2
+ Object.defineProperty(Array.prototype, "last", {
3
+ value() {
4
+ return this[this.length-1];
5
+ },
6
+ writable: true,
7
+ configurable: true
8
+ });
@@ -0,0 +1,15 @@
1
+ if(Array.prototype.pairs==null)
2
+ Array.prototype.pairs=function (func) {
3
+ const l=this.length,li=l-1;
4
+ if(func) {
5
+ for (let i=0; i<li ; i++) {
6
+ for (let j =i+1; j<l; j++) {
7
+ func(this[i], this[j],i,j,this);
8
+ }
9
+ }
10
+ } else {
11
+ const r=[];
12
+ this.pairs(func===null? (p1,p2,i1,i2)=>r.push([p1,p2,i1,i2]) : (p1,p2)=>r.push([p1,p2]) )
13
+ return r;
14
+ }
15
+ }
@@ -6,6 +6,7 @@
6
6
  defaults: {
7
7
  name: {value:"",required:false},
8
8
  action: {value:"avg",required:true},
9
+ columns:{value:"",required:false},
9
10
  outputs: {value:(["realtime","realtimePredict"].includes(this.action)?3:2),required:true},
10
11
  outliersBase: {value:"avg",required:false},
11
12
  outliersStdDevs: {value:"3",required:false},
@@ -44,6 +45,11 @@
44
45
  }
45
46
  });
46
47
  $("#node-input-action").change(function() {
48
+ if(["distances","distancesMax","distancesMin"].includes( $(this).val() )) {
49
+ $(".form-row-http-in-columns").show();
50
+ } else {
51
+ $(".form-row-http-in-columns").hide();
52
+ }
47
53
  if(["movingAvgSimple","movingAvgWeighted","movingAvgExponential","realtime","realtimePredict"].includes( $(this).val() )) {
48
54
  $(".form-row-http-in-term").show();
49
55
  } else {
@@ -142,6 +148,9 @@
142
148
  <option value="corelationship">Corelationship</option>
143
149
  <option value="deltas">Deltas</option>
144
150
  <option value="deltaNormalised">Deltas Normalised</option>
151
+ <option value="distances">Euclidean Distances</option>
152
+ <option value="distancesMax">Euclidean Distances Max</option>
153
+ <option value="distancesMin">Euclidean Distances Min</option>
145
154
  <option value="pearsonR">Linear Correlation Coefficient</option>
146
155
  <option value="max">Maximum</option>
147
156
  <option value="avg">Mean</option>
@@ -158,12 +167,19 @@
158
167
  <option value="realtimePredict">RealTime Metrics + predicts</option>
159
168
  <option value="standardize">Standardization (Z-score Normalization)</option>
160
169
  <option value="stdDev">Standard Deviation</option>
170
+ <option value="sampleStdDev">Standard Deviation (Sample)</option>
161
171
  <option value="skew">Skewness</option>
162
172
  <option value="sum">Sum</option>
163
173
  <option value="variance">Variance</option>
174
+ <option value="sampleVariance">Variance (Sample)</option>
164
175
  </select>
165
176
  </div>
166
177
 
178
+ <div class="form-row form-row-http-in-columns hide">
179
+ <label for="node-input-columns"><i class="icon-bookmark"></i> Columns</label>
180
+ <input type="text" id="node-input-columns" placeholder="columns">
181
+ </div>
182
+
167
183
  <div class="form-row form-row-http-in-term hide">
168
184
  <label for="node-input-term"><i class="icon-bookmark"></i> Term</label>
169
185
  <input type="number" id="node-input-term" placeholder="term" min="2" max="100" step="1">
@@ -229,4 +245,8 @@ If real-time stats then a message can send directive instruction in topic:
229
245
  <li>3 standard deviations of the mean (or median), around 99.7%
230
246
  </ul>
231
247
  </p>
248
+ <p>
249
+ Distance functions take in an array of points in an array and calculates the euclidean distance.
250
+ e.g. max input [ [1,1],[2,2],[3,3],[4,5]]), has output [{distance:5,points:[[1,1],[4,5]],index:[0,3]}]
251
+ </p>
232
252
  </script>
@@ -1,13 +1,9 @@
1
1
  const logger = new (require("node-red-contrib-logger"))("Data Analysis");
2
2
  logger.sendInfo("Copyright 2020 Jaroslav Peter Prib");
3
3
 
4
- Object.defineProperty(Array.prototype, "last", {
5
- value() {
6
- return this[this.length-1];
7
- },
8
- writable: true,
9
- configurable: true
10
- });
4
+ require("./arrayLast");
5
+ const ed=require("./euclideanDistance.js");
6
+ require("./forNestedEach");
11
7
 
12
8
  function crossNormalisedDeltas() {
13
9
  const dataPoints=this.dataPoint;
@@ -120,8 +116,18 @@ function setDataPoint(value,term,node,dp) {
120
116
  dp.weightedMovingAvg=(dp.weightedMovingAvg*2/count)/(count+1);
121
117
  dp.exponentialWeightedMoving.forEach(c=>c.sample(value));
122
118
  }
119
+ function getColumns(node) {
120
+ if(node.columns) {
121
+ if(node.columns instanceof Array) return node.columns
122
+ return eval("["+node.columns+"]");
123
+ }
124
+ }
123
125
  functions={
124
126
  avg:(d)=>functions.sum(d)/d.length,
127
+ arrayMax:(d)=>{ // not tested
128
+ let max=[],indices
129
+ a.forNestedEach((e,f,l)=>{const i=l[l.length-1];if(max[l]<e) {max=e,indices=l}})
130
+ },
125
131
  covariance: (d)=>{
126
132
  const means=[],covars=[],dl=d.length,dlminus1=dl-1,N=d[0].length;
127
133
  d.forEach((e,i)=>{
@@ -141,11 +147,16 @@ functions={
141
147
  corelationship:(d)=>{
142
148
  const covars=functions.covariance(d);
143
149
  const stdDev=d.map(c=>functions.stdDev(c));
144
- logger.send({label:"test",d:d,covars:covars,stdDev:stdDev});
145
- return 1//covars.map((a)=>a.map((c,i)=>c==null?null:c/(stdDev[i+1]*stdDev[i])));
150
+ return covars.map((a)=>a.map((c,i)=>c==null?null:c/(stdDev[i+1]*stdDev[i])));
146
151
  },
147
152
  deltas :(d)=>d.map( (c,i)=>c-(d[i-1]||0) ),
148
153
  deltaNormalised :(d)=>d.map( (c,i)=>(c-(d[i-1]||0)) / (d[i-1]||0) ),
154
+ distances: (d,term,node)=>{
155
+ if(node.columns) return ed.distances(d,getColumns(node))
156
+ return ed.distances(d);
157
+ },
158
+ distancesMin: (d,term,node)=>ed.minDistances(d,getColumns(node)),
159
+ distancesMax: (d,term,node)=>ed.maxDistances(d,getColumns(node)),
149
160
  max: (d)=> Math.max(...d),
150
161
  median:(d)=>{
151
162
  const i=Math.floor(d.length/2);
@@ -266,11 +277,23 @@ functions={
266
277
  }
267
278
  return dp;
268
279
  },
280
+ sampleVariance:(d)=>{
281
+ const mean=functions.avg(d);
282
+ const sum=functions.sumWithFunction(d,(v)=>v-mean)
283
+ return sum/(d.length-1)
284
+ },
269
285
  sum:(d)=>d.reduce((p,c)=>p+c),
270
286
  sumWithFunction:(d,f)=>d.reduce((p,c)=>p+f.apply(this,[c]),0),
271
287
  variance:(d)=>{ //Var(X) = E (X − E(X))2 = E(X2) − (E(X))2
272
- return functions.sumWithFunction(d,(v)=>Math.pow(v,2))
273
- - Math.pow(functions.avg(d),2);
288
+ const n=d.length;
289
+ return functions.sumWithFunction(d,(v)=>Math.pow(v,2))/n - Math.pow(functions.avg(d),2);
290
+ },
291
+ sampleStdDev:(d)=>Math.sqrt(functions.sampleVariance(d)),
292
+ sampleVariance:(d)=>{
293
+ if(d.length<2)return 0
294
+ const mean=functions.avg(d);
295
+ const sum=d.reduce((a,e)=>a+Math.pow(e-mean,2),0);
296
+ return sum/(d.length-1)
274
297
  }
275
298
  }
276
299
  functions.mean=functions.avg;
@@ -361,7 +384,8 @@ module.exports = function (RED) {
361
384
  return;
362
385
  }
363
386
  try{
364
- msg.result=node.actionfunction.apply(node,[node.getData(msg,node),node.term,node]);
387
+ const data=node.getData(msg,node);
388
+ if(data) msg.result=node.actionfunction.apply(node,[data,node.term,node]);
365
389
  switch(node.action) {
366
390
  case "realtime":
367
391
  if(msg.result.outlier) {
@@ -371,6 +395,7 @@ module.exports = function (RED) {
371
395
  break;
372
396
  }
373
397
  } catch(ex) {
398
+ if(logger.active) logger.send({label:"error input",action:node.action,message:ex.message,stack:ex.stack});
374
399
  msg.error=ex.message;
375
400
  if(node.maxErrorDisplay) {
376
401
  --node.maxErrorDisplay;
@@ -0,0 +1,54 @@
1
+ require("./arrayPairs");
2
+ function distance(point1,point2) {
3
+ try{
4
+ if(point1==null | point2==null | point2.length==0 ) return NaN;
5
+ if(point1.length!==point2.length) throw Error("argument 1 array size not equal argument 2 array size") ;
6
+ const sum=point1.reduce((s,c,i)=>{
7
+ const d=c-point2[i];
8
+ return s+d*d;
9
+ },0);
10
+ return Math.sqrt(sum);
11
+ } catch(ex){
12
+ if(!(point1 instanceof Array)) throw Error("argument 1 not array") ;
13
+ if(!(point2 instanceof Array)) throw Error("argument 2 not array") ;
14
+ throw ex;
15
+ }
16
+ }
17
+
18
+ function distanceColumns(point1,point2,columns=[]) {
19
+ const sum=columns.reduce((s,c)=>{
20
+ const d=point1[c]-point2[c];
21
+ return s+d*d;
22
+ },0);
23
+ return Math.sqrt(sum)
24
+ }
25
+ function arrayPairs(points,f=distance,args) {
26
+ if(!(points instanceof Array)) throw Error("argument 1 not array") ;
27
+ const r=[];
28
+ points.pairs((p1,p2,i1,i2)=>{
29
+ r.push([ f(p1,p2,args), i1, i2])
30
+ });
31
+ return r;
32
+ }
33
+ function arrayPairsSet(points,f=distance,args,sf=(a,b)=>a>b,maxSetSize) {
34
+ let m,r=[],pi1,pi2;
35
+ points.pairs((p1,p2,i1,i2)=>{
36
+ const d=f(p1,p2,args);
37
+ if(m===d) {
38
+ r.push({points:[p1,p2],index:[i1,i2],distance:d});
39
+ if(maxSetSize && maxSetSize>r.length) r.shift
40
+ } else if(m==null | sf(m,d)==true) {
41
+ m=d;
42
+ r=[{points:[p1,p2],index:[i1,i2],distance:d}];
43
+ }
44
+ });
45
+ return r;
46
+ }
47
+ module.exports={
48
+ distance:distance,
49
+ distanceColumns:distanceColumns,
50
+ distances:arrayPairs,
51
+ distancesColumns:(a,columns)=>arrayPairs(a,distanceColumns,columns),
52
+ minDistances:(a,columns)=>arrayPairsSet(a, columns?distanceColumns:distance, columns, (a,b)=>a>b),
53
+ maxDistances:(a,columns)=>arrayPairsSet(a, columns?distanceColumns:distance, columns, (a,b)=>a<b)
54
+ };
@@ -0,0 +1,24 @@
1
+ if(Array.prototype.forNestedEach==null)
2
+ Array.prototype.forNestedEach=function (func,filter,level=[]) {
3
+ const l=level.length;
4
+ if(filter.hasOwnProperty(l)) {
5
+ const e=filter[l];
6
+ const callFilter=(v)=>!e.includes(v);
7
+ this.forEach((element, index, array) => {
8
+ if(callFilter(index)) return;
9
+ if(element instanceof Array) {
10
+ element.forNestedEach(func,filter,level.push(index))
11
+ } else {
12
+ func(element,index,array,level)
13
+ }
14
+ } )
15
+ return
16
+ }
17
+ this.forEach((element, index, array) => {
18
+ if(element instanceof Array) {
19
+ element.forNestedEach(func,filter,level.push(index))
20
+ } else {
21
+ func(element,index,array,level)
22
+ }
23
+ } )
24
+ }
package/package.json CHANGED
@@ -1,31 +1,24 @@
1
1
  {
2
2
  "name": "node-red-contrib-prib-functions",
3
- "version": "0.14.2",
3
+ "version": "0.17.0",
4
4
  "description": "Node-RED added node functions.",
5
5
  "dependencies": {
6
- "node-red-contrib-logger": "0.0.4",
7
- "avsc": "*",
8
- "fast-xml-parser": "*",
9
- "xlsx": "*"
6
+ "avsc": "^5.6.1",
7
+ "fast-xml-parser": "^3.19.0",
8
+ "node-red-contrib-logger": "^0.0.6",
9
+ "xlsx": "^0.17.0"
10
10
  },
11
11
  "devDependencies": {
12
- "bl": ">=1.2.3",
13
- "bcrypt": ">=5.0.0",
14
- "eslint": "^6.8.0",
15
- "eslint-config-standard": "^14.1.0",
16
- "eslint-plugin-import": "^2.19.1",
17
- "eslint-plugin-mocha": "^8.0.0",
18
- "eslint-plugin-node": "^11.0.0",
19
- "eslint-plugin-promise": "^4.2.1",
20
- "eslint-plugin-standard": "^4.0.1",
21
- "mocha": "^8.1.3",
22
- "node-red": "^1.1.3",
23
- "node-red-node-test-helper": "*"
12
+ "@node-red/runtime": ">=1.2.8",
13
+ "axios": ">=0.21.1",
14
+ "bcrypt": "^5.0.1",
15
+ "bl": "^5.0.0",
16
+ "mocha": "^9.2.2",
17
+ "node-red": "^2.2.2",
18
+ "node-red-node-test-helper": "^0.2.7"
24
19
  },
25
20
  "scripts": {
26
- "test": "mocha \"test/**/*.js\"",
27
- "lint": "eslint --ext .js ./",
28
- "lint-fix": "eslint --fix --ext .js ./"
21
+ "test": "mocha \"test/**/e*.js\""
29
22
  },
30
23
  "repository": {
31
24
  "type": "git",
@@ -46,6 +39,8 @@
46
39
  "data",
47
40
  "data analysis",
48
41
  "delta",
42
+ "distance",
43
+ "euclidian distance",
49
44
  "EMA",
50
45
  "EWMA",
51
46
  "Exponential Moving Average",
@@ -61,6 +56,7 @@
61
56
  "mean",
62
57
  "median",
63
58
  "minimun",
59
+ "model",
64
60
  "monitor",
65
61
  "moving",
66
62
  "Moving Average",
@@ -70,6 +66,8 @@
70
66
  "Process",
71
67
  "range",
72
68
  "realtime",
69
+ "sample variance",
70
+ "sample stddev",
73
71
  "Simple Moving Average",
74
72
  "skew",
75
73
  "SMA",
@@ -0,0 +1,226 @@
1
+ const assert=require("assert");
2
+ const should=require("should");
3
+ const asn1=require("../transform/asn1.js");
4
+ const request=require('request');
5
+ /*
6
+ function stringFromArrayBuffer (arrayBuffer, onSuccess, onFail) {
7
+ const reader = new FileReader();
8
+ reader.onload = function (event) {
9
+ onSuccess(event.target.result);
10
+ };
11
+ reader.onerror = function (event) {
12
+ onFail(event.target.error);
13
+ };
14
+ reader.readAsBinaryString(new Blob([ arrayBuffer ],
15
+ { type: 'application/octet-stream' }));
16
+ }
17
+
18
+ */
19
+ /*
20
+ * as per https://www.w3.org/Protocols/HTTP-NG/asn1.html
21
+ *
22
+ */
23
+ const asn1eg = new Uint8Array([
24
+ 0x60, // [0110|0000], [APPLICATION 0, Compound] - GetRequest
25
+ 0x80, // [1000|0000], Indefinite length
26
+
27
+ 0x01, // [0000|0001], [BOOLEAN] GetRequest.headerOnly
28
+ 0x01, // [0000|0001], length 1
29
+ 0x01, // [0000|0001], value TRUE
30
+
31
+ 0x01, // [0000|0001], [BOOLEAN] GetRequest.lock
32
+ 0x01, // [0000|0001], length 1
33
+ 0x00, // [0000|0000] value FALSE
34
+
35
+ 0x61, // [0110|0001], [APPLICATION 1, Compound] - GetRequest.types
36
+ 0x80, // indefinite length
37
+
38
+ 0xa0, // [1010|0000], [CONTEXT 0, Compound] types.standardTypes
39
+ 0x80, // indefinite length
40
+
41
+ 0x03, // [0000|0011] [BIT STRING]
42
+ 0x02, // length 2
43
+ 0x04, // Four bits unused
44
+ 0x80, //[1000|0000] {html}
45
+
46
+ 0x03, // [0000|0011] [BIT STRING]
47
+ 0x02, // length 2
48
+ 0x04, // Four bits unused
49
+ 0x40, // [0100|0000] {plain-text}
50
+
51
+ 0x00,
52
+ 0x00, // End of list of standard Types
53
+ 0x00,
54
+ 0x00, // End of Accept Types
55
+ 0x04, // [0000|0100], [OCTET STRING] GetRequest.url
56
+ 0x16, // [0001|0110], length 22
57
+ "/ses/magic/moxen.html", // value
58
+ 0x00,
59
+ 0x00 // End of get request
60
+ ]);
61
+
62
+ describe('asn1', function() {
63
+ it("decode ans1 eg.",(done)=>{
64
+ const result=asn1.decodeBER(asn1eg);
65
+ console.log({label:"asn1eg",data:asn1eg,result:result.toString()});
66
+ done();
67
+ });
68
+
69
+ it("decode",(done)=>{
70
+ const certB64 = "MIIDLjCCAhagAwIBAgIBATANBgkqhkiG9w0BAQsFADA6MRkwFwYDVQQDExBUZXN0IGNlcnRpZmljYXRlMR0wGwYJKoZIhvcNAQkBFg5zb21lQGVtYWlsLm5ldDAeFw0yMTAzMTYwMDAwMDBaFw0yMTA0MTYwMDAwMDBaMDoxGTAXBgNVBAMTEFRlc3QgY2VydGlmaWNhdGUxHTAbBgkqhkiG9w0BCQEWDnNvbWVAZW1haWwubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3N6J0GUJ8URj2fduC26mjCzWf75jM3QSLQYiXSTAMqJA9apf09GMmT+UC6jq2J1U49mXGezE64uXv2tyys9S07xgRkNAWPJXz0opKYud4XPEpdxKfQkD2XklK+8R3BPhAOOxSpfR+SFkLxTMiDHsOt+Xbb98DZ8F3QkzHLvX42jEfAR0StIRLgFYEtf4vX9q4OsYTeJ4xk61lTJc3d0ep/JTp55fxWRaQdzhg+fkv9XwJxxhW9pJRekZORnRb4Q1Zyw+uecuIffsmhLzang45npfzAKXuPaE6lnRMHauLQ1rGGqYA/Vaq4UU6yZUTVLpsKON7b1xogMQrqIkbqtTuQIDAQABoz8wPTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIEkDAdBgNVHQ4EFgQUl4hohjz9Xxb4lYhsOiq9wVqKv8YwDQYJKoZIhvcNAQELBQADggEBAIKH86qkFJV3FZyblAMWDSEbEi4MV2Epb5ty7wpSatHvz8NKtmB/hVFGwWFBj5OfS9wfaX6Uw24DyBSBOOqEzonUeqFTDo54zqQ4fQ+UlC/79aq7awGpEuXFnUF3xiLFqHNz5zUeKEYY0W5XKFg/TiW6hAmxlDg5ybAoHDROpwT4u6TuOK6OxMneQRBESmZlO43DYwCG950fXEDJT2gXVLbbSSTln8JBHfTAwOgmsDtaZOCieTS6KYwscWy3u/8xxMyX8NS3A1Zeh0jtk/irKzfsNAdcl8aQwdckGAkPWT/9EqawC33Ep3+2br41+K1jjGT8LeYDlMYSJycWo9tltKc=";
71
+ // const result=asn1.decodeBER(new Uint8Array(Buffer.from(certB64, "base64")).buffer);
72
+ const data=asn1.base64(certB64);
73
+ const result=asn1.decodeBER(data);
74
+ console.log({label:"certB64",data:data,result:result.toString()});
75
+ done();
76
+ });
77
+
78
+ it("encode",(done)=>{
79
+ const value=new Uint8Array([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]);
80
+ console.log({hex:asn1.hex(value)})
81
+ const berData = new asn1.encode({
82
+ value: (value.buffer),
83
+ identifier: {
84
+ "class": 1,
85
+ tag: 1,
86
+ iconstructed: false
87
+ },
88
+ length: {
89
+ indefiniteForm: false,
90
+ longForm: false,
91
+ length: 10
92
+ }
93
+ });
94
+ assert.equal(berData.length, 10, "Incorrect value for blockLength");
95
+ done();
96
+ });
97
+
98
+ const wikiExample=new Uint8Array([0x30,0x13,0x02,0x01,0x05,0x16,0x0e,0x41,0x6e,0x79,0x62,0x6f,0x64,0x79,0x20,0x74,0x68,0x65,0x72,0x65,0x3f]);
99
+ const wikiDecoded=asn1.decode(wikiExample);
100
+ /*
101
+ 30 — type tag indicating SEQUENCE
102
+ 13 — length in octets of value that follows
103
+ 02 — type tag indicating INTEGER
104
+ 01 — length in octets of value that follows
105
+ 05 — value (5)
106
+ 16 — type tag indicating IA5String
107
+ (IA5 means the full 7-bit ISO 646 set, including variants,
108
+ but is generally US-ASCII)
109
+ 0e — length in octets of value that follows
110
+ 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f — value ("Anybody there?")
111
+ */
112
+ console.log(wikiDecoded.toString())
113
+ });
114
+
115
+ describe("ASN.1:2008",()=> {
116
+ function getCase(content,callback){
117
+ const testURL="https://raw.githubusercontent.com/YuryStrozhevsky/ASN1-2008-free-test-suite/master/suite/"+content;
118
+ request(testURL, function (error, response, body) {
119
+ // console.log({url:testURL,error:error,response:response,body:body});
120
+ callback(body)
121
+ });
122
+
123
+ }
124
+ function caseDecodeThrowsError(testCase,error,errorType='TypeError'){
125
+ it(testCase,(done)=>{
126
+ assert.throws(()=>{
127
+ getCase(testCase,(data)=>{
128
+ try{
129
+ const dataArray=new TextEncoder().encode(data);
130
+ const results=asn1.decode(dataArray).toString();
131
+ console.log("caseDecodeThrowsError no error for "+testCase+" results: "+results)
132
+ done()
133
+ } catch(ex){
134
+ console.log("caseDecodeThrowsError error:"+ex.message)
135
+ throw ex;
136
+ }
137
+ })
138
+ },
139
+ {name: errorType ,message:error}
140
+ );
141
+ });
142
+ }
143
+ function caseDecodeOK(testCase,result){
144
+ it(testCase,(done)=>{
145
+ getCase(testCase,(data)=>{
146
+ console.log()
147
+ const result=asn1.decode(data);
148
+ done();
149
+ });
150
+ });
151
+ }
152
+ caseDecodeThrowsError("tc1.ber","tag > 0x24");
153
+ /* caseDecodeThrowsError("tc2.ber","???");
154
+ caseDecodeThrowsError("tc3.ber","???");
155
+ caseDecodeThrowsError("tc4.ber","???");
156
+ caseDecodeOK("tc5.ber");
157
+ caseDecodeOK("tc6.ber");
158
+ caseDecodeOK("tc7.ber");
159
+ caseDecodeOK("tc8.ber");
160
+ caseDecodeOK("tc9.ber");
161
+ caseDecodeOK("tc10.ber");
162
+ caseDecodeOK("tc11.ber");
163
+ caseDecodeOK("tc12.ber");
164
+ caseDecodeOK("tc13.ber");
165
+ caseDecodeOK("tc14.ber");
166
+ caseDecodeOK("tc15.ber");
167
+ caseDecodeOK("tc16.ber");
168
+ caseDecodeOK("tc17.ber");
169
+ caseDecodeThrowsError("tc18.ber","Value block value must be equal to -4095");
170
+ caseDecodeThrowsError("tc19.ber","End of input reached before message was fully decoded (inconsistent offset and length values)", "Error message inside value block does not match");
171
+ caseDecodeThrowsError("tc20.ber","Value block value must be equal to -4095");
172
+ caseDecodeThrowsError("tc21.ber","Value block value must be equal to -4095");
173
+ caseDecodeThrowsError("tc22.ber","Value block value must be equal to -4095");
174
+ caseDecodeOK("tc23.ber");
175
+ caseDecodeOK("tc24.ber");
176
+ caseDecodeOK("tc25.ber");
177
+ caseDecodeOK("tc26.ber");
178
+ caseDecodeOK("tc27.ber");
179
+ caseDecodeOK("tc28.ber");
180
+ caseDecodeOK("tc29.ber");
181
+ caseDecodeOK("tc30.ber");
182
+ caseDecodeOK("tc31.ber");
183
+ caseDecodeOK("tc32.ber");
184
+ caseDecodeOK("tc33.ber");
185
+ caseDecodeOK("tc34.ber");
186
+ caseDecodeOK("tc35.ber");
187
+ caseDecodeOK("tc36.ber");
188
+ caseDecodeOK("tc37.ber");
189
+ caseDecodeOK("tc38.ber");
190
+ caseDecodeOK("tc39.ber");
191
+ caseDecodeOK("tc40.ber");
192
+ caseDecodeOK("tc41.ber");
193
+ caseDecodeOK("tc42.ber");
194
+ caseDecodeOK("tc43.ber");
195
+ caseDecodeOK("tc44.ber");
196
+ caseDecodeOK("tc45.ber");
197
+ caseDecodeOK("tc46.ber");
198
+ caseDecodeOK("tc47.ber");
199
+ caseDecodeOK("tc48.ber");
200
+ */
201
+ });
202
+
203
+ /*
204
+
205
+ ECN - Encoding Control Notation (ECN)
206
+
207
+ FooProtocol DEFINITIONS ::= BEGIN
208
+
209
+ FooQuestion ::= SEQUENCE {
210
+ trackingNumber INTEGER(0..199),
211
+ question IA5String
212
+ }
213
+
214
+ FooAnswer ::= SEQUENCE {
215
+ questionNumber INTEGER(10..20),
216
+ answer BOOLEAN
217
+ }
218
+
219
+ FooHistory ::= SEQUENCE {
220
+ questions SEQUENCE(SIZE(0..10)) OF FooQuestion,
221
+ answers SEQUENCE(SIZE(1..10)) OF FooAnswer,
222
+ anArray SEQUENCE(SIZE(100)) OF INTEGER(0..1000),
223
+ ...
224
+ }
225
+ END
226
+ */
@@ -0,0 +1,112 @@
1
+ const assert=require('assert');
2
+ const should=require("should");
3
+ const dataAnalysis=require("../dataAnalysis/dataAnalysis.js");
4
+ const helper=require("node-red-node-test-helper");
5
+ helper.init(require.resolve('node-red'));
6
+ const helperNodeResults={id :"helperNodeResults", type : "helper"}
7
+ const helperNodeDetails={id :"helperNodeDetails", type : "helper"}
8
+ const helperNodeOutliers={id :"helperNodeOutliers", type : "helper"}
9
+
10
+ const base = {
11
+ id : "n1",
12
+ type:"Data Analysis",
13
+ // action: "avg",
14
+ // outputs: {value:(["realtime","realtimePredict"].includes(this.action)?3:2),required:true},
15
+ // outliersBase: "sum",
16
+ // outliersStdDevs: 3,
17
+ // term:10,
18
+ // keyProperty:"msg.topic",
19
+ dataProperty: "msg.payload",
20
+ // dataProperties: ["msg.payload[0]","msg.payload[1]"],
21
+ wires : [[helperNodeResults.id],[helperNodeDetails.id],[helperNodeOutliers.id]]
22
+ } ;
23
+ function getNode(node) {
24
+ const n=helper.getNode(node.id);
25
+ if(n) return n;
26
+ throw Error("node id: "+node.id+" not found, node: "+JSON.stringify(node))
27
+ } ;
28
+ function test(label,action,data,expected) {
29
+ it(label+" "+action, function(done) {
30
+ if(data.length==0) {
31
+ done("no data")
32
+ return
33
+ }
34
+ let errors=[];
35
+ const newBase=Object.assign({},base,{action:action});
36
+ const flow=[helperNodeResults,helperNodeDetails,helperNodeOutliers,newBase];
37
+ let count=data.length;
38
+ helper.load(dataAnalysis,flow, function() {
39
+ try{
40
+ const resultsNode=getNode(helperNodeResults);
41
+ const detailsNode=getNode(helperNodeDetails);
42
+ const outliersNode=getNode(helperNodeOutliers);
43
+ const n1=getNode(newBase);
44
+ n1.should.have.property("action", action);
45
+ resultsNode.on("input", function(msg) {
46
+ try{
47
+ if(msg.error) throw Error(msg.error);
48
+ assert.strictEqual(msg.result,expected[msg._i]);
49
+ } catch(ex) {
50
+ console.log(JSON.stringify({label:"**** error ",test:label,error:ex.message,count:count,action:action,msg:msg,expected:expected[msg._i]}))
51
+ errors.push(ex.message)
52
+ }
53
+ if(--count) return;
54
+ if(errors.length) done(errors)
55
+ else done();
56
+ });
57
+ detailsNode.on("input", function(msg) {
58
+ console.log(JSON.stringify({label:"***** details ",action:action,msg:msg}))
59
+ });
60
+ outliersNode.on("input", function(msg) {
61
+ console.log(JSON.stringify({label:"***** outliers ",action:action,msg:msg}))
62
+ });
63
+ data.forEach((e,i)=>{
64
+ n1.receive({payload:e,_i :i});
65
+ })
66
+ } catch(ex) {
67
+ console.log(ex.stack)
68
+ done(ex);
69
+ }
70
+ });
71
+ }).timeout(4000);
72
+ }
73
+
74
+ describe('Data Anaysis', function() {
75
+ beforeEach(function(done) {
76
+ helper.startServer(done);
77
+ });
78
+ afterEach(function(done) {
79
+ helper.unload();
80
+ helper.stopServer(done);
81
+ });
82
+ const zeros=[undefined,[0],[0,0],[0,0,0]];
83
+ test("test zeros","sum",zeros,[undefined,0,0,0]);
84
+ test("test zeros","avg",zeros,[undefined,0,0,0]);
85
+ test("test zeros","max",zeros,[undefined,0,0,0]);
86
+ test("test zeros","min",zeros,[undefined,0,0,0]);
87
+ test("test zeros","stdDev",zeros,[undefined,0,0,0]);
88
+ const ones=[[1],[1,1],[1,1,1]];
89
+ test("test ones","sum",ones,[1,2,3]);
90
+ test("test ones","avg",ones,[1,1,1]);
91
+ test("test ones","max",ones,[1,1,1]);
92
+ test("test ones","min",ones,[1,1,1]);
93
+ test("test ones","stdDev",ones,[0,0,0]);
94
+ const minus=[[-1],[-1,1],[-1,1,-1]];
95
+ test("test minus","sum",minus,[-1,0,-1]);
96
+ test("test minus","avg",minus,[-1,0,-1/3]);
97
+ test("test minus","max",minus,[-1,1,1]);
98
+ test("test minus","min",minus,[-1,-1,-1]);
99
+ test("test minus","stdDev",minus,[0,1,0.9428090415820634]);
100
+
101
+ const twos=[[2],[2,2],[2,2,2],[2,2,2,2]];
102
+ test("test twos","sum",twos,[2,4,6,8]);
103
+ test("test twos","avg",twos,[2,2,2,2]);
104
+ test("test twos","stdDev",twos,[0,0,0,0]);
105
+
106
+ const c1234=[[1],[1,2],[1,2,3],[1,2,3,4]];
107
+ test("test c1234","sum",c1234,[1,3,6,10]);
108
+ test("test c1234","avg",c1234,[1,1.5,2,10/4]);
109
+ test("test c1234","stdDev",c1234,[0,0.5,0.8164965809277263,1.118033988749895]);
110
+ test("test c1234","sampleStdDev",c1234,[0,0.7071067811865476,1,1.2909944487358056]);
111
+ });
112
+
@@ -0,0 +1,99 @@
1
+ const assert=require('assert');
2
+ const should=require("should");
3
+ const ed=require("../dataAnalysis/euclideanDistance.js");
4
+ require("../dataAnalysis/forNestedEach");
5
+
6
+ describe('euclideanDistance', function() {
7
+ it("array forNestedEach", function(done) {
8
+ const atest=[1,2,3,4];
9
+ atest.forNestedEach((e,i,a,l)=>e="a",{0:[2]})
10
+ assert.deepEqual(atest,[ 1, 2, 3, 4 ])
11
+ atest.forNestedEach((e,i,a,l)=>a[i]="a",{0:[2]})
12
+ assert.deepEqual(atest,[ 1, 2, 'a', 4 ]);
13
+ const atest1=[[11,12],[21,22],[31,32],[41,42]];
14
+ atest1.forNestedEach((e,i,a,l)=>a[i]="a",{0:[0,4]})
15
+ assert.deepEqual(atest1 ,[ [ 'a', 'a' ], [ 21, 22 ], [ 31, 32 ], [ 41, 42 ] ])
16
+ done()
17
+ });
18
+ it("array pairs", function(done) {
19
+ assert.strictEqual([].pairs().length,0)
20
+ assert.strictEqual([1].pairs().length,0)
21
+ assert.deepEqual([1,2].pairs(null),[[1,2,0,1]])
22
+ assert.deepEqual([1,2,3].pairs(null),[[1,2,0,1],[1,3,0,2],[2,3,1,2]])
23
+ assert.deepEqual([1,2,3,4].pairs(null),[[1,2,0,1],[1,3,0,2],[1,4,0,3],[2,3,1,2],[2,4,1,3],[3,4,2,3]])
24
+ assert.deepEqual([1,2,3,4].pairs(),[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]])
25
+ assert.deepEqual([[1,2],[3,4]].pairs(),[[[1,2],[3,4]]])
26
+ done()
27
+ });
28
+ it("distance 1d", function(done) {
29
+ assert.strictEqual(ed.distance([],[]), NaN);
30
+ assert.strictEqual(ed.distance([0],[0]), 0);
31
+ assert.strictEqual(ed.distance([0],[1]), 1);
32
+ assert.strictEqual(ed.distance([0],[-1]), 1);
33
+ assert.strictEqual(ed.distance([1],[0]), 1);
34
+ assert.strictEqual(ed.distance([-1],[0]), 1);
35
+ assert.strictEqual(ed.distance([1],[2]), 1);
36
+ assert.strictEqual(ed.distance([2],[1]), 1);
37
+ assert.strictEqual(ed.distance([2],[4]), 2);
38
+ done()
39
+ });
40
+ it("distance 2d", function(done) {
41
+ assert.strictEqual(ed.distance([0,0],[0,0]) ,0);
42
+ assert.strictEqual(ed.distance([0,0],[1,0]) ,1);
43
+ assert.strictEqual(ed.distance([0,0],[0,1]) ,1);
44
+ assert.strictEqual(ed.distance([0,0],[1,1]) ,Math.sqrt(1+1));
45
+ assert.strictEqual(ed.distance([1,0],[0,0]) ,1);
46
+ assert.strictEqual(ed.distance([0,1],[0,0]) ,1);
47
+ assert.strictEqual(ed.distance([1,1],[0,0]) ,Math.sqrt(1+1));
48
+ assert.strictEqual(ed.distance([-1,1],[0,0]) ,Math.sqrt(1+1));
49
+ assert.strictEqual(ed.distance([-1,-1],[0,0]) ,Math.sqrt(1+1));
50
+ assert.strictEqual(ed.distance([1,1],[2,2]), Math.sqrt(1+1));
51
+ assert.strictEqual(ed.distance([0,1],[2,2]), Math.sqrt(2*2+1));
52
+ assert.strictEqual(ed.distance([0,0],[2,3]), Math.sqrt(2*2+3*3));
53
+ done()
54
+ });
55
+ it("distance 3d", function(done) {
56
+ assert.strictEqual(ed.distance([0,0],[0,0]), 0);
57
+ assert.strictEqual(ed.distance([1,1,1],[2,2,2]), Math.sqrt(1+1+1));
58
+ assert.strictEqual(ed.distance([1,1,2],[2,0,4]), Math.sqrt(1+1+2*2));
59
+ assert.strictEqual(ed.distance([0,-1,-2],[0,1,2]), Math.sqrt(0+2*2+4*4));
60
+ done()
61
+ });
62
+ it("distanceColumns 1d", function(done) {
63
+ assert.strictEqual(ed.distanceColumns(), 0);
64
+ assert.strictEqual(ed.distanceColumns([1,2,3],[1,2,3]), 0);
65
+ assert.strictEqual(ed.distanceColumns([1,2,3],[1,2,3],[]), 0);
66
+ assert.strictEqual(ed.distanceColumns([0,0,0],[1,2,3],[0]), 1);
67
+ assert.strictEqual(ed.distanceColumns([0,0,0],[1,2,3],[0,1]), Math.sqrt(1+2*2));
68
+ assert.strictEqual(ed.distanceColumns([0,0,0],[1,2,3],[0,1,2]), Math.sqrt(1+2*2+3*3));
69
+ done()
70
+ });
71
+ it("distances 1d", function(done) {
72
+ assert.deepEqual(ed.distances([[1],[2],[3],[4]]), [[1,0,1],[2,0,2],[3,0,3],[1,1,2],[2,1,3],[1,2,3]] );
73
+ assert.deepEqual(ed.distances([ [1,2,3,4], [11,12,13,14], [111,112,113,114] ]), [[20,0,1],[220,0,2],[200,1,2]]);
74
+ done()
75
+ });
76
+ it("distancesColumns 1d", function(done) {
77
+ assert.deepEqual(ed.distancesColumns([["d",1],["d",2],["d",3],["d",4]],[1]), [[1,0,1],[2,0,2],[3,0,3],[1,1,2],[2,1,3],[1,2,3]] );
78
+ assert.deepEqual(ed.distancesColumns([ ["d",1,2,3,4], ["d",11,12,13,14], ["d",111,112,113,114] ], [1,2,3,4]), [[20,0,1],[220,0,2],[200,1,2]]);
79
+ done()
80
+ });
81
+ it("minDistance 1d", function(done) {
82
+ assert.deepEqual(ed.minDistances([ [1], [2],[3],[4] ]), [{distance:1,points:[[1],[2]],index:[0,1]},{distance:1,points:[[2],[3]],index:[1,2]},{distance:1,points:[[3],[4]],index:[2,3]}]);
83
+ assert.deepEqual(ed.minDistances([ [4], [3],[2],[1] ]), [{distance:1,points:[[4],[3]],index:[0,1]},{distance:1,points:[[3],[2]],index:[1,2]},{distance:1,points:[[2],[1]],index:[2,3]}]);
84
+ done()
85
+ });
86
+ it("maxDistance 1d", function(done) {
87
+ assert.deepEqual(ed.maxDistances([ [1],[2],[3], [4]]), [{distance:3,points:[[1],[4]],index:[0,3]}]);
88
+ done()
89
+ });
90
+ it("maxDistance 2d", function(done) {
91
+ assert.deepEqual(ed.maxDistances([ [1,1],[2,2],[3,3],[4,5]]), [{distance:5,points:[[1,1],[4,5]],index:[0,3]}]);
92
+ done()
93
+ });
94
+ it("maxDistance Columns", function(done) {
95
+ assert.deepEqual(ed.maxDistances([ [1,1,"d"],[2,2,"d"],[3,3,"d"],[4,5,"d"]],[0,1]), [{distance:5,points:[[1,1,"d"],[4,5,"d"]],index:[0,3]}]);
96
+ done()
97
+ });
98
+
99
+ });
@@ -14,8 +14,8 @@
14
14
  },
15
15
  inputs:1,
16
16
  inputLabels: "",
17
- outputs:1,
18
- outputLabels: ["admin"],
17
+ outputs:2,
18
+ outputLabels: ["Messages","Finished"],
19
19
  icon: "inject.png",
20
20
  label: function() {
21
21
  return this.name||this._("Load Injector");
@@ -8,8 +8,9 @@ function thinkTimeTime() {
8
8
  }
9
9
  function nextMessageInjection() {
10
10
  if (this.runtimeTimer) {
11
+ this.count++;
11
12
  this.receive();
12
- var node=this;
13
+ const node=this;
13
14
  this.nextMessageInjectTimer=setTimeout(function(node){nextMessageInjection.apply(node);},thinkTimeTime.apply(node),node);
14
15
  }
15
16
  }
@@ -22,11 +23,15 @@ function runtimeStop() {
22
23
  clearTimeout(this.nextMessageInjectTimer);
23
24
  this.nextMessageInjectTimer=null;
24
25
  }
26
+ this.stopped=Date.now();
27
+ this.send([null,{payload:{count:this.count,started:this.started,stopped:this.stopped}}])
25
28
  this.status({fill:"red",shape:"ring",text:"Stopped"});
26
29
  this.error("Stopped injector");
27
30
  }
28
31
  function runtimeStart() {
29
- var node=this;
32
+ const node=this;
33
+ this.started=Date.now();
34
+ this.count=0;
30
35
  this.runtimeTimer=true;
31
36
  this.runtimeTimer=setTimeout(function(){runtimeStop.apply(node);},this.runtime*1000);
32
37
  this.status({fill:"green",shape:"ring",text:"Started"});
@@ -37,7 +42,7 @@ function runtimeStart() {
37
42
  module.exports = function (RED) {
38
43
  function LoadInjectorNode(n) {
39
44
  RED.nodes.createNode(this, n);
40
- var node=Object.assign(this,n);
45
+ const node=Object.assign(this,n);
41
46
  this.thinktimemin=Number(this.thinktimemin);
42
47
  this.thinktimemax=Number(this.thinktimemax);
43
48
  if(this.thinktimemax<this.thinktimemin) {
@@ -96,7 +101,7 @@ module.exports = function (RED) {
96
101
 
97
102
  // RED.httpAdmin.post("/loadinjector/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
98
103
  RED.httpAdmin.get("/loadinjector/:id", function(req,res) {
99
- var node = RED.nodes.getNode(req.params.id);
104
+ const node = RED.nodes.getNode(req.params.id);
100
105
  if (node && node.type==="Load Injector") {
101
106
  try {
102
107
  node.warn("Request to "+(node.runtimeTimer?"stop":"start")+" injector");
package/testing/test.html CHANGED
@@ -6,9 +6,13 @@
6
6
  Can select location of property for result on return message.
7
7
  For json test positive infinity by property value "Infinity",negative infinity by "-Infinity" and not a number by "NaN".
8
8
  </p>
9
+ Error factor allows for numbers to be out by being less than factor abs(expected-result)/expected
9
10
  <p>
10
11
  A message sent on first port should go thru other nodes and return to test is payload
11
12
  is has the expected results. Several test can be arranged.
13
+ </p>
14
+ <p>
15
+
12
16
  </p>
13
17
  <h3>Inputs</h3>
14
18
  If message genrated by this node, then payload checked against expected result
@@ -37,6 +41,17 @@
37
41
  </label>
38
42
  </div>
39
43
 
44
+ <div class="form-row form-row-http-in-errorFactor show">
45
+ <label for="node-input-errorFactor" style="white-space: nowrap"><i class="icon-bookmark"></i> Error factor</label>
46
+ <input type="number" id="node-input-errorFactor" list="defaultErrorFactors" >
47
+ <datalist id="defaultErrorFactors">
48
+ <option value="0">
49
+ <option value="0.001">
50
+ <option value="0.000001">
51
+ <option value="0.000000001">
52
+ </datalist>
53
+ </div>
54
+
40
55
  <div class="form-row form-row-http-in-resultProperty show">
41
56
  <label for="node-input-resultProperty" style="white-space: nowrap"><i class="icon-bookmark"></i> Property </label>
42
57
  <input type="text" id="node-input-resultProperty" placeholder="msg.payload">
@@ -61,6 +76,7 @@
61
76
  color:"#a6bbcf",
62
77
  defaults: {
63
78
  name: {value:""},
79
+ errorFactor: {value:0,required:false},
64
80
  escapeString: {value:false,required:false},
65
81
  infiniteIsNull: {value:false,required:false},
66
82
  payload: {value:"", validate: RED.validators.typedInput("payloadType")},
package/testing/test.js CHANGED
@@ -29,17 +29,19 @@ function setError(msg,node,err) {
29
29
  node.send([null,msg]);
30
30
  }
31
31
 
32
- function equalObjects(obj1,obj2) {
32
+ function equalObjects(obj1,obj2,errorFactor) {
33
33
  if( obj1 === obj2 ) return true;
34
34
  if( obj1 === Number.POSITIVE_INFINITY && obj2==="Infinity") return true;
35
35
  if( obj1 === Number.NEGATIVE_INFINITY && obj2==="-Infinity") return true;
36
36
  if( Number.isNaN(obj1) && obj2==="NaN") return true;
37
- if( typeof obj1 != typeof obj2 ) return false;
37
+ const obj1type=typeof obj1;
38
+ if( obj1type != typeof obj2 ) return false;
39
+ if(errorFactor && obj1type=="number") return (Math.abs(obj2-obj1)/obj2)<errorFactor;
38
40
  if( !(obj1 instanceof Object) ) return false;
39
41
  if( Object.keys(obj1).length !== Object.keys(obj2).length ) return false;
40
42
  try{
41
43
  for(let key in obj1) {
42
- if( !equalObjects(obj1[key],obj2[key]) ) return false;
44
+ if( !equalObjects(obj1[key],obj2[key],errorFactor) ) return false;
43
45
  }
44
46
  } catch(e) {
45
47
  return false;
@@ -69,7 +71,7 @@ module.exports = function(RED) {
69
71
  try{
70
72
  if(msg._test.id!==node.id) {
71
73
  setError(msg,node,"Sent by another test "+msg._test.id);
72
- } else if(!equalObjects(node.getData(msg,node),msg._test.result)) {
74
+ } else if(!equalObjects(node.getData(msg,node),msg._test.result,node.errorFactor)) {
73
75
  msg._test.testedValue=node.getData(msg,node);
74
76
  setError(msg,node,"Test failed");
75
77
  } else {
@@ -0,0 +1,237 @@
1
+ const throwError=(msg,p)=>{throw Object.assign(new Error(msg),p)}
2
+ const Uint8ArrayFromBase64=(b64)=>new Uint8Array(Buffer.from(b64, "base64"))
3
+ const base64FromUint8Array=(a)=>Buffer.toString(a, "base64")
4
+ const hexFromArray=(a)=>Buffer.from(a).toString('hex')
5
+ const UInt8ToBinary=(a)=> {
6
+ const buf = Buffer.allocUnsafe(a.length);
7
+ a.forEach((c,i)=>buf.writeUInt8(c,i))
8
+ return buf
9
+ };
10
+
11
+ const test={
12
+ PrintableString:(s)=>{if(s.test(/^[a-z0-9 '()+,-./:=?]*$/i)) return; throw Error("invalid PrintableString")}
13
+ }
14
+
15
+ function Classes() {}
16
+ Classes.prototype.UNIVERSAL=0b00000000;
17
+ Classes.prototype.APPLICATION=0b01000000;
18
+ Classes.prototype.CONTEXTSPECIFIC=0b10000000;
19
+ Classes.prototype.PRIVATE=0b11000000;
20
+ Classes.prototype.values={1:Classes.prototype.UNIVERSAL,2:Classes.prototype.APPLICATION,3:Classes.prototype.CONTEXTSPECIFIC,4:Classes.prototype.PRIVATE};
21
+ Classes.prototype.names={1:"UNIVERSAL",2:"APPLICATION",3:"CONTEXT-SPECIFIC",4:"PRIVATE"};
22
+ Classes.prototype.setOctet=(octet,tag)=>octet|=(Classes.prototype.values[tag]);
23
+ Classes.prototype.setOctetName=(octet,tagName)=>octet|=(Classes.prototype[tagName]);
24
+ Classes.prototype.getOctetValue=(octet)=>octet>>6+1;
25
+ //Classes.prototype.getOctetTag=(octet)=>octet|=Classes.prototype[tag];
26
+ //Classes.prototype.toString=(octet)=>classes.names[Classes.getOctetTag(octet)];
27
+ const classes=new Classes()
28
+
29
+ function PC() {}
30
+ PC.prototype.P=0b0000;
31
+ PC.prototype.Primitive=0b00000000;
32
+ PC.prototype.C=0b00100000;
33
+ PC.prototype.Constructed =0b00100000;
34
+ PC.prototype.isPrimitive =(octet)=>octet&0b00000000;
35
+ PC.prototype.isConstructed =(octet)=>octet&0b00100000;
36
+ PC.prototype.setOctet=(octet)=>octet|=(classes[tag]);
37
+ PC.prototype.toString=(octet)=>pc.isPrimitive(octet)?"Primitive":"Constructed";
38
+ PC.prototype.set=(octet,action)=>octet=(octet^0b00100000)&pc[action];
39
+ const pc=new PC();
40
+
41
+ function Octet1(v) {
42
+ this.value=v
43
+ }
44
+ Octet1.prototype.getClass=()=>Classes.getOctetValue(this.value);
45
+ Octet1.prototype.getTag=()=>this.value&0b00011111;
46
+ Octet1.prototype.isLongForm=()=>(this.value^0b10000000) & (this.value^0b01111111);
47
+ Octet1.prototype.toJSON=()=>{return {"class":this.getClass(this.value), tag:this.getTag(this.value), longForm:this.isLongForm(this.value)};};
48
+ Octet1.prototype.toString=function(){const r=this.toJSON();r.className=classes.names(r.classID) ;return JSON.stringify(r)};
49
+ Octet1.prototype.isConstructed=()=>pc.isConstructed(this.value);
50
+
51
+ function IdentifierOctets(dataArray,i){
52
+ if(dataArray) this.decode(dataArray,i)
53
+ }
54
+ IdentifierOctets.prototype.decode=function (dataArray,i){
55
+ this.tags=[];
56
+ if(!dataArray.length) return;
57
+ const octet1=new Octet1(dataArray[i++]);
58
+ this.tagClass=octet1.getClass();
59
+ this.isConstructed=octet1.isConstructed();
60
+ if(octet1.isLongForm()) //isLongForm
61
+ this.tags.push(octet1.getTag());
62
+ else {
63
+ let more=true
64
+ while (more) {
65
+ const octet=dataArray[i++];
66
+ const tag=octet&0b01111111;
67
+ if(tag>0x24) throw Error("tag = 0x00")
68
+ if(tag>0x24) throw Error("tag > 0x24")
69
+ this.tags.push(tag);
70
+ more=octet&0b1000000;;
71
+ }
72
+ }
73
+ };
74
+ IdentifierOctets.prototype.toJSON=function (){
75
+ return {"class":this.tagClass,constructed:this.isConstructed,tags:this.tags}
76
+ }
77
+ IdentifierOctets.prototype.toJSONExtended=function (){
78
+ const r=this.toJSON();
79
+ r.tags=r.tags.map(c=>({id:c,name:tags[c].name}));
80
+ return r
81
+ };
82
+ IdentifierOctets.prototype.toString==function (){
83
+ return JSON.stringify(this.toJSONExtended())
84
+ };
85
+
86
+ //Basic Encoding Rules (BER)
87
+ function BERencoding(dataArray,i=0) {
88
+ //Identifier octets Type i++
89
+ // if(!(dataArray instanceof Uint8Array) ) throw Error("data not Uint8Array but "+dataArray.constructor.name)
90
+ this.decodeBER(dataArray,i);
91
+ }
92
+ BERencoding.prototype.encode=function(v){
93
+ for(const p in v) {
94
+ switch (p) {
95
+ case "Identifier":
96
+ this.setIdentifier(v[p])
97
+ next;
98
+ default:
99
+ throw Error("unknown base property "+p)
100
+ }
101
+ }
102
+ }
103
+ BERencoding.prototype.setIdentifier=function(v){
104
+ for(const p in v) {
105
+ switch (p) {
106
+ case "class":
107
+ this.tagClass=v[p]
108
+ next;
109
+ case "tag":
110
+ this.tags=[v[p]]
111
+ next;
112
+ case "constructed":
113
+ this.isConstructed=v[p]
114
+ next;
115
+ default:
116
+ throw Error("unknown identifier property "+p)
117
+ }
118
+ }
119
+ }
120
+ BERencoding.prototype.setSchema=function(schema){
121
+ }
122
+
123
+ BERencoding.prototype.decodeBER=function(dataArray,i){
124
+ if(dataArray.constructor.name!=="Uint8Array") throw Error("data not Uint8Array but "+dataArray.constructor.name)
125
+ this.identifier=new IdentifierOctets(dataArray,i);
126
+ if(dataArray[i]===0xFF) { //isIndefinite
127
+ const eocIndex=dataArray.indexOf(0x00,i)
128
+ this.data=dataArray.slice(i,eocIndex-1);
129
+ i=eocIndex+1;
130
+ } else {
131
+ const endPosition=i+LengthOctet.prototype.getLength(dataArray,i);
132
+ this.data=dataArray.slice(i,endPosition);
133
+ i=endPosition+1;
134
+ }
135
+ }
136
+ BERencoding.prototype.decode=BERencoding.prototype.decodeBER;
137
+ BERencoding.prototype.toJSON= function(){
138
+ return {identifier:this.identifier.toJSONExtended(),data:this.data,dataHex:this.data2Hex()};
139
+ }
140
+ BERencoding.prototype.toString= function(){
141
+ return JSON.stringify(this.toJSON());
142
+ }
143
+ BERencoding.prototype.data2Hex= function(){
144
+ const r=[];
145
+ this.data.forEach(c=>r.push("0x"+c.toString(16)))
146
+ return r;
147
+ }
148
+ const constructPrimitive=null;
149
+ const constructConstructed=false
150
+ const constructBoth=true;
151
+ const constructReserved=undefined;
152
+ const PrintableString2Value=(a,i,l)=>{
153
+ const s=i;
154
+ i=i+l;
155
+ return a.slice(s,i)
156
+ };
157
+ const Value2PrintableString=(v)=>{
158
+ test.PrintableString(v);
159
+ return v;
160
+ }
161
+
162
+ const tags=[
163
+ {name:"End-of-Content (EOC)",permittedConstruction:constructPrimitive},
164
+ {name:"BOOLEAN",permittedConstruction:constructPrimitive,translatedTo:(v)=>v?0x01:0x00,translatedFrom:(a,i)=>a[i++]==0x01},
165
+ {name:"INTEGER",permittedConstruction:constructPrimitive},
166
+ {name:"BIT STRING",permittedConstruction:constructBoth},
167
+ {name:"OCTET STRING",permittedConstruction:constructBoth},
168
+ {name:"NULL",permittedConstruction:constructPrimitive},
169
+ {name:"OBJECT IDENTIFIER",permittedConstruction:constructPrimitive},
170
+ {name:"Object Descriptor",permittedConstruction:constructBoth},
171
+ {name:"EXTERNAL",permittedConstruction:constructConstructed},
172
+ {name:"REAL (float)",permittedConstruction:constructPrimitive},
173
+ {name:"ENUMERATED",permittedConstruction:constructPrimitive},
174
+ {name:"EMBEDDED PDV",permittedConstruction:constructConstructed},
175
+ {name:"UTF8String",permittedConstruction:constructBoth},
176
+ {name:"RELATIVE-OID",permittedConstruction:constructPrimitive},
177
+ {name:"TIME",permittedConstruction:constructPrimitive},
178
+ {name:"Reserved",permittedConstruction:constructReserved},
179
+ {name:"SEQUENCE and SEQUENCE OF",permittedConstruction:constructConstructed},
180
+ {name:"SET and SET OF",permittedConstruction:constructConstructed},
181
+ {name:"NumericString",permittedConstruction:constructBoth},
182
+ {name:"PrintableString",permittedConstruction:constructBoth,translatedTo:Value2PrintableString,translatedFrom:PrintableString2Value},
183
+ {name:"T61String",permittedConstruction:constructBoth},
184
+ {name:"VideotexString",permittedConstruction:constructBoth},
185
+ {name:"IA5String",permittedConstruction:constructBoth},
186
+ {name:"UTCTime",permittedConstruction:constructBoth},
187
+ {name:"GeneralizedTime",permittedConstruction:constructBoth},
188
+ {name:"GraphicString",permittedConstruction:constructBoth},
189
+ {name:"VisibleString",permittedConstruction:constructBoth},
190
+ {name:"GeneralString",permittedConstruction:constructBoth},
191
+ {name:"UniversalString",permittedConstruction:constructBoth},
192
+ {name:"CHARACTER STRING",permittedConstruction:constructConstructed},
193
+ {name:"BMPString",permittedConstruction:constructBoth},
194
+ {name:"DATE",permittedConstruction:constructPrimitive},
195
+ {name:"TIME-OF-DAY",permittedConstruction:constructPrimitive},
196
+ {name:"DATE-TIME",permittedConstruction:constructPrimitive},
197
+ {name:"DURATION",permittedConstruction:constructPrimitive},
198
+ {name:"OID-IRI",permittedConstruction:constructPrimitive},
199
+ {name:"RELATIVE-OID-IRI",permittedConstruction:constructPrimitive},
200
+ ];
201
+
202
+ function LengthOctet() {}
203
+ LengthOctet.prototype.definiteShort=0x00;
204
+ LengthOctet.prototype.definiteLong=0xC0;
205
+ LengthOctet.prototype.indefinite= 0xC0;
206
+ LengthOctet.prototype.reserved=0xFF;
207
+ LengthOctet.prototype.isIndefinite= (o)=>o&0xC0;
208
+ LengthOctet.prototype.octets=(o)=>o^0b01111111;
209
+ LengthOctet.prototype.getLength=(octets,i)=>{
210
+ const octet=octets[i++];
211
+ if(octet===0xC0) return undefined;
212
+ if(octet&0xC0) return octet; //isDefiniteShort
213
+ if(octet===0xFF) return NaN; //isReserved
214
+ let l=octet&0b01111111; // length
215
+ let length=octets[i++];
216
+ while (l--) length=length*256+octets[i++]
217
+ if(length < 0) throw new Error('Negative length: ' + length);
218
+ return length;
219
+ }
220
+
221
+ // Canonical Encoding Rules (CER)
222
+ function CERencoding(dataArray,i=0) {
223
+ throw Error("to be done")
224
+ }
225
+ //Distinguished Encoding Rules (DER)
226
+ function DERencoding(dataArray,i=0) {
227
+ throw Error("to be done")
228
+ }
229
+
230
+ module.exports={
231
+ base64:Uint8ArrayFromBase64,
232
+ hex:hexFromArray,
233
+ decode:(dataArray,i)=>new BERencoding(dataArray,i),
234
+ decodeBER:(dataArray,i)=>new BERencoding(dataArray,i),
235
+ decodeBase64:(v)=>BERencoding(arrayFromBase46(v)),
236
+ encode:BERencoding
237
+ }