//
// Copyright (C) 2008 Juan-Carlos Maureira
// Copyright (C) INRIA
// Copyright (C) 2013 OpenSim Ltd.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//

#include "inet/applications/dhcp/DHCPClient.h"

#include "inet/networklayer/ipv4/IPv4InterfaceData.h"
#include "inet/common/lifecycle/NodeStatus.h"
#include "inet/common/NotifierConsts.h"
#include "inet/common/lifecycle/NodeOperations.h"

namespace inet {

Define_Module(DHCPClient);

DHCPClient::~DHCPClient()
{
    cancelAndDelete(timerT1);
    cancelAndDelete(timerTo);
    cancelAndDelete(timerT2);
    cancelAndDelete(leaseTimer);
    cancelAndDelete(startTimer);
    delete lease;
}

void DHCPClient::initialize(int stage)
{
    if (stage == INITSTAGE_LOCAL) {
        timerT1 = new cMessage("T1 Timer", T1);
        timerT2 = new cMessage("T2 Timer", T2);
        timerTo = new cMessage("DHCP Timeout");
        leaseTimer = new cMessage("Lease Timeout", LEASE_TIMEOUT);
        startTimer = new cMessage("Starting DHCP", START_DHCP);
        startTime = par("startTime");
    }
    else if (stage == INITSTAGE_APPLICATION_LAYER) {
        NodeStatus *nodeStatus = dynamic_cast<NodeStatus *>(findContainingNode(this)->getSubmodule("status"));
        isOperational = (!nodeStatus) || nodeStatus->getState() == NodeStatus::UP;

        numSent = 0;
        numReceived = 0;
        xid = 0;
        responseTimeout = 60;    // response timeout in seconds RFC 2131, 4.4.3

        WATCH(numSent);
        WATCH(numReceived);
        WATCH(clientState);
        WATCH(xid);

        // DHCP UDP ports
        clientPort = 68;    // client
        serverPort = 67;    // server

        // get the hostname
        host = getContainingNode(this);

        // for a wireless interface subscribe the association event to start the DHCP protocol
        host->subscribe(NF_L2_ASSOCIATED, this);
        host->subscribe(NF_INTERFACE_DELETED, this);

        // get the routing table to update and subscribe it to the blackboard
        irt = getModuleFromPar<IIPv4RoutingTable>(par("routingTableModule"), this);
        // set client to idle state
        clientState = IDLE;
        // get the interface to configure

        if (isOperational) {
            ie = chooseInterface();
            // grab the interface MAC address
            macAddress = ie->getMacAddress();
            startApp();
        }
    }
}

InterfaceEntry *DHCPClient::chooseInterface()
{
    IInterfaceTable *ift = getModuleFromPar<IInterfaceTable>(par("interfaceTableModule"), this);
    const char *interfaceName = par("interface");
    InterfaceEntry *ie = nullptr;

    if (strlen(interfaceName) > 0) {
        ie = ift->getInterfaceByName(interfaceName);
        if (ie == nullptr)
            throw cRuntimeError("Interface \"%s\" does not exist", interfaceName);
    }
    else {
        // there should be exactly one non-loopback interface that we want to configure
        for (int i = 0; i < ift->getNumInterfaces(); i++) {
            InterfaceEntry *current = ift->getInterface(i);
            if (!current->isLoopback()) {
                if (ie)
                    throw cRuntimeError("Multiple non-loopback interfaces found, please select explicitly which one you want to configure via DHCP");
                ie = current;
            }
        }
        if (!ie)
            throw cRuntimeError("No non-loopback interface found to be configured via DHCP");
    }

    if (!ie->ipv4Data()->getIPAddress().isUnspecified())
        throw cRuntimeError("Refusing to start DHCP on interface \"%s\" that already has an IP address", ie->getName());
    return ie;
}

void DHCPClient::finish()
{
    cancelEvent(timerT1);
    cancelEvent(timerTo);
    cancelEvent(timerT2);
    cancelEvent(leaseTimer);
    cancelEvent(startTimer);
}

namespace {
static bool routeMatches(const IPv4Route *entry, const IPv4Address& target, const IPv4Address& nmask,
        const IPv4Address& gw, int metric, const char *dev)
{
    if (!target.equals(entry->getDestination()))
        return false;
    if (!nmask.equals(entry->getNetmask()))
        return false;
    if (!gw.equals(entry->getGateway()))
        return false;
    if (metric && metric != entry->getMetric())
        return false;
    if (strcmp(dev, entry->getInterfaceName()))
        return false;

    return true;
}
}

const char *DHCPClient::getStateName(ClientState state)
{
    switch (state) {
#define CASE(X)    case X: \
        return #X;
        CASE(INIT);
        CASE(INIT_REBOOT);
        CASE(REBOOTING);
        CASE(SELECTING);
        CASE(REQUESTING);
        CASE(BOUND);
        CASE(RENEWING);
        CASE(REBINDING);

        default:
            return "???";
#undef CASE
    }
}

const char *DHCPClient::getAndCheckMessageTypeName(DHCPMessageType type)
{
    switch (type) {
#define CASE(X)    case X: \
        return #X;
        CASE(DHCPDISCOVER);
        CASE(DHCPOFFER);
        CASE(DHCPREQUEST);
        CASE(DHCPDECLINE);
        CASE(DHCPACK);
        CASE(DHCPNAK);
        CASE(DHCPRELEASE);
        CASE(DHCPINFORM);

        default:
            throw cRuntimeError("Unknown or invalid DHCP message type %d", type);
#undef CASE
    }
}

void DHCPClient::refreshDisplay() const
{
    getDisplayString().setTagArg("t", 0, getStateName(clientState));
}

void DHCPClient::handleMessage(cMessage *msg)
{
    if (!isOperational) {
        EV_ERROR << "Message '" << msg << "' arrived when module status is down, dropping." << endl;
        delete msg;
        return;
    }
    if (msg->isSelfMessage()) {
        handleTimer(msg);
    }
    else if (msg->arrivedOn("udpIn")) {
        DHCPMessage *dhcpPacket = dynamic_cast<DHCPMessage *>(msg);
        if (!dhcpPacket)
            throw cRuntimeError(dhcpPacket, "Unexpected packet received (not a DHCPMessage)");

        handleDHCPMessage(dhcpPacket);
        delete msg;
    }
    else
        throw cRuntimeError("Unknown incoming gate: '%s'", msg->getArrivalGate()->getFullName());
}

void DHCPClient::handleTimer(cMessage *msg)
{
    int category = msg->getKind();

    if (category == START_DHCP) {
        openSocket();
        if (lease) {
            clientState = INIT_REBOOT;
            initRebootedClient();
        }
        else {    // we have no lease from the previous DHCP process
            clientState = INIT;
            initClient();
        }
    }
    else if (category == WAIT_OFFER) {
        EV_DETAIL << "No DHCP offer received within timeout. Restarting. " << endl;
        initClient();
    }
    else if (category == WAIT_ACK) {
        EV_DETAIL << "No DHCP ACK received within timeout. Restarting." << endl;
        initClient();
    }
    else if (category == T1) {
        EV_DETAIL << "T1 expired. Starting RENEWING state." << endl;
        clientState = RENEWING;
        scheduleTimerTO(WAIT_ACK);
        sendRequest();
    }
    else if (category == T2 && clientState == RENEWING) {
        EV_DETAIL << "T2 expired. Starting REBINDING state." << endl;
        clientState = REBINDING;
        cancelEvent(timerT1);
        cancelEvent(timerT2);
        cancelEvent(timerTo);
        cancelEvent(leaseTimer);

        sendRequest();
        scheduleTimerTO(WAIT_ACK);
    }
    else if (category == T2) {
        // T1 < T2 always holds by definition and when T1 expires client will move to RENEWING
        throw cRuntimeError("T2 occurred in wrong state. (T1 must be earlier than T2.)");
    }
    else if (category == LEASE_TIMEOUT) {
        EV_INFO << "Lease has expired. Starting DHCP process in INIT state." << endl;
        unbindLease();
        clientState = INIT;
        initClient();
    }
    else
        throw cRuntimeError("Unknown self message '%s'", msg->getName());
}

void DHCPClient::recordOffer(DHCPMessage *dhcpOffer)
{
    if (!dhcpOffer->getYiaddr().isUnspecified()) {
        IPv4Address ip = dhcpOffer->getYiaddr();

        //Byte serverIdB = dhcpOffer->getOptions().get(SERVER_ID);
        IPv4Address serverId = dhcpOffer->getOptions().getServerIdentifier();

        // minimal information to configure the interface
        // create the lease to request
        delete lease;
        lease = new DHCPLease();
        lease->ip = ip;
        lease->mac = macAddress;
        lease->serverId = serverId;
    }
    else
        EV_WARN << "DHCPOFFER arrived, but no IP address has been offered. Discarding it and remaining in SELECTING." << endl;
}

void DHCPClient::recordLease(DHCPMessage *dhcpACK)
{
    if (!dhcpACK->getYiaddr().isUnspecified()) {
        IPv4Address ip = dhcpACK->getYiaddr();
        EV_DETAIL << "DHCPACK arrived with " << "IP: " << ip << endl;

        lease->subnetMask = dhcpACK->getOptions().getSubnetMask();

        if (dhcpACK->getOptions().getRouterArraySize() > 0)
            lease->gateway = dhcpACK->getOptions().getRouter(0);
        if (dhcpACK->getOptions().getDnsArraySize() > 0)
            lease->dns = dhcpACK->getOptions().getDns(0);
        if (dhcpACK->getOptions().getNtpArraySize() > 0)
            lease->ntp = dhcpACK->getOptions().getNtp(0);

        lease->leaseTime = dhcpACK->getOptions().getLeaseTime();
        lease->renewalTime = dhcpACK->getOptions().getRenewalTime();
        lease->rebindTime = dhcpACK->getOptions().getRebindingTime();

        // std::cout << lease->leaseTime << " " << lease->renewalTime << " " << lease->rebindTime << endl;
    }
    else
        EV_ERROR << "DHCPACK arrived, but no IP address confirmed." << endl;
}

void DHCPClient::bindLease()
{
    ie->ipv4Data()->setIPAddress(lease->ip);
    ie->ipv4Data()->setNetmask(lease->subnetMask);

    std::string banner = "Got IP " + lease->ip.str();
    host->bubble(banner.c_str());

    /*
        The client SHOULD perform a final check on the parameters (ping, ARP).
        If the client detects that the address is already in use:
        EV_INFO << "The offered IP " << lease->ip << " is not available." << endl;
        sendDecline(lease->ip);
        initClient();
     */

    EV_INFO << "The requested IP " << lease->ip << "/" << lease->subnetMask << " is available. Assigning it to "
            << host->getFullName() << "." << endl;

    IPv4Route *iroute = nullptr;
    for (int i = 0; i < irt->getNumRoutes(); i++) {
        IPv4Route *e = irt->getRoute(i);
        if (routeMatches(e, IPv4Address(), IPv4Address(), lease->gateway, 0, ie->getName())) {
            iroute = e;
            break;
        }
    }
    if (iroute == nullptr) {
        // create gateway route
        route = new IPv4Route();
        route->setDestination(IPv4Address());
        route->setNetmask(IPv4Address());
        route->setGateway(lease->gateway);
        route->setInterface(ie);
        route->setSourceType(IPv4Route::MANUAL);
        irt->addRoute(route);
    }

    // update the routing table
    cancelEvent(leaseTimer);
    scheduleAt(simTime() + lease->leaseTime, leaseTimer);
}

void DHCPClient::unbindLease()
{
    EV_INFO << "Unbinding lease on " << ie->getName() << "." << endl;

    cancelEvent(timerT1);
    cancelEvent(timerT2);
    cancelEvent(timerTo);
    cancelEvent(leaseTimer);

    irt->deleteRoute(route);
    ie->ipv4Data()->setIPAddress(IPv4Address());
    ie->ipv4Data()->setNetmask(IPv4Address::ALLONES_ADDRESS);
}

void DHCPClient::initClient()
{
    EV_INFO << "Starting DHCP configuration process." << endl;

    cancelEvent(timerT1);
    cancelEvent(timerT2);
    cancelEvent(timerTo);
    cancelEvent(leaseTimer);

    sendDiscover();
    scheduleTimerTO(WAIT_OFFER);
    clientState = SELECTING;
}

void DHCPClient::initRebootedClient()
{
    sendRequest();
    scheduleTimerTO(WAIT_ACK);
    clientState = REBOOTING;
}

void DHCPClient::handleDHCPMessage(DHCPMessage *msg)
{
    ASSERT(isOperational && ie != nullptr);

    if (msg->getOp() != BOOTREPLY) {
        EV_WARN << "Client received a non-BOOTREPLY message, dropping." << endl;
        return;
    }

    if (msg->getXid() != xid) {
        EV_WARN << "Message transaction ID is not valid, dropping." << endl;
        return;
    }

    DHCPMessageType messageType = (DHCPMessageType)msg->getOptions().getMessageType();
    switch (clientState) {
        case INIT:
            EV_WARN << getAndCheckMessageTypeName(messageType) << " message arrived in INIT state. In this state, client does not wait for any message at all, dropping." << endl;
            break;

        case SELECTING:
            if (messageType == DHCPOFFER) {
                EV_INFO << "DHCPOFFER message arrived in SELECTING state with IP address: " << msg->getYiaddr() << "." << endl;
                scheduleTimerTO(WAIT_ACK);
                clientState = REQUESTING;
                recordOffer(msg);
                sendRequest();    // we accept the first offer
            }
            else
                EV_WARN << "Client is in SELECTING and the arriving packet is not a DHCPOFFER, dropping." << endl;
            break;

        case REQUESTING:
            if (messageType == DHCPOFFER) {
                EV_WARN << "We don't accept DHCPOFFERs in REQUESTING state, dropping." << endl;    // remains in REQUESTING
            }
            else if (messageType == DHCPACK) {
                EV_INFO << "DHCPACK message arrived in REQUESTING state. The requested IP address is available in the server's pool of addresses." << endl;
                handleDHCPACK(msg);
                clientState = BOUND;
            }
            else if (messageType == DHCPNAK) {
                EV_INFO << "DHCPNAK message arrived in REQUESTING state. Restarting the configuration process." << endl;
                initClient();
            }
            else {
                EV_WARN << getAndCheckMessageTypeName(messageType) << " message arrived in REQUESTING state. In this state, client does not expect messages of this type, dropping." << endl;
            }
            break;

        case BOUND:
            EV_DETAIL << "We are in BOUND, discard all DHCP messages." << endl;    // remain in BOUND
            break;

        case RENEWING:
            if (messageType == DHCPACK) {
                handleDHCPACK(msg);
                EV_INFO << "DHCPACK message arrived in RENEWING state. The renewing process was successful." << endl;
                clientState = BOUND;
            }
            else if (messageType == DHCPNAK) {
                EV_INFO << "DHPCNAK message arrived in RENEWING state. The renewing process was unsuccessful. Restarting the DHCP configuration process." << endl;
                unbindLease();
                initClient();
            }
            else {
                EV_WARN << getAndCheckMessageTypeName(messageType) << " message arrived in RENEWING state. In this state, client does not expect messages of this type, dropping." << endl;
            }
            break;

        case REBINDING:
            if (messageType == DHCPNAK) {
                EV_INFO << "DHPCNAK message arrived in REBINDING state. The rebinding process was unsuccessful. Restarting the DHCP configuration process." << endl;
                unbindLease();
                initClient();
            }
            else if (messageType == DHCPACK) {
                handleDHCPACK(msg);
                EV_INFO << "DHCPACK message arrived in REBINDING state. The rebinding process was successful." << endl;
                clientState = BOUND;
            }
            else {
                EV_WARN << getAndCheckMessageTypeName(messageType) << " message arrived in REBINDING state. In this state, client does not expect messages of this type, dropping." << endl;
            }
            break;

        case REBOOTING:
            if (messageType == DHCPACK) {
                handleDHCPACK(msg);
                EV_INFO << "DHCPACK message arrived in REBOOTING state. Initialization with known IP address was successful." << endl;
                clientState = BOUND;
            }
            else if (messageType == DHCPNAK) {
                EV_INFO << "DHCPNAK message arrived in REBOOTING. Initialization with known IP address was unsuccessful." << endl;
                unbindLease();
                initClient();
            }
            else {
                EV_WARN << getAndCheckMessageTypeName(messageType) << " message arrived in REBOOTING state. In this state, client does not expect messages of this type, dropping." << endl;
            }
            break;

        default:
            throw cRuntimeError("Unknown or invalid client state %d", clientState);
    }
}

void DHCPClient::receiveSignal(cComponent *source, int signalID, cObject *obj, cObject *details)
{
    Enter_Method_Silent();
    printNotificationBanner(signalID, obj);

    // host associated. link is up. change the state to init.
    if (signalID == NF_L2_ASSOCIATED) {
        InterfaceEntry *associatedIE = check_and_cast_nullable<InterfaceEntry *>(obj);
        if (associatedIE && ie == associatedIE) {
            EV_INFO << "Interface associated, starting DHCP." << endl;
            unbindLease();
            initClient();
        }
    }
    else if (signalID == NF_INTERFACE_DELETED) {
        if (isOperational)
            throw cRuntimeError("Reacting to interface deletions is not implemented in this module");
    }
}

void DHCPClient::sendRequest()
{
    // setting the xid
    xid = intuniform(0, RAND_MAX);    // generating a new xid for each transmission

    DHCPMessage *request = new DHCPMessage("DHCPREQUEST");
    request->setOp(BOOTREQUEST);
    request->setByteLength(280);    // DHCP request packet size
    request->setHtype(1);    // ethernet
    request->setHlen(6);    // hardware Address length (6 octets)
    request->setHops(0);
    request->setXid(xid);    // transaction id
    request->setSecs(0);    // 0 seconds from transaction started
    request->setBroadcast(false);    // unicast
    request->setYiaddr(IPv4Address());    // no 'your IP' addr
    request->setGiaddr(IPv4Address());    // no DHCP Gateway Agents
    request->setChaddr(macAddress);    // my mac address;
    request->setSname("");    // no server name given
    request->setFile("");    // no file given
    request->getOptions().setMessageType(DHCPREQUEST);
    request->getOptions().setClientIdentifier(macAddress);

    // set the parameters to request
    request->getOptions().setParameterRequestListArraySize(4);
    request->getOptions().setParameterRequestList(0, SUBNET_MASK);
    request->getOptions().setParameterRequestList(1, ROUTER);
    request->getOptions().setParameterRequestList(2, DNS);
    request->getOptions().setParameterRequestList(3, NTP_SRV);

    // RFC 4.3.6 Table 4
    if (clientState == INIT_REBOOT) {
        request->getOptions().setRequestedIp(lease->ip);
        request->setCiaddr(IPv4Address());    // zero
        EV_INFO << "Sending DHCPREQUEST asking for IP " << lease->ip << " via broadcast." << endl;
        sendToUDP(request, clientPort, IPv4Address::ALLONES_ADDRESS, serverPort);
    }
    else if (clientState == REQUESTING) {
        request->getOptions().setServerIdentifier(lease->serverId);
        request->getOptions().setRequestedIp(lease->ip);
        request->setCiaddr(IPv4Address());    // zero
        EV_INFO << "Sending DHCPREQUEST asking for IP " << lease->ip << " via broadcast." << endl;
        sendToUDP(request, clientPort, IPv4Address::ALLONES_ADDRESS, serverPort);
    }
    else if (clientState == RENEWING) {
        request->setCiaddr(lease->ip);    // the client IP
        EV_INFO << "Sending DHCPREQUEST extending lease for IP " << lease->ip << " via unicast to " << lease->serverId << "." << endl;
        sendToUDP(request, clientPort, lease->serverId, serverPort);
    }
    else if (clientState == REBINDING) {
        request->setCiaddr(lease->ip);    // the client IP
        EV_INFO << "Sending DHCPREQUEST renewing the IP " << lease->ip << " via broadcast." << endl;
        sendToUDP(request, clientPort, IPv4Address::ALLONES_ADDRESS, serverPort);
    }
    else
        throw cRuntimeError("Invalid state");
}

void DHCPClient::sendDiscover()
{
    // setting the xid
    xid = intuniform(0, RAND_MAX);
    //std::cout << xid << endl;
    DHCPMessage *discover = new DHCPMessage("DHCPDISCOVER");
    discover->setOp(BOOTREQUEST);
    discover->setByteLength(280);    // DHCP Discover packet size;
    discover->setHtype(1);    // ethernet
    discover->setHlen(6);    // hardware Address lenght (6 octets)
    discover->setHops(0);
    discover->setXid(xid);    // transaction id
    discover->setSecs(0);    // 0 seconds from transaction started
    discover->setBroadcast(false);    // unicast
    discover->setChaddr(macAddress);    // my mac address
    discover->setSname("");    // no server name given
    discover->setFile("");    // no file given
    discover->getOptions().setMessageType(DHCPDISCOVER);
    discover->getOptions().setClientIdentifier(macAddress);
    discover->getOptions().setRequestedIp(IPv4Address());

    // set the parameters to request
    discover->getOptions().setParameterRequestListArraySize(4);
    discover->getOptions().setParameterRequestList(0, SUBNET_MASK);
    discover->getOptions().setParameterRequestList(1, ROUTER);
    discover->getOptions().setParameterRequestList(2, DNS);
    discover->getOptions().setParameterRequestList(3, NTP_SRV);

    EV_INFO << "Sending DHCPDISCOVER." << endl;
    sendToUDP(discover, clientPort, IPv4Address::ALLONES_ADDRESS, serverPort);
}

void DHCPClient::sendDecline(IPv4Address declinedIp)
{
    xid = intuniform(0, RAND_MAX);
    DHCPMessage *decline = new DHCPMessage("DHCPDECLINE");
    decline->setOp(BOOTREQUEST);
    decline->setByteLength(280);    // DHCPDECLINE packet size
    decline->setHtype(1);    // ethernet
    decline->setHlen(6);    // hardware Address length (6 octets)
    decline->setHops(0);
    decline->setXid(xid);    // transaction id
    decline->setSecs(0);    // 0 seconds from transaction started
    decline->setBroadcast(false);    // unicast
    decline->setChaddr(macAddress);    // my MAC address
    decline->setSname("");    // no server name given
    decline->setFile("");    // no file given
    decline->getOptions().setMessageType(DHCPDECLINE);
    decline->getOptions().setRequestedIp(declinedIp);
    EV_INFO << "Sending DHCPDECLINE." << endl;
    sendToUDP(decline, clientPort, IPv4Address::ALLONES_ADDRESS, serverPort);
}

void DHCPClient::handleDHCPACK(DHCPMessage *msg)
{
    recordLease(msg);
    cancelEvent(timerTo);
    scheduleTimerT1();
    scheduleTimerT2();
    bindLease();
}

void DHCPClient::scheduleTimerTO(TimerType type)
{
    // cancel the previous timeout
    cancelEvent(timerTo);
    timerTo->setKind(type);
    scheduleAt(simTime() + responseTimeout, timerTo);
}

void DHCPClient::scheduleTimerT1()
{
    // cancel the previous T1
    cancelEvent(timerT1);
    scheduleAt(simTime() + (lease->renewalTime), timerT1);    // RFC 2131 4.4.5
}

void DHCPClient::scheduleTimerT2()
{
    // cancel the previous T2
    cancelEvent(timerT2);
    scheduleAt(simTime() + (lease->rebindTime), timerT2);    // RFC 2131 4.4.5
}

void DHCPClient::sendToUDP(cPacket *msg, int srcPort, const L3Address& destAddr, int destPort)
{
    EV_INFO << "Sending packet " << msg << endl;
    UDPSocket::SendOptions options;
    options.outInterfaceId = ie->getInterfaceId();
    socket.sendTo(msg, destAddr, destPort, &options);
}

void DHCPClient::openSocket()
{
    socket.setOutputGate(gate("udpOut"));
    socket.bind(clientPort);
    socket.setBroadcast(true);
    EV_INFO << "DHCP server bound to port " << serverPort << "." << endl;
}

void DHCPClient::startApp()
{
    simtime_t start = std::max(startTime, simTime());
    ie = chooseInterface();
    scheduleAt(start, startTimer);
}

void DHCPClient::stopApp()
{
    cancelEvent(timerT1);
    cancelEvent(timerT2);
    cancelEvent(timerTo);
    cancelEvent(leaseTimer);
    cancelEvent(startTimer);

    // TODO: Client should send DHCPRELEASE to the server. However, the correct operation
    // of DHCP does not depend on the transmission of DHCPRELEASE messages.
    // TODO: socket.close();
}

bool DHCPClient::handleOperationStage(LifecycleOperation *operation, int stage, IDoneCallback *doneCallback)
{
    Enter_Method_Silent();
    if (dynamic_cast<NodeStartOperation *>(operation)) {
        if ((NodeStartOperation::Stage)stage == NodeStartOperation::STAGE_APPLICATION_LAYER) {
            startApp();
            isOperational = true;
        }
    }
    else if (dynamic_cast<NodeShutdownOperation *>(operation)) {
        if ((NodeShutdownOperation::Stage)stage == NodeShutdownOperation::STAGE_APPLICATION_LAYER) {
            stopApp();
            isOperational = false;
            ie = nullptr;
        }
    }
    else if (dynamic_cast<NodeCrashOperation *>(operation)) {
        if ((NodeCrashOperation::Stage)stage == NodeCrashOperation::STAGE_CRASH) {
            stopApp();
            isOperational = false;
            ie = nullptr;
        }
    }
    else
        throw cRuntimeError("Unsupported lifecycle operation '%s'", operation->getClassName());
    return true;
}

} // namespace inet

