/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "config.h"

#include "guacamole/socket.h"

#include <stdlib.h>

/**
 * Data specific to the tee implementation of guac_socket.
 */
typedef struct guac_socket_tee_data {

    /**
     * The guac_socket to which all socket operations should be delegated.
     */
    guac_socket* primary;

    /**
     * The guac_socket to which all write and flush operations should be
     * duplicated.
     */
    guac_socket* secondary;

} guac_socket_tee_data;

/**
 * Callback function which reads only from the primary socket.
 *
 * @param socket
 *     The tee socket to read from.
 *
 * @param buf
 *     The buffer to read data into.
 *
 * @param count
 *     The maximum number of bytes to read into the given buffer.
 *
 * @return
 *     The value returned by guac_socket_read() when invoked on the primary
 *     socket with the given parameters.
 */
static ssize_t __guac_socket_tee_read_handler(guac_socket* socket,
        void* buf, size_t count) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Delegate read to wrapped socket */
    return guac_socket_read(data->primary, buf, count);

}

/**
 * Callback function which writes the given data to both underlying sockets,
 * returning only the result from the primary socket.
 *
 * @param socket
 *     The tee socket to write through.
 *
 * @param buf
 *     The buffer of data to write.
 *
 * @param count
 *     The number of bytes in the buffer to be written.
 *
 * @return
 *     The number of bytes written if the write was successful, or -1 if an
 *     error occurs.
 */
static ssize_t __guac_socket_tee_write_handler(guac_socket* socket,
        const void* buf, size_t count) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Write to secondary socket (ignoring result) */
    guac_socket_write(data->secondary, buf, count);

    /* Delegate write to wrapped socket */
    if (guac_socket_write(data->primary, buf, count))
        return -1;

    /* All data written successfully */
    return count;

}

/**
 * Callback function which flushes both underlying sockets, returning only the
 * result from the primary socket.
 *
 * @param socket
 *     The tee socket to flush.
 *
 * @return
 *     The value returned by guac_socket_flush() when invoked on the primary
 *     socket.
 */
static ssize_t __guac_socket_tee_flush_handler(guac_socket* socket) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Flush secondary socket (ignoring result) */
    guac_socket_flush(data->secondary);

    /* Delegate flush to wrapped socket */
    return guac_socket_flush(data->primary);

}

/**
 * Callback function which delegates the lock operation to the primary
 * socket alone.
 *
 * @param socket
 *     The tee socket on which guac_socket_instruction_begin() was invoked.
 */
static void __guac_socket_tee_lock_handler(guac_socket* socket) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Delegate lock to wrapped sockets */
    guac_socket_instruction_begin(data->primary);
    guac_socket_instruction_begin(data->secondary);

}

/**
 * Callback function which delegates the unlock operation to the primary
 * socket alone.
 *
 * @param socket
 *     The tee socket on which guac_socket_instruction_end() was invoked.
 */
static void __guac_socket_tee_unlock_handler(guac_socket* socket) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Delegate unlock to wrapped sockets */
    guac_socket_instruction_end(data->secondary);
    guac_socket_instruction_end(data->primary);

}

/**
 * Callback function which delegates the select operation to the primary
 * socket alone.
 *
 * @param socket
 *     The tee socket on which guac_socket_select() was invoked.
 *
 * @param usec_timeout
 *     The timeout to specify when invoking guac_socket_select() on the
 *     primary socket.
 *
 * @return
 *     The value returned by guac_socket_select() when invoked with the
 *     given parameters on the primary socket.
 */
static int __guac_socket_tee_select_handler(guac_socket* socket,
        int usec_timeout) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Delegate select to wrapped socket */
    return guac_socket_select(data->primary, usec_timeout);

}

/**
 * Callback function which frees all underlying data associated with the
 * given tee socket, including both primary and secondary sockets.
 *
 * @param socket
 *     The tee socket being freed.
 *
 * @return
 *     Always zero.
 */
static int __guac_socket_tee_free_handler(guac_socket* socket) {

    guac_socket_tee_data* data = (guac_socket_tee_data*) socket->data;

    /* Free underlying sockets */
    guac_socket_free(data->primary);
    guac_socket_free(data->secondary);

    /* Freeing the tee socket always succeeds */
    free(data);
    return 0;

}

guac_socket* guac_socket_tee(guac_socket* primary, guac_socket* secondary) {

    /* Set up socket to split outout into a file */
    guac_socket_tee_data* data = malloc(sizeof(guac_socket_tee_data));
    data->primary = primary;
    data->secondary = secondary;

    /* Associate tee-specific data with new socket */
    guac_socket* socket = guac_socket_alloc();
    socket->data = data;

    /* Assign handlers */
    socket->read_handler   = __guac_socket_tee_read_handler;
    socket->write_handler  = __guac_socket_tee_write_handler;
    socket->select_handler = __guac_socket_tee_select_handler;
    socket->flush_handler  = __guac_socket_tee_flush_handler;
    socket->lock_handler   = __guac_socket_tee_lock_handler;
    socket->unlock_handler = __guac_socket_tee_unlock_handler;
    socket->free_handler   = __guac_socket_tee_free_handler;

    return socket;

}

