//-----------------------------------------------------------------------
//  This software is part of the OpenBeOS distribution and is covered 
//  by the OpenBeOS license.
//
//  Copyright (c) 2003-2004 Waldemar Kornewald, Waldemar.Kornewald@web.de
//-----------------------------------------------------------------------

#include <OS.h>

#include <KPPPInterface.h>
#include <KPPPConfigurePacket.h>
#include <KPPPDevice.h>
#include <KPPPLCPExtension.h>
#include <KPPPOptionHandler.h>

#include <LockerHelper.h>

#include <net/if.h>
#include <core_funcs.h>


#define PPP_STATE_MACHINE_TIMEOUT			3000000
	// 3 seconds


KPPPStateMachine::KPPPStateMachine(KPPPInterface& interface)
	: fInterface(interface),
	fLCP(interface.LCP()),
	fState(PPP_INITIAL_STATE),
	fPhase(PPP_DOWN_PHASE),
	fID(system_time() & 0xFF),
	fMagicNumber(0),
	fLocalAuthenticationStatus(PPP_NOT_AUTHENTICATED),
	fPeerAuthenticationStatus(PPP_NOT_AUTHENTICATED),
	fLocalAuthenticationName(NULL),
	fPeerAuthenticationName(NULL),
	fMaxRequest(10),
	fMaxTerminate(2),
	fMaxNak(5),
	fRequestID(0),
	fTerminateID(0),
	fEchoID(0),
	fNextTimeout(0)
{
}


KPPPStateMachine::~KPPPStateMachine()
{
	free(fLocalAuthenticationName);
	free(fPeerAuthenticationName);
}


uint8
KPPPStateMachine::NextID()
{
	return (uint8) atomic_add(&fID, 1);
}


// remember: NewState() must always be called _after_ IllegalEvent()
// because IllegalEvent() also looks at the current state.
void
KPPPStateMachine::NewState(ppp_state next)
{
#if DEBUG
	dprintf("KPPPSM: NewState(%d) state=%d\n", next, State());
#endif
	
	// maybe we do not need the timer anymore
	if(next < PPP_CLOSING_STATE || next == PPP_OPENED_STATE)
		fNextTimeout = 0;
	
	if(State() == PPP_OPENED_STATE && next != State())
		ResetLCPHandlers();
	
	fState = next;
}


void
KPPPStateMachine::NewPhase(ppp_phase next)
{
#if DEBUG
	if(next <= PPP_ESTABLISHMENT_PHASE || next == PPP_ESTABLISHED_PHASE)
		dprintf("KPPPSM: NewPhase(%d) phase=%d\n", next, Phase());
#endif
	
	// there is nothing after established phase and nothing before down phase
	if(next > PPP_ESTABLISHED_PHASE)
		next = PPP_ESTABLISHED_PHASE;
	else if(next < PPP_DOWN_PHASE)
		next = PPP_DOWN_PHASE;
	
	// Report a down event to parent if we are not usable anymore.
	// The report threads get their notification later.
	if(Phase() == PPP_ESTABLISHED_PHASE && next != Phase()) {
		if(Interface().Ifnet()) {
			Interface().Ifnet()->if_flags &= ~IFF_RUNNING;
			
			if(!Interface().DoesDialOnDemand())
				Interface().Ifnet()->if_flags &= ~IFF_UP;
		}
		
		if(Interface().Parent())
			Interface().Parent()->StateMachine().DownEvent(Interface());
	}
	
	fPhase = next;
	
	if(Phase() == PPP_ESTABLISHED_PHASE) {
		if(Interface().Ifnet())
			Interface().Ifnet()->if_flags |= IFF_UP | IFF_RUNNING;
		
		Interface().Report(PPP_CONNECTION_REPORT, PPP_REPORT_UP_SUCCESSFUL,
			&fInterface.fID, sizeof(ppp_interface_id));
	}
}


// public actions
bool
KPPPStateMachine::Reconfigure()
{
#if DEBUG
	dprintf("KPPPSM: Reconfigure() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(State() < PPP_REQ_SENT_STATE)
		return false;
	
	NewState(PPP_REQ_SENT_STATE);
	NewPhase(PPP_ESTABLISHMENT_PHASE);
		// indicates to handlers that we are reconfiguring
	
	DownProtocols();
	ResetLCPHandlers();
	
	locker.UnlockNow();
	
	return SendConfigureRequest();
}


bool
KPPPStateMachine::SendEchoRequest()
{
#if DEBUG
	dprintf("KPPPSM: SendEchoRequest() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(State() != PPP_OPENED_STATE)
		return false;
	
	struct mbuf *packet = m_gethdr(MT_DATA);
	if(!packet)
		return false;
	
	packet->m_data += LCP().AdditionalOverhead();
	packet->m_pkthdr.len = packet->m_len = 8;
		// echo requests are at least eight bytes long
	
	ppp_lcp_packet *request = mtod(packet, ppp_lcp_packet*);
	request->code = PPP_ECHO_REQUEST;
	request->id = NextID();
	fEchoID = request->id;
	request->length = htons(packet->m_len);
	memcpy(request->data, &fMagicNumber, sizeof(fMagicNumber));
	
	return LCP().Send(packet) == B_OK;
}


bool
KPPPStateMachine::SendDiscardRequest()
{
#if DEBUG
	dprintf("KPPPSM: SendDiscardRequest() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(State() != PPP_OPENED_STATE)
		return false;
	
	struct mbuf *packet = m_gethdr(MT_DATA);
	if(!packet)
		return false;
	
	packet->m_data += LCP().AdditionalOverhead();
	packet->m_pkthdr.len = packet->m_len = 8;
		// discard requests are at least eight bytes long
	
	ppp_lcp_packet *request = mtod(packet, ppp_lcp_packet*);
	request->code = PPP_DISCARD_REQUEST;
	request->id = NextID();
	request->length = htons(packet->m_len);
	memcpy(request->data, &fMagicNumber, sizeof(fMagicNumber));
	
	return LCP().Send(packet) == B_OK;
}


// authentication events
void
KPPPStateMachine::LocalAuthenticationRequested()
{
#if DEBUG
	dprintf("KPPPSM: LocalAuthenticationRequested() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fLocalAuthenticationStatus = PPP_AUTHENTICATING;
	free(fLocalAuthenticationName);
	fLocalAuthenticationName = NULL;
}


void
KPPPStateMachine::LocalAuthenticationAccepted(const char *name)
{
#if DEBUG
	dprintf("KPPPSM: LocalAuthenticationAccepted() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fLocalAuthenticationStatus = PPP_AUTHENTICATION_SUCCESSFUL;
	free(fLocalAuthenticationName);
	if(name)
		fLocalAuthenticationName = strdup(name);
	else
		fLocalAuthenticationName = NULL;
	
	Interface().Report(PPP_CONNECTION_REPORT,
		PPP_REPORT_LOCAL_AUTHENTICATION_SUCCESSFUL, &fInterface.fID,
		sizeof(ppp_interface_id));
}


void
KPPPStateMachine::LocalAuthenticationDenied(const char *name)
{
#if DEBUG
	dprintf("KPPPSM: LocalAuthenticationDenied() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fLocalAuthenticationStatus = PPP_AUTHENTICATION_FAILED;
	free(fLocalAuthenticationName);
	if(name)
		fLocalAuthenticationName = strdup(name);
	else
		fLocalAuthenticationName = NULL;
}


void
KPPPStateMachine::PeerAuthenticationRequested()
{
#if DEBUG
	dprintf("KPPPSM: PeerAuthenticationRequested() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fPeerAuthenticationStatus = PPP_AUTHENTICATING;
	free(fPeerAuthenticationName);
	fPeerAuthenticationName = NULL;
}


void
KPPPStateMachine::PeerAuthenticationAccepted(const char *name)
{
#if DEBUG
	dprintf("KPPPSM: PeerAuthenticationAccepted() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fPeerAuthenticationStatus = PPP_AUTHENTICATION_SUCCESSFUL;
	free(fPeerAuthenticationName);
	if(name)
		fPeerAuthenticationName = strdup(name);
	else
		fPeerAuthenticationName = NULL;
	
	Interface().Report(PPP_CONNECTION_REPORT,
		PPP_REPORT_PEER_AUTHENTICATION_SUCCESSFUL, &fInterface.fID,
		sizeof(ppp_interface_id));
}


void
KPPPStateMachine::PeerAuthenticationDenied(const char *name)
{
#if DEBUG
	dprintf("KPPPSM: PeerAuthenticationDenied() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	fPeerAuthenticationStatus = PPP_AUTHENTICATION_FAILED;
	free(fPeerAuthenticationName);
	if(name)
		fPeerAuthenticationName = strdup(name);
	else
		fPeerAuthenticationName = NULL;
	
	CloseEvent();
}


void
KPPPStateMachine::UpFailedEvent(KPPPInterface& interface)
{
#if DEBUG
	dprintf("KPPPSM: UpFailedEvent(interface) state=%d phase=%d\n",
		State(), Phase());
#endif
	
	// TODO:
	// log that an interface did not go up
}


void
KPPPStateMachine::UpEvent(KPPPInterface& interface)
{
#if DEBUG
	dprintf("KPPPSM: UpEvent(interface) state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(Phase() <= PPP_TERMINATION_PHASE) {
		interface.StateMachine().CloseEvent();
		return;
	}
	
	Interface().CalculateBaudRate();
	
	if(Phase() == PPP_ESTABLISHMENT_PHASE) {
		// this is the first interface that went up
		Interface().SetMRU(interface.MRU());
		locker.UnlockNow();
		ThisLayerUp();
	} else if(Interface().MRU() > interface.MRU())
		Interface().SetMRU(interface.MRU());
			// MRU should always be the smallest value of all children
	
	NewState(PPP_OPENED_STATE);
}


void
KPPPStateMachine::DownEvent(KPPPInterface& interface)
{
#if DEBUG
	dprintf("KPPPSM: DownEvent(interface) state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	uint32 MRU = 0;
		// the new MRU
	
	Interface().CalculateBaudRate();
	
	// when all children are down we should not be running
	if(Interface().IsMultilink() && !Interface().Parent()) {
		uint32 count = 0;
		KPPPInterface *child;
		for(int32 index = 0; index < Interface().CountChildren(); index++) {
			child = Interface().ChildAt(index);
			
			if(child && child->IsUp()) {
				// set MRU to the smallest value of all children
				if(MRU == 0)
					MRU = child->MRU();
				else if(MRU > child->MRU())
					MRU = child->MRU();
				
				++count;
			}
		}
		
		if(MRU == 0)
			Interface().SetMRU(1500);
		else
			Interface().SetMRU(MRU);
		
		if(count == 0) {
			locker.UnlockNow();
			DownEvent();
		}
	}
}


void
KPPPStateMachine::UpFailedEvent(KPPPProtocol *protocol)
{
#if DEBUG
	dprintf("KPPPSM: UpFailedEvent(protocol) state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if((protocol->Flags() & PPP_NOT_IMPORTANT) == 0) {
		if(Interface().Mode() == PPP_CLIENT_MODE) {
			// pretend we lost connection
			if(Interface().IsMultilink() && !Interface().Parent())
				for(int32 index = 0; index < Interface().CountChildren(); index++)
					Interface().ChildAt(index)->StateMachine().CloseEvent();
			else if(Interface().Device())
				Interface().Device()->Down();
			else
				CloseEvent();
					// just to be on the secure side ;)
		} else
			CloseEvent();
	}
}


void
KPPPStateMachine::UpEvent(KPPPProtocol *protocol)
{
#if DEBUG
	dprintf("KPPPSM: UpEvent(protocol) state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(Phase() >= PPP_ESTABLISHMENT_PHASE)
		BringProtocolsUp();
}


void
KPPPStateMachine::DownEvent(KPPPProtocol *protocol)
{
#if DEBUG
	dprintf("KPPPSM: DownEvent(protocol) state=%d phase=%d\n",
		State(), Phase());
#endif
}


// This is called by the device to tell us that it entered establishment
// phase. We can use Device::Down() to abort establishment until UpEvent()
// is called.
// The return value says if we are waiting for an UpEvent(). If false is
// returned the device should immediately abort its attempt to connect.
bool
KPPPStateMachine::TLSNotify()
{
#if DEBUG
	dprintf("KPPPSM: TLSNotify() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(State() == PPP_STARTING_STATE) {
			if(Phase() == PPP_DOWN_PHASE)
				NewPhase(PPP_ESTABLISHMENT_PHASE);
					// this says that the device is going up
			return true;
	}
	
	return false;
}


// This is called by the device to tell us that it entered termination phase.
// A Device::Up() should wait until the device went down.
// If false is returned we want to stay connected, though we called
// Device::Down().
bool
KPPPStateMachine::TLFNotify()
{
#if DEBUG
	dprintf("KPPPSM: TLFNotify() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	NewPhase(PPP_TERMINATION_PHASE);
		// tell DownEvent() that it may create a connection-lost-report
	
	return true;
}


void
KPPPStateMachine::UpFailedEvent()
{
#if DEBUG
	dprintf("KPPPSM: UpFailedEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_STARTING_STATE:
			Interface().Report(PPP_CONNECTION_REPORT, PPP_REPORT_DEVICE_UP_FAILED,
				&fInterface.fID, sizeof(ppp_interface_id));
			if(Interface().Parent())
				Interface().Parent()->StateMachine().UpFailedEvent(Interface());
			
			NewPhase(PPP_DOWN_PHASE);
				// tell DownEvent() that it should not create a connection-lost-report
			DownEvent();
		break;
		
		default:
			IllegalEvent(PPP_UP_FAILED_EVENT);
	}
}


void
KPPPStateMachine::UpEvent()
{
#if DEBUG
	dprintf("KPPPSM: UpEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	// This call is public, thus, it might not only be called by the device.
	// We must recognize these attempts to fool us and handle them correctly.
	
	LockerHelper locker(fLock);
	
	if(!Interface().Device() || !Interface().Device()->IsUp())
		return;
			// it is not our device that went up...
	
	Interface().CalculateBaudRate();
	
	switch(State()) {
		case PPP_INITIAL_STATE:
			if(Interface().Mode() != PPP_SERVER_MODE
					|| Phase() != PPP_ESTABLISHMENT_PHASE) {
				// we are a client or we do not listen for an incoming
				// connection, so this is an illegal event
				IllegalEvent(PPP_UP_EVENT);
				NewState(PPP_CLOSED_STATE);
				locker.UnlockNow();
				ThisLayerFinished();
				
				return;
			}
			
			// TODO: handle server-up! (maybe already done correctly)
			NewState(PPP_REQ_SENT_STATE);
			InitializeRestartCount();
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		case PPP_STARTING_STATE:
			// we must have called TLS() which sets establishment phase
			if(Phase() != PPP_ESTABLISHMENT_PHASE) {
				// there must be a BUG in the device add-on or someone is trying to
				// fool us (UpEvent() is public) as we did not request the device
				// to go up
				IllegalEvent(PPP_UP_EVENT);
				NewState(PPP_CLOSED_STATE);
				locker.UnlockNow();
				ThisLayerFinished();
				break;
			}
			
			NewState(PPP_REQ_SENT_STATE);
			InitializeRestartCount();
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		default:
			IllegalEvent(PPP_UP_EVENT);
	}
}


void
KPPPStateMachine::DownEvent()
{
#if DEBUG
	dprintf("KPPPSM: DownEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(Interface().Device() && Interface().Device()->IsUp())
		return;
			// it is not our device that went up...
	
	Interface().CalculateBaudRate();
	
	// reset IdleSince
	Interface().fIdleSince = 0;
	
	switch(State()) {
		// XXX: this does not belong to the standard, but may happen in our
		// implementation
		case PPP_STARTING_STATE:
		break;
		
		case PPP_CLOSED_STATE:
		case PPP_CLOSING_STATE:
			NewState(PPP_INITIAL_STATE);
		break;
		
		case PPP_STOPPED_STATE:
			// The RFC says we should reconnect, but our implementation
			// will only do this if auto-redial is enabled (only clients).
			NewState(PPP_STARTING_STATE);
		break;
		
		case PPP_STOPPING_STATE:
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
		case PPP_OPENED_STATE:
			NewState(PPP_STARTING_STATE);
		break;
		
		default:
			IllegalEvent(PPP_DOWN_EVENT);
	}
	
	ppp_phase oldPhase = Phase();
	NewPhase(PPP_DOWN_PHASE);
	
	DownProtocols();
	
	fLocalAuthenticationStatus = PPP_NOT_AUTHENTICATED;
	fPeerAuthenticationStatus = PPP_NOT_AUTHENTICATED;
	
	// maybe we need to redial
	if(State() == PPP_STARTING_STATE) {
		bool needsRedial = false;
		
		// we do not try to redial if authentication failed
		if(fLocalAuthenticationStatus == PPP_AUTHENTICATION_FAILED
				|| fLocalAuthenticationStatus == PPP_AUTHENTICATING)
			Interface().Report(PPP_CONNECTION_REPORT,
				PPP_REPORT_LOCAL_AUTHENTICATION_FAILED, &fInterface.fID,
				sizeof(ppp_interface_id));
		else if(fPeerAuthenticationStatus == PPP_AUTHENTICATION_FAILED
				|| fPeerAuthenticationStatus == PPP_AUTHENTICATING)
			Interface().Report(PPP_CONNECTION_REPORT,
				PPP_REPORT_PEER_AUTHENTICATION_FAILED, &fInterface.fID,
				sizeof(ppp_interface_id));
		else {
			// if we are going up and lost connection the redial attempt becomes
			// a dial retry which is managed by the main thread in Interface::Up()
			if(Interface().fUpThread == -1)
				needsRedial = true;
			
			// test if UpFailedEvent() was not called
			if(oldPhase != PPP_DOWN_PHASE)
				Interface().Report(PPP_CONNECTION_REPORT, PPP_REPORT_CONNECTION_LOST,
					&fInterface.fID, sizeof(ppp_interface_id));
		}
		
		if(Interface().Parent())
			Interface().Parent()->StateMachine().UpFailedEvent(Interface());
		
		NewState(PPP_INITIAL_STATE);
		
		if(Interface().DoesAutoRedial()) {
			if(needsRedial)
				Interface().Redial(Interface().RedialDelay());
		} else if(!Interface().DoesDialOnDemand())
			Interface().Delete();
	} else {
		Interface().Report(PPP_CONNECTION_REPORT, PPP_REPORT_DOWN_SUCCESSFUL,
			&fInterface.fID, sizeof(ppp_interface_id));
		
		if(!Interface().DoesDialOnDemand())
			Interface().Delete();
	}
}


// private events
void
KPPPStateMachine::OpenEvent()
{
#if DEBUG
	dprintf("KPPPSM: OpenEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	// reset all handlers
	if(Phase() != PPP_ESTABLISHED_PHASE) {
		DownProtocols();
		ResetLCPHandlers();
	}
	
	switch(State()) {
		case PPP_INITIAL_STATE:
			if(!Interface().Report(PPP_CONNECTION_REPORT, PPP_REPORT_GOING_UP,
					&fInterface.fID, sizeof(ppp_interface_id)))
				return;
			
			if(Interface().Mode() == PPP_SERVER_MODE) {
				NewPhase(PPP_ESTABLISHMENT_PHASE);
				
				if(Interface().Device() && !Interface().Device()->Up()) {
					Interface().Device()->UpFailedEvent();
					return;
				}
			} else
				NewState(PPP_STARTING_STATE);
			
			if(Interface().IsMultilink() && !Interface().Parent()) {
				NewPhase(PPP_ESTABLISHMENT_PHASE);
				for(int32 index = 0; index < Interface().CountChildren(); index++)
					if(Interface().ChildAt(index)->Mode() == Interface().Mode())
						Interface().ChildAt(index)->StateMachine().OpenEvent();
			} else {
				locker.UnlockNow();
				ThisLayerStarted();
			}
		break;
		
		case PPP_CLOSED_STATE:
			if(Phase() == PPP_DOWN_PHASE) {
				// the device is already going down
				return;
			}
			
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
			InitializeRestartCount();
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		case PPP_CLOSING_STATE:
			NewState(PPP_STOPPING_STATE);
		break;
		
		default:
			;
	}
}


void
KPPPStateMachine::CloseEvent()
{
#if DEBUG
	dprintf("KPPPSM: CloseEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(Interface().IsMultilink() && !Interface().Parent()) {
		NewState(PPP_INITIAL_STATE);
		
		if(Phase() != PPP_DOWN_PHASE)
			NewPhase(PPP_TERMINATION_PHASE);
		
		ThisLayerDown();
		
		for(int32 index = 0; index < Interface().CountChildren(); index++)
			Interface().ChildAt(index)->StateMachine().CloseEvent();
		
		return;
	}
	
	switch(State()) {
		case PPP_OPENED_STATE:
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_CLOSING_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
				// indicates to handlers that we are terminating
			InitializeRestartCount();
			locker.UnlockNow();
			if(State() == PPP_OPENED_STATE)
				ThisLayerDown();
			SendTerminateRequest();
		break;
		
		case PPP_STARTING_STATE:
			NewState(PPP_INITIAL_STATE);
			
			// TLSNotify() will know that we were faster because we
			// are in PPP_INITIAL_STATE now
			if(Phase() == PPP_ESTABLISHMENT_PHASE) {
				// the device is already up
				NewPhase(PPP_DOWN_PHASE);
					// this says the following DownEvent() was not caused by
					// a connection fault
				locker.UnlockNow();
				ThisLayerFinished();
			}
		break;
		
		case PPP_STOPPING_STATE:
			NewState(PPP_CLOSING_STATE);
		break;
		
		case PPP_STOPPED_STATE:
			NewState(PPP_STOPPED_STATE);
		break;
		
		default:
			;
	}
}


// timeout (restart counters are > 0)
void
KPPPStateMachine::TOGoodEvent()
{
#if DEBUG
	dprintf("KPPPSM: TOGoodEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_CLOSING_STATE:
		case PPP_STOPPING_STATE:
			locker.UnlockNow();
			SendTerminateRequest();
		break;
		
		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
		
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_SENT_STATE:
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		default:
			IllegalEvent(PPP_TO_GOOD_EVENT);
	}
}


// timeout (restart counters are <= 0)
void
KPPPStateMachine::TOBadEvent()
{
#if DEBUG
	dprintf("KPPPSM: TOBadEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_CLOSING_STATE:
			NewState(PPP_CLOSED_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		case PPP_STOPPING_STATE:
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_STOPPED_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		default:
			IllegalEvent(PPP_TO_BAD_EVENT);
	}
}


// receive configure request (acceptable request)
void
KPPPStateMachine::RCRGoodEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RCRGoodEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RCR_GOOD_EVENT);
			m_freem(packet);
		break;
		
		case PPP_CLOSED_STATE:
			locker.UnlockNow();
			SendTerminateAck();
			m_freem(packet);
		break;
		
		case PPP_STOPPED_STATE:
			// irc,scr,sca/8
			// XXX: should we do nothing and wait for DownEvent()?
			m_freem(packet);
		break;
		
		case PPP_REQ_SENT_STATE:
			NewState(PPP_ACK_SENT_STATE);
		
		case PPP_ACK_SENT_STATE:
			locker.UnlockNow();
			SendConfigureAck(packet);
		break;
		
		case PPP_ACK_RCVD_STATE:
			NewState(PPP_OPENED_STATE);
			locker.UnlockNow();
			SendConfigureAck(packet);
			ThisLayerUp();
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_ACK_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
				// indicates to handlers that we are reconfiguring
			locker.UnlockNow();
			ThisLayerDown();
			SendConfigureRequest();
			SendConfigureAck(packet);
		break;
		
		default:
			m_freem(packet);
	}
}


// receive configure request (unacceptable request)
void
KPPPStateMachine::RCRBadEvent(struct mbuf *nak, struct mbuf *reject)
{
#if DEBUG
	dprintf("KPPPSM: RCRBadEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RCR_BAD_EVENT);
		break;
		
		case PPP_CLOSED_STATE:
			locker.UnlockNow();
			SendTerminateAck();
		break;
		
		case PPP_STOPPED_STATE:
			// irc,scr,scn/6
			// XXX: should we do nothing and wait for DownEvent()?
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
				// indicates to handlers that we are reconfiguring
			locker.UnlockNow();
			ThisLayerDown();
			SendConfigureRequest();
		
		case PPP_ACK_SENT_STATE:
			if(State() == PPP_ACK_SENT_STATE)
				NewState(PPP_REQ_SENT_STATE);
					// OPENED_STATE might have set this already
		
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
			locker.UnlockNow();
			if(nak && ntohs(mtod(nak, ppp_lcp_packet*)->length) > 3)
				SendConfigureNak(nak);
			else if(reject && ntohs(mtod(reject, ppp_lcp_packet*)->length) > 3)
				SendConfigureNak(reject);
		return;
			// prevents the nak/reject from being m_freem()'d
		
		default:
			;
	}
	
	if(nak)
		m_freem(nak);
	if(reject)
		m_freem(reject);
}


// receive configure ack
void
KPPPStateMachine::RCAEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RCAEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(fRequestID != mtod(packet, ppp_lcp_packet*)->id) {
		// this packet is not a reply to our request
		
		// TODO:
		// log this event
		m_freem(packet);
		return;
	}
	
	// let the option handlers parse this ack
	KPPPConfigurePacket ack(packet);
	KPPPOptionHandler *optionHandler;
	for(int32 index = 0; index < LCP().CountOptionHandlers(); index++) {
		optionHandler = LCP().OptionHandlerAt(index);
		if(optionHandler->ParseAck(ack) != B_OK) {
			m_freem(packet);
			locker.UnlockNow();
			CloseEvent();
			return;
		}
	}
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RCA_EVENT);
		break;
		
		case PPP_CLOSED_STATE:
		case PPP_STOPPED_STATE:
			locker.UnlockNow();
			SendTerminateAck();
		break;
		
		case PPP_REQ_SENT_STATE:
			NewState(PPP_ACK_RCVD_STATE);
			InitializeRestartCount();
		break;
		
		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		case PPP_ACK_SENT_STATE:
			NewState(PPP_OPENED_STATE);
			InitializeRestartCount();
			locker.UnlockNow();
			ThisLayerUp();
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
				// indicates to handlers that we are reconfiguring
			locker.UnlockNow();
			ThisLayerDown();
			SendConfigureRequest();
		break;
		
		default:
			;
	}
	
	m_freem(packet);
}


// receive configure nak/reject
void
KPPPStateMachine::RCNEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RCNEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(fRequestID != mtod(packet, ppp_lcp_packet*)->id) {
		// this packet is not a reply to our request
		
		// TODO:
		// log this event
		m_freem(packet);
		return;
	}
	
	// let the option handlers parse this nak/reject
	KPPPConfigurePacket nak_reject(packet);
	KPPPOptionHandler *optionHandler;
	for(int32 index = 0; index < LCP().CountOptionHandlers(); index++) {
		optionHandler = LCP().OptionHandlerAt(index);
		
		if(nak_reject.Code() == PPP_CONFIGURE_NAK) {
			if(optionHandler->ParseNak(nak_reject) != B_OK) {
				m_freem(packet);
				locker.UnlockNow();
				CloseEvent();
				return;
			}
		} else if(nak_reject.Code() == PPP_CONFIGURE_REJECT) {
			if(optionHandler->ParseReject(nak_reject) != B_OK) {
				m_freem(packet);
				locker.UnlockNow();
				CloseEvent();
				return;
			}
		}
	}
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RCN_EVENT);
		break;
		
		case PPP_CLOSED_STATE:
		case PPP_STOPPED_STATE:
			locker.UnlockNow();
			SendTerminateAck();
		break;
		
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_SENT_STATE:
			InitializeRestartCount();
		
		case PPP_ACK_RCVD_STATE:
			if(State() == PPP_ACK_RCVD_STATE)
				NewState(PPP_REQ_SENT_STATE);
			locker.UnlockNow();
			SendConfigureRequest();
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
				// indicates to handlers that we are reconfiguring
			locker.UnlockNow();
			ThisLayerDown();
			SendConfigureRequest();
		break;
		
		default:
			;
	}
	
	m_freem(packet);
}


// receive terminate request
void
KPPPStateMachine::RTREvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RTREvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	// we should not use the same ID as the peer
	if(fID == mtod(packet, ppp_lcp_packet*)->id)
		fID -= 128;
	
	fLocalAuthenticationStatus = PPP_NOT_AUTHENTICATED;
	fPeerAuthenticationStatus = PPP_NOT_AUTHENTICATED;
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RTR_EVENT);
			m_freem(packet);
		break;
		
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
				// indicates to handlers that we are terminating
			locker.UnlockNow();
			SendTerminateAck(packet);
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_STOPPING_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
				// indicates to handlers that we are terminating
			ZeroRestartCount();
			locker.UnlockNow();
			ThisLayerDown();
			SendTerminateAck(packet);
		break;
		
		default:
			NewPhase(PPP_TERMINATION_PHASE);
				// indicates to handlers that we are terminating
			locker.UnlockNow();
			SendTerminateAck(packet);
	}
}


// receive terminate ack
void
KPPPStateMachine::RTAEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RTAEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	if(fTerminateID != mtod(packet, ppp_lcp_packet*)->id) {
		// this packet is not a reply to our request
		
		// TODO:
		// log this event
		m_freem(packet);
		return;
	}
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RTA_EVENT);
		break;
		
		case PPP_CLOSING_STATE:
			NewState(PPP_CLOSED_STATE);
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		case PPP_STOPPING_STATE:
			NewState(PPP_STOPPED_STATE);
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			NewPhase(PPP_ESTABLISHMENT_PHASE);
				// indicates to handlers that we are reconfiguring
			locker.UnlockNow();
			ThisLayerDown();
			SendConfigureRequest();
		break;
		
		default:
			;
	}
	
	m_freem(packet);
}


// receive unknown code
void
KPPPStateMachine::RUCEvent(struct mbuf *packet, uint16 protocolNumber,
	uint8 code = PPP_PROTOCOL_REJECT)
{
#if DEBUG
	dprintf("KPPPSM: RUCEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RUC_EVENT);
			m_freem(packet);
		break;
		
		default:
			locker.UnlockNow();
			SendCodeReject(packet, protocolNumber, code);
	}
}


// receive code/protocol reject (acceptable such as IPX reject)
void
KPPPStateMachine::RXJGoodEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RXJGoodEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	// This method does not m_freem(packet) because the acceptable rejects are
	// also passed to the parent. RXJEvent() will m_freem(packet) when needed.
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RXJ_GOOD_EVENT);
		break;
		
		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
		break;
		
		default:
			;
	}
}


// receive code/protocol reject (catastrophic such as LCP reject)
void
KPPPStateMachine::RXJBadEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RXJBadEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RXJ_BAD_EVENT);
		break;
		
		case PPP_CLOSING_STATE:
			NewState(PPP_CLOSED_STATE);
		
		case PPP_CLOSED_STATE:
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_STOPPED_STATE);
		
		case PPP_STOPPING_STATE:
			NewPhase(PPP_TERMINATION_PHASE);
		
		case PPP_STOPPED_STATE:
			locker.UnlockNow();
			ThisLayerFinished();
		break;
		
		case PPP_OPENED_STATE:
			NewState(PPP_STOPPING_STATE);
			NewPhase(PPP_TERMINATION_PHASE);
				// indicates to handlers that we are terminating
			InitializeRestartCount();
			locker.UnlockNow();
			ThisLayerDown();
			SendTerminateRequest();
		break;
	}
	
	m_freem(packet);
}


// receive echo request/reply, discard request
void
KPPPStateMachine::RXREvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RXREvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	ppp_lcp_packet *echo = mtod(packet, ppp_lcp_packet*);
	
	if(echo->code == PPP_ECHO_REPLY && echo->id != fEchoID) {
		// TODO:
		// log that we got a reply, but no request was sent
	}
	
	switch(State()) {
		case PPP_INITIAL_STATE:
		case PPP_STARTING_STATE:
			IllegalEvent(PPP_RXR_EVENT);
		break;
		
		case PPP_OPENED_STATE:
			if(echo->code == PPP_ECHO_REQUEST)
				SendEchoReply(packet);
		return;
			// this prevents the packet from being freed
		
		default:
			;
	}
	
	m_freem(packet);
}


// general events (for Good/Bad events)
void
KPPPStateMachine::TimerEvent()
{
#if DEBUG
	if(fNextTimeout != 0)
		dprintf("KPPPSM: TimerEvent()\n");
#endif
	
	LockerHelper locker(fLock);
	if(fNextTimeout == 0 || fNextTimeout > system_time())
		return;
	fNextTimeout = 0;
	locker.UnlockNow();
	
	switch(State()) {
		case PPP_CLOSING_STATE:
		case PPP_STOPPING_STATE:
			if(fTerminateCounter <= 0)
				TOBadEvent();
			else
				TOGoodEvent();
		break;
		
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			if(fRequestCounter <= 0)
				TOBadEvent();
			else
				TOGoodEvent();
		break;
		
		default:
			;
	}
}


// ReceiveConfigureRequest
// Here we get a configure-request packet from LCP and aks all OptionHandlers
// if its values are acceptable. From here we call our Good/Bad counterparts.
void
KPPPStateMachine::RCREvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RCREvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	KPPPConfigurePacket request(packet);
	KPPPConfigurePacket nak(PPP_CONFIGURE_NAK);
	KPPPConfigurePacket reject(PPP_CONFIGURE_REJECT);
	
	// we should not use the same id as the peer
	if(fID == mtod(packet, ppp_lcp_packet*)->id)
		fID -= 128;
	
	nak.SetID(request.ID());
	reject.SetID(request.ID());
	
	// each handler should add unacceptable values for each item
	status_t result;
		// the return value of ParseRequest()
	KPPPOptionHandler *optionHandler;
	for(int32 index = 0; index < request.CountItems(); index++) {
		optionHandler = LCP().OptionHandlerFor(request.ItemAt(index)->type);
		
		if(!optionHandler || !optionHandler->IsEnabled()) {
			dprintf("KPPPSM::RCREvent(): unknown type:%d\n", request.ItemAt(index)->type);
			// unhandled items should be added to the reject
			reject.AddItem(request.ItemAt(index));
			continue;
		}
		
#if DEBUG
		dprintf("KPPPSM::RCREvent(): OH=%s\n", optionHandler->Name());
#endif
		result = optionHandler->ParseRequest(request, index, nak, reject);
		
		if(result == PPP_UNHANDLED) {
			// unhandled items should be added to the reject
			reject.AddItem(request.ItemAt(index));
			continue;
		} else if(result != B_OK) {
			// the request contains a value that has been sent more than
			// once or the value is corrupted
			dprintf("KPPPSM::RCREvent(): OptionHandler returned parse error!\n");
			m_freem(packet);
			CloseEvent();
			return;
		}
	}
	
	// Additional values may be appended.
	// If we sent too many naks we should not append additional values.
	if(fNakCounter > 0) {
		for(int32 index = 0; index < LCP().CountOptionHandlers(); index++) {
			optionHandler = LCP().OptionHandlerAt(index);
			if(optionHandler && optionHandler->IsEnabled()) {
				result = optionHandler->ParseRequest(request, request.CountItems(),
					nak, reject);
				
				if(result != B_OK) {
					// the request contains a value that has been sent more than
					// once or the value is corrupted
					dprintf("KPPPSM::RCREvent(): OptionHandler returned append error!\n");
					m_freem(packet);
					CloseEvent();
					return;
				}
			}
		}
	}
	
	if(nak.CountItems() > 0) {
		RCRBadEvent(nak.ToMbuf(Interface().MRU(), LCP().AdditionalOverhead()), NULL);
		m_freem(packet);
	} else if(reject.CountItems() > 0) {
		RCRBadEvent(NULL, reject.ToMbuf(Interface().MRU(), LCP().AdditionalOverhead()));
		m_freem(packet);
	} else
		RCRGoodEvent(packet);
}


// ReceiveCodeReject
// LCP received a code/protocol-reject packet and we look if it is acceptable.
// From here we call our Good/Bad counterparts.
void
KPPPStateMachine::RXJEvent(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: RXJEvent() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	ppp_lcp_packet *reject = mtod(packet, ppp_lcp_packet*);
	
	if(reject->code == PPP_CODE_REJECT) {
		uint8 rejectedCode = reject->data[0];
		
		// test if the rejected code belongs to the minimum LCP requirements
		if(rejectedCode >= PPP_MIN_LCP_CODE && rejectedCode <= PPP_MAX_LCP_CODE) {
			if(Interface().IsMultilink() && !Interface().Parent()) {
				// Main interfaces do not have states between STARTING and OPENED.
				// An RXJBadEvent() would enter one of those states which is bad.
				m_freem(packet);
				CloseEvent();
			} else
				RXJBadEvent(packet);
			
			return;
		}
		
		// find the LCP extension and disable it
		KPPPLCPExtension *lcpExtension;
		for(int32 index = 0; index < LCP().CountLCPExtensions(); index++) {
			lcpExtension = LCP().LCPExtensionAt(index);
			
			if(lcpExtension->Code() == rejectedCode)
				lcpExtension->SetEnabled(false);
		}
		
		m_freem(packet);
	} else if(reject->code == PPP_PROTOCOL_REJECT) {
		// disable all handlers for rejected protocol type
		uint16 rejected = *((uint16*) reject->data);
			// rejected protocol number
		
		if(rejected == PPP_LCP_PROTOCOL) {
			// LCP must not be rejected!
			RXJBadEvent(packet);
			return;
		}
		
		// disable protocols with the rejected protocol number
		KPPPProtocol *protocol = Interface().FirstProtocol();
		for(; protocol; protocol = protocol->NextProtocol()) {
			if(protocol->ProtocolNumber() == rejected)
				protocol->SetEnabled(false);
					// disable protocol
		}
		
		RXJGoodEvent(packet);
			// this event handler does not m_freem(packet)!!!
		
		// notify parent, too
		if(Interface().Parent())
			Interface().Parent()->StateMachine().RXJEvent(packet);
		else
			m_freem(packet);
	}
}


// actions (all private)
void
KPPPStateMachine::IllegalEvent(ppp_event event)
{
	// TODO:
	// update error statistics
	dprintf("KPPPSM: IllegalEvent(event=%d) state=%d phase=%d\n",
		event, State(), Phase());
}


void
KPPPStateMachine::ThisLayerUp()
{
#if DEBUG
	dprintf("KPPPSM: ThisLayerUp() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	
	// We begin with authentication phase and wait until each phase is done.
	// We stop when we reach established phase.
	
	// Do not forget to check if we are going down.
	if(Phase() != PPP_ESTABLISHMENT_PHASE)
		return;
	
	NewPhase(PPP_AUTHENTICATION_PHASE);
	
	locker.UnlockNow();
	
	BringProtocolsUp();
}


void
KPPPStateMachine::ThisLayerDown()
{
#if DEBUG
	dprintf("KPPPSM: ThisLayerDown() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	// KPPPProtocol::Down() should block if needed.
	DownProtocols();
}


void
KPPPStateMachine::ThisLayerStarted()
{
#if DEBUG
	dprintf("KPPPSM: ThisLayerStarted() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(Interface().Device() && !Interface().Device()->Up())
		Interface().Device()->UpFailedEvent();
}


void
KPPPStateMachine::ThisLayerFinished()
{
#if DEBUG
	dprintf("KPPPSM: ThisLayerFinished() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(Interface().Device())
		Interface().Device()->Down();
}


void
KPPPStateMachine::InitializeRestartCount()
{
	fRequestCounter = fMaxRequest;
	fTerminateCounter = fMaxTerminate;
	fNakCounter = fMaxNak;
}


void
KPPPStateMachine::ZeroRestartCount()
{
	fRequestCounter = 0;
	fTerminateCounter = 0;
	fNakCounter = 0;
}


bool
KPPPStateMachine::SendConfigureRequest()
{
#if DEBUG
	dprintf("KPPPSM: SendConfigureRequest() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	--fRequestCounter;
	fNextTimeout = system_time() + PPP_STATE_MACHINE_TIMEOUT;
	locker.UnlockNow();
	
	KPPPConfigurePacket request(PPP_CONFIGURE_REQUEST);
	request.SetID(NextID());
	fRequestID = request.ID();
	
	for(int32 index = 0; index < LCP().CountOptionHandlers(); index++) {
		// add all items
		if(LCP().OptionHandlerAt(index)->AddToRequest(request) != B_OK) {
			CloseEvent();
			return false;
		}
	}
	
	return LCP().Send(request.ToMbuf(Interface().MRU(),
		LCP().AdditionalOverhead())) == B_OK;
}


bool
KPPPStateMachine::SendConfigureAck(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: SendConfigureAck() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(!packet)
		return false;
	
	mtod(packet, ppp_lcp_packet*)->code = PPP_CONFIGURE_ACK;
	KPPPConfigurePacket ack(packet);
	
	// notify all option handlers that we are sending an ack for each value
	for(int32 index = 0; index < LCP().CountOptionHandlers(); index++) {
		if(LCP().OptionHandlerAt(index)->SendingAck(ack) != B_OK) {
			m_freem(packet);
			CloseEvent();
			return false;
		}
	}
	
	return LCP().Send(packet) == B_OK;
}


bool
KPPPStateMachine::SendConfigureNak(struct mbuf *packet)
{
#if DEBUG
	dprintf("KPPPSM: SendConfigureNak() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(!packet)
		return false;
	
	ppp_lcp_packet *nak = mtod(packet, ppp_lcp_packet*);
	if(nak->code == PPP_CONFIGURE_NAK) {
		if(fNakCounter == 0) {
			// We sent enough naks. Let's try a reject.
			nak->code = PPP_CONFIGURE_REJECT;
		} else
			--fNakCounter;
	}
	
	return LCP().Send(packet) == B_OK;
}


bool
KPPPStateMachine::SendTerminateRequest()
{
#if DEBUG
	dprintf("KPPPSM: SendTerminateRequest() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	LockerHelper locker(fLock);
	--fTerminateCounter;
	fNextTimeout = system_time() + PPP_STATE_MACHINE_TIMEOUT;
	locker.UnlockNow();
	
	struct mbuf *packet = m_gethdr(MT_DATA);
	if(!packet)
		return false;
	
	packet->m_pkthdr.len = packet->m_len = 4;
	
	// reserve some space for other protocols
	packet->m_data += LCP().AdditionalOverhead();
	
	ppp_lcp_packet *request = mtod(packet, ppp_lcp_packet*);
	request->code = PPP_TERMINATE_REQUEST;
	request->id = fTerminateID = NextID();
	request->length = htons(4);
	
	return LCP().Send(packet) == B_OK;
}


bool
KPPPStateMachine::SendTerminateAck(struct mbuf *request = NULL)
{
#if DEBUG
	dprintf("KPPPSM: SendTerminateAck() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	struct mbuf *reply = request;
	
	ppp_lcp_packet *ack;
	
	if(!reply) {
		reply = m_gethdr(MT_DATA);
		if(!reply)
			return false;
		
		reply->m_data += LCP().AdditionalOverhead();
		reply->m_pkthdr.len = reply->m_len = 4;
		
		ack = mtod(reply, ppp_lcp_packet*);
		ack->id = NextID();
	} else
		ack = mtod(reply, ppp_lcp_packet*);
	
	ack->code = PPP_TERMINATE_ACK;
	ack->length = htons(4);
	
	return LCP().Send(reply) == B_OK;
}


bool
KPPPStateMachine::SendCodeReject(struct mbuf *packet, uint16 protocolNumber, uint8 code)
{
#if DEBUG
	dprintf("KPPPSM: SendCodeReject(protocolNumber=%X;code=%d) state=%d phase=%d\n",
		protocolNumber, code, State(), Phase());
#endif
	
	if(!packet)
		return false;
	
	int32 length;
		// additional space needed for this reject
	if(code == PPP_PROTOCOL_REJECT)
		length = 6;
	else
		length = 4;
	
	M_PREPEND(packet, length);
		// add some space for the header
	
	// adjust packet if too big
	int32 adjust = Interface().MRU();
	if(packet->m_flags & M_PKTHDR) {
		adjust -= packet->m_pkthdr.len;
	} else
		adjust -= packet->m_len;
	
	if(adjust < 0)
		m_adj(packet, adjust);
	
	ppp_lcp_packet *reject = mtod(packet, ppp_lcp_packet*);
	reject->code = code;
	reject->id = NextID();
	if(packet->m_flags & M_PKTHDR)
		reject->length = htons(packet->m_pkthdr.len);
	else
		reject->length = htons(packet->m_len);
	
	protocolNumber = htons(protocolNumber);
	if(code == PPP_PROTOCOL_REJECT)
		memcpy(&reject->data, &protocolNumber, sizeof(protocolNumber));
	
	return LCP().Send(packet) == B_OK;
}


bool
KPPPStateMachine::SendEchoReply(struct mbuf *request)
{
#if DEBUG
	dprintf("KPPPSM: SendEchoReply() state=%d phase=%d\n",
		State(), Phase());
#endif
	
	if(!request)
		return false;
	
	ppp_lcp_packet *reply = mtod(request, ppp_lcp_packet*);
	reply->code = PPP_ECHO_REPLY;
		// the request becomes a reply
	
	if(request->m_flags & M_PKTHDR)
		request->m_pkthdr.len = 8;
	request->m_len = 8;
	
	memcpy(reply->data, &fMagicNumber, sizeof(fMagicNumber));
	
	return LCP().Send(request) == B_OK;
}


// methods for bringing protocols up
void
KPPPStateMachine::BringProtocolsUp()
{
	// use a simple check for phase changes (e.g., caused by CloseEvent())
	while(Phase() <= PPP_ESTABLISHED_PHASE && Phase() >= PPP_AUTHENTICATION_PHASE) {
		if(BringPhaseUp() > 0)
			break;
		
		LockerHelper locker(fLock);
		
		if(Phase() < PPP_AUTHENTICATION_PHASE)
			return;
				// phase was changed by another event
		else if(Phase() == PPP_ESTABLISHED_PHASE) {
			if(Interface().Parent())
				Interface().Parent()->StateMachine().UpEvent(Interface());
			break;
		} else
			NewPhase((ppp_phase) (Phase() + 1));
	}
}


// this returns the number of handlers waiting to go up
uint32
KPPPStateMachine::BringPhaseUp()
{
	// Servers do not need to bring all protocols up.
	// The client specifies which protocols he wants to go up.
	
	LockerHelper locker(fLock);
	
	// check for phase change
	if(Phase() < PPP_AUTHENTICATION_PHASE)
		return 0;
	
	uint32 count = 0;
	KPPPProtocol *protocol = Interface().FirstProtocol();
	for(; protocol; protocol = protocol->NextProtocol()) {
		if(protocol->IsEnabled() && protocol->ActivationPhase() == Phase()) {
			if(protocol->IsGoingUp() && Interface().Mode() == PPP_CLIENT_MODE)
				++count;
			else if(protocol->IsDown() && protocol->IsUpRequested()) {
				if(Interface().Mode() == PPP_CLIENT_MODE)
					++count;
				
				protocol->Up();
			}
		}
	}
	
	// We only wait until authentication is complete.
	if(Interface().Mode() == PPP_SERVER_MODE
			&& (LocalAuthenticationStatus() == PPP_AUTHENTICATING
			|| PeerAuthenticationStatus() == PPP_AUTHENTICATING))
		++count;
	
	return count;
}


void
KPPPStateMachine::DownProtocols()
{
	KPPPProtocol *protocol = Interface().FirstProtocol();
	
	for(; protocol; protocol = protocol->NextProtocol())
		if(protocol->IsEnabled())
			protocol->Down();
}


void
KPPPStateMachine::ResetLCPHandlers()
{
	for(int32 index = 0; index < LCP().CountOptionHandlers(); index++)
		LCP().OptionHandlerAt(index)->Reset();
	
	for(int32 index = 0; index < LCP().CountLCPExtensions(); index++)
		LCP().LCPExtensionAt(index)->Reset();
}
