Compare commits
	
		
			No commits in common. "6e05a0b45c2f28a74becb928d78169112d71fbb7" and "2a0c1e5a3b1cbb7bc8fe110b53fe5661fad63b4a" have entirely different histories.
		
	
	
		
			6e05a0b45c
			...
			2a0c1e5a3b
		
	
		
| @ -1,3 +0,0 @@ | |||||||
| node_modules |  | ||||||
| npm-debug.log |  | ||||||
| .* |  | ||||||
							
								
								
									
										17
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,17 +0,0 @@ | |||||||
| FROM node:16 |  | ||||||
| 
 |  | ||||||
| # Create app directory |  | ||||||
| WORKDIR /usr/src/app |  | ||||||
| 
 |  | ||||||
| # Install app dependencies |  | ||||||
| COPY package*.json ./ |  | ||||||
| RUN npm install |  | ||||||
| 
 |  | ||||||
| RUN apt-get update |  | ||||||
| RUN apt-get -y install \ |  | ||||||
|     tcpdump |  | ||||||
| 
 |  | ||||||
| # Bundle app source |  | ||||||
| COPY ./src/ . |  | ||||||
| 
 |  | ||||||
| CMD ["npm", "run", "start"] |  | ||||||
							
								
								
									
										266
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										266
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,266 +0,0 @@ | |||||||
| { |  | ||||||
|   "name": "rfmon-to-influx", |  | ||||||
|   "version": "1.0.0", |  | ||||||
|   "lockfileVersion": 2, |  | ||||||
|   "requires": true, |  | ||||||
|   "packages": { |  | ||||||
|     "": { |  | ||||||
|       "name": "rfmon-to-influx", |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "license": "AGPL-3.0", |  | ||||||
|       "dependencies": { |  | ||||||
|         "@influxdata/influxdb-client": "^1.20.0", |  | ||||||
|         "@influxdata/influxdb-client-apis": "^1.20.0", |  | ||||||
|         "log4js": "^6.3.0", |  | ||||||
|         "luxon": "^2.1.1", |  | ||||||
|         "string-argv": "^0.3.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/@influxdata/influxdb-client": { |  | ||||||
|       "version": "1.20.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client/-/influxdb-client-1.20.0.tgz", |  | ||||||
|       "integrity": "sha512-jaKSI63hmQ5VSkJrFJkYIXaKlhoF+mGd4HmOf7v/X7pmEi69ReHp922Wyx6/OeCrpndRMbsadk+XmGNdd43cFw==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/@influxdata/influxdb-client-apis": { |  | ||||||
|       "version": "1.20.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client-apis/-/influxdb-client-apis-1.20.0.tgz", |  | ||||||
|       "integrity": "sha512-KMTmXH4rbpS+NWGpqDjxcKTyan2rbiT2IM5AdRElKhH2sHbH96xwLgziaxeC+OCJLeNAdehJgae3I8WiZjbwdg==", |  | ||||||
|       "peerDependencies": { |  | ||||||
|         "@influxdata/influxdb-client": "*" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/date-format": { |  | ||||||
|       "version": "3.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", |  | ||||||
|       "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=4.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/debug": { |  | ||||||
|       "version": "4.3.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", |  | ||||||
|       "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", |  | ||||||
|       "dependencies": { |  | ||||||
|         "ms": "2.1.2" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=6.0" |  | ||||||
|       }, |  | ||||||
|       "peerDependenciesMeta": { |  | ||||||
|         "supports-color": { |  | ||||||
|           "optional": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/flatted": { |  | ||||||
|       "version": "2.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", |  | ||||||
|       "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/fs-extra": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", |  | ||||||
|       "dependencies": { |  | ||||||
|         "graceful-fs": "^4.2.0", |  | ||||||
|         "jsonfile": "^4.0.0", |  | ||||||
|         "universalify": "^0.1.0" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=6 <7 || >=8" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/graceful-fs": { |  | ||||||
|       "version": "4.2.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", |  | ||||||
|       "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/jsonfile": { |  | ||||||
|       "version": "4.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", |  | ||||||
|       "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", |  | ||||||
|       "optionalDependencies": { |  | ||||||
|         "graceful-fs": "^4.1.6" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/log4js": { |  | ||||||
|       "version": "6.3.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", |  | ||||||
|       "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", |  | ||||||
|       "dependencies": { |  | ||||||
|         "date-format": "^3.0.0", |  | ||||||
|         "debug": "^4.1.1", |  | ||||||
|         "flatted": "^2.0.1", |  | ||||||
|         "rfdc": "^1.1.4", |  | ||||||
|         "streamroller": "^2.2.4" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=8.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/luxon": { |  | ||||||
|       "version": "2.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.1.1.tgz", |  | ||||||
|       "integrity": "sha512-6VQVNw7+kQu3hL1ZH5GyOhnk8uZm21xS7XJ/6vDZaFNcb62dpFDKcH8TI5NkoZOdMRxr7af7aYGrJlE/Wv0i1w==", |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=12" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/ms": { |  | ||||||
|       "version": "2.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |  | ||||||
|       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/rfdc": { |  | ||||||
|       "version": "1.3.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", |  | ||||||
|       "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/streamroller": { |  | ||||||
|       "version": "2.2.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", |  | ||||||
|       "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", |  | ||||||
|       "dependencies": { |  | ||||||
|         "date-format": "^2.1.0", |  | ||||||
|         "debug": "^4.1.1", |  | ||||||
|         "fs-extra": "^8.1.0" |  | ||||||
|       }, |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=8.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/streamroller/node_modules/date-format": { |  | ||||||
|       "version": "2.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", |  | ||||||
|       "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=4.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/string-argv": { |  | ||||||
|       "version": "0.3.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", |  | ||||||
|       "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">=0.6.19" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node_modules/universalify": { |  | ||||||
|       "version": "0.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", |  | ||||||
|       "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", |  | ||||||
|       "engines": { |  | ||||||
|         "node": ">= 4.0.0" |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   "dependencies": { |  | ||||||
|     "@influxdata/influxdb-client": { |  | ||||||
|       "version": "1.20.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client/-/influxdb-client-1.20.0.tgz", |  | ||||||
|       "integrity": "sha512-jaKSI63hmQ5VSkJrFJkYIXaKlhoF+mGd4HmOf7v/X7pmEi69ReHp922Wyx6/OeCrpndRMbsadk+XmGNdd43cFw==" |  | ||||||
|     }, |  | ||||||
|     "@influxdata/influxdb-client-apis": { |  | ||||||
|       "version": "1.20.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/@influxdata/influxdb-client-apis/-/influxdb-client-apis-1.20.0.tgz", |  | ||||||
|       "integrity": "sha512-KMTmXH4rbpS+NWGpqDjxcKTyan2rbiT2IM5AdRElKhH2sHbH96xwLgziaxeC+OCJLeNAdehJgae3I8WiZjbwdg==", |  | ||||||
|       "requires": {} |  | ||||||
|     }, |  | ||||||
|     "date-format": { |  | ||||||
|       "version": "3.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", |  | ||||||
|       "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==" |  | ||||||
|     }, |  | ||||||
|     "debug": { |  | ||||||
|       "version": "4.3.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", |  | ||||||
|       "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", |  | ||||||
|       "requires": { |  | ||||||
|         "ms": "2.1.2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "flatted": { |  | ||||||
|       "version": "2.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", |  | ||||||
|       "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==" |  | ||||||
|     }, |  | ||||||
|     "fs-extra": { |  | ||||||
|       "version": "8.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", |  | ||||||
|       "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", |  | ||||||
|       "requires": { |  | ||||||
|         "graceful-fs": "^4.2.0", |  | ||||||
|         "jsonfile": "^4.0.0", |  | ||||||
|         "universalify": "^0.1.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "graceful-fs": { |  | ||||||
|       "version": "4.2.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", |  | ||||||
|       "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" |  | ||||||
|     }, |  | ||||||
|     "jsonfile": { |  | ||||||
|       "version": "4.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", |  | ||||||
|       "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", |  | ||||||
|       "requires": { |  | ||||||
|         "graceful-fs": "^4.1.6" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "log4js": { |  | ||||||
|       "version": "6.3.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", |  | ||||||
|       "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", |  | ||||||
|       "requires": { |  | ||||||
|         "date-format": "^3.0.0", |  | ||||||
|         "debug": "^4.1.1", |  | ||||||
|         "flatted": "^2.0.1", |  | ||||||
|         "rfdc": "^1.1.4", |  | ||||||
|         "streamroller": "^2.2.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "luxon": { |  | ||||||
|       "version": "2.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.1.1.tgz", |  | ||||||
|       "integrity": "sha512-6VQVNw7+kQu3hL1ZH5GyOhnk8uZm21xS7XJ/6vDZaFNcb62dpFDKcH8TI5NkoZOdMRxr7af7aYGrJlE/Wv0i1w==" |  | ||||||
|     }, |  | ||||||
|     "ms": { |  | ||||||
|       "version": "2.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |  | ||||||
|       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |  | ||||||
|     }, |  | ||||||
|     "rfdc": { |  | ||||||
|       "version": "1.3.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", |  | ||||||
|       "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" |  | ||||||
|     }, |  | ||||||
|     "streamroller": { |  | ||||||
|       "version": "2.2.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", |  | ||||||
|       "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", |  | ||||||
|       "requires": { |  | ||||||
|         "date-format": "^2.1.0", |  | ||||||
|         "debug": "^4.1.1", |  | ||||||
|         "fs-extra": "^8.1.0" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "date-format": { |  | ||||||
|           "version": "2.1.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", |  | ||||||
|           "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==" |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "string-argv": { |  | ||||||
|       "version": "0.3.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", |  | ||||||
|       "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==" |  | ||||||
|     }, |  | ||||||
|     "universalify": { |  | ||||||
|       "version": "0.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", |  | ||||||
|       "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										23
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								package.json
									
									
									
									
									
								
							| @ -1,23 +0,0 @@ | |||||||
| { |  | ||||||
|   "name": "rfmon-to-influx", |  | ||||||
|   "version": "1.0.0", |  | ||||||
|   "description": "Writing (mostly meta-) data received in Wireless-Monitor-Mode into an InfluxDB", |  | ||||||
|   "main": "main.js", |  | ||||||
|   "scripts": { |  | ||||||
|     "test": "echo \"Error: no test specified\" && exit 1", |  | ||||||
|     "start": "node main.js" |  | ||||||
|   }, |  | ||||||
|   "repository": { |  | ||||||
|     "type": "git", |  | ||||||
|     "url": "https://gitea.ruekov.eu/Ruakij/rfmon-to-influx" |  | ||||||
|   }, |  | ||||||
|   "author": "Ruakij", |  | ||||||
|   "license": "AGPL-3.0", |  | ||||||
|   "dependencies": { |  | ||||||
|     "@influxdata/influxdb-client": "^1.20.0", |  | ||||||
|     "@influxdata/influxdb-client-apis": "^1.20.0", |  | ||||||
|     "log4js": "^6.3.0", |  | ||||||
|     "luxon": "^2.1.1", |  | ||||||
|     "string-argv": "^0.3.1" |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,105 +0,0 @@ | |||||||
| const PacketType = { |  | ||||||
|     Beacon: 'Beacon', |  | ||||||
|     ProbeRequest: 'ProbeRequest', |  | ||||||
|     ProbeResponse: 'ProbeResponse', |  | ||||||
|     Data: 'Data', |  | ||||||
|     RequestToSend: 'RequestToSend', |  | ||||||
|     ClearToSend: 'ClearToSend', |  | ||||||
|     Acknowledgment: 'Acknowledgment', |  | ||||||
|     BlockAcknowledgment: 'BlockAcknowledgment', |  | ||||||
|     NoData: 'NoData', |  | ||||||
|     Authentication: 'Authentication', |  | ||||||
|     AssociationRequest: 'AssociationRequest', |  | ||||||
|     AssociationResponse: 'AssociationResponse', |  | ||||||
|     Disassociation: 'Disassociation', |  | ||||||
|     Handshake: 'Handshake', |  | ||||||
|     Unknown: 'Unknown' |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const FlagType = { |  | ||||||
|     MoreFragments: "MoreFragments", |  | ||||||
|     Retry: "Retry", |  | ||||||
|     PwrMgt: "PwrMgt", |  | ||||||
|     MoreData: "MoreData", |  | ||||||
|     Protected: "Protected", |  | ||||||
|     Order: "Order" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class Packet{ |  | ||||||
|     timestampMicros; |  | ||||||
| 
 |  | ||||||
|     flags = {}; |  | ||||||
| 
 |  | ||||||
|     srcMac; |  | ||||||
|     dstMac; |  | ||||||
|     bssid; |  | ||||||
| 
 |  | ||||||
|     signal; |  | ||||||
|     frequency; |  | ||||||
|     dataRate; |  | ||||||
| 
 |  | ||||||
|     durationMicros; |  | ||||||
| 
 |  | ||||||
|     payloadData; |  | ||||||
|     get payloadSize(){ |  | ||||||
|         return this.payloadData.length; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     packetType; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Extensions of Packet
 |  | ||||||
| class PacketWithSSID extends Packet{ |  | ||||||
|     ssid; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class BeaconPacket extends PacketWithSSID{} |  | ||||||
| class ProbeRequestPacket extends PacketWithSSID{} |  | ||||||
| class ProbeResponsePacket extends PacketWithSSID{} |  | ||||||
| 
 |  | ||||||
| const AuthenticationType = { |  | ||||||
|     OpenSystem_1: 'OpenSystem_1', |  | ||||||
|     OpenSystem_2: 'OpenSystem_2', |  | ||||||
|     Unknown: 'Unknown', |  | ||||||
| } |  | ||||||
| class AuthenticationPacket extends Packet{ |  | ||||||
|     authenticationType; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class AssociationRequestPacket extends PacketWithSSID{} |  | ||||||
| class AssociationResponsePacket extends Packet{ |  | ||||||
|     associationIsSuccessful; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class DisassociationPacket extends Packet{ |  | ||||||
|     disassociationReason; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const HandshakeStage = { |  | ||||||
|     1: '1', |  | ||||||
|     2: '2', |  | ||||||
|     3: '3', |  | ||||||
|     4: '4' |  | ||||||
| } |  | ||||||
| class HandshakePacket extends Packet{ |  | ||||||
|     handshakeStage; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     PacketType, |  | ||||||
|     FlagType, |  | ||||||
|     Packet, |  | ||||||
|     PacketWithSSID, |  | ||||||
|     BeaconPacket, |  | ||||||
|     ProbeRequestPacket, |  | ||||||
|     ProbeResponsePacket, |  | ||||||
|     AuthenticationType, |  | ||||||
|     AuthenticationPacket, |  | ||||||
|     AssociationRequestPacket, |  | ||||||
|     AssociationResponsePacket, |  | ||||||
|     DisassociationPacket, |  | ||||||
|     HandshakeStage, |  | ||||||
|     HandshakePacket, |  | ||||||
| }; |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| function requireEnvVars(requiredEnv){ |  | ||||||
|     // Ensure required ENV vars are set
 |  | ||||||
|     let unsetEnv = requiredEnv.filter((env) => !(typeof process.env[env] !== 'undefined')); |  | ||||||
| 
 |  | ||||||
|     if (unsetEnv.length > 0) { |  | ||||||
|         return "Required ENV variables are not set: [" + unsetEnv.join(', ') + "]"; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     requireEnvVars |  | ||||||
| }; |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| const logger = require("./logger.js")("exec"); |  | ||||||
| 
 |  | ||||||
| const { spawn } = require("child_process"); |  | ||||||
| const { parseArgsStringToArgv } = require('string-argv'); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function exec(cmd, options){ |  | ||||||
|     const [bin, ...args] = parseArgsStringToArgv(cmd); |  | ||||||
| 
 |  | ||||||
|     logger.addContext("binary", "bin"); |  | ||||||
|     logger.debug(`Spawn process '${cmd}'`); |  | ||||||
|     return spawn(bin, args, options); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     exec |  | ||||||
| }; |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| // From https://stackoverflow.com/a/34356351
 |  | ||||||
| 
 |  | ||||||
| // Convert a hex string to a byte array
 |  | ||||||
| function hexToBytes(hex) { |  | ||||||
|     for (var bytes = [], c = 0; c < hex.length; c += 2) |  | ||||||
|         bytes.push(parseInt(hex.substr(c, 2), 16)); |  | ||||||
|     return bytes; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Convert a byte array to a hex string
 |  | ||||||
| function bytesToHex(bytes) { |  | ||||||
|     for (var hex = [], i = 0; i < bytes.length; i++) { |  | ||||||
|         var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i]; |  | ||||||
|         hex.push((current >>> 4).toString(16)); |  | ||||||
|         hex.push((current & 0xF).toString(16)); |  | ||||||
|     } |  | ||||||
|     return hex.join(""); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     hexToBytes, |  | ||||||
|     bytesToHex |  | ||||||
| } |  | ||||||
| @ -1,61 +0,0 @@ | |||||||
| const logger = require.main.require("./helper/logger.js")("influx-checks"); |  | ||||||
| 
 |  | ||||||
| const Os = require("os"); |  | ||||||
| const { InfluxDB, Point } = require('@influxdata/influxdb-client') |  | ||||||
| const Influx = require('@influxdata/influxdb-client-apis'); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function checkHealth(influxDb){ |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|         new Influx.HealthAPI(influxDb)    // Check influx health
 |  | ||||||
|             .getHealth() |  | ||||||
|             .catch((err) => { |  | ||||||
|                 logger.error("Could not communicate with Influx:"); |  | ||||||
|                 logger.error(`Error [${err.code}]:`, err.message); |  | ||||||
|                 reject(); |  | ||||||
|             }) |  | ||||||
|             .then((res) => { |  | ||||||
|                 logger.debug("Server healthy.", "Version: ", res.version); |  | ||||||
|                 resolve(res); |  | ||||||
|             }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function checkBucket(influxDb, options){ |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|         new Influx.BucketsAPI(influxDb).getBuckets(options) |  | ||||||
|             .catch((err) => {       // Weirdly the influx-Api returns 404 for searches of non-existing buckets
 |  | ||||||
|                 logger.error("Could not get bucket:"); |  | ||||||
|                 logger.error(`Error [${err.code}]:`, err.message); |  | ||||||
|                 reject(); |  | ||||||
|             }).then((res) => {      // But an empty list when the bucket exists, but token does not have permission to get details
 |  | ||||||
|                 logger.debug("Bucket found"); |  | ||||||
|                 resolve(res); |  | ||||||
|                 // Now we know the bucket exists and we have some kind of permission.. but we still dont know if we are able to write to it..
 |  | ||||||
|             }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function checkWriteApi(influxDb, options){ |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|         const writeApi = influxDb.getWriteApi(options.org, options.bucket);     // Get WriteAPI
 |  | ||||||
|         writeApi.writePoint(new Point("worker_connectionTest").tag("hostname", Os.hostname()))    // Write point
 |  | ||||||
|         writeApi.close() |  | ||||||
|             .catch((err) => { |  | ||||||
|                 logger.error("Could not get writeApi:"); |  | ||||||
|                 logger.error(`Error [${err.code}]:`, err.message); |  | ||||||
|                 reject(); |  | ||||||
|             }).then((res) => { |  | ||||||
|                 logger.debug("Writing ok"); |  | ||||||
|                 resolve(); |  | ||||||
|             }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     checkHealth, |  | ||||||
|     checkBucket, |  | ||||||
|     checkWriteApi, |  | ||||||
| }; |  | ||||||
| @ -1,11 +0,0 @@ | |||||||
| const log4js = require("log4js"); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function setup(category = "unknown"){ |  | ||||||
|     const logger = log4js.getLogger(category); |  | ||||||
|     logger.level = process.env.LOGLEVEL ?? "INFO"; |  | ||||||
|     return logger; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = setup; |  | ||||||
| @ -1,46 +0,0 @@ | |||||||
| // This file specifies functions to help a user with e.g. configuration-errors
 |  | ||||||
| 
 |  | ||||||
| function detectStreamData(stream, timeout = 5000){ |  | ||||||
|     return new Promise((resolve, reject) => { |  | ||||||
|         let timeoutHandler; |  | ||||||
|         if(timeout){ |  | ||||||
|             timeoutHandler = setTimeout(() => { |  | ||||||
|                 reject('timeout'); |  | ||||||
|                 remListeners(); |  | ||||||
|             }, |  | ||||||
|             timeout); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function remListeners(){  |  | ||||||
|             stream.removeListener('error', errorHandler); |  | ||||||
|             stream.removeListener('data', dataHandler); |  | ||||||
|             if(timeoutHandler) clearTimeout(timeoutHandler); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         function errorHandler(err) { |  | ||||||
|             remListeners(); |  | ||||||
|         } |  | ||||||
|         function dataHandler(data) { |  | ||||||
|             resolve(data); |  | ||||||
|             remListeners(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         stream.on('error', errorHandler); |  | ||||||
|         stream.on('data', dataHandler); |  | ||||||
|     });     |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function detectStreamsData(streams, timeout = 5000){ |  | ||||||
|     let promises = []; |  | ||||||
|     streams.forEach((stream) => { |  | ||||||
|         promises.push(detectStreamData(stream, timeout)); |  | ||||||
|     }) |  | ||||||
|     return promises; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     detectStreamData, |  | ||||||
|     detectStreamsData, |  | ||||||
| }; |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| const { HandshakeStage } = require.main.require('./dto/Packet.js'); |  | ||||||
| 
 |  | ||||||
| function keyInfoFromRaw(keyInfoRaw) { |  | ||||||
|     return { |  | ||||||
|         "KeyDescriptorVersion": keyInfoRaw>>0 & 0b111, |  | ||||||
|         "KeyType":              keyInfoRaw>>3 & 0b1, |  | ||||||
|         "KeyIndex":             keyInfoRaw>>4 & 0b11, |  | ||||||
|         "Install":              keyInfoRaw>>6 & 0b1, |  | ||||||
|         "KeyACK":               keyInfoRaw>>7 & 0b1, |  | ||||||
|         "KeyMIC":               keyInfoRaw>>8 & 0b1, |  | ||||||
|         "Secure":               keyInfoRaw>>9 & 0b1, |  | ||||||
|         "Error":                keyInfoRaw>>10 & 0b1, |  | ||||||
|         "Request":              keyInfoRaw>>11 & 0b1, |  | ||||||
|         "EncryptedKeyData":     keyInfoRaw>>12 & 0b1, |  | ||||||
|         "SMKMessage":           keyInfoRaw>>13 & 0b1, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const HANDSHAKE_STAGE_KEYINFO = { |  | ||||||
|     "keys": ["Install", "KeyACK", "KeyMIC", "Secure"], |  | ||||||
|     "0100": HandshakeStage[1], |  | ||||||
|     "0010": HandshakeStage[2], |  | ||||||
|     "1111": HandshakeStage[3], |  | ||||||
|     "0011": HandshakeStage[4], |  | ||||||
| }; |  | ||||||
| function handshakeStageFromKeyInfo(keyInfo){ |  | ||||||
| 
 |  | ||||||
|     // Extract compare-keys
 |  | ||||||
|     let keyData = ""; |  | ||||||
|     for (const key of HANDSHAKE_STAGE_KEYINFO['keys']) { |  | ||||||
|         keyData += keyInfo[key].toString(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Get and return stage
 |  | ||||||
|     return  HANDSHAKE_STAGE_KEYINFO[keyData]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     keyInfoFromRaw, |  | ||||||
|     handshakeStageFromKeyInfo, |  | ||||||
| }; |  | ||||||
							
								
								
									
										131
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								src/main.js
									
									
									
									
									
								
							| @ -1,131 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| const logFactory = require("./helper/logger.js"); |  | ||||||
| const logger = logFactory("main"); |  | ||||||
| 
 |  | ||||||
| const { requireEnvVars } = require("./helper/env.js"); |  | ||||||
| const { exit } = require("process"); |  | ||||||
| const { exec } = require("./helper/exec.js"); |  | ||||||
| 
 |  | ||||||
| const { InfluxDB } = require('@influxdata/influxdb-client'); |  | ||||||
| const InfluxChecks = require('./helper/influx-checks.js'); |  | ||||||
| 
 |  | ||||||
| const { RegexBlockStream } = require("./streamHandler/RegexBlockStream.js"); |  | ||||||
| const { PacketStreamFactory } = require("./streamHandler/PacketStreamFactory.js"); |  | ||||||
| const { PacketInfluxPointFactory } = require("./streamHandler/PacketInfluxPointFactory.js"); |  | ||||||
| const { InfluxPointWriter } = require("./streamHandler/InfluxPointWriter.js"); |  | ||||||
| 
 |  | ||||||
| const userHelper = require("./helper/userHelper.js"); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /// Setup ENVs
 |  | ||||||
| const env = process.env; |  | ||||||
| // Defaults
 |  | ||||||
| { |  | ||||||
|   env.LOGLEVEL            ??= "INFO"; |  | ||||||
|   env.WIFI_INTERFACE      ??= "wlan0"; |  | ||||||
| } |  | ||||||
| // Required vars
 |  | ||||||
| let errorMsg = requireEnvVars([ |  | ||||||
|   "INFLUX_URL", "INFLUX_TOKEN", |  | ||||||
|   "INFLUX_ORG", "INFLUX_BUCKET" |  | ||||||
| ]); |  | ||||||
| if(errorMsg){ |  | ||||||
|   logger.fatal(errorMsg); |  | ||||||
|   exit(1); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| (async function() { |  | ||||||
|   logger.info("Setup Influx.."); |  | ||||||
|   const influxDb = new InfluxDB({url: env.INFLUX_URL, token: env.INFLUX_TOKEN}); |  | ||||||
| 
 |  | ||||||
|   await InfluxChecks.checkHealth(influxDb) |  | ||||||
|     .then((res) => {return InfluxChecks.checkBucket(influxDb, { |  | ||||||
|       org: env.INFLUX_ORG, |  | ||||||
|       name: env.INFLUX_BUCKET |  | ||||||
|     })}) |  | ||||||
|     .then((res) => {return InfluxChecks.checkWriteApi(influxDb, { |  | ||||||
|       org: env.INFLUX_ORG, |  | ||||||
|       bucket: env.INFLUX_BUCKET |  | ||||||
|     })}) |  | ||||||
|     .catch((err) => { |  | ||||||
|       if(err) { |  | ||||||
|         logger.error("Error whilst checking influx:"); |  | ||||||
|         logger.error(err); |  | ||||||
|       } |  | ||||||
|       logger.fatal("Setup influx failed!"); |  | ||||||
|       exit(1); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   logger.info("Influx ok"); |  | ||||||
| 
 |  | ||||||
|   logger.info("Starting tcpdump.."); |  | ||||||
|   const TCPDUMP_BASECMD = "tcpdump -vvv -e -n -X -s0 -i" |  | ||||||
|   let cmd = `${TCPDUMP_BASECMD} ${env.WIFI_INTERFACE}`; |  | ||||||
| 
 |  | ||||||
|   let proc = exec(cmd); |  | ||||||
|   logger.debug("Creating & Attaching streams.."); |  | ||||||
|   let regexBlockStream = new RegexBlockStream(/^\d{2}:\d{2}:\d{2}.\d{6}.*(\n( {4,8}|\t\t?).*)+\n/gm); |  | ||||||
|   let packetStreamFactory = new PacketStreamFactory(); |  | ||||||
|   let packetInfluxPointFactory = new PacketInfluxPointFactory(); |  | ||||||
|   let influxPointWriter = new InfluxPointWriter(influxDb, env.INFLUX_ORG, env.INFLUX_BUCKET); |  | ||||||
|   proc.stdout |  | ||||||
|     .setEncoding("utf8") |  | ||||||
|     .pipe(regexBlockStream) |  | ||||||
|     .pipe(packetStreamFactory) |  | ||||||
|     .pipe(packetInfluxPointFactory) |  | ||||||
|     .pipe(influxPointWriter); |  | ||||||
| 
 |  | ||||||
|   logger.debug("Attaching error-logger.."); |  | ||||||
|   const loggerTcpdump = logFactory("tcpdump"); |  | ||||||
|   proc.stderr.setEncoding("utf8").on("data", (data) => { |  | ||||||
|     if(!data.match(/^(tcpdump: )?listening on /i) || !data.match(/^\d+ packets captured/i)) {  // Catch start-error
 |  | ||||||
|         loggerTcpdump.debug(data); |  | ||||||
|     } |  | ||||||
|     else loggerTcpdump.error(data); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   // FIXME: This is a hacky workaround to not let errors from subprocess bubble up and terminate our process
 |  | ||||||
|   regexBlockStream.on('error', (err) => {}); |  | ||||||
| 
 |  | ||||||
|   proc.on("error", (err) => { |  | ||||||
|     loggerTcpdump.error(err); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   const loggerPacketStream = logFactory("PacketStreamFactory"); |  | ||||||
|   userHelper.detectStreamData(proc.stdout, 10000)       // Expect tcpdump-logs to have data after max. 10s
 |  | ||||||
|     .then(() => { |  | ||||||
|         loggerTcpdump.debug("Got first data"); |  | ||||||
|         userHelper.detectStreamData(packetStreamFactory, 10000)      // Expect then to have packets after further 10s
 |  | ||||||
|             .then(() => { |  | ||||||
|                 loggerPacketStream.debug("Got first packet"); |  | ||||||
|             }) |  | ||||||
|             .catch((err) => { |  | ||||||
|                 if(err == 'timeout') loggerPacketStream.warn("No packets"); |  | ||||||
|             }); |  | ||||||
|     }) |  | ||||||
|     .catch((err) => { |  | ||||||
|         if(err == 'timeout') loggerTcpdump.warn("No data after 10s! Wrong configuration?"); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   logger.debug("Attaching exit-handler.."); |  | ||||||
|   proc.on("exit", (code) => { |  | ||||||
|     loggerTcpdump.debug(`tcpdump exited code: ${code}`); |  | ||||||
|     if (code) { |  | ||||||
|         loggerTcpdump.fatal(`tcpdump exited with non-zero code: ${code}`); |  | ||||||
|         exit(1); |  | ||||||
|     } |  | ||||||
|     logger.info("Shutdown"); |  | ||||||
|     exit(0); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   // Handle stop-signals for graceful shutdown
 |  | ||||||
|   function shutdownReq() { |  | ||||||
|     logger.info("Shutdown request received.."); |  | ||||||
|     logger.debug("Stopping subprocess tcpdump, then exiting myself.."); |  | ||||||
|     proc.kill();    // Kill process (send SIGTERM), then upper event-handler will stop self
 |  | ||||||
|   } |  | ||||||
|   process.on('SIGTERM', shutdownReq); |  | ||||||
|   process.on('SIGINT', shutdownReq); |  | ||||||
| 
 |  | ||||||
|   logger.info("Startup complete"); |  | ||||||
| })(); |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| const logger = require.main.require("./helper/logger.js")("InfluxPointWriter"); |  | ||||||
| const { Writable } = require('stream'); |  | ||||||
| const {InfluxDB, Point, HttpError} = require('@influxdata/influxdb-client') |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Get points and write them into influx |  | ||||||
|  */ |  | ||||||
| class InfluxPointWriter extends Writable{ |  | ||||||
|     /** |  | ||||||
|      *  |  | ||||||
|      * @param {InfluxDB} influxDb InfluxDb |  | ||||||
|      * @param {string} org Organization to use |  | ||||||
|      * @param {string} bucket Bucket to use |  | ||||||
|      * @param {Partial<WriteOptions>} options Options for WriteApi |  | ||||||
|      */ |  | ||||||
|     constructor(influxDb, org, bucket, options){ |  | ||||||
|         super({ |  | ||||||
|             objectMode: true |  | ||||||
|         }); |  | ||||||
|         this._api = influxDb.getWriteApi(org, bucket, 'us', options); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _write(point, encoding, next){ |  | ||||||
|         this._api.writePoint(point); |  | ||||||
|         next(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _flush(next){ |  | ||||||
|         this._api.flush(true) |  | ||||||
|             .catch((err) => { next(new Error(`WriteApi rejected promise for flush: ${err}`)); }) |  | ||||||
|             .then(next); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     InfluxPointWriter |  | ||||||
| }; |  | ||||||
| @ -1,85 +0,0 @@ | |||||||
| const logger = require.main.require("./helper/logger.js")("PacketStreamFactory"); |  | ||||||
| const { Transform } = require('stream'); |  | ||||||
| const {Point} = require('@influxdata/influxdb-client') |  | ||||||
| 
 |  | ||||||
| /** Keys to always use as tags */ |  | ||||||
| const TAG_LIST = [ |  | ||||||
|     "srcMac", |  | ||||||
|     "dstMac", |  | ||||||
|     "bssid", |  | ||||||
|     "frequency", |  | ||||||
|     "flags", |  | ||||||
|     "packetType", |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| /** Measurement-name and corresponding field-key */ |  | ||||||
| const MEASUREMENT_MAP = new Map([ |  | ||||||
|     ["Signal", "signal"], |  | ||||||
|     ["PayloadSize", "payloadSize"], |  | ||||||
|     ["DataRate", "dataRate"], |  | ||||||
|     ["SSID", "ssid"], |  | ||||||
|     ["AuthenticationType", "authenticationType"], |  | ||||||
|     ["AssociationSuccess", "associationIsSuccessful"], |  | ||||||
|     ["DisassociationReason", "disassociationReason"], |  | ||||||
| ]); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Get packets and convert them into influx-points |  | ||||||
|  */ |  | ||||||
| class PacketInfluxPointFactory extends Transform{ |  | ||||||
|     constructor(){ |  | ||||||
|         super({ |  | ||||||
|             readableObjectMode: true, |  | ||||||
|             writableObjectMode: true |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _transform(packet, encoding, next){ |  | ||||||
|         // Create measurements
 |  | ||||||
|         MEASUREMENT_MAP.forEach((objKey, measurement) => { |  | ||||||
|             if(packet[objKey] == null) return; |  | ||||||
| 
 |  | ||||||
|             let point = new Point(measurement);     // Create point
 |  | ||||||
|              |  | ||||||
|             // Set tags
 |  | ||||||
|             TAG_LIST.filter(tag => Object.keys(packet).includes(tag))   // Filter tags available on object
 |  | ||||||
|                     .filter(tag => packet[tag] != null)                         // Filter tags not falsy on object
 |  | ||||||
|                     .forEach(tag => { |  | ||||||
|                         tagObjectRecursively(point, tag, packet[tag]); |  | ||||||
|                     }); |  | ||||||
| 
 |  | ||||||
|             point.setField('value', packet[objKey]);        // Set field
 |  | ||||||
| 
 |  | ||||||
|             this.push(point);     // Push point into stream
 |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         next();     // Get next packet
 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function tagObjectRecursively(point, tag, field, suffix = ""){ |  | ||||||
|     if(typeof(field) == "object"){ |  | ||||||
|         // TODO: Convert boolean-arrays like "packet.flags" to key: value
 |  | ||||||
|         Object.entries(field).map(([key, value]) => { |  | ||||||
|             tagObjectRecursively(point, tag, value, `_${key}${suffix}`); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|     else point.tag(tag+suffix, field); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** Mapping for type -> field-method */ |  | ||||||
| const POINT_FIELD_TYPE = new Map([ |  | ||||||
|     ['boolean',   function(key, value){ return this.booleanField(key, value); }], |  | ||||||
|     ['number',    function(key, value){ return this.intField(key, value); }], |  | ||||||
|     ['string',    function(key, value){ return this.stringField(key, value); }], |  | ||||||
| ]); |  | ||||||
| Point.prototype.setField = function(key, value){ |  | ||||||
|     let setField = POINT_FIELD_TYPE.get(typeof value); |  | ||||||
|     return setField.apply(this, [key, value]); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     PacketInfluxPointFactory |  | ||||||
| }; |  | ||||||
| @ -1,152 +0,0 @@ | |||||||
| const logger = require.main.require("./helper/logger.js")("PacketStreamFactory"); |  | ||||||
| const { Transform } = require('stream'); |  | ||||||
| const { DateTime } = require("luxon"); |  | ||||||
| const { PacketType, FlagType, Packet, PacketWithSSID, BeaconPacket, ProbeRequestPacket, ProbeResponsePacket, AuthenticationPacket, AuthenticationType, AssociationResponsePacket, DisassociationPacket, HandshakePacket, HandshakeStage } = require.main.require('./dto/Packet.js'); |  | ||||||
| const hexConv = require.main.require("./helper/hexConverter.js"); |  | ||||||
| const wifiStateAnalyser = require.main.require("./helper/wifiStateAnalyzer.js"); |  | ||||||
| 
 |  | ||||||
| const PACKET_TYPE_MAP = { |  | ||||||
|     "Beacon":           PacketType.Beacon, |  | ||||||
|     "Probe Request":    PacketType.ProbeRequest, |  | ||||||
|     "Probe Response":   PacketType.ProbeResponse, |  | ||||||
|     "Data":             PacketType.Data, |  | ||||||
|     "Request-To-Send":  PacketType.RequestToSend, |  | ||||||
|     "Clear-To-Send":    PacketType.ClearToSend, |  | ||||||
|     "Acknowledgment":   PacketType.Acknowledgment, |  | ||||||
|     "BA":               PacketType.BlockAcknowledgment, |  | ||||||
|     "Authentication":   PacketType.Authentication, |  | ||||||
|     "Assoc Request":    PacketType.AssociationRequest, |  | ||||||
|     "Assoc Response":   PacketType.AssociationResponse, |  | ||||||
|     "Disassociation:":  PacketType.Disassociation, |  | ||||||
|     "EAPOL":            PacketType.Handshake, |  | ||||||
| }; |  | ||||||
| const PACKET_TYPES_REGEX = Object.keys(PACKET_TYPE_MAP).join('|'); |  | ||||||
| 
 |  | ||||||
| const AUTHENTICATION_TYPE_MAP = { |  | ||||||
|     "(Open System)-1":  AuthenticationType.OpenSystem_1, |  | ||||||
|     "(Open System)-2":  AuthenticationType.OpenSystem_2, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const FLAG_TYPE_MAP = { |  | ||||||
|     "Retry": FlagType.Retry, |  | ||||||
|     "Pwr Mgmt": FlagType.PwrMgt, |  | ||||||
|     "More Data": FlagType.MoreData, |  | ||||||
|     "Protected": FlagType.Protected, |  | ||||||
| } |  | ||||||
| const FLAG_TYPE_MAPS_REGEX = Object.keys(FLAG_TYPE_MAP).join('|'); |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Read data from text-blocks and convert them to Packet |  | ||||||
|  */ |  | ||||||
| class PacketStreamFactory extends Transform{ |  | ||||||
|     constructor(){ |  | ||||||
|         super({ |  | ||||||
|             readableObjectMode: true, |  | ||||||
|             writableObjectMode: true |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _transform(chunk, encoding, next){ |  | ||||||
|         let packet = new Packet(); |  | ||||||
| 
 |  | ||||||
|         const lines = chunk.split('\n'); |  | ||||||
|         const header = lines.splice(0, 1)[0];       // Grab first line, 'lines' is now the payload
 |  | ||||||
|         packet = this._handleHeader(packet, header); |  | ||||||
|         packet = this._handlePayload(packet, lines); |  | ||||||
|          |  | ||||||
|         next(null, packet);     // Get next chunk
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _handleHeader(packet, data){ |  | ||||||
|         // Convert time to epoch-micros         Unfortunately luxon doesnt use micros, but millis as smallest time-unit requiring some "hacks"
 |  | ||||||
|         packet.timestampMicros = DateTime.fromISO(data.slice(0, 12)).toSeconds() + data.slice(12, 15)/1000000; |  | ||||||
| 
 |  | ||||||
|         // Find flags
 |  | ||||||
|         data.match(data.match(new RegExp('(?<=^|\\s)('+ FLAG_TYPE_MAPS_REGEX +')(?=$|\\s)', 'ig')) |  | ||||||
|             ?.forEach(match => packet.flags[FLAG_TYPE_MAP[match]] = true)       // Set them to true in flags
 |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         packet.dataRate = Number(data.match(/(?<=^|\s)\d+(\.\d+)?(?=\sMb\/?s($|\s))/i)?.[0]) || null; |  | ||||||
|         packet.frequency = Number(data.match(/(?<=^|\s)\d{4}(?=\sMHz($|\s))/i)?.[0]) || null; |  | ||||||
| 
 |  | ||||||
|         packet.durationMicros = Number(data.match(/(?<=^|\s)\d{1,4}(?=us($|\s))/i)?.[0]) || null; |  | ||||||
| 
 |  | ||||||
|         packet.signal = Number(data.match(/(?<=^|\s)-\d{2,3}(?=dBm\sSignal($|\s))/i)?.[0]) || null; |  | ||||||
| 
 |  | ||||||
|         let packetTypeStr = data.match(new RegExp('(?<=^|\\s)('+ PACKET_TYPES_REGEX +')(?=$|\\s)', 'i'))?.[0]; |  | ||||||
|         if(packetTypeStr) |  | ||||||
|             packet.packetType = PACKET_TYPE_MAP[packetTypeStr]; |  | ||||||
|         else if(data.match(/(SA|TA|DA|RA|BSSID):.{17}\s*$/i)){ |  | ||||||
|             packet.packetType = PacketType.NoData |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             packet.packetType = PacketType.Unknown; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         packet.srcMac = data.match(/(?<=(^|\s)(SA|TA):).{17}(?=$|\s)/i)?.[0] ?? null; |  | ||||||
| 
 |  | ||||||
|         packet.dstMac = data.match(/(?<=(^|\s)(DA|RA):).{17}(?=$|\s)/i)?.[0] ?? null; |  | ||||||
|          |  | ||||||
|         packet.bssid = data.match(/(?<=(^|\s)BSSID:).{17}(?=$|\s)/i)?.[0] ?? null; |  | ||||||
| 
 |  | ||||||
|         // Cover special cases with more data
 |  | ||||||
|         let newPacket; |  | ||||||
|         switch(packet.packetType){ |  | ||||||
|             case PacketType.Beacon: |  | ||||||
|             case PacketType.ProbeRequest: |  | ||||||
|             case PacketType.ProbeResponse: |  | ||||||
|             case PacketType.AssociationRequest: |  | ||||||
|                 newPacket = new PacketWithSSID(); |  | ||||||
|                 newPacket.ssid = data.match(new RegExp('(?<=(^|\\s)'+ packetTypeStr +'\\s\\().{0,32}(?=\\)($|\\s))', 'i'))?.[0] ?? null; |  | ||||||
|                 break; |  | ||||||
|              |  | ||||||
|             case PacketType.Authentication: |  | ||||||
|                 newPacket = new AuthenticationPacket(); |  | ||||||
|                 newPacket.authenticationType = AUTHENTICATION_TYPE_MAP[data.match(/(?<=(^|\s)Authentication\s).{3,}(?=\:(\s|$))/i)[0]] ?? AuthenticationType.Unknown; |  | ||||||
|                 break; |  | ||||||
| 
 |  | ||||||
|             case PacketType.AssociationResponse: |  | ||||||
|                 newPacket = new AssociationResponsePacket(); |  | ||||||
|                 newPacket.associationIsSuccessful = data.match(/(?<=(^|\s)Assoc\sResponse\s.{0,30})Successful(?=\s|$)/i) ? true : false; |  | ||||||
|                 break; |  | ||||||
| 
 |  | ||||||
|             case PacketType.Disassociation: |  | ||||||
|                 newPacket = new DisassociationPacket(); |  | ||||||
|                 newPacket.disassociationReason = data.match(/(?<=(^|\s)Disassociation:\s).*$/i)?.[0] ?? null; |  | ||||||
|                 break; |  | ||||||
|         } |  | ||||||
|         if(newPacket) packet = Object.assign(newPacket, packet);   // Use new, more specific, packet and copy old data over
 |  | ||||||
| 
 |  | ||||||
|         return packet; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _handlePayload(packet, data){ |  | ||||||
|         data = data.join(''); |  | ||||||
| 
 |  | ||||||
|         // Get payload-Hex-Data. If there is no data: empty
 |  | ||||||
|         packet.payloadData = hexConv.hexToBytes(data.match(/(?<=\s)([A-F0-9]{1,4}(?=\s))/igm)?.join('') ?? ''); |  | ||||||
|         packet.payloadData.splice(packet.payloadData.length-4, 4);      // Remove FrameCheck sequence
 |  | ||||||
| 
 |  | ||||||
|         // Cover special cases with more data
 |  | ||||||
|         let newPacket; |  | ||||||
|         switch(packet.packetType){ |  | ||||||
|             case PacketType.Handshake: |  | ||||||
|                 newPacket = new HandshakePacket(); |  | ||||||
| 
 |  | ||||||
|                 // Read key-information
 |  | ||||||
|                 const keyInfoRaw = (packet.payloadData[0x5]<<0x8) + packet.payloadData[0x6]; |  | ||||||
|                 const keyInfo = wifiStateAnalyser.keyInfoFromRaw(keyInfoRaw);   // Convert
 |  | ||||||
| 
 |  | ||||||
|                 newPacket.handshakeStage =  wifiStateAnalyser.handshakeStageFromKeyInfo(keyInfo);   // Get stage
 |  | ||||||
|                 break; |  | ||||||
|         } |  | ||||||
|         if(newPacket) packet = Object.assign(newPacket, packet); |  | ||||||
| 
 |  | ||||||
|         return packet; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     PacketStreamFactory |  | ||||||
| }; |  | ||||||
| @ -1,67 +0,0 @@ | |||||||
| const logger = require.main.require("./helper/logger.js")("RegexBlockStream"); |  | ||||||
| const { Transform } = require('stream') |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Matches whole blocks as regex and passes them on |  | ||||||
|  */ |  | ||||||
| class RegexBlockStream extends Transform{ |  | ||||||
|     matcher; |  | ||||||
|     withholdLastBlock; |  | ||||||
|     matchAllOnFlush; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @param {RegExp} matcher Block-match |  | ||||||
|      * @param {boolean} withholdLastBlock When true, the last matches block will not be submitted to prevent submitting incomplete blocks. |  | ||||||
|      * @param {boolean} matchAllOnFlush (Only in combination with withholdLastBlock) When enabled, the buffer will be matched on last time on _flush (stream deconstruction) and write any, also incomplete, blocks |  | ||||||
|      * @remarks WARNING: It should match a clean-block (including e.g. newline)! Otherwise buffer will get dirty and use more and more resources. |  | ||||||
|      */ |  | ||||||
|     constructor(matcher, withholdLastBlock = true, matchAllOnFlush = false){ |  | ||||||
|         super({ |  | ||||||
|             readableObjectMode: true, |  | ||||||
|             writableObjectMode: true |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         this.matcher = matcher; |  | ||||||
|         this.withholdLastBlock = withholdLastBlock; |  | ||||||
|         this.matchAllOnFlush = matchAllOnFlush; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _transform(chunk, encoding, next){ |  | ||||||
|         chunk = this.readableBuffer.length? this.readableBuffer.join('') + chunk: chunk;     // Add previous buffer to current chunk
 |  | ||||||
|         this.readableBuffer.length && this.readableBuffer.clear();      // Clear buffer once we read it
 |  | ||||||
| 
 |  | ||||||
|         let matches = chunk.match(this.matcher);    // Match
 |  | ||||||
|         if(matches){ |  | ||||||
|             if(this.withholdLastBlock) matches.pop();       // Remove last if we want to withhold it
 |  | ||||||
|             chunk = this._writeMatches(matches, chunk); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         this.readableBuffer.push(chunk);   // Store remaining data in buffer
 |  | ||||||
|         next();     // Get next chunk
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _writeMatches(matches, chunk = null){ |  | ||||||
|         if(matches){ |  | ||||||
|             matches.forEach((match) => { |  | ||||||
|                 this.push(match);   // Write match to stream
 |  | ||||||
|                 if(chunk) chunk = chunk.replace(match, '');   // Remove match from chunks
 |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|         if(chunk) return chunk; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _flush(next){ |  | ||||||
|         if(matchAllOnFlush){    // When requested, we'll match one last time over the remaining buffer
 |  | ||||||
|             let chunk = this.readableBuffer.join(''); |  | ||||||
|             let matches = chunk.match(this.matcher);    // Match remaining buffer
 |  | ||||||
|             _writeMatches(matches);    // Write matches including last element
 |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         next();     // Tell system we are done
 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Specify exports
 |  | ||||||
| module.exports = { |  | ||||||
|     RegexBlockStream |  | ||||||
| }; |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user