<commit_before>/*
 * Copyright (c) 2013 <NAME>, Inc. All rights reserved.
 */

#include "bgp/state_machine.h"

#include <boost/assign.hpp>
#include <boost/asio.hpp>
#include <boost/foreach.hpp>

#include "base/logging.h"
#include "base/test/task_test_util.h"
#include "net/bgp_af.h"
#include "bgp/bgp_config.h"
#include "bgp/bgp_config_parser.h"
#include "bgp/bgp_factory.h"
#include "bgp/bgp_peer.h"
#include "bgp/bgp_server.h"
#include "bgp/bgp_session.h"
#include "bgp/bgp_session_manager.h"
#include "bgp/bgp_proto.h"
#include "control-node/control_node.h"
#include "io/event_manager.h"
#include "testing/gunit.h"

#include "bgp_message_test.h"
#include "bgp_server_test_util.h"

using namespace std;
using namespace boost::assign;
using namespace boost::posix_time;
using boost::system::error_code;

class BgpSessionMock : public BgpSession {
public:
    enum State {
        CONNECT,
        ESTABLISHED,
        CLOSE,
    };

    enum Direction {
        ACTIVE,
        PASSIVE,
    };

    BgpSessionMock(BgpSessionManager *session, State state, Direction direction)
        : BgpSession(session, NULL), state_(state), direction_(direction) {
    }

    ~BgpSessionMock() {
        BGP_DEBUG_UT("Session delete");
    }

    virtual bool Send(const u_int8_t *data, size_t size, size_t *sent) {
        return true;
    }

    virtual bool Connected(Endpoint remote) {
        state_ = BgpSessionMock::ESTABLISHED;
        EventObserver obs = observer();
        if (obs != NULL) {
            obs(this, CONNECT_COMPLETE);
        }
        return true;
    }

    void Close() {
        state_ = BgpSessionMock::CLOSE;
        BgpSession::Close();
    }

    State state() { return state_; }
    Direction direction() { return direction_; }

private:
    State state_;
    Direction direction_;
};

class BgpSessionManagerMock : public BgpSessionManager {
public:
    BgpSessionManagerMock(EventManager *evm, BgpServer *server)
        : BgpSessionManager(evm, server), active_session_(NULL),
          passive_session_(NULL), duplicate_session_(NULL),
          create_session_fail_(false) {
    }

    const bool create_session_fail() const { return create_session_fail_; }
    void set_create_session_fail(bool flag) { create_session_fail_ = flag; }

    virtual TcpSession *CreateSession() {
        assert(!active_session_);

        // Simulate CreateSession() API failure, such as when the process runs
        // out of file descriptors.
        if (create_session_fail_) return NULL;

        active_session_ =
            new BgpSessionMock(this, BgpSessionMock::CONNECT,
                BgpSessionMock::ACTIVE);
        return active_session_;
    }

    BgpSessionMock *CreatePassiveSession() {
        assert(!passive_session_);
        passive_session_ =
            new BgpSessionMock(this, BgpSessionMock::ESTABLISHED,
                BgpSessionMock::PASSIVE);
        return passive_session_;
    }

    BgpSessionMock *CreateDuplicateSession() {
        assert(passive_session_);
        assert(!duplicate_session_);
        duplicate_session_ =
            new BgpSessionMock(this, BgpSessionMock::ESTABLISHED,
                BgpSessionMock::PASSIVE);
        return duplicate_session_;
    }

    virtual void Connect(TcpSession *session, Endpoint remote) {
    }

    virtual void DeleteSession(TcpSession *session) {
        if (active_session_ == session) {
            active_session_ = NULL;
        } else if (passive_session_ == session) {
            passive_session_ = NULL;
        } else if (duplicate_session_ == session) {
            duplicate_session_ = NULL;
        } else {
            assert(false);
        }
        delete static_cast<BgpSessionMock *>(session);
    }

    BgpSessionMock *active_session() { return active_session_; }
    BgpSessionMock *passive_session() { return passive_session_; }
    BgpSessionMock *duplicate_session() { return duplicate_session_; }

private:
    friend class StateMachineTest;
    BgpSessionMock *active_session_, *passive_session_, *duplicate_session_;
    bool create_session_fail_;
};

class StateMachineTest : public ::testing::Test {
protected:
    StateMachineTest() : server_(&evm_, "A"),
        timer_(TimerManager::CreateTimer(*evm_.io_service(), "Dummy timer")) {
        session_mgr_ =
          static_cast<BgpSessionManagerMock *>(server_.session_manager());
        server_.Configure(GetConfig());
        task_util::WaitForIdle();

        string uuid = BgpConfigParser::session_uuid("A", "B", 1);
        TASK_UTIL_EXPECT_NE(server_.FindPeerByUuid(
                                BgpConfigManager::kMasterInstance, uuid),
                            (BgpPeer *) NULL);
        peer_ = server_.FindPeerByUuid(BgpConfigManager::kMasterInstance, uuid);
        sm_ = peer_->state_machine();
        sm_->set_idle_hold_time(1);

        error_code ec;
        lower_id_ = Ip4Address::from_string("172.16.17.32", ec).to_ulong();
        higher_id_ = Ip4Address::from_string("172.16.17.32", ec).to_ulong();
    }

    ~StateMachineTest() {
        task_util::WaitForIdle();
        server_.Shutdown();
        task_util::WaitForIdle();
        TimerManager::DeleteTimer(timer_);
        evm_.Shutdown();
    }

    virtual void SetUp() {
    }

    virtual void TearDown() {
    }

    string GetConfig() {
        ostringstream out;
        out <<
        "<config>\
            <bgp-router name=\'A\'>\
                <identifier>192.168.0.1</identifier>\
                <address>127.0.0.1</address>\
                <autonomous-system>10458</autonomous-system>\
                <session to='B'/>\
            </bgp-router>\
            <bgp-router name=\'B\'>\
                <address>127.0.0.1</address>\
                <autonomous-system>10458</autonomous-system>\
            </bgp-router>\
        </config>";
        return out.str();
    }

    bool DummyTimerHandler() {
        BGP_DEBUG_UT("Dummy handler called");
        return false;
    }

    void RunToState(StateMachine::State state) {
        timer_->Start(15000,
                     boost::bind(&StateMachineTest::DummyTimerHandler, this));
        task_util::WaitForIdle();
        for (int count = 0; count < 100 && sm_->get_state() != state; count++) {
            evm_.RunOnce();
            task_util::WaitForIdle();
        }
        timer_->Cancel();
        VerifyState(state);
    }

    void GetToState(StateMachine::State state) {
        switch (state) {
        case StateMachine::IDLE: {
            EvAdminDown();
            VerifyState(state);
            break;
        }
        case StateMachine::ACTIVE: {
            GetToState(StateMachine::IDLE);
            EvAdminUp();
            task_util::WaitForIdle();
            EvIdleHoldTimerExpired();
            VerifyState(state);
            break;
        }
        case StateMachine::CONNECT: {
            GetToState(StateMachine::ACTIVE);
            EvConnectTimerExpired();
            VerifyState(state);
            break;
        }
        case StateMachine::OPENSENT: {
            GetToState(StateMachine::CONNECT);
            TcpSession::Endpoint endpoint;
            session_mgr_->active_session()->Connected(endpoint);
            VerifyState(state);
            break;
        }
        case StateMachine::OPENCONFIRM: {
            GetToState(StateMachine::OPENSENT);
            BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
            BgpMessageTest::GenerateOpenMessage(open);
            peer_->ResetCapabilities();
            sm_->OnMessage(session_mgr_->active_session(), open);
            VerifyState(StateMachine::OPENCONFIRM);
            break;
        }
        case StateMachine::ESTABLISHED: {
            GetToState(StateMachine::OPENCONFIRM);
            BgpProto::Keepalive *keepalive = new BgpProto::Keepalive;
            sm_->OnMessage(session_mgr_->active_session(), keepalive);
            VerifyState(StateMachine::ESTABLISHED);
            break;
        }
        default: {
            ASSERT_TRUE(false);
            break;
        }
        }
    }

    void VerifyState(StateMachine::State state) {
        task_util::WaitForIdle();
        TaskScheduler::GetInstance()->Stop();

        BGP_DEBUG_UT("VerifyState " << state);
        TASK_UTIL_EXPECT_EQ(state, sm_->get_state());

        switch (state) {
        case StateMachine::IDLE:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            if (peer_->IsAdminDown()) {
                TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            } else {
                TASK_UTIL_EXPECT_TRUE(IdleHoldTimerRunning());
            }
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::ACTIVE:
            TASK_UTIL_EXPECT_TRUE(ConnectTimerRunning() != OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ==
                                  OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::CONNECT:
            TASK_UTIL_EXPECT_TRUE(ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            if (!session_mgr_->create_session_fail()) {
                TASK_UTIL_EXPECT_TRUE(sm_->active_session() != NULL);
            } else {
                TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            }
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ==
                                  OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::OPENSENT:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() != NULL ||
                                  sm_->passive_session() != NULL);
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ||
                                  !OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::OPENCONFIRM:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() != NULL);
            break;

        case StateMachine::ESTABLISHED:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() != NULL);
            break;

        default:
            ASSERT_TRUE(false);
            break;
        }

        TaskScheduler::GetInstance()->Start();
    }

    void VerifyDirection(BgpSessionMock::Direction direction) {
        BgpSession *session = sm_->peer()->session();
        BgpSessionMock *mock_session = dynamic_cast<BgpSessionMock *>(session);
        ASSERT_TRUE(mock_session != NULL);
        TASK_UTIL_EXPECT_TRUE(mock_session->direction() == direction);
    }

    BgpSessionMock *GetSession(BgpSessionMock *session) {
        if (session) return session;
        session = session_mgr_->active_session();
        if (!session || session->state() != BgpSessionMock::ESTABLISHED) {
            if (!session_mgr_->passive_session())
                EvTcpPassiveOpen();
            session = session_mgr_->passive_session();
        }
        return session;
    }

    void EvStart() {
        sm_->reset_idle_hold_time();
        sm_->Initialize();
    }
    void EvStop() {
        sm_->Shutdown();
    }
    void EvAdminUp() {
        peer_->SetAdminState(false);
        sm_->set_idle_hold_time(1);
    }
    void EvAdminDown() {
        peer_->SetAdminState(true);
        sm_->set_idle_hold_time(1);
    }
    void EvConnectTimerExpired() {
        sm_->FireConnectTimer();
    }
    void EvIdleHoldTimerExpired() {
        sm_->FireIdleHoldTimer();
    }
    void EvOpenTimerExpired() {
        sm_->FireOpenTimer();
    }
    void EvTcpPassiveOpen() {
        BgpSessionMock *session = session_mgr_->CreatePassiveSession();
        TcpSession::Endpoint endpoint;
        peer_->AcceptSession(session);
    }
    void EvTcpDuplicatePassiveOpen() {
        BgpSessionMock *session = session_mgr_->CreateDuplicateSession();
        TcpSession::Endpoint endpoint;
        peer_->AcceptSession(session);
    }
    void EvOpenTimerExpiredPassive() {
        EvTcpPassiveOpen();
        task_util::WaitForIdle();
        sm_->FireOpenTimer();
    }
    void EvHoldTimerExpired() {
        sm_->FireHoldTimer();
    }
    void EvTcpConnected() {
        sm_->OnSessionEvent(session_mgr_->active_session(),
            TcpSession::CONNECT_COMPLETE);
    }
    void EvTcpConnectFail() {
        sm_->OnSessionEvent(session_mgr_->active_session(),
            TcpSession::CONNECT_FAILED);
    }
    void EvTcpClose(BgpSessionMock *session = NULL) {
        if (!session) session = session_mgr_->active_session();
        sm_->OnSessionEvent(session, TcpSession::CLOSE);
    }
    void EvBgpHeaderError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::MsgHdrErr;
        sm_->OnSessionError(session, &context);
    }
    void EvBgpOpen(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
        BgpMessageTest::GenerateOpenMessage(open);
        peer_->ResetCapabilities();
        sm_->OnMessage(session, open);
    }
    void EvBgpOpenCustom(BgpSessionMock *session,
        uint32_t identifier = 1, int holdtime = 30) {
        BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
        BgpMessageTest::GenerateOpenMessage(open);
        open->identifier = identifier;
        open->holdtime = holdtime;
        peer_->ResetCapabilities();
        sm_->OnMessage(session, open);
    }
    void EvBgpOpenError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::OpenMsgErr;
        sm_->OnSessionError(session, &context);
    }
    void EvBgpKeepalive(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Keepalive *keepalive = new BgpProto::Keepalive;
        sm_->OnMessage(session, keepalive);
    }
    void EvBgpNotification(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Notification *notification = new BgpProto::Notification;
        sm_->OnMessage(session, notification);
    }
    void EvBgpUpdate(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Update *update = new BgpProto::Update;
        BgpMessageTest::GenerateEmptyUpdateMessage(update);
        sm_->OnMessage(session, update);
    }
    void EvBgpUpdateError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::UpdateMsgErr;
        sm_->OnSessionError(session, &context);
    }

    bool ConnectTimerRunning() { return sm_->connect_timer_->running(); }
    bool OpenTimerRunning() { return sm_->open_timer_->running(); }
    bool HoldTimerRunning() { return sm_->hold_timer_->running(); }
    bool IdleHoldTimerRunning() { return sm_->idle_hold_timer_->running(); }

    EventManager evm_;
    BgpServerTest server_;
    BgpSessionManagerMock *session_mgr_;
    BgpPeer *peer_;
    StateMachine *sm_;
    Timer *timer_;
    uint32_t lower_id_, higher_id_;
};

typedef boost::function<void(void)> EvGen;
struct EvGenComp {
    bool operator()(const EvGen &lhs, const EvGen &rhs) { return &lhs < &rhs; }
};

TEST_F(StateMachineTest, Matrix) {
    boost::asio::io_service::work work(*evm_.io_service());
    typedef map<EvGen, StateMachine::State, EvGenComp> Transitions;

#define TRANSITION(F, E) \
    ((EvGen) boost::bind(&StateMachineTest_Matrix_Test::F, this), E)
#define TRANSITION2(F, E) \
    ((EvGen) boost::bind(&StateMachineTest_Matrix_Test::F, this, \
            (BgpSessionMock *) NULL), E)

    Transitions idle = map_list_of
            TRANSITION(EvTcpPassiveOpen, StateMachine::IDLE)
            TRANSITION(EvAdminUp, StateMachine::ACTIVE);

    Transitions active = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::ACTIVE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::CONNECT)
            TRANSITION(EvOpenTimerExpired, StateMachine::ACTIVE)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION(EvTcpPassiveOpen, StateMachine::ACTIVE)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM);

    Transitions connect = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::ACTIVE)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION(EvTcpConnected, StateMachine::OPENSENT)
            TRANSITION(EvTcpConnectFail, StateMachine::ACTIVE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::CONNECT)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::CONNECT)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions opensent = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::OPENSENT)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::OPENSENT)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions openconfirm = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpOpen, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::ESTABLISHED)
            TRANSITION2(EvTcpClose, StateMachine::IDLE)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::OPENCONFIRM)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions established = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION2(EvBgpOpen, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvTcpClose, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::ESTABLISHED)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::ESTABLISHED)
            TRANSITION(EvOpenTimerExpired, StateMachine::ESTABLISHED)
            TRANSITION(EvTcpPassiveOpen, StateMachine::ESTABLISHED)
            TRANSITION2(EvBgpUpdate, StateMachine::ESTABLISHED);

    Transitions matrix[] =
        { idle, active, connect, opensent, openconfirm, established };

    for (int k = StateMachine::IDLE; k <= StateMachine::ESTABLISHED; k++) {
        StateMachine::State i = static_cast<StateMachine::State> (k);
        int count = 0;
        for (Transitions::iterator j = matrix[i].begin();
                j != matrix[i].end(); j++) {
            BGP_DEBUG_UT("Starting test " << count++ << " in state " << i
                    << " expecting " << j->second << " *********************");
            GetToState(i);
            j->first();
            RunToState(j->second);

            // If the final state is IDLE, and the peer is not AdminDown,
            // we expect to go to ACTIVE state after the idle hold timer
            // expires.
            if (j->second == StateMachine::IDLE) {
                if (sm_->peer()->IsAdminDown()) {
                    TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
                } else {
                    RunToState(StateMachine::ACTIVE);
                }
            }
            sm_->set_idle_hold_time(1);
        }
    }
}

//
// Simplest sequence of events to go to Established.
//
TEST_F(StateMachineTest, Basic) {
    GetToState(StateMachine::ACTIVE);
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
    EvBgpOpen();
    VerifyState(StateMachine::OPENCONFIRM);
    EvBgpKeepalive();
    VerifyState(StateMachine::ESTABLISHED);
}

//
// Simulate EvTcpConnectFail and EvTcpClose.
//
TEST_F(StateMachineTest, ConnectionErrors) {
    boost::asio::io_service::work work(*evm_.io_service());
    GetToState(StateMachine::CONNECT);
    EvTcpConnectFail();
    VerifyState(StateMachine::ACTIVE);
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
    EvTcpClose();
    VerifyState(StateMachine::ACTIVE);
}

//
// Simulate CreateSession failure and verify that we cycle through the
// Connect and Active states till CreateSession eventually succeeds.
//
TEST_F(StateMachineTest, CreateSessionFail) {

    // Mark CreateSession() to fail.
    session_mgr_->set_create_session_fail(true);
    GetToState(StateMachine::ACTIVE);

    // Feed a few EvConnectTimerExpired and go through Active/Connect.
    for (int i = 0; i < 4; i++) {
        EvConnectTimerExpired();
        VerifyState(StateMachine::CONNECT);
        EvConnectTimerExpired();
        VerifyState(StateMachine::ACTIVE);
    }

    // Restore CreateSession() to succeed.
    session_mgr_->set_create_session_fail(false);

    // Make sure we can progress beyond Connect.
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
}

//
// First get to Active state and then inject EvConnectTimerExpired and
// EvTcpConnectFail multiple times to cycle through Connect and Active
// states.
//
// Verify that the connect time backs off as expected.
//
TEST_F(StateMachineTest, ConnectTimerBackoff) {
    GetToState(StateMachine::ACTIVE);
    for (int idx = 0; idx < 10; idx++) {
        int connect_time = sm_->GetConnectTime();
        if (idx == 0) {
            TASK_UTIL_EXPECT_EQ(0, connect_time);
        } else if (idx <= 5) {
            TASK_UTIL_EXPECT_EQ(1 << (idx -1), connect_time);
        } else {
            TASK_UTIL_EXPECT_EQ(StateMachine::kConnectInterval, connect_time);
        }
        EvConnectTimerExpired();
        VerifyState(StateMachine::CONNECT);
        EvTcpConnectFail();
        VerifyState(StateMachine::ACTIVE);
    }
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
}

class StateMachineIdleTest : public StateMachineTest {
protected:
    virtual void SetUp() {
        GetToState(StateMachine::IDLE);
    }

    virtual void TearDown() {
    }
};

// Old State: Idle
// Event:     EvTcpPassiveOpen
// New State: Idle
TEST_F(StateMachineIdleTest, TcpPassiveOpen) {
    TaskScheduler::GetInstance()->Stop();
    EvTcpPassiveOpen();
    TaskScheduler::GetInstance()->Start();
    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);

    // Also verify that we can proceed after this event
    GetToState(StateMachine::ACTIVE);
}


// Old State: Idle
// Event:     EvStop + EvIdleHoldTimerExpired
// New State: Idle
TEST_F(StateMachineIdleTest, StopThenIdleHoldTimerExpired) {
    TaskScheduler::GetInstance()->Stop();
    EvStop();
    EvIdleHoldTimerExpired();
    TaskScheduler::GetInstance()->Start();
    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);

    // Also verify that we can proceed after this event
    GetToState(StateMachine::ACTIVE);
}


// Old State: Idle
// Event:     EvTcpPassiveOpen + EvBgpOpen
// New State: Idle
TEST_F(StateMachineIdleTest, TcpPassiveOpenThenBgpOpen) {
    TaskScheduler::GetInstance()->Stop();
    EvTcpPassiveOpen();
    BgpSessionMock *session = session_mgr_->passive_session();
    EvBgpOpenCustom(session, lower_id_);
    TaskScheduler::GetInstance()->Start();

    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(session_mgr_->passive_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(session_mgr_->active_session() == NULL);

    // Also verify that we can proceed after this event<commit_msg>Tweak test to ensure that it exercises the intended test scenario. IdleHoldTimer must be running when we call EvIdleHoldTimerExpired.<commit_after>/*
 * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
 */

#include "bgp/state_machine.h"

#include <boost/assign.hpp>
#include <boost/asio.hpp>
#include <boost/foreach.hpp>

#include "base/logging.h"
#include "base/test/task_test_util.h"
#include "net/bgp_af.h"
#include "bgp/bgp_config.h"
#include "bgp/bgp_config_parser.h"
#include "bgp/bgp_factory.h"
#include "bgp/bgp_peer.h"
#include "bgp/bgp_server.h"
#include "bgp/bgp_session.h"
#include "bgp/bgp_session_manager.h"
#include "bgp/bgp_proto.h"
#include "control-node/control_node.h"
#include "io/event_manager.h"
#include "testing/gunit.h"

#include "bgp_message_test.h"
#include "bgp_server_test_util.h"

using namespace std;
using namespace boost::assign;
using namespace boost::posix_time;
using boost::system::error_code;

class BgpSessionMock : public BgpSession {
public:
    enum State {
        CONNECT,
        ESTABLISHED,
        CLOSE,
    };

    enum Direction {
        ACTIVE,
        PASSIVE,
    };

    BgpSessionMock(BgpSessionManager *session, State state, Direction direction)
        : BgpSession(session, NULL), state_(state), direction_(direction) {
    }

    ~BgpSessionMock() {
        BGP_DEBUG_UT("Session delete");
    }

    virtual bool Send(const u_int8_t *data, size_t size, size_t *sent) {
        return true;
    }

    virtual bool Connected(Endpoint remote) {
        state_ = BgpSessionMock::ESTABLISHED;
        EventObserver obs = observer();
        if (obs != NULL) {
            obs(this, CONNECT_COMPLETE);
        }
        return true;
    }

    void Close() {
        state_ = BgpSessionMock::CLOSE;
        BgpSession::Close();
    }

    State state() { return state_; }
    Direction direction() { return direction_; }

private:
    State state_;
    Direction direction_;
};

class BgpSessionManagerMock : public BgpSessionManager {
public:
    BgpSessionManagerMock(EventManager *evm, BgpServer *server)
        : BgpSessionManager(evm, server), active_session_(NULL),
          passive_session_(NULL), duplicate_session_(NULL),
          create_session_fail_(false) {
    }

    const bool create_session_fail() const { return create_session_fail_; }
    void set_create_session_fail(bool flag) { create_session_fail_ = flag; }

    virtual TcpSession *CreateSession() {
        assert(!active_session_);

        // Simulate CreateSession() API failure, such as when the process runs
        // out of file descriptors.
        if (create_session_fail_) return NULL;

        active_session_ =
            new BgpSessionMock(this, BgpSessionMock::CONNECT,
                BgpSessionMock::ACTIVE);
        return active_session_;
    }

    BgpSessionMock *CreatePassiveSession() {
        assert(!passive_session_);
        passive_session_ =
            new BgpSessionMock(this, BgpSessionMock::ESTABLISHED,
                BgpSessionMock::PASSIVE);
        return passive_session_;
    }

    BgpSessionMock *CreateDuplicateSession() {
        assert(passive_session_);
        assert(!duplicate_session_);
        duplicate_session_ =
            new BgpSessionMock(this, BgpSessionMock::ESTABLISHED,
                BgpSessionMock::PASSIVE);
        return duplicate_session_;
    }

    virtual void Connect(TcpSession *session, Endpoint remote) {
    }

    virtual void DeleteSession(TcpSession *session) {
        if (active_session_ == session) {
            active_session_ = NULL;
        } else if (passive_session_ == session) {
            passive_session_ = NULL;
        } else if (duplicate_session_ == session) {
            duplicate_session_ = NULL;
        } else {
            assert(false);
        }
        delete static_cast<BgpSessionMock *>(session);
    }

    BgpSessionMock *active_session() { return active_session_; }
    BgpSessionMock *passive_session() { return passive_session_; }
    BgpSessionMock *duplicate_session() { return duplicate_session_; }

private:
    friend class StateMachineTest;
    BgpSessionMock *active_session_, *passive_session_, *duplicate_session_;
    bool create_session_fail_;
};

class StateMachineTest : public ::testing::Test {
protected:
    StateMachineTest() : server_(&evm_, "A"),
        timer_(TimerManager::CreateTimer(*evm_.io_service(), "Dummy timer")) {
        session_mgr_ =
          static_cast<BgpSessionManagerMock *>(server_.session_manager());
        server_.Configure(GetConfig());
        task_util::WaitForIdle();

        string uuid = BgpConfigParser::session_uuid("A", "B", 1);
        TASK_UTIL_EXPECT_NE(server_.FindPeerByUuid(
                                BgpConfigManager::kMasterInstance, uuid),
                            (BgpPeer *) NULL);
        peer_ = server_.FindPeerByUuid(BgpConfigManager::kMasterInstance, uuid);
        sm_ = peer_->state_machine();
        sm_->set_idle_hold_time(1);

        error_code ec;
        lower_id_ = Ip4Address::from_string("172.16.17.32", ec).to_ulong();
        higher_id_ = Ip4Address::from_string("172.16.17.32", ec).to_ulong();
    }

    ~StateMachineTest() {
        task_util::WaitForIdle();
        server_.Shutdown();
        task_util::WaitForIdle();
        TimerManager::DeleteTimer(timer_);
        evm_.Shutdown();
    }

    virtual void SetUp() {
    }

    virtual void TearDown() {
    }

    string GetConfig() {
        ostringstream out;
        out <<
        "<config>\
            <bgp-router name=\'A\'>\
                <identifier>192.168.0.1</identifier>\
                <address>127.0.0.1</address>\
                <autonomous-system>10458</autonomous-system>\
                <session to='B'/>\
            </bgp-router>\
            <bgp-router name=\'B\'>\
                <address>127.0.0.1</address>\
                <autonomous-system>10458</autonomous-system>\
            </bgp-router>\
        </config>";
        return out.str();
    }

    bool DummyTimerHandler() {
        BGP_DEBUG_UT("Dummy handler called");
        return false;
    }

    void RunToState(StateMachine::State state) {
        timer_->Start(15000,
                     boost::bind(&StateMachineTest::DummyTimerHandler, this));
        task_util::WaitForIdle();
        for (int count = 0; count < 100 && sm_->get_state() != state; count++) {
            evm_.RunOnce();
            task_util::WaitForIdle();
        }
        timer_->Cancel();
        VerifyState(state);
    }

    void GetToState(StateMachine::State state) {
        switch (state) {
        case StateMachine::IDLE: {
            EvAdminDown();
            VerifyState(state);
            break;
        }
        case StateMachine::ACTIVE: {
            GetToState(StateMachine::IDLE);
            EvAdminUp();
            task_util::WaitForIdle();
            EvIdleHoldTimerExpired();
            VerifyState(state);
            break;
        }
        case StateMachine::CONNECT: {
            GetToState(StateMachine::ACTIVE);
            EvConnectTimerExpired();
            VerifyState(state);
            break;
        }
        case StateMachine::OPENSENT: {
            GetToState(StateMachine::CONNECT);
            TcpSession::Endpoint endpoint;
            session_mgr_->active_session()->Connected(endpoint);
            VerifyState(state);
            break;
        }
        case StateMachine::OPENCONFIRM: {
            GetToState(StateMachine::OPENSENT);
            BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
            BgpMessageTest::GenerateOpenMessage(open);
            peer_->ResetCapabilities();
            sm_->OnMessage(session_mgr_->active_session(), open);
            VerifyState(StateMachine::OPENCONFIRM);
            break;
        }
        case StateMachine::ESTABLISHED: {
            GetToState(StateMachine::OPENCONFIRM);
            BgpProto::Keepalive *keepalive = new BgpProto::Keepalive;
            sm_->OnMessage(session_mgr_->active_session(), keepalive);
            VerifyState(StateMachine::ESTABLISHED);
            break;
        }
        default: {
            ASSERT_TRUE(false);
            break;
        }
        }
    }

    void VerifyState(StateMachine::State state) {
        task_util::WaitForIdle();
        TaskScheduler::GetInstance()->Stop();

        BGP_DEBUG_UT("VerifyState " << state);
        TASK_UTIL_EXPECT_EQ(state, sm_->get_state());

        switch (state) {
        case StateMachine::IDLE:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            if (peer_->IsAdminDown()) {
                TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            } else {
                TASK_UTIL_EXPECT_TRUE(IdleHoldTimerRunning());
            }
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::ACTIVE:
            TASK_UTIL_EXPECT_TRUE(ConnectTimerRunning() != OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ==
                                  OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::CONNECT:
            TASK_UTIL_EXPECT_TRUE(ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            if (!session_mgr_->create_session_fail()) {
                TASK_UTIL_EXPECT_TRUE(sm_->active_session() != NULL);
            } else {
                TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            }
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ==
                                  OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::OPENSENT:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() != NULL ||
                                  sm_->passive_session() != NULL);
            TASK_UTIL_EXPECT_TRUE((sm_->passive_session() != NULL) ||
                                  !OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() == NULL);
            break;

        case StateMachine::OPENCONFIRM:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() != NULL);
            break;

        case StateMachine::ESTABLISHED:
            TASK_UTIL_EXPECT_TRUE(!ConnectTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!OpenTimerRunning());
            TASK_UTIL_EXPECT_TRUE(HoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
            TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
            TASK_UTIL_EXPECT_TRUE(peer_->KeepaliveTimerRunning());
            TASK_UTIL_EXPECT_TRUE(peer_->session() != NULL);
            break;

        default:
            ASSERT_TRUE(false);
            break;
        }

        TaskScheduler::GetInstance()->Start();
    }

    void VerifyDirection(BgpSessionMock::Direction direction) {
        BgpSession *session = sm_->peer()->session();
        BgpSessionMock *mock_session = dynamic_cast<BgpSessionMock *>(session);
        ASSERT_TRUE(mock_session != NULL);
        TASK_UTIL_EXPECT_TRUE(mock_session->direction() == direction);
    }

    BgpSessionMock *GetSession(BgpSessionMock *session) {
        if (session) return session;
        session = session_mgr_->active_session();
        if (!session || session->state() != BgpSessionMock::ESTABLISHED) {
            if (!session_mgr_->passive_session())
                EvTcpPassiveOpen();
            session = session_mgr_->passive_session();
        }
        return session;
    }

    void EvStart() {
        sm_->reset_idle_hold_time();
        sm_->Initialize();
    }
    void EvStop() {
        sm_->Shutdown();
    }
    void EvAdminUp() {
        peer_->SetAdminState(false);
        sm_->set_idle_hold_time(1);
    }
    void EvAdminDown() {
        peer_->SetAdminState(true);
        sm_->set_idle_hold_time(1);
    }
    void EvConnectTimerExpired() {
        sm_->FireConnectTimer();
    }
    void EvIdleHoldTimerExpired() {
        sm_->FireIdleHoldTimer();
    }
    void EvOpenTimerExpired() {
        sm_->FireOpenTimer();
    }
    void EvTcpPassiveOpen() {
        BgpSessionMock *session = session_mgr_->CreatePassiveSession();
        TcpSession::Endpoint endpoint;
        peer_->AcceptSession(session);
    }
    void EvTcpDuplicatePassiveOpen() {
        BgpSessionMock *session = session_mgr_->CreateDuplicateSession();
        TcpSession::Endpoint endpoint;
        peer_->AcceptSession(session);
    }
    void EvOpenTimerExpiredPassive() {
        EvTcpPassiveOpen();
        task_util::WaitForIdle();
        sm_->FireOpenTimer();
    }
    void EvHoldTimerExpired() {
        sm_->FireHoldTimer();
    }
    void EvTcpConnected() {
        sm_->OnSessionEvent(session_mgr_->active_session(),
            TcpSession::CONNECT_COMPLETE);
    }
    void EvTcpConnectFail() {
        sm_->OnSessionEvent(session_mgr_->active_session(),
            TcpSession::CONNECT_FAILED);
    }
    void EvTcpClose(BgpSessionMock *session = NULL) {
        if (!session) session = session_mgr_->active_session();
        sm_->OnSessionEvent(session, TcpSession::CLOSE);
    }
    void EvBgpHeaderError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::MsgHdrErr;
        sm_->OnSessionError(session, &context);
    }
    void EvBgpOpen(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
        BgpMessageTest::GenerateOpenMessage(open);
        peer_->ResetCapabilities();
        sm_->OnMessage(session, open);
    }
    void EvBgpOpenCustom(BgpSessionMock *session,
        uint32_t identifier = 1, int holdtime = 30) {
        BgpProto::OpenMessage *open = new BgpProto::OpenMessage;
        BgpMessageTest::GenerateOpenMessage(open);
        open->identifier = identifier;
        open->holdtime = holdtime;
        peer_->ResetCapabilities();
        sm_->OnMessage(session, open);
    }
    void EvBgpOpenError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::OpenMsgErr;
        sm_->OnSessionError(session, &context);
    }
    void EvBgpKeepalive(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Keepalive *keepalive = new BgpProto::Keepalive;
        sm_->OnMessage(session, keepalive);
    }
    void EvBgpNotification(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Notification *notification = new BgpProto::Notification;
        sm_->OnMessage(session, notification);
    }
    void EvBgpUpdate(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        BgpProto::Update *update = new BgpProto::Update;
        BgpMessageTest::GenerateEmptyUpdateMessage(update);
        sm_->OnMessage(session, update);
    }
    void EvBgpUpdateError(BgpSessionMock *session = NULL) {
        session = GetSession(session);
        ParseErrorContext context;
        context.error_code = BgpProto::Notification::UpdateMsgErr;
        sm_->OnSessionError(session, &context);
    }

    bool ConnectTimerRunning() { return sm_->connect_timer_->running(); }
    bool OpenTimerRunning() { return sm_->open_timer_->running(); }
    bool HoldTimerRunning() { return sm_->hold_timer_->running(); }
    bool IdleHoldTimerRunning() { return sm_->idle_hold_timer_->running(); }

    EventManager evm_;
    BgpServerTest server_;
    BgpSessionManagerMock *session_mgr_;
    BgpPeer *peer_;
    StateMachine *sm_;
    Timer *timer_;
    uint32_t lower_id_, higher_id_;
};

typedef boost::function<void(void)> EvGen;
struct EvGenComp {
    bool operator()(const EvGen &lhs, const EvGen &rhs) { return &lhs < &rhs; }
};

TEST_F(StateMachineTest, Matrix) {
    boost::asio::io_service::work work(*evm_.io_service());
    typedef map<EvGen, StateMachine::State, EvGenComp> Transitions;

#define TRANSITION(F, E) \
    ((EvGen) boost::bind(&StateMachineTest_Matrix_Test::F, this), E)
#define TRANSITION2(F, E) \
    ((EvGen) boost::bind(&StateMachineTest_Matrix_Test::F, this, \
            (BgpSessionMock *) NULL), E)

    Transitions idle = map_list_of
            TRANSITION(EvTcpPassiveOpen, StateMachine::IDLE)
            TRANSITION(EvAdminUp, StateMachine::ACTIVE);

    Transitions active = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::ACTIVE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::CONNECT)
            TRANSITION(EvOpenTimerExpired, StateMachine::ACTIVE)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION(EvTcpPassiveOpen, StateMachine::ACTIVE)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM);

    Transitions connect = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::ACTIVE)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION(EvTcpConnected, StateMachine::OPENSENT)
            TRANSITION(EvTcpConnectFail, StateMachine::ACTIVE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::CONNECT)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::CONNECT)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions opensent = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::OPENSENT)
            TRANSITION2(EvTcpClose, StateMachine::ACTIVE)
            TRANSITION2(EvBgpOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::OPENSENT)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENSENT)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions openconfirm = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION(EvTcpPassiveOpen, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpOpen, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::ESTABLISHED)
            TRANSITION2(EvTcpClose, StateMachine::IDLE)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::OPENCONFIRM)
            TRANSITION(EvOpenTimerExpiredPassive, StateMachine::OPENCONFIRM)
            TRANSITION2(EvBgpUpdate, StateMachine::IDLE);

    Transitions established = map_list_of
            TRANSITION(EvStop, StateMachine::IDLE)
            TRANSITION(EvAdminDown, StateMachine::IDLE)
            TRANSITION2(EvBgpOpen, StateMachine::IDLE)
            TRANSITION(EvHoldTimerExpired, StateMachine::IDLE)
            TRANSITION2(EvTcpClose, StateMachine::IDLE)
            TRANSITION2(EvBgpKeepalive, StateMachine::ESTABLISHED)
            TRANSITION2(EvBgpHeaderError, StateMachine::IDLE)
            TRANSITION2(EvBgpOpenError, StateMachine::IDLE)
            TRANSITION2(EvBgpUpdateError, StateMachine::IDLE)
            TRANSITION2(EvBgpNotification, StateMachine::IDLE)
            TRANSITION(EvConnectTimerExpired, StateMachine::ESTABLISHED)
            TRANSITION(EvOpenTimerExpired, StateMachine::ESTABLISHED)
            TRANSITION(EvTcpPassiveOpen, StateMachine::ESTABLISHED)
            TRANSITION2(EvBgpUpdate, StateMachine::ESTABLISHED);

    Transitions matrix[] =
        { idle, active, connect, opensent, openconfirm, established };

    for (int k = StateMachine::IDLE; k <= StateMachine::ESTABLISHED; k++) {
        StateMachine::State i = static_cast<StateMachine::State> (k);
        int count = 0;
        for (Transitions::iterator j = matrix[i].begin();
                j != matrix[i].end(); j++) {
            BGP_DEBUG_UT("Starting test " << count++ << " in state " << i
                    << " expecting " << j->second << " *********************");
            GetToState(i);
            j->first();
            RunToState(j->second);

            // If the final state is IDLE, and the peer is not AdminDown,
            // we expect to go to ACTIVE state after the idle hold timer
            // expires.
            if (j->second == StateMachine::IDLE) {
                if (sm_->peer()->IsAdminDown()) {
                    TASK_UTIL_EXPECT_TRUE(!IdleHoldTimerRunning());
                } else {
                    RunToState(StateMachine::ACTIVE);
                }
            }
            sm_->set_idle_hold_time(1);
        }
    }
}

//
// Simplest sequence of events to go to Established.
//
TEST_F(StateMachineTest, Basic) {
    GetToState(StateMachine::ACTIVE);
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
    EvBgpOpen();
    VerifyState(StateMachine::OPENCONFIRM);
    EvBgpKeepalive();
    VerifyState(StateMachine::ESTABLISHED);
}

//
// Simulate EvTcpConnectFail and EvTcpClose.
//
TEST_F(StateMachineTest, ConnectionErrors) {
    boost::asio::io_service::work work(*evm_.io_service());
    GetToState(StateMachine::CONNECT);
    EvTcpConnectFail();
    VerifyState(StateMachine::ACTIVE);
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
    EvTcpClose();
    VerifyState(StateMachine::ACTIVE);
}

//
// Simulate CreateSession failure and verify that we cycle through the
// Connect and Active states till CreateSession eventually succeeds.
//
TEST_F(StateMachineTest, CreateSessionFail) {

    // Mark CreateSession() to fail.
    session_mgr_->set_create_session_fail(true);
    GetToState(StateMachine::ACTIVE);

    // Feed a few EvConnectTimerExpired and go through Active/Connect.
    for (int i = 0; i < 4; i++) {
        EvConnectTimerExpired();
        VerifyState(StateMachine::CONNECT);
        EvConnectTimerExpired();
        VerifyState(StateMachine::ACTIVE);
    }

    // Restore CreateSession() to succeed.
    session_mgr_->set_create_session_fail(false);

    // Make sure we can progress beyond Connect.
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
}

//
// First get to Active state and then inject EvConnectTimerExpired and
// EvTcpConnectFail multiple times to cycle through Connect and Active
// states.
//
// Verify that the connect time backs off as expected.
//
TEST_F(StateMachineTest, ConnectTimerBackoff) {
    GetToState(StateMachine::ACTIVE);
    for (int idx = 0; idx < 10; idx++) {
        int connect_time = sm_->GetConnectTime();
        if (idx == 0) {
            TASK_UTIL_EXPECT_EQ(0, connect_time);
        } else if (idx <= 5) {
            TASK_UTIL_EXPECT_EQ(1 << (idx -1), connect_time);
        } else {
            TASK_UTIL_EXPECT_EQ(StateMachine::kConnectInterval, connect_time);
        }
        EvConnectTimerExpired();
        VerifyState(StateMachine::CONNECT);
        EvTcpConnectFail();
        VerifyState(StateMachine::ACTIVE);
    }
    EvConnectTimerExpired();
    VerifyState(StateMachine::CONNECT);
    BgpSessionMock *session = session_mgr_->active_session();
    TcpSession::Endpoint endpoint;
    session->Connected(endpoint);
    VerifyState(StateMachine::OPENSENT);
}

class StateMachineIdleTest : public StateMachineTest {
protected:
    virtual void SetUp() {
        GetToState(StateMachine::IDLE);
    }

    virtual void TearDown() {
    }
};

// Old State: Idle
// Event:     EvTcpPassiveOpen
// New State: Idle
TEST_F(StateMachineIdleTest, TcpPassiveOpen) {
    TaskScheduler::GetInstance()->Stop();
    EvTcpPassiveOpen();
    TaskScheduler::GetInstance()->Start();
    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);

    // Also verify that we can proceed after this event
    GetToState(StateMachine::ACTIVE);
}

// Old State: Idle
// Event:     EvAdminUp + EvAdminDown + EvIdleHoldTimerExpired
// New State: Idle
// Intent:    IdleHoldTimer expires right after an AdminDown is enqueued.
//            We shouldn't proceed beyond the Idle state if this happens.
// Other:     Need to make sure that IdleHoldTimer is running when we call
//            EvIdleHoldTimerExpired, otherwise no event will be enqueued.
TEST_F(StateMachineIdleTest, AdminUpThenAdminDownThenIdleHoldTimerExpired) {
    TaskScheduler::GetInstance()->Stop();
    EvAdminUp();
    sm_->set_idle_hold_time(StateMachine::kMaxIdleHoldTime);
    TaskScheduler::GetInstance()->Start();
    VerifyState(StateMachine::IDLE);
    TaskScheduler::GetInstance()->Stop();
    EvAdminDown();
    EvIdleHoldTimerExpired();
    TaskScheduler::GetInstance()->Start();
    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(sm_->active_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(sm_->passive_session() == NULL);
}

// Old State: Idle
// Event:     EvTcpPassiveOpen + EvBgpOpen
// New State: Idle
TEST_F(StateMachineIdleTest, TcpPassiveOpenThenBgpOpen) {
    TaskScheduler::GetInstance()->Stop();
    EvTcpPassiveOpen();
    BgpSessionMock *session = session_mgr_->passive_session();
    EvBgpOpenCustom(session, lower_id_);
    TaskScheduler::GetInstance()->Start();

    VerifyState(StateMachine::IDLE);
    TASK_UTIL_EXPECT_TRUE(session_mgr_->passive_session() == NULL);
    TASK_UTIL_EXPECT_TRUE(session_mgr_->active_session() == NULL);

    // Also verify that we can proceed after this event
{'fraction_non_alphanumeric': 0.11112462006079027, 'fraction_numerical': 0.004446374294398611, 'mean_word_length': 2.873519913885899, 'pattern_counts': {'":': 0, '<': 104, '<?xml version=': 0, '>': 305, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 10, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7538503', 'n_tokens_mistral': 17308, 'n_tokens_neox': 17084, 'n_words': 2732}