Index: components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalTunnelBridge.java |
diff --git a/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalTunnelBridge.java b/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalTunnelBridge.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c42909f0ae8ea9dd3bb3ff0cc0a0dfeb7bf2f21c |
--- /dev/null |
+++ b/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalTunnelBridge.java |
@@ -0,0 +1,190 @@ |
+// 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. |
+ |
+package org.chromium.components.devtools_bridge; |
+ |
+import android.util.Log; |
+ |
+import java.io.IOException; |
+import java.nio.ByteBuffer; |
+import java.util.concurrent.CountDownLatch; |
+ |
+/** |
+ * It allows testing DevTools socket tunneling on a single device. |
+ * |
+ * SocketTunnelClient opens LocalServerSocket named |socketToExpose| and |
+ * tunnels all incoming connections to |socketToReplicate| using |
+ * SocketTunnelServer and DataPipe between them. All data passes through |
+ * WebRTC data channel but doens't leave the device. |
+ */ |
+public class LocalTunnelBridge { |
+ private static final String TAG = "LocalTunnelBridge"; |
+ |
+ private final DataPipe mPipe; |
+ private final SocketTunnelServer mServer; |
+ private final SocketTunnelClient mClient; |
+ private boolean mLogPackets = false; |
+ |
+ private final CountDownLatch mServerDataChannelOpenedFlag = new CountDownLatch(1); |
+ private final CountDownLatch mServerDataChannelClosedFlag = new CountDownLatch(1); |
+ |
+ public LocalTunnelBridge(String socketToReplicate, String socketToExpose) throws IOException { |
+ mPipe = new DataPipe(); |
+ |
+ mServer = new SocketTunnelServer(socketToReplicate) { |
+ @Override |
+ protected void onProtocolError(ProtocolError e) { |
+ throw new RuntimeException("Protocol error on server", e); |
+ } |
+ |
+ @Override |
+ protected void sendToDataChannel(ByteBuffer packet) { |
+ if (mLogPackets) |
+ Log.d(TAG, "Sending " + stringifyServerPacket(packet)); |
+ super.sendToDataChannel(packet); |
+ } |
+ |
+ @Override |
+ protected void onReceivedDataPacket(int connectionId, byte[] data) |
+ throws ProtocolError { |
+ if (mLogPackets) { |
+ Log.d(TAG, "Received client data packet with " + |
+ Integer.toString(data.length) + " bytes"); |
+ } |
+ super.onReceivedDataPacket(connectionId, data); |
+ } |
+ |
+ @Override |
+ protected void onReceivedControlPacket(int connectionId, byte opCode) |
+ throws ProtocolError { |
+ if (mLogPackets) { |
+ Log.d(TAG, "Received client control packet"); |
+ } |
+ super.onReceivedControlPacket(connectionId, opCode); |
+ } |
+ |
+ @Override |
+ protected void onSocketException(IOException e, int connectionId) { |
+ Log.d(TAG, "Server socket exception on " + e + |
+ " (connection " + Integer.toString(connectionId) + ")"); |
+ super.onSocketException(e, connectionId); |
+ } |
+ |
+ protected void onDataChannelOpened() { |
+ Log.d(TAG, "Server data channel opened"); |
+ super.onDataChannelOpened(); |
+ mServerDataChannelOpenedFlag.countDown(); |
+ } |
+ |
+ protected void onDataChannelClosed() { |
+ Log.d(TAG, "Client data channel opened"); |
+ super.onDataChannelClosed(); |
+ mServerDataChannelClosedFlag.countDown(); |
+ } |
+ }; |
+ |
+ mServer.bind(mPipe.dataChannel(0)); |
+ |
+ mClient = new SocketTunnelClient(socketToExpose) { |
+ @Override |
+ protected void onProtocolError(ProtocolError e) { |
+ throw new RuntimeException("Protocol error on client" + e); |
+ } |
+ |
+ @Override |
+ protected void onReceivedDataPacket(int connectionId, byte[] data) |
+ throws ProtocolError { |
+ if (mLogPackets) { |
+ Log.d(TAG, "Received server data packet with " |
+ + Integer.toString(data.length) + " bytes"); |
+ } |
+ super.onReceivedDataPacket(connectionId, data); |
+ } |
+ |
+ @Override |
+ protected void onReceivedControlPacket(int connectionId, byte opCode) |
+ throws ProtocolError { |
+ if (mLogPackets) { |
+ Log.d(TAG, "Received server control packet"); |
+ } |
+ super.onReceivedControlPacket(connectionId, opCode); |
+ } |
+ |
+ @Override |
+ protected void sendToDataChannel(ByteBuffer packet) { |
+ if (mLogPackets) { |
+ Log.d(TAG, "Sending " + stringifyClientPacket(packet)); |
+ } |
+ super.sendToDataChannel(packet); |
+ } |
+ }; |
+ mClient.bind(mPipe.dataChannel(1)); |
+ } |
+ |
+ public void start() { |
+ mPipe.connect(); |
+ } |
+ |
+ public void stop() { |
+ mPipe.disconnect(); |
+ } |
+ |
+ public void dispose() { |
+ mClient.unbind(); |
+ mServer.unbind(); |
+ mPipe.dispose(); |
+ } |
+ |
+ public void waitAllConnectionsClosed() throws InterruptedException { |
+ while (mServer.hasConnections() || mClient.hasConnections()) { |
+ Thread.sleep(50); |
+ } |
+ } |
+ |
+ private String stringifyDataPacket(String type, PacketDecoder decoder) { |
+ if (!decoder.isDataPacket()) { |
+ throw new RuntimeException("Invalid packet"); |
+ } |
+ return type + "_DATA:" + Integer.toString(decoder.data().length); |
+ } |
+ |
+ private String stringifyClientPacket(ByteBuffer packet) { |
+ PacketDecoder decoder = decode(packet); |
+ if (!decoder.isControlPacket()) |
+ return stringifyDataPacket("CLIENT", decoder); |
+ switch (decoder.opCode()) { |
+ case SocketTunnelBase.CLIENT_OPEN: |
+ return "CLIENT_OPEN " + Integer.valueOf(decoder.connectionId()); |
+ case SocketTunnelBase.CLIENT_CLOSE: |
+ return "CLIENT_CLOSE " + Integer.valueOf(decoder.connectionId()); |
+ default: |
+ throw new RuntimeException("Invalid packet"); |
+ } |
+ } |
+ |
+ private String stringifyServerPacket(ByteBuffer packet) { |
+ PacketDecoder decoder = decode(packet); |
+ if (!decoder.isControlPacket()) |
+ return stringifyDataPacket("SERVER", decoder); |
+ switch (decoder.opCode()) { |
+ case SocketTunnelBase.SERVER_OPEN_ACK: |
+ return "SERVER_OPEN_ACK " + Integer.valueOf(decoder.connectionId()); |
+ case SocketTunnelBase.SERVER_CLOSE: |
+ return "SERVER_CLOSE " + Integer.valueOf(decoder.connectionId()); |
+ default: |
+ throw new RuntimeException("Invalid packet"); |
+ } |
+ } |
+ |
+ private PacketDecoder decode(ByteBuffer packet) { |
+ int position = packet.position(); |
+ packet.position(0); |
+ if (position == 0) { |
+ throw new RuntimeException("Empty packet"); |
+ } |
+ PacketDecoder decoder = PacketDecoder.decode(packet); |
+ packet.position(position); |
+ return decoder; |
+ } |
+} |