/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


//
// connection - a (potentially) persistent access path to a (possibly :-) remote entity
//
#include "netconnection.h"
#include "protocol.h"
#include "netmanager.h"
#include "transfer.h"


namespace Security {
namespace Network {


//
// Create a Connection object for a particular Protocol and HostTarget.
// Note that these two arguments are potentially unrelated; in general,
// you can't assume that &proto == &host.protocol().
//
Connection::Connection(Protocol &proto, const HostTarget &host)
    : protocol(proto), hostTarget(host), mTransfer(NULL), mRetainMe(false), mRestarting(false)
{
    debug("netconn", 
        "connection %p created for %s", this, hostTarget.urlForm().c_str());
}


//
// Destroy a Connection, assuming it's idle.
//
Connection::~Connection()
{
    assert(!isDocked());
    debug("netconn", "connection %p destroyed", this);
}


//
// Dock the Connection to a Transfer.
//
void Connection::dock(Transfer *xfer)
{
    assert(!isDocked());
    assert(!xfer->isDocked());
    mTransfer = xfer;
    xfer->mConnection = this;
    debug("netconn", "connection %p docked xfer %p", this, xfer);
}


//
// Undock the Connection from its currently docked Transfer.
// The mRetainMe flag determines what happens next: we either
// submit ourselves to our Manager for retention, or for cleanup.
//
void Connection::undock()
{
    // paranoia first
    assert(isDocked());
    assert(mTransfer->mConnection == this);
    
    // will we be kept?
    bool retain = mRetainMe && mTransfer->shareConnections();
    
    // physically sever our relationship with the Transfer
    debug("netconn", "connection %p undocking xfer %p", this, mTransfer);
    mTransfer->mConnection = NULL;
    mTransfer = NULL;

    // submit ourselves to the manager for retention
    if (retain)
        protocol.manager.retainConnection(this);
    else
        protocol.manager.closeConnection(this);
}


//
// Forwarders for finish/fail
//
void Connection::finish()
{
    assert(isDocked());
    mTransfer->finish();
}

void Connection::fail()
{
    if (isDocked()) {
        // fail the transfer we're docked to, which will undock us and dispose of us
        mTransfer->fail();
    } else {
        // we failed while in limbo. Self-dispose
        retain(false);
        protocol.manager.closeConnection(this);
    }
}


//
// Drop the current Connection and re-execute start()
//
void Connection::restart()
{
    if (mRestarting) {
        Transfer *transfer = mTransfer;
        debug("netconn", "%p restarting xfer %p", this, transfer);
        
        // throw outselves out
        retain(false);
        undock();
        
        // restart the transfer
        transfer->start();
    } else {
        // restart request on Connection that's not marked restarting.
        // Presumably a real error, and we assume error indications have already
        // been set (in the Transfer) by the caller as desired.
        fail();
    }
}


//
// The default implementation of validate() does nothing and succeeds.
//
bool Connection::validate()
{
    return true;
}


//
// The file descriptor of a TCPConnection is itself (as a TCPClientSocket)
//
int TCPConnection::fileDesc() const
{
    return *this;
}


//
// The TCPConnection destructor will remove any remaining I/O hook
//
TCPConnection::~TCPConnection()
{
    close();
}

void TCPConnection::close()
{
    if (isOpen()) {
        protocol.manager.removeIO(this);
        TCPClientSocket::close();
    }
}


//
// Asynchronous connect processing for TCPClient subclasses.
// The full call sets up data and initiates the first connect attempt; the second
// form needs to be called on failure notification to (re)try other addresses.
//
void TCPConnection::connect(const Host &host, IPPort port)
{
    mAddressCandidates = host.addresses();
    mPort = port;
    nextCandidate();
    protocol.manager.addIO(this);
    mode(connecting);
}

void TCPConnection::connect()
{
    if (mAddressCandidates.empty()) {
        // out of candidates. This connection attempt is failing
        UnixError::throwMe(EHOSTUNREACH);
    }

    close();
    nextCandidate();
    protocol.manager.addIO(this);
    mode(connecting);
}

void TCPConnection::nextCandidate()
{
    // pull the next address from the candidate set
    std::set<IPAddress>::iterator it = mAddressCandidates.begin();
    IPAddress addr = *it;
    mAddressCandidates.erase(it);
    
    open(addr, mPort, O_NONBLOCK);
}


}	// end namespace Network
}	// end namespace Security
