#include "netif/ppp/ppp_opts.h"
#if PPP_SUPPORT
#if 0#endif
#include "netif/ppp/ppp_impl.h"
#include "netif/ppp/fsm.h"
static void fsm_timeout (void *);
static void fsm_rconfreq(fsm *f, u_char id, u_char *inp, int len);
static void fsm_rconfack(fsm *f, int id, u_char *inp, int len);
static void fsm_rconfnakrej(fsm *f, int code, int id, u_char *inp, int len);
static void fsm_rtermreq(fsm *f, int id, u_char *p, int len);
static void fsm_rtermack(fsm *f);
static void fsm_rcoderej(fsm *f, u_char *inp, int len);
static void fsm_sconfreq(fsm *f, int retransmit);
#define PROTO_NAME(f) ((f)->callbacks->proto_name)
void fsm_init(fsm *f) {
ppp_pcb *pcb = f->pcb;
f->state = PPP_FSM_INITIAL;
f->flags = 0;
f->id = 0;
f->maxnakloops = pcb->settings.fsm_max_nak_loops;
f->term_reason_len = 0;
}
void fsm_lowerup(fsm *f) {
switch( f->state ){
case PPP_FSM_INITIAL:
f->state = PPP_FSM_CLOSED;
break;
case PPP_FSM_STARTING:
if( f->flags & OPT_SILENT )
f->state = PPP_FSM_STOPPED;
else {
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
}
break;
default:
FSMDEBUG(("%s: Up event in state %d!", PROTO_NAME(f), f->state));
}
}
void fsm_lowerdown(fsm *f) {
switch( f->state ){
case PPP_FSM_CLOSED:
f->state = PPP_FSM_INITIAL;
break;
case PPP_FSM_STOPPED:
f->state = PPP_FSM_STARTING;
if( f->callbacks->starting )
(*f->callbacks->starting)(f);
break;
case PPP_FSM_CLOSING:
f->state = PPP_FSM_INITIAL;
UNTIMEOUT(fsm_timeout, f);
break;
case PPP_FSM_STOPPING:
case PPP_FSM_REQSENT:
case PPP_FSM_ACKRCVD:
case PPP_FSM_ACKSENT:
f->state = PPP_FSM_STARTING;
UNTIMEOUT(fsm_timeout, f);
break;
case PPP_FSM_OPENED:
if( f->callbacks->down )
(*f->callbacks->down)(f);
f->state = PPP_FSM_STARTING;
break;
default:
FSMDEBUG(("%s: Down event in state %d!", PROTO_NAME(f), f->state));
}
}
void fsm_open(fsm *f) {
switch( f->state ){
case PPP_FSM_INITIAL:
f->state = PPP_FSM_STARTING;
if( f->callbacks->starting )
(*f->callbacks->starting)(f);
break;
case PPP_FSM_CLOSED:
if( f->flags & OPT_SILENT )
f->state = PPP_FSM_STOPPED;
else {
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
}
break;
case PPP_FSM_CLOSING:
f->state = PPP_FSM_STOPPING;
case PPP_FSM_STOPPED:
case PPP_FSM_OPENED:
if( f->flags & OPT_RESTART ){
fsm_lowerdown(f);
fsm_lowerup(f);
}
break;
default:
break;
}
}
static void terminate_layer(fsm *f, int nextstate) {
ppp_pcb *pcb = f->pcb;
if( f->state != PPP_FSM_OPENED )
UNTIMEOUT(fsm_timeout, f);
else if( f->callbacks->down )
(*f->callbacks->down)(f);
f->retransmits = pcb->settings.fsm_max_term_transmits;
fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
(const u_char *) f->term_reason, f->term_reason_len);
if (f->retransmits == 0) {
f->state = nextstate == PPP_FSM_CLOSING ? PPP_FSM_CLOSED : PPP_FSM_STOPPED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
return;
}
TIMEOUT(fsm_timeout, f, pcb->settings.fsm_timeout_time);
--f->retransmits;
f->state = nextstate;
}
void fsm_close(fsm *f, const char *reason) {
f->term_reason = reason;
f->term_reason_len = (reason == NULL? 0: (u8_t)LWIP_MIN(strlen(reason), 0xFF) );
switch( f->state ){
case PPP_FSM_STARTING:
f->state = PPP_FSM_INITIAL;
break;
case PPP_FSM_STOPPED:
f->state = PPP_FSM_CLOSED;
break;
case PPP_FSM_STOPPING:
f->state = PPP_FSM_CLOSING;
break;
case PPP_FSM_REQSENT:
case PPP_FSM_ACKRCVD:
case PPP_FSM_ACKSENT:
case PPP_FSM_OPENED:
terminate_layer(f, PPP_FSM_CLOSING);
break;
default:
break;
}
}
static void fsm_timeout(void *arg) {
fsm *f = (fsm *) arg;
ppp_pcb *pcb = f->pcb;
switch (f->state) {
case PPP_FSM_CLOSING:
case PPP_FSM_STOPPING:
if( f->retransmits <= 0 ){
f->state = (f->state == PPP_FSM_CLOSING)? PPP_FSM_CLOSED: PPP_FSM_STOPPED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
} else {
fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
(const u_char *) f->term_reason, f->term_reason_len);
TIMEOUT(fsm_timeout, f, pcb->settings.fsm_timeout_time);
--f->retransmits;
}
break;
case PPP_FSM_REQSENT:
case PPP_FSM_ACKRCVD:
case PPP_FSM_ACKSENT:
if (f->retransmits <= 0) {
ppp_warn(("%s: timeout sending Config-Requests", PROTO_NAME(f)));
f->state = PPP_FSM_STOPPED;
if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
(*f->callbacks->finished)(f);
} else {
if (f->callbacks->retransmit)
(*f->callbacks->retransmit)(f);
fsm_sconfreq(f, 1);
if( f->state == PPP_FSM_ACKRCVD )
f->state = PPP_FSM_REQSENT;
}
break;
default:
FSMDEBUG(("%s: Timeout event in state %d!", PROTO_NAME(f), f->state));
}
}
void fsm_input(fsm *f, u_char *inpacket, int l) {
u_char *inp;
u_char code, id;
int len;
inp = inpacket;
if (l < HEADERLEN) {
FSMDEBUG(("fsm_input(%x): Rcvd short header.", f->protocol));
return;
}
GETCHAR(code, inp);
GETCHAR(id, inp);
GETSHORT(len, inp);
if (len < HEADERLEN) {
FSMDEBUG(("fsm_input(%x): Rcvd illegal length.", f->protocol));
return;
}
if (len > l) {
FSMDEBUG(("fsm_input(%x): Rcvd short packet.", f->protocol));
return;
}
len -= HEADERLEN;
if( f->state == PPP_FSM_INITIAL || f->state == PPP_FSM_STARTING ){
FSMDEBUG(("fsm_input(%x): Rcvd packet in state %d.",
f->protocol, f->state));
return;
}
switch (code) {
case CONFREQ:
fsm_rconfreq(f, id, inp, len);
break;
case CONFACK:
fsm_rconfack(f, id, inp, len);
break;
case CONFNAK:
case CONFREJ:
fsm_rconfnakrej(f, code, id, inp, len);
break;
case TERMREQ:
fsm_rtermreq(f, id, inp, len);
break;
case TERMACK:
fsm_rtermack(f);
break;
case CODEREJ:
fsm_rcoderej(f, inp, len);
break;
default:
if( !f->callbacks->extcode
|| !(*f->callbacks->extcode)(f, code, id, inp, len) )
fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
break;
}
}
static void fsm_rconfreq(fsm *f, u_char id, u_char *inp, int len) {
int code, reject_if_disagree;
switch( f->state ){
case PPP_FSM_CLOSED:
fsm_sdata(f, TERMACK, id, NULL, 0);
return;
case PPP_FSM_CLOSING:
case PPP_FSM_STOPPING:
return;
case PPP_FSM_OPENED:
if( f->callbacks->down )
(*f->callbacks->down)(f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
case PPP_FSM_STOPPED:
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
default:
break;
}
if (f->callbacks->reqci){
reject_if_disagree = (f->nakloops >= f->maxnakloops);
code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
} else if (len)
code = CONFREJ;
else
code = CONFACK;
fsm_sdata(f, code, id, inp, len);
if (code == CONFACK) {
if (f->state == PPP_FSM_ACKRCVD) {
UNTIMEOUT(fsm_timeout, f);
f->state = PPP_FSM_OPENED;
if (f->callbacks->up)
(*f->callbacks->up)(f);
} else
f->state = PPP_FSM_ACKSENT;
f->nakloops = 0;
} else {
if (f->state != PPP_FSM_ACKRCVD)
f->state = PPP_FSM_REQSENT;
if( code == CONFNAK )
++f->nakloops;
}
}
static void fsm_rconfack(fsm *f, int id, u_char *inp, int len) {
ppp_pcb *pcb = f->pcb;
if (id != f->reqid || f->seen_ack)
return;
if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
(len == 0)) ){
ppp_error(("Received bad configure-ack: %P", inp, len));
return;
}
f->seen_ack = 1;
f->rnakloops = 0;
switch (f->state) {
case PPP_FSM_CLOSED:
case PPP_FSM_STOPPED:
fsm_sdata(f, TERMACK, id, NULL, 0);
break;
case PPP_FSM_REQSENT:
f->state = PPP_FSM_ACKRCVD;
f->retransmits = pcb->settings.fsm_max_conf_req_transmits;
break;
case PPP_FSM_ACKRCVD:
UNTIMEOUT(fsm_timeout, f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
case PPP_FSM_ACKSENT:
UNTIMEOUT(fsm_timeout, f);
f->state = PPP_FSM_OPENED;
f->retransmits = pcb->settings.fsm_max_conf_req_transmits;
if (f->callbacks->up)
(*f->callbacks->up)(f);
break;
case PPP_FSM_OPENED:
if (f->callbacks->down)
(*f->callbacks->down)(f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
default:
break;
}
}
static void fsm_rconfnakrej(fsm *f, int code, int id, u_char *inp, int len) {
int ret;
int treat_as_reject;
if (id != f->reqid || f->seen_ack)
return;
if (code == CONFNAK) {
++f->rnakloops;
treat_as_reject = (f->rnakloops >= f->maxnakloops);
if (f->callbacks->nakci == NULL
|| !(ret = f->callbacks->nakci(f, inp, len, treat_as_reject))) {
ppp_error(("Received bad configure-nak: %P", inp, len));
return;
}
} else {
f->rnakloops = 0;
if (f->callbacks->rejci == NULL
|| !(ret = f->callbacks->rejci(f, inp, len))) {
ppp_error(("Received bad configure-rej: %P", inp, len));
return;
}
}
f->seen_ack = 1;
switch (f->state) {
case PPP_FSM_CLOSED:
case PPP_FSM_STOPPED:
fsm_sdata(f, TERMACK, id, NULL, 0);
break;
case PPP_FSM_REQSENT:
case PPP_FSM_ACKSENT:
UNTIMEOUT(fsm_timeout, f);
if (ret < 0)
f->state = PPP_FSM_STOPPED;
else
fsm_sconfreq(f, 0);
break;
case PPP_FSM_ACKRCVD:
UNTIMEOUT(fsm_timeout, f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
case PPP_FSM_OPENED:
if (f->callbacks->down)
(*f->callbacks->down)(f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
default:
break;
}
}
static void fsm_rtermreq(fsm *f, int id, u_char *p, int len) {
ppp_pcb *pcb = f->pcb;
switch (f->state) {
case PPP_FSM_ACKRCVD:
case PPP_FSM_ACKSENT:
f->state = PPP_FSM_REQSENT;
break;
case PPP_FSM_OPENED:
if (len > 0) {
ppp_info(("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p));
} else
ppp_info(("%s terminated by peer", PROTO_NAME(f)));
f->retransmits = 0;
f->state = PPP_FSM_STOPPING;
if (f->callbacks->down)
(*f->callbacks->down)(f);
TIMEOUT(fsm_timeout, f, pcb->settings.fsm_timeout_time);
break;
default:
break;
}
fsm_sdata(f, TERMACK, id, NULL, 0);
}
static void fsm_rtermack(fsm *f) {
switch (f->state) {
case PPP_FSM_CLOSING:
UNTIMEOUT(fsm_timeout, f);
f->state = PPP_FSM_CLOSED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
break;
case PPP_FSM_STOPPING:
UNTIMEOUT(fsm_timeout, f);
f->state = PPP_FSM_STOPPED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
break;
case PPP_FSM_ACKRCVD:
f->state = PPP_FSM_REQSENT;
break;
case PPP_FSM_OPENED:
if (f->callbacks->down)
(*f->callbacks->down)(f);
fsm_sconfreq(f, 0);
f->state = PPP_FSM_REQSENT;
break;
default:
break;
}
}
static void fsm_rcoderej(fsm *f, u_char *inp, int len) {
u_char code, id;
if (len < HEADERLEN) {
FSMDEBUG(("fsm_rcoderej: Rcvd short Code-Reject packet!"));
return;
}
GETCHAR(code, inp);
GETCHAR(id, inp);
ppp_warn(("%s: Rcvd Code-Reject for code %d, id %d", PROTO_NAME(f), code, id));
if( f->state == PPP_FSM_ACKRCVD )
f->state = PPP_FSM_REQSENT;
}
void fsm_protreject(fsm *f) {
switch( f->state ){
case PPP_FSM_CLOSING:
UNTIMEOUT(fsm_timeout, f);
case PPP_FSM_CLOSED:
f->state = PPP_FSM_CLOSED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
break;
case PPP_FSM_STOPPING:
case PPP_FSM_REQSENT:
case PPP_FSM_ACKRCVD:
case PPP_FSM_ACKSENT:
UNTIMEOUT(fsm_timeout, f);
case PPP_FSM_STOPPED:
f->state = PPP_FSM_STOPPED;
if( f->callbacks->finished )
(*f->callbacks->finished)(f);
break;
case PPP_FSM_OPENED:
terminate_layer(f, PPP_FSM_STOPPING);
break;
default:
FSMDEBUG(("%s: Protocol-reject event in state %d!",
PROTO_NAME(f), f->state));
}
}
static void fsm_sconfreq(fsm *f, int retransmit) {
ppp_pcb *pcb = f->pcb;
struct pbuf *p;
u_char *outp;
int cilen;
if( f->state != PPP_FSM_REQSENT && f->state != PPP_FSM_ACKRCVD && f->state != PPP_FSM_ACKSENT ){
if( f->callbacks->resetci )
(*f->callbacks->resetci)(f);
f->nakloops = 0;
f->rnakloops = 0;
}
if( !retransmit ){
f->retransmits = pcb->settings.fsm_max_conf_req_transmits;
f->reqid = ++f->id;
}
f->seen_ack = 0;
if( f->callbacks->cilen && f->callbacks->addci ){
cilen = (*f->callbacks->cilen)(f);
if( cilen > pcb->peer_mru - HEADERLEN )
cilen = pcb->peer_mru - HEADERLEN;
} else
cilen = 0;
p = pbuf_alloc(PBUF_RAW, (u16_t)(cilen + HEADERLEN + PPP_HDRLEN), PBUF_RAM);
if(NULL == p)
return;
if(p->tot_len != p->len) {
pbuf_free(p);
return;
}
outp = (u_char*)p->payload;
MAKEHEADER(outp, f->protocol);
PUTCHAR(CONFREQ, outp);
PUTCHAR(f->reqid, outp);
PUTSHORT(cilen + HEADERLEN, outp);
if (cilen != 0) {
(*f->callbacks->addci)(f, outp, &cilen);
LWIP_ASSERT("cilen == p->len - HEADERLEN - PPP_HDRLEN", cilen == p->len - HEADERLEN - PPP_HDRLEN);
}
ppp_write(pcb, p);
--f->retransmits;
TIMEOUT(fsm_timeout, f, pcb->settings.fsm_timeout_time);
}
void fsm_sdata(fsm *f, u_char code, u_char id, const u_char *data, int datalen) {
ppp_pcb *pcb = f->pcb;
struct pbuf *p;
u_char *outp;
int outlen;
if (datalen > pcb->peer_mru - HEADERLEN)
datalen = pcb->peer_mru - HEADERLEN;
outlen = datalen + HEADERLEN;
p = pbuf_alloc(PBUF_RAW, (u16_t)(outlen + PPP_HDRLEN), PBUF_RAM);
if(NULL == p)
return;
if(p->tot_len != p->len) {
pbuf_free(p);
return;
}
outp = (u_char*)p->payload;
if (datalen && data != NULL) {
MEMCPY(outp + PPP_HDRLEN + HEADERLEN, data, datalen);
}
MAKEHEADER(outp, f->protocol);
PUTCHAR(code, outp);
PUTCHAR(id, outp);
PUTSHORT(outlen, outp);
ppp_write(pcb, p);
}
#endif