/*
 * Copyright (C) 2011 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.
 */

#include "qemu-common.h"
#include "android/globals.h"  /* for android_hw */
#include "android/hw-qemud.h"
#include "android/utils/misc.h"
#include "android/utils/system.h"
#include "android/utils/debug.h"
#include "android/adb-server.h"
#include "android/adb-qemud.h"

#define  E(...)    derror(__VA_ARGS__)
#define  W(...)    dwarning(__VA_ARGS__)
#define  D(...)    VERBOSE_PRINT(adbclient,__VA_ARGS__)
#define  DD(...)   VERBOSE_PRINT(adb,__VA_ARGS__)
#define  D_ACTIVE  VERBOSE_CHECK(adbclient)
#define  DD_ACTIVE VERBOSE_CHECK(adb)
#define  QB(b, s)  quote_bytes((const char*)b, (s < 32) ? s : 32)

#define SERVICE_NAME        "adb"
#define DEBUG_SERVICE_NAME  "adb-debug"

/* Enumerates ADB client state values. */
typedef enum AdbClientState {
    /* Waiting on a connection from ADB host. */
    ADBC_STATE_WAIT_ON_HOST,
    /* ADB host is connected. Waiting on the transport initialization completion
     * in the guest. */
    ADBC_STATE_HOST_CONNECTED,
    /* Connection between ADB host and ADB guest is fully established. */
    ADBC_STATE_CONNECTED,
    /* ADB host has been disconnected. */
    ADBC_STATE_HOST_DISCONNECTED,
    /* ADB guest has been disconnected. */
    ADBC_STATE_GUEST_DISCONNECTED,
} AdbClientState;

/* ADB client descriptor. */
typedef struct AdbClient AdbClient;
struct AdbClient {
    /* Opaque pointer returned from adb_server_register_guest API. */
    void*           opaque;
    /* QEMUD client pipe for this client. */
    QemudClient*    qemud_client;
    /* Connection state. */
    AdbClientState  state;
};

/* ADB debugging client descriptor. */
typedef struct AdbDbgClient AdbDbgClient;
struct AdbDbgClient {
    /* QEMUD client pipe for this client. */
    QemudClient*    qemud_client;
};

/********************************************************************************
 *                      ADB host communication.
 *******************************************************************************/

/* A callback that is invoked when the host is connected.
 * Param:
 *  opaque - AdbClient instance.
 *  connection - An opaque pointer that identifies connection with the ADB host.
 */
static void
_adb_on_host_connected(void* opaque, void* connection)
{
    AdbClient* const adb_client = (AdbClient*)opaque;

    if (adb_client->state == ADBC_STATE_WAIT_ON_HOST) {
        D("ADB client %p(o=%p) is connected to the host %p",
          adb_client, adb_client->opaque, connection);

        /* Bump the state up. */
         adb_client->state = ADBC_STATE_HOST_CONNECTED;

        /* Notify the ADB guest that host has been  connected.This will unblock
         * the guest from a 'read', then guest will register the transport, and
         * will send 'setart' request, indicating that it is ready to receive
         * data from the host. */
        qemud_client_send(adb_client->qemud_client, (const uint8_t*)"ok", 2);
    } else {
        D("Unexpected ADB host connection while state is %d", adb_client->state);
    }
}

/* A callback that is invoked when the host gets disconnected.
 * Param:
 *  opaque - AdbClient instance.
 *  connection - An opaque pointer that identifies connection with the ADB host.
 */
static void
_adb_on_host_disconnect(void* opaque, void* connection)
{
    AdbClient* const adb_client = (AdbClient*)opaque;

    D("ADB client %p(o=%p) is disconnected from the host %p",
      adb_client, adb_client->opaque, connection);
    adb_client->state = ADBC_STATE_HOST_DISCONNECTED;
}

/* A callback that is invoked when the host sends data.
 * Param:
 *  opaque - AdbClient instance.
 *  connection - An opaque pointer that identifies connection with the ADB host.
 *  buff, size - Buffer containing the host data.
 */
static void
_adb_on_host_data(void* opaque, void* connection, const void* buff, int size)
{
    AdbClient* const adb_client = (AdbClient*)opaque;
    D("ADB client %p(o=%p) received from the host %p %d bytes in %s",
      adb_client, adb_client->opaque, connection, size, QB(buff, size));

    if (adb_client->state == ADBC_STATE_CONNECTED) {
        /* Dispatch data down to the guest. */
        qemud_client_send(adb_client->qemud_client, (const uint8_t*)buff, size);
    } else {
        D("Unexpected data from ADB host %p while client %p(o=%p) is in state %d",
          connection, adb_client, adb_client->opaque, adb_client->state);
    }
}

/* ADB guest API required for adb_server_register_guest */
static AdbGuestRoutines _adb_client_routines = {
    /* A callback that is invoked when the host is connected. */
    _adb_on_host_connected,
    /* A callback that is invoked when the host gets disconnected. */
    _adb_on_host_disconnect,
    /* A callback that is invoked when the host sends data. */
    _adb_on_host_data,
};

/********************************************************************************
 *                      ADB guest communication.
 *******************************************************************************/

/* Allocates AdbClient instance. */
static AdbClient*
_adb_client_new(void)
{
    AdbClient* adb_client;

    ANEW0(adb_client);

    return adb_client;
}

/* Frees AdbClient instance, allocated with _adb_client_new */
static void
_adb_client_free(AdbClient* adb_client)
{
    if (adb_client != NULL) {
        free(adb_client);
    }
}

/* A callback that is invoked when ADB guest sends data to the service.
 * Param:
 *  opaque - AdbClient instance.
 *  msg, msglen - Message received from the ADB guest.
 *  client - adb QEMUD client.
 */
static void
_adb_client_recv(void* opaque, uint8_t* msg, int msglen, QemudClient* client)
{
    AdbClient* const adb_client = (AdbClient*)opaque;

    D("ADB client %p(o=%p) received from guest %d bytes in %s",
      adb_client, adb_client->opaque, msglen, QB(msg, msglen));

    /* Properly dispatch the message, depending on the client state. */
    switch (adb_client->state) {
        case ADBC_STATE_CONNECTED:
            /* Connection is fully established. Dispatch the message to the
             * host. */
            adb_server_on_guest_message(adb_client->opaque, msg, msglen);
            break;

        case ADBC_STATE_WAIT_ON_HOST:
            /* At this state the only message that is allowed is 'accept' */
            if (msglen == 6 && !memcmp(msg, "accept", 6)) {
                /* Register ADB guest connection with the ADB server. */
                adb_client->opaque =
                    adb_server_register_guest(adb_client, &_adb_client_routines);
                if (adb_client->opaque == NULL) {
                    D("Unable to register ADB guest with the ADB server.");
                    /* KO the guest. */
                    qemud_client_send(adb_client->qemud_client,
                                      (const uint8_t*)"ko", 2);
                }
            } else {
                D("Unexpected guest request while waiting on ADB host to connect.");
            }
            break;

        case ADBC_STATE_HOST_CONNECTED:
            /* At this state the only message that is allowed is 'start' */
            if (msglen == 5 && !memcmp(msg, "start", 5)) {
                adb_client->state = ADBC_STATE_CONNECTED;
                adb_server_complete_connection(adb_client->opaque);
            } else {
                D("Unexpected request while waiting on connection to start.");
            }
            break;

        default:
            D("Unexpected ADB guest request '%s' while client state is %d.",
              QB(msg, msglen), adb_client->state);
            break;
    }
}

/* A callback that is invoked when ADB guest disconnects from the service. */
static void
_adb_client_close(void* opaque)
{
    AdbClient* const adb_client = (AdbClient*)opaque;

    D("ADB client %p(o=%p) is disconnected from the guest.",
      adb_client, adb_client->opaque);
    adb_client->state = ADBC_STATE_GUEST_DISCONNECTED;
    if (adb_client->opaque != NULL) {
        /* Close connection with the host. */
        adb_server_on_guest_closed(adb_client->opaque);
    }
    _adb_client_free(adb_client);
}

/* A callback that is invoked when ADB daemon running inside the guest connects
 * to the service.
 * Client parameters are ignored here. Typically they contain the ADB port number
 * which is always 5555 for the device / emulated system.
 */
static QemudClient*
_adb_service_connect(void*          opaque,
                     QemudService*  serv,
                     int            channel,
                     const char*    client_param)
{
    /* Create new QEMUD client for the connection with ADB daemon. */
    AdbClient* const adb_client = _adb_client_new();

    D("Connecting ADB guest: '%s'", client_param ? client_param : "<null>");
    adb_client->qemud_client =
        qemud_client_new(serv, channel, client_param, adb_client,
                         _adb_client_recv, _adb_client_close, NULL, NULL);
    if (adb_client->qemud_client == NULL) {
        D("Unable to create QEMUD client for ADB guest.");
        _adb_client_free(adb_client);
        return NULL;
    }

    return adb_client->qemud_client;
}

/********************************************************************************
 *                      Debugging ADB guest communication.
 *******************************************************************************/

/* Allocates AdbDbgClient instance. */
static AdbDbgClient*
_adb_dbg_client_new(void)
{
    AdbDbgClient* adb_dbg_client;

    ANEW0(adb_dbg_client);

    return adb_dbg_client;
}

/* Frees AdbDbgClient instance, allocated with _adb_dbg_client_new */
static void
_adb_dbg_client_free(AdbDbgClient* adb_dbg_client)
{
    if (adb_dbg_client != NULL) {
        free(adb_dbg_client);
    }
}

/* A callback that is invoked when ADB debugging guest sends data to the service.
 * Param:
 *  opaque - AdbDbgClient instance.
 *  msg, msglen - Message received from the ADB guest.
 *  client - adb-debug QEMUD client.
 */
static void
_adb_dbg_client_recv(void* opaque, uint8_t* msg, int msglen, QemudClient* client)
{
    if (DD_ACTIVE) {
        fprintf(stderr, "ADB: %s", (const char*)msg);
    }
}

/* A callback that is invoked when ADB debugging guest disconnects from the
 * service. */
static void
_adb_dbg_client_close(void* opaque)
{
    AdbDbgClient* const adb_dbg_client = (AdbDbgClient*)opaque;

    DD("ADB debugging client %p is disconnected from the guest.", adb_dbg_client);
    _adb_dbg_client_free(adb_dbg_client);
}

/* A callback that is invoked when ADB daemon running inside the guest connects
 * to the debugging service.
 * Client parameters are ignored here.
 */
static QemudClient*
_adb_debug_service_connect(void*          opaque,
                           QemudService*  serv,
                           int            channel,
                           const char*    client_param)
{
    /* Create new QEMUD client for the connection with ADB debugger. */
    AdbDbgClient* const adb_dbg_client = _adb_dbg_client_new();

    DD("Connecting ADB debugging guest: '%s'",
       client_param ? client_param : "<null>");
    adb_dbg_client->qemud_client =
        qemud_client_new(serv, channel, client_param, adb_dbg_client,
                         _adb_dbg_client_recv, _adb_dbg_client_close, NULL, NULL);
    if (adb_dbg_client->qemud_client == NULL) {
        DD("Unable to create QEMUD client for ADB debugging guest.");
        _adb_dbg_client_free(adb_dbg_client);
        return NULL;
    }

    return adb_dbg_client->qemud_client;
}

/********************************************************************************
 *                      ADB service API.
 *******************************************************************************/

void
android_adb_service_init(void)
{
static int _inited = 0;

    if (!adb_server_is_initialized()) {
        return;
    }

    if (!_inited) {
        /* Register main ADB service. */
        QemudService*  serv = qemud_service_register(SERVICE_NAME, 0, NULL,
                                                     _adb_service_connect,
                                                     NULL, NULL);
        if (serv == NULL) {
            derror("%s: Could not register '%s' service",
                   __FUNCTION__, SERVICE_NAME);
            return;
        }
        D("%s: Registered '%s' qemud service", __FUNCTION__, SERVICE_NAME);

        /* Register debugging ADB service. */
        serv = qemud_service_register(DEBUG_SERVICE_NAME, 0, NULL,
                                      _adb_debug_service_connect, NULL, NULL);
        if (serv != NULL) {
            DD("Registered '%s' qemud service", DEBUG_SERVICE_NAME);
        } else {
            dwarning("%s: Could not register '%s' service",
                   __FUNCTION__, DEBUG_SERVICE_NAME);
        }
    }
}
