Generic PPP control protocol support
authorKristen Carlson Accardi <kristen@linux.intel.com>
Tue, 23 Mar 2010 00:05:57 +0000 (17:05 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Tue, 23 Mar 2010 00:28:23 +0000 (17:28 -0700)
Implement a generic protocol that can be shared by both the LCP and the
NCP implementation.

Makefile.am
gatchat/ppp.h
gatchat/ppp_cp.c [new file with mode: 0644]
gatchat/ppp_cp.h [new file with mode: 0644]

index 6891d16..a58f10e 100644 (file)
@@ -57,7 +57,8 @@ gatchat_sources = gatchat/gatchat.h gatchat/gatchat.c \
                                gatchat/gat.h \
                                gatchat/gatserver.h gatchat/gatserver.c \
                                gatchat/gatppp.c gatchat/gatppp.h \
-                               gatchat/ppp.c gatchat/ppp.h
+                               gatchat/ppp.c gatchat/ppp.h gatchat/ppp_cp.h \
+                               gatchat/ppp_cp.c
 
 udev_files = plugins/ofono.rules
 
index 573c967..0f39440 100644 (file)
@@ -18,6 +18,8 @@
  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+#include "ppp_cp.h"
+
 #define DEFAULT_MRU    1500
 #define BUFFERSZ       DEFAULT_MRU*2
 #define DEFAULT_ACCM   0x00000000
diff --git a/gatchat/ppp_cp.c b/gatchat/ppp_cp.c
new file mode 100644 (file)
index 0000000..6967f9d
--- /dev/null
@@ -0,0 +1,1503 @@
+/*
+ *
+ *  PPP library with GLib integration
+ *
+ *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <glib.h>
+#include <arpa/inet.h>
+#include "gatppp.h"
+#include "ppp.h"
+
+#ifdef DEBUG
+const char *pppcp_state_strings[] =
+       {"INITIAL", "STARTING", "CLOSED", "STOPPED", "CLOSING", "STOPPING",
+       "REQSENT", "ACKRCVD", "ACKSENT", "OPENED" };
+
+#define pppcp_trace(p) \
+       (g_print("%s: current state %d:%s\n", __FUNCTION__, \
+               p->state, pppcp_state_strings[p->state]))
+#else
+#define pppcp_trace(p)
+#endif
+
+#define pppcp_to_ppp_packet(p) \
+       (p-PPP_HEADROOM)
+
+struct pppcp_event {
+       enum pppcp_event_type type;
+       gint len;
+       guint8 data[0];
+};
+
+#define INITIAL_RESTART_TIMEOUT        3000
+#define MAX_TERMINATE          2
+#define MAX_CONFIGURE          10
+#define MAX_FAILURE            5
+#define CP_HEADER_SZ           4
+
+static struct pppcp_packet *pppcp_packet_new(struct pppcp_data *data,
+                                               guint type, guint bufferlen)
+{
+       struct pppcp_packet *packet;
+       struct ppp_header *ppp_packet;
+       guint16 packet_length = bufferlen + sizeof(*packet);
+
+       ppp_packet = g_try_malloc0(packet_length + 2);
+       if (!ppp_packet)
+               return NULL;
+
+       /* add our protocol information */
+       ppp_packet->proto = htons(data->proto);
+
+       /* advance past protocol to add CP header information */
+       packet = (struct pppcp_packet *) (ppp_packet->info);
+
+       packet->length = htons(packet_length);
+       packet->code = type;
+       return packet;
+}
+
+static gboolean pppcp_timeout(gpointer user_data)
+{
+       struct pppcp_data *data = user_data;
+
+       pppcp_trace(data);
+
+       data->restart_timer = 0;
+
+       if (data->restart_counter)
+               pppcp_generate_event(data, TO_PLUS, NULL, 0);
+       else
+               pppcp_generate_event(data, TO_MINUS, NULL, 0);
+       return FALSE;
+}
+
+static void pppcp_start_timer(struct pppcp_data *data)
+{
+       data->restart_timer = g_timeout_add(data->restart_interval,
+                               pppcp_timeout, data);
+}
+
+static void pppcp_stop_timer(struct pppcp_data *data)
+{
+       if (data->restart_timer) {
+               g_source_remove(data->restart_timer);
+               data->restart_timer = 0;
+       }
+}
+
+static gboolean pppcp_timer_is_running(struct pppcp_data *data)
+{
+       /* determine if the restart timer is running */
+       if (data->restart_timer)
+               return TRUE;
+       return FALSE;
+}
+
+static struct pppcp_event *pppcp_event_new(enum pppcp_event_type type,
+                                       gpointer event_data, guint len)
+{
+       struct pppcp_event *event;
+       guint8 *data = event_data;
+
+       event = g_try_malloc0(sizeof(struct pppcp_event) + len);
+       if (!event)
+               return NULL;
+
+       event->type = type;
+       memcpy(event->data, data, len);
+       event->len = len;
+       return event;
+}
+
+static struct pppcp_event *pppcp_get_event(struct pppcp_data *data)
+{
+       return g_queue_pop_head(data->event_queue);
+}
+
+/* actions */
+/* log an illegal event, but otherwise do nothing */
+static void pppcp_illegal_event(guint8 state, guint8 type)
+{
+       g_printerr("Illegal event %d while in state %d\n", type, state);
+}
+
+static void pppcp_this_layer_up(struct pppcp_data *data)
+{
+       struct pppcp_action *action = data->action;
+
+       if (action->this_layer_up)
+               action->this_layer_up(data);
+}
+
+static void pppcp_this_layer_down(struct pppcp_data *data)
+{
+       struct pppcp_action *action = data->action;
+
+       if (action->this_layer_up)
+               action->this_layer_down(data);
+}
+
+static void pppcp_this_layer_started(struct pppcp_data *data)
+{
+       struct pppcp_action *action = data->action;
+
+       if (action->this_layer_up)
+               action->this_layer_started(data);
+}
+
+static void pppcp_this_layer_finished(struct pppcp_data *data)
+{
+       struct pppcp_action *action = data->action;
+
+       if (action->this_layer_up)
+               action->this_layer_finished(data);
+}
+
+static void pppcp_free_option(gpointer data, gpointer user_data)
+{
+       struct ppp_option *option = data;
+       g_free(option);
+}
+
+static void pppcp_clear_options(struct pppcp_data *data)
+{
+       g_list_foreach(data->acceptable_options, pppcp_free_option, NULL);
+       g_list_foreach(data->unacceptable_options, pppcp_free_option, NULL);
+       g_list_foreach(data->rejected_options, pppcp_free_option, NULL);
+       g_list_free(data->acceptable_options);
+       g_list_free(data->unacceptable_options);
+       g_list_free(data->rejected_options);
+       data->acceptable_options = NULL;
+       data->unacceptable_options = NULL;
+       data->rejected_options = NULL;
+}
+
+/*
+ * set the restart counter to either max-terminate
+ * or max-configure.  The counter is decremented for
+ * each transmission, including the first.
+ */
+static void pppcp_initialize_restart_count(struct pppcp_data *data, guint value)
+{
+       pppcp_trace(data);
+       pppcp_clear_options(data);
+       data->restart_counter = value;
+}
+
+/*
+ * set restart counter to zero
+ */
+static void pppcp_zero_restart_count(struct pppcp_data *data)
+{
+       data->restart_counter = 0;
+}
+
+/*
+ * TBD - generate new identifier for packet
+ */
+static guint8 new_identity(struct pppcp_data *data, guint prev_identifier)
+{
+       return prev_identifier+1;
+}
+
+static void get_option_length(gpointer data, gpointer user_data)
+{
+       struct ppp_option *option = data;
+       guint8 *length = user_data;
+
+       *length += option->length;
+}
+
+static void copy_option(gpointer data, gpointer user_data)
+{
+       struct ppp_option *option = data;
+       guint8 **location = user_data;
+       memcpy(*location, (guint8 *) option, option->length);
+       *location += option->length;
+}
+
+void pppcp_add_config_option(struct pppcp_data *data, struct ppp_option *option)
+{
+       data->config_options = g_list_append(data->config_options, option);
+}
+
+/*
+ * transmit a Configure-Request packet
+ * start the restart timer
+ * decrement the restart counter
+ */
+static void pppcp_send_configure_request(struct pppcp_data *data)
+{
+       struct pppcp_packet *packet;
+       guint8 olength = 0;
+       guint8 *odata;
+
+       pppcp_trace(data);
+
+       /* figure out how much space to allocate for options */
+       g_list_foreach(data->config_options, get_option_length, &olength);
+
+       packet = pppcp_packet_new(data, CONFIGURE_REQUEST, olength);
+
+       /* copy config options into packet data */
+       odata = packet->data;
+       g_list_foreach(data->config_options, copy_option, &odata);
+
+       /*
+        * if this is the first request, we need a new identifier.
+        * if this is a retransmission, leave the identifier alone.
+        */
+       if (data->restart_counter == data->max_configure)
+               data->config_identifier =
+                       new_identity(data, data->config_identifier);
+       packet->identifier = data->config_identifier;
+
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+
+       /* XXX don't retransmit right now */
+#if 0
+       data->restart_counter--;
+       pppcp_start_timer(data);
+#endif
+}
+
+/*
+ * transmit a Configure-Ack packet
+ */
+static void pppcp_send_configure_ack(struct pppcp_data *data,
+                                       guint8 *request)
+{
+       struct pppcp_packet *packet;
+       struct pppcp_packet *pppcp_header = (struct pppcp_packet *) request;
+       guint len;
+       guint8 *odata;
+
+       pppcp_trace(data);
+
+       /* subtract for header. */
+       len = ntohs(pppcp_header->length) - sizeof(*packet);
+
+       packet = pppcp_packet_new(data, CONFIGURE_ACK, len);
+
+       /* copy the applied options in. */
+       odata = packet->data;
+
+       if (g_list_length(data->acceptable_options))
+               g_list_foreach(data->acceptable_options, copy_option, &odata);
+
+       /* match identifier of the request */
+       packet->identifier = pppcp_header->identifier;
+
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+}
+
+/*
+ * transmit a Configure-Nak or Configure-Reject packet
+ */
+static void pppcp_send_configure_nak(struct pppcp_data *data,
+                                       guint8 *configure_packet)
+{
+       struct pppcp_packet *packet;
+       struct pppcp_packet *pppcp_header =
+                       (struct pppcp_packet *) configure_packet;
+       guint8 olength = 0;
+       guint8 *odata;
+
+       /* if we have any rejected options, send a config-reject */
+       if (g_list_length(data->rejected_options)) {
+               /* figure out how much space to allocate for options */
+               g_list_foreach(data->rejected_options, get_option_length,
+                               &olength);
+
+               packet = pppcp_packet_new(data, CONFIGURE_REJECT, olength);
+
+               /* copy the rejected options in. */
+               odata = packet->data;
+               g_list_foreach(data->rejected_options, copy_option,
+                               &odata);
+
+               packet->identifier = pppcp_header->identifier;
+               ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+
+       }
+       /* if we have any unacceptable options, send a config-nak */
+       if (g_list_length(data->unacceptable_options)) {
+               olength = 0;
+
+               /* figure out how much space to allocate for options */
+               g_list_foreach(data->unacceptable_options, get_option_length,
+                               &olength);
+
+               packet = pppcp_packet_new(data, CONFIGURE_NAK, olength);
+
+               /* copy the unacceptable options in. */
+               odata = packet->data;
+               g_list_foreach(data->unacceptable_options, copy_option,
+                               &odata);
+
+               packet->identifier = pppcp_header->identifier;
+               ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                               ntohs(packet->length));
+       }
+}
+
+/*
+ * transmit a Terminate-Request packet.
+ * start the restart timer.
+ * decrement the restart counter
+ */
+static void pppcp_send_terminate_request(struct pppcp_data *data)
+{
+       struct pppcp_packet *packet;
+
+       /*
+        * the data field can be used by the sender (us).
+        * leave this empty for now.
+        */
+       packet = pppcp_packet_new(data, TERMINATE_REQUEST, 0);
+
+       /*
+        * Is this a retransmission?  If so, do not change
+        * the identifier.  If not, we need a fresh identity.
+        */
+       if (data->restart_counter == data->max_terminate)
+               data->terminate_identifier =
+                       new_identity(data, data->terminate_identifier);
+       packet->identifier = data->terminate_identifier;
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+       data->restart_counter--;
+       pppcp_start_timer(data);
+}
+
+/*
+ * transmit a Terminate-Ack packet
+ */
+static void pppcp_send_terminate_ack(struct pppcp_data *data,
+                                       guint8 *request)
+{
+       struct pppcp_packet *packet;
+       struct pppcp_packet *pppcp_header = (struct pppcp_packet *) request;
+
+       packet = pppcp_packet_new(data, TERMINATE_ACK, 0);
+
+       /* match identifier of the request */
+       packet->identifier = pppcp_header->identifier;
+
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(pppcp_header->length));
+}
+
+/*
+ * transmit a Code-Reject packet
+ *
+ * XXX this seg faults.
+ */
+static void pppcp_send_code_reject(struct pppcp_data *data,
+                                       guint8 *rejected_packet)
+{
+       struct pppcp_packet *packet;
+
+       packet = pppcp_packet_new(data, CODE_REJECT,
+                       ntohs(((struct pppcp_packet *) rejected_packet)->length));
+
+       /*
+        * Identifier must be changed for each Code-Reject sent
+        */
+       packet->identifier = new_identity(data, data->reject_identifier);
+
+       /*
+        * rejected packet should be copied in, but it should be
+        * truncated if it needs to be to comply with mtu requirement
+        */
+       memcpy(packet->data, rejected_packet,
+                       ntohs(packet->length - CP_HEADER_SZ));
+
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+}
+
+/*
+ * transmit an Echo-Reply packet
+ */
+static void pppcp_send_echo_reply(struct pppcp_data *data,
+                               guint8 *request)
+{
+       struct pppcp_packet *packet;
+       struct pppcp_packet *header = (struct pppcp_packet *) request;
+
+       /*
+        * 0 bytes for data, 4 bytes for magic number
+        */
+       packet = pppcp_packet_new(data, ECHO_REPLY, 4);
+
+       /*
+        * match identifier of request
+        */
+       packet->identifier = header->identifier;
+
+       /* magic number? */
+       ppp_transmit(data->ppp, pppcp_to_ppp_packet((guint8 *) packet),
+                       ntohs(packet->length));
+
+}
+
+static void pppcp_transition_state(enum pppcp_state new_state,
+                                       struct pppcp_data *data)
+{
+       /*
+        * if switching from a state where
+        * TO events occur, to one where they
+        * may not, shut off the timer
+        */
+       switch (new_state) {
+       case INITIAL:
+       case STARTING:
+       case CLOSED:
+       case STOPPED:
+       case OPENED:
+               /* if timer is running, stop it */
+               if (pppcp_timer_is_running(data))
+                       pppcp_stop_timer(data);
+               break;
+       case CLOSING:
+       case STOPPING:
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               break;
+       }
+       data->state = new_state;
+}
+
+static void pppcp_up_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+       switch (data->state) {
+       case INITIAL:
+               /* switch state to CLOSED */
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case STARTING:
+               /* irc, scr/6 */
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case CLOSED:
+       case STOPPED:
+       case OPENED:
+       case CLOSING:
+       case STOPPING:
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_illegal_event(data->state, UP);
+       }
+}
+
+static void pppcp_down_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       switch (data->state) {
+       case CLOSED:
+               pppcp_transition_state(INITIAL, data);
+               break;
+       case STOPPED:
+               /* tls/1 */
+               pppcp_transition_state(STARTING, data);
+               pppcp_this_layer_started(data);
+               break;
+       case CLOSING:
+               pppcp_transition_state(INITIAL, data);
+               break;
+       case STOPPING:
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_transition_state(STARTING, data);
+               break;
+       case OPENED:
+               pppcp_transition_state(STARTING, data);
+               pppcp_this_layer_down(data);
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, DOWN);
+               /* illegal */
+       }
+}
+
+static void pppcp_open_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+       switch (data->state) {
+       case INITIAL:
+               /* tls/1 */
+               pppcp_transition_state(STARTING, data);
+               pppcp_this_layer_started(data);
+               break;
+       case STARTING:
+               pppcp_transition_state(STARTING, data);
+               break;
+       case CLOSED:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case STOPPED:
+               /* 3r */
+               pppcp_transition_state(STOPPED, data);
+               break;
+       case CLOSING:
+       case STOPPING:
+               /* 5r */
+               pppcp_transition_state(STOPPING, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_transition_state(data->state, data);
+               break;
+       case OPENED:
+               /* 9r */
+               pppcp_transition_state(data->state, data);
+               break;
+       }
+}
+
+static void pppcp_close_event(struct pppcp_data *data, guint8* packet, guint len)
+{
+       switch (data->state) {
+       case INITIAL:
+               pppcp_transition_state(INITIAL, data);
+               break;
+       case STARTING:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(INITIAL, data);
+               break;
+       case CLOSED:
+       case STOPPED:
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(CLOSING, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_initialize_restart_count(data, data->max_terminate);
+               pppcp_send_terminate_request(data);
+               pppcp_transition_state(CLOSING, data);
+               break;
+       }
+}
+
+static void pppcp_to_plus_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSING:
+               pppcp_send_terminate_request(data);
+               pppcp_transition_state(CLOSING, data);
+               break;
+       case STOPPING:
+               pppcp_send_terminate_request(data);
+               pppcp_transition_state(STOPPING, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case ACKSENT:
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+       case CLOSED:
+       case STOPPED:
+       case OPENED:
+               pppcp_illegal_event(data->state, TO_PLUS);
+       }
+}
+
+static void pppcp_to_minus_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+       switch (data->state) {
+       case CLOSING:
+               pppcp_transition_state(CLOSED, data);
+               pppcp_this_layer_finished(data);
+               break;
+       case STOPPING:
+               pppcp_transition_state(STOPPED, data);
+               pppcp_this_layer_finished(data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               /* tlf/3p */
+               pppcp_transition_state(STOPPED, data);
+               pppcp_this_layer_finished(data);
+               break;
+       case INITIAL:
+       case STARTING:
+       case CLOSED:
+       case STOPPED:
+       case OPENED:
+               pppcp_illegal_event(data->state, TO_MINUS);
+       }
+}
+
+static void pppcp_rcr_plus_event(struct pppcp_data *data,
+                               guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+       switch (data->state) {
+       case CLOSED:
+               pppcp_send_terminate_ack(data, packet);
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case STOPPED:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_send_configure_ack(data, packet);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(data->state, data);
+               break;
+       case REQSENT:
+               pppcp_send_configure_ack(data, packet);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case ACKRCVD:
+               pppcp_send_configure_ack(data, packet);
+               pppcp_this_layer_up(data);
+               pppcp_transition_state(OPENED, data);
+               break;
+       case ACKSENT:
+               pppcp_send_configure_ack(data, packet);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_send_configure_request(data);
+               pppcp_send_configure_ack(data, packet);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RCR_PLUS);
+       }
+}
+
+static void pppcp_rcr_minus_event(struct pppcp_data *data,
+                               guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+               pppcp_send_terminate_ack(data, packet);
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case STOPPED:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_send_configure_nak(data, packet);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(data->state, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+               pppcp_send_configure_nak(data, packet);
+               pppcp_transition_state(data->state, data);
+               break;
+       case ACKSENT:
+               pppcp_send_configure_nak(data, packet);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_send_configure_request(data);
+               pppcp_send_configure_nak(data, packet);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RCR_MINUS);
+       }
+}
+
+static void pppcp_rca_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+               pppcp_send_terminate_ack(data, packet);
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(data->state, data);
+               break;
+       case REQSENT:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_transition_state(ACKRCVD, data);
+               break;
+       case ACKRCVD:
+               /* scr/6x */
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+       case ACKSENT:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_this_layer_up(data);
+               pppcp_transition_state(OPENED, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RCA);
+       }
+}
+
+static void pppcp_rcn_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+               pppcp_send_terminate_ack(data, packet);
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(data->state, data);
+       case REQSENT:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case ACKRCVD:
+               /* scr/6x */
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case ACKSENT:
+               pppcp_initialize_restart_count(data, data->max_configure);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RCN);
+       }
+}
+
+static void pppcp_rtr_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+               pppcp_send_terminate_ack(data, packet);
+       case CLOSING:
+       case STOPPING:
+               break;
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_send_terminate_ack(data, packet);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_zero_restart_count(data);
+               pppcp_send_terminate_ack(data, packet);
+               pppcp_transition_state(STOPPING, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RTR);
+       }
+}
+
+static void pppcp_rta_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+               pppcp_transition_state(data->state, data);
+               break;
+       case CLOSING:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case STOPPING:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(STOPPED, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case ACKSENT:
+               pppcp_transition_state(ACKSENT, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_send_configure_request(data);
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RTA);
+       }
+}
+
+static void pppcp_ruc_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+       case CLOSING:
+       case STOPPING:
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+       case OPENED:
+               pppcp_send_code_reject(data, packet);
+               pppcp_transition_state(data->state, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RUC);
+       }
+}
+
+static void pppcp_rxj_plus_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+       case CLOSING:
+       case STOPPING:
+               pppcp_transition_state(data->state, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+               pppcp_transition_state(REQSENT, data);
+               break;
+       case ACKSENT:
+       case OPENED:
+               pppcp_transition_state(data->state, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RXJ_PLUS);
+       }
+}
+
+static void pppcp_rxj_minus_event(struct pppcp_data *data,
+                               guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(data->state, data);
+               break;
+       case CLOSING:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(CLOSED, data);
+               break;
+       case STOPPING:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(STOPPED, data);
+               break;
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_this_layer_finished(data);
+               pppcp_transition_state(STOPPED, data);
+               break;
+       case OPENED:
+               pppcp_this_layer_down(data);
+               pppcp_initialize_restart_count(data, data->max_terminate);
+               pppcp_send_terminate_request(data);
+               pppcp_transition_state(STOPPING, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RXJ_MINUS);
+       }
+}
+
+static void pppcp_rxr_event(struct pppcp_data *data, guint8 *packet, guint len)
+{
+       pppcp_trace(data);
+
+       switch (data->state) {
+       case CLOSED:
+       case STOPPED:
+       case CLOSING:
+       case STOPPING:
+       case REQSENT:
+       case ACKRCVD:
+       case ACKSENT:
+               pppcp_transition_state(data->state, data);
+               break;
+       case OPENED:
+               pppcp_send_echo_reply(data, packet);
+               pppcp_transition_state(OPENED, data);
+               break;
+       case INITIAL:
+       case STARTING:
+               pppcp_illegal_event(data->state, RXR);
+       }
+}
+
+static void pppcp_handle_event(gpointer user_data)
+{
+       struct pppcp_event *event;
+       struct pppcp_data *data = user_data;
+
+       while ((event = pppcp_get_event(data))) {
+               if (event->type > RXR)
+                       pppcp_illegal_event(data->state, event->type);
+               else
+                       data->event_ops[event->type](data, event->data,
+                                                       event->len);
+               g_free(event);
+       }
+}
+
+/*
+ * send the event handler a new event to process
+ */
+void pppcp_generate_event(struct pppcp_data *data,
+                               enum pppcp_event_type event_type,
+                               gpointer event_data, guint data_len)
+{
+       struct pppcp_event *event;
+
+       event = pppcp_event_new(event_type, event_data, data_len);
+       if (event)
+               g_queue_push_tail(data->event_queue, event);
+       pppcp_handle_event(data);
+}
+
+static gint is_option(gconstpointer a, gconstpointer b)
+{
+       const struct ppp_option *o = a;
+       guint8 otype = (guint8) GPOINTER_TO_UINT(b);
+
+       if (o->type == otype)
+               return 0;
+       else
+               return -1;
+}
+
+static void verify_config_option(gpointer elem, gpointer user_data)
+{
+       struct ppp_option *config = elem;
+       struct pppcp_data *data = user_data;
+       GList *list = NULL;
+       struct ppp_option *option;
+
+       /*
+        * determine whether this config option is in the
+        * acceptable options list
+        */
+       if (g_list_length(data->acceptable_options))
+               list = g_list_find_custom(data->acceptable_options,
+                               GUINT_TO_POINTER(config->type),
+                               is_option);
+       if (!list) {
+               /*
+                * if the option did not exist, we need to store a copy
+                * of the option in the unacceptable_options list so it
+                * can be nak'ed.
+                */
+               option = g_try_malloc0(config->length);
+               if (option == NULL)
+                       return;
+               option->type = config->type;
+               option->length = config->length;
+               data->unacceptable_options =
+                       g_list_append(data->unacceptable_options, option);
+       }
+
+}
+
+static void remove_config_option(gpointer elem, gpointer user_data)
+{
+       struct ppp_option *config = elem;
+       struct pppcp_data *data = user_data;
+       GList *list;
+
+       /*
+        * determine whether this config option is in the
+        * applied options list
+        */
+       if (g_list_length(data->config_options)) {
+               list = g_list_find_custom(data->config_options,
+                               GUINT_TO_POINTER(config->type),
+                               is_option);
+               if (list)
+                       data->config_options =
+                               g_list_delete_link(data->config_options, list);
+       }
+}
+
+static guint8 pppcp_process_configure_request(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       gint len;
+       int i = 0;
+       struct ppp_option *option;
+       enum option_rval rval = OPTION_ERR;
+       struct pppcp_action *action = data->action;
+
+       pppcp_trace(data);
+
+       len = ntohs(packet->length) - CP_HEADER_SZ;
+
+       /*
+        * check the options.
+        */
+       while (i < len) {
+               guint8 otype = packet->data[i];
+               guint8 olen = packet->data[i+1];
+               option = g_try_malloc0(olen);
+               if (option == NULL)
+                       break;
+               option->type = otype;
+               option->length = olen;
+               memcpy(option->data, &packet->data[i+2], olen-2);
+               if (action->option_scan)
+                       rval = action->option_scan(option, data);
+               switch (rval) {
+               case OPTION_ACCEPT:
+                       data->acceptable_options =
+                               g_list_append(data->acceptable_options, option);
+                       break;
+               case OPTION_REJECT:
+                       data->rejected_options =
+                               g_list_append(data->rejected_options, option);
+                       break;
+               case OPTION_NAK:
+                       data->unacceptable_options =
+                               g_list_append(data->unacceptable_options,
+                                               option);
+                       break;
+               case OPTION_ERR:
+                       g_printerr("unhandled option type %d\n", otype);
+               }
+               /* skip ahead to the next option */
+               i += olen;
+       }
+
+       /* make sure all required config options were included */
+       g_list_foreach(data->config_options, verify_config_option, data);
+
+       if (g_list_length(data->unacceptable_options) ||
+                       g_list_length(data->rejected_options))
+               return RCR_MINUS;
+
+       /*
+        * all options were acceptable, so we should apply them before
+        * sending a configure-ack
+        *
+        * Remove all applied options from the config_option list.  The
+        * protocol will have to re-add them if they want them renegotiated
+        * when the ppp goes down.
+        */
+       if (action->option_process) {
+               g_list_foreach(data->acceptable_options,
+                               action->option_process, data->priv);
+               g_list_foreach(data->acceptable_options, remove_config_option,
+                               data);
+       }
+
+       return RCR_PLUS;
+}
+
+static guint8 pppcp_process_configure_ack(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       guint len;
+       GList *list;
+       struct ppp_option *acked_option;
+       guint i = 0;
+       struct pppcp_action *action = data->action;
+
+       pppcp_trace(data);
+
+       len = ntohs(packet->length) - CP_HEADER_SZ;
+
+       /* if identifiers don't match, we should silently discard */
+       if (packet->identifier != data->config_identifier) {
+               g_printerr("received an ack id %d, but config id is %d\n",
+                       packet->identifier, data->config_identifier);
+               return 0;
+       }
+
+       /*
+        * check each acked option.  If it is what we requested,
+        * then we can apply these option values.
+        *
+        * XXX what if it isn't?  Do this correctly -- for now
+        * we are just going to assume that all options matched
+        * and apply them.
+        */
+       while (i < len) {
+               guint8 otype = packet->data[i];
+               guint8 olen = packet->data[i + 1];
+               acked_option = g_try_malloc0(olen);
+               if (acked_option == NULL)
+                       break;
+               acked_option->type = otype;
+               acked_option->length = olen;
+               memcpy(acked_option->data, &packet->data[i + 2], olen - 2);
+               list = g_list_find_custom(data->config_options,
+                               GUINT_TO_POINTER(acked_option->type),
+                               is_option);
+               if (list) {
+                       /*
+                        * once we've applied the option, delete it from
+                        * the config_options list.
+                        */
+                       if (action->option_process)
+                               action->option_process(acked_option,
+                                                       data->priv);
+                       data->config_options =
+                               g_list_delete_link(data->config_options, list);
+               } else
+                       g_printerr("oops -- found acked option %d we didn't request\n", acked_option->type);
+               g_free(acked_option);
+
+               /* skip ahead to the next option */
+               i += olen;
+       }
+       return RCA;
+}
+
+static guint8 pppcp_process_configure_nak(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       guint len;
+       GList *list;
+       struct ppp_option *naked_option;
+       struct ppp_option *config_option;
+       guint i = 0;
+       enum option_rval rval = OPTION_ERR;
+       struct pppcp_action *action = data->action;
+
+       pppcp_trace(data);
+
+       len = ntohs(packet->length) - CP_HEADER_SZ;
+
+       /* if identifiers don't match, we should silently discard */
+       if (packet->identifier != data->config_identifier)
+               return 0;
+
+       /*
+        * check each unacceptable option.  If it is acceptable, then
+        * we can resend the configure request with this value. we need
+        * to check the current config options to see if we need to
+        * modify a value there, or add a new option.
+        */
+       while (i < len) {
+               guint8 otype = packet->data[i];
+               guint8 olen = packet->data[i+1];
+               naked_option = g_try_malloc0(olen);
+               if (naked_option == NULL)
+                       break;
+               naked_option->type = otype;
+               naked_option->length = olen;
+               memcpy(naked_option->data, &packet->data[i + 2], olen - 2);
+               if (action->option_scan)
+                       rval = action->option_scan(naked_option, data);
+               if (rval == OPTION_ACCEPT) {
+                       /*
+                        * check the current config options to see if they
+                        * match.
+                        */
+                       list = g_list_find_custom(data->config_options,
+                               GUINT_TO_POINTER(otype),
+                               is_option);
+                       if (list) {
+                               /* modify current option value to match */
+                               config_option = list->data;
+
+                               /*
+                                * option values should match, otherwise
+                                * we need to reallocate
+                                */
+                               if ((config_option->length ==
+                                       naked_option->length) && (olen - 2)) {
+                                               memcpy(config_option->data,
+                                                       naked_option->data,
+                                                       olen - 2);
+                               } else {
+                                       /* XXX implement this */
+                                       g_printerr("uh oh, option value doesn't match\n");
+                               }
+                               g_free(naked_option);
+                       } else {
+                               /* add to list of config options */
+                               pppcp_add_config_option(data, naked_option);
+                       }
+               } else {
+                       /* XXX handle this correctly */
+                       g_printerr("oops, option wasn't acceptable\n");
+                       g_free(naked_option);
+               }
+
+               /* skip ahead to the next option */
+               i += olen;
+       }
+       return RCN;
+}
+
+static guint8 pppcp_process_configure_reject(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       /*
+        * make sure identifier matches that of last sent configure
+        * request
+        */
+       if (packet->identifier == data->config_identifier) {
+               /*
+                * check to see which options were rejected
+                * Rejected options must be a subset of requested
+                * options.
+                *
+                * when a new configure-request is sent, we may
+                * not request any of these options be negotiated
+                */
+               return RCN;
+       }
+       return 0;
+}
+
+static guint8 pppcp_process_terminate_request(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       return RTR;
+}
+
+static guint8 pppcp_process_terminate_ack(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       /*
+        * if we wind up using the data field for anything, then
+        * we'd want to check the identifier.
+        * even if the identifiers don't match, we still handle
+        * a terminate ack, as it is allowed to be unelicited
+        */
+       return RTA;
+}
+
+static guint8 pppcp_process_code_reject(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       /*
+        * determine if the code reject is catastrophic or not.
+        * return RXJ_PLUS if this reject is acceptable, RXJ_MINUS if
+        * it is catastrophic.
+        */
+       return RXJ_MINUS;
+}
+
+static guint8 pppcp_process_protocol_reject(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       /*
+        * determine if the protocol reject is catastrophic or not.
+        * return RXJ_PLUS if this reject is acceptable, RXJ_MINUS if
+        * it is catastrophic.
+        */
+       return RXJ_MINUS;
+}
+
+static guint8 pppcp_process_echo_request(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       return RXR;
+}
+
+static guint8 pppcp_process_echo_reply(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       return 0;
+}
+
+static guint8 pppcp_process_discard_request(struct pppcp_data *data,
+                                       struct pppcp_packet *packet)
+{
+       return 0;
+}
+
+/*
+ * parse the packet and determine which event this packet caused
+ */
+void pppcp_process_packet(gpointer priv, guint8 *new_packet)
+{
+       struct pppcp_data *data = priv;
+       struct pppcp_packet *packet = (struct pppcp_packet *) new_packet;
+       guint8 event_type;
+       gpointer event_data = NULL;
+       guint data_len = 0;
+
+       if (data == NULL)
+               return;
+
+       /* check flags to see if we support this code */
+       if (!(data->valid_codes & (1 << packet->code))) {
+               event_type = RUC;
+       } else
+               event_type = data->packet_ops[packet->code-1](data, packet);
+       if (event_type) {
+               data_len = ntohs(packet->length);
+               event_data = packet;
+               pppcp_generate_event(data, event_type, event_data, data_len);
+       }
+}
+
+void pppcp_set_valid_codes(struct pppcp_data *data, guint16 codes)
+{
+       if (data == NULL)
+               return;
+
+       data->valid_codes = codes;
+}
+
+void pppcp_free(struct pppcp_data *data)
+{
+       if (data == NULL)
+               return;
+
+       /* free event queue */
+       if (!g_queue_is_empty(data->event_queue))
+               g_queue_foreach(data->event_queue, (GFunc) g_free, NULL);
+       g_queue_free(data->event_queue);
+
+       /* remove all config options */
+       pppcp_clear_options(data);
+
+       /* free self */
+       g_free(data);
+}
+
+struct pppcp_data *pppcp_new(GAtPPP *ppp, guint16 proto,
+                               gpointer priv)
+{
+       struct pppcp_data *data;
+
+       data = g_try_malloc0(sizeof(struct pppcp_data));
+       if (!data)
+               return NULL;
+
+       data->state = INITIAL;
+       data->restart_interval = INITIAL_RESTART_TIMEOUT;
+       data->max_terminate = MAX_TERMINATE;
+       data->max_configure = MAX_CONFIGURE;
+       data->max_failure = MAX_FAILURE;
+       data->event_queue = g_queue_new();
+       data->identifier = 0;
+       data->ppp = ppp;
+       data->proto = proto;
+       data->priv = priv;
+
+       /* setup func ptrs for processing packet by pppcp code */
+       data->packet_ops[CONFIGURE_REQUEST - 1] =
+                                       pppcp_process_configure_request;
+       data->packet_ops[CONFIGURE_ACK - 1] = pppcp_process_configure_ack;
+       data->packet_ops[CONFIGURE_NAK - 1] = pppcp_process_configure_nak;
+       data->packet_ops[CONFIGURE_REJECT - 1] = pppcp_process_configure_reject;
+       data->packet_ops[TERMINATE_REQUEST - 1] =
+                                       pppcp_process_terminate_request;
+       data->packet_ops[TERMINATE_ACK - 1] = pppcp_process_terminate_ack;
+       data->packet_ops[CODE_REJECT - 1] = pppcp_process_code_reject;
+       data->packet_ops[PROTOCOL_REJECT - 1] = pppcp_process_protocol_reject;
+       data->packet_ops[ECHO_REQUEST - 1] = pppcp_process_echo_request;
+       data->packet_ops[ECHO_REPLY - 1] = pppcp_process_echo_reply;
+       data->packet_ops[DISCARD_REQUEST - 1] = pppcp_process_discard_request;
+
+       /* setup func ptrs for handling events by event type */
+       data->event_ops[UP] = pppcp_up_event;
+       data->event_ops[DOWN] = pppcp_down_event;
+       data->event_ops[OPEN] = pppcp_open_event;
+       data->event_ops[CLOSE] = pppcp_close_event;
+       data->event_ops[TO_PLUS] = pppcp_to_plus_event;
+       data->event_ops[TO_MINUS] = pppcp_to_minus_event;
+       data->event_ops[RCR_PLUS] = pppcp_rcr_plus_event;
+       data->event_ops[RCR_MINUS] = pppcp_rcr_minus_event;
+       data->event_ops[RCA] = pppcp_rca_event;
+       data->event_ops[RCN] = pppcp_rcn_event;
+       data->event_ops[RTR] = pppcp_rtr_event;
+       data->event_ops[RTA] = pppcp_rta_event;
+       data->event_ops[RUC] = pppcp_ruc_event;
+       data->event_ops[RXJ_PLUS] = pppcp_rxj_plus_event;
+       data->event_ops[RXJ_MINUS] = pppcp_rxj_minus_event;
+       data->event_ops[RXR] = pppcp_rxr_event;
+
+       return data;
+}
diff --git a/gatchat/ppp_cp.h b/gatchat/ppp_cp.h
new file mode 100644 (file)
index 0000000..875d02f
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ *
+ *  PPP library with GLib integration
+ *
+ *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+struct pppcp_data;
+
+enum pppcp_code {
+       CONFIGURE_REQUEST = 1,
+       CONFIGURE_ACK,
+       CONFIGURE_NAK,
+       CONFIGURE_REJECT,
+       TERMINATE_REQUEST,
+       TERMINATE_ACK,
+       CODE_REJECT,
+       PROTOCOL_REJECT,
+       ECHO_REQUEST,
+       ECHO_REPLY,
+       DISCARD_REQUEST
+};
+
+enum pppcp_event_type {
+       UP,
+       DOWN,
+       OPEN,
+       CLOSE,
+       TO_PLUS,
+       TO_MINUS,
+       RCR_PLUS,
+       RCR_MINUS,
+       RCA,
+       RCN,
+       RTR,
+       RTA,
+       RUC,
+       RXJ_PLUS,
+       RXJ_MINUS,
+       RXR,
+};
+
+enum pppcp_state {
+       INITIAL,
+       STARTING,
+       CLOSED,
+       STOPPED,
+       CLOSING,
+       STOPPING,
+       REQSENT,
+       ACKRCVD,
+       ACKSENT,
+       OPENED,
+};
+
+/* option format */
+struct ppp_option {
+       guint8 type;
+       guint8 length;
+       guint8 data[0];
+};
+
+enum option_rval {
+       OPTION_ACCEPT,
+       OPTION_REJECT,
+       OPTION_NAK,
+       OPTION_ERR,
+};
+
+struct pppcp_action {
+       void (*this_layer_up)(struct pppcp_data *data);
+       void (*this_layer_down)(struct pppcp_data *data);
+       void (*this_layer_started)(struct pppcp_data *data);
+       void (*this_layer_finished)(struct pppcp_data *data);
+       enum option_rval (*option_scan)(struct ppp_option *option,
+                                               gpointer user_data);
+       void (*option_process)(gpointer option, gpointer user_data);
+};
+
+struct pppcp_packet {
+       guint8 code;
+       guint8 identifier;
+       guint16 length;
+       guint8 data[0];
+} __attribute__((packed));
+
+struct pppcp_data {
+       enum pppcp_state state;
+       guint restart_timer;
+       guint restart_counter;
+       guint restart_interval;
+       guint max_terminate;
+       guint max_configure;
+       guint max_failure;
+       guint32 magic_number;
+       GQueue *event_queue;
+       GList *config_options;
+       GList *acceptable_options;
+       GList *unacceptable_options;
+       GList *rejected_options;
+       GList *applied_options;
+       GAtPPP *ppp;
+       guint8 identifier;  /* don't think I need this now */
+       guint8 config_identifier;
+       guint8 terminate_identifier;
+       guint8 reject_identifier;
+       struct pppcp_action *action;
+       guint16 valid_codes;
+       guint8 (*packet_ops[11])(struct pppcp_data *data,
+                                       struct pppcp_packet *packet);
+       void (*event_ops[16])(struct pppcp_data *data, guint8 *packet,
+                               guint length);
+       gpointer priv;
+       guint16 proto;
+};
+
+struct pppcp_data *pppcp_new(GAtPPP *ppp, guint16 proto, gpointer priv);
+void pppcp_free(struct pppcp_data *data);
+void pppcp_add_config_option(struct pppcp_data *data,
+                               struct ppp_option *option);
+void pppcp_set_valid_codes(struct pppcp_data *data, guint16 codes);
+void pppcp_generate_event(struct pppcp_data *data,
+                               enum pppcp_event_type event_type,
+                               gpointer event_data, guint data_len);
+void pppcp_process_packet(gpointer priv, guint8 *new_packet);