/*
 * SPDX-FileCopyrightText: 2001 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <tango/internal/net.h>
#include <tango/internal/utils.h>

#include <tango/client/Database.h>
#include <tango/client/DbDevice.h>
#include <tango/client/DbDevImportInfo.h>
#include <tango/client/NotifdEventConsumer.h>
#include <tango/client/ZmqEventConsumer.h>
#include <tango/server/DeviceImpl.h>
#include <tango/server/seq_vec.h>
#include <tango/server/Util.h>
#include <tango/server/WriterLock.h>

#ifdef _TG_WINDOWS_
  #include <process.h>
  #include <ws2tcpip.h>
#else
  #include <arpa/inet.h>
  #include <netdb.h>
  #include <netinet/in.h>
  #include <pwd.h>
  #include <sys/socket.h>
#endif /* _TG_WINDOWS_ */

#include <tango/internal/telemetry/telemetry_kernel_macros.h>

#include <tango/common/PointerWithLock.h>

#include <algorithm>
#include <chrono>
#include <csignal>
#include <ctime>
#include <memory>

using namespace CORBA;

namespace
{
constexpr auto RECONNECTION_DELAY = std::chrono::seconds(1);
} // namespace

namespace Tango
{

//-----------------------------------------------------------------------------
//
// ConnectionExt class methods:
//
// - assignement operator
//
//-----------------------------------------------------------------------------

Connection::ConnectionExt &Connection::ConnectionExt::operator=(TANGO_UNUSED(const Connection::ConnectionExt &rval))
{
    return *this;
}

//-----------------------------------------------------------------------------
//
// Connection::Connection() - constructor to manage a connection to a device
//
//-----------------------------------------------------------------------------

Connection::Connection(CORBA::ORB_var orb_in) :
    ext(new ConnectionExt())
{
    //
    // If the proxy is created from inside a device server, use the server orb
    //

    ApiUtil *au = ApiUtil::instance();
    if((CORBA::is_nil(orb_in)) && (au->is_orb_nil()))
    {
        if(au->in_server())
        {
            ApiUtil::instance()->set_orb(Util::instance()->get_orb());
        }
        else
        {
            ApiUtil::instance()->create_orb();
        }
    }
    else
    {
        if(!CORBA::is_nil(orb_in))
        {
            au->set_orb(orb_in);
        }
    }

    //
    // Get user connect timeout if one is defined
    //

    int ucto = au->get_user_connect_timeout();
    if(ucto != -1)
    {
        user_connect_timeout = ucto;
    }
}

Connection::Connection(bool dummy) :
    ext(nullptr)
{
    if(dummy)
    {
        ext = std::make_unique<ConnectionExt>();
    }
}

//-----------------------------------------------------------------------------
//
// Connection::~Connection() - destructor to destroy connection to TANGO device
//
//-----------------------------------------------------------------------------

Connection::~Connection() { }

//-----------------------------------------------------------------------------
//
// Connection::Connection() - copy constructor
//
//-----------------------------------------------------------------------------

Connection::Connection(const Connection &sou) :
    ext(nullptr)
{
    dbase_used = sou.dbase_used;
    from_env_var = sou.from_env_var;
    host = sou.host;
    port = sou.port;
    port_num = sou.port_num;

    db_host = sou.db_host;
    db_port = sou.db_port;
    db_port_num = sou.db_port_num;

    ior = sou.ior;
    pasyn_ctr = sou.pasyn_ctr;
    pasyn_cb_ctr = sou.pasyn_cb_ctr;

    device = sou.device;
    if(sou.version >= 2)
    {
        device_2 = sou.device_2;
    }

    timeout = sou.timeout;
    connection_state = sou.connection_state;
    version = sou.version;
    server_version = sou.server_version;
    source = sou.source;

    check_acc = sou.check_acc;
    access = sou.access;

    tr_reco = sou.tr_reco;
    device_3 = sou.device_3;

    prev_failed_t0 = sou.prev_failed_t0;

    device_4 = sou.device_4;

    user_connect_timeout = sou.user_connect_timeout;
    tango_host_localhost = sou.tango_host_localhost;

    device_5 = sou.device_5;

    device_6 = sou.device_6;

    if(sou.ext != nullptr)
    {
        ext = std::make_unique<ConnectionExt>();
        *(ext) = *(sou.ext);
    }
}

//-----------------------------------------------------------------------------
//
// Connection::operator=() - assignement operator
//
//-----------------------------------------------------------------------------

Connection &Connection::operator=(const Connection &rval)
{
    if(this == &rval)
    {
        return *this;
    }

    dbase_used = rval.dbase_used;
    from_env_var = rval.from_env_var;
    host = rval.host;
    port = rval.port;
    port_num = rval.port_num;

    db_host = rval.db_host;
    db_port = rval.db_port;
    db_port_num = rval.db_port_num;

    ior = rval.ior;
    pasyn_ctr = rval.pasyn_ctr;
    pasyn_cb_ctr = rval.pasyn_cb_ctr;

    device = rval.device;
    if(rval.version >= 2)
    {
        device_2 = rval.device_2;
    }

    timeout = rval.timeout;
    connection_state = rval.connection_state;
    version = rval.version;
    server_version = rval.server_version;
    source = rval.source;

    check_acc = rval.check_acc;
    access = rval.access;

    tr_reco = rval.tr_reco;
    device_3 = rval.device_3;

    prev_failed_t0 = rval.prev_failed_t0;

    device_4 = rval.device_4;

    user_connect_timeout = rval.user_connect_timeout;
    tango_host_localhost = rval.tango_host_localhost;

    device_5 = rval.device_5;

    device_6 = rval.device_6;

    if(rval.ext != nullptr)
    {
        ext = std::make_unique<ConnectionExt>();
        *(ext) = *(rval.ext);
    }
    else
    {
        ext.reset(nullptr);
    }

    return *this;
}

//-----------------------------------------------------------------------------
//
// Connection::check_and_reconnect() methods family
//
// Check if a re-connection is needed and if true, try to reconnect.
// These meethods also manage a TangoMonitor object for thread safety.
// Some of them set parameters while the object is locked in order to pass
// them to the caller in thread safe way.
//
//-----------------------------------------------------------------------------

void Connection::check_and_reconnect()
{
    int local_connection_state;
    {
        ReaderLock guard(con_to_mon);
        local_connection_state = connection_state;
    }
    if(local_connection_state != CONNECTION_OK)
    {
        WriterLock guard(con_to_mon);
        if(connection_state != CONNECTION_OK)
        {
            reconnect(dbase_used);
        }
    }
}

void Connection::check_and_reconnect(Tango::DevSource &sou)
{
    int local_connection_state;
    {
        ReaderLock guard(con_to_mon);
        local_connection_state = connection_state;
        sou = source;
    }
    if(local_connection_state != CONNECTION_OK)
    {
        WriterLock guard(con_to_mon);
        if(connection_state != CONNECTION_OK)
        {
            reconnect(dbase_used);
        }
    }
}

void Connection::check_and_reconnect(Tango::AccessControlType &act)
{
    int local_connection_state;
    {
        ReaderLock guard(con_to_mon);
        local_connection_state = connection_state;
        act = access;
    }
    if(local_connection_state != CONNECTION_OK)
    {
        WriterLock guard(con_to_mon);
        if(connection_state != CONNECTION_OK)
        {
            reconnect(dbase_used);
            act = access;
        }
    }
}

void Connection::check_and_reconnect(Tango::DevSource &sou, Tango::AccessControlType &act)
{
    int local_connection_state;
    {
        ReaderLock guard(con_to_mon);
        local_connection_state = connection_state;
        act = access;
        sou = source;
    }
    if(local_connection_state != CONNECTION_OK)
    {
        WriterLock guard(con_to_mon);
        if(connection_state != CONNECTION_OK)
        {
            reconnect(dbase_used);
            act = access;
        }
    }
}

void Connection::set_connection_state(int con)
{
    WriterLock guard(con_to_mon);
    connection_state = con;
}

Tango::DevSource Connection::get_source()
{
    ReaderLock guard(con_to_mon);
    return source;
}

void Connection::set_source(Tango::DevSource sou)
{
    WriterLock guard(con_to_mon);
    source = sou;
}

//-----------------------------------------------------------------------------
//
// Connection::connect() - method to create connection to a TANGO device
//        using its stringified CORBA reference i.e. IOR or corbaloc
//
//-----------------------------------------------------------------------------

void Connection::connect(const std::string &corba_name)
{
    bool retry = true;
    long db_retries = DB_START_PHASE_RETRIES;
    bool connect_to_db = false;

    while(retry)
    {
        try
        {
            Object_var obj;
            obj = ApiUtil::instance()->get_orb()->string_to_object(corba_name.c_str());

            //
            // Narrow CORBA string name to CORBA object
            // First, try as a Device_5, then as a Device_4, then as .... and finally as a Device
            //
            // But we have want to know if the connection to the device is OK or not.
            // The _narrow() call does not necessary generates a remote call. It all depends on the object IDL type
            // stored in the IOR. If in the IOR, the IDL is the same than the one on which the narrow is done (Device_5
            // on both side for instance), then the _narrow call will not generate any remote call and therefore, we
            // don't know if the connection is OK or NOT. This is the reason of the _non_existent() call. In case the
            // IDl in the IOR and in the narrow() call are different, then the _narrow() call try to execute a remote
            // _is_a() call and therefore tries to connect to the device. IN this case, the _non_existent() call is
            // useless. But because we don want to analyse the IOR ourself, we always call _non_existent() Reset the
            // connection timeout only after the _non_existent call.
            //

            if(corba_name.find(DbObjName) != std::string::npos)
            {
                connect_to_db = true;
            }

            if(!connect_to_db)
            {
                if(user_connect_timeout != -1)
                {
                    omniORB::setClientConnectTimeout(user_connect_timeout);
                }
                else
                {
                    omniORB::setClientConnectTimeout(NARROW_CLNT_TIMEOUT);
                }
            }

            device_6 = Device_6::_narrow(obj);

            if(CORBA::is_nil(device_6))
            {
                device_5 = Device_5::_narrow(obj);

                if(CORBA::is_nil(device_5))
                {
                    device_4 = Device_4::_narrow(obj);

                    if(CORBA::is_nil(device_4))
                    {
                        device_3 = Device_3::_narrow(obj);

                        if(CORBA::is_nil(device_3))
                        {
                            device_2 = Device_2::_narrow(obj);
                            if(CORBA::is_nil(device_2))
                            {
                                device = Device::_narrow(obj);
                                if(CORBA::is_nil(device))
                                {
                                    std::cerr << "Can't build connection to object " << corba_name << std::endl;
                                    connection_state = CONNECTION_NOTOK;

                                    TangoSys_OMemStream desc;
                                    desc << "Failed to connect to device " << dev_name();
                                    desc << " (device nil after _narrowing)" << std::ends;
                                    TANGO_THROW_DETAILED_EXCEPTION(ApiConnExcept, API_CantConnectToDevice, desc.str());
                                }
                                else
                                {
                                    device->_non_existent();
                                    version = 1;
                                }
                            }
                            else
                            {
                                device_2->_non_existent();
                                version = 2;
                                device = Device_2::_duplicate(device_2);
                            }
                        }
                        else
                        {
                            device_3->_non_existent();
                            version = 3;
                            device_2 = Device_3::_duplicate(device_3);
                            device = Device_3::_duplicate(device_3);
                        }
                    }
                    else
                    {
                        device_4->_non_existent();
                        version = 4;
                        device_3 = Device_4::_duplicate(device_4);
                        device_2 = Device_4::_duplicate(device_4);
                        device = Device_4::_duplicate(device_4);
                    }
                }
                else
                {
                    device_5->_non_existent();
                    version = 5;
                    device_4 = Device_5::_duplicate(device_5);
                    device_3 = Device_5::_duplicate(device_5);
                    device_2 = Device_5::_duplicate(device_5);
                    device = Device_5::_duplicate(device_5);
                }
            }
            else
            {
                device_6->_non_existent();
                version = 6;
                device_5 = Device_6::_duplicate(device_6);
                device_4 = Device_6::_duplicate(device_6);
                device_3 = Device_6::_duplicate(device_6);
                device_2 = Device_6::_duplicate(device_6);
                device = Device_6::_duplicate(device_6);
            }

            //
            // Warning! Some non standard code (omniORB specific).
            // Set a flag if the object is running on a host with several net addresses. This is used during
            // re-connection algo.
            //

            if(corba_name[0] == 'I' && corba_name[1] == 'O' && corba_name[2] == 'R')
            {
                IOP::IOR ior;
                toIOR(corba_name.c_str(), ior);
                IIOP::ProfileBody pBody;
                IIOP::unmarshalProfile(ior.profiles[0], pBody);

                CORBA::ULong total = pBody.components.length();

                for(CORBA::ULong index = 0; index < total; index++)
                {
                    IOP::TaggedComponent &c = pBody.components[index];
                    if(c.tag == 3)
                    {
                        ext->has_alt_adr = true;
                        break;
                    }
                    else
                    {
                        ext->has_alt_adr = false;
                    }
                }
            }

            //            if (connect_to_db == false)
            //                omniORB::setClientConnectTimeout(0);
            retry = false;

            //
            // Mark the connection as OK and set timeout to its value
            // (The default is 3 seconds)
            //

            connection_state = CONNECTION_OK;
            if(timeout != CLNT_TIMEOUT)
            {
                set_timeout_millis(timeout);
            }
        }
        catch(CORBA::SystemException &ce)
        {
            //            if (connect_to_db == false)
            //                omniORB::setClientConnectTimeout(0);

            TangoSys_OMemStream desc;
            TangoSys_MemStream reason;
            bool db_connect = false;

            desc << "Failed to connect to ";

            std::string::size_type pos = corba_name.find(':');
            if(pos == std::string::npos)
            {
                desc << "device " << dev_name() << std::ends;
                reason << API_CantConnectToDevice << std::ends;
            }
            else
            {
                std::string prot = corba_name.substr(0, pos);
                if(prot == "corbaloc")
                {
                    std::string::size_type tmp = corba_name.find('/');
                    if(tmp != std::string::npos)
                    {
                        std::string dev = corba_name.substr(tmp + 1);
                        if(dev == "database")
                        {
                            desc << "database on host ";
                            desc << db_host << " with port ";
                            desc << db_port << std::ends;
                            reason << API_CantConnectToDatabase << std::ends;
                            db_retries--;
                            if(db_retries != 0)
                            {
                                db_connect = true;
                            }
                        }
                        else
                        {
                            desc << "device " << dev_name() << std::ends;
                            if(CORBA::OBJECT_NOT_EXIST::_downcast(&ce) != nullptr)
                            {
                                reason << API_DeviceNotDefined << std::ends;
                            }
                            else if(CORBA::TRANSIENT::_downcast(&ce) != nullptr)
                            {
                                reason << API_ServerNotRunning << std::ends;
                            }
                            else
                            {
                                reason << API_CantConnectToDevice << std::ends;
                            }
                        }
                    }
                    else
                    {
                        desc << "device " << dev_name() << std::ends;
                        reason << API_CantConnectToDevice << std::ends;
                    }
                }
                else
                {
                    desc << "device " << dev_name() << std::ends;
                    reason << API_CantConnectToDevice << std::ends;
                }
            }

            if(!db_connect)
            {
                TANGO_RETHROW_DETAILED_EXCEPTION(ApiConnExcept, ce, reason.str(), desc.str());
            }
        }
    }
}

//-----------------------------------------------------------------------------
//
// Connection::toIOR() - Convert string IOR to omniORB IOR object
// omniORB specific code !!!
//
//-----------------------------------------------------------------------------

void Connection::toIOR(const char *iorstr, IOP::IOR &ior)
{
    size_t s = (iorstr != nullptr ? strlen(iorstr) : 0);
    if(s < 4)
    {
        throw CORBA::MARSHAL(0, CORBA::COMPLETED_NO);
    }
    const char *p = iorstr;
    if(p[0] != 'I' || p[1] != 'O' || p[2] != 'R' || p[3] != ':')
    {
        throw CORBA::MARSHAL(0, CORBA::COMPLETED_NO);
    }

    s = (s - 4) / 2; // how many octets are there in the string
    p += 4;

    cdrMemoryStream buf((CORBA::ULong) s, false);

    for(int i = 0; i < (int) s; i++)
    {
        int j = i * 2;
        CORBA::Octet v;

        if(p[j] >= '0' && p[j] <= '9')
        {
            v = ((p[j] - '0') << 4);
        }
        else if(p[j] >= 'a' && p[j] <= 'f')
        {
            v = ((p[j] - 'a' + 10) << 4);
        }
        else if(p[j] >= 'A' && p[j] <= 'F')
        {
            v = ((p[j] - 'A' + 10) << 4);
        }
        else
        {
            throw CORBA::MARSHAL(0, CORBA::COMPLETED_NO);
        }

        if(p[j + 1] >= '0' && p[j + 1] <= '9')
        {
            v += (p[j + 1] - '0');
        }
        else if(p[j + 1] >= 'a' && p[j + 1] <= 'f')
        {
            v += (p[j + 1] - 'a' + 10);
        }
        else if(p[j + 1] >= 'A' && p[j + 1] <= 'F')
        {
            v += (p[j + 1] - 'A' + 10);
        }
        else
        {
            throw CORBA::MARSHAL(0, CORBA::COMPLETED_NO);
        }

        buf.marshalOctet(v);
    }

    buf.rewindInputPtr();
    CORBA::Boolean b = buf.unmarshalBoolean();
    buf.setByteSwapFlag(b);

    ior.type_id = IOP::IOR::unmarshaltype_id(buf);
    ior.profiles <<= buf;
}

//-----------------------------------------------------------------------------
//
// Connection::reconnect() - reconnect to a CORBA object
//
//-----------------------------------------------------------------------------

void Connection::reconnect(bool db_used)
{
    auto now = std::chrono::steady_clock::now();

    if(connection_state != CONNECTION_OK)
    {
        // Do not reconnect if to soon
        if(prev_failed_t0.has_value() && (now - *prev_failed_t0) < RECONNECTION_DELAY)
        {
            TangoSys_OMemStream desc;
            desc << "Failed to connect to device " << dev_name() << std::endl;
            desc << "The connection request was delayed." << std::endl;
            desc << "The last connection request was done less than "
                 << std::chrono::milliseconds(RECONNECTION_DELAY).count() << " ms ago" << std::ends;

            TANGO_THROW_EXCEPTION(API_CantConnectToDevice, desc.str());
        }
    }

    try
    {
        std::string corba_name;
        if(connection_state != CONNECTION_OK)
        {
            if(db_used)
            {
                corba_name = get_corba_name(check_acc);
                if(!check_acc)
                {
                    ApiUtil *au = ApiUtil::instance();
                    int db_num;
                    if(get_from_env_var())
                    {
                        db_num = au->get_db_ind();
                    }
                    else
                    {
                        db_num = au->get_db_ind(get_db_host(), get_db_port_num());
                    }
                    (au->get_db_vect())[db_num]->clear_access_except_errors();
                }
            }
            else
            {
                corba_name = build_corba_name();
            }

            connect(corba_name);
        }

        //
        // Try to ping the device. With omniORB, it is possible that the first
        // real access to the device is done when a call to one of the interface
        // operation is done. Do it now.
        //

        if(connection_state == CONNECTION_OK)
        {
            try
            {
                //                if (user_connect_timeout != -1)
                //                    omniORB::setClientConnectTimeout(user_connect_timeout);
                //                else
                //                    omniORB::setClientConnectTimeout(NARROW_CLNT_TIMEOUT);

                //
                // Impl. change for Tango-10 + IDLv6 - see cppTango #1193
                //
                // we now call 'info' instead of 'ping' so that we can obtain the so called
                // 'server_version' - i.e., the ultimate version supported by the server in
                // which the device we are connected to is running.
                //
                // in some particular cases, this allows us to offer new features to devices
                // simply recompiled against the latest version of tango without modifing
                // their inheritance schema.
                //
                // the telemetry service is an example of such a case - devices inheriting
                // from Device_4Impl or Device5_impl will be able to propagate the trace
                // context as far as they are running within a server where idl 6 or above is
                // supported.
                //
                std::unique_ptr<Tango::DevInfo> device_info(device->info());
                server_version = device_info->server_version;

                //                omniORB::setClientConnectTimeout(0);

                prev_failed_t0 = std::nullopt;

                //
                // If the device is the database, call its post-reconnection method
                //
                // TODO: Implement this with a virtual method in Connection and Database
                // class. Doing it now, will break compatibility (one more virtual method)
                //

                if(corba_name.find("database") != std::string::npos)
                {
                    static_cast<Database *>(this)->post_reconnection();
                }
            }
            catch(CORBA::SystemException &ce)
            {
                //                omniORB::setClientConnectTimeout(0);
                connection_state = CONNECTION_NOTOK;

                TangoSys_OMemStream desc;
                desc << "Failed to connect to device " << dev_name() << std::ends;

                TANGO_RETHROW_DETAILED_EXCEPTION(ApiConnExcept, ce, API_CantConnectToDevice, desc.str());
            }
        }
    }
    catch(DevFailed &)
    {
        prev_failed_t0 = now;

        throw;
    }
}

//+----------------------------------------------------------------------------------------------------------------
//
// function:
//
// 		ClntIdent Connection::get_client_identification() const
//
// description:
//
//      Returns a ClntIdent initialized according to the IDL version of the device (peer) and the ultimate
//      IDL version supported by the server in which the peer is running.
//
//      Added for the telemetry service introduced in IDLv6.
//
//      Any device with IDL version >= 4 running in a server in which the ultimate version of the IDL is >= 6,
//      will benefit from the telemetry features offered at the kernel level (low level profiling). This
//      will limit the discontinuity in the tracing information.
//
//      See ClntIdent in tango.idl v6 for details.
//
//-----------------------------------------------------------------------------------------------------------------
ClntIdent Connection::get_client_identification() const
{
    // the client identification struct to be returned
    ClntIdent ci;

    // the pid of the cpp server (acting as a client) or the pure cpp client within which this code is executed
    TangoSys_Pid pid = ApiUtil::instance()->get_client_pid();

    if(version >= 4 && server_version >= 6)
    {
        // IDLv6 case
        CppClntIdent_6 ci_v6;
        ci_v6.cpp_clnt = pid;
        auto trace_context{W3CTraceContextV0()};
#ifdef TANGO_USE_TELEMETRY
        // populate W3C headers of the IDLv6 data structure for trace context propagation
        std::string trace_parent;
        std::string trace_state;
        Tango::telemetry::Interface::get_trace_context(trace_parent, trace_state);
        trace_context.trace_parent = Tango::string_dup(trace_parent.c_str());
        trace_context.trace_state = Tango::string_dup(trace_state.c_str());
#endif
        ci_v6.trace_context.data(trace_context);
        ci.cpp_clnt_6(ci_v6);
    }
    else
    {
        // pre-IDLv6 case (the pid is the only info set for a cpp client)
        ci.cpp_clnt(pid);
    }

    return ci;
}

//-----------------------------------------------------------------------------
//
// Connection::is_connected() - returns true if connection is in the OK state
//
//-----------------------------------------------------------------------------

bool Connection::is_connected()
{
    bool connected = true;
    ReaderLock guard(con_to_mon);
    if(connection_state != CONNECTION_OK)
    {
        connected = false;
    }
    return connected;
}

//-----------------------------------------------------------------------------
//
// Connection::get_env_var() - Get an environment variable
//
// This method get an environment variable value from different source.
// which are (orderd by piority)
//
// 1 - A real environement variable
// 2 - A file ".tangorc" in the user home directory
// 3 - A file "/etc/tangorc"
//
// in :    - env_var_name : The environment variable name
//
// out : - env_var : The string initialised with the env. variable value
//
// This method returns 0 of the env. variable is found. Otherwise, it returns -1
//
//-----------------------------------------------------------------------------

int Connection::get_env_var(const char *env_var_name, std::string &env_var)
{
    int ret = -1;
    char *env_c_str;

    //
    // try to get it as a classical env. variable
    //

    env_c_str = getenv(env_var_name);

    if(env_c_str == nullptr)
    {
#ifndef _TG_WINDOWS_
        uid_t user_id = geteuid();

        struct passwd pw;
        struct passwd *pw_ptr;
        char buffer[1024];

        if(getpwuid_r(user_id, &pw, buffer, sizeof(buffer), &pw_ptr) != 0)
        {
            return ret;
        }

        if(pw_ptr == nullptr)
        {
            return ret;
        }

        //
        // Try to get it from the user home dir file
        //

        std::string home_file(pw.pw_dir);
        home_file = home_file + "/" + USER_ENV_VAR_FILE;

        int local_ret;
        std::string local_env_var;
        local_ret = get_env_var_from_file(home_file, env_var_name, local_env_var);

        if(local_ret == 0)
        {
            env_var = local_env_var;
            ret = 0;
        }
        else
        {
            //
            // Try to get it from a host defined file
            //

            home_file = TANGO_RC_FILE;
            local_ret = get_env_var_from_file(home_file, env_var_name, local_env_var);
            if(local_ret == 0)
            {
                env_var = local_env_var;
                ret = 0;
            }
        }
#else
        char *env_tango_root;

        env_tango_root = getenv(WindowsEnvVariable);
        if(env_tango_root != nullptr)
        {
            std::string home_file(env_tango_root);
            home_file = home_file + "/" + WINDOWS_ENV_VAR_FILE;

            int local_ret;
            std::string local_env_var;
            local_ret = get_env_var_from_file(home_file, env_var_name, local_env_var);

            if(local_ret == 0)
            {
                env_var = local_env_var;
                ret = 0;
            }
        }

#endif
    }
    else
    {
        env_var = env_c_str;
        ret = 0;
    }

    return ret;
}

//-----------------------------------------------------------------------------
//
// Connection::get_env_var_from_file() - Get an environment variable from a file
//
// in :    - env_var : The environment variable name
//        - f_name : The file name
//
// out : - ret_env_var : The string initialised with the env. variable value
//
// This method returns 0 of the env. variable is found. Otherwise, it returns -1
//
//-----------------------------------------------------------------------------

int Connection::get_env_var_from_file(const std::string &f_name, const char *env_var, std::string &ret_env_var)
{
    std::ifstream inFile;
    std::string file_line;
    std::string var(env_var);
    int ret = -1;

    inFile.open(f_name.c_str());
    if(!inFile)
    {
        return ret;
    }

    std::transform(var.begin(), var.end(), var.begin(), ::tolower);

    std::string::size_type pos_env, pos_comment;

    while(!inFile.eof())
    {
        getline(inFile, file_line);
        std::transform(file_line.begin(), file_line.end(), file_line.begin(), ::tolower);

        if((pos_env = file_line.find(var)) != std::string::npos)
        {
            pos_comment = file_line.find('#');
            if((pos_comment != std::string::npos) && (pos_comment < pos_env))
            {
                continue;
            }

            std::string::size_type pos;
            if((pos = file_line.find('=')) != std::string::npos)
            {
                std::string tg_host = file_line.substr(pos + 1);
                std::string::iterator end_pos = remove(tg_host.begin(), tg_host.end(), ' ');
                tg_host.erase(end_pos, tg_host.end());

                ret_env_var = tg_host;
                ret = 0;
                break;
            }
        }
    }

    inFile.close();
    return ret;
}

//-------------------------------------------------------------------------------------------------------------------
//
// method:
//        Connection::get_fqdn()
//
// description:
//         This method gets the host fully qualified domain name (from DNS) and modified the passed string accordingly
//
// argument:
//         in/out :
//            - the_host: The original host name
//
//------------------------------------------------------------------------------------------------------------------

void Connection::get_fqdn(std::string &the_host)
{
    //
    // If the host name we received is the name of the host we are running on,
    // set a flag
    //

    char buffer[Tango::detail::TANGO_MAX_HOSTNAME_LEN];
    bool local_host = false;

    if(gethostname(buffer, Tango::detail::TANGO_MAX_HOSTNAME_LEN) == 0)
    {
        if(::strcmp(buffer, the_host.c_str()) == 0)
        {
            local_host = true;
        }
    }

    struct addrinfo hints;

    memset(&hints, 0, sizeof(struct addrinfo));

    hints.ai_flags = AI_ADDRCONFIG;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    struct addrinfo *info;
    struct addrinfo *ptr;
    char tmp_host[512];
    bool host_found = false;
    std::vector<std::string> ip_list;

    //
    // If we are running on local host, get IP address(es) from NIC board
    //

    if(local_host)
    {
        ApiUtil *au = ApiUtil::instance();
        au->get_ip_from_if(ip_list);
        hints.ai_flags |= AI_NUMERICHOST;
    }
    else
    {
        ip_list.push_back(the_host);
    }

    //
    // Try to get FQDN
    //

    size_t i;
    for(i = 0; i < ip_list.size() && !host_found; i++)
    {
        int result = getaddrinfo(ip_list[i].c_str(), nullptr, &hints, &info);

        if(result == 0)
        {
            ptr = info;
            int nb_loop = 0;
            std::string myhost;
            std::string::size_type pos;

            while(ptr != nullptr)
            {
                if(getnameinfo(ptr->ai_addr, ptr->ai_addrlen, tmp_host, 512, nullptr, 0, NI_NAMEREQD) == 0)
                {
                    nb_loop++;
                    myhost = tmp_host;
                    pos = myhost.find('.');
                    if(pos != std::string::npos)
                    {
                        std::string canon = myhost.substr(0, pos);
                        if(canon == the_host)
                        {
                            the_host = myhost;
                            host_found = true;
                            break;
                        }
                    }
                }
                ptr = ptr->ai_next;
            }
            freeaddrinfo(info);

            if(!host_found && nb_loop == 1 && i == (ip_list.size() - 1))
            {
                the_host = myhost;
            }
        }
    }
}

//-----------------------------------------------------------------------------
//
// Connection::get_timeout_millis() - public method to get timeout on a TANGO device
//
//-----------------------------------------------------------------------------

int Connection::get_timeout_millis()
{
    ReaderLock guard(con_to_mon);
    return timeout;
}

//-----------------------------------------------------------------------------
//
// Connection::set_timeout_millis() - public method to set timeout on a TANGO device
//
//-----------------------------------------------------------------------------

void Connection::set_timeout_millis(int millisecs)
{
    WriterLock guard(con_to_mon);

    timeout = millisecs;

    try
    {
        if(connection_state != CONNECTION_OK)
        {
            reconnect(dbase_used);
        }

        omniORB::setClientCallTimeout(device, millisecs);
    }
    catch(Tango::DevFailed &)
    {
    }
}

//-----------------------------------------------------------------------------
//
// Connection::command_inout() - public method to execute a command on a TANGO device
//
//-----------------------------------------------------------------------------

DeviceData Connection::command_inout(const std::string &command)
{
    DeviceData data_in;

    return (command_inout(command, data_in));
}

//-----------------------------------------------------------------------------
//
// Connection::command_inout() - public method to execute a command on a TANGO device
//
//-----------------------------------------------------------------------------

DeviceData Connection::command_inout(const std::string &command, const DeviceData &data_in)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({{"tango.operation.target", dev_name()}, {"tango.operation.argument", command}}));

    //
    // We are using a pointer to an Any as the return value of the command_inout
    // call. This is because the assignament to the Any_var any in the
    // DeviceData object in faster in this case (no copy).
    // Don't forget that the any_var in the DeviceData takes ownership of the
    // memory allocated
    //

    DeviceData data_out;
    int ctr = 0;
    DevSource local_source;
    AccessControlType local_act;

    while(ctr < 2)
    {
        try
        {
            check_and_reconnect(local_source, local_act);

            //
            // Manage control access in case the access right
            // is READ_ONLY. We need to check if the command is a
            // "READ" command or not
            //

            if(local_act == ACCESS_READ)
            {
                ApiUtil *au = ApiUtil::instance();

                std::vector<Database *> &v_d = au->get_db_vect();
                Database *db;
                if(v_d.empty())
                {
                    db = static_cast<Database *>(this);
                }
                else
                {
                    int db_num;

                    if(get_from_env_var())
                    {
                        db_num = au->get_db_ind();
                    }
                    else
                    {
                        db_num = au->get_db_ind(get_db_host(), get_db_port_num());
                    }
                    db = v_d[db_num];
                    /*                    if (db->is_control_access_checked() == false)
                                            db = static_cast<Database *>(this);*/
                }

                //
                // If the command is not allowed, throw exception
                // Also throw exception if it was not possible to get the list
                // of allowed commands from the control access service
                //
                // The ping rule is simply to send to the client correct
                // error message in case of re-connection
                //

                std::string d_name = dev_name();

                if(!db->is_command_allowed(d_name, command))
                {
                    try
                    {
                        Device_var dev = Device::_duplicate(device);
                        dev->ping();
                    }
                    catch(...)
                    {
                        set_connection_state(CONNECTION_NOTOK);
                        throw;
                    }

                    DevErrorList &e = db->get_access_except_errors();
                    /*                    if (e.length() != 0)
                                        {
                                            DevFailed df(e);
                                            throw df;
                                        }*/

                    TangoSys_OMemStream desc;
                    if(e.length() == 0)
                    {
                        desc << "Command " << command << " on device " << dev_name() << " is not authorized"
                             << std::ends;
                    }
                    else
                    {
                        desc << "Command " << command << " on device " << dev_name()
                             << " is not authorized because an error occurs while talking to the Controlled Access "
                                "Service"
                             << std::ends;
                        std::string ex(e[0].desc);
                        if(ex.find("defined") != std::string::npos)
                        {
                            desc << "\n" << ex;
                        }
                        desc << std::ends;
                    }

                    TANGO_THROW_DETAILED_EXCEPTION(NotAllowedExcept, API_ReadOnlyMode, desc.str());
                }
            }

            //
            // Now, try to execute the command
            //

            CORBA::Any *received;
            if(version >= 4)
            {
                Device_4_var dev = Device_4::_duplicate(device_4);
                received =
                    dev->command_inout_4(command.c_str(), data_in.any, local_source, get_client_identification());
            }
            else if(version >= 2)
            {
                Device_2_var dev = Device_2::_duplicate(device_2);
                received = dev->command_inout_2(command.c_str(), data_in.any, local_source);
            }
            else
            {
                Device_var dev = Device::_duplicate(device);
                received = dev->command_inout(command.c_str(), data_in.any);
            }

            ctr = 2;
            data_out.any = received;
        }
        catch(Tango::ConnectionFailed &e)
        {
            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;
            TANGO_RETHROW_DETAILED_EXCEPTION(ApiConnExcept, e, API_CommandFailed, desc.str());
        }
        catch(Tango::DevFailed &e)
        {
            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;

            if(::strcmp(e.errors[0].reason, DEVICE_UNLOCKED_REASON) == 0)
            {
                TANGO_RETHROW_DETAILED_EXCEPTION(DeviceUnlockedExcept, e, DEVICE_UNLOCKED_REASON, desc.str());
            }
            else
            {
                TANGO_RETHROW_EXCEPTION(e, API_CommandFailed, desc.str());
            }
        }
        catch(CORBA::TRANSIENT &trans)
        {
            TRANSIENT_NOT_EXIST_EXCEPT_CMD(trans);
        }
        catch(CORBA::OBJECT_NOT_EXIST &one)
        {
            if(one.minor() == omni::OBJECT_NOT_EXIST_NoMatch || one.minor() == 0)
            {
                TRANSIENT_NOT_EXIST_EXCEPT_CMD(one);
            }
            else
            {
                set_connection_state(CONNECTION_NOTOK);
                TangoSys_OMemStream desc;
                desc << "Failed to execute command_inout on device " << dev_name();
                desc << ", command " << command << std::ends;
                TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, one, API_CommunicationFailed, desc.str());
            }
        }
        catch(CORBA::COMM_FAILURE &comm)
        {
            if(comm.minor() == omni::COMM_FAILURE_WaitingForReply)
            {
                TRANSIENT_NOT_EXIST_EXCEPT_CMD(comm);
            }
            else
            {
                set_connection_state(CONNECTION_NOTOK);
                TangoSys_OMemStream desc;
                desc << "Failed to execute command_inout on device " << dev_name();
                desc << ", command " << command << std::ends;
                TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, comm, API_CommunicationFailed, desc.str());
            }
        }
        catch(CORBA::SystemException &ce)
        {
            set_connection_state(CONNECTION_NOTOK);

            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;
            TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, ce, API_CommunicationFailed, desc.str());
        }
    }

    return data_out;

    TANGO_TELEMETRY_TRACE_END();
}

//-----------------------------------------------------------------------------
//
// Connection::command_inout() - public method to execute a command on a TANGO device
//                 using low level CORBA types
//
//-----------------------------------------------------------------------------

CORBA::Any_var Connection::command_inout(const std::string &command, const CORBA::Any &any)
{
    TANGO_TELEMETRY_TRACE_BEGIN(({{"tango.operation.target", dev_name()}, {"tango.operation.argument", command}}));

    int ctr = 0;
    Tango::DevSource local_source;
    Tango::AccessControlType local_act;

    while(ctr < 2)
    {
        try
        {
            check_and_reconnect(local_source, local_act);

            //
            // Manage control access in case the access right
            // is READ_ONLY. We need to check if the command is a
            // "READ" command or not
            //

            if(local_act == ACCESS_READ)
            {
                ApiUtil *au = ApiUtil::instance();

                std::vector<Database *> &v_d = au->get_db_vect();
                Database *db;
                if(v_d.empty())
                {
                    db = static_cast<Database *>(this);
                }
                else
                {
                    int db_num;

                    if(get_from_env_var())
                    {
                        db_num = au->get_db_ind();
                    }
                    else
                    {
                        db_num = au->get_db_ind(get_db_host(), get_db_port_num());
                    }
                    db = v_d[db_num];
                    /*                    if (db->is_control_access_checked() == false)
                                            db = static_cast<Database *>(this);*/
                }

                //
                // If the command is not allowed, throw exception
                // Also throw exception if it was not possible to get the list
                // of allowed commands from the control access service
                //
                // The ping rule is simply to send to the client correct
                // error message in case of re-connection
                //

                std::string d_name = dev_name();
                if(!db->is_command_allowed(d_name, command))
                {
                    try
                    {
                        Device_var dev = Device::_duplicate(device);
                        dev->ping();
                    }
                    catch(...)
                    {
                        set_connection_state(CONNECTION_NOTOK);
                        throw;
                    }

                    DevErrorList &e = db->get_access_except_errors();
                    /*                    if (e.length() != 0)
                                        {
                                            DevFailed df(e);
                                            throw df;
                                        }*/

                    TangoSys_OMemStream desc;
                    if(e.length() == 0)
                    {
                        desc << "Command " << command << " on device " << dev_name() << " is not authorized"
                             << std::ends;
                    }
                    else
                    {
                        desc << "Command " << command << " on device " << dev_name()
                             << " is not authorized because an error occurs while talking to the Controlled Access "
                                "Service"
                             << std::ends;
                        std::string ex(e[0].desc);
                        if(ex.find("defined") != std::string::npos)
                        {
                            desc << "\n" << ex;
                        }
                        desc << std::ends;
                    }

                    TANGO_THROW_DETAILED_EXCEPTION(NotAllowedExcept, API_ReadOnlyMode, desc.str());
                }
            }

            if(version >= 4)
            {
                Device_4_var dev = Device_4::_duplicate(device_4);
                return (dev->command_inout_4(command.c_str(), any, local_source, get_client_identification()));
            }
            else if(version >= 2)
            {
                Device_2_var dev = Device_2::_duplicate(device_2);
                return (dev->command_inout_2(command.c_str(), any, local_source));
            }
            else
            {
                Device_var dev = Device::_duplicate(device);
                return (dev->command_inout(command.c_str(), any));
            }
        }
        catch(Tango::ConnectionFailed &e)
        {
            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;
            TANGO_RETHROW_DETAILED_EXCEPTION(ApiConnExcept, e, API_CommandFailed, desc.str());
        }
        catch(Tango::DevFailed &e)
        {
            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;

            if(::strcmp(e.errors[0].reason, DEVICE_UNLOCKED_REASON) == 0)
            {
                TANGO_RETHROW_DETAILED_EXCEPTION(DeviceUnlockedExcept, e, DEVICE_UNLOCKED_REASON, desc.str());
            }
            else
            {
                TANGO_RETHROW_EXCEPTION(e, API_CommandFailed, desc.str());
            }
        }
        catch(CORBA::TRANSIENT &trans)
        {
            TRANSIENT_NOT_EXIST_EXCEPT_CMD(trans);
        }
        catch(CORBA::OBJECT_NOT_EXIST &one)
        {
            if(one.minor() == omni::OBJECT_NOT_EXIST_NoMatch || one.minor() == 0)
            {
                TRANSIENT_NOT_EXIST_EXCEPT_CMD(one);
            }
            else
            {
                set_connection_state(CONNECTION_NOTOK);
                TangoSys_OMemStream desc;
                desc << "Failed to execute command_inout on device " << dev_name();
                desc << ", command " << command << std::ends;
                TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, one, API_CommunicationFailed, desc.str());
            }
        }
        catch(CORBA::COMM_FAILURE &comm)
        {
            if(comm.minor() == omni::COMM_FAILURE_WaitingForReply)
            {
                TRANSIENT_NOT_EXIST_EXCEPT_CMD(comm);
            }
            else
            {
                set_connection_state(CONNECTION_NOTOK);
                TangoSys_OMemStream desc;
                desc << "Failed to execute command_inout on device " << dev_name();
                desc << ", command " << command << std::ends;
                TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, comm, API_CommunicationFailed, desc.str());
            }
        }
        catch(CORBA::SystemException &ce)
        {
            set_connection_state(CONNECTION_NOTOK);

            TangoSys_OMemStream desc;
            desc << "Failed to execute command_inout on device " << dev_name();
            desc << ", command " << command << std::ends;
            TANGO_RETHROW_DETAILED_EXCEPTION(ApiCommExcept, ce, API_CommunicationFailed, desc.str());
        }
    }

    //
    // Just to make VC++ quiet (will never reach this code !)
    //

    CORBA::Any_var tmp;
    return tmp;

    TANGO_TELEMETRY_TRACE_END();
}

long Connection::add_asyn_request(CORBA::Request_ptr req, TgRequest::ReqType req_type)
{
    omni_mutex_lock guard(asyn_mutex);
    long id = ApiUtil::instance()->get_pasyn_table()->store_request(req, req_type);
    pasyn_ctr++;
    return id;
}

void Connection::remove_asyn_request(long id)
{
    omni_mutex_lock guard(asyn_mutex);

    ApiUtil::instance()->get_pasyn_table()->remove_request(id);
    pasyn_ctr--;
}

void Connection::add_asyn_cb_request(CORBA::Request_ptr req, CallBack *cb, Connection *con, TgRequest::ReqType req_type)
{
    omni_mutex_lock guard(asyn_mutex);
    ApiUtil::instance()->get_pasyn_table()->store_request(req, cb, con, req_type);
    pasyn_cb_ctr++;
}

void Connection::remove_asyn_cb_request(Connection *con, CORBA::Request_ptr req)
{
    omni_mutex_lock guard(asyn_mutex);
    ApiUtil::instance()->get_pasyn_table()->remove_request(con, req);
    pasyn_cb_ctr--;
}

long Connection::get_pasyn_cb_ctr()
{
    long ret;
    asyn_mutex.lock();
    ret = pasyn_cb_ctr;
    asyn_mutex.unlock();
    return ret;
}

} // namespace Tango
