| #!mojo mojo:dart_content_handler |
| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:core'; |
| import 'dart:typed_data'; |
| |
| import 'package:mojo/application.dart'; |
| import 'package:mojo/bindings.dart'; |
| import 'package:mojo/core.dart'; |
| import 'package:mojo/mojo/network_error.mojom.dart'; |
| import 'package:mojo_services/mojo/files/file.mojom.dart' as files; |
| import 'package:mojo_services/mojo/files/types.mojom.dart' as files; |
| import 'package:mojo_services/mojo/net_address.mojom.dart'; |
| import 'package:mojo_services/mojo/network_service.mojom.dart'; |
| import 'package:mojo_services/mojo/tcp_bound_socket.mojom.dart'; |
| import 'package:mojo_services/mojo/tcp_connected_socket.mojom.dart'; |
| import 'package:mojo_services/mojo/terminal/terminal_client.mojom.dart'; |
| |
| void ignoreFuture(Future f) { |
| f.catchError((e) {}); |
| } |
| |
| NetAddress makeIPv4NetAddress(List<int> addr, int port) { |
| var rv = new NetAddress(); |
| rv.family = NetAddressFamily.ipv4; |
| rv.ipv4 = new NetAddressIPv4(); |
| rv.ipv4.addr = new List<int>.from(addr); |
| rv.ipv4.port = port; |
| return rv; |
| } |
| |
| void fputs(files.File f, String s) { |
| ignoreFuture(f.write((s + '\n').codeUnits, 0, files.Whence.fromCurrent)); |
| } |
| |
| // Connects the terminal |File| and the socket. |
| // TODO(vtl): |
| // * Error handling: both connection/socket errors and terminal errors. |
| // * Relatedly, we should listen for _socketSender's peer being closed (also |
| // _socket, I guess). |
| // * Handle the socket send pipe being full (currently, we assume it's never |
| // full). |
| class Connector { |
| final Application _application; |
| files.FileProxy _terminal; |
| TcpConnectedSocketProxy _socket; |
| MojoDataPipeProducer _socketSender; |
| MojoDataPipeConsumer _socketReceiver; |
| MojoEventSubscription _socketReceiverEventSubscription; |
| final ByteData _readBuffer; |
| final ByteData _writeBuffer; |
| |
| // TODO(vtl): Don't just hard-code buffer sizes. |
| Connector(this._application, this._terminal) |
| : _readBuffer = new ByteData(16 * 1024), |
| _writeBuffer = new ByteData(16 * 1024); |
| |
| Future connect(NetAddress remote_address) async { |
| try { |
| var networkService = new NetworkServiceProxy.unbound(); |
| _application.connectToService('mojo:network_service', networkService); |
| |
| NetAddress local_address = makeIPv4NetAddress([0, 0, 0, 0], 0); |
| var boundSocket = new TcpBoundSocketProxy.unbound(); |
| await networkService.createTcpBoundSocket(local_address, boundSocket); |
| await networkService.close(); |
| |
| var sendDataPipe = new MojoDataPipe(); |
| _socketSender = sendDataPipe.producer; |
| var receiveDataPipe = new MojoDataPipe(); |
| _socketReceiver = receiveDataPipe.consumer; |
| _socket = new TcpConnectedSocketProxy.unbound(); |
| await boundSocket.connect(remote_address, sendDataPipe.consumer, |
| receiveDataPipe.producer, _socket); |
| await boundSocket.close(); |
| |
| // Set up reading from the terminal. |
| _startReadingFromTerminal(); |
| |
| // Set up reading from the socket. |
| _socketReceiverEventSubscription = |
| new MojoEventSubscription(_socketReceiver.handle); |
| _socketReceiverEventSubscription.subscribe(_onSocketReceiverEvent); |
| } catch (e) { |
| _shutDown(); |
| } |
| } |
| |
| void _startReadingFromTerminal() { |
| // TODO(vtl): Do we have to do something on error? |
| _terminal |
| .read(_writeBuffer.lengthInBytes, 0, files.Whence.fromCurrent) |
| .then(_onReadFromTerminal) |
| .catchError((e) { |
| _shutDown(); |
| }); |
| } |
| |
| void _onReadFromTerminal(files.FileReadResponseParams p) { |
| if (p.error != files.Error.ok) { |
| // TODO(vtl): Do terminal errors. |
| return; |
| } |
| |
| // TODO(vtl): Verify that |bytesRead.length| is within the expected range. |
| for (var i = 0, j = 0; i < p.bytesRead.length; i++, j++) { |
| // TODO(vtl): Temporary hack: Translate \r to \n, since we don't have |
| // built-in support for that. |
| if (p.bytesRead[i] == 13) { |
| _writeBuffer.setUint8(i, 10); |
| } else { |
| _writeBuffer.setUint8(i, p.bytesRead[i]); |
| } |
| } |
| |
| // TODO(vtl): Handle the send data pipe being full (or closed). |
| _socketSender |
| .write(new ByteData.view(_writeBuffer.buffer, 0, p.bytesRead.length)); |
| |
| _startReadingFromTerminal(); |
| } |
| |
| void _onSocketReceiverEvent(int mojoSignals) { |
| var shouldShutDown = false; |
| if (MojoHandleSignals.isReadable(mojoSignals)) { |
| var numBytesRead = _socketReceiver.read(_readBuffer); |
| if (_socketReceiver.status == MojoResult.kOk) { |
| assert(numBytesRead > 0); |
| _terminal |
| .write(_readBuffer.buffer.asUint8List(0, numBytesRead), 0, |
| files.Whence.fromCurrent) |
| .catchError((e) { |
| _shutDown(); |
| }); |
| _socketReceiverEventSubscription.enableReadEvents(); |
| } else { |
| shouldShutDown = true; |
| } |
| } else if (MojoHandleSignals.isPeerClosed(mojoSignals)) { |
| shouldShutDown = true; |
| } else { |
| String signals = MojoHandleSignals.string(mojoSignals); |
| throw 'Unexpected handle event: $signals'; |
| } |
| if (shouldShutDown) { |
| _shutDown(); |
| } |
| } |
| |
| void _shutDown() { |
| if (_socketReceiverEventSubscription != null) { |
| ignoreFuture(_socketReceiverEventSubscription.close()); |
| _socketReceiverEventSubscription = null; |
| } |
| if (_socketSender != null) { |
| if (_socketSender.handle.isValid) _socketSender.handle.close(); |
| _socketSender = null; |
| } |
| if (_socketReceiver != null) { |
| if (_socketReceiver.handle.isValid) _socketReceiver.handle.close(); |
| _socketReceiver = null; |
| } |
| if (_terminal != null) { |
| ignoreFuture(_terminal.close()); |
| _terminal = null; |
| } |
| } |
| } |
| |
| class TerminalClientImpl implements TerminalClient { |
| TerminalClientStub _stub; |
| Application _application; |
| String _resolvedUrl; |
| |
| TerminalClientImpl( |
| this._application, this._resolvedUrl, MojoMessagePipeEndpoint endpoint) { |
| _stub = new TerminalClientStub.fromEndpoint(endpoint, this); |
| } |
| |
| @override |
| void connectToTerminal(files.FileProxy terminal) { |
| var url = Uri.parse(_resolvedUrl); |
| NetAddress remote_address; |
| try { |
| remote_address = _getNetAddressFromUrl(url); |
| } catch (e) { |
| fputs( |
| terminal, |
| 'HALP: Add a query: ?host=<host>&port=<port>\n' |
| '(<host> must be "localhost" or n1.n2.n3.n4)\n\n' |
| 'Got query parameters:\n' + |
| url.queryParameters.toString()); |
| ignoreFuture(terminal.close()); |
| return; |
| } |
| |
| // TODO(vtl): Currently, we only do IPv4, so this should work. |
| fputs( |
| terminal, |
| 'Connecting to: ' + |
| remote_address.ipv4.addr.join('.') + |
| ':' + |
| remote_address.ipv4.port.toString() + |
| '...'); |
| |
| var connector = new Connector(_application, terminal); |
| // TODO(vtl): Do we have to do something on error? |
| connector.connect(remote_address).catchError((e) {}); |
| } |
| |
| // Note: May throw all sorts of things. |
| static NetAddress _getNetAddressFromUrl(Uri url) { |
| var params = url.queryParameters; |
| var host = params['host']; |
| return makeIPv4NetAddress( |
| (host == 'localhost') ? [127, 0, 0, 1] : Uri.parseIPv4Address(host), |
| int.parse(params['port'])); |
| } |
| } |
| |
| class NetcatApplication extends Application { |
| NetcatApplication.fromHandle(MojoHandle handle) : super.fromHandle(handle); |
| |
| @override |
| void acceptConnection(String requestorUrl, String resolvedUrl, |
| ApplicationConnection connection) { |
| connection.provideService(TerminalClient.serviceName, |
| (endpoint) => new TerminalClientImpl(this, resolvedUrl, endpoint)); |
| } |
| } |
| |
| main(List args, Object handleToken) { |
| MojoHandle appHandle = new MojoHandle(handleToken); |
| new NetcatApplication.fromHandle(appHandle) |
| ..onError = ((Object e) { |
| MojoHandle.reportLeakedHandles(); |
| }); |
| } |