/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <memory.h>
#include <errno.h>
#include <unistd.h>
#if defined(_POSIX_C_SOURCE)
#include <pwd.h>
#endif /* POSIX */
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# include <sys/stat.h>
# include <sys/un.h>
# include <sys/socket.h>
#include "mas_api_internal.h"
#include "mas/rtp_api.h"
#include "mas_core.h"
#include "../devices/net/net_common.h"

/**
 ** WARNING
 **
 ** This stuff is still in an unstable state.  It's likely to change.
 **   -Mike
 ** 
 **/

/* A peer node is allocated for every network connection from the
   client.  Typically, this is between the client and the local
   server.  There will be a peer for every channel open between client
   and local server. */
struct mas_peer_node
{
    int32 id;
    int32  session_type;
    struct mas_package infopack;
    char *version;
    struct rtp_session session;

    struct mas_peer_node* next;
    struct mas_peer_node* prev;
};

/* used by the rtp_api wrapper for jrtplib */
static struct mas_peer_node* _mas_peer_head = 0;
static uint32 _ssrc = 0;

/* support variables for local/display networking.  */
static mas_channel_t _local_control_channel;
static mas_channel_t _display_control_channel;
static mas_port_t    _dcsrc1, _dcsnk1;
static mas_port_t    _dcsrc2, _dcsnk2;
static mas_device_t  _local_net_device;
static char          _display_hostname[1024];
static char _mas_rtp_unix[MAX_FNAME_LENGTH];
static char _mas_rtcp_unix[MAX_FNAME_LENGTH];

/** LOCAL PROTOTYPES *******************************************************/
static int32 _unix_connect( char* rtp_path, char* rtcp_path,
			    struct mas_peer_node* peer );
static struct mas_peer_node* _new_peer_node( void );
static int32 _delete_peer_node( struct mas_peer_node* node );
static int32 _append_peer_node(struct mas_peer_node* head,
			       struct mas_peer_node* node);
static struct mas_peer_node* _find_peer( int32 id );
static int32  _get_display_host_offset( char* hostname, int* offset);
static int32  _get_ssh_client( char* host);
static int32 _make_local_control_channel( void );
static int32 _make_local_data_channel( char* name, mas_channel_t*
                                       data_channel, mas_port_t* source,
                                       mas_port_t* sink );
static int32 _send_to_peerid( int32 id, struct mas_data* data );
static int32 _send_package_to_peerid( int32 id, struct mas_package* package );
static int32 _recv_from_peerid( int32 id, struct mas_data* data );
static int32 _recv_package_from_peerid( int32 id, struct mas_package* package );
static int32 _test_recv_data_waiting_from_peerid( int32 id );

int32
mas_get_local_control_channel( mas_channel_t* channel )
{
    int32 err;

    err = _alloc_channel(channel);
    if (err < 0) return err;
    err = _copy_channel( *channel, _local_control_channel );
    if (err < 0) return err;

    return 0;
}

int32
mas_get_display_control_channel( mas_channel_t* channel )
{
    int32 err;

    err = _alloc_channel(channel);
    if (err < 0) return err;
    err = _copy_channel( *channel, _display_control_channel );
    if (err < 0) return err;

    return 0;
}

int32
mas_send( mas_channel_t channel, struct mas_data* data )
{
    return _send_to_peerid( channel->id, data );
}

int32
mas_send_to_local( struct mas_data* data )
{
    return _send_to_peerid( _local_control_channel->id, data );
}

int32
mas_send_to_display( struct mas_data* data )
{
    return _send_to_peerid( _display_control_channel->id, data );
}

int32
mas_send_package( mas_channel_t channel, struct mas_package* package )
{
    return _send_package_to_peerid( channel->id, package );
}

int32
mas_send_package_to_local( struct mas_package* package )
{
    return _send_package_to_peerid( _local_control_channel->id, package );
}

int32
mas_send_package_to_display( struct mas_package* package )
{
    return _send_package_to_peerid( _display_control_channel->id, package );
}

int32
mas_send_event( struct mas_event *event, mas_channel_t channel )
{
    char buffer[4096];
    struct mas_package  package;
    int32 err;

    masc_setup_package( &package, buffer, sizeof buffer, MASC_PACKAGE_STATIC );
    masc_pack_event( &package, event );
    err = masc_finalize_package( &package );
    if ( err < 0 ) return err;
    
    /* send it to the server */
    if ( channel == 0 )
    {
        err = mas_send_package_to_display( &package );
    }
    else
    {
        err = mas_send_package( channel, &package );
    }
    
    masc_strike_package( &package );
    
    return err;
}

int32
mas_recv( mas_channel_t channel, struct mas_data* data )
{
    return _recv_from_peerid( channel->id, data );
}

int32
mas_recv_from_local( struct mas_data* data )
{
    return _recv_from_peerid( _local_control_channel->id, data );
}

int32
mas_recv_from_display( struct mas_data* data )
{
    return _recv_from_peerid( _display_control_channel->id, data );
}

int32
mas_recv_package( mas_channel_t channel, struct mas_package* package )
{
    if ( channel == NULL )
    {
        return _recv_package_from_peerid( _display_control_channel->id, package );
    }
    else
    {
        return _recv_package_from_peerid( channel->id, package );
    }
}

int32
mas_recv_package_from_local( struct mas_package* package )
{
    return _recv_package_from_peerid( _local_control_channel->id, package );
}

int32
mas_recv_package_from_display( struct mas_package* package )
{
    return _recv_package_from_peerid( _display_control_channel->id, package );
}

int32
mas_test_recv_data_waiting( mas_channel_t channel )
{
    return _test_recv_data_waiting_from_peerid( channel->id );
}

int32
mas_test_recv_data_waiting_from_display( void )
{
    return _test_recv_data_waiting_from_peerid( _display_control_channel->id );
}

int32
mas_test_recv_data_waiting_from_local( void )
{
    return _test_recv_data_waiting_from_peerid( _local_control_channel->id );
}

int32
mas_init( void )
{
    int32     err;
    char      ssh_client[256];
    int      offset;
    int      mp;
    
    /* establish link with local server */
    err = _make_local_control_channel();
    if ( err < 0 ) return err;
    _alloc_channel( &_local_control_channel );
    _local_control_channel->id = _mas_peer_head->id;
    _local_control_channel->hostname = masc_rtalloc( 16 );
    strcpy( _local_control_channel->hostname, "localhost" );
    
    /* are we running on the local DISPLAY? */
    mp = _get_display_host_offset(_display_hostname, &offset);

    /* mp == 2 for MAS_PATH, 1 for DISPLAY, or 0 for none (localhost) */
    /* Only use SSH_CLIENT if MAS_PATH isn't set. */
    if ( mp < 2 )
    {
        /* this is some temporary trickiness to get around ssh proxying,
           until we get the X11 tunnel set up */
        _get_ssh_client( ssh_client );
        if ( ssh_client[0] != 0 )
        {
            if (offset > 5) /* hack */
            {
                masc_log_message(MAS_VERBLVL_DEBUG, "SSH X11 port forwarding detected.  Trying host from SSH_CLIENT.");
                strncpy( _display_hostname, ssh_client, 255 );
            }
        }
    }
    
    /* create the control channel */
    err = mas_make_control_channel( _display_hostname, &_display_control_channel );
    return err;
}

int32
mas_make_control_channel( char* hostname, mas_channel_t* control_channel )
{
    int32     err;
    struct mas_data_characteristic* dc;
    int localhost = FALSE;
    
    mas_assert( hostname != 0, "hostname is null" );

    /* avoid doing weird, wrong things.  Try to figure out if the
       caller is really trying to get at the local host. */
    if ( *hostname == 0 ) localhost = TRUE;
    else if ( strcmp(hostname, "127.0.0.1") == 0 ) localhost = TRUE;
    else if ( strcmp(hostname, "localhost") == 0 ) localhost = TRUE;
        
    if ( localhost )
    {
        masc_log_message(MAS_VERBLVL_DEBUG, "Connecting to LOCAL MAS server.");
        err = _alloc_channel( control_channel );
        if ( err < 0 ) return err;
        err = _copy_channel( *control_channel, _local_control_channel );
        if ( err < 0 ) return err;
        return 0;
    }

    /* The session is remote.  Try to connect to the remote MAS server. */
    masc_log_message(MAS_VERBLVL_DEBUG, "Connecting to MAS at %s.", hostname);

    /* 1. Open a local data channel */
    err = _make_local_data_channel( "proxy", control_channel, &_dcsrc1, &_dcsnk1 );
    if (err < 0) return err;

    /* 2. Retrieve the local net device */
    err = mas_asm_get_device_by_name_on_channel( "net", &_local_net_device, _local_control_channel );
    if (err < 0) return err;

    /* 3. Tell the net device to connect to the remote-side net device. */
    err = mas_net_connect_control( _local_net_device, hostname, &_dcsrc2, &_dcsnk2);
    if (err < 0)
    {
        if (mas_get_derror(err) == 1)
        {
            /* The local server IS the display server */
            /** TODO: free up the old data channel */
            masc_log_message(MAS_VERBLVL_DEBUG, "%s is local MAS server, using local control connection.", hostname);
            err = _copy_channel( *control_channel, _local_control_channel );
            if ( err < 0 ) return err;
            return 0;
        }
        else return err;
    }
    
    /* 4. Connect source/sink pairs to form a complete proxy. */
    dc = MAS_NEW( dc );
    masc_setup_dc( dc, 1 );
    masc_append_dc_key_value( dc, "protocol", "mcp" );
    err = mas_asm_connect_source_sink( _dcsrc1, _dcsnk2, dc );
    if (err < 0) return err;
    err = mas_asm_connect_source_sink( _dcsrc2, _dcsnk1, dc );
    if (err < 0) return err;

    /* 5. Set the hostname of the display control channel */
    if ( (*control_channel)->hostname != 0 )
        masc_rtfree( (*control_channel)->hostname );
    (*control_channel)->hostname = masc_rtalloc( strlen( hostname ) + 1 );
    strcpy( (*control_channel)->hostname, hostname );

    return 0;
}

int32
mas_make_data_channel( char* name, mas_channel_t* data_channel,
                       mas_port_t* remote_source, mas_port_t* remote_sink )
{
    int32 err;
    mas_port_t local_source1;
    mas_port_t local_sink1;
    mas_port_t local_source2;
    mas_port_t local_sink2;
    struct mas_data_characteristic* dc;

    /* create the client->localserver data channel */
    err = _make_local_data_channel( name, data_channel, &local_source1,
                                    &local_sink1 );

    /* do we need to proxy it? */
    if ( _local_control_channel->id == _display_control_channel->id )
    {
        /* nope, it's local */
        _alloc_port( remote_source );
        _alloc_port( remote_sink );
        (*remote_source)->portnum = local_source1->portnum;
        (*remote_sink)->portnum = local_sink1->portnum;
        _alloc_channel( &((*remote_source)->control_channel) );
        _alloc_channel( &((*remote_sink)->control_channel) );
        _copy_channel( (*remote_source)->control_channel, local_source1->control_channel );
        _copy_channel( (*remote_sink)->control_channel, local_sink1->control_channel );
        _free_port( &local_source1 );
        _free_port( &local_sink1 );
        
        return 0;
    }
    

    /* else, we need to open a server-server data channel and set up
     * proxying */
    err = mas_net_connect_data( _local_net_device,
                                _display_control_channel,
                                name, &local_source2, &local_sink2,
                                remote_source, remote_sink );
    /* NOTE: remote_source, remote_sink are the ports on the remote
     * server that the client is actually interested in.  We hide the
     * proxying details from the client. */
    
    /* Connect local source/sink pairs to form a complete proxy. */
    dc = MAS_NEW( dc );
    masc_setup_dc( dc, 1 );
    masc_append_dc_key_value( dc, "protocol", "mcp" );
    err = mas_asm_connect_source_sink( local_source1, local_sink2, dc );
    if (err < 0) return err;
    err = mas_asm_connect_source_sink( local_source2, local_sink1, dc );
    if (err < 0) return err;

    /* Set the hostname of the data channel */
    if ( (*data_channel)->hostname != 0 )
        masc_rtfree( (*data_channel)->hostname );
    (*data_channel)->hostname = masc_rtalloc( strlen( _display_hostname ) + 1 );
    strcpy( (*data_channel)->hostname, _display_hostname );
    
    return 0;
}

/*** LOCAL FUNCTIONS *******************************************************/

#if defined(_POSIX_C_SOURCE)
int32
_unix_user_info( struct mas_package *infopack )
{
    struct passwd *pwd;
    
    /* This is just here to give the server some idea of who you are.
       It is NOT a substitute for an authentication scheme.  It is
       temporary. */
    pwd = getpwuid( getuid() );
    if ( pwd == NULL )
    {
        masc_log_message(MAS_VERBLVL_ERROR, "[MAS ERROR] Unable to identify you." );
        return mas_error(MERR_INVALID);
    }

    masc_pushk_string( infopack, "user", pwd->pw_name );
    masc_pushk_string( infopack, "name", pwd->pw_gecos );
    
    /****************************************************************/
    
    return 0;
}
#endif /* _POSIX_C_SOURCE */

int32
_authenticate_basic( struct mas_peer_node *peer, char *arg )
{
    char *authmsg;
    int  authmsg_len;
    struct mas_package infopack;
    struct rtp_packet packet;
    char buffer[1024];
    char *rarg;
    int32 err;

    masc_setup_package( &infopack, buffer, sizeof buffer, MASC_PACKAGE_STATIC );

#if defined(_POSIX_C_SOURCE)
    err = _unix_user_info( &infopack );
    if ( err < 0 )
        return err;
    masc_finalize_package( &infopack );
#endif /*_POSIX_C_SOURCE*/

    if ( infopack.size == 0 )
    {
        net_create_authmsg( MASRELEASE, arg, NULL, &authmsg, &authmsg_len );
    }
    else
    {
        net_create_authmsg( MASRELEASE, arg, &infopack, &authmsg, &authmsg_len );
    }
        
    /* send the auth packet */
    err = rtp_p2p_send_control(&peer->session, authmsg, authmsg_len);
    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);
    masc_rtfree( authmsg );
    masc_strike_package( &infopack );
    
    /* receive the server response */
    err = rtp_p2p_recv( &peer->session, &packet, TRUE, FALSE );
    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    /* parse it */
    err = net_parse_authmsg( packet.payload, packet.payload_len, &rarg, &peer->infopack, &peer->version );
    if ( err < 0 )
        return err;

    if ( rarg == NULL || rarg[0] != 'O' || rarg[1] != 'K' )
    {
        masc_rtfree( rarg );
        return mas_error( MERR_INVALID );
    }

    masc_rtfree( rarg );
    return 0;
}


int32
_make_local_control_channel( void )
{
    int32     err;
    
    /* multiple control channels feature not implemented */
    if ( _mas_peer_head != 0 ) return mas_error( MERR_NOTIMP );

    if ( ( _mas_peer_head = _new_peer_node() ) == 0 ) 
	return mas_error(MERR_MEMORY);

    /* create rtp/rtcp UNIX filenames - these are static */
    masc_strlcpy( _mas_rtp_unix, MAS_UNIXSOCKDIR, sizeof _mas_rtp_unix );
    masc_strlcat( _mas_rtp_unix, "/", sizeof _mas_rtp_unix );
    masc_strlcpy( _mas_rtcp_unix, _mas_rtp_unix, sizeof _mas_rtcp_unix );
    masc_strlcat( _mas_rtp_unix, RTP_UNIX_LISTEN_FILE, sizeof _mas_rtp_unix );
    masc_strlcat( _mas_rtcp_unix, RTCP_UNIX_LISTEN_FILE, sizeof _mas_rtcp_unix );

    err = _unix_connect( _mas_rtp_unix, _mas_rtcp_unix, _mas_peer_head );
    if ( err < 0 )
    {

	err = MAS_ERR_CRITICAL | mas_error(MERR_COMM);
	masc_logerror(err, "Creating UNIX connection.");
	return err;
    }

    /* Control channels use 32-bit NTP timestamp */
    rtp_set_tsu(&_mas_peer_head->session, 1.52590218967E-5);

    /* set control channel type */
    rtp_set_pt(&_mas_peer_head->session, RTP_PT_MAS );

    /* get OUR SSRC - this will be used for all future connections
     * from this client. */
    rtp_get_local_ssrc( &_mas_peer_head->session, &_ssrc );

    /* REAL AUTHENTICATION GOES HERE ****************************/
    err = _authenticate_basic( _mas_peer_head, "CONTROL" );
    /************************************************************/
    
    /* debugging */
#ifdef DEBUG
    if ( _mas_peer_head->version )
        masc_log_message( MAS_VERBLVL_DEBUG, "[MAS] connected to server version %s", _mas_peer_head->version);
    if ( _mas_peer_head->infopack.contents )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "[MAS] server information follows:");
        masc_debug_package( &_mas_peer_head->infopack, 0 );
    }
#endif
    
    return err; /* zero IS the control channel number */
}

/**
 * create a data channel between the client and the local MAS server
 **/

int32
_make_local_data_channel( char* name, mas_channel_t* data_channel,
                             mas_port_t* source, mas_port_t* sink )
{
    int32                 err;
    struct mas_peer_node* peer;

    if ( ( peer = _new_peer_node() ) == 0 ) 
	return mas_error(MERR_MEMORY);
    
    /* start with UNIX */
    err = _unix_connect( _mas_rtp_unix, _mas_rtcp_unix, peer );
    if ( err < 0 )
    {

	err = MAS_ERR_CRITICAL | mas_error(MERR_COMM);
	masc_logerror(err, "Creating UNIX data connection.");
	return err;
    }
 
    /* Set our ssrc -- all our connections need to have the same
     * one.  Be sure this gets set BEFORE authentication! */
    rtp_set_local_ssrc( &peer->session, _ssrc );
    
    err = _authenticate_basic( peer, "DATA" );
    if ( err < 0 ) return err;
    
    /* add the session to our list */
    _append_peer_node ( _mas_peer_head, peer );

    /* create the new data channel */
    err = _alloc_channel( data_channel );
    (*data_channel)->id = peer->id;
    (*data_channel)->hostname = masc_rtalloc( 16 );
    strcpy( (*data_channel)->hostname, "localhost" );

    /* grab the ports from the info package */
    _alloc_port( sink );
    _alloc_port( source );
    masc_pullk_int32( &peer->infopack, "src", &(*source)->portnum );
    masc_pullk_int32( &peer->infopack, "snk", &(*sink)->portnum );
    
    /* copy the control channel. */
    mas_get_local_control_channel( &((*source)->control_channel) );
    mas_get_local_control_channel( &((*sink)->control_channel) );
    
    return 0;
}

int32
_unix_connect( char* rtp_path, char* rtcp_path, struct mas_peer_node* peer )
{
    int32 err;
    int reterrcode;
    RTPSOCKET rtpsock;
    RTPSOCKET rtcpsock;
    struct sockaddr_un rtp_peer_addr;
    struct sockaddr_un rtcp_peer_addr;
    
    rtp_peer_addr.sun_family = AF_UNIX;
    strncpy( rtp_peer_addr.sun_path, rtp_path, RTP_UNIX_PATH_MAX-1 );
    rtp_peer_addr.sun_path[RTP_UNIX_PATH_MAX-1] = 0;
    rtcp_peer_addr.sun_family = AF_UNIX;
    strncpy( rtcp_peer_addr.sun_path, rtcp_path, RTP_UNIX_PATH_MAX-1 );
    rtcp_peer_addr.sun_path[RTP_UNIX_PATH_MAX-1] = 0;
    
    err = rtp_transport_stream_connect(RTP_SESSTYPE_UNIX, &rtpsock,
				       &rtcpsock, 
				       (struct sockaddr*)&rtp_peer_addr,
				       (struct
					sockaddr*)&rtcp_peer_addr, &reterrcode); 
    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    err = rtp_create_stream_pair_p2p_session(&peer->session,
					     0, 0, RTP_SESSTYPE_UNIX,
					     rtpsock, rtcpsock);
    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    return 0;
}

/* Pull the hostname of the display-side out of the DISPLAY
   environment variable.  If it's found to be the local machine,
   "localhost" is returned */

int32
_get_display_host_offset( char* hostname, int* offset)
{
    char* mas_peer;
    char* chr;
    char* end;
    int   retval = 2; /* mas peer */

    *offset = 0; /* default */

    /* caller must have allocated hostname */
    if ( hostname == 0 ) return mas_error(MERR_NULLPTR);

    /* Grab the MAS_HOST environment var */
    if ( (mas_peer = getenv("MAS_HOST") ) == 0 )
    {
        retval = 1; /* display */
        /* If it's not defined, try DISPLAY */
        if ( (mas_peer = getenv("DISPLAY") ) == 0 )
        {
            retval = 0; /* localhost */
            /* No DISPLAY or MAS_HOST?  Set to localhost. */
            strcpy( hostname, "localhost" );
            return retval;
        }
    }
    
    memcpy(hostname, mas_peer, strlen(mas_peer)+1);
    chr = strchr(hostname, ':');

    /* extract the port offset */
    if (chr != 0)
    {
        *chr = 0;
        chr++;
        end = strchr( chr, '.' ); /* try to find period */
        if ( end == 0 )
        {
            end = strchr( chr, ' ' ); /* try for whitespace */
            if ( end == 0 )
            {
                end = chr;
                while( *end != 0 ) end++; /* just find the end */
            }
        }

        /* terminate the string at the end, and grab the offset */
        if ( end != 0 )
        {
            *end = 0;
            *offset = atoi( chr );
        }
    }
        
    masc_trim_string(hostname);
    if ( *hostname == 0 ) strcpy(hostname, "localhost");
    else if ( strcmp(hostname, "127.0.0.1") == 0 )
        strcpy(hostname, "localhost");

    return retval;
}

/* Pull the host (usually dotted-quad IP address) of the other side of
   an ssh session out of the SSH_CLIENT or SSH2_CLIENT environment
   variables.  If neither is found, a null string is returned. */

int32
_get_ssh_client( char* host )
{
    char* ssh_ptr = 0;
    char* chr;
    
    /* caller must have allocated hostname */
    if ( host == 0 ) return mas_error(MERR_NULLPTR);
    host[0] = 0;

    /**** Try SSH_CLIENT, then SSH2_CLIENT environment variables ********/
    if ( (ssh_ptr = getenv("SSH_CLIENT") ) == 0 )
        ssh_ptr = getenv("SSH2_CLIENT");

    if ( ssh_ptr == 0 ) return 0;

    /* locate the end of the hostname */
    chr = strchr(ssh_ptr, ' ');
    if ( chr != 0 ) *chr = 0;
    strncpy( host, ssh_ptr, 255 );

    return 0;
}

struct mas_peer_node*
_find_peer( int32 id )
{
    struct mas_peer_node* node = _mas_peer_head;

    while ( node != 0 ) 
    {
	if ( node->id == id ) break;
	else node = node->next;
    }
    
    return node;
}

/*** mas_peer_node linked list handling routines *************/

struct mas_peer_node*
_new_peer_node( void )
{
    static int32 lastid = 0;
    struct mas_peer_node* node;

    node = MAS_NEW( node );
    if ( node == NULL )
        return NULL;

    node->session_type = RTP_SESSTYPE_NONE;

    /* start ID on a random number for control channels */
    if ( lastid == 0 ) lastid=1+(int) (65535*rand()/(RAND_MAX+1.0));
    lastid++;
    node->id = lastid;

    return node;
}

int32
_delete_peer_node( struct mas_peer_node* node )
{
    if (node)
    {
	/* connect nodes on either side in list */
	if (node->prev != 0)
	    node->prev->next = node->next;
	if (node->next != 0)
	    node->next->prev = node->prev;

	masc_rtfree(node);
    }
    else return mas_error(MERR_MEMORY);

    return 0;
}

int32
_append_peer_node(struct mas_peer_node* head,
		  struct mas_peer_node* node)
{
    struct mas_peer_node* tail = head;
    
    while( tail->next != NULL ) tail = tail->next;

    tail->next = node;
    node->prev = tail;

    return 0;
}

/** PEER ID based send/recv functions ******************************/

int32
_send_to_peerid( int32 id, struct mas_data* data )
{
    int32 err = 0;
    struct mas_peer_node* node;
    
    node = _find_peer( id );
    if ( node )
	err = rtp_p2p_send(&node->session, data->segment, data->length, data->header.type, data->header.mark, data->header.media_timestamp, data->header.sequence);
    else return mas_error( MERR_INVALID );

    /* CHECK FOR RTCP DATA & PROCESS */
    rtp_process_rtcp_if_any( &node->session );

    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    return 0;
}

int32
_send_package_to_peerid( int32 id, struct mas_package* package )
{
    int32 err;
    struct mas_peer_node* node;
    
    node = _find_peer( id );
    
    if ( node )
	err = rtp_p2p_send_control(&node->session, package->contents, package->size );
    else return mas_error( MERR_INVALID );

    /* CHECK FOR RTCP DATA & PROCESS */
    rtp_process_rtcp_if_any( &node->session );

    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    return 0;
}

int32
_recv_from_peerid( int32 id, struct mas_data* data )
{
    int32 err;
    struct mas_peer_node* node;
    struct rtp_packet     packet;
    
    node = _find_peer( id );
    if ( node == 0 ) return mas_error( MERR_INVALID );

    err = 0;
    while ( err == 0 )
	err = rtp_p2p_recv(&node->session, &packet, TRUE, FALSE);
    
    if ( err < 0 ) return mas_error(MERR_COMM);

    data->segment = packet.payload;
    data->length = packet.payload_len;
    data->allocated_length = packet.payload_len;

    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);
    return 0;
}

int32
_recv_package_from_peerid( int32 id, struct mas_package* package )
{
    int32 err;
    struct mas_peer_node* node;
    struct rtp_packet     packet;
    
    node = _find_peer( id );
    if ( node == 0 ) return mas_error( MERR_INVALID );

    err = 0;
    while ( err == 0 )
	err = rtp_p2p_recv(&node->session, &packet, TRUE, FALSE);
    
    if ( err < 0 ) return mas_error(MERR_COMM);

    err = masc_setup_package( package, packet.payload, packet.payload_len, MASC_PACKAGE_EXTRACT );
    if ( err < 0 )
        return mas_error(MERR_IO) | mas_make_derror(-err);

    return 0;
}

/* If we can call select(), returns 1 if data is waiting to be read on
   the peer id, 0 if no data can be read, or a negative error condition */
int32
_test_recv_data_waiting_from_peerid( int32 id )
{
#if defined(_POSIX_C_SOURCE)
    struct mas_peer_node* node;
    struct timeval timeout;
    fd_set local_fd_set;
    
    node = _find_peer( id );
    if ( node == 0 ) return mas_error( MERR_INVALID );

    timeout.tv_sec = 0;  /* just poll */
    timeout.tv_usec = 0;

    FD_ZERO( &local_fd_set );
    FD_SET(node->session.rx_rtp_socket, &local_fd_set);
    select( node->session.rx_rtp_socket + 1, &local_fd_set, 0, 0, &timeout );
    if ( FD_ISSET( node->session.rx_rtp_socket, &local_fd_set ) )
        return 1;

    return 0;

#else
    return mas_error(MERR_NOTIMP);
#endif
}

uint32
_get_ssrc( void )
{
    return _ssrc;
}
