1*418db63cSGunnar Mills/**
2*418db63cSGunnar Mills * Controller for virtual-media
3*418db63cSGunnar Mills *
4*418db63cSGunnar Mills * @module app/serverControl
5*418db63cSGunnar Mills * @exports virtualMediaController
6*418db63cSGunnar Mills * @name virtualMediaController
7*418db63cSGunnar Mills */
8*418db63cSGunnar Mills
9*418db63cSGunnar Millswindow.angular && (function(angular) {
10*418db63cSGunnar Mills  'use strict';
11*418db63cSGunnar Mills
12*418db63cSGunnar Mills  angular.module('app.serverControl').controller('virtualMediaController', [
13*418db63cSGunnar Mills    '$scope', 'APIUtils', 'toastService', 'dataService', 'nbdServerService',
14*418db63cSGunnar Mills    function($scope, APIUtils, toastService, dataService, nbdServerService) {
15*418db63cSGunnar Mills      $scope.devices = [];
16*418db63cSGunnar Mills
17*418db63cSGunnar Mills      // Only one Virtual Media WebSocket device is currently available.
18*418db63cSGunnar Mills      // Path is /vm/0/0.
19*418db63cSGunnar Mills      // TODO: Support more than 1 VM device, when backend support is added.
20*418db63cSGunnar Mills      var vmDevice = {};
21*418db63cSGunnar Mills      // Hardcode to 0 since /vm/0/0. Last 0 is the device ID.
22*418db63cSGunnar Mills      // To support more than 1 device ID, replace with a call to get the
23*418db63cSGunnar Mills      // device IDs and names.
24*418db63cSGunnar Mills      vmDevice.id = 0;
25*418db63cSGunnar Mills      vmDevice.deviceName = 'Virtual media device';
26*418db63cSGunnar Mills      findExistingConnection(vmDevice);
27*418db63cSGunnar Mills      $scope.devices.push(vmDevice);
28*418db63cSGunnar Mills
29*418db63cSGunnar Mills      $scope.startVM = function(index) {
30*418db63cSGunnar Mills        $scope.devices[index].isActive = true;
31*418db63cSGunnar Mills        var file = $scope.devices[index].file;
32*418db63cSGunnar Mills        var id = $scope.devices[index].id;
33*418db63cSGunnar Mills        var host = dataService.getHost().replace('https://', '');
34*418db63cSGunnar Mills        var server = new NBDServer('wss://' + host + '/vm/0/' + id, file, id);
35*418db63cSGunnar Mills        $scope.devices[index].nbdServer = server;
36*418db63cSGunnar Mills        nbdServerService.addConnection(id, server, file);
37*418db63cSGunnar Mills        server.start();
38*418db63cSGunnar Mills      };
39*418db63cSGunnar Mills      $scope.stopVM = function(index) {
40*418db63cSGunnar Mills        $scope.devices[index].isActive = false;
41*418db63cSGunnar Mills        var server = $scope.devices[index].nbdServer;
42*418db63cSGunnar Mills        server.stop();
43*418db63cSGunnar Mills      };
44*418db63cSGunnar Mills
45*418db63cSGunnar Mills      $scope.resetFile = function(index) {
46*418db63cSGunnar Mills        document.getElementById('file-upload').value = '';
47*418db63cSGunnar Mills        $scope.devices[index].file = '';
48*418db63cSGunnar Mills      };
49*418db63cSGunnar Mills
50*418db63cSGunnar Mills      function findExistingConnection(vmDevice) {
51*418db63cSGunnar Mills        // Checks with existing connections kept in nbdServerService for an open
52*418db63cSGunnar Mills        // Websocket connection.
53*418db63cSGunnar Mills        var existingConnectionsMap = nbdServerService.getExistingConnections();
54*418db63cSGunnar Mills        if (existingConnectionsMap.hasOwnProperty(vmDevice.id)) {
55*418db63cSGunnar Mills          // Open ws will have a ready state of 1
56*418db63cSGunnar Mills          if (existingConnectionsMap[vmDevice.id].server.ws.readyState === 1) {
57*418db63cSGunnar Mills            vmDevice.isActive = true;
58*418db63cSGunnar Mills            vmDevice.file = existingConnectionsMap[vmDevice.id].file;
59*418db63cSGunnar Mills            vmDevice.nbdServer = existingConnectionsMap[vmDevice.id].server;
60*418db63cSGunnar Mills          }
61*418db63cSGunnar Mills        }
62*418db63cSGunnar Mills        return vmDevice;
63*418db63cSGunnar Mills      }
64*418db63cSGunnar Mills    }
65*418db63cSGunnar Mills  ]);
66*418db63cSGunnar Mills})(angular);
67*418db63cSGunnar Mills
68*418db63cSGunnar Mills/* handshake flags */
69*418db63cSGunnar Millsconst NBD_FLAG_FIXED_NEWSTYLE = 0x1;
70*418db63cSGunnar Millsconst NBD_FLAG_NO_ZEROES = 0x2;
71*418db63cSGunnar Mills
72*418db63cSGunnar Mills/* transmission flags */
73*418db63cSGunnar Millsconst NBD_FLAG_HAS_FLAGS = 0x1;
74*418db63cSGunnar Millsconst NBD_FLAG_READ_ONLY = 0x2;
75*418db63cSGunnar Mills
76*418db63cSGunnar Mills/* option negotiation */
77*418db63cSGunnar Millsconst NBD_OPT_EXPORT_NAME = 0x1;
78*418db63cSGunnar Millsconst NBD_REP_FLAG_ERROR = 0x1 << 31;
79*418db63cSGunnar Millsconst NBD_REP_ERR_UNSUP = NBD_REP_FLAG_ERROR | 1;
80*418db63cSGunnar Mills
81*418db63cSGunnar Mills/* command definitions */
82*418db63cSGunnar Millsconst NBD_CMD_READ = 0;
83*418db63cSGunnar Millsconst NBD_CMD_WRITE = 1;
84*418db63cSGunnar Millsconst NBD_CMD_DISC = 2;
85*418db63cSGunnar Millsconst NBD_CMD_TRIM = 4;
86*418db63cSGunnar Mills
87*418db63cSGunnar Mills/* errno */
88*418db63cSGunnar Millsconst EPERM = 1;
89*418db63cSGunnar Millsconst EIO = 5;
90*418db63cSGunnar Millsconst EINVAL = 22;
91*418db63cSGunnar Millsconst ENOSPC = 28;
92*418db63cSGunnar Mills
93*418db63cSGunnar Mills/* internal object state */
94*418db63cSGunnar Millsconst NBD_STATE_UNKNOWN = 1;
95*418db63cSGunnar Millsconst NBD_STATE_OPEN = 2;
96*418db63cSGunnar Millsconst NBD_STATE_WAIT_CFLAGS = 3;
97*418db63cSGunnar Millsconst NBD_STATE_WAIT_OPTION = 4;
98*418db63cSGunnar Millsconst NBD_STATE_TRANSMISSION = 5;
99*418db63cSGunnar Mills
100*418db63cSGunnar Millsfunction NBDServer(endpoint, file, id) {
101*418db63cSGunnar Mills  this.file = file;
102*418db63cSGunnar Mills  this.id = id;
103*418db63cSGunnar Mills  this.endpoint = endpoint;
104*418db63cSGunnar Mills  this.ws = null;
105*418db63cSGunnar Mills  this.state = NBD_STATE_UNKNOWN;
106*418db63cSGunnar Mills  this.msgbuf = null;
107*418db63cSGunnar Mills
108*418db63cSGunnar Mills  this.start = function() {
109*418db63cSGunnar Mills    this.ws = new WebSocket(this.endpoint);
110*418db63cSGunnar Mills    this.state = NBD_STATE_OPEN;
111*418db63cSGunnar Mills    this.ws.binaryType = 'arraybuffer';
112*418db63cSGunnar Mills    this.ws.onmessage = this._on_ws_message.bind(this);
113*418db63cSGunnar Mills    this.ws.onopen = this._on_ws_open.bind(this);
114*418db63cSGunnar Mills    this.ws.onclose = this._on_ws_close.bind(this);
115*418db63cSGunnar Mills    this.ws.onerror = this._on_ws_error.bind(this);
116*418db63cSGunnar Mills  };
117*418db63cSGunnar Mills
118*418db63cSGunnar Mills  this.stop = function() {
119*418db63cSGunnar Mills    this.ws.close();
120*418db63cSGunnar Mills    this.state = NBD_STATE_UNKNOWN;
121*418db63cSGunnar Mills  };
122*418db63cSGunnar Mills
123*418db63cSGunnar Mills  this._on_ws_error = function(ev) {
124*418db63cSGunnar Mills    console.log('vm/0/' + id + 'error: ' + ev);
125*418db63cSGunnar Mills  };
126*418db63cSGunnar Mills
127*418db63cSGunnar Mills  this._on_ws_close = function(ev) {
128*418db63cSGunnar Mills    console.log(
129*418db63cSGunnar Mills        'vm/0/' + id + ' closed with code: ' + ev.code +
130*418db63cSGunnar Mills        ' reason: ' + ev.reason);
131*418db63cSGunnar Mills  };
132*418db63cSGunnar Mills
133*418db63cSGunnar Mills  /* websocket event handlers */
134*418db63cSGunnar Mills  this._on_ws_open = function(ev) {
135*418db63cSGunnar Mills    console.log('vm/0/' + id + ' opened');
136*418db63cSGunnar Mills    this.client = {
137*418db63cSGunnar Mills      flags: 0,
138*418db63cSGunnar Mills    };
139*418db63cSGunnar Mills    this._negotiate();
140*418db63cSGunnar Mills  };
141*418db63cSGunnar Mills
142*418db63cSGunnar Mills  this._on_ws_message = function(ev) {
143*418db63cSGunnar Mills    var data = ev.data;
144*418db63cSGunnar Mills
145*418db63cSGunnar Mills    if (this.msgbuf == null) {
146*418db63cSGunnar Mills      this.msgbuf = data;
147*418db63cSGunnar Mills    } else {
148*418db63cSGunnar Mills      var tmp = new Uint8Array(this.msgbuf.byteLength + data.byteLength);
149*418db63cSGunnar Mills      tmp.set(new Uint8Array(this.msgbuf), 0);
150*418db63cSGunnar Mills      tmp.set(new Uint8Array(data), this.msgbuf.byteLength);
151*418db63cSGunnar Mills      this.msgbuf = tmp.buffer;
152*418db63cSGunnar Mills    }
153*418db63cSGunnar Mills
154*418db63cSGunnar Mills    for (;;) {
155*418db63cSGunnar Mills      var handler = this.recv_handlers[this.state];
156*418db63cSGunnar Mills      if (!handler) {
157*418db63cSGunnar Mills        console.log('no handler for state ' + this.state);
158*418db63cSGunnar Mills        this.stop();
159*418db63cSGunnar Mills        break;
160*418db63cSGunnar Mills      }
161*418db63cSGunnar Mills
162*418db63cSGunnar Mills      var consumed = handler(this.msgbuf);
163*418db63cSGunnar Mills      if (consumed < 0) {
164*418db63cSGunnar Mills        console.log(
165*418db63cSGunnar Mills            'handler[state=' + this.state + '] returned error ' + consumed);
166*418db63cSGunnar Mills        this.stop();
167*418db63cSGunnar Mills        break;
168*418db63cSGunnar Mills      }
169*418db63cSGunnar Mills
170*418db63cSGunnar Mills      if (consumed == 0) {
171*418db63cSGunnar Mills        break;
172*418db63cSGunnar Mills      }
173*418db63cSGunnar Mills
174*418db63cSGunnar Mills      if (consumed > 0) {
175*418db63cSGunnar Mills        if (consumed == this.msgbuf.byteLength) {
176*418db63cSGunnar Mills          this.msgbuf = null;
177*418db63cSGunnar Mills          break;
178*418db63cSGunnar Mills        }
179*418db63cSGunnar Mills        this.msgbuf = this.msgbuf.slice(consumed);
180*418db63cSGunnar Mills      }
181*418db63cSGunnar Mills    }
182*418db63cSGunnar Mills  };
183*418db63cSGunnar Mills
184*418db63cSGunnar Mills  this._negotiate = function() {
185*418db63cSGunnar Mills    var buf = new ArrayBuffer(18);
186*418db63cSGunnar Mills    var data = new DataView(buf, 0, 18);
187*418db63cSGunnar Mills
188*418db63cSGunnar Mills    /* NBD magic: NBDMAGIC */
189*418db63cSGunnar Mills    data.setUint32(0, 0x4e42444d);
190*418db63cSGunnar Mills    data.setUint32(4, 0x41474943);
191*418db63cSGunnar Mills
192*418db63cSGunnar Mills    /* newstyle negotiation: IHAVEOPT */
193*418db63cSGunnar Mills    data.setUint32(8, 0x49484156);
194*418db63cSGunnar Mills    data.setUint32(12, 0x454F5054);
195*418db63cSGunnar Mills
196*418db63cSGunnar Mills    /* flags: fixed newstyle negotiation, no padding */
197*418db63cSGunnar Mills    data.setUint16(16, NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES);
198*418db63cSGunnar Mills
199*418db63cSGunnar Mills    this.state = NBD_STATE_WAIT_CFLAGS;
200*418db63cSGunnar Mills    this.ws.send(buf);
201*418db63cSGunnar Mills  };
202*418db63cSGunnar Mills
203*418db63cSGunnar Mills  /* handlers */
204*418db63cSGunnar Mills  this._handle_cflags = function(buf) {
205*418db63cSGunnar Mills    if (buf.byteLength < 4) {
206*418db63cSGunnar Mills      return 0;
207*418db63cSGunnar Mills    }
208*418db63cSGunnar Mills
209*418db63cSGunnar Mills    var data = new DataView(buf, 0, 4);
210*418db63cSGunnar Mills    this.client.flags = data.getUint32(0);
211*418db63cSGunnar Mills
212*418db63cSGunnar Mills    this.state = NBD_STATE_WAIT_OPTION;
213*418db63cSGunnar Mills    return 4;
214*418db63cSGunnar Mills  };
215*418db63cSGunnar Mills
216*418db63cSGunnar Mills  this._handle_option = function(buf) {
217*418db63cSGunnar Mills    if (buf.byteLength < 16) return 0;
218*418db63cSGunnar Mills
219*418db63cSGunnar Mills    var data = new DataView(buf, 0, 16);
220*418db63cSGunnar Mills    if (data.getUint32(0) != 0x49484156 || data.getUint32(4) != 0x454F5054) {
221*418db63cSGunnar Mills      console.log('invalid option magic');
222*418db63cSGunnar Mills      return -1;
223*418db63cSGunnar Mills    }
224*418db63cSGunnar Mills
225*418db63cSGunnar Mills    var opt = data.getUint32(8);
226*418db63cSGunnar Mills    var len = data.getUint32(12);
227*418db63cSGunnar Mills
228*418db63cSGunnar Mills
229*418db63cSGunnar Mills    if (buf.byteLength < 16 + len) {
230*418db63cSGunnar Mills      return 0;
231*418db63cSGunnar Mills    }
232*418db63cSGunnar Mills
233*418db63cSGunnar Mills    switch (opt) {
234*418db63cSGunnar Mills      case NBD_OPT_EXPORT_NAME:
235*418db63cSGunnar Mills        var n = 10;
236*418db63cSGunnar Mills        if (!(this.client.flags & NBD_FLAG_NO_ZEROES)) n += 124;
237*418db63cSGunnar Mills        var resp = new ArrayBuffer(n);
238*418db63cSGunnar Mills        var view = new DataView(resp, 0, 10);
239*418db63cSGunnar Mills        /* export size. */
240*418db63cSGunnar Mills        var size = this.file.size;
241*418db63cSGunnar Mills        view.setUint32(0, Math.floor(size / (2 ** 32)));
242*418db63cSGunnar Mills        view.setUint32(4, size & 0xffffffff);
243*418db63cSGunnar Mills        /* transmission flags: read-only */
244*418db63cSGunnar Mills        view.setUint16(8, NBD_FLAG_HAS_FLAGS | NBD_FLAG_READ_ONLY);
245*418db63cSGunnar Mills        this.ws.send(resp);
246*418db63cSGunnar Mills
247*418db63cSGunnar Mills        this.state = NBD_STATE_TRANSMISSION;
248*418db63cSGunnar Mills        break;
249*418db63cSGunnar Mills
250*418db63cSGunnar Mills      default:
251*418db63cSGunnar Mills        console.log('handle_option: Unsupported option: ' + opt);
252*418db63cSGunnar Mills        /* reject other options */
253*418db63cSGunnar Mills        var resp = new ArrayBuffer(20);
254*418db63cSGunnar Mills        var view = new DataView(resp, 0, 20);
255*418db63cSGunnar Mills        view.setUint32(0, 0x0003e889);
256*418db63cSGunnar Mills        view.setUint32(4, 0x045565a9);
257*418db63cSGunnar Mills        view.setUint32(8, opt);
258*418db63cSGunnar Mills        view.setUint32(12, NBD_REP_ERR_UNSUP);
259*418db63cSGunnar Mills        view.setUint32(16, 0);
260*418db63cSGunnar Mills        this.ws.send(resp);
261*418db63cSGunnar Mills    }
262*418db63cSGunnar Mills
263*418db63cSGunnar Mills    return 16 + len;
264*418db63cSGunnar Mills  };
265*418db63cSGunnar Mills
266*418db63cSGunnar Mills  this._create_cmd_response = function(req, rc, data = null) {
267*418db63cSGunnar Mills    var len = 16;
268*418db63cSGunnar Mills    if (data) len += data.byteLength;
269*418db63cSGunnar Mills    var resp = new ArrayBuffer(len);
270*418db63cSGunnar Mills    var view = new DataView(resp, 0, 16);
271*418db63cSGunnar Mills    view.setUint32(0, 0x67446698);
272*418db63cSGunnar Mills    view.setUint32(4, rc);
273*418db63cSGunnar Mills    view.setUint32(8, req.handle_msB);
274*418db63cSGunnar Mills    view.setUint32(12, req.handle_lsB);
275*418db63cSGunnar Mills    if (data) new Uint8Array(resp, 16).set(new Uint8Array(data));
276*418db63cSGunnar Mills    return resp;
277*418db63cSGunnar Mills  };
278*418db63cSGunnar Mills
279*418db63cSGunnar Mills  this._handle_cmd = function(buf) {
280*418db63cSGunnar Mills    if (buf.byteLength < 28) {
281*418db63cSGunnar Mills      return 0;
282*418db63cSGunnar Mills    }
283*418db63cSGunnar Mills
284*418db63cSGunnar Mills    var view = new DataView(buf, 0, 28);
285*418db63cSGunnar Mills
286*418db63cSGunnar Mills    if (view.getUint32(0) != 0x25609513) {
287*418db63cSGunnar Mills      console.log('invalid request magic');
288*418db63cSGunnar Mills      return -1;
289*418db63cSGunnar Mills    }
290*418db63cSGunnar Mills
291*418db63cSGunnar Mills    var req = {
292*418db63cSGunnar Mills      flags: view.getUint16(4),
293*418db63cSGunnar Mills      type: view.getUint16(6),
294*418db63cSGunnar Mills      handle_msB: view.getUint32(8),
295*418db63cSGunnar Mills      handle_lsB: view.getUint32(12),
296*418db63cSGunnar Mills      offset_msB: view.getUint32(16),
297*418db63cSGunnar Mills      offset_lsB: view.getUint32(20),
298*418db63cSGunnar Mills      length: view.getUint32(24),
299*418db63cSGunnar Mills    };
300*418db63cSGunnar Mills
301*418db63cSGunnar Mills    /* we don't support writes, so nothing needs the data at present */
302*418db63cSGunnar Mills    /* req.data = buf.slice(28); */
303*418db63cSGunnar Mills
304*418db63cSGunnar Mills    var err = 0;
305*418db63cSGunnar Mills    var consumed = 28;
306*418db63cSGunnar Mills
307*418db63cSGunnar Mills    /* the command handlers return 0 on success, and send their
308*418db63cSGunnar Mills     * own response. Otherwise, a non-zero error code will be
309*418db63cSGunnar Mills     * used as a simple error response
310*418db63cSGunnar Mills     */
311*418db63cSGunnar Mills    switch (req.type) {
312*418db63cSGunnar Mills      case NBD_CMD_READ:
313*418db63cSGunnar Mills        err = this._handle_cmd_read(req);
314*418db63cSGunnar Mills        break;
315*418db63cSGunnar Mills
316*418db63cSGunnar Mills      case NBD_CMD_DISC:
317*418db63cSGunnar Mills        err = this._handle_cmd_disconnect(req);
318*418db63cSGunnar Mills        break;
319*418db63cSGunnar Mills
320*418db63cSGunnar Mills      case NBD_CMD_WRITE:
321*418db63cSGunnar Mills        /* we also need length bytes of data to consume a write
322*418db63cSGunnar Mills         * request */
323*418db63cSGunnar Mills        if (buf.byteLength < 28 + req.length) {
324*418db63cSGunnar Mills          return 0;
325*418db63cSGunnar Mills        }
326*418db63cSGunnar Mills        consumed += req.length;
327*418db63cSGunnar Mills        err = EPERM;
328*418db63cSGunnar Mills        break;
329*418db63cSGunnar Mills
330*418db63cSGunnar Mills      case NBD_CMD_TRIM:
331*418db63cSGunnar Mills        err = EPERM;
332*418db63cSGunnar Mills        break;
333*418db63cSGunnar Mills
334*418db63cSGunnar Mills      default:
335*418db63cSGunnar Mills        console.log('invalid command 0x' + req.type.toString(16));
336*418db63cSGunnar Mills        err = EINVAL;
337*418db63cSGunnar Mills    }
338*418db63cSGunnar Mills
339*418db63cSGunnar Mills    if (err) {
340*418db63cSGunnar Mills      console.log('error handle_cmd: ' + err);
341*418db63cSGunnar Mills      var resp = this._create_cmd_response(req, err);
342*418db63cSGunnar Mills      this.ws.send(resp);
343*418db63cSGunnar Mills    }
344*418db63cSGunnar Mills
345*418db63cSGunnar Mills    return consumed;
346*418db63cSGunnar Mills  };
347*418db63cSGunnar Mills
348*418db63cSGunnar Mills  this._handle_cmd_read = function(req) {
349*418db63cSGunnar Mills    var offset;
350*418db63cSGunnar Mills
351*418db63cSGunnar Mills    offset = (req.offset_msB * 2 ** 32) + req.offset_lsB;
352*418db63cSGunnar Mills
353*418db63cSGunnar Mills    if (offset > Number.MAX_SAFE_INTEGER) return ENOSPC;
354*418db63cSGunnar Mills
355*418db63cSGunnar Mills    if (offset + req.length > Number.MAX_SAFE_INTEGER) return ENOSPC;
356*418db63cSGunnar Mills
357*418db63cSGunnar Mills    if (offset + req.length > file.size) return ENOSPC;
358*418db63cSGunnar Mills
359*418db63cSGunnar Mills    var blob = this.file.slice(offset, offset + req.length);
360*418db63cSGunnar Mills    var reader = new FileReader();
361*418db63cSGunnar Mills
362*418db63cSGunnar Mills    reader.onload = (function(ev) {
363*418db63cSGunnar Mills                      var reader = ev.target;
364*418db63cSGunnar Mills                      if (reader.readyState != FileReader.DONE) return;
365*418db63cSGunnar Mills                      var resp =
366*418db63cSGunnar Mills                          this._create_cmd_response(req, 0, reader.result);
367*418db63cSGunnar Mills                      this.ws.send(resp);
368*418db63cSGunnar Mills                    }).bind(this);
369*418db63cSGunnar Mills
370*418db63cSGunnar Mills    reader.onerror = (function(ev) {
371*418db63cSGunnar Mills                       var reader = ev.target;
372*418db63cSGunnar Mills                       console.log('error reading file: ' + reader.error);
373*418db63cSGunnar Mills                       var resp = this._create_cmd_response(req, EIO);
374*418db63cSGunnar Mills                       this.ws.send(resp);
375*418db63cSGunnar Mills                     }).bind(this);
376*418db63cSGunnar Mills    reader.readAsArrayBuffer(blob);
377*418db63cSGunnar Mills
378*418db63cSGunnar Mills    return 0;
379*418db63cSGunnar Mills  };
380*418db63cSGunnar Mills
381*418db63cSGunnar Mills  this._handle_cmd_disconnect = function(req) {
382*418db63cSGunnar Mills    this.stop();
383*418db63cSGunnar Mills    return 0;
384*418db63cSGunnar Mills  };
385*418db63cSGunnar Mills
386*418db63cSGunnar Mills  this.recv_handlers = Object.freeze({
387*418db63cSGunnar Mills    [NBD_STATE_WAIT_CFLAGS]: this._handle_cflags.bind(this),
388*418db63cSGunnar Mills    [NBD_STATE_WAIT_OPTION]: this._handle_option.bind(this),
389*418db63cSGunnar Mills    [NBD_STATE_TRANSMISSION]: this._handle_cmd.bind(this),
390*418db63cSGunnar Mills  });
391*418db63cSGunnar Mills}
392