Sfoglia il codice sorgente

Initial commit.

tags/v0.0.1
Peter Vivell 7 anni fa
commit
50df9a4bc4

+ 52
- 0
README.md Vedi File

@@ -0,0 +1,52 @@
1
+
2
+# Radangel
3
+## Device: Kromek RadAngel
4
+The [Kromek RadAngel](https://www.kromek.com/product/radangel-czt-gamma-ray-spectrometer/) is an affordable CZT-based gamma-ray spectrometer device connecting via USB as HID device.
5
+
6
+RadAngel a small and portable gamma-ray spectrometer that can be utilised for educational purposes in teaching concepts of radiation as well as for training in the use of radiation sensors.
7
+
8
+Its compact size and quick set-up time make it ideally suited for individual or small group projects, laboratory-based teaching, classroom demonstrations and field studies.
9
+ 
10
+This instrument utilizes a Cadmium Zinc Telluride (CZT) solid state detector, which enables room temperature operation with excellent energy resolution (12 bits hence 4096 channels from ~30 keV to ~3 MeV). 
11
+The unit is completely self-contained, with a built-in detector, amplifiers, power supply and communications. 
12
+The digitized detector signals are sent to a PC via the mini-USB which also powers the unit, so no external supply is required.
13
+
14
+![Kromek RadAngel](docs/Kromek_RadAngel.png)
15
+
16
+## Daemon: radangel
17
+
18
+The `radangel` binary communicates with the RadAngel HID device (`04d8:0100`) via USB (`hidapi-libusb`) and uses threads (`pthreads`) to read events and push them in a queue, then pop them from the queue and write them into files.
19
+
20
+To grant permissions to the USB device, the udev system can be employed:
21
+
22
+ `echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0100", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kromek-radangel.rules`
23
+
24
+Compiling the radangel binary for the linux operating system is easy:
25
+Just run `make` in the `src` directory or
26
+
27
+`gcc -std=gnu99 -Wall radangel.c -o radangel -pthread -lm -lhidapi-libusb`
28
+
29
+The output of the `radangel` daemon consists of two files (time series and histogram):
30
+
31
+* `events.tsv` each line corresponds to one event with timestamp and channel (gamma-ray energy).
32
+* `stats.tsv`each line corresponds to one channel and displays the number of occurance (histogram).
33
+
34
+Running the binary with the appropriate arguments:
35
+
36
+`radangel --verbose --event events.tsv --stats stats.tsv`
37
+
38
+The daemon can be run as service with the `radangel.service` systemd file.
39
+
40
+## Web: service
41
+
42
+The changes in the output files from `radangel` daemon can be streamed to the browser in realtime with the two systemd units `datasrc.service`
43
+and `websockify.service`:
44
+
45
+*  `datasrc.service` systemd unit employs `inotail` to monitor the output files and streams the changes with `socat` into a tcp socket.
46
+*  `websockify.service` launches the `websockify` daemon which provides the websocket endpoint behind `nginx` webserver (see `nginx-datasrc-location.conf`) and translates between websocket and the plain tcp socket.
47
+
48
+To compile the `websockify` daemon just run `make` in the `src/contrib` directory.
49
+
50
+The web interface itself uses [Plotly.js](https://plot.ly/javascript/) to display the histogram of the channels in realtime by connecting to the websocket endpoint and provides a convenient user interface for exploring the details of the gamma-spectrum.
51
+
52
+![Background Gamma Spectrum](docs/background-gamma-spectrum.png)

+ 31
- 0
config/nginx-datasrc-location.conf Vedi File

@@ -0,0 +1,31 @@
1
+    ## <DATASRC> ##
2
+
3
+    location /data/hist.tsv {
4
+        alias /tmp/stats.tsv;
5
+    }
6
+
7
+    location /data/stream.tsv { 
8
+        alias /tmp/event.tsv;
9
+    }
10
+
11
+    location /data/stream.ws {
12
+        proxy_http_version 1.1;
13
+        proxy_set_header Upgrade         $http_upgrade;
14
+        proxy_set_header Connection      "upgrade";
15
+
16
+        proxy_set_header Host            $host; # $http_host;
17
+        proxy_set_header X-Real-IP       $remote_addr;
18
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
19
+        proxy_set_header X-NginX-Proxy   true;
20
+
21
+        proxy_read_timeout 3600;
22
+        proxy_buffering off;
23
+        tcp_nodelay on;
24
+
25
+        error_page 502 = /data/stream.tsv; # serve http for non-ws clients
26
+
27
+        proxy_pass http://127.0.0.1:5800/websockify;
28
+    }
29
+
30
+    ## </DATASRC> ##
31
+

BIN
docs/Kromek_RadAngel.png Vedi File


BIN
docs/RA4SA4ERev3.pdf Vedi File


BIN
docs/background-gamma-spectrum.png Vedi File


+ 67
- 0
html/index.html Vedi File

@@ -0,0 +1,67 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+<head>
5
+	<meta charset="utf-8" />
6
+	<title>γ-Spectrum</title>
7
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
8
+	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+	<!-- <link href="css/bootstrap.min.css" rel="stylesheet" media="screen"> -->
10
+	<style>
11
+		html,
12
+		body {
13
+			height: 100%;
14
+			max-height: 100%;
15
+			margin: 0;
16
+			padding: 0;
17
+			overflow: hidden;
18
+			background: black;
19
+		}
20
+
21
+		div.graph,
22
+		div.plot-container,
23
+		div.svg-container,
24
+		div.svg-container svg {
25
+			min-height: 100%;
26
+			/* height: auto !important; */
27
+			height: 100%;
28
+			margin: 0;
29
+			padding: 0;
30
+			overflow: hidden;
31
+		}
32
+	</style>
33
+	<script src="js/plotly-cartesian-latest.min.js"></script>
34
+	<script src="js/chroma.min.js"></script>
35
+	<script src="js/streamplot.js"></script>
36
+	<script>
37
+		function init(id) {
38
+			return createGraph(id, {
39
+				'src': {
40
+					'stream': '/data/stream.ws',
41
+					'init': '/data/hist.tsv'
42
+				},
43
+				'title': {
44
+					'plot': 'γ-Spectrum',
45
+					'xaxis': 'Energy / keV',
46
+					'yaxis': 'Events'
47
+				},
48
+				'colors': {
49
+					'graph': ['white', 'black'], // fg to bg
50
+					'lines': ['blue', 'cyan', 'lightgreen', 'yellow', 'red'] // cols left to right
51
+				},
52
+				'maxPoints': 4096,
53
+				'size': 2,
54
+				'scale': 1.5,
55
+				'shape': 'linear', // 'spline', // 'linear'
56
+				'smoothing': 2.0
57
+			});
58
+		}
59
+	</script>
60
+</head>
61
+
62
+<body onload="init('graph');">
63
+	<div id="graph" class="graph">
64
+	</div>
65
+</body>
66
+
67
+</html>

+ 2733
- 0
html/js/chroma.js
File diff soppresso perché troppo grande
Vedi File


+ 33
- 0
html/js/chroma.min.js
File diff soppresso perché troppo grande
Vedi File


+ 25
- 0
html/js/get.sh Vedi File

@@ -0,0 +1,25 @@
1
+#/bin/sh
2
+
3
+# <!-- https://github.com/plotly/plotly.js/blob/master/dist/README.md -->
4
+#wget 'https://cdn.plot.ly/plotly-latest.js'
5
+#wget 'https://cdn.plot.ly/plotly-latest.min.js'
6
+#wget 'https://cdn.plot.ly/plotly-basic-latest.js'
7
+#wget 'https://cdn.plot.ly/plotly-basic-latest.min.js'
8
+wget 'https://cdn.plot.ly/plotly-cartesian-latest.js'
9
+wget 'https://cdn.plot.ly/plotly-cartesian-latest.min.js'
10
+#wget 'https://cdn.plot.ly/plotly-gl2d-latest.js'
11
+#wget 'https://cdn.plot.ly/plotly-gl2d-latest.min.js'
12
+
13
+#<!-- https://github.com/gka/chroma.js/blob/master/docs/src/index.md -->
14
+wget 'https://github.com/gka/chroma.js/raw/master/chroma.js'
15
+wget 'https://github.com/gka/chroma.js/raw/master/chroma.min.js'
16
+
17
+#<! -- http://bl.ocks.org/syntagmatic/raw/3341641/ -->
18
+#wget 'http://bl.ocks.org/syntagmatic/raw/3341641/render-queue.js'
19
+
20
+# Tone,js
21
+#wget 'https://tonejs.github.io/build/Tone.js'
22
+#wget 'https://tonejs.github.io/build/Tone.min.js'
23
+
24
+# nginx gzip_static on;
25
+for F in *.js ; do cat $F | gzip -9 > $F.gz ; done

+ 79293
- 0
html/js/plotly-cartesian-latest.js
File diff soppresso perché troppo grande
Vedi File


+ 7
- 0
html/js/plotly-cartesian-latest.min.js
File diff soppresso perché troppo grande
Vedi File


+ 402
- 0
html/js/streamplot.js Vedi File

@@ -0,0 +1,402 @@
1
+function dataSrc(url, config) {
2
+  config = config || {};
3
+  url = url || [
4
+    location.protocol == 'https:' ? 'wss://' : 'ws://',
5
+    location.hostname,
6
+    location.protocol == 'https:' ? ':443' : ':80',
7
+    '/data/stream.ws'
8
+  ].join('');
9
+  var ctx = {
10
+    'url': url,
11
+    'ws': {},
12
+    'proto': 'binary',
13
+    'buffer': '',
14
+    'headers': [],
15
+    'data': [],
16
+    'separatorRows': config.separatorRows || "\n",
17
+    'separatorCols': config.separatorCols || "\t",
18
+    'ping': (function() {
19
+      var ac = new(window.AudioContext || window.webkitAudioContext)();
20
+      var osc = ac.createOscillator();
21
+      var gn = ac.createGain();
22
+      osc.connect(gn);
23
+      gn.connect(ac.destination);
24
+      gn.gain.setTargetAtTime(0.0, ac.currentTime, 0.0);
25
+      osc.start(ac.currentTime);
26
+      return function(freq) {
27
+        var d = [0.001, 0.01, 0.1]; // relative decay constants: freq < rise <  fall e.g. [0.001, 0.1, 0.2]
28
+        var t = 0.100 // 100 ms target time
29
+        var v = 0.25 // volume
30
+        function energy(channel) {
31
+          var c = [405, 4095];
32
+          var e = [29.5503, 3628.78];
33
+          return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
34
+        };
35
+        var f = energy(freq);
36
+        osc.frequency.setTargetAtTime(f, ac.currentTime + d[0] * t, d[0] * t);
37
+        gn.gain.setTargetAtTime(v, ac.currentTime + d[1] * t, d[1] * t);
38
+        gn.gain.setTargetAtTime(0.0, ac.currentTime + d[2] * t, d[2] * t);
39
+      }
40
+    })()
41
+  };
42
+
43
+  function connect() {
44
+    if (ctx.proto == 'binary') {
45
+      ctx.ws = new WebSocket(ctx.url, 'binary');
46
+      ctx.ws.binaryType = 'arraybuffer';
47
+    } else {
48
+      ctx.ws = new WebSocket(ctx.url);
49
+    }
50
+    ctx.ws.onerror = function(evt) {
51
+      console.log('WS ERROR: ' + evt.data);
52
+    };
53
+    ctx.ws.onopen = function(evt) {
54
+      console.log('WS OPENED: ' + ctx.url)
55
+    };
56
+    ctx.ws.onmessage = function(evt) {
57
+      //console.log('WS DATA:');
58
+      ctx.buffer += ctx.proto == 'binary' ? String.fromCharCode.apply(null, new Uint8Array(evt.data)) : evt.data;
59
+      var rows = ctx.buffer.split(ctx.separatorRows);
60
+      ctx.buffer = rows.pop(); // buffer incomplete rows
61
+      for (var r = 0; r < rows.length; r++) {
62
+        var cols = rows[r].split(ctx.separatorCols);
63
+        var row = [];
64
+        for (var c = 0; c < cols.length; c++) {
65
+          row.push((function(str) {
66
+            var num = parseFloat(str);
67
+            c > 0 ? ctx.ping(num) : true;
68
+            return isNaN(num) ? str : num;
69
+          })(cols[c]));
70
+        }
71
+        if (ctx.headers.length == 0) {
72
+          ctx.headers = row;
73
+        } else {
74
+          ctx.data.push(row);
75
+        }
76
+      }
77
+    };
78
+    ctx.ws.onclose = function(evt) {
79
+      console.log('WS CLOSED:' + evt.code);
80
+      window.setTimeout(connect, 1000); // retry every 1000 ms
81
+    };
82
+  }
83
+  connect();
84
+  this.getHeaders = function() { // get headers strings array
85
+    return ctx.headers;
86
+  };
87
+  this.getData = function(len) { // get len datasets and delete old data
88
+    return ctx.data.splice(0).slice(-1 * (len || 1));
89
+  };
90
+  return this;
91
+}
92
+
93
+
94
+function makeData(src, len) {
95
+  //console.log('makeData(src,' + len + ')');
96
+  len = len || 100;
97
+  var rows = src.getData(len);
98
+  var cols = src.getHeaders();
99
+  var data = [];
100
+  var vals = [];
101
+  for (var c = 0; c < cols.length; c++) {
102
+    vals = [];
103
+    for (var r = 0; r < rows.length; r++) {
104
+      var val = typeof(rows[r][c]) != 'undefined' ? rows[r][c] : null
105
+      //console.log( cols[c] + '[' + r + '] = ' + val);
106
+      vals.push(val);
107
+    }
108
+    data.push(vals);
109
+  }
110
+  var time = data && data.length > 0 ? data.shift() : null;
111
+  return {
112
+    'x': (function(n) {
113
+      for (x = []; n--; x.push(time));
114
+      return x
115
+    })(cols.length - 1),
116
+    'y': data,
117
+    'i': (function(n) {
118
+      for (a = []; n--; a[n] = n);
119
+      return a;
120
+    })(cols.length - 1)
121
+  };
122
+}
123
+
124
+
125
+function formatNames(str, find, replace) {
126
+  str = str.toString();
127
+  find = find || '_';
128
+  replace = replace || ' ';
129
+  return str.replace(
130
+    new RegExp(
131
+      find.replace(
132
+        new RegExp(
133
+          '([.*+?^=!:${}()|\[\]\/\\])',
134
+          'g'
135
+        ),
136
+        '\\$1'
137
+      ),
138
+      'g'
139
+    ),
140
+    replace
141
+  );
142
+}
143
+
144
+
145
+function makePlot(ctx, hdr) {
146
+  //console.log(JSON.stringify(hdr, null, "\t"));
147
+  var data = [];
148
+  var cmap = chroma.scale(chroma.bezier(ctx.data.line.color)).mode('lab').correctLightness(false).colors(hdr.length - 1, null);
149
+  for (var i = 1; i < hdr.length; i++) {
150
+    var trace = JSON.parse(JSON.stringify(ctx.data))
151
+    trace['type'] = 'scatter';
152
+    trace['mode'] = 'lines';
153
+    trace['fill'] = 'tonexty'; // 'tozeroy';
154
+    trace['connectgaps'] = true;
155
+    trace['name'] = formatNames(hdr[i]);
156
+    trace['x'] = new Array(ctx.maxPoints);
157
+    trace['y'] = new Array(ctx.maxPoints);
158
+    trace['line']['width'] = 1;
159
+    trace['line']['color'] = cmap[i - 1].css();
160
+    trace['fillcolor'] = cmap[i - 1].alpha(0.2).css()
161
+    data.push(trace);
162
+  }
163
+  document.title = ctx.layout.title;
164
+  return Plotly.plot(ctx.id, data, ctx.layout, ctx.config); // https://github.com/plotly/plotly.js/blob/master/src/plot_api/plot_api.js#L53
165
+}
166
+
167
+
168
+function makeConfig(id, ctx) {
169
+  var cmap = chroma.bezier(ctx.colors.graph).scale().colors(5);
170
+  var data = {
171
+    'name': 'trace', // replaced by tsv column headers
172
+    'type': 'scatter',
173
+    'mode': 'lines', // 'lines+markers',
174
+    'line': {
175
+      'shape': ctx.shape,
176
+      'smoothing': ctx.smoothing,
177
+      'color': ctx.colors.lines, // interpolated by colormap function
178
+      'width': ctx.size
179
+    },
180
+    'marker': {
181
+      'color': cmap[2],
182
+      'size': 1.5 * ctx.size
183
+    },
184
+    'y': [0] // replaced by tsv column data
185
+  };
186
+  var layout = {
187
+    'title': ctx.title.plot,
188
+    'titlefont': {
189
+      'family': 'Courier New, monospace',
190
+      'size': 24,
191
+      'color': cmap[0]
192
+    },
193
+    'autosize': true,
194
+    // 'width': window.innerWidth,
195
+    // 'height': window.innerHeight,
196
+    'plot_bgcolor': cmap[4],
197
+    'paper_bgcolor': cmap[4],
198
+    'xaxis': {
199
+      'title': ctx.title.xaxis,
200
+      'range': [0, 3650],
201
+      'color': cmap[1],
202
+      'titlefont': {
203
+        'family': 'Courier New, monospace',
204
+        'size': 18,
205
+        'color': cmap[0]
206
+      },
207
+      'tickwidth': ctx.size,
208
+      'tickcolor': cmap[1],
209
+      'linecolor': cmap[1],
210
+      'gridcolor': cmap[3],
211
+      'zerolinecolor': cmap[1],
212
+      'mirror': true
213
+    },
214
+    'yaxis': {
215
+      'title': ctx.title.yaxis,
216
+      'type': 'log',
217
+      'autorange': true,
218
+      'color': cmap[1],
219
+      'titlefont': {
220
+        'family': 'Courier New, monospace',
221
+        'size': 18,
222
+        'color': cmap[0]
223
+      },
224
+      'tickwidth': ctx.size,
225
+      'tickcolor': cmap[1],
226
+      'linecolor': cmap[1],
227
+      'gridcolor': cmap[3],
228
+      'zerolinecolor': cmap[1],
229
+      'mirror': true
230
+    },
231
+    'margin': {
232
+      'l': 64,
233
+      'r': 32,
234
+      'b': 64,
235
+      't': 80,
236
+      'pad': 0
237
+    }
238
+  };
239
+  var config = {
240
+    'scrollZoom': true,
241
+    'showLink': false,
242
+    'displaylogo': false,
243
+    'modeBarButtonsToAdd': [
244
+      {
245
+        'name': 'Toggle Lin/Log',
246
+        'click': function(gd) {
247
+          Plotly.relayout(gd, 'yaxis.type', gd.layout.yaxis.type == 'log' ? 'linear' : 'log');
248
+        },
249
+        'icon': {
250
+          'width': 1000,
251
+          'ascent': 850,
252
+          'descent': 0,
253
+          'path': 'm 598.75596,109.5 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.624987 V 62.624975 c 0,-8.632812 6.99222,-15.624987 15.62503,-15.624987 H 770.63093 V -77.999996 H 598.75596 c -8.63281,0 -15.62503,-6.99221 -15.62503,-15.62499 v -31.250024 c 0,-8.63283 6.99222,-15.625 15.62503,-15.625 H 770.63093 V -265.49999 H 598.75596 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.625 v -31.25002 c 0,-8.63283 6.99222,-15.625 15.62503,-15.625 H 770.63093 V -452.99999 H 598.75596 c -8.63281,0 -15.62503,-6.99222 -15.62503,-15.625 v -31.25002 c 0,-8.63283 6.99222,-15.62499 15.62503,-15.62499 H 770.63093 V -640.49999 C 770.63093,-675.01175 742.64268,-703 708.13096,-703 h -375 c -34.51171,0 -62.50001,27.98825 -62.50001,62.50001 v 874.99998 c 0,34.51171 27.9883,62.50001 62.50001,62.50001 h 375 c 34.51172,0 62.49997,-27.9883 62.49997,-62.50001 V 109.5 Z'
254
+        }
255
+      }
256
+    ],
257
+    'mmodeBarButtonsToRemove': ['sendDataToCloud']
258
+  };
259
+  return {
260
+    'id': id,
261
+    'data': data,
262
+    'layout': layout,
263
+    'config': config,
264
+    'maxPoints': ctx.maxPoints
265
+  };
266
+}
267
+
268
+
269
+function createGraph(id, ctx) {
270
+  ctx = { // sane defaults
271
+    'src': {
272
+      'stream': ctx.src.stream || '/data/stream.ws',
273
+      'init': ctx.src.init || '/data/hist.tsv'
274
+    },
275
+    'title': {
276
+      'plot': ctx.title.plot || 'Plot Title',
277
+      'xaxis': ctx.title.xaxis || 'Time / s',
278
+      'yaxis': ctx.title.yaxis || 'Measured Entity / a.u.'
279
+    },
280
+    'colors': {
281
+      'graph': ctx.colors.graph || ['black', 'white'],
282
+      'lines': ctx.colors.lines || ['blue', 'cyan', 'lightgreen', 'yellow', 'red']
283
+    },
284
+    'maxPoints': ctx.maxPoints || 100,
285
+    'size': ctx.size || 2,
286
+    'scale': ctx.scale || 1.5,
287
+    'shape': ctx.shape || 'linear',
288
+    'smoothing': ctx.smoothing || 1
289
+  };
290
+
291
+  var src = dataSrc([
292
+    location.protocol == 'https:' ? 'wss://' : 'ws://',
293
+    location.hostname,
294
+    location.protocol == 'https:' ? ':443' : ':80',
295
+    ctx.src.stream
296
+  ].join(''));
297
+
298
+  var setup = window.setInterval(function() { // wait for headers to arrive
299
+    var headers = src.getHeaders();
300
+    if (headers.length > 0) {
301
+      clearInterval(setup);
302
+      makePlot(makeConfig(id, ctx), headers);
303
+
304
+      function loadInitData(dataUrl) {
305
+        var xmlhttp = new XMLHttpRequest();
306
+        xmlhttp.open("GET", dataUrl); // xmlhttp.open("GET", url, false);
307
+        xmlhttp.onreadystatechange = function() {
308
+          if ((xmlhttp.status == 200) && (xmlhttp.readyState == 4)) {
309
+            var maxPoints = ctx.maxPoints || 4096;
310
+
311
+            function energy(channel) {
312
+              var c = [405, 4095];
313
+              var e = [29.5503, 3628.78];
314
+              return e[0] + (e[1] - e[0]) * (channel - c[0]) / (c[1] - c[0]);
315
+            };
316
+
317
+            var data = {
318
+              'x': [(function(n) {
319
+                for (a = []; n--; a[n] = energy(n));
320
+                return a;
321
+              })(maxPoints)],
322
+              'y': [
323
+                []
324
+              ],
325
+              'i': [],
326
+              't': 0,
327
+              'e': 0
328
+            };
329
+
330
+            var initData = xmlhttp.responseText.split("\n").slice(0, maxPoints).map(parseFloat);
331
+            for (i = 0; i < maxPoints; i++) { // prefill initial data
332
+              var val = initData[i];
333
+              if (isNaN(val)) {
334
+                data.y[0][i] = 0;
335
+              } else {
336
+                data.y[0][i] = val;
337
+                data.e += val;
338
+              }
339
+            }
340
+            //console.log(data);
341
+
342
+            var res = true;
343
+            res &= Plotly.relayout(id, {
344
+              'title': ctx.title.plot + ' ' + data.t + 's',
345
+              'yaxis.title': ctx.title.yaxis + ' / ' + data.e
346
+            }).catch(function(e) {
347
+              //console.log('ERROR[Plotly.relayout()]: ' + e);
348
+            });
349
+
350
+            res &= Plotly.extendTraces(id, {
351
+              'x': data.x,
352
+              'y': data.y
353
+            }, [0], maxPoints).catch(function(e) {
354
+              //console.log('ERROR[Plotly.extendTraces()]: ' + e);
355
+            });
356
+
357
+            var update = window.setInterval(function() { // update plot in realtime
358
+
359
+              var rawdata = makeData(src, 1024 * 1024 * 1024); // get at most 1 GB of events
360
+
361
+              data.i = (function(n) {
362
+                for (a = []; n--; a[n] = n);
363
+                return a;
364
+              })(rawdata.y.length);
365
+
366
+              for (i = 0; i < rawdata.y.length; i++) {
367
+                for (j = 0; j < rawdata.y[i].length; j++) {
368
+                  data.y[i][rawdata.y[i][j]] += 1;
369
+                  data.e += 1
370
+                }
371
+              }
372
+
373
+              var evtTime = rawdata.x[0][rawdata.x[0].length - 1];
374
+              data.t = evtTime ? evtTime : data.t;
375
+
376
+              return data.i.length > 0 ? (function() {
377
+                var res = true;
378
+                res &= Plotly.relayout(id, {
379
+                  'title': ctx.title.plot + ' ' + data.t + 's',
380
+                  'yaxis.title': ctx.title.yaxis + ' / ' + data.e
381
+                }).catch(function(e) {
382
+                  //console.log('ERROR[Plotly.relayout()]: ' + e);
383
+                });
384
+
385
+                res &= Plotly.extendTraces(id, {
386
+                  'x': data.x,
387
+                  'y': data.y
388
+                }, data.i, maxPoints).catch(function(e) {
389
+                  //console.log('ERROR[Plotly.extendTraces()]: ' + e);
390
+                });
391
+                return res;
392
+
393
+              })() : true;
394
+            }, 500); // time resolution 500ms
395
+          }
396
+        };
397
+        xmlhttp.send();
398
+      }
399
+      loadInitData(ctx.src.init);
400
+    }
401
+  }, 500);
402
+}

+ 34
- 0
service/datasrc.service Vedi File

@@ -0,0 +1,34 @@
1
+[Unit]
2
+Description=Data Source
3
+After=multi-user.target
4
+
5
+[Service]
6
+Type=simple
7
+
8
+User=www-data
9
+Group=www-data
10
+
11
+Environment=LISTEN_HOST=127.0.0.1
12
+Environment=LISTEN_PORT=5900
13
+Environment=TARGET_FILE=/tmp/event.tsv
14
+Environment=DATA_MAX_SIZE=0
15
+#Environment=DATA_MAX_SIZE=1073741824
16
+#EnvironmentFile=/etc/default/datasrc
17
+
18
+PermissionsStartOnly=true
19
+ExecStartPre=/usr/bin/touch ${TARGET_FILE}
20
+ExecStartPre=/bin/chown www-data:www-data ${TARGET_FILE}
21
+
22
+WorkingDirectory=/tmp
23
+ExecStart=/usr/bin/socat \
24
+        TCP-LISTEN:${LISTEN_PORT},bind=${LISTEN_HOST},reuseaddr,fork \
25
+        "EXEC:/usr/bin/inotail -n ${DATA_MAX_SIZE} -f ${TARGET_FILE}"
26
+
27
+NoNewPrivileges=true
28
+RestartSec=5
29
+Restart=on-failure
30
+
31
+[Install]
32
+WantedBy=multi-user.target
33
+
34
+

+ 35
- 0
service/radangel.service Vedi File

@@ -0,0 +1,35 @@
1
+[Unit]
2
+Description=RadAngel gamma spectrometer
3
+After=multi-user.target
4
+
5
+[Service]
6
+Type=simple
7
+
8
+User=www-data
9
+Group=www-data
10
+
11
+Environment=EVENT_FILE=/tmp/event.tsv
12
+Environment=STATS_FILE=/tmp/stats.tsv
13
+#EnvironmentFile=/etc/default/radangel
14
+
15
+PermissionsStartOnly=true
16
+ExecStartPre=/usr/bin/touch ${EVENT_FILE}
17
+ExecStartPre=/usr/bin/touch ${STATS_FILE}
18
+ExecStartPre=/bin/chown www-data:www-data ${EVENT_FILE}
19
+ExecStartPre=/bin/chown www-data:www-data ${STATS_FILE}
20
+ExecStartPre=/bin/chmod o+r ${EVENT_FILE}
21
+ExecStartPre=/bin/chmod o+r ${STATS_FILE}
22
+
23
+WorkingDirectory=/opt/radangel
24
+ExecStart=/opt/radangel/radangel --verbose \
25
+	--event ${EVENT_FILE}              \
26
+	--stats ${STATS_FILE}
27
+
28
+NoNewPrivileges=true
29
+RestartSec=5
30
+Restart=on-failure
31
+
32
+[Install]
33
+WantedBy=multi-user.target
34
+
35
+

+ 35
- 0
service/websockify.service Vedi File

@@ -0,0 +1,35 @@
1
+[Unit]
2
+Description=Start websockify
3
+After=multi-user.target
4
+
5
+[Service]
6
+Type=forking
7
+
8
+User=www-data
9
+Group=www-data
10
+
11
+Environment=LISTEN_HOST=127.0.0.1
12
+Environment=LISTEN_PORT=5800
13
+Environment=TARGET_HOST=127.0.0.1
14
+Environment=TARGET_PORT=5900
15
+#EnvironmentFile=/etc/default/websockify
16
+
17
+PIDFile=/var/run/websockify.pid
18
+PermissionsStartOnly=true
19
+ExecStartPre=/bin/sh -c '\
20
+	touch /var/run/websockify.pid; \
21
+	chown www-data:www-data /var/run/websockify.pid'
22
+WorkingDirectory=/tmp
23
+ExecStart=/opt/websockify/websockify --daemon --pid /var/run/websockify.pid \
24
+	${LISTEN_HOST}:${LISTEN_PORT} \
25
+	${TARGET_HOST}:${TARGET_PORT}
26
+
27
+NoNewPrivileges=true
28
+RestartSec=5
29
+Restart=on-failure
30
+
31
+
32
+[Install]
33
+WantedBy=multi-user.target
34
+
35
+

+ 13
- 0
src/Makefile Vedi File

@@ -0,0 +1,13 @@
1
+TARGETS=radangel
2
+CFLAGS += -fPIC
3
+
4
+all: $(TARGETS)
5
+
6
+radangel: radangel.o
7
+	$(CC) -std=gnu99 -Wall $(LDFLAGS) $^ -pthread -lm -lhidapi-libusb -o $@
8
+
9
+radangel.o: radangel.c
10
+
11
+clean:
12
+	rm -f radangel *.o
13
+

+ 15
- 0
src/contrib/Makefile Vedi File

@@ -0,0 +1,15 @@
1
+TARGETS=websockify
2
+CFLAGS += -fPIC
3
+
4
+all: $(TARGETS)
5
+
6
+websockify: websockify.o websocket.o base64.o
7
+	$(CC) $(LDFLAGS) $^ -lssl -lcrypto -o $@
8
+
9
+websocket.o: websocket.c websocket.h
10
+websockify.o: websockify.c websocket.h
11
+base64.o: base64.c
12
+
13
+clean:
14
+	rm -f websockify *.o
15
+

+ 313
- 0
src/contrib/base64.c Vedi File

@@ -0,0 +1,313 @@
1
+/*
2
+ * Copyright (c) 1996-1999 by Internet Software Consortium.
3
+ *
4
+ * Permission to use, copy, modify, and distribute this software for any
5
+ * purpose with or without fee is hereby granted, provided that the above
6
+ * copyright notice and this permission notice appear in all copies.
7
+ *
8
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
9
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
10
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
11
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
13
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
14
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15
+ * SOFTWARE.
16
+ */
17
+
18
+/*
19
+ * Portions Copyright (c) 1995 by International Business Machines, Inc.
20
+ *
21
+ * International Business Machines, Inc. (hereinafter called IBM) grants
22
+ * permission under its copyrights to use, copy, modify, and distribute this
23
+ * Software with or without fee, provided that the above copyright notice and
24
+ * all paragraphs of this notice appear in all copies, and that the name of IBM
25
+ * not be used in connection with the marketing of any product incorporating
26
+ * the Software or modifications thereof, without specific, written prior
27
+ * permission.
28
+ *
29
+ * To the extent it has a right to do so, IBM grants an immunity from suit
30
+ * under its patents, if any, for the use, sale or manufacture of products to
31
+ * the extent that such products are used for performing Domain Name System
32
+ * dynamic updates in TCP/IP networks by means of the Software.  No immunity is
33
+ * granted for any product per se or for any other function of any product.
34
+ *
35
+ * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES,
36
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
37
+ * PARTICULAR PURPOSE.  IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL,
38
+ * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING
39
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN
40
+ * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES.
41
+ */
42
+
43
+#if !defined(LINT) && !defined(CODECENTER)
44
+static const char rcsid[] = "$BINDId: base64.c,v 8.7 1999/10/13 16:39:33 vixie Exp $";
45
+#endif /* not lint */
46
+
47
+#include <sys/types.h>
48
+#include <sys/param.h>
49
+#include <sys/socket.h>
50
+
51
+#include <netinet/in.h>
52
+#include <arpa/inet.h>
53
+#include <arpa/nameser.h>
54
+
55
+#include <ctype.h>
56
+#include <resolv.h>
57
+#include <stdio.h>
58
+#include <stdlib.h>
59
+#include <string.h>
60
+
61
+#define Assert(Cond) if (!(Cond)) abort()
62
+
63
+static const char Base64[] =
64
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
65
+static const char Pad64 = '=';
66
+
67
+/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt)
68
+   The following encoding technique is taken from RFC 1521 by Borenstein
69
+   and Freed.  It is reproduced here in a slightly edited form for
70
+   convenience.
71
+
72
+   A 65-character subset of US-ASCII is used, enabling 6 bits to be
73
+   represented per printable character. (The extra 65th character, "=",
74
+   is used to signify a special processing function.)
75
+
76
+   The encoding process represents 24-bit groups of input bits as output
77
+   strings of 4 encoded characters. Proceeding from left to right, a
78
+   24-bit input group is formed by concatenating 3 8-bit input groups.
79
+   These 24 bits are then treated as 4 concatenated 6-bit groups, each
80
+   of which is translated into a single digit in the base64 alphabet.
81
+
82
+   Each 6-bit group is used as an index into an array of 64 printable
83
+   characters. The character referenced by the index is placed in the
84
+   output string.
85
+
86
+                         Table 1: The Base64 Alphabet
87
+
88
+      Value Encoding  Value Encoding  Value Encoding  Value Encoding
89
+          0 A            17 R            34 i            51 z
90
+          1 B            18 S            35 j            52 0
91
+          2 C            19 T            36 k            53 1
92
+          3 D            20 U            37 l            54 2
93
+          4 E            21 V            38 m            55 3
94
+          5 F            22 W            39 n            56 4
95
+          6 G            23 X            40 o            57 5
96
+          7 H            24 Y            41 p            58 6
97
+          8 I            25 Z            42 q            59 7
98
+          9 J            26 a            43 r            60 8
99
+         10 K            27 b            44 s            61 9
100
+         11 L            28 c            45 t            62 +
101
+         12 M            29 d            46 u            63 /
102
+         13 N            30 e            47 v
103
+         14 O            31 f            48 w         (pad) =
104
+         15 P            32 g            49 x
105
+         16 Q            33 h            50 y
106
+
107
+   Special processing is performed if fewer than 24 bits are available
108
+   at the end of the data being encoded.  A full encoding quantum is
109
+   always completed at the end of a quantity.  When fewer than 24 input
110
+   bits are available in an input group, zero bits are added (on the
111
+   right) to form an integral number of 6-bit groups.  Padding at the
112
+   end of the data is performed using the '=' character.
113
+
114
+   Since all base64 input is an integral number of octets, only the
115
+         -------------------------------------------------
116
+   following cases can arise:
117
+
118
+       (1) the final quantum of encoding input is an integral
119
+           multiple of 24 bits; here, the final unit of encoded
120
+	   output will be an integral multiple of 4 characters
121
+	   with no "=" padding,
122
+       (2) the final quantum of encoding input is exactly 8 bits;
123
+           here, the final unit of encoded output will be two
124
+	   characters followed by two "=" padding characters, or
125
+       (3) the final quantum of encoding input is exactly 16 bits;
126
+           here, the final unit of encoded output will be three
127
+	   characters followed by one "=" padding character.
128
+   */
129
+
130
+int
131
+ws_b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) {
132
+	size_t datalength = 0;
133
+	u_char input[3];
134
+	u_char output[4];
135
+	size_t i;
136
+
137
+	while (2 < srclength) {
138
+		input[0] = *src++;
139
+		input[1] = *src++;
140
+		input[2] = *src++;
141
+		srclength -= 3;
142
+
143
+		output[0] = input[0] >> 2;
144
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
145
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
146
+		output[3] = input[2] & 0x3f;
147
+		Assert(output[0] < 64);
148
+		Assert(output[1] < 64);
149
+		Assert(output[2] < 64);
150
+		Assert(output[3] < 64);
151
+
152
+		if (datalength + 4 > targsize)
153
+			return (-1);
154
+		target[datalength++] = Base64[output[0]];
155
+		target[datalength++] = Base64[output[1]];
156
+		target[datalength++] = Base64[output[2]];
157
+		target[datalength++] = Base64[output[3]];
158
+	}
159
+
160
+	/* Now we worry about padding. */
161
+	if (0 != srclength) {
162
+		/* Get what's left. */
163
+		input[0] = input[1] = input[2] = '\0';
164
+		for (i = 0; i < srclength; i++)
165
+			input[i] = *src++;
166
+
167
+		output[0] = input[0] >> 2;
168
+		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
169
+		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
170
+		Assert(output[0] < 64);
171
+		Assert(output[1] < 64);
172
+		Assert(output[2] < 64);
173
+
174
+		if (datalength + 4 > targsize)
175
+			return (-1);
176
+		target[datalength++] = Base64[output[0]];
177
+		target[datalength++] = Base64[output[1]];
178
+		if (srclength == 1)
179
+			target[datalength++] = Pad64;
180
+		else
181
+			target[datalength++] = Base64[output[2]];
182
+		target[datalength++] = Pad64;
183
+	}
184
+	if (datalength >= targsize)
185
+		return (-1);
186
+	target[datalength] = '\0';	/* Returned value doesn't count \0. */
187
+	return (datalength);
188
+}
189
+//libresolv_hidden_def (b64_ntop)
190
+
191
+/* skips all whitespace anywhere.
192
+   converts characters, four at a time, starting at (or after)
193
+   src from base - 64 numbers into three 8 bit bytes in the target area.
194
+   it returns the number of data bytes stored at the target, or -1 on error.
195
+ */
196
+
197
+int
198
+ws_b64_pton(char const *src, u_char *target, size_t targsize) {
199
+	int tarindex, state, ch;
200
+	char *pos;
201
+
202
+	state = 0;
203
+	tarindex = 0;
204
+
205
+	while ((ch = *src++) != '\0') {
206
+		if (isspace(ch))	/* Skip whitespace anywhere. */
207
+			continue;
208
+
209
+		if (ch == Pad64)
210
+			break;
211
+
212
+		pos = strchr(Base64, ch);
213
+		if (pos == 0) 		/* A non-base64 character. */
214
+			return (-1);
215
+
216
+		switch (state) {
217
+		case 0:
218
+			if (target) {
219
+				if ((size_t)tarindex >= targsize)
220
+					return (-1);
221
+				target[tarindex] = (pos - Base64) << 2;
222
+			}
223
+			state = 1;
224
+			break;
225
+		case 1:
226
+			if (target) {
227
+				if ((size_t)tarindex + 1 >= targsize)
228
+					return (-1);
229
+				target[tarindex]   |=  (pos - Base64) >> 4;
230
+				target[tarindex+1]  = ((pos - Base64) & 0x0f)
231
+							<< 4 ;
232
+			}
233
+			tarindex++;
234
+			state = 2;
235
+			break;
236
+		case 2:
237
+			if (target) {
238
+				if ((size_t)tarindex + 1 >= targsize)
239
+					return (-1);
240
+				target[tarindex]   |=  (pos - Base64) >> 2;
241
+				target[tarindex+1]  = ((pos - Base64) & 0x03)
242
+							<< 6;
243
+			}
244
+			tarindex++;
245
+			state = 3;
246
+			break;
247
+		case 3:
248
+			if (target) {
249
+				if ((size_t)tarindex >= targsize)
250
+					return (-1);
251
+				target[tarindex] |= (pos - Base64);
252
+			}
253
+			tarindex++;
254
+			state = 0;
255
+			break;
256
+		default:
257
+			abort();
258
+		}
259
+	}
260
+
261
+	/*
262
+	 * We are done decoding Base-64 chars.  Let's see if we ended
263
+	 * on a byte boundary, and/or with erroneous trailing characters.
264
+	 */
265
+
266
+	if (ch == Pad64) {		/* We got a pad char. */
267
+		ch = *src++;		/* Skip it, get next. */
268
+		switch (state) {
269
+		case 0:		/* Invalid = in first position */
270
+		case 1:		/* Invalid = in second position */
271
+			return (-1);
272
+
273
+		case 2:		/* Valid, means one byte of info */
274
+			/* Skip any number of spaces. */
275
+			for ((void)NULL; ch != '\0'; ch = *src++)
276
+				if (!isspace(ch))
277
+					break;
278
+			/* Make sure there is another trailing = sign. */
279
+			if (ch != Pad64)
280
+				return (-1);
281
+			ch = *src++;		/* Skip the = */
282
+			/* Fall through to "single trailing =" case. */
283
+			/* FALLTHROUGH */
284
+
285
+		case 3:		/* Valid, means two bytes of info */
286
+			/*
287
+			 * We know this char is an =.  Is there anything but
288
+			 * whitespace after it?
289
+			 */
290
+			for ((void)NULL; ch != '\0'; ch = *src++)
291
+				if (!isspace(ch))
292
+					return (-1);
293
+
294
+			/*
295
+			 * Now make sure for cases 2 and 3 that the "extra"
296
+			 * bits that slopped past the last full byte were
297
+			 * zeros.  If we don't check them, they become a
298
+			 * subliminal channel.
299
+			 */
300
+			if (target && target[tarindex] != 0)
301
+				return (-1);
302
+		}
303
+	} else {
304
+		/*
305
+		 * We ended by seeing the end of the string.  Make sure we
306
+		 * have no partial bytes lying around.
307
+		 */
308
+		if (state != 0)
309
+			return (-1);
310
+	}
311
+
312
+	return (tarindex);
313
+}

+ 866
- 0
src/contrib/websocket.c Vedi File

@@ -0,0 +1,866 @@
1
+/*
2
+ * WebSocket lib with support for "wss://" encryption.
3
+ * Copyright 2010 Joel Martin
4
+ * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
5
+ *
6
+ * You can make a cert/key with openssl using:
7
+ * openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
8
+ * as taken from http://docs.python.org/dev/library/ssl.html#certificates
9
+ */
10
+#include <unistd.h>
11
+#include <stdio.h>
12
+#include <stdlib.h>
13
+#include <errno.h>
14
+#include <strings.h>
15
+#include <sys/types.h> 
16
+#include <sys/socket.h>
17
+#include <sys/stat.h>
18
+#include <netinet/in.h>
19
+#include <arpa/inet.h>
20
+#include <netdb.h>
21
+#include <signal.h> // daemonizing
22
+#include <fcntl.h>  // daemonizing
23
+#include <openssl/err.h>
24
+#include <openssl/ssl.h>
25
+#include <openssl/md5.h> /* md5 hash */
26
+#include <openssl/sha.h> /* sha1 hash */
27
+#include "websocket.h"
28
+
29
+int ws_b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize);
30
+int ws_b64_pton(char const *src, u_char *target, size_t targsize);
31
+
32
+/*
33
+ * Global state
34
+ *
35
+ *   Warning: not thread safe
36
+ */
37
+int ssl_initialized = 0;
38
+int pipe_error = 0;
39
+settings_t settings;
40
+
41
+
42
+void traffic(const char * token) {
43
+    if ((settings.verbose) && (! settings.daemon)) {
44
+        fprintf(stdout, "%s", token);
45
+        fflush(stdout);
46
+    }
47
+}
48
+
49
+void error(char *msg)
50
+{
51
+    perror(msg);
52
+}
53
+
54
+void fatal(char *msg)
55
+{
56
+    perror(msg);
57
+    exit(1);
58
+}
59
+
60
+/* resolve host with also IP address parsing */ 
61
+int resolve_host(struct in_addr *sin_addr, const char *hostname) 
62
+{ 
63
+    if (!inet_aton(hostname, sin_addr)) { 
64
+        struct addrinfo *ai, *cur; 
65
+        struct addrinfo hints; 
66
+        memset(&hints, 0, sizeof(hints)); 
67
+        hints.ai_family = AF_INET; 
68
+        if (getaddrinfo(hostname, NULL, &hints, &ai)) 
69
+            return -1; 
70
+        for (cur = ai; cur; cur = cur->ai_next) { 
71
+            if (cur->ai_family == AF_INET) { 
72
+                *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr; 
73
+                freeaddrinfo(ai); 
74
+                return 0; 
75
+            } 
76
+        } 
77
+        freeaddrinfo(ai); 
78
+        return -1; 
79
+    } 
80
+    return 0; 
81
+} 
82
+
83
+
84
+/*
85
+ * SSL Wrapper Code
86
+ */
87
+
88
+ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len) {
89
+    if (ctx->ssl) {
90
+        //handler_msg("SSL recv\n");
91
+        return SSL_read(ctx->ssl, buf, len);
92
+    } else {
93
+        return recv(ctx->sockfd, buf, len, 0);
94
+    }
95
+}
96
+
97
+ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len) {
98
+    if (ctx->ssl) {
99
+        //handler_msg("SSL send\n");
100
+        return SSL_write(ctx->ssl, buf, len);
101
+    } else {
102
+        return send(ctx->sockfd, buf, len, 0);
103
+    }
104
+}
105
+
106
+ws_ctx_t *alloc_ws_ctx() {
107
+    ws_ctx_t *ctx;
108
+    if (! (ctx = malloc(sizeof(ws_ctx_t))) )
109
+        { fatal("malloc()"); }
110
+
111
+    if (! (ctx->cin_buf = malloc(BUFSIZE)) )
112
+        { fatal("malloc of cin_buf"); }
113
+    if (! (ctx->cout_buf = malloc(BUFSIZE)) )
114
+        { fatal("malloc of cout_buf"); }
115
+    if (! (ctx->tin_buf = malloc(BUFSIZE)) )
116
+        { fatal("malloc of tin_buf"); }
117
+    if (! (ctx->tout_buf = malloc(BUFSIZE)) )
118
+        { fatal("malloc of tout_buf"); }
119
+
120
+    ctx->headers = malloc(sizeof(headers_t));
121
+    ctx->ssl = NULL;
122
+    ctx->ssl_ctx = NULL;
123
+    return ctx;
124
+}
125
+
126
+void free_ws_ctx(ws_ctx_t *ctx) {
127
+    free(ctx->cin_buf);
128
+    free(ctx->cout_buf);
129
+    free(ctx->tin_buf);
130
+    free(ctx->tout_buf);
131
+    free(ctx);
132
+}
133
+
134
+ws_ctx_t *ws_socket(ws_ctx_t *ctx, int socket) {
135
+    ctx->sockfd = socket;
136
+    return ctx;
137
+}
138
+
139
+ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, char * certfile, char * keyfile) {
140
+    int ret;
141
+    char msg[1024];
142
+    char * use_keyfile;
143
+    ws_socket(ctx, socket);
144
+
145
+    if (keyfile && (keyfile[0] != '\0')) {
146
+        // Separate key file
147
+        use_keyfile = keyfile;
148
+    } else {
149
+        // Combined key and cert file
150
+        use_keyfile = certfile;
151
+    }
152
+
153
+    // Initialize the library
154
+    if (! ssl_initialized) {
155
+        SSL_library_init();
156
+        OpenSSL_add_all_algorithms();
157
+        SSL_load_error_strings();
158
+        ssl_initialized = 1;
159
+
160
+    }
161
+
162
+    ctx->ssl_ctx = SSL_CTX_new(TLSv1_server_method());
163
+    if (ctx->ssl_ctx == NULL) {
164
+        ERR_print_errors_fp(stderr);
165
+        fatal("Failed to configure SSL context");
166
+    }
167
+
168
+    if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, use_keyfile,
169
+                                    SSL_FILETYPE_PEM) <= 0) {
170
+        sprintf(msg, "Unable to load private key file %s\n", use_keyfile);
171
+        fatal(msg);
172
+    }
173
+
174
+    if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, certfile,
175
+                                     SSL_FILETYPE_PEM) <= 0) {
176
+        sprintf(msg, "Unable to load certificate file %s\n", certfile);
177
+        fatal(msg);
178
+    }
179
+
180
+//    if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, "DEFAULT") != 1) {
181
+//        sprintf(msg, "Unable to set cipher\n");
182
+//        fatal(msg);
183
+//    }
184
+
185
+    // Associate socket and ssl object
186
+    ctx->ssl = SSL_new(ctx->ssl_ctx);
187
+    SSL_set_fd(ctx->ssl, socket);
188
+
189
+    ret = SSL_accept(ctx->ssl);
190
+    if (ret < 0) {
191
+        ERR_print_errors_fp(stderr);
192
+        return NULL;
193
+    }
194
+
195
+    return ctx;
196
+}
197
+
198
+void ws_socket_free(ws_ctx_t *ctx) {
199
+    if (ctx->ssl) {
200
+        SSL_free(ctx->ssl);
201
+        ctx->ssl = NULL;
202
+    }
203
+    if (ctx->ssl_ctx) {
204
+        SSL_CTX_free(ctx->ssl_ctx);
205
+        ctx->ssl_ctx = NULL;
206
+    }
207
+    if (ctx->sockfd) {
208
+        shutdown(ctx->sockfd, SHUT_RDWR);
209
+        close(ctx->sockfd);
210
+        ctx->sockfd = 0;
211
+    }
212
+}
213
+
214
+/* ------------------------------------------------------- */
215
+
216
+
217
+int encode_hixie(u_char const *src, size_t srclength,
218
+                 char *target, size_t targsize) {
219
+    int sz = 0, len = 0;
220
+    target[sz++] = '\x00';
221
+    len = ws_b64_ntop(src, srclength, target+sz, targsize-sz);
222
+    if (len < 0) {
223
+        return len;
224
+    }
225
+    sz += len;
226
+    target[sz++] = '\xff';
227
+    return sz;
228
+}
229
+
230
+int decode_hixie(char *src, size_t srclength,
231
+                 u_char *target, size_t targsize,
232
+                 unsigned int *opcode, unsigned int *left) {
233
+    char *start, *end, cntstr[4];
234
+    int i, len, framecount = 0, retlen = 0;
235
+    unsigned char chr;
236
+    if ((src[0] != '\x00') || (src[srclength-1] != '\xff')) {
237
+        handler_emsg("WebSocket framing error\n");
238
+        return -1;
239
+    }
240
+    *left = srclength;
241
+
242
+    if (srclength == 2 &&
243
+        (src[0] == '\xff') && 
244
+        (src[1] == '\x00')) {
245
+        // client sent orderly close frame
246
+        *opcode = 0x8; // Close frame
247
+        return 0;
248
+    }
249
+    *opcode = 0x1; // Text frame
250
+
251
+    start = src+1; // Skip '\x00' start
252
+    do {
253
+        /* We may have more than one frame */
254
+        end = (char *)memchr(start, '\xff', srclength);
255
+        *end = '\x00';
256
+        len = ws_b64_pton(start, target+retlen, targsize-retlen);
257
+        if (len < 0) {
258
+            return len;
259
+        }
260
+        retlen += len;
261
+        start = end + 2; // Skip '\xff' end and '\x00' start 
262
+        framecount++;
263
+    } while (end < (src+srclength-1));
264
+    if (framecount > 1) {
265
+        snprintf(cntstr, 3, "%d", framecount);
266
+        traffic(cntstr);
267
+    }
268
+    *left = 0;
269
+    return retlen;
270
+}
271
+
272
+int encode_hybi(u_char const *src, size_t srclength,
273
+                char *target, size_t targsize, unsigned int opcode)
274
+{
275
+    unsigned long long b64_sz, len_offset = 1, payload_offset = 2;
276
+    int len = 0;
277
+
278
+    if (opcode != OPCODE_TEXT && opcode != OPCODE_BINARY) {
279
+        handler_emsg("Invalid opcode. Opcode must be 0x01 for text mode, or 0x02 for binary mode.\n");
280
+        return -1;
281
+    }
282
+
283
+    target[0] = (char)((opcode & 0x0F) | 0x80);
284
+
285
+    if ((int)srclength <= 0) {
286
+        return 0;
287
+    }
288
+
289
+    if (opcode & OPCODE_TEXT) {
290
+        len = ((srclength - 1) / 3) * 4 + 4;
291
+    } else {
292
+        len = srclength;
293
+    }
294
+
295
+    if (len <= 125) {
296
+        target[1] = (char) len;
297
+        payload_offset = 2;
298
+    } else if ((len > 125) && (len < 65536)) {
299
+        target[1] = (char) 126;
300
+        *(u_short*)&(target[2]) = htons(len);
301
+        payload_offset = 4;
302
+    } else {
303
+        handler_emsg("Sending frames larger than 65535 bytes not supported\n");
304
+        return -1;
305
+        //target[1] = (char) 127;
306
+        //*(u_long*)&(target[2]) = htonl(b64_sz);
307
+        //payload_offset = 10;
308
+    }
309
+
310
+    if (opcode & OPCODE_TEXT) {
311
+        len = ws_b64_ntop(src, srclength, target+payload_offset, targsize-payload_offset);
312
+    } else {
313
+        memcpy(target+payload_offset, src, srclength);
314
+        len = srclength;
315
+    }
316
+
317
+    if (len < 0) {
318
+        return len;
319
+    }
320
+
321
+    return len + payload_offset;
322
+}
323
+
324
+int decode_hybi(unsigned char *src, size_t srclength,
325
+                u_char *target, size_t targsize,
326
+                unsigned int *opcode, unsigned int *left)
327
+{
328
+    unsigned char *frame, *mask, *payload, save_char;
329
+    char cntstr[4];
330
+    int masked = 0;
331
+    int i = 0, len, framecount = 0;
332
+    size_t remaining;
333
+    unsigned int target_offset = 0, hdr_length = 0, payload_length = 0;
334
+    
335
+    *left = srclength;
336
+    frame = src;
337
+
338
+    //printf("Deocde new frame\n");
339
+    while (1) {
340
+        // Need at least two bytes of the header
341
+        // Find beginning of next frame. First time hdr_length, masked and
342
+        // payload_length are zero
343
+        frame += hdr_length + 4*masked + payload_length;
344
+        //printf("frame[0..3]: 0x%x 0x%x 0x%x 0x%x (tot: %d)\n",
345
+        //       (unsigned char) frame[0],
346
+        //       (unsigned char) frame[1],
347
+        //       (unsigned char) frame[2],
348
+        //       (unsigned char) frame[3], srclength);
349
+
350
+        if (frame > src + srclength) {
351
+            //printf("Truncated frame from client, need %d more bytes\n", frame - (src + srclength) );
352
+            break;
353
+        }
354
+        remaining = (src + srclength) - frame;
355
+        if (remaining < 2) {
356
+            //printf("Truncated frame header from client\n");
357
+            break;
358
+        }
359
+        framecount ++;
360
+
361
+        *opcode = frame[0] & 0x0f;
362
+        masked = (frame[1] & 0x80) >> 7;
363
+
364
+        if (*opcode == 0x8) {
365
+            // client sent orderly close frame
366
+            break;
367
+        }
368
+
369
+        payload_length = frame[1] & 0x7f;
370
+        if (payload_length < 126) {
371
+            hdr_length = 2;
372
+            //frame += 2 * sizeof(char);
373
+        } else if (payload_length == 126) {
374
+            payload_length = (frame[2] << 8) + frame[3];
375
+            hdr_length = 4;
376
+        } else {
377
+            handler_emsg("Receiving frames larger than 65535 bytes not supported\n");
378
+            return -1;
379
+        }
380
+        if ((hdr_length + 4*masked + payload_length) > remaining) {
381
+            continue;
382
+        }
383
+        //printf("    payload_length: %u, raw remaining: %u\n", payload_length, remaining);
384
+        payload = frame + hdr_length + 4*masked;
385
+
386
+        if (*opcode != OPCODE_TEXT && *opcode != OPCODE_BINARY) {
387
+            handler_msg("Ignoring non-data frame, opcode 0x%x\n", *opcode);
388
+            continue;
389
+        }
390
+
391
+        if (payload_length == 0) {
392
+            handler_msg("Ignoring empty frame\n");
393
+            continue;
394
+        }
395
+
396
+        if ((payload_length > 0) && (!masked)) {
397
+            handler_emsg("Received unmasked payload from client\n");
398
+            return -1;
399
+        }
400
+
401
+        // Terminate with a null for base64 decode
402
+        save_char = payload[payload_length];
403
+        payload[payload_length] = '\0';
404
+
405
+        // unmask the data
406
+        mask = payload - 4;
407
+        for (i = 0; i < payload_length; i++) {
408
+            payload[i] ^= mask[i%4];
409
+        }
410
+
411
+        if (*opcode & OPCODE_TEXT) {
412
+            // base64 decode the data
413
+            len = ws_b64_pton((const char*)payload, target+target_offset, targsize);
414
+        } else {
415
+            memcpy(target+target_offset, payload, payload_length);
416
+            len = payload_length;
417
+        }
418
+
419
+        // Restore the first character of the next frame
420
+        payload[payload_length] = save_char;
421
+        if (len < 0) {
422
+            handler_emsg("Base64 decode error code %d", len);
423
+            return len;
424
+        }
425
+        target_offset += len;
426
+
427
+        //printf("    len %d, raw %s\n", len, frame);
428
+    }
429
+
430
+    if (framecount > 1) {
431
+        snprintf(cntstr, 3, "%d", framecount);
432
+        traffic(cntstr);
433
+    }
434
+    
435
+    *left = remaining;
436
+    return target_offset;
437
+}
438
+
439
+
440
+
441
+int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
442
+    char *start, *end;
443
+    headers_t *headers = ws_ctx->headers;
444
+
445
+    headers->key1[0] = '\0';
446
+    headers->key2[0] = '\0';
447
+    headers->key3[0] = '\0';
448
+    
449
+    if ((strlen(handshake) < 92) || (bcmp(handshake, "GET ", 4) != 0)) {
450
+        return 0;
451
+    }
452
+    start = handshake+4;
453
+    end = strstr(start, " HTTP/1.1");
454
+    if (!end) { return 0; }
455
+    strncpy(headers->path, start, end-start);
456
+    headers->path[end-start] = '\0';
457
+
458
+    start = strstr(handshake, "\r\nHost: ");
459
+    if (!start) { return 0; }
460
+    start += 8;
461
+    end = strstr(start, "\r\n");
462
+    strncpy(headers->host, start, end-start);
463
+    headers->host[end-start] = '\0';
464
+
465
+    headers->origin[0] = '\0';
466
+    start = strstr(handshake, "\r\nOrigin: ");
467
+    if (start) {
468
+        start += 10;
469
+    } else {
470
+        start = strstr(handshake, "\r\nSec-WebSocket-Origin: ");
471
+        if (!start) { return 0; }
472
+        start += 24;
473
+    }
474
+    end = strstr(start, "\r\n");
475
+    strncpy(headers->origin, start, end-start);
476
+    headers->origin[end-start] = '\0';
477
+   
478
+    start = strstr(handshake, "\r\nSec-WebSocket-Version: ");
479
+    if (start) {
480
+        // HyBi/RFC 6455
481
+        start += 25;
482
+        end = strstr(start, "\r\n");
483
+        strncpy(headers->version, start, end-start);
484
+        headers->version[end-start] = '\0';
485
+        ws_ctx->hixie = 0;
486
+        ws_ctx->hybi = strtol(headers->version, NULL, 10);
487
+
488
+        start = strstr(handshake, "\r\nSec-WebSocket-Key: ");
489
+        if (!start) { return 0; }
490
+        start += 21;
491
+        end = strstr(start, "\r\n");
492
+        strncpy(headers->key1, start, end-start);
493
+        headers->key1[end-start] = '\0';
494
+   
495
+        start = strstr(handshake, "\r\nConnection: ");
496
+        if (!start) { return 0; }
497
+        start += 14;
498
+        end = strstr(start, "\r\n");
499
+        strncpy(headers->connection, start, end-start);
500
+        headers->connection[end-start] = '\0';
501
+   
502
+        start = strstr(handshake, "\r\nSec-WebSocket-Protocol: ");
503
+        if (!start) { return 0; }
504
+        start += 26;
505
+        end = strstr(start, "\r\n");
506
+        strncpy(headers->protocols, start, end-start);
507
+        headers->protocols[end-start] = '\0';
508
+    } else {
509
+        // Hixie 75 or 76
510
+        ws_ctx->hybi = 0;
511
+
512
+        start = strstr(handshake, "\r\n\r\n");
513
+        if (!start) { return 0; }
514
+        start += 4;
515
+        if (strlen(start) == 8) {
516
+            ws_ctx->hixie = 76;
517
+            strncpy(headers->key3, start, 8);
518
+            headers->key3[8] = '\0';
519
+
520
+            start = strstr(handshake, "\r\nSec-WebSocket-Key1: ");
521
+            if (!start) { return 0; }
522
+            start += 22;
523
+            end = strstr(start, "\r\n");
524
+            strncpy(headers->key1, start, end-start);
525
+            headers->key1[end-start] = '\0';
526
+        
527
+            start = strstr(handshake, "\r\nSec-WebSocket-Key2: ");
528
+            if (!start) { return 0; }
529
+            start += 22;
530
+            end = strstr(start, "\r\n");
531
+            strncpy(headers->key2, start, end-start);
532
+            headers->key2[end-start] = '\0';
533
+        } else {
534
+            ws_ctx->hixie = 75;
535
+        }
536
+
537
+    }
538
+
539
+    return 1;
540
+}
541
+
542
+int parse_hixie76_key(char * key) {
543
+    unsigned long i, spaces = 0, num = 0;
544
+    for (i=0; i < strlen(key); i++) {
545
+        if (key[i] == ' ') {
546
+            spaces += 1;
547
+        }
548
+        if ((key[i] >= 48) && (key[i] <= 57)) {
549
+            num = num * 10 + (key[i] - 48);
550
+        }
551
+    }
552
+    return num / spaces;
553
+}
554
+
555
+int gen_md5(headers_t *headers, char *target) {
556
+    unsigned long key1 = parse_hixie76_key(headers->key1);
557
+    unsigned long key2 = parse_hixie76_key(headers->key2);
558
+    char *key3 = headers->key3;
559
+
560
+    MD5_CTX c;
561
+    char in[HIXIE_MD5_DIGEST_LENGTH] = {
562
+        key1 >> 24, key1 >> 16, key1 >> 8, key1,
563
+        key2 >> 24, key2 >> 16, key2 >> 8, key2,
564
+        key3[0], key3[1], key3[2], key3[3],
565
+        key3[4], key3[5], key3[6], key3[7]
566
+    };
567
+
568
+    MD5_Init(&c);
569
+    MD5_Update(&c, (void *)in, sizeof in);
570
+    MD5_Final((void *)target, &c);
571
+
572
+    target[HIXIE_MD5_DIGEST_LENGTH] = '\0';
573
+
574
+    return 1;
575
+}
576
+
577
+static void gen_sha1(headers_t *headers, char *target) {
578
+    SHA_CTX c;
579
+    unsigned char hash[SHA_DIGEST_LENGTH];
580
+    int r;
581
+
582
+    SHA1_Init(&c);
583
+    SHA1_Update(&c, headers->key1, strlen(headers->key1));
584
+    SHA1_Update(&c, HYBI_GUID, 36);
585
+    SHA1_Final(hash, &c);
586
+
587
+    r = ws_b64_ntop(hash, sizeof hash, target, HYBI10_ACCEPTHDRLEN);
588
+    //assert(r == HYBI10_ACCEPTHDRLEN - 1);
589
+}
590
+
591
+
592
+ws_ctx_t *do_handshake(int sock) {
593
+    char handshake[4096], response[4096], sha1[29], trailer[17];
594
+    char *scheme, *pre;
595
+    headers_t *headers;
596
+    int len, ret, i, offset;
597
+    ws_ctx_t * ws_ctx;
598
+    char *response_protocol;
599
+
600
+    // Peek, but don't read the data
601
+    len = recv(sock, handshake, 1024, MSG_PEEK);
602
+    handshake[len] = 0;
603
+    if (len == 0) {
604
+        handler_msg("ignoring empty handshake\n");
605
+        return NULL;
606
+    } else if ((bcmp(handshake, "\x16", 1) == 0) ||
607
+               (bcmp(handshake, "\x80", 1) == 0)) {
608
+        // SSL
609
+        if (!settings.cert) {
610
+            handler_msg("SSL connection but no cert specified\n");
611
+            return NULL;
612
+        } else if (access(settings.cert, R_OK) != 0) {
613
+            handler_msg("SSL connection but '%s' not found\n",
614
+                        settings.cert);
615
+            return NULL;
616
+        }
617
+        ws_ctx = alloc_ws_ctx();
618
+        ws_socket_ssl(ws_ctx, sock, settings.cert, settings.key);
619
+        if (! ws_ctx) { return NULL; }
620
+        scheme = "wss";
621
+        handler_msg("using SSL socket\n");
622
+    } else if (settings.ssl_only) {
623
+        handler_msg("non-SSL connection disallowed\n");
624
+        return NULL;
625
+    } else {
626
+        ws_ctx = alloc_ws_ctx();
627
+        ws_socket(ws_ctx, sock);
628
+        if (! ws_ctx) { return NULL; }
629
+        scheme = "ws";
630
+        handler_msg("using plain (not SSL) socket\n");
631
+    }
632
+    offset = 0;
633
+    for (i = 0; i < 10; i++) {
634
+        /* (offset + 1): reserve one byte for the trailing '\0' */
635
+        if (0 > (len = ws_recv(ws_ctx, handshake + offset, sizeof(handshake) - (offset + 1)))) {
636
+            handler_emsg("Read error during handshake: %m\n");
637
+            free_ws_ctx(ws_ctx);
638
+            return NULL;
639
+        } else if (0 == len) {
640
+            handler_emsg("Client closed during handshake\n");
641
+            free_ws_ctx(ws_ctx);
642
+            return NULL;
643
+        }
644
+        offset += len;
645
+        handshake[offset] = 0;
646
+        if (strstr(handshake, "\r\n\r\n")) {
647
+            break;
648
+        } else if (sizeof(handshake) <= (size_t)(offset + 1)) {
649
+            handler_emsg("Oversized handshake\n");
650
+            free_ws_ctx(ws_ctx);
651
+            return NULL;
652
+        } else if (9 == i) {
653
+            handler_emsg("Incomplete handshake\n");
654
+            free_ws_ctx(ws_ctx);
655
+            return NULL;
656
+        }
657
+        usleep(10);
658
+    }
659
+
660
+    //handler_msg("handshake: %s\n", handshake);
661
+    if (!parse_handshake(ws_ctx, handshake)) {
662
+        handler_emsg("Invalid WS request\n");
663
+        free_ws_ctx(ws_ctx);
664
+        return NULL;
665
+    }
666
+
667
+    headers = ws_ctx->headers;
668
+
669
+    if (strstr(headers->protocols, "binary")) {
670
+      ws_ctx->opcode = OPCODE_BINARY;
671
+      response_protocol = "binary";
672
+    } else if (strstr(headers->protocols, "base64")) {
673
+      ws_ctx->opcode = OPCODE_TEXT;
674
+      response_protocol = "base64";
675
+    } else {
676
+      handler_emsg("Invalid protocol '%s', expecting 'binary' or 'base64'\n",
677
+          headers->protocols);
678
+      return NULL;
679
+    }
680
+
681
+    if (ws_ctx->hybi > 0) {
682
+        handler_msg("using protocol HyBi/IETF 6455 %d\n", ws_ctx->hybi);
683
+        gen_sha1(headers, sha1);
684
+        sprintf(response, SERVER_HANDSHAKE_HYBI, sha1, response_protocol);
685
+    } else {
686
+        if (ws_ctx->hixie == 76) {
687
+            handler_msg("using protocol Hixie 76\n");
688
+            gen_md5(headers, trailer);
689
+            pre = "Sec-";
690
+        } else {
691
+            handler_msg("using protocol Hixie 75\n");
692
+            trailer[0] = '\0';
693
+            pre = "";
694
+        }
695
+        sprintf(response, SERVER_HANDSHAKE_HIXIE, pre, headers->origin, pre, scheme,
696
+                headers->host, headers->path, pre, "base64", trailer);
697
+    }
698
+    
699
+    //handler_msg("response: %s\n", response);
700
+    ws_send(ws_ctx, response, strlen(response));
701
+
702
+    return ws_ctx;
703
+}
704
+
705
+void signal_handler(int sig) {
706
+    switch (sig) {
707
+        case SIGHUP:
708
+            if (settings.whitelist != NULL) {
709
+              load_whitelist();
710
+            }
711
+            break;
712
+        case SIGPIPE: pipe_error = 1; break; // handle inline
713
+        case SIGTERM:
714
+            remove(settings.pid);
715
+            exit(0);
716
+            break;
717
+    }
718
+}
719
+
720
+void daemonize(int keepfd) {
721
+    int pid, i;
722
+
723
+    umask(0);
724
+    chdir("/");
725
+    setgid(getgid());
726
+    setuid(getuid());
727
+
728
+    /* Double fork to daemonize */
729
+    pid = fork();
730
+    if (pid<0) { fatal("fork error"); }
731
+    if (pid>0) { exit(0); }  // parent exits
732
+    setsid();                // Obtain new process group
733
+    pid = fork();
734
+    if (pid<0) { fatal("fork error"); }
735
+    if (pid>0) {
736
+      // parent exits
737
+      FILE *pidf = fopen(settings.pid, "w");
738
+      if (pidf) {
739
+        fprintf(pidf, "%d", pid);
740
+        fclose(pidf);
741
+      } else {
742
+        fprintf(stderr, "Could not write daemon PID file '%s': %s\n", settings.pid, strerror(errno));
743
+      }
744
+      exit(0);
745
+    }
746
+
747
+    /* Signal handling */
748
+    signal(SIGHUP, signal_handler);   // catch HUP
749
+    signal(SIGTERM, signal_handler);  // catch kill
750
+
751
+    /* Close open files */
752
+    for (i=getdtablesize(); i>=0; --i) {
753
+        if (i != keepfd) {
754
+            close(i);
755
+        } else if (settings.verbose) {
756
+            printf("keeping fd %d\n", keepfd);
757
+        }
758
+    }
759
+    i=open("/dev/null", O_RDWR);  // Redirect stdin
760
+    dup(i);                       // Redirect stdout
761
+    dup(i);                       // Redirect stderr
762
+}
763
+
764
+
765
+void start_server() {
766
+    int lsock, csock, pid, sopt = 1, i;
767
+    struct sockaddr_in serv_addr, cli_addr;
768
+    socklen_t clilen;
769
+    ws_ctx_t *ws_ctx;
770
+
771
+
772
+    /* Initialize buffers */
773
+    lsock = socket(AF_INET, SOCK_STREAM, 0);
774
+    if (lsock < 0) { error("ERROR creating listener socket"); }
775
+    bzero((char *) &serv_addr, sizeof(serv_addr));
776
+    serv_addr.sin_family = AF_INET;
777
+    serv_addr.sin_port = htons(settings.listen_port);
778
+
779
+    /* Resolve listen address */
780
+    if (settings.listen_host && (settings.listen_host[0] != '\0')) {
781
+        if (resolve_host(&serv_addr.sin_addr, settings.listen_host) < -1) {
782
+            fatal("Could not resolve listen address");
783
+        }
784
+    } else {
785
+        serv_addr.sin_addr.s_addr = INADDR_ANY;
786
+    }
787
+
788
+    setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, (char *)&sopt, sizeof(sopt));
789
+    if (bind(lsock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
790
+        fatal("ERROR on binding listener socket");
791
+    }
792
+    listen(lsock,100);
793
+
794
+    signal(SIGPIPE, signal_handler);  // catch pipe
795
+
796
+    if (settings.daemon) {
797
+        daemonize(lsock);
798
+    }
799
+
800
+
801
+    // Reep zombies
802
+    signal(SIGCHLD, SIG_IGN);
803
+
804
+    printf("Waiting for connections on %s:%d\n",
805
+            settings.listen_host, settings.listen_port);
806
+
807
+    while (1) {
808
+        clilen = sizeof(cli_addr);
809
+        pipe_error = 0;
810
+        pid = 0;
811
+        csock = accept(lsock, 
812
+                       (struct sockaddr *) &cli_addr, 
813
+                       &clilen);
814
+        if (csock < 0) {
815
+            error("ERROR on accept");
816
+            continue;
817
+        }
818
+        handler_msg("got client connection from %s\n",
819
+                    inet_ntoa(cli_addr.sin_addr));
820
+
821
+        if (!settings.run_once) {
822
+            handler_msg("forking handler process\n");
823
+            pid = fork();
824
+        }
825
+
826
+        if (pid == 0) {  // handler process
827
+            ws_ctx = do_handshake(csock);
828
+            if (settings.run_once) {
829
+                if (ws_ctx == NULL) {
830
+                    // Not a real WebSocket connection
831
+                    continue;
832
+                } else {
833
+                    // Successful connection, stop listening for new
834
+                    // connections
835
+                    close(lsock);
836
+                }
837
+            }
838
+            if (ws_ctx == NULL) {
839
+                handler_msg("No connection after handshake\n");
840
+                break;   // Child process exits
841
+            }
842
+
843
+            settings.handler(ws_ctx);
844
+            if (pipe_error) {
845
+                handler_emsg("Closing due to SIGPIPE\n");
846
+            }
847
+            break;   // Child process exits
848
+        } else {         // parent process
849
+            settings.handler_id += 1;
850
+        }
851
+    }
852
+    if (pid == 0) {
853
+        if (ws_ctx) {
854
+            ws_socket_free(ws_ctx);
855
+            free_ws_ctx(ws_ctx);
856
+        } else {
857
+            shutdown(csock, SHUT_RDWR);
858
+            close(csock);
859
+        }
860
+        handler_msg("handler exit\n");
861
+    } else {
862
+        handler_msg("websockify exit\n");
863
+    }
864
+
865
+}
866
+

+ 109
- 0
src/contrib/websocket.h Vedi File

@@ -0,0 +1,109 @@
1
+#include <openssl/ssl.h>
2
+
3
+#define BUFSIZE 65536
4
+#define DBUFSIZE (BUFSIZE * 3) / 4 - 20
5
+
6
+#define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
7
+Upgrade: WebSocket\r\n\
8
+Connection: Upgrade\r\n\
9
+%sWebSocket-Origin: %s\r\n\
10
+%sWebSocket-Location: %s://%s%s\r\n\
11
+%sWebSocket-Protocol: %s\r\n\
12
+\r\n%s"
13
+
14
+#define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\
15
+Upgrade: websocket\r\n\
16
+Connection: Upgrade\r\n\
17
+Sec-WebSocket-Accept: %s\r\n\
18
+Sec-WebSocket-Protocol: %s\r\n\
19
+\r\n"
20
+
21
+#define HYBI_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
22
+
23
+#define HYBI10_ACCEPTHDRLEN 29
24
+
25
+#define HIXIE_MD5_DIGEST_LENGTH 16
26
+
27
+#define POLICY_RESPONSE "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n"
28
+
29
+#define OPCODE_TEXT    0x01
30
+#define OPCODE_BINARY  0x02
31
+
32
+typedef struct {
33
+    char path[1024+1];
34
+    char host[1024+1];
35
+    char origin[1024+1];
36
+    char version[1024+1];
37
+    char connection[1024+1];
38
+    char protocols[1024+1];
39
+    char key1[1024+1];
40
+    char key2[1024+1];
41
+    char key3[8+1];
42
+} headers_t;
43
+
44
+typedef struct {
45
+    int        sockfd;
46
+    SSL_CTX   *ssl_ctx;
47
+    SSL       *ssl;
48
+    int        hixie;
49
+    int        hybi;
50
+    int        opcode;
51
+    headers_t *headers;
52
+    char      *cin_buf;
53
+    char      *cout_buf;
54
+    char      *tin_buf;
55
+    char      *tout_buf;
56
+} ws_ctx_t;
57
+
58
+typedef struct {
59
+    int verbose;
60
+    char listen_host[256];
61
+    int listen_port;
62
+    void (*handler)(ws_ctx_t*);
63
+    int handler_id;
64
+    char *cert;
65
+    char *key;
66
+    int ssl_only;
67
+    int daemon;
68
+    int run_once;
69
+    char *whitelist;
70
+    char *pattern;
71
+    char *pid;
72
+} settings_t;
73
+
74
+
75
+ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len);
76
+
77
+ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len);
78
+
79
+/* base64.c declarations */
80
+//int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize);
81
+//int b64_pton(char const *src, u_char *target, size_t targsize);
82
+
83
+#define gen_handler_msg(stream, ...) \
84
+    if (! settings.daemon) { \
85
+        fprintf(stream, "  %d: ", settings.handler_id); \
86
+        fprintf(stream, __VA_ARGS__); \
87
+    }
88
+
89
+#define handler_msg(...) gen_handler_msg(stdout, __VA_ARGS__);
90
+#define handler_emsg(...) gen_handler_msg(stderr, __VA_ARGS__);
91
+
92
+/* forward declarations */
93
+void traffic(const char * token);
94
+int encode_hybi(u_char const *src, size_t srclength,
95
+                char *target, size_t targsize, unsigned int opcode);
96
+int encode_hixie(u_char const *src, size_t srclength,
97
+                 char *target, size_t targsize);
98
+int decode_hybi(unsigned char *src, size_t srclength,
99
+                u_char *target, size_t targsize,
100
+                unsigned int *opcode, unsigned int *left);
101
+int decode_hixie(char *src, size_t srclength,
102
+                 u_char *target, size_t targsize,
103
+                 unsigned int *opcode, unsigned int *left);
104
+int resolve_host(struct in_addr *sin_addr, const char *hostname);
105
+void start_server();
106
+
107
+
108
+int load_whitelist();
109
+

+ 499
- 0
src/contrib/websockify.c Vedi File

@@ -0,0 +1,499 @@
1
+/*
2
+ * A WebSocket to TCP socket proxy with support for "wss://" encryption.
3
+ * Copyright 2010 Joel Martin
4
+ * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
5
+ *
6
+ * You can make a cert/key with openssl using:
7
+ * openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
8
+ * as taken from http://docs.python.org/dev/library/ssl.html#certificates
9
+ */
10
+#include <stdio.h>
11
+#include <errno.h>
12
+#include <limits.h>
13
+#include <getopt.h>
14
+#include <sys/socket.h>
15
+#include <netinet/in.h>
16
+#include <netdb.h>
17
+#include <sys/select.h>
18
+#include <fcntl.h>
19
+#include <unistd.h>
20
+#include <sys/stat.h>
21
+#include <signal.h>
22
+#include "websocket.h"
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+char traffic_legend[] = "\n\
31
+Traffic Legend:\n\
32
+    }  - Client receive\n\
33
+    }. - Client receive partial\n\
34
+    {  - Target receive\n\
35
+\n\
36
+    >  - Target send\n\
37
+    >. - Target send partial\n\
38
+    <  - Client send\n\
39
+    <. - Client send partial\n\
40
+";
41
+
42
+char USAGE[] = "Usage: [options] " \
43
+               "[source_addr:]source_port target_addr{:target_port}\n\n" \
44
+               "  --verbose|-v         verbose messages and per frame traffic\n" \
45
+               "  --daemon|-D          become a daemon (background process)\n" \
46
+               "  --run-once           handle a single WebSocket connection and exit\n" \
47
+               "  --cert CERT          SSL certificate file\n" \
48
+               "  --key KEY            SSL key file (if separate from cert)\n" \
49
+               "  --ssl-only           disallow non-encrypted connections\n" \
50
+               "  --whitelist|-w LIST  new-line separated target port whitelist file\n" \
51
+               "                       (target_port is not required only with this option)\n" \
52
+               "  --pattern|-P         target port request pattern. Default: '/%d'\n" \
53
+               "  --pid|-p             desired path of pid file. Default: '/var/run/websockify.pid'";
54
+
55
+#define usage(fmt, args...) \
56
+    do { \
57
+        fprintf(stderr, "%s\n\n", USAGE); \
58
+        fprintf(stderr, fmt , ## args); \
59
+        exit(1); \
60
+    } while(0)
61
+
62
+char target_host[256];
63
+int target_port;
64
+int *target_ports;
65
+
66
+//extern pipe_error;
67
+extern int pipe_error;
68
+extern settings_t settings;
69
+
70
+void do_proxy(ws_ctx_t *ws_ctx, int target) {
71
+    fd_set rlist, wlist, elist;
72
+    struct timeval tv;
73
+    int i, maxfd, client = ws_ctx->sockfd;
74
+    unsigned int opcode, left, ret;
75
+    unsigned int tout_start, tout_end, cout_start, cout_end;
76
+    unsigned int tin_start, tin_end;
77
+    ssize_t len, bytes;
78
+
79
+    tout_start = tout_end = cout_start = cout_end;
80
+    tin_start = tin_end = 0;
81
+    maxfd = client > target ? client+1 : target+1;
82
+
83
+    while (1) {
84
+        tv.tv_sec = 1;
85
+        tv.tv_usec = 0;
86
+
87
+        FD_ZERO(&rlist);
88
+        FD_ZERO(&wlist);
89
+        FD_ZERO(&elist);
90
+
91
+        FD_SET(client, &elist);
92
+        FD_SET(target, &elist);
93
+
94
+        if (tout_end == tout_start) {
95
+            // Nothing queued for target, so read from client
96
+            FD_SET(client, &rlist);
97
+        } else {
98
+            // Data queued for target, so write to it
99
+            FD_SET(target, &wlist);
100
+        }
101
+        if (cout_end == cout_start) {
102
+            // Nothing queued for client, so read from target
103
+            FD_SET(target, &rlist);
104
+        } else {
105
+            // Data queued for client, so write to it
106
+            FD_SET(client, &wlist);
107
+        }
108
+
109
+        ret = select(maxfd, &rlist, &wlist, &elist, &tv);
110
+        if (pipe_error) { break; }
111
+
112
+        if (FD_ISSET(target, &elist)) {
113
+            handler_emsg("target exception\n");
114
+            break;
115
+        }
116
+        if (FD_ISSET(client, &elist)) {
117
+            handler_emsg("client exception\n");
118
+            break;
119
+        }
120
+
121
+        if (ret == -1) {
122
+            handler_emsg("select(): %s\n", strerror(errno));
123
+            break;
124
+        } else if (ret == 0) {
125
+            //handler_emsg("select timeout\n");
126
+            continue;
127
+        }
128
+
129
+        if (FD_ISSET(target, &wlist)) {
130
+            len = tout_end-tout_start;
131
+            bytes = send(target, ws_ctx->tout_buf + tout_start, len, 0);
132
+            if (pipe_error) { break; }
133
+            if (bytes < 0) {
134
+                handler_emsg("target connection error: %s\n",
135
+                             strerror(errno));
136
+                break;
137
+            }
138
+            tout_start += bytes;
139
+            if (tout_start >= tout_end) {
140
+                tout_start = tout_end = 0;
141
+                traffic(">");
142
+            } else {
143
+                traffic(">.");
144
+            }
145
+        }
146
+
147
+        if (FD_ISSET(client, &wlist)) {
148
+            len = cout_end-cout_start;
149
+            bytes = ws_send(ws_ctx, ws_ctx->cout_buf + cout_start, len);
150
+            if (pipe_error) { break; }
151
+            if (len < 3) {
152
+                handler_emsg("len: %d, bytes: %d: %d\n",
153
+                             (int) len, (int) bytes,
154
+                             (int) *(ws_ctx->cout_buf + cout_start));
155
+            }
156
+            cout_start += bytes;
157
+            if (cout_start >= cout_end) {
158
+                cout_start = cout_end = 0;
159
+                traffic("<");
160
+            } else {
161
+                traffic("<.");
162
+            }
163
+        }
164
+
165
+        if (FD_ISSET(target, &rlist)) {
166
+            bytes = recv(target, ws_ctx->cin_buf, DBUFSIZE , 0);
167
+            if (pipe_error) { break; }
168
+            if (bytes <= 0) {
169
+                handler_emsg("target closed connection\n");
170
+                break;
171
+            }
172
+            cout_start = 0;
173
+            if (ws_ctx->hybi) {
174
+                cout_end = encode_hybi(ws_ctx->cin_buf, bytes,
175
+                                   ws_ctx->cout_buf, BUFSIZE, ws_ctx->opcode);
176
+            } else {
177
+                cout_end = encode_hixie(ws_ctx->cin_buf, bytes,
178
+                                    ws_ctx->cout_buf, BUFSIZE);
179
+            }
180
+            /*
181
+            printf("encoded: ");
182
+            for (i=0; i< cout_end; i++) {
183
+                printf("%u,", (unsigned char) *(ws_ctx->cout_buf+i));
184
+            }
185
+            printf("\n");
186
+            */
187
+            if (cout_end < 0) {
188
+                handler_emsg("encoding error\n");
189
+                break;
190
+            }
191
+            traffic("{");
192
+        }
193
+
194
+        if (FD_ISSET(client, &rlist)) {
195
+            bytes = ws_recv(ws_ctx, ws_ctx->tin_buf + tin_end, BUFSIZE-1);
196
+            if (pipe_error) { break; }
197
+            if (bytes <= 0) {
198
+                handler_emsg("client closed connection\n");
199
+                break;
200
+            }
201
+            tin_end += bytes;
202
+            /*
203
+            printf("before decode: ");
204
+            for (i=0; i< bytes; i++) {
205
+                printf("%u,", (unsigned char) *(ws_ctx->tin_buf+i));
206
+            }
207
+            printf("\n");
208
+            */
209
+            if (ws_ctx->hybi) {
210
+                len = decode_hybi(ws_ctx->tin_buf + tin_start,
211
+                                  tin_end-tin_start,
212
+                                  ws_ctx->tout_buf, BUFSIZE-1,
213
+                                  &opcode, &left);
214
+            } else {
215
+                len = decode_hixie(ws_ctx->tin_buf + tin_start,
216
+                                   tin_end-tin_start,
217
+                                   ws_ctx->tout_buf, BUFSIZE-1,
218
+                                   &opcode, &left);
219
+            }
220
+
221
+            if (opcode == 8) {
222
+                handler_msg("client sent orderly close frame\n");
223
+                break;
224
+            }
225
+
226
+            /*
227
+            printf("decoded: ");
228
+            for (i=0; i< len; i++) {
229
+                printf("%u,", (unsigned char) *(ws_ctx->tout_buf+i));
230
+            }
231
+            printf("\n");
232
+            */
233
+            if (len < 0) {
234
+                handler_emsg("decoding error\n");
235
+                break;
236
+            }
237
+            if (left) {
238
+                tin_start = tin_end - left;
239
+                //printf("partial frame from client");
240
+            } else {
241
+                tin_start = 0;
242
+                tin_end = 0;
243
+            }
244
+
245
+            traffic("}");
246
+            tout_start = 0;
247
+            tout_end = len;
248
+        }
249
+    }
250
+}
251
+
252
+void proxy_handler(ws_ctx_t *ws_ctx) {
253
+    int tsock = 0;
254
+    struct sockaddr_in taddr;
255
+
256
+    if (target_ports != NULL) {
257
+        if (sscanf(ws_ctx->headers->path, settings.pattern, &target_port) != 1) {
258
+        handler_emsg("Could not match pattern '%s' to request path '%s'\n",
259
+                     settings.pattern, ws_ctx->headers->path);
260
+        return;
261
+        }
262
+        int *p;
263
+        int found = 0;
264
+        for (p = target_ports; *p; p++) {
265
+            if (*p == target_port) {
266
+                found = 1;
267
+                break;
268
+            }
269
+        }
270
+        if (!found) {
271
+            handler_emsg("Rejecting connection to non-whitelisted port: '%d'\n",
272
+                         target_port);
273
+            return;
274
+        }
275
+    }
276
+
277
+    handler_msg("connecting to: %s:%d\n", target_host, target_port);
278
+
279
+    tsock = socket(AF_INET, SOCK_STREAM, 0);
280
+    if (tsock < 0) {
281
+        handler_emsg("Could not create target socket: %s\n",
282
+                     strerror(errno));
283
+        return;
284
+    }
285
+    bzero((char *) &taddr, sizeof(taddr));
286
+    taddr.sin_family = AF_INET;
287
+    taddr.sin_port = htons(target_port);
288
+
289
+    /* Resolve target address */
290
+    if (resolve_host(&taddr.sin_addr, target_host) < -1) {
291
+        handler_emsg("Could not resolve target address: %s\n",
292
+                     strerror(errno));
293
+    }
294
+
295
+    if (connect(tsock, (struct sockaddr *) &taddr, sizeof(taddr)) < 0) {
296
+        handler_emsg("Could not connect to target: %s\n",
297
+                     strerror(errno));
298
+        close(tsock);
299
+        return;
300
+    }
301
+
302
+    if ((settings.verbose) && (! settings.daemon)) {
303
+        printf("%s", traffic_legend);
304
+    }
305
+
306
+    do_proxy(ws_ctx, tsock);
307
+
308
+    shutdown(tsock, SHUT_RDWR);
309
+    close(tsock);
310
+}
311
+
312
+int load_whitelist() {
313
+  printf("loading port whitelist '%s'\n", settings.whitelist);
314
+  FILE *whitelist = fopen(settings.whitelist, "r");
315
+  if (whitelist == NULL) {
316
+    fprintf(stderr, "Error opening whitelist file '%s':\n\t%s\n",
317
+          settings.whitelist, strerror(errno));
318
+    return -1;
319
+  }
320
+
321
+  const int tplen_grow = 512;
322
+  int tplen = tplen_grow, tpcount = 0;
323
+  target_ports = (int*)malloc(tplen*sizeof(int));
324
+  if (target_ports == NULL) {
325
+    fprintf(stderr, "Whitelist port malloc error");
326
+    return -2;
327
+  }
328
+
329
+  char *line = NULL;
330
+  ssize_t n = 0, nread = 0;
331
+  while ((nread = getline(&line, &n, whitelist)) > 0) {
332
+      if (line[0] == '\n') continue;
333
+      line[nread-1] = '\x00';
334
+      long int port = strtol(line, NULL, 10);
335
+      if (port < 1 || port > 65535) {
336
+          fprintf(stderr,
337
+            "Whitelist port '%s' is not between valid range 1 and 65535", line);
338
+          return -3;
339
+      }
340
+      tpcount++;
341
+      if (tpcount >= tplen) {
342
+          tplen += tplen_grow;
343
+          target_ports = (int*)realloc(target_ports, tplen*sizeof(int));
344
+          if (target_ports == NULL) {
345
+              fprintf(stderr, "Whitelist port realloc error");
346
+              return -2;
347
+          }
348
+      }
349
+      target_ports[tpcount-1] = port;
350
+  }
351
+  if (line != NULL) free(line);
352
+
353
+  if (tpcount == 0) {
354
+      fprintf(stderr, "0 ports read from whitelist file '%s'\n",
355
+                      settings.whitelist);
356
+      return -4;
357
+  }
358
+
359
+  target_ports = (int*)realloc(target_ports, (tpcount + 1)*sizeof(int));
360
+  if (target_ports == NULL) {
361
+      fprintf(stderr, "Whitelist port realloc error");
362
+      return -2;
363
+  }
364
+  target_ports[tpcount] = 0;
365
+  return 0;
366
+}
367
+
368
+int main(int argc, char *argv[])
369
+{
370
+    int fd, c, option_index = 0;
371
+    char *found;
372
+    static struct option long_options[] = {
373
+        {"verbose",   no_argument,       0,                 'v'},
374
+        {"ssl-only",  no_argument,       &settings.ssl_only, 1 },
375
+        {"daemon",    no_argument,       0,                 'D'},
376
+        /* ---- */
377
+        {"run-once",  no_argument,       0,                 'r'},
378
+        {"cert",      required_argument, 0,                 'c'},
379
+        {"key",       required_argument, 0,                 'k'},
380
+        {"whitelist", required_argument, 0,                 'w'},
381
+        {"pattern",   required_argument, 0,                 'P'},
382
+        {"pid",       required_argument, 0,                 'p'},
383
+        {0, 0, 0, 0}
384
+    };
385
+
386
+    settings.cert = realpath("self.pem", NULL);
387
+    if (!settings.cert) {
388
+        /* Make sure it's always set to something */
389
+        settings.cert = "self.pem";
390
+    }
391
+    settings.key = "";
392
+    settings.pattern = "/%d";
393
+    settings.pid = "/var/run/websockify.pid";
394
+
395
+    while (1) {
396
+        c = getopt_long (argc, argv, "vDrc:k:w:p:P:",
397
+                         long_options, &option_index);
398
+
399
+        /* Detect the end */
400
+        if (c == -1) break;
401
+
402
+        switch (c) {
403
+            case 0:
404
+                break; // ignore
405
+            case 1:
406
+                break; // ignore
407
+            case 'v':
408
+                settings.verbose = 1;
409
+                break;
410
+            case 'D':
411
+                settings.daemon = 1;
412
+                break;
413
+            case 'r':
414
+                settings.run_once = 1;
415
+                break;
416
+            case 'c':
417
+                settings.cert = realpath(optarg, NULL);
418
+                if (! settings.cert) {
419
+                    usage("No cert file at %s\n", optarg);
420
+                }
421
+                break;
422
+            case 'k':
423
+                settings.key = realpath(optarg, NULL);
424
+                if (! settings.key) {
425
+                    usage("No key file at %s\n", optarg);
426
+                }
427
+                break;
428
+            case 'w':
429
+                settings.whitelist = realpath(optarg, NULL);
430
+                if (! settings.whitelist) {
431
+                    usage("No whitelist file at %s\n", optarg);
432
+                }
433
+                break;
434
+            case 'P':
435
+                settings.pattern = optarg;
436
+                break;
437
+            case 'p':
438
+                settings.pid = optarg;
439
+                break;
440
+            default:
441
+                usage(" ");
442
+        }
443
+    }
444
+
445
+    if ((argc-optind) != 2) {
446
+        usage("Invalid number of arguments\n");
447
+    }
448
+
449
+    found = strstr(argv[optind], ":");
450
+    if (found) {
451
+        memcpy(settings.listen_host, argv[optind], found-argv[optind]);
452
+        settings.listen_port = strtol(found+1, NULL, 10);
453
+    } else {
454
+        settings.listen_host[0] = '\0';
455
+        settings.listen_port = strtol(argv[optind], NULL, 10);
456
+    }
457
+    optind++;
458
+    if (settings.listen_port == 0) {
459
+        usage("Could not parse listen_port\n");
460
+    }
461
+
462
+    found = strstr(argv[optind], ":");
463
+    if (found && settings.whitelist == NULL) {
464
+        memcpy(target_host, argv[optind], found-argv[optind]);
465
+        target_port = strtol(found+1, NULL, 10);
466
+        target_ports = NULL;
467
+    } else if (!found && settings.whitelist != NULL) {
468
+        if (load_whitelist()) {
469
+          usage("Whitelist error.");
470
+        }
471
+        memcpy(target_host, argv[optind], strlen(argv[optind]));
472
+        target_port = -1;
473
+
474
+    } else {
475
+        usage("Target argument must be host:port or provide host and a port whitelist\n");
476
+    }
477
+    if (target_port == 0) {
478
+        usage("Could not parse target port\n");
479
+    }
480
+
481
+    if (settings.ssl_only) {
482
+        if (access(settings.cert, R_OK) != 0) {
483
+            usage("SSL only and cert file '%s' not found\n", settings.cert);
484
+        }
485
+    } else if (access(settings.cert, R_OK) != 0) {
486
+        fprintf(stderr, "Warning: '%s' not found\n", settings.cert);
487
+    }
488
+
489
+    //printf("  verbose: %d\n",   settings.verbose);
490
+    //printf("  ssl_only: %d\n",  settings.ssl_only);
491
+    //printf("  daemon: %d\n",    settings.daemon);
492
+    //printf("  run_once: %d\n",  settings.run_once);
493
+    //printf("  cert: %s\n",      settings.cert);
494
+    //printf("  key: %s\n",       settings.key);
495
+
496
+    settings.handler = proxy_handler;
497
+    start_server();
498
+
499
+}

+ 474
- 0
src/radangel.c Vedi File

@@ -0,0 +1,474 @@
1
+/*
2
+ *	RadAngel gamma spectrometer USB HID device control and readout program
3
+ *
4
+ *	gcc -std=gnu99 -Wall radangel.c -o radangel -pthread -lm -lhidapi-libusb
5
+ *
6
+ *      strip -s radangel
7
+ *
8
+ *	echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0100", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kromek-radangel.rules
9
+ *
10
+ */
11
+
12
+
13
+#include <stdio.h>
14
+#include <stdlib.h>
15
+#include <stdarg.h>
16
+#include <pthread.h>
17
+#include <string.h>
18
+#include <getopt.h>
19
+#include <sys/time.h>
20
+#include <hidapi/hidapi.h>
21
+#include <unistd.h> // usleep()
22
+
23
+
24
+#define MAX_STR 255
25
+#define MAX_CHANNEL 4096
26
+#define QUEUESIZE 1024
27
+#define DEV_VID 0x4d8
28
+#define DEV_PID 0x100
29
+#define LINE_MAX 4096
30
+
31
+// non blocking
32
+#define HID_TIMEOUT -1
33
+// 500 ms timeout
34
+//#define HID_TIMEOUT 500
35
+
36
+
37
+typedef struct {
38
+	struct timespec start_time;
39
+} config;
40
+
41
+typedef struct {
42
+	FILE* file;
43
+	char* name;
44
+} output;
45
+
46
+typedef struct {
47
+	hid_device* handle;
48
+	config config;
49
+	output event;
50
+	output stats;
51
+} context;
52
+
53
+typedef struct {
54
+	struct timespec event_time;
55
+	unsigned short channel;
56
+} data;
57
+
58
+typedef struct {
59
+	data data[QUEUESIZE];
60
+	long head, tail;
61
+	int full, empty;
62
+	pthread_mutex_t *mutex;
63
+	pthread_cond_t *not_full, *not_empty;
64
+	context context;
65
+} queue;
66
+
67
+void usage(char* name);
68
+void info(const char* fmt, ...);
69
+void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result);
70
+queue *queue_init(void);
71
+void queue_free(queue *q);
72
+void queue_add(queue *q, data src);
73
+void queue_del(queue *q, data *dst);
74
+void *event_source(void *args);
75
+void *event_sink(void *args);
76
+//void *update_context(void *args);
77
+
78
+
79
+static int verbose_flag; // Flag set by -v | --verbose
80
+
81
+
82
+void usage(char* name) {
83
+        info("%s [<arguments>]\n\n", name);
84
+	info("\t-h          | --help             Usage message.\n");
85
+        info("\t-v          | --verbose          Verbose output.\n");
86
+	info("\t-e <string> | --event  <string>  Event data file path.\n");
87
+	info("\t-s <string> | --stats  <string>  Stats data file path.\n");
88
+	info("\n");
89
+}
90
+
91
+
92
+int main(int argc, char **argv) {
93
+	queue *fifo = NULL;
94
+	pthread_t pro, con;
95
+
96
+	struct timespec start_time = { 0, 0 };
97
+	int c;
98
+	int option_index = 0;
99
+
100
+	fifo = queue_init();
101
+	if (fifo ==  NULL) {
102
+		info("Error: main queue_init() failed.\n");
103
+		exit(1);
104
+	}
105
+	fifo->context.event.name = "-";
106
+	fifo->context.event.file = stdout;
107
+	fifo->context.stats.name = NULL;
108
+	fifo->context.stats.file = NULL;
109
+
110
+	while (1) {
111
+		static struct option long_options[] = {
112
+			{"verbose",	no_argument, 		&verbose_flag,	1  },
113
+			{"help", 	no_argument,		NULL, 		'h'},
114
+			{"event",	required_argument, 	NULL,		'e'},
115
+			{"stats",	required_argument, 	NULL,		's'},
116
+			{0, 		0, 			0, 		0  } // end of options
117
+		};
118
+		option_index = 0;
119
+		c = getopt_long(argc, argv, "he:s:", long_options, &option_index);
120
+		if (c == -1) { // end of options
121
+			break;
122
+		}
123
+		switch (c) {
124
+			case 0:
125
+				if (long_options[option_index].flag != 0) { // option sets a flag
126
+					break;
127
+				}
128
+				info("option %s", long_options[option_index].name);
129
+				if (optarg) {
130
+					info(" with arg %s", optarg);
131
+				}
132
+				info("\n");
133
+			break;
134
+			case 'e':
135
+				fifo->context.event.name = optarg;
136
+			break;
137
+			case 's':
138
+				fifo->context.stats.name = optarg;
139
+			break;
140
+			case 'h':
141
+				usage(argv[0]);
142
+				queue_free(fifo);
143
+				exit(1);
144
+			break;
145
+			case '?': // getopt_long already printed an error message.
146
+			break;
147
+			default:
148
+				abort();
149
+		}
150
+	}
151
+	if (optind < argc) { // remaining command line arguments (not options).
152
+		info("unknown argument: ");
153
+		while (optind < argc) {
154
+			info("%s ", argv[optind++]);
155
+		}
156
+		info("\n");
157
+		queue_free(fifo);
158
+		exit(1);
159
+	}
160
+
161
+
162
+	// ensure input
163
+ 	if (hid_init()) {
164
+		info("Error: unable to initialize hidapi.\n");
165
+		queue_free(fifo);
166
+		exit(1);
167
+	}
168
+	// open device later in read thread, might not be connected yet
169
+	// measurement start
170
+	clock_gettime(CLOCK_MONOTONIC, &start_time);
171
+	fifo->context.config.start_time = start_time;
172
+
173
+	// start the producer and consumer threads
174
+	pthread_create(&pro, NULL, event_source, fifo);
175
+	pthread_create(&con, NULL, event_sink, fifo);
176
+	pthread_join(pro, NULL);
177
+	pthread_join(con, NULL);
178
+
179
+	if (fifo->context.event.file != NULL) {
180
+		fclose(fifo->context.event.file);
181
+	}
182
+	if (fifo->context.stats.file != NULL) {
183
+		fclose(fifo->context.stats.file);
184
+	}
185
+	if (fifo->context.handle != NULL) {
186
+		hid_close(fifo->context.handle);
187
+	}
188
+	hid_exit(); // Free static HIDAPI objects
189
+	queue_free(fifo);
190
+
191
+	return 0;
192
+}
193
+
194
+
195
+void info(const char* fmt, ...) {
196
+	va_list args;
197
+	va_start(args, fmt);
198
+	if (verbose_flag) {
199
+		fprintf(stderr, fmt, args);
200
+	}
201
+	va_end(args);
202
+}
203
+
204
+
205
+queue *queue_init(void) {
206
+	queue *q;
207
+
208
+	q = (queue *) malloc (sizeof (queue));
209
+	if (q == NULL) return (NULL);
210
+
211
+	q->empty = 1;
212
+	q->full = 0;
213
+	q->head = 0;
214
+	q->tail = 0;
215
+
216
+	q->mutex = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t));
217
+	pthread_mutex_init(q->mutex, NULL);
218
+
219
+	q->not_full = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
220
+	pthread_cond_init(q->not_full, NULL);
221
+
222
+	q->not_empty = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
223
+	pthread_cond_init(q->not_empty, NULL);
224
+
225
+	return (q);
226
+}
227
+
228
+
229
+void queue_free(queue *q) {
230
+	pthread_mutex_destroy(q->mutex);
231
+	free(q->mutex);
232
+
233
+	pthread_cond_destroy(q->not_full);
234
+	free(q->not_full);
235
+
236
+	pthread_cond_destroy(q->not_empty);
237
+	free(q->not_empty);
238
+
239
+	free(q);
240
+}
241
+
242
+
243
+void queue_add(queue *q, data src) {
244
+	q->data[q->tail] = src;
245
+	q->tail++;
246
+
247
+	if (q->tail == QUEUESIZE) {
248
+		q->tail = 0;
249
+	}
250
+
251
+	if (q->tail == q->head) {
252
+		q->full = 1;
253
+	}
254
+
255
+	q->empty = 0;
256
+
257
+	return;
258
+}
259
+
260
+
261
+void queue_del(queue *q, data *dst) {
262
+	*dst = q->data[q->head];
263
+	q->head++;
264
+
265
+	if (q->head == QUEUESIZE) {
266
+		q->head = 0;
267
+	}
268
+
269
+	if (q->head == q->tail) {
270
+		q->empty = 1;
271
+	}
272
+
273
+	q->full = 0;
274
+
275
+	return;
276
+}
277
+
278
+
279
+void *event_source(void *q) {
280
+	queue *fifo;
281
+	fifo = (queue *)q;
282
+	data data;
283
+
284
+	hid_device* handle = fifo->context.handle;
285
+	unsigned char buffer[64];
286
+	int result;
287
+
288
+	struct timespec event_time = { 0, 0L };
289
+	struct timespec sleep_time = { 0, 500000000L}; // retry time
290
+
291
+	do { // read events loop
292
+		while (handle == NULL) { // try to aquire handle
293
+			handle = hid_open(DEV_VID, DEV_PID, NULL);
294
+			if (handle != NULL) {
295
+				fifo->context.handle = handle;
296
+			} else {
297
+				nanosleep(&sleep_time, NULL); // wait to retry
298
+			}
299
+		}
300
+		while (handle != NULL) { // try to read data
301
+			memset(buffer, 0, sizeof(buffer));
302
+			result = hid_read_timeout(handle, buffer, sizeof(buffer), HID_TIMEOUT);
303
+			if (result > 0) { // got some bytes
304
+				break;
305
+			} else if (result < 0) { // something went wrong: disconnect?
306
+				hid_close(handle);
307
+				handle = NULL; // invalidate handle for reaquire
308
+				fifo->context.handle = handle;
309
+			}
310
+		}
311
+
312
+		clock_gettime(CLOCK_MONOTONIC, &event_time);
313
+
314
+		if (buffer[0] == 4) { // got an event
315
+			data.channel = (((buffer[1] << 8) | buffer[2]) >> 4);
316
+			data.event_time = event_time;
317
+
318
+			// lock the queue, wait for space, push
319
+			pthread_mutex_lock(fifo->mutex);
320
+			while (fifo->full) { // queue is full, wait for consumer
321
+				pthread_cond_wait(fifo->not_full, fifo->mutex); // unlocks, block, relock
322
+			}
323
+			queue_add(fifo, data);
324
+			pthread_mutex_unlock(fifo->mutex);
325
+			pthread_cond_signal(fifo->not_empty);
326
+		}
327
+
328
+	} while (1); // read events loop
329
+
330
+	return (NULL);
331
+}
332
+
333
+
334
+void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result) {
335
+    if ((stop->tv_nsec - start->tv_nsec) < 0) {
336
+        result->tv_sec = stop->tv_sec - start->tv_sec - 1;
337
+        result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
338
+    } else {
339
+        result->tv_sec = stop->tv_sec - start->tv_sec;
340
+        result->tv_nsec = stop->tv_nsec - start->tv_nsec;
341
+    }
342
+    return;
343
+}
344
+
345
+
346
+void *event_sink(void *q) {
347
+	queue *fifo;
348
+	fifo = (queue *)q;
349
+	data data;
350
+
351
+	char *events_filename = fifo->context.event.name;
352
+	char *stats_filename  = fifo->context.stats.name;
353
+	char *events_filemode = "a+";
354
+	char *stats_filemode  = "w";
355
+	FILE *events_file = events_filename ? ((strncmp(events_filename, "-", 1) == 0) ? stdout : fopen(events_filename, events_filemode)) : NULL;
356
+	FILE *stats_file  = stats_filename  ? ((strncmp(stats_filename,  "-", 1) == 0) ? stdout : fopen(stats_filename,  stats_filemode )) : NULL;
357
+	fifo->context.event.file = events_file;
358
+	fifo->context.stats.file = stats_file;
359
+
360
+	struct timespec start_time = fifo->context.config.start_time;
361
+	struct timespec event_time = { 0, 0L };
362
+	struct timespec sleep_time = { 0, 500000000L}; // retry time
363
+	long int tv_sec;
364
+	unsigned long int tv_nsec;
365
+
366
+	unsigned int  channel = 0;
367
+	unsigned long histogram[MAX_CHANNEL];
368
+	memset(histogram, 0, MAX_CHANNEL * sizeof(unsigned long));
369
+
370
+	const char event_header[] = "time\tchannel\n";
371
+	const char event_format[] = "%ld.%03ld\t%d\n";
372
+	const char stats_header[] = "channel\n";
373
+	const char stats_format[] = "%ld\n";
374
+	char line[LINE_MAX];
375
+
376
+	int i, result = 1;
377
+
378
+	// reconstruct event_time and histogram
379
+	if (events_filename && events_file && (events_file != stdout)) {
380
+		rewind(events_file);
381
+		while (fgets(line, LINE_MAX, events_file) != NULL) {
382
+			if(sscanf(line, event_format, &tv_sec, &tv_nsec, &channel) == 3) {
383
+				event_time.tv_sec = tv_sec;
384
+				event_time.tv_nsec = tv_nsec;
385
+				histogram[channel % MAX_CHANNEL] += 1; // update histogram
386
+			}
387
+		}
388
+	}
389
+	if (events_filename && events_file && ((events_file == stdout) || (ftell(events_file) == 0))) {
390
+		fprintf(events_file, event_header);
391
+	}
392
+
393
+	// readjust start_time
394
+	timespec_diff(&event_time, &start_time, &start_time);
395
+	fifo->context.config.start_time = start_time;
396
+
397
+
398
+	if (stats_file && stats_filename) {
399
+		stats_file  = (strncmp(stats_filename,  "-", 1) == 0) ? stdout : freopen(stats_filename,  stats_filemode, stats_file);
400
+		fifo->context.stats.file = stats_file;
401
+		result = !!stats_file;
402
+		if (stats_file) {
403
+			result = fprintf(stats_file, stats_header);
404
+			for (i = 0; i < MAX_CHANNEL; i++) {
405
+				result = result && (fprintf(stats_file, stats_format,
406
+					histogram[i]
407
+				) > 0);
408
+			}
409
+			fflush(stats_file);
410
+		}
411
+	}
412
+
413
+
414
+	do { // output loop
415
+		// lock the queue, wait for data, pull
416
+		pthread_mutex_lock(fifo->mutex);
417
+		while (fifo->empty) { // queue is empty, wait for producer
418
+			pthread_cond_wait(fifo->not_empty, fifo->mutex); // unlock, block, relock
419
+		}
420
+		queue_del(fifo, &data);
421
+		pthread_mutex_unlock (fifo->mutex);
422
+		pthread_cond_signal(fifo->not_full);
423
+
424
+		// event time since measurement start
425
+		event_time = data.event_time;
426
+		channel = data.channel;
427
+		timespec_diff(&start_time, &event_time, &event_time);
428
+
429
+		// update histogram
430
+		histogram[channel % MAX_CHANNEL] += 1;
431
+
432
+		if (events_file) {
433
+			result = (fprintf(events_file, event_format,
434
+				event_time.tv_sec,
435
+				event_time.tv_nsec / 1000000,
436
+				channel
437
+			) > 0);
438
+			fflush(events_file);
439
+		} else if (events_filename) {// try reopen
440
+			nanosleep(&sleep_time, NULL); // wait to retry
441
+			events_file = (strncmp(events_filename, "-", 1) == 0) ? stdout : freopen(events_filename, events_filemode, events_file);
442
+			fifo->context.event.file = events_file;
443
+			result = !!events_file;
444
+		}
445
+
446
+		if (stats_file && stats_filename) {
447
+			stats_file  = (strncmp(stats_filename,  "-", 1) == 0) ? stdout : freopen(stats_filename,  stats_filemode, stats_file);
448
+			fifo->context.stats.file = stats_file;
449
+			result = !!stats_file;
450
+			if (stats_file) {
451
+				for (i = 0; i < MAX_CHANNEL; i++) {
452
+					result = result && (fprintf(stats_file, stats_format,
453
+						histogram[i]
454
+					) > 0);
455
+				}
456
+				fflush(stats_file);
457
+			}
458
+		}
459
+
460
+	} while (result); // output loop
461
+
462
+	return (NULL);
463
+}
464
+
465
+
466
+/*
467
+void *update_context (void *q) {
468
+	queue *fifo;
469
+	fifo = (queue *)q;
470
+
471
+	return (NULL);
472
+}
473
+*/
474
+

Loading…
Annulla
Salva