/* slipenc.c 20100513 Martin Peach */
/* encode a list of bytes as SLIP */
/*
*   From RFC 1055:
*   PROTOCOL
*
*   The SLIP protocol defines two special characters: SLIP_END and SLIP_ESC. SLIP_END is
*   octal 300 (decimal 192) and SLIP_ESC is octal 333 (decimal 219) not to be
*   confused with the ASCII ESCape character; for the purposes of this
*   discussion, SLIP_ESC will indicate the SLIP SLIP_ESC character.  To send a
*   packet, a SLIP host simply starts sending the data in the packet.  If
*   a data byte is the same code as SLIP_END character, a two byte sequence of
*   SLIP_ESC and octal 334 (decimal 220) is sent instead.  If it the same as
*   an SLIP_ESC character, an two byte sequence of SLIP_ESC and octal 335 (decimal
*   221) is sent instead.  When the last byte in the packet has been
*   sent, an SLIP_END character is then transmitted.
*
*   Phil Karn suggests a simple change to the algorithm, which is to
*   begin as well as end packets with an SLIP_END character.  This will flush
*   any erroneous bytes which have been caused by line noise.  In the
*   normal case, the receiver will simply see two back-to-back SLIP_END
*   characters, which will generate a bad IP packet.  If the SLIP
*   implementation does not throw away the zero-length IP packet, the IP
*   implementation certainly will.  If there was line noise, the data
*   received due to it will be discarded without affecting the following
*   packet.
*
*   Because there is no 'standard' SLIP specification, there is no real
*   defined maximum packet size for SLIP.  It is probably best to accept
*   the maximum packet size used by the Berkeley UNIX SLIP drivers: 1006
*   bytes including the IP and transport protocol headers (not including
*   the framing characters).  Therefore any new SLIP implementations
*   should be prepared to accept 1006 byte datagrams and should not send
*   more than 1006 bytes in a datagram.
*/

#include "m_pd.h" 

/* -------------------------- slipenc -------------------------- */
#ifndef _SLIPCODES
/* SLIP special character codes */
#define SLIP_END        0300 /* indicates end of packet */
#define SLIP_ESC        0333 /* indicates byte stuffing */
#define SLIP_ESC_END    0334 /* SLIP_ESC SLIP_ESC_END means SLIP_END data byte */
#define SLIP_ESC_ESC    0335 /* SLIP_ESC SLIP_ESC_ESC means SLIP_ESC data byte */
#define MAX_SLIP        1006 /* maximum SLIP packet size */
#define _SLIPCODES
#endif // _SLIPCODES

static t_class *slipenc_class;

typedef struct _slipenc
{
    t_object    x_obj;
    t_outlet    *x_slipenc_out;
    t_atom      *x_slip_buf;
    int         x_slip_length;
    int         x_slip_max_length;
} t_slipenc;

static void *slipenc_new(t_symbol *s, int argc, t_atom *argv);
static void slipenc_list(t_slipenc *x, t_symbol *s, int ac, t_atom *av);
static void slipenc_free(t_slipenc *x);
void slipenc_setup(void);

static void *slipenc_new(t_symbol *s, int argc, t_atom *argv)
{
    int i, max_len;
    t_slipenc  *x = (t_slipenc *)pd_new(slipenc_class);

    if (x == NULL) return x;
    
    x->x_slip_max_length = MAX_SLIP; // default unless float argument given
    for (i = 0; i < argc; ++i)
    {
        if (argv[i].a_type == A_FLOAT)
        {
            max_len = atom_getfloat(&argv[i]);
            if (max_len > 3)
            {
                x->x_slip_max_length = max_len;
                post("slipenc: maximum packet length is %d", x->x_slip_max_length);
            }
            else
                post("slipenc: maximum packet length must be greater than 3, using %d", x->x_slip_max_length);
            break;
        }
    }
    x->x_slip_buf = (t_atom *)getbytes(sizeof(t_atom)*x->x_slip_max_length);
    if(x->x_slip_buf == NULL)
    {
        error("slipenc: unable to allocate %lu bytes for x_slip_buf", (long)sizeof(t_atom)*x->x_slip_max_length);
        return NULL;
    }
    /* Initialize all the slip buf atoms to float type */
    for (i = 0; i < x->x_slip_max_length; ++i) x->x_slip_buf[i].a_type = A_FLOAT;
    x->x_slipenc_out = outlet_new(&x->x_obj, &s_list);
    return (x);
}

static void slipenc_list(t_slipenc *x, t_symbol *s, int ac, t_atom *av)
{
    /* SLIP encode a list of bytes */
    float   f;
    int     i, c;

    i = 0;    
    /* for each byte in the packet, send the appropriate character sequence */
    while (i < ac)
    {
        x->x_slip_length = 0;
        /* send an initial SLIP_END character to flush out any data that may */
        /* have accumulated in the receiver due to line noise */
        x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_END;

        while((i < ac)&&(x->x_slip_length < (x->x_slip_max_length-2)))
        {
            /* check each atom for byteness */
            f = atom_getfloat(&av[i++]);
            c = (((int)f) & 0x0FF);
            if (c != f)
            {
                /* abort, bad input character */
                pd_error (x, "slipenc: input %f out of range [0..255]", f);
                return;
            }
            if(SLIP_END == c)
            {
                /* If it's the same code as a SLIP_END character, replace it with a */
                /* special two-character code so as not to make the receiver think we sent SLIP_END */
                x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_ESC;
                x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_ESC_END;
            }
            else if (SLIP_ESC == c)
            {
                /* If it's the same code as a SLIP_ESC character, replace it with a special two-character code */
                /* so as not to make the receiver think we sent SLIP_ESC */
                x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_ESC;
                x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_ESC_ESC;
            }
            else
            {
                /* Otherwise, pass the character */
                x->x_slip_buf[x->x_slip_length++].a_w.w_float = c;
            }
        }
        /* Add the SLIP_END code to tell the receiver that the packet is complete */
        x->x_slip_buf[x->x_slip_length++].a_w.w_float = SLIP_END;
        outlet_list(x->x_slipenc_out, &s_list, x->x_slip_length, x->x_slip_buf);
    }
}

static void slipenc_free(t_slipenc *x)
{
    if (x->x_slip_buf != NULL) freebytes((void *)x->x_slip_buf, sizeof(t_atom)*x->x_slip_max_length);
}

void slipenc_setup(void)
{
    slipenc_class = class_new(gensym("slipenc"), 
        (t_newmethod)slipenc_new, (t_method)slipenc_free,
        sizeof(t_slipenc), 0, A_GIMME, 0);
    class_addlist(slipenc_class, slipenc_list);
}

/* fin slipenc.c*/
