Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 56d7e400 authored by Maksymilian Osowski's avatar Maksymilian Osowski
Browse files

Added forwarding service.

It creates the sockets on the device that allow DumpRenderTree2 to get tests from the server running on the remote machine. Communication happens through the adb.

Change-Id: I7f26ffc78195b6c5f4d423a57d5ee5f0f4a9c615
parent 385a655b
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dumprendertree2.forwarder;

import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * The utility class that can setup a socket allowing the device to communicate with remote
 * machines through the machine that the device is connected to via adb.
 */
public class AdbUtils {
    private static final String LOG_TAG = "AdbUtils";

    private static final String ADB_OK = "OKAY";
    private static final int ADB_PORT = 5037;
    private static final String ADB_HOST = "127.0.0.1";
    private static final int ADB_RESPONSE_SIZE = 4;

    /**
     * Send an ADB command using existing socket connection
     *
     * The streams provided must be from a socket connected to adb already
     *
     * @param is input stream of the socket connection
     * @param os output stream of the socket
     * @param cmd the adb command to send
     * @return if adb gave a success response
     * @throws IOException
     */
    private static boolean sendAdbCmd(InputStream is, OutputStream os, String cmd)
            throws IOException {
        byte[] buf = new byte[ADB_RESPONSE_SIZE];

        cmd = String.format("%04X", cmd.length()) + cmd;
        os.write(cmd.getBytes());
        int read = is.read(buf);
        if (read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) {
            Log.w(LOG_TAG, "adb cmd faild.");
            return false;
        }
        return true;
    }

    /**
     * Get a tcp socket connection to specified IP address and port proxied by adb
     *
     * The proxying is transparent, e.g. if a socket is returned, then it can be written to and
     * read from as if it is directly connected to the target
     *
     * @param remoteAddress IP address of the host to connect to
     * @param remotePort port of the host to connect to
     * @return a valid Socket instance if successful, null otherwise
     */
    public static Socket getSocketToRemoteMachine(String remoteAddress, int remotePort) {
        try {
            Socket socket = new Socket(ADB_HOST, ADB_PORT);
            String cmd = "tcp:" + remotePort + ":" + remoteAddress;
            if (!sendAdbCmd(socket.getInputStream(), socket.getOutputStream(), cmd)) {
                socket.close();
                return null;
            }
            return socket;
        } catch (IOException ioe) {
            Log.w(LOG_TAG, "error creating adb socket", ioe);
            return null;
        }
    }
}
 No newline at end of file
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dumprendertree2.forwarder;

import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * Worker class for {@link Forwarder}. A ConnectionHandler will be created once the Forwarder
 * accepts an incoming connection, and it will then forward the incoming/outgoing streams to a
 * connection already proxied by adb networking (see also {@link AdbUtils}).
 */
public class ConnectionHandler {

    private static final String LOG_TAG = "ConnectionHandler";

    private class SocketPipeThread extends Thread {

        private Socket mInSocket, mOutSocket;

        public SocketPipeThread(Socket inSocket, Socket outSocket) {
            mInSocket = inSocket;
            mOutSocket = outSocket;
        }

        @Override
        public void run() {
            InputStream is;
            OutputStream os;
            try {
                synchronized (this) {
                    is = mInSocket.getInputStream();
                    os = mOutSocket.getOutputStream();
                }
            } catch (IOException e) {
                Log.w(LOG_TAG, this.toString(), e);
                return;
            }

            byte[] buffer = new byte[4096];
            int length;
            while (true) {
                try {
                    synchronized (this) {
                        if ((length = is.read(buffer)) <= 0) {
                            break;
                        }
                        os.write(buffer, 0, length);
                    }
                } catch (IOException e) {
                    /** This exception means one of the streams is closed */
                    Log.v(LOG_TAG, this.toString(), e);
                    break;
                }
            }
        }

        @Override
        public String toString() {
            return "SocketPipeThread:\n" + mInSocket + "\n=>\n" + mOutSocket;
        }
    }

    private Socket mFromSocket, mToSocket;
    private SocketPipeThread mFromToPipe, mToFromPipe;

    public ConnectionHandler(Socket fromSocket, Socket toSocket) {
        mFromSocket = fromSocket;
        mToSocket = toSocket;
        mFromToPipe = new SocketPipeThread(mFromSocket, mToSocket);
        mToFromPipe = new SocketPipeThread(mToSocket, mFromSocket);
    }

    public void start() {
        mFromToPipe.start();
        mToFromPipe.start();
    }

    public void stop() {
        shutdown(mFromSocket);
        shutdown(mToSocket);
    }

    private void shutdown(Socket socket) {
        try {
            synchronized (mFromToPipe) {
                synchronized (mToFromPipe) {
                    /** This will stop the while loop in the run method */
                    socket.shutdownInput();
                    socket.shutdownOutput();
                    socket.close();
                }
            }
        } catch (IOException e) {
            Log.e(LOG_TAG, "mFromToPipe=" + mFromToPipe + " mToFromPipe=" + mToFromPipe, e);
        }
    }
}
 No newline at end of file
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dumprendertree2.forwarder;

import android.util.Log;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import java.util.Set;

/**
 * A port forwarding server. Listens on localhost on specified port and forwards the tcp
 * communications to external socket via adb networking proxy.
 */
public class Forwarder extends Thread {
    private static final String LOG_TAG = "Forwarder";

    private int mPort;
    private String mRemoteMachineIpAddress;

    private Boolean mIsRunning = false;
    private ServerSocket mServerSocket;

    private Set<ConnectionHandler> mConnectionHandlers = new HashSet<ConnectionHandler>();

    public Forwarder(int port, String remoteMachineIpAddress) {
        mPort = port;
        mRemoteMachineIpAddress = remoteMachineIpAddress;
    }

    @Override
    public void start() {
        Log.i(LOG_TAG, "start(): Starting fowarder on port: " + mPort);
        synchronized (this) {
            if (mIsRunning) {
                Log.w(LOG_TAG, "start(): Forwarder on port: " + mPort + " already running! NOOP.");
                return;
            }
        }

        try {
            mServerSocket = new ServerSocket(mPort);
        } catch (IOException e) {
            Log.e(LOG_TAG, "mPort=" + mPort, e);
            return;
        }

        mIsRunning = true;
        super.start();
    }

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (!mIsRunning) {
                    return;
                }

                /** These sockets will be closed when Forwarder.stop() is called */
                Socket localSocket;
                Socket remoteSocket;
                try {
                    localSocket = mServerSocket.accept();
                    remoteSocket = AdbUtils.getSocketToRemoteMachine(mRemoteMachineIpAddress,
                            mPort);
                } catch (IOException e) {
                    /** This most likely means that mServerSocket is already closed */
                    Log.w(LOG_TAG + "mPort=" + mPort, e);
                    return;
                }

                if (remoteSocket == null) {
                    try {
                        localSocket.close();
                    } catch (IOException e) {
                        Log.e(LOG_TAG, "mPort=" + mPort, e);
                    }

                    Log.e(LOG_TAG, "run(): mPort= " + mPort + " Failed to start forwarding from " +
                            localSocket);
                    continue;
                }

                ConnectionHandler forwarder = new ConnectionHandler(localSocket, remoteSocket);
                mConnectionHandlers.add(forwarder);
                forwarder.start();

            }
        }
    }

    public void finish() {
        synchronized (this) {
            if (!mIsRunning) {
                return;
            }
        }

        try {
            mServerSocket.close();
        } catch (IOException e) {
            Log.e(LOG_TAG, "mPort=" + mPort, e);
        }

        synchronized (this) {
            mIsRunning = false;
        }

        for (ConnectionHandler connectionHandler : mConnectionHandlers) {
            connectionHandler.stop();
        }
        mConnectionHandlers.clear();
    }
}
 No newline at end of file
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dumprendertree2.forwarder;

import java.util.HashSet;
import java.util.Set;

/**
 * A simple class to start and stop Forwarders running on some ports.
 *
 * It uses a singleton pattern and is thread safe.
 */
public class ForwarderManager {
    /**
     * The IP address of the server serving the tests.
     */
    private static final String HOST_IP = "127.0.0.1";

    /**
     * We use these ports because other webkit platforms do. They are set up in
     * external/webkit/LayoutTests/http/conf/apache2-debian-httpd.conf
     */
    public static final int HTTP_PORT = 8080;
    public static final int HTTPS_PORT = 8443;

    private static ForwarderManager forwarderManager;

    private Set<Forwarder> mServers;

    private ForwarderManager() {
        mServers = new HashSet<Forwarder>(2);
        mServers.add(new Forwarder(HTTP_PORT, HOST_IP));
        mServers.add(new Forwarder(HTTPS_PORT, HOST_IP));
    }

    public static synchronized ForwarderManager getForwarderManager() {
        if (forwarderManager == null) {
            forwarderManager = new ForwarderManager();
        }
        return forwarderManager;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    public synchronized void start() {
        for (Forwarder server : mServers) {
            server.start();
        }
    }

    public synchronized void stop() {
        for (Forwarder server : mServers) {
            server.finish();
        }
    }
}
 No newline at end of file