/*  $Id: ncbi_service_connector.c,v 6.30 2001/12/04 15:56:47 lavr Exp $
 * ===========================================================================
 *
 *                            PUBLIC DOMAIN NOTICE
 *               National Center for Biotechnology Information
 *
 *  This software/database is a "United States Government Work" under the
 *  terms of the United States Copyright Act.  It was written as part of
 *  the author's official duties as a United States Government employee and
 *  thus cannot be copyrighted.  This software/database is freely available
 *  to the public for use. The National Library of Medicine and the U.S.
 *  Government have not placed any restriction on its use or reproduction.
 *
 *  Although all reasonable efforts have been taken to ensure the accuracy
 *  and reliability of the software and data, the NLM and the U.S.
 *  Government do not and cannot warrant the performance or results that
 *  may be obtained by using this software or data. The NLM and the U.S.
 *  Government disclaim all warranties, express or implied, including
 *  warranties of performance, merchantability or fitness for any particular
 *  purpose.
 *
 *  Please cite the author in any work or product based on this material.
 *
 * ===========================================================================
 *
 * Author:  Anton Lavrentiev
 *
 * File Description:
 *   Implementation of CONNECTOR to a named service
 *
 * --------------------------------------------------------------------------
 * $Log: ncbi_service_connector.c,v $
 * Revision 6.30  2001/12/04 15:56:47  lavr
 * Use strdup() instead of explicit strcpy(malloc(...), ...)
 *
 * Revision 6.29  2001/10/26 21:17:59  lavr
 * 'service=name' now always precedes other params in connects to DISPD/NCBID
 *
 * Revision 6.28  2001/09/28 20:51:31  lavr
 * Comments revised; parameter (and SHttpConnector's) names adjusted
 * Retry logic moved entirely into s_Adjust()
 *
 * Revision 6.27  2001/09/24 20:29:36  lavr
 * +SSERVICE_Extra* parameter in constructor; store and use of this parameter
 *
 * Revision 6.26  2001/09/10 21:21:20  lavr
 * Support for temporary switching into firewall mode as per dispatcher request
 *
 * Revision 6.25  2001/07/26 15:12:17  lavr
 * while(1) -> for(;;)
 *
 * Revision 6.24  2001/06/05 14:11:29  lavr
 * SERV_MIME_UNDEFINED split into 2 (typed) constants:
 * SERV_MIME_TYPE_UNDEFINED and SERV_MIME_SUBTYPE_UNDEFINED
 *
 * Revision 6.23  2001/06/04 17:02:17  lavr
 * Insert MIME type/subtype in Content-Type for servers,
 * which have this feature configured
 *
 * Revision 6.22  2001/05/30 18:46:38  lavr
 * Always call dispatcher in header-parsing callback (to see err.msg. if any)
 *
 * Revision 6.21  2001/05/23 21:53:19  lavr
 * Do not close dispatcher in Open; leave it as it is
 *
 * Revision 6.20  2001/05/17 15:03:07  lavr
 * Typos corrected
 *
 * Revision 6.19  2001/05/11 15:32:05  lavr
 * Minor patch in code of 'fallback to STATELESS'
 *
 * Revision 6.18  2001/05/08 20:27:05  lavr
 * Patches in re-try code
 *
 * Revision 6.17  2001/05/03 16:37:09  lavr
 * Flow control fixed in s_Open for firewall connection
 *
 * Revision 6.16  2001/04/26 20:20:57  lavr
 * Default tags are explicitly used to differ from a Web browser
 *
 * Revision 6.15  2001/04/25 15:49:54  lavr
 * Memory leaks in Open (when unsuccessful) fixed
 *
 * Revision 6.14  2001/04/24 21:36:50  lavr
 * More structured code to re-try abrupted connection/mapping attempts
 *
 * Revision 6.13  2001/03/07 23:01:07  lavr
 * fSERV_Any used instead of 0 in SERVICE_CreateConnector
 *
 * Revision 6.12  2001/03/06 23:55:37  lavr
 * SOCK_gethostaddr -> SOCK_gethostbyname
 *
 * Revision 6.11  2001/03/02 20:10:07  lavr
 * Typo fixed
 *
 * Revision 6.10  2001/03/01 00:32:15  lavr
 * FIX: Empty update does not generate parse error
 *
 * Revision 6.9  2001/02/09 17:35:45  lavr
 * Modified: fSERV_StatelessOnly overrides info->stateless
 * Bug fixed: free(type) -> free(name)
 *
 * Revision 6.8  2001/01/25 17:04:43  lavr
 * Reversed:: DESTROY method calls free() to delete connector structure
 *
 * Revision 6.7  2001/01/23 23:09:19  lavr
 * Flags added to 'Ex' constructor
 *
 * Revision 6.6  2001/01/11 16:38:18  lavr
 * free(connector) removed from s_Destroy function
 * (now always called from outside, in METACONN_Remove)
 *
 * Revision 6.5  2001/01/08 22:39:40  lavr
 * Further development of service-mapping protocol: stateless/stateful
 * is now separated from firewall/direct mode (see also in few more files)
 *
 * Revision 6.4  2001/01/03 22:35:53  lavr
 * Next working revision (bugfixes and protocol changes)
 *
 * Revision 6.3  2000/12/29 18:05:12  lavr
 * First working revision.
 *
 * Revision 6.2  2000/10/20 17:34:39  lavr
 * Partially working service connector (service mapping works)
 * Checkin for backup purposes
 *
 * Revision 6.1  2000/10/07 22:14:07  lavr
 * Initial revision, placeholder mostly
 *
 * ==========================================================================
 */

#include "ncbi_comm.h"
#if defined(_DEBUG) && !defined(NDEBUG)
#include "ncbi_priv.h"
#endif
#include "ncbi_servicep.h"
#include <connect/ncbi_ansi_ext.h>
#include <connect/ncbi_connection.h>
#include <connect/ncbi_http_connector.h>
#include <connect/ncbi_service_connector.h>
#include <connect/ncbi_socket_connector.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct SServiceConnectorTag {
    const char*        name;            /* Verbal connector type             */
    const char*        service;         /* Requested service name            */
    TSERV_Type         types;           /* Server types, record keeping only */
    SConnNetInfo*      net_info;        /* Connection information            */
    SERV_ITER          iter;            /* Dispatcher information            */
    CONNECTOR          conn;            /* Low level communication connector */
    SMetaConnector     meta;            /*        ...and its virtual methods */
    unsigned int       host;            /* from parsed connection info...    */
    unsigned short     port;
    ticket_t           ticket;
    SSERVICE_Extra     params;
} SServiceConnector;


/*
 * INTERNALS: Implementation of virtual functions
 */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
    static const char* s_VT_GetType(CONNECTOR       connector);
    static EIO_Status  s_VT_Open   (CONNECTOR       connector,
                                    const STimeout* timeout);
    static EIO_Status  s_VT_Status (CONNECTOR       connector,
                                    EIO_Event       dir);
    static EIO_Status  s_VT_Close  (CONNECTOR       connector,
                                    const STimeout* timeout);
    static void        s_Setup     (SMetaConnector *meta,
                                    CONNECTOR connector);
    static void        s_Destroy   (CONNECTOR connector);
#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */


static const char* s_VT_GetType
(CONNECTOR connector)
{
    SServiceConnector* xxx = (SServiceConnector*) connector->handle;
    return xxx->name ? xxx->name : "SERVICE";
}


static int/*bool*/ s_OpenDispatcher(SServiceConnector* uuu)
{
    if (!(uuu->iter = SERV_OpenEx(uuu->service, uuu->types,
                                  SERV_LOCALHOST, uuu->net_info, 0, 0)))
        return 0/*false*/;
    return 1/*true*/;
}


static void s_CloseDispatcher(SServiceConnector* uuu)
{
    SERV_Close(uuu->iter);
    uuu->iter = 0;
}


/* Reset functions, which are implemented only in transport
 * connectors, but not in this connector.
 */
static void s_Reset(SMetaConnector *meta)
{
    CONN_SET_METHOD(meta, wait,       0,           0);
    CONN_SET_METHOD(meta, write,      0,           0);
    CONN_SET_METHOD(meta, flush,      0,           0);
    CONN_SET_METHOD(meta, read,       0,           0);
    CONN_SET_METHOD(meta, status,     s_VT_Status, 0);
#ifdef IMPLEMENTED__CONN_WaitAsync
    CONN_SET_METHOD(meta, wait_async, 0,           0);
#endif
}


#ifdef __cplusplus
extern "C" {
    static int s_ParseHeader(const char *,void *,int);
}
#endif /* __cplusplus */

static int/*bool*/ s_ParseHeader(const char* header, void* data,
                                 int/*bool*/ server_error)
{
    static const char kStateless[] = "TRY_STATELESS";
    SServiceConnector* uuu = (SServiceConnector*) data;

    SERV_Update(uuu->iter, header);
    if (server_error)
        return 1/*parsed okay*/;

    while (header && *header) {
        if (strncasecmp(header, HTTP_CONNECTION_INFO,
                        sizeof(HTTP_CONNECTION_INFO) - 1) == 0) {
            unsigned int i1, i2, i3, i4, ticket;
            unsigned char o1, o2, o3, o4;
            char host[32];

            header += sizeof(HTTP_CONNECTION_INFO) - 1;
            while (*header && isspace((unsigned char)(*header)))
                header++;
            if (strncasecmp(header, kStateless, sizeof(kStateless) - 1) == 0) {
                /* Special keyword for switching into stateless mode */
                uuu->host = (unsigned int)(-1);
#if defined(_DEBUG) && !defined(NDEBUG)
                if (uuu->net_info->debug_printout)
                    CORE_LOG(eLOG_Warning,
                             "[SERVICE] Fallback to stateless requested");
#endif
                break;
            }
            if (sscanf(header, "%u.%u.%u.%u %hu %x",
                       &i1, &i2, &i3, &i4, &uuu->port, &ticket) < 6)
                return 0/*failed*/;
            o1 = i1; o2 = i2; o3 = i3; o4 = i4;
            sprintf(host, "%u.%u.%u.%u", o1, o2, o3, o4);
            uuu->host = SOCK_gethostbyname(host);
            uuu->ticket = SOCK_htonl(ticket);
            break;
        }
        if ((header = strchr(header, '\n')) != 0)
            header++;
    }

    return 1/*success*/;
}


static char* s_AdjustNetParams(SConnNetInfo* net_info,
                               EReqMethod    req_method,
                               const char*   cgi_name,
                               const char*   first_arg,
                               const char*   first_val,
                               const char*   cgi_args,
                               const char*   static_header,
                               EMIME_Type    mime_t,
                               EMIME_SubType mime_s,
                               char*         dynamic_header /*will be freed!*/)
{
    char content_type[MAX_CONTENT_TYPE_LEN], *retval;

    net_info->req_method = req_method;

    if (cgi_name) {
        strncpy(net_info->path, cgi_name, sizeof(net_info->path) - 1);
        net_info->path[sizeof(net_info->path) - 1] = 0;
    }

    if (cgi_args) {
        strncpy(net_info->args, cgi_args, sizeof(net_info->args) - 1);
        net_info->args[sizeof(net_info->args) - 1] = 0;
    }

    if (first_arg) {
        size_t m = strlen(net_info->args);
        int/*bool*/ amp = m ? 1 : 0;
        size_t n = strlen(first_arg) +
            (first_val ? 1/*=*/ + strlen(first_val) : 0) +
            (amp ? 1/*&*/ : 0);
        if (n < sizeof(net_info->args)) {
            size_t p = sizeof(net_info->args) - n;
            if (++m > p)
                m = p;
            memmove(&net_info->args[n], &net_info->args[0], m);
            strcpy(net_info->args, first_arg);
            if (first_val) {
                strcat(net_info->args, "=");
                strcat(net_info->args, first_val);
            }
            if (amp)
                net_info->args[n - 1] = '&';
            net_info->args[n + m - 1] = 0;
        } else
            *net_info->args = 0;
    }

    if (mime_t == SERV_MIME_TYPE_UNDEFINED ||
        mime_s == SERV_MIME_SUBTYPE_UNDEFINED ||
        !MIME_ComposeContentTypeEx(mime_t, mime_s, eENCOD_None,
                                   content_type, sizeof(content_type))) {
        *content_type = 0;
    }
    if ((retval = (char*) malloc((static_header
                                  ? strlen(static_header) : 0) +
                                 strlen(content_type) + 1/*EOL*/ +
                                 (dynamic_header ?
                                  strlen(dynamic_header) : 0))) != 0) {
        strcpy(retval, static_header ? static_header : "");
        strcat(retval, content_type);
        strcat(retval, dynamic_header ? dynamic_header : "");
    }
    if (dynamic_header)
        free(dynamic_header);

    return retval;
}


static const SSERV_Info* s_GetNextInfo(SServiceConnector* uuu)
{
    if (uuu->params.get_next_info)
        return (*uuu->params.get_next_info)(uuu->iter, uuu->params.data);
    else
        return SERV_GetNextInfo(uuu->iter);
}


/* Although all additional HTTP tags, which comprise dispatching, have
 * default values, which in most cases are fine with us, we will use
 * these tags explicitly to distinguish calls originated from within the
 * service connector from the calls from a Web browser, for example.
 * This technique allows the dispatcher to decide whether to use more
 * expensive dispatching (inlovling loopback connections) in case of browser.
 */

#ifdef __cplusplus
extern "C" {
    static int s_AdjustNetInfo(SConnNetInfo *, void *,unsigned int);
}
#endif /* __cplusplus */

/* This callback is only for services called via direct HTTP */
static int/*bool*/ s_AdjustNetInfo(SConnNetInfo* net_info, void* data,
                                   unsigned int n)
{
    SServiceConnector* uuu = (SServiceConnector*) data;
    const char* user_header = 0;
    const SSERV_Info* info;

    assert(n != 0); /* paranoia assertion :-) */
    if (net_info->firewall && !net_info->stateless)
        return 0; /*cannot adjust firewall stateful client*/

    for (;;) {
        if (!(info = s_GetNextInfo(uuu)))
            return 0/*false - not adjusted*/;
        /* Skip any 'stateful_capable' issues here, which might
         * have left behind a failed stateful dispatching with a
         * fallback to stateless HTTP mode */
        if (!info->sful)
            break;
    }

    {{
        char* iter_header = SERV_Print(uuu->iter);
        switch (info->type) {
        case fSERV_Ncbid:
            user_header = "Connection-Mode: STATELESS\r\n"; /*default*/
            user_header = s_AdjustNetParams(net_info, eReqMethod_Post,
                                            NCBID_NAME,
                                            "service", uuu->service,
                                            SERV_NCBID_ARGS(&info->u.ncbid),
                                            user_header,
                                            info->mime_t, info->mime_s,
                                            iter_header);
            break;
        case fSERV_Http:
        case fSERV_HttpGet:
        case fSERV_HttpPost:
            user_header = "Client-Mode: STATELESS_ONLY\r\n"; /* default */
            user_header = s_AdjustNetParams(net_info,
                                            info->type == fSERV_HttpGet
                                            ? eReqMethod_Get :
                                            (info->type == fSERV_HttpPost
                                             ? eReqMethod_Post :
                                             eReqMethod_Any),
                                            SERV_HTTP_PATH(&info->u.http),
                                            0, 0,
                                            SERV_HTTP_ARGS(&info->u.http),
                                            user_header,
                                            info->mime_t, info->mime_s,
                                            iter_header);
            break;
        case fSERV_Standalone:
        case fSERV_Firewall:
            user_header = "Client-Mode: STATELESS_ONLY\r\n"; /*default*/
            user_header = s_AdjustNetParams(net_info, eReqMethod_Post,
                                            uuu->net_info->path,
                                            "service", uuu->service,
                                            uuu->net_info->args,
                                            user_header,
                                            info->mime_t, info->mime_s,
                                            iter_header);
            break;
        default:
            assert(0);
            if (iter_header)
                free(iter_header);
            break;
        }
    }}
    if (!user_header)
        return 0/*false - not adjusted*/;

    ConnNetInfo_SetUserHeader(net_info, user_header);
    assert(strcmp(net_info->http_user_header, user_header) == 0);
    free((char*) user_header);

    if (info->type == fSERV_Ncbid || (info->type & fSERV_Http) != 0) {
        SOCK_ntoa(info->host, net_info->host, sizeof(net_info->host));
        net_info->port = info->port;
    } else {
        strcpy(net_info->host, uuu->net_info->host);
        net_info->port = uuu->net_info->port;
    }

    if (net_info->http_proxy_adjusted)
        net_info->http_proxy_adjusted = 0/*false*/;

    return 1/*true - adjusted*/;
}


static CONNECTOR s_Open
(SServiceConnector* uuu,
 const STimeout*    timeout,
 const SSERV_Info*  info,
 SConnNetInfo*      net_info,
 int/*bool*/        second_try)
{
    const char* user_header = 0;

    if (info) {
        /* Not a firewall/relay connection here */
        EReqMethod req_method;
        /* We know the connection point, let's try to use it! */
        assert(!net_info->firewall && !second_try);
        SOCK_ntoa(info->host, net_info->host, sizeof(net_info->host));
        net_info->port = info->port;

        switch (info->type) {
        case fSERV_Ncbid:
            /* Connection directly to NCBID, add NCBID-specific tags */
            if (net_info->stateless) {
                /* Connection request with data */
                user_header = "Connection-Mode: STATELESS\r\n"; /*default*/
                req_method = eReqMethod_Post;
            } else {
                /* We will wait for conn-info back */
                user_header = "Connection-Mode: STATEFUL\r\n";
                req_method = eReqMethod_Get;
            }
            user_header = s_AdjustNetParams(net_info, req_method,
                                            NCBID_NAME,
                                            "service", uuu->service,
                                            SERV_NCBID_ARGS(&info->u.ncbid),
                                            user_header,
                                            info->mime_t, info->mime_s, 0);
            if (!user_header)
                return 0;
            break;
        case fSERV_Http:
        case fSERV_HttpGet:
        case fSERV_HttpPost:
            /* Connection directly to CGI */
            user_header = "Client-Mode: STATELESS_ONLY\r\n"; /*default*/
            req_method =
                info->type == fSERV_HttpGet
                ? eReqMethod_Get :
                    (info->type == fSERV_HttpPost
                     ? eReqMethod_Post : eReqMethod_Any);
            user_header = s_AdjustNetParams(net_info, req_method,
                                            SERV_HTTP_PATH(&info->u.http),
                                            0, 0,
                                            SERV_HTTP_ARGS(&info->u.http),
                                            user_header,
                                            info->mime_t, info->mime_s, 0);
            if (!user_header)
                return 0;
            break;
        case fSERV_Standalone:
            if (net_info->stateless) {
                /* This will be a pass-thru connection, socket otherwise */
                user_header = "Client-Mode: STATELESS_ONLY\r\n"; /*default*/
                user_header = s_AdjustNetParams(net_info, eReqMethod_Post,
                                                0,
                                                "service", uuu->service,
                                                0,
                                                user_header,
                                                info->mime_t, info->mime_s, 0);
                if (!user_header)
                    return 0;
            }
            break;
        default:
            assert(0);
            return 0;
        }
    } else {
        /* Firewall, connection to dispatcher, special tags */
        assert(net_info->firewall);
        user_header = net_info->stateless
            ? "Client-Mode: STATELESS_ONLY\r\n" /*default*/
            : "Client-Mode: STATEFUL_CAPABLE\r\n";
        user_header = s_AdjustNetParams(net_info,
                                        net_info->stateless
                                        ? eReqMethod_Post : eReqMethod_Get,
                                        0, second_try ? 0 : "service",
                                        uuu->service, 0,
                                        user_header,
                                        SERV_MIME_TYPE_UNDEFINED,
                                        SERV_MIME_SUBTYPE_UNDEFINED, 0);
        if (!user_header)
            return 0;
    }

    if (user_header) {
        /* We create HTTP connector here */
        char* iter_header = SERV_Print(uuu->iter);
        size_t n;
        if (iter_header /*NB: <CR><LF>-terminated*/) {
            if ((n = strlen(user_header)) > 0) {
                if ((iter_header = (char*)
                     realloc(iter_header, strlen(iter_header) + n + 1)) != 0)
                    strcat(iter_header, user_header);
                free((char*) user_header);
            }
            user_header = iter_header;
        } else if (!*user_header)
            user_header = 0; /* Special case of assignment of static "" */
        ConnNetInfo_SetUserHeader(net_info, user_header);
        assert(!user_header ||
               strcmp(net_info->http_user_header, user_header) == 0);
        if (user_header)
            free((char*) user_header);

        if (!net_info->stateless && (!info || info->type == fSERV_Ncbid)) {
            /* HTTP connector is auxiliary only */
            CONNECTOR conn;
            CONN c;

            /* Clear connection info */
            uuu->host = 0;
            uuu->port = 0;
            uuu->ticket = 0;
            net_info->max_try = 1;
            conn = HTTP_CreateConnectorEx(net_info, 0/*flags*/,
                                          s_ParseHeader, 0/*adj.info*/,
                                          uuu/*adj.data*/, 0/*cleanup.data*/);
            /* Wait for connection info back (error-transparent by DISPD.CGI)*/
            if (conn && CONN_Create(conn, &c) == eIO_Success) {
                CONN_SetTimeout(c, eIO_Open, timeout);
                CONN_SetTimeout(c, eIO_ReadWrite, timeout);
                CONN_SetTimeout(c, eIO_Close, timeout);
                /* This dummy read triggers parse header callback */
                CONN_Read(c, 0, 0, &n, eIO_Plain);
                CONN_Close(c);
            }
            if (!uuu->host)
                return 0/*No connection info returned*/;
            if (uuu->host == (unsigned int)(-1)) {
                assert(!info && !second_try); /*firewall mode only*/
                /* Try to use stateless mode instead */
                net_info->stateless = 1;
                return s_Open(uuu, timeout, 0, net_info, 1/*second try*/);
            }
            SOCK_ntoa(uuu->host, net_info->host, sizeof(net_info->host));
            net_info->port = uuu->port;
            /* Build and return target SOCKET connector */
            return SOCK_CreateConnectorEx(net_info->host, net_info->port,
                                          1/*max.try*/,
                                          &uuu->ticket, sizeof(uuu->ticket),
                                          net_info->debug_printout ==
                                          eDebugPrintout_Data
                                          ? eSCC_DebugPrintout
                                          : 0);
        }
        net_info->max_try = 1000000000/*very large number so we can retry*/;
        return HTTP_CreateConnectorEx(net_info, fHCC_AutoReconnect,
                                      s_ParseHeader, s_AdjustNetInfo,
                                      uuu/*adj.data*/, 0/*cleanup.data*/);
    }
    /* We create SOCKET connector here */
    return SOCK_CreateConnectorEx(net_info->host, net_info->port,
                                  1/*max.try*/, 0/*init.data*/, 0/*data.size*/,
                                  net_info->debug_printout ==
                                  eDebugPrintout_Data ? eSCC_DebugPrintout :0);
}


static EIO_Status s_Close
(CONNECTOR       connector,
 const STimeout* timeout,
 int/*bool*/     close_dispatcher)
{
    SServiceConnector* uuu = (SServiceConnector*) connector->handle;
    EIO_Status status = eIO_Success;

    if (uuu->meta.close)
        status = (*uuu->meta.close)(uuu->meta.c_close, timeout);

    if (uuu->name) {
        free((void*) uuu->name);
        uuu->name = 0;
    }

    if (close_dispatcher) {
        s_CloseDispatcher(uuu);
        if (uuu->params.cleanup)
            (*uuu->params.cleanup)(uuu->params.data);
    }

    if (uuu->conn) {
        SMetaConnector* meta = connector->meta;
        METACONN_Remove(meta, uuu->conn);
        uuu->conn = 0;
        s_Reset(meta);
    }

    return status;
}


static EIO_Status s_VT_Open
(CONNECTOR       connector,
 const STimeout* timeout)
{
    SServiceConnector* uuu = (SServiceConnector*) connector->handle;
    SMetaConnector* meta = connector->meta;
    const SSERV_Info* info;
    SConnNetInfo* net_info;
    CONNECTOR conn;

    assert(!uuu->conn && !uuu->name);
    for (;;) {
        if (!uuu->iter && !s_OpenDispatcher(uuu))
            break;

        if (uuu->net_info->firewall)
            info = 0;
        else if (!(info = s_GetNextInfo(uuu)))
            break;

        if (!(net_info = ConnNetInfo_Clone(uuu->net_info)))
            break;
        if (info && info->type == fSERV_Firewall)
            info = 0;

        conn = s_Open(uuu, timeout, info, net_info, 0/*second_try - false*/);

        ConnNetInfo_Destroy(net_info);

        if (!conn) {
            if (uuu->net_info->firewall)
                break;
            else
                continue;
        }

        METACONN_Add(&uuu->meta, conn);
        conn->meta = meta;
        conn->next = meta->list;
        meta->list = conn;

        CONN_SET_METHOD(meta, wait,  uuu->meta.wait,  uuu->meta.c_wait);
        CONN_SET_METHOD(meta, write, uuu->meta.write, uuu->meta.c_write);
        CONN_SET_METHOD(meta, flush, uuu->meta.flush, uuu->meta.c_flush);
        CONN_SET_METHOD(meta, read,  uuu->meta.read,  uuu->meta.c_read);
        CONN_SET_METHOD(meta, status,uuu->meta.status,uuu->meta.c_status);
#ifdef IMPLEMENTED__CONN_WaitAsync
        CONN_SET_METHOD(meta, wait_async,
                        uuu->meta.wait_async, uuu->meta.c_wait_async);
#endif
        if (uuu->meta.get_type) {
            const char* type;
            if ((type = (*uuu->meta.get_type)(uuu->meta.c_get_type)) != 0) {
                static const char prefix[] = "SERVICE/";
                char* name = (char*) malloc(strlen(type) + sizeof(prefix));
                if (name) {
                    strcpy(name, prefix);
                    strcat(name, type);
                    uuu->name = name;
                }
            }
        }

        if (uuu->meta.open) {
            EIO_Status status = (*uuu->meta.open)(uuu->meta.c_open, timeout);
            if (status != eIO_Success) {
                s_Close(connector, timeout, 0/*close_dispatcher - don't!*/);
                continue;
            }
        }

        return eIO_Success;
    }

    return eIO_Unknown;
}


static EIO_Status s_VT_Status
(CONNECTOR connector,
 EIO_Event dir)
{
    return eIO_Success;
}


static EIO_Status s_VT_Close
(CONNECTOR       connector,
 const STimeout* timeout)
{
    return s_Close(connector, timeout, 1/*close_dispatcher*/);
}


static void s_Setup(SMetaConnector *meta, CONNECTOR connector)
{
    /* initialize virtual table */
    CONN_SET_METHOD(meta, get_type,   s_VT_GetType,   connector);
    CONN_SET_METHOD(meta, open,       s_VT_Open,      connector);
    CONN_SET_METHOD(meta, close,      s_VT_Close,     connector);
    /* all the rest is reset to NULL */
    s_Reset(meta);
}


static void s_Destroy(CONNECTOR connector)
{
    SServiceConnector* uuu = (SServiceConnector*) connector->handle;

    ConnNetInfo_Destroy(uuu->net_info);
    if (uuu->name)
        free((void*) uuu->name);
    free((void*) uuu->service);
    free(uuu);
    free(connector);
}


/***********************************************************************
 *  EXTERNAL -- the connector's "constructor"
 ***********************************************************************/

extern CONNECTOR SERVICE_CreateConnectorEx
(const char*           service,
 TSERV_Type            types,
 const SConnNetInfo*   net_info,
 const SSERVICE_Extra* params)
{
    CONNECTOR          ccc;
    SServiceConnector* xxx;

    if (!service || !*service)
        return 0;

    ccc = (SConnector*)        malloc(sizeof(SConnector));
    xxx = (SServiceConnector*) malloc(sizeof(SServiceConnector));
    xxx->name     = 0;
    xxx->service  = strdup(service);
    xxx->net_info = net_info
        ? ConnNetInfo_Clone(net_info) : ConnNetInfo_Create(service);
    if (types & fSERV_StatelessOnly)
        xxx->net_info->stateless = 1/*true*/;
    if (types & fSERV_Firewall)
        xxx->net_info->firewall = 1/*true*/;
    xxx->types    = types;
    xxx->iter     = 0;
    xxx->conn     = 0;
    if (params)
        memcpy(&xxx->params, params, sizeof(xxx->params));
    else
        memset(&xxx->params, 0, sizeof(xxx->params));
    memset(&xxx->meta, 0, sizeof(xxx->meta));

    assert(xxx->net_info->http_user_header == 0);

    /* initialize connector structure */
    ccc->handle  = xxx;
    ccc->next    = 0;
    ccc->meta    = 0;
    ccc->setup   = s_Setup;
    ccc->destroy = s_Destroy;

    /* Now make the first probe dispatching */
    if (!s_OpenDispatcher(xxx)) {
        s_CloseDispatcher(xxx);
        s_Destroy(ccc);
        return 0;
    }
    assert(xxx->iter != 0);

    /* Done */
    return ccc;
}
