/*PROTECTED REGION ID(DataBase::main.cpp) ENABLED START*/
/* clang-format on */
//=============================================================================
//
// file :        DataBase.cpp
//
// description : C++ source for the DataBase device server main.
//               The main rule is to initialise (and create) the Tango
//               system and to create the DServerClass singleton.
//                The main should be the same for every Tango device server.
//
// project :     TANGO.
//
//=============================================================================
//                This file is generated by POGO
//        (Program Obviously used to Generate tango Object)
//=============================================================================
//
//
// Copyright (C) :      2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014
//						European Synchrotron Radiation Facility
//                      BP 220, Grenoble 38043
//                      FRANCE
//
// This file is part of Tango.
//
// Tango is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tango is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Tango.  If not, see <http://www.gnu.org/licenses/>.
//

#include <tango/tango.h>
#include "DataBase.h"
#include "DatabaseConnectionPool.h"
#include "Logging.h"
#include "Utils.h"
#include "ClassFactory.h"

#ifndef WIN32
  #include <sys/resource.h>
#endif

#ifndef _TG_WINDOWS_
  #include <arpa/inet.h>
  #include <omniORB4/internal/giopStrand.h>
  #include <omniORB4/internal/giopStream.h>
  #include <omniORB4/internal/GIOP_S.h>

  #include <omniORB4/omniInterceptors.h>

  #include <unordered_set>
std::unordered_set<std::string> addresses_allowed;
omni_thread::key_t key;
int acl_enabled;

CORBA::Boolean retrieve_client_address(omni::omniInterceptors::serverReceiveRequest_T::info_T &info)
{
    // The address returned by peeraddress() will be of the form "giop:tcp:<ip>:<port>"
    // or "giop:ssl:<ip>:<port>" where <ip> seems to be in the inet_ntop format:
    // omniORB/orbcore/tcp/tcpConnection.cc +228 and omniORB/orbcore/tcpSocket.cc +1071
    Value_t *v = new Value_t(info.giop_s.strand().connection->peeraddress());
    omni_thread::self()->set_value(key, v);
    return true;
}

bool convert_to_ntop_format(std::string &address)
{
    int af;
    char str[INET6_ADDRSTRLEN];
    char buf[sizeof(struct in6_addr)];

    /* Convert to binary form */
    if(inet_pton(AF_INET, address.c_str(), buf))
    {
        af = AF_INET;
    }
    else
    {
        if(inet_pton(AF_INET6, address.c_str(), buf))
        {
            af = AF_INET6;
        }
        else
        {
            std::cerr << "Address " << address << " is ignored due invalid format" << std::endl;
            return false;
        }
    }

    /* Convert to text form */
    if(inet_ntop(af, buf, str, sizeof(buf)))
    {
        address = std::string(str);
        return true;
    }
    else
    {
        std::cerr << "Fail to convert address to text format" << std::endl;
        return false;
    }
}

void load_authorization_rules(std::ifstream &acl_file)
{
    std::string line;
    while(std::getline(acl_file, line))
    {
        line.erase(0, line.find_first_not_of(" "));
        size_t pos = line.find_first_of("#");
        if(pos != std::string::npos)
        {
            line.erase(pos, std::string::npos);
        }

        /* Ignore comments which have to start with # and empty lines */
        if(line[0] != '#' && line.size())
        {
            std::string transport = line.substr(0, MIN_TRANSPORT_STRING_LENGTH);
            if(transport == "giop:uni" /* giop:unix */)
            {
                /* UNIX domain sockets are always allowed by default */
            }
            else if(transport == "giop:tcp" || transport == "giop:ssl")
            {
                std::string prefix = line.substr(0, 9);
                std::string address = line.substr(9);
                char octet[4][4];
                /* Try to extract octet by octet in the case of IPv4 address */
                if(sscanf(address.c_str(), "%[0-9].%[0-9].%[0-9].%[0-9*]", octet[0], octet[1], octet[2], octet[3]) != 4)
                {
                    // It should be an IPv6 pass it as is (without expand anything)
                    if(convert_to_ntop_format(address))
                    {
                        addresses_allowed.insert(prefix + address);
                    }
                }
                else
                {
                    if(octet[3][0] == '*')
                    {
                        /* Expand wildcard to 1..254 */
                        char buf[4];
                        for(int i = 1; i < 255; ++i)
                        {
                            snprintf(buf, sizeof(buf), "%d", i);
                            address = std::string(octet[0]) + "." + octet[1] + "." + octet[2] + "." + buf;
                            if(convert_to_ntop_format(address))
                            {
                                addresses_allowed.insert(prefix + address);
                            }
                        }
                    }
                    else
                    {
                        if(convert_to_ntop_format(address))
                        {
                            addresses_allowed.insert(prefix + address);
                        }
                    }
                }
            }
            else
            {
                /* giop:htt, giop:fd: or a future yet no-existing transport layer
                 * are not supported yet so blocked by default */
            }
        }
    }
}
#endif

int main(int argc, char *argv[])
{
    TANGO_LOG << "main(): arrived " << std::endl;

    Tango::Util *tango_util;
    Tango::Util::_UseDb = false; // suppress database use

#ifndef WIN32

    // Setting maximum number of opened file

    // Default limit
    struct rlimit limit;
    limit.rlim_cur = 1024;
    limit.rlim_max = 1024;

  #ifndef _TG_WINDOWS_
    // Browse argv and try to find the --aclFile option
    int k = 0;
    while(k < argc && !acl_enabled)
    {
        acl_enabled = (strcasecmp(argv[k], "-aclFile") == 0);
        if(!acl_enabled)
        {
            k++;
        }
    }
    if(acl_enabled)
    {
        if(k >= argc - 1)
        {
            TANGO_LOG << "Invalid aclFile parameter." << std::endl;
            return -1;
        }
        std::ifstream infile;
        infile.open(argv[k + 1]);
        if(infile.is_open())
        {
            load_authorization_rules(infile);
            infile.close();
        }
        else
        {
            TANGO_LOG << "File " << argv[k + 1] << " not found." << std::endl;
            return -1;
        }
    }
  #endif

    // Browse argv and try to find the -maxFile option
    int found = 0;
    int i = 0;
    while(i < argc && !found)
    {
        found = (strcasecmp(argv[i], "-maxFile") == 0);
        if(!found)
        {
            i++;
        }
    }
    if(found)
    {
        if(i >= argc - 1)
        {
            TANGO_LOG << "Invalid flimit parameter." << std::endl;
            return -1;
        }
        int flimit = ParseString<int>(argv[i + 1]);
        if(flimit == 0)
        {
            TANGO_LOG << "Invalid flimit parameter." << std::endl;
            return -1;
        }
        limit.rlim_cur = flimit;
        limit.rlim_max = flimit;
    }

    // Apply the max open file limit
    if(setrlimit(RLIMIT_NOFILE, &limit) != 0)
    {
        TANGO_LOG << "setrlimit(RLIMIT_NOFILE," << (int) limit.rlim_cur << ") failed." << std::endl;
        if(errno == EPERM)
        {
            TANGO_LOG << "You may need to increase maximum number of opened file system limit." << std::endl;
        }
        else
        {
            TANGO_LOG << "setrlimit() failed with error code : " << (int) errno << std::endl;
        }
        return -1;
    }
#endif

    // Browse argv and try to find the -connPoolSize option
    int j = 0;
    int found_conn = 0;
    int conn_size = DEFAULT_CONN_POOL_SIZE;
    while(j < argc && !found_conn)
    {
#ifndef WIN32
        found_conn = (strcasecmp(argv[j], "-poolSize") == 0);
#else
        found_conn = (stricmp(argv[j], "-poolSize") == 0);
#endif
        if(!found_conn)
        {
            j++;
        }
    }
    if(found_conn)
    {
        if(j >= argc - 1)
        {
            TANGO_LOG << "Invalid poolSize parameter." << std::endl;
            return -1;
        }
        conn_size = ParseString<int>(argv[j + 1]);
        if(conn_size <= 0)
        {
            TANGO_LOG << "Invalid poolSize parameter." << std::endl;
            return -1;
        }
    }

    DatabaseConnectionPool::set_conn_pool_size(conn_size);

    try
    {
        //
        // Initialise the device server
        //
        TANGO_LOG_INFO << "main(): calling  Tango::Util::Init(argc,argv)" << std::endl;
        tango_util = Tango::Util::init(argc, argv);

#if TANGO_VERSION >= TANGO_MAKE_VERSION(10, 3, 0)
        tango_util->register_dserver_constructor(&DataBase_ns::constructor);
#endif

        // construct database name

        DataBase_ns::DataBase::db_name = "sys/database/";
        DataBase_ns::DataBase::db_name.append(argv[1]);
        TANGO_LOG_INFO << "main(): create DataBase " << DataBase_ns::DataBase::db_name << std::endl;

        //
        // Create and install interceptors
        //

        DataBase_ns::DbInter *dbi = new DataBase_ns::DbInter();
        tango_util->set_interceptors(dbi);

#ifndef _TG_WINDOWS_
        //
        // Add an OmniORB interceptor
        //
        if(acl_enabled)
        {
            key = omni_thread::allocate_key();
            omniORB::getInterceptors()->serverReceiveRequest.add(&retrieve_client_address);
        }
#endif

        //
        // Set the serialization method
        //

        tango_util->set_serial_model(Tango::NO_SYNC);

        //
        // Create the device server singleton which will create everything
        //

        TANGO_LOG_INFO << "main(): calling  tango_util->server_init()" << std::endl;
        tango_util->server_init();

        //
        // Export devices to the outside world as CORBA named servant and to TANGO
        // database
        //
        Tango::DeviceImpl *dbase, *dserver;
        Tango::DevVarStringArray *export_parms = new Tango::DevVarStringArray();

        dserver = tango_util->get_dserver_device();
        dbase = tango_util->get_device_by_name(DataBase_ns::DataBase::db_name);
        //
        // export database as named servant
        //
        TANGO_LOG << "main(): export DataBase as named servant (name=database)" << std::endl;
        export_parms->length(5);

        // export dserver object to TANGO database

        Tango::Device_var d = dserver->_this();
        dserver->set_d_var(Tango::Device::_duplicate(d));
        (*export_parms)[0] = CORBA::string_dup(dserver->get_name().c_str());
        auto orb = Tango::Util::instance()->get_orb();
        CORBA::String_var dserver_str_ior = orb->object_to_string(d);
        (*export_parms)[1] = CORBA::string_dup(dserver_str_ior);
        (*export_parms)[2] = CORBA::string_dup(tango_util->get_host_name().c_str());
        (*export_parms)[3] = CORBA::string_dup(tango_util->get_pid_str().c_str());
        (*export_parms)[4] = CORBA::string_dup(tango_util->get_version_str().c_str());

        (static_cast<DataBase_ns::DataBase *>(dbase))->db_export_device(export_parms);

        // export database object to TANGO database

        (*export_parms)[0] = CORBA::string_dup(dbase->get_name().c_str());
        CORBA::String_var str_ior = orb->object_to_string(dbase->get_d_var());
        (*export_parms)[1] = CORBA::string_dup(str_ior);
        ((DataBase_ns::DataBase *) dbase)->db_export_device(export_parms);

        delete export_parms;

#if TANGO_VERSION >= TANGO_MAKE_VERSION(10, 3, 0)
        Tango::Util::_UseDb = true;
        tango_util->connect_db();
#endif

        //
        // Run the endless loop
        //

        TANGO_LOG << "Ready to accept request" << std::endl;
        orb->run();
    }
    catch(std::bad_alloc &)
    {
        TANGO_LOG << "Can't allocate memory to store device object !!!" << std::endl;
        TANGO_LOG << "Exiting" << std::endl;
    }
    catch(std::exception &e)
    {
        TANGO_LOG << "Exception: " << e.what() << std::endl;
        TANGO_LOG << "Exiting" << std::endl;
    }
    catch(CORBA::Exception &e)
    {
        TANGO_LOG << "Received a CORBA::Exception" << std::endl;
        Tango::Except::print_exception(e);
        TANGO_LOG << "Exiting" << std::endl;
    }

    return (0);
}

/* clang-format off */
/*PROTECTED REGION END*/	//	DataBase::main.cpp
