Skip to content
This repository has been archived by the owner on Aug 1, 2020. It is now read-only.

Commit

Permalink
Implement Plasma Dash
Browse files Browse the repository at this point in the history
  • Loading branch information
ide committed Oct 5, 2015
1 parent 0761526 commit b0a0b87
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 4 deletions.
17 changes: 17 additions & 0 deletions .babelrc
@@ -0,0 +1,17 @@
{
"whitelist": [
"asyncToGenerator",
"es6.classes",
"es6.destructuring",
"es6.modules",
"es6.parameters",
"es6.properties.shorthand",
"es6.spread",
"es7.asyncFunctions",
"es7.classProperties",
"es7.trailingFunctionCommas",
"flow",
"runtime",
"strict",
]
}
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -8,7 +8,7 @@ Plasma Dash is designed to run on a Raspberry Pi. Specifically, it runs on [Rasp

Plasma Dash's runs on Node 4 and up on OS X and Linux. It depends on [libpcap](http://www.tcpdump.org/):

```
```sh
# Ubuntu and Debian
sudo apt-get install libpcap-dev
# Fedora and CentOS
Expand All @@ -17,7 +17,7 @@ sudo yum install libpcap-devel

Install Plasma Dash in your project using npm:

```
```sh
npm install --save plasma-dash
```

Expand Down
27 changes: 27 additions & 0 deletions gulpfile.babel.js
@@ -0,0 +1,27 @@
import gulp from 'gulp';
import babel from 'gulp-babel';
import changed from 'gulp-changed';
import rimraf from 'rimraf';

const paths = {
source: 'src/**/*.js',
build: 'build',
};

function build() {
return gulp.src(paths.source)
.pipe(changed(paths.build))
.pipe(babel())
.pipe(gulp.dest(paths.build));
}

function watch(done) {
gulp.watch(paths.source, build);
done();
}

gulp.task(build);
gulp.task('watch', gulp.parallel(build, watch));
gulp.task('clean', done => {
rimraf(paths.build, done);
});
21 changes: 19 additions & 2 deletions package.json
Expand Up @@ -3,18 +3,25 @@
"version": "0.0.1",
"description": "A small server that reacts to Amazon Dash buttons on your WiFi network",
"main": "build/PlasmaDash.js",
"bin": {
"plasma-dash": "build/cli.js"
},
"engines": {
"node": ">=4"
},
"scripts": {
"build": "gulp build",
"watch": "gulp watch",
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ide/plasma-dash.git"
},
"keywords": [
"dash-button"
"amazon",
"dash",
"button"
],
"author": "James Ide",
"license": "MIT",
Expand All @@ -23,7 +30,17 @@
},
"homepage": "https://github.com/ide/plasma-dash#readme",
"devDependencies": {
"babel-core": "^5.8.25",
"babel-eslint": "^4.1.3",
"eslint": "^1.6.0"
"eslint": "^1.6.0",
"gulp": "gulpjs/gulp#4.0",
"gulp-babel": "^5.2.1",
"gulp-changed": "^1.3.0",
"rimraf": "^2.4.3"
},
"dependencies": {
"babel-runtime": "^5.8.25",
"pcap": "ide/node_pcap#node-4",
"yargs": "^3.26.0"
}
}
14 changes: 14 additions & 0 deletions src/NetworkInterfaces.js
@@ -0,0 +1,14 @@
import os from 'os';

export default class NetworkInterfaces {
static getDefault() {
let interfaces = os.networkInterfaces();
let names = Object.keys(interfaces);
for (let name of names) {
if (interfaces[name].every(iface => !iface.internal)) {
return name;
}
}
return null;
}
}
99 changes: 99 additions & 0 deletions src/PlasmaDash.js
@@ -1 +1,100 @@
import assert from 'assert';
import pcap from 'pcap';

import NetworkInterfaces from './NetworkInterfaces';

type Options = {
networkInterface?: string,
};

let pcapSession;

function getPcapSession(interfaceName: string) {
if (!pcapSession) {
pcapSession = pcap.createSession(interfaceName);
} else {
assert.equal(
interfaceName, pcapSession.device_name,
'The existing pcap session must be listening on the specified interface',
);
}
return pcapSession;
}

export default class PlasmaDash {
constructor(macAddress: string, options?: Options = {}) {
this._macAddress = macAddress;
this._networkInterface = options.networkInterface ||
NetworkInterfaces.getDefault();
this._packetListener = this._handlePacket.bind(this);
this._dashListeners = new Set();
this._isResponding = false;
}

addListener(listener): Subscription {
if (!this._dashListeners.size) {
let session = getPcapSession();
session.addListener('packet', this._packetListener);
}

// We run the listeners with Promise.all, which rejects early as soon as
// any of its promises are rejected. Since we want to wait for all of the
// listeners to finish we need to catch any errors they may throw.
let guardedListener = this._createGuardedListener(listener);
this._dashListeners.add(guardedListener);

return new Subscription(() => {
this._dashListeners.remove(guardedListener);
if (!this._dashListeners.size) {
let session = getPcapSession();
session.removeListener('packet', this._packetListener);
if (!session.listenerCount('packet')) {
session.close();
}
}
});
}

_createGuardedListener(listener) {
return async function(...args) {
try {
return await listener(...args);
} catch (error) {
return error;
}
};
}

async _handlePacket(rawPacket) {
if (this._isResponding) {
return;
}

let packet = pcap.decode(rawPacket);

this._isResponding = true;
try {
// The listeners are guarded so this should never throw, but wrap it in
// try-catch to be defensive
await Promise.all(Array.from(this._dashListeners).map(
listener => listener(packet)
));
} finally {
this._isResponding = false;
}
}
}

class Subscription {
constructor(onRemove) {
this._remove = onRemove;
}

remove() {
if (this._remove) {
return;
}
this._remove();
delete this._remove;
}
}
28 changes: 28 additions & 0 deletions src/cli.js
@@ -0,0 +1,28 @@
import pcap from 'pcap';
import yargs from 'yargs';

import NetworkInterfaces from './NetworkInterfaces';

if (require.main === module) {
let argv = yargs
.usage('Usage: $0 <command> [options]')
.command('scan', 'Scan for ARP probes')
.example(
'$0 scan -i wlan0',
'Scan for ARP probes on the given network interface'
)
.alias('i', 'interface')
.nargs('i', 1)
.default('i', NetworkInterfaces.getDefault())
.describe('i', 'The network interface on which to listen')
.help('h')
.alias('h', 'help')
.argv;
let commands = new Set(argv._);
if (commands.has('scan')) {
let pcapSession = pcap.createSession(argv.interface, 'arp');
pcapSession.addListener('packet', rawPacket => {
console.log(rawPacket);
});
}
}

0 comments on commit b0a0b87

Please sign in to comment.