/* $Id: Config.cpp $ */
/** @file
 * Configuration for DHCP.
 */

/*
 * Copyright (C) 2013-2014 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/**
 * XXX: license.
 */

#include <iprt/asm.h>
#include <iprt/getopt.h>
#include <iprt/net.h>
#include <iprt/time.h>

#include <VBox/sup.h>
#include <VBox/intnet.h>
#include <VBox/intnetinline.h>
#include <VBox/vmm/vmm.h>
#include <VBox/version.h>

#include <VBox/com/string.h>

#include <iprt/cpp/xml.h>

#define BASE_SERVICES_ONLY
#include "../NetLib/VBoxNetBaseService.h"
#include "../NetLib/VBoxNetLib.h"
#include "../NetLib/shared_ptr.h"

#include <list>
#include <vector>
#include <map>
#include <string>

#include "Config.h"
#include "ClientDataInt.h"

bool operator== (const Lease& lhs, const Lease& rhs)
{
    return (lhs.m.get() == rhs.m.get());
}


bool operator!= (const Lease& lhs, const Lease& rhs)
{
    return !(lhs == rhs);
}


bool operator< (const Lease& lhs, const Lease& rhs)
{
    return (   (lhs.getAddress() < rhs.getAddress())
            || (lhs.issued() < rhs.issued()));
}
/* consts */

const NullConfigEntity *g_NullConfig = new NullConfigEntity();
RootConfigEntity *g_RootConfig = new RootConfigEntity(std::string("ROOT"), 1200 /* 20 min. */);
const ClientMatchCriteria *g_AnyClient = new AnyClientMatchCriteria();

static ConfigurationManager *g_ConfigurationManager = ConfigurationManager::getConfigurationManager();

static NetworkManager *g_NetworkManager = NetworkManager::getNetworkManager();

bool MACClientMatchCriteria::check(const Client& client) const
{
    return (client == m_mac);
}


int BaseConfigEntity::match(Client& client, BaseConfigEntity **cfg)
{
    int iMatch = (m_criteria && m_criteria->check(client)? m_MatchLevel: 0);
    if (m_children.empty())
    {
        if (iMatch > 0)
        {
            *cfg = this;
            return iMatch;
        }
    }
    else
    {
        *cfg = this;
        /* XXX: hack */
        BaseConfigEntity *matching = this;
        int matchingLevel = m_MatchLevel;

        for (std::vector<BaseConfigEntity *>::iterator it = m_children.begin();
             it != m_children.end();
             ++it)
        {
            iMatch = (*it)->match(client, &matching);
            if (iMatch > matchingLevel)
            {
                *cfg = matching;
                matchingLevel = iMatch;
            }
        }
        return matchingLevel;
    }
    return iMatch;
}

/* Client */
/* Configs
    NetworkConfigEntity(std::string name,
                        ConfigEntity* pCfg,
                        ClientMatchCriteria* criteria,
                        RTNETADDRIPV4& networkID,
                        RTNETADDRIPV4& networkMask)
*/
static const RTNETADDRIPV4 g_AnyIpv4 = {0};
static const RTNETADDRIPV4 g_AllIpv4 = {0xffffffff};
RootConfigEntity::RootConfigEntity(std::string name, uint32_t expPeriod):
  NetworkConfigEntity(name, g_NullConfig, g_AnyClient, g_AnyIpv4, g_AllIpv4)
{
    m_MatchLevel = 2;
    m_u32ExpirationPeriod = expPeriod;
}

/* Configuration Manager */
struct ConfigurationManager::Data
{
    Data():fFileExists(false){}

    MapLease2Ip4Address  m_allocations;
    Ipv4AddressContainer m_nameservers;
    Ipv4AddressContainer m_routers;

    std::string          m_domainName;
    VecClient            m_clients;
    com::Utf8Str         m_leaseStorageFilename;
    bool                 fFileExists;
};

ConfigurationManager *ConfigurationManager::getConfigurationManager()
{
    if (!g_ConfigurationManager)


    {
        g_ConfigurationManager = new ConfigurationManager();
        g_ConfigurationManager->init();
    }

    return g_ConfigurationManager;
}


const std::string tagXMLLeases = "Leases";
const std::string tagXMLLeasesAttributeVersion = "version";
const std::string tagXMLLeasesVersion_1_0 = "1.0";
const std::string tagXMLLease = "Lease";
const std::string tagXMLLeaseAttributeMac = "mac";
const std::string tagXMLLeaseAttributeNetwork = "network";
const std::string tagXMLLeaseAddress = "Address";
const std::string tagXMLAddressAttributeValue = "value";
const std::string tagXMLLeaseTime = "Time";
const std::string tagXMLTimeAttributeIssued = "issued";
const std::string tagXMLTimeAttributeExpiration = "expiration";
const std::string tagXMLLeaseOptions = "Options";

/**
 * <Leases version="1.0">
 *   <Lease mac="" network=""/>
 *    <Address value=""/>
 *    <Time issued="" expiration=""/>
 *    <options>
 *      <option name="" type=""/>
 *      </option>
 *    </options>
 *   </Lease>
 * </Leases>
 */
int ConfigurationManager::loadFromFile(const com::Utf8Str& leaseStorageFileName)
{
    m->m_leaseStorageFilename = leaseStorageFileName;

    xml::XmlFileParser parser;
    xml::Document doc;

    try {
        parser.read(m->m_leaseStorageFilename.c_str(), doc);
    }
    catch (...)
    {
        return VINF_SUCCESS;
    }

    /* XML parsing */
    xml::ElementNode *root = doc.getRootElement();

    if (!root || !root->nameEquals(tagXMLLeases.c_str()))
    {
        m->fFileExists = false;
        return VERR_NOT_FOUND;
    }

    com::Utf8Str version;
    if (root)
        root->getAttributeValue(tagXMLLeasesAttributeVersion.c_str(), version);

    /* XXX: version check */
    xml::NodesLoop leases(*root);

    bool valueExists;
    const xml::ElementNode *lease;
    while ((lease = leases.forAllNodes()))
    {
        if (!lease->nameEquals(tagXMLLease.c_str()))
            continue;

        ClientData *data = new ClientData();
        Lease l(data);
        if (l.fromXML(lease))
        {

            m->m_allocations.insert(MapLease2Ip4AddressPair(l, l.getAddress()));


            NetworkConfigEntity *pNetCfg = NULL;
            Client c(data);
            int rc = g_RootConfig->match(c, (BaseConfigEntity **)&pNetCfg);
            Assert(rc >= 0 && pNetCfg);

            l.setConfig(pNetCfg);

            m->m_clients.push_back(c);
        }
    }

    return VINF_SUCCESS;
}


int ConfigurationManager::saveToFile()
{
    if (m->m_leaseStorageFilename.isEmpty())
        return VINF_SUCCESS;

    xml::Document doc;

    xml::ElementNode *root = doc.createRootElement(tagXMLLeases.c_str());
    if (!root)
        return VERR_INTERNAL_ERROR;

    root->setAttribute(tagXMLLeasesAttributeVersion.c_str(), tagXMLLeasesVersion_1_0.c_str());

    for(MapLease2Ip4AddressConstIterator it = m->m_allocations.begin();
        it != m->m_allocations.end(); ++it)
    {
        xml::ElementNode *lease = root->createChild(tagXMLLease.c_str());
        if (!it->first.toXML(lease))
        {
            /* XXX: todo logging + error handling */
        }
    }

    try {
        xml::XmlFileWriter writer(doc);
        writer.write(m->m_leaseStorageFilename.c_str(), true);
    } catch(...){}

    return VINF_SUCCESS;
}


int ConfigurationManager::extractRequestList(PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& rawOpt)
{
    return ConfigurationManager::findOption(RTNET_DHCP_OPT_PARAM_REQ_LIST, pDhcpMsg, cbDhcpMsg, rawOpt);
}


Client ConfigurationManager::getClientByDhcpPacket(const RTNETBOOTP *pDhcpMsg, size_t cbDhcpMsg)
{

    VecClientIterator it;
    bool fDhcpValid = false;
    uint8_t uMsgType = 0;

    fDhcpValid = RTNetIPv4IsDHCPValid(NULL, pDhcpMsg, cbDhcpMsg, &uMsgType);
    AssertReturn(fDhcpValid, Client::NullClient);

    LogFlowFunc(("dhcp:mac:%RTmac\n", &pDhcpMsg->bp_chaddr.Mac));
    /* 1st. client IDs */
    for ( it = m->m_clients.begin();
         it != m->m_clients.end();
         ++it)
    {
        if ((*it) == pDhcpMsg->bp_chaddr.Mac)
        {
            LogFlowFunc(("client:mac:%RTmac\n",  it->getMacAddress()));
            /* check timestamp that request wasn't expired. */
            return (*it);
        }
    }

    if (it == m->m_clients.end())
    {
        /* We hasn't got any session for this client */
        Client c;
        c.initWithMac(pDhcpMsg->bp_chaddr.Mac);
        m->m_clients.push_back(c);
        return m->m_clients.back();
    }

    return Client::NullClient;
}

/**
 * Finds an option.
 *
 * @returns On success, a pointer to the first byte in the option data (no none
 *          then it'll be the byte following the 0 size field) and *pcbOpt set
 *          to the option length.
 *          On failure, NULL is returned and *pcbOpt unchanged.
 *
 * @param   uOption         The option to search for.
 * @param   pDhcpMsg        The DHCP message.
 *                          that this is adjusted if the option length is larger
 *                          than the message buffer.
 */
int
ConfigurationManager::findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& opt)
{
    Assert(uOption != RTNET_DHCP_OPT_PAD);

    /*
     * Validate the DHCP bits and figure the max size of the options in the vendor field.
     */
    if (cbDhcpMsg <= RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts))
        return VERR_INVALID_PARAMETER;

    if (pDhcpMsg->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE))
        return VERR_INVALID_PARAMETER;

    size_t cbLeft = cbDhcpMsg - RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts);
    if (cbLeft > RTNET_DHCP_OPT_SIZE)
        cbLeft = RTNET_DHCP_OPT_SIZE;

    /*
     * Search the vendor field.
     */
    bool            fExtended = false;
    uint8_t const  *pb = &pDhcpMsg->bp_vend.Dhcp.dhcp_opts[0];
    while (pb && cbLeft > 0)
    {
        uint8_t uCur  = *pb;
        if (uCur == RTNET_DHCP_OPT_PAD)
        {
            cbLeft--;
            pb++;
        }
        else if (cbLeft <= 1)
            break;
        else
        {
            size_t cbCur = pb[1];
            if (cbCur > cbLeft - 2)
                cbCur = cbLeft - 2;
            if (uCur == uOption)
            {
                opt.u8OptId = uCur;
                memcpy(opt.au8RawOpt, pb+2, cbCur);
                opt.cbRawOpt = cbCur;
                return VINF_SUCCESS;
            }
            pb     += cbCur + 2;
            cbLeft -= cbCur - 2;
        }
    }

    /** @todo search extended dhcp option field(s) when present */

    return VERR_NOT_FOUND;
}


/**
 * We bind lease for client till it continue with it on DHCPREQUEST.
 */
Lease ConfigurationManager::allocateLease4Client(const Client& client, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg)
{
    {
        /**
         * This mean that client has already bound or commited lease.
         * If we've it happens it means that we received DHCPDISCOVER twice.
         */
        const Lease l = client.lease();
        if (l != Lease::NullLease)
        {
            /* Here we should take lease from the m_allocation which was feed with leases
             *  on start
             */
            if (l.isExpired())
            {
                expireLease4Client(const_cast<Client&>(client));
                if (!l.isExpired())
                    return l;
            }
            else
            {
                AssertReturn(l.getAddress().u != 0, Lease::NullLease);
                return l;
            }
        }
    }

    RTNETADDRIPV4 hintAddress;
    RawOption opt;
    NetworkConfigEntity *pNetCfg;

    Client cl(client);
    AssertReturn(g_RootConfig->match(cl, (BaseConfigEntity **)&pNetCfg) > 0, Lease::NullLease);

    /* DHCPDISCOVER MAY contain request address */
    hintAddress.u = 0;
    int rc = findOption(RTNET_DHCP_OPT_REQ_ADDR, pDhcpMsg, cbDhcpMsg, opt);
    if (RT_SUCCESS(rc))
    {
        hintAddress.u = *(uint32_t *)opt.au8RawOpt;
        if (   RT_H2N_U32(hintAddress.u) < RT_H2N_U32(pNetCfg->lowerIp().u)
            || RT_H2N_U32(hintAddress.u) > RT_H2N_U32(pNetCfg->upperIp().u))
            hintAddress.u = 0; /* clear hint */
    }

    if (   hintAddress.u
        && !isAddressTaken(hintAddress))
    {
        Lease l(cl);
        l.setConfig(pNetCfg);
        l.setAddress(hintAddress);
        m->m_allocations.insert(MapLease2Ip4AddressPair(l, hintAddress));
        return l;
    }

    uint32_t u32 = 0;
    for(u32 = RT_H2N_U32(pNetCfg->lowerIp().u);
        u32 <= RT_H2N_U32(pNetCfg->upperIp().u);
        ++u32)
    {
        RTNETADDRIPV4 address;
        address.u = RT_H2N_U32(u32);
        if (!isAddressTaken(address))
        {
            Lease l(cl);
            l.setConfig(pNetCfg);
            l.setAddress(address);
            m->m_allocations.insert(MapLease2Ip4AddressPair(l, address));
            return l;
        }
    }

    return Lease::NullLease;
}


int ConfigurationManager::commitLease4Client(Client& client)
{
    Lease l = client.lease();
    AssertReturn(l != Lease::NullLease, VERR_INTERNAL_ERROR);

    l.bindingPhase(false);
    const NetworkConfigEntity *pCfg = l.getConfig();

    AssertPtr(pCfg);
    l.setExpiration(pCfg->expirationPeriod());
    l.phaseStart(RTTimeMilliTS());

    saveToFile();

    return VINF_SUCCESS;
}


int ConfigurationManager::expireLease4Client(Client& client)
{
    Lease l = client.lease();
    AssertReturn(l != Lease::NullLease, VERR_INTERNAL_ERROR);

    if (l.isInBindingPhase())
    {

        MapLease2Ip4AddressIterator it = m->m_allocations.find(l);
        AssertReturn(it != m->m_allocations.end(), VERR_NOT_FOUND);

        /*
         * XXX: perhaps it better to keep this allocation ????
         */
        m->m_allocations.erase(it);

        l.expire();
        return VINF_SUCCESS;
    }

    l = Lease(client); /* re-new */
    return VINF_SUCCESS;
}


bool ConfigurationManager::isAddressTaken(const RTNETADDRIPV4& addr, Lease& lease)
{
    MapLease2Ip4AddressIterator it;

    for (it = m->m_allocations.begin();
         it != m->m_allocations.end();
         ++it)
    {
        if (it->second.u == addr.u)
        {
            if (lease != Lease::NullLease)
                lease = it->first;

            return true;
        }
    }
    lease = Lease::NullLease;
    return false;
}


bool ConfigurationManager::isAddressTaken(const RTNETADDRIPV4& addr)
{
    Lease ignore;
    return isAddressTaken(addr, ignore);
}


NetworkConfigEntity *ConfigurationManager::addNetwork(NetworkConfigEntity *,
                                    const RTNETADDRIPV4& networkId,
                                    const RTNETADDRIPV4& netmask,
                                    RTNETADDRIPV4& LowerAddress,
                                    RTNETADDRIPV4& UpperAddress)
{
        static int id;
        char name[64];

        RTStrPrintf(name, RT_ELEMENTS(name), "network-%d", id);
        std::string strname(name);
        id++;


        if (!LowerAddress.u)
            LowerAddress = networkId;

        if (!UpperAddress.u)
            UpperAddress.u = networkId.u | (~netmask.u);

        return new NetworkConfigEntity(strname,
                            g_RootConfig,
                            g_AnyClient,
                            5,
                            networkId,
                            netmask,
                            LowerAddress,
                            UpperAddress);
}

HostConfigEntity *ConfigurationManager::addHost(NetworkConfigEntity* pCfg,
                                                const RTNETADDRIPV4& address,
                                                ClientMatchCriteria *criteria)
{
    static int id;
    char name[64];

    RTStrPrintf(name, RT_ELEMENTS(name), "host-%d", id);
    std::string strname(name);
    id++;

    return new HostConfigEntity(address, strname, pCfg, criteria);
}

int ConfigurationManager::addToAddressList(uint8_t u8OptId, RTNETADDRIPV4& address)
{
    switch(u8OptId)
    {
        case RTNET_DHCP_OPT_DNS:
            m->m_nameservers.push_back(address);
            break;
        case RTNET_DHCP_OPT_ROUTERS:
            m->m_routers.push_back(address);
            break;
        default:
            Log(("dhcp-opt: list (%d) unsupported\n", u8OptId));
    }
    return VINF_SUCCESS;
}


int ConfigurationManager::flushAddressList(uint8_t u8OptId)
{
    switch(u8OptId)
    {
        case RTNET_DHCP_OPT_DNS:
            m->m_nameservers.clear();
            break;
        case RTNET_DHCP_OPT_ROUTERS:
            m->m_routers.clear();
            break;
        default:
            Log(("dhcp-opt: list (%d) unsupported\n", u8OptId));
    }
    return VINF_SUCCESS;
}


const Ipv4AddressContainer& ConfigurationManager::getAddressList(uint8_t u8OptId)
{
    switch(u8OptId)
    {
        case RTNET_DHCP_OPT_DNS:
            return m->m_nameservers;

        case RTNET_DHCP_OPT_ROUTERS:
            return m->m_routers;

    }
    /* XXX: Grrr !!! */
    return m_empty;
}


int ConfigurationManager::setString(uint8_t u8OptId, const std::string& str)
{
    switch (u8OptId)
    {
        case RTNET_DHCP_OPT_DOMAIN_NAME:
            m->m_domainName = str;
            break;
        default:
            break;
    }

    return VINF_SUCCESS;
}


const std::string& ConfigurationManager::getString(uint8_t u8OptId)
{
    switch (u8OptId)
    {
        case RTNET_DHCP_OPT_DOMAIN_NAME:
            if (m->m_domainName.length())
                return m->m_domainName;
            else
                return m_noString;
        default:
            break;
    }

    return m_noString;
}


void ConfigurationManager::init()
{
    m = new ConfigurationManager::Data();
}


ConfigurationManager::~ConfigurationManager() { if (m) delete m; }

/**
 * Network manager
 */
struct NetworkManager::Data
{
    Data()
    {
        RT_ZERO(BootPReplyMsg);
        cbBooPReplyMsg = 0;

        m_OurAddress.u = 0;
        m_OurNetmask.u = 0;
        RT_ZERO(m_OurMac);
    }

    union {
        RTNETBOOTP BootPHeader;
        uint8_t au8Storage[1024];
    } BootPReplyMsg;
    int cbBooPReplyMsg;

    RTNETADDRIPV4 m_OurAddress;
    RTNETADDRIPV4 m_OurNetmask;
    RTMAC m_OurMac;
    const VBoxNetHlpUDPService *m_service;
};


NetworkManager::NetworkManager():m(NULL)
{
    m = new NetworkManager::Data();
}


NetworkManager::~NetworkManager()
{
    delete m;
    m = NULL;
}


NetworkManager *NetworkManager::getNetworkManager()
{
    if (!g_NetworkManager)
        g_NetworkManager = new NetworkManager();

    return g_NetworkManager;
}


const RTNETADDRIPV4& NetworkManager::getOurAddress() const
{
    return m->m_OurAddress;
}


const RTNETADDRIPV4& NetworkManager::getOurNetmask() const
{
    return m->m_OurNetmask;
}


const RTMAC& NetworkManager::getOurMac() const
{
    return m->m_OurMac;
}


void NetworkManager::setOurAddress(const RTNETADDRIPV4& aAddress)
{
    m->m_OurAddress = aAddress;
}


void NetworkManager::setOurNetmask(const RTNETADDRIPV4& aNetmask)
{
    m->m_OurNetmask = aNetmask;
}


void NetworkManager::setOurMac(const RTMAC& aMac)
{
    m->m_OurMac = aMac;
}


void NetworkManager::setService(const VBoxNetHlpUDPService *srv)
{
    m->m_service = srv;
}

/**
 * Network manager creates DHCPOFFER datagramm
 */
int NetworkManager::offer4Client(const Client& client, uint32_t u32Xid,
                                 uint8_t *pu8ReqList, int cReqList)
{
    Lease l(client); /* XXX: oh, it looks badly, but now we have lease */
    prepareReplyPacket4Client(client, u32Xid);

    RTNETADDRIPV4 address = l.getAddress();
    m->BootPReplyMsg.BootPHeader.bp_yiaddr =  address;

    /* Ubuntu ???*/
    m->BootPReplyMsg.BootPHeader.bp_ciaddr =  address;

    /* options:
     * - IP lease time
     * - message type
     * - server identifier
     */
    RawOption opt;
    RT_ZERO(opt);

    std::vector<RawOption> extra;
    opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE;
    opt.au8RawOpt[0] = RTNET_DHCP_MT_OFFER;
    opt.cbRawOpt = 1;
    extra.push_back(opt);

    opt.u8OptId = RTNET_DHCP_OPT_LEASE_TIME;

    const NetworkConfigEntity *pCfg = l.getConfig();
    AssertPtr(pCfg);

    *(uint32_t *)opt.au8RawOpt = RT_H2N_U32(pCfg->expirationPeriod());
    opt.cbRawOpt = sizeof(RTNETADDRIPV4);

    extra.push_back(opt);

    processParameterReqList(client, pu8ReqList, cReqList, extra);

    return doReply(client, extra);
}

/**
 * Network manager creates DHCPACK
 */
int NetworkManager::ack(const Client& client, uint32_t u32Xid,
                        uint8_t *pu8ReqList, int cReqList)
{
    RTNETADDRIPV4 address;

    prepareReplyPacket4Client(client, u32Xid);

    Lease l = client.lease();
    address = l.getAddress();
    m->BootPReplyMsg.BootPHeader.bp_ciaddr =  address;


    /* rfc2131 4.3.1 is about DHCPDISCOVER and this value is equal to ciaddr from
     * DHCPREQUEST or 0 ...
     * XXX: Using addressHint is not correct way to initialize [cy]iaddress...
     */
    m->BootPReplyMsg.BootPHeader.bp_ciaddr = address;
    m->BootPReplyMsg.BootPHeader.bp_yiaddr = address;

    Assert(m->BootPReplyMsg.BootPHeader.bp_yiaddr.u);

    /* options:
     * - IP address lease time (if DHCPREQUEST)
     * - message type
     * - server identifier
     */
    RawOption opt;
    RT_ZERO(opt);

    std::vector<RawOption> extra;
    opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE;
    opt.au8RawOpt[0] = RTNET_DHCP_MT_ACK;
    opt.cbRawOpt = 1;
    extra.push_back(opt);

    /*
     * XXX: lease time should be conditional. If on dhcprequest then tim should be provided,
     * else on dhcpinform it mustn't.
     */
    opt.u8OptId = RTNET_DHCP_OPT_LEASE_TIME;
    *(uint32_t *)opt.au8RawOpt = RT_H2N_U32(l.getExpiration());
    opt.cbRawOpt = sizeof(RTNETADDRIPV4);
    extra.push_back(opt);

    processParameterReqList(client, pu8ReqList, cReqList, extra);

    return doReply(client, extra);
}

/**
 * Network manager creates DHCPNAK
 */
int NetworkManager::nak(const Client& client, uint32_t u32Xid)
{

    Lease l = client.lease();
    if (l == Lease::NullLease)
        return VERR_INTERNAL_ERROR;

    prepareReplyPacket4Client(client, u32Xid);

    /* this field filed in prepareReplyPacket4Session, and
     * RFC 2131 require to have it zero fo NAK.
     */
    m->BootPReplyMsg.BootPHeader.bp_yiaddr.u = 0;

    /* options:
     * - message type (if DHCPREQUEST)
     * - server identifier
     */
    RawOption opt;
    std::vector<RawOption> extra;

    opt.u8OptId = RTNET_DHCP_OPT_MSG_TYPE;
    opt.au8RawOpt[0] = RTNET_DHCP_MT_NAC;
    opt.cbRawOpt = 1;
    extra.push_back(opt);

    return doReply(client, extra);
}

/**
 *
 */
int NetworkManager::prepareReplyPacket4Client(const Client& client, uint32_t u32Xid)
{
    RT_ZERO(m->BootPReplyMsg);

    m->BootPReplyMsg.BootPHeader.bp_op     = RTNETBOOTP_OP_REPLY;
    m->BootPReplyMsg.BootPHeader.bp_htype  = RTNET_ARP_ETHER;
    m->BootPReplyMsg.BootPHeader.bp_hlen   = sizeof(RTMAC);
    m->BootPReplyMsg.BootPHeader.bp_hops   = 0;
    m->BootPReplyMsg.BootPHeader.bp_xid    = u32Xid;
    m->BootPReplyMsg.BootPHeader.bp_secs   = 0;
    /* XXX: bp_flags should be processed specially */
    m->BootPReplyMsg.BootPHeader.bp_flags  = 0;
    m->BootPReplyMsg.BootPHeader.bp_ciaddr.u = 0;
    m->BootPReplyMsg.BootPHeader.bp_giaddr.u = 0;

    m->BootPReplyMsg.BootPHeader.bp_chaddr.Mac = client.getMacAddress();

    const Lease l = client.lease();
    m->BootPReplyMsg.BootPHeader.bp_yiaddr = l.getAddress();
    m->BootPReplyMsg.BootPHeader.bp_siaddr.u = 0;


    m->BootPReplyMsg.BootPHeader.bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE);

    memset(&m->BootPReplyMsg.BootPHeader.bp_vend.Dhcp.dhcp_opts[0],
           '\0',
           RTNET_DHCP_OPT_SIZE);

    return VINF_SUCCESS;
}


int NetworkManager::doReply(const Client& client, const std::vector<RawOption>& extra)
{
    int rc;

    /*
      Options....
     */
    VBoxNetDhcpWriteCursor Cursor(&m->BootPReplyMsg.BootPHeader, RTNET_DHCP_NORMAL_SIZE);

    /* The basics */

    Cursor.optIPv4Addr(RTNET_DHCP_OPT_SERVER_ID, m->m_OurAddress);

    const Lease l = client.lease();
    const std::map<uint8_t, RawOption>& options = l.options();

    for(std::vector<RawOption>::const_iterator it = extra.begin();
        it != extra.end(); ++it)
    {
        if (!Cursor.begin(it->u8OptId, it->cbRawOpt))
            break;
        Cursor.put(it->au8RawOpt, it->cbRawOpt);

    }

    for(std::map<uint8_t, RawOption>::const_iterator it = options.begin();
        it != options.end(); ++it)
    {
        if (!Cursor.begin(it->second.u8OptId, it->second.cbRawOpt))
            break;
        Cursor.put(it->second.au8RawOpt, it->second.cbRawOpt);

    }

    Cursor.optEnd();

    /*
     */
#if 0
    /** @todo need to see someone set this flag to check that it's correct. */
    if (!(pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST))
    {
        rc = VBoxNetUDPUnicast(m_pSession,
                               m_hIf,
                               m_pIfBuf,
                               m_OurAddress,
                               &m_OurMac,
                               RTNETIPV4_PORT_BOOTPS,                 /* sender */
                               IPv4AddrBrdCast,
                               &BootPReplyMsg.BootPHeader->bp_chaddr.Mac,
                               RTNETIPV4_PORT_BOOTPC,    /* receiver */
                               &BootPReplyMsg, cbBooPReplyMsg);
    }
    else
#endif
        rc = m->m_service->hlpUDPBroadcast(RTNETIPV4_PORT_BOOTPS,               /* sender */
                                           RTNETIPV4_PORT_BOOTPC,
                                           &m->BootPReplyMsg,
                                           RTNET_DHCP_NORMAL_SIZE);

    AssertRCReturn(rc,rc);

    return VINF_SUCCESS;
}


int NetworkManager::processParameterReqList(const Client& client, const uint8_t *pu8ReqList, 
                                            int cReqList, std::vector<RawOption>& extra)
{
    const Lease l = client.lease();

    const NetworkConfigEntity *pNetCfg = l.getConfig();

    /* request parameter list */
    RawOption opt;
    bool fIgnore;
    uint8_t u8Req;
    for (int idxParam = 0; idxParam < cReqList; ++idxParam)
    {
        fIgnore = false;
        RT_ZERO(opt);
        u8Req = opt.u8OptId = pu8ReqList[idxParam];

        switch(u8Req)
        {
            case RTNET_DHCP_OPT_SUBNET_MASK:
                ((PRTNETADDRIPV4)opt.au8RawOpt)->u = pNetCfg->netmask().u;
                opt.cbRawOpt = sizeof(RTNETADDRIPV4);

                break;

            case RTNET_DHCP_OPT_ROUTERS:
            case RTNET_DHCP_OPT_DNS:
                {
                    const Ipv4AddressContainer lst =
                      g_ConfigurationManager->getAddressList(u8Req);
                    PRTNETADDRIPV4 pAddresses = (PRTNETADDRIPV4)&opt.au8RawOpt[0];

                    for (Ipv4AddressConstIterator it = lst.begin();
                         it != lst.end();
                         ++it)
                    {
                        *pAddresses = (*it);
                        pAddresses++;
                        opt.cbRawOpt += sizeof(RTNETADDRIPV4);
                    }

                    if (lst.empty())
                        fIgnore = true;
                }
                break;
            case RTNET_DHCP_OPT_DOMAIN_NAME:
                {
                    std::string domainName = g_ConfigurationManager->getString(u8Req);
                    if (domainName == g_ConfigurationManager->m_noString)
                    {
                        fIgnore = true;
                        break;
                    }

                    char *pszDomainName = (char *)&opt.au8RawOpt[0];

                    strcpy(pszDomainName, domainName.c_str());
                    opt.cbRawOpt = domainName.length();
                }
                break;
            default:
                Log(("opt: %d is ignored\n", u8Req));
                fIgnore = true;
                break;
        }

        if (!fIgnore)
            extra.push_back(opt);

    }

    return VINF_SUCCESS;
}

/* Client */
Client::Client()
{
    m = SharedPtr<ClientData>();
}


void Client::initWithMac(const RTMAC& mac)
{
    m = SharedPtr<ClientData>(new ClientData());
    m->m_mac = mac;
}


bool Client::operator== (const RTMAC& mac) const
{
    return (m.get() && m->m_mac == mac);
}


const RTMAC& Client::getMacAddress() const
{
    return m->m_mac;
}


Lease Client::lease()
{
    if (!m.get()) return Lease::NullLease;

    if (m->fHasLease)
        return Lease(*this);
    else
        return Lease::NullLease;
}


const Lease Client::lease() const
{
    return const_cast<Client *>(this)->lease();
}


Client::Client(ClientData *data):m(SharedPtr<ClientData>(data)){}

/* Lease */
Lease::Lease()
{
    m = SharedPtr<ClientData>();
}


Lease::Lease (const Client& c)
{
    m = SharedPtr<ClientData>(c.m);
    if (   !m->fHasLease
        || (   isExpired()
            && !isInBindingPhase()))
    {
        m->fHasLease = true;
        m->fBinding = true;
        phaseStart(RTTimeMilliTS());
    }
}


bool Lease::isExpired() const
{
    AssertPtrReturn(m.get(), false);

    if (!m->fBinding)
        return (ASMDivU64ByU32RetU32(RTTimeMilliTS() - m->u64TimestampLeasingStarted, 1000)
                > m->u32LeaseExpirationPeriod);
    else
        return (ASMDivU64ByU32RetU32(RTTimeMilliTS() - m->u64TimestampBindingStarted, 1000)
                > m->u32BindExpirationPeriod);
}


void Lease::expire()
{
    /* XXX: TODO */
}


void Lease::phaseStart(uint64_t u64Start)
{
    if (m->fBinding)
        m->u64TimestampBindingStarted = u64Start;
    else
        m->u64TimestampLeasingStarted = u64Start;
}


void Lease::bindingPhase(bool fOnOff)
{
    m->fBinding = fOnOff;
}


bool Lease::isInBindingPhase() const
{
    return m->fBinding;
}


uint64_t Lease::issued() const
{
    return m->u64TimestampLeasingStarted;
}


void Lease::setExpiration(uint32_t exp)
{
    if (m->fBinding)
        m->u32BindExpirationPeriod = exp;
    else
        m->u32LeaseExpirationPeriod = exp;
}


uint32_t Lease::getExpiration() const
{
    if (m->fBinding)
        return m->u32BindExpirationPeriod;
    else
        return m->u32LeaseExpirationPeriod;
}


RTNETADDRIPV4 Lease::getAddress() const
{
    return m->m_address;
}


void Lease::setAddress(RTNETADDRIPV4 address)
{
    m->m_address = address;
}


const NetworkConfigEntity *Lease::getConfig() const
{
    return m->pCfg;
}


void Lease::setConfig(NetworkConfigEntity *pCfg)
{
    m->pCfg = pCfg;
}


const MapOptionId2RawOption& Lease::options() const
{
    return m->options;
}


Lease::Lease(ClientData *pd):m(SharedPtr<ClientData>(pd)){}


bool Lease::toXML(xml::ElementNode *node) const
{
    bool valueAddition = node->setAttribute(tagXMLLeaseAttributeMac.c_str(), com::Utf8StrFmt("%RTmac", &m->m_mac));
    if (!valueAddition) return false;

    valueAddition = node->setAttribute(tagXMLLeaseAttributeNetwork.c_str(), com::Utf8StrFmt("%RTnaipv4", m->m_network));
    if (!valueAddition) return false;

    xml::ElementNode *address = node->createChild(tagXMLLeaseAddress.c_str());
    if (!address) return false;

    valueAddition = address->setAttribute(tagXMLAddressAttributeValue.c_str(), com::Utf8StrFmt("%RTnaipv4", m->m_address));
    if (!valueAddition) return false;

    xml::ElementNode *time = node->createChild(tagXMLLeaseTime.c_str());
    if (!time) return false;

    valueAddition = time->setAttribute(tagXMLTimeAttributeIssued.c_str(),
                                       m->u64TimestampLeasingStarted);
    if (!valueAddition) return false;

    valueAddition = time->setAttribute(tagXMLTimeAttributeExpiration.c_str(),
                                       m->u32LeaseExpirationPeriod);
    if (!valueAddition) return false;

    return true;
}


bool Lease::fromXML(const xml::ElementNode *node)
{
    com::Utf8Str mac;
    bool valueExists = node->getAttributeValue(tagXMLLeaseAttributeMac.c_str(), mac);
    if (!valueExists) return false;
    int rc = RTNetStrToMacAddr(mac.c_str(), &m->m_mac);
    if (RT_FAILURE(rc)) return false;

    com::Utf8Str network;
    valueExists = node->getAttributeValue(tagXMLLeaseAttributeNetwork.c_str(), network);
    if (!valueExists) return false;
    rc = RTNetStrToIPv4Addr(network.c_str(), &m->m_network);
    if (RT_FAILURE(rc)) return false;

    /* Address */
    const xml::ElementNode *address = node->findChildElement(tagXMLLeaseAddress.c_str());
    if (!address) return false;
    com::Utf8Str addressValue;
    valueExists = address->getAttributeValue(tagXMLAddressAttributeValue.c_str(), addressValue);
    if (!valueExists) return false;
    rc = RTNetStrToIPv4Addr(addressValue.c_str(), &m->m_address);

    /* Time */
    const xml::ElementNode *time = node->findChildElement(tagXMLLeaseTime.c_str());
    if (!time) return false;

    valueExists = time->getAttributeValue(tagXMLTimeAttributeIssued.c_str(),
                                          &m->u64TimestampLeasingStarted);
    if (!valueExists) return false;
    m->fBinding = false;

    valueExists = time->getAttributeValue(tagXMLTimeAttributeExpiration.c_str(),
                                          &m->u32LeaseExpirationPeriod);
    if (!valueExists) return false;

    m->fHasLease = true;
    return true;
}


const Lease Lease::NullLease;

const Client Client::NullClient;
