1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5define("mojo/public/js/router", [
6  "console",
7  "mojo/public/js/codec",
8  "mojo/public/js/core",
9  "mojo/public/js/connector",
10  "mojo/public/js/lib/control_message_handler",
11  "mojo/public/js/validator",
12], function(console, codec, core, connector, controlMessageHandler, validator) {
13
14  var Connector = connector.Connector;
15  var MessageReader = codec.MessageReader;
16  var Validator = validator.Validator;
17  var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
18
19  function Router(handle, interface_version, connectorFactory) {
20    if (!core.isHandle(handle))
21      throw new Error("Router constructor: Not a handle");
22    if (connectorFactory === undefined)
23      connectorFactory = Connector;
24    this.connector_ = new connectorFactory(handle);
25    this.incomingReceiver_ = null;
26    this.errorHandler_ = null;
27    this.nextRequestID_ = 0;
28    this.completers_ = new Map();
29    this.payloadValidators_ = [];
30    this.testingController_ = null;
31
32    if (interface_version !== undefined) {
33      this.controlMessageHandler_ = new
34          ControlMessageHandler(interface_version);
35    }
36
37    this.connector_.setIncomingReceiver({
38        accept: this.handleIncomingMessage_.bind(this),
39    });
40    this.connector_.setErrorHandler({
41        onError: this.handleConnectionError_.bind(this),
42    });
43  }
44
45  Router.prototype.close = function() {
46    this.completers_.clear();  // Drop any responders.
47    this.connector_.close();
48    this.testingController_ = null;
49  };
50
51  Router.prototype.accept = function(message) {
52    this.connector_.accept(message);
53  };
54
55  Router.prototype.reject = function(message) {
56    // TODO(mpcomplete): no way to trasmit errors over a Connection.
57  };
58
59  Router.prototype.acceptAndExpectResponse = function(message) {
60    // Reserve 0 in case we want it to convey special meaning in the future.
61    var requestID = this.nextRequestID_++;
62    if (requestID == 0)
63      requestID = this.nextRequestID_++;
64
65    message.setRequestID(requestID);
66    var result = this.connector_.accept(message);
67    if (!result)
68      return Promise.reject(Error("Connection error"));
69
70    var completer = {};
71    this.completers_.set(requestID, completer);
72    return new Promise(function(resolve, reject) {
73      completer.resolve = resolve;
74      completer.reject = reject;
75    });
76  };
77
78  Router.prototype.setIncomingReceiver = function(receiver) {
79    this.incomingReceiver_ = receiver;
80  };
81
82  Router.prototype.setPayloadValidators = function(payloadValidators) {
83    this.payloadValidators_ = payloadValidators;
84  };
85
86  Router.prototype.setErrorHandler = function(handler) {
87    this.errorHandler_ = handler;
88  };
89
90  Router.prototype.encounteredError = function() {
91    return this.connector_.encounteredError();
92  };
93
94  Router.prototype.enableTestingMode = function() {
95    this.testingController_ = new RouterTestingController(this.connector_);
96    return this.testingController_;
97  };
98
99  Router.prototype.handleIncomingMessage_ = function(message) {
100    var noError = validator.validationError.NONE;
101    var messageValidator = new Validator(message);
102    var err = messageValidator.validateMessageHeader();
103    for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
104      err = this.payloadValidators_[i](messageValidator);
105
106    if (err == noError)
107      this.handleValidIncomingMessage_(message);
108    else
109      this.handleInvalidIncomingMessage_(message, err);
110  };
111
112  Router.prototype.handleValidIncomingMessage_ = function(message) {
113    if (this.testingController_)
114      return;
115
116    if (message.expectsResponse()) {
117      if (controlMessageHandler.isControlMessage(message)) {
118        if (this.controlMessageHandler_) {
119          this.controlMessageHandler_.acceptWithResponder(message, this);
120        } else {
121          this.close();
122        }
123      } else if (this.incomingReceiver_) {
124        this.incomingReceiver_.acceptWithResponder(message, this);
125      } else {
126        // If we receive a request expecting a response when the client is not
127        // listening, then we have no choice but to tear down the pipe.
128        this.close();
129      }
130    } else if (message.isResponse()) {
131      var reader = new MessageReader(message);
132      var requestID = reader.requestID;
133      var completer = this.completers_.get(requestID);
134      if (completer) {
135        this.completers_.delete(requestID);
136        completer.resolve(message);
137      } else {
138        console.log("Unexpected response with request ID: " + requestID);
139      }
140    } else {
141      if (controlMessageHandler.isControlMessage(message)) {
142        if (this.controlMessageHandler_) {
143          var ok = this.controlMessageHandler_.accept(message);
144          if (ok) return;
145        }
146        this.close();
147      } else if (this.incomingReceiver_) {
148        this.incomingReceiver_.accept(message);
149      }
150    }
151  };
152
153  Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
154    if (!this.testingController_) {
155      // TODO(yzshen): Consider notifying the embedder.
156      // TODO(yzshen): This should also trigger connection error handler.
157      // Consider making accept() return a boolean and let the connector deal
158      // with this, as the C++ code does.
159      console.log("Invalid message: " + validator.validationError[error]);
160
161      this.close();
162      return;
163    }
164
165    this.testingController_.onInvalidIncomingMessage(error);
166  };
167
168  Router.prototype.handleConnectionError_ = function(result) {
169    this.completers_.forEach(function(value) {
170      value.reject(result);
171    });
172    if (this.errorHandler_)
173      this.errorHandler_();
174    this.close();
175  };
176
177  // The RouterTestingController is used in unit tests. It defeats valid message
178  // handling and delgates invalid message handling.
179
180  function RouterTestingController(connector) {
181    this.connector_ = connector;
182    this.invalidMessageHandler_ = null;
183  }
184
185  RouterTestingController.prototype.waitForNextMessage = function() {
186    this.connector_.waitForNextMessageForTesting();
187  };
188
189  RouterTestingController.prototype.setInvalidIncomingMessageHandler =
190      function(callback) {
191    this.invalidMessageHandler_ = callback;
192  };
193
194  RouterTestingController.prototype.onInvalidIncomingMessage =
195      function(error) {
196    if (this.invalidMessageHandler_)
197      this.invalidMessageHandler_(error);
198  };
199
200  var exports = {};
201  exports.Router = Router;
202  return exports;
203});
204