#include <u.h>
#include <libc.h>
#include <ip.h>
#include <auth.h>
#include "ppp.h"

enum
{
       PAD     = 128,
       NLIST   = (1<<5),
       BPOW    = 10,
};

typedef struct Barena Barena;
struct Barena
{
       QLock;
       Block*  list[NLIST];
};
static Barena area;

#define ADEBUG if(0)


/*
*  allocation tracing
*/
enum
{
       Npc=    64,
};
typedef struct Arefent Arefent;
struct Arefent
{
       uint    pc;
       uint    n;
};
typedef struct Aref Aref;
struct  Aref
{
       Arefent tab[Npc];
       QLock;
};
Aref arefblock;

static ulong    callerpc(void*);
static void     aref(Aref*, ulong);
static void     aunref(Aref*, ulong);

Block*
allocb(int len)
{
       int sz;
       Block *bp, **l;

       len += PAD;
       sz = (len>>BPOW)&(NLIST-1);

       qlock(&area);
       l = &area.list[sz];
       for(bp = *l; bp; bp = bp->flist) {
               if(bp->bsz >= len) {
                       *l = bp->flist;
                       qunlock(&area);

                       bp->next = nil;
                       bp->list = nil;
                       bp->flist = nil;
                       bp->base = (uchar*)bp+sizeof(Block);
                       bp->rptr = bp->base+PAD;
                       bp->wptr = bp->rptr;
                       bp->lim  = bp->base+bp->bsz;
                       bp->flow = nil;
                       bp->flags= 0;
                       ADEBUG {
                               bp->pc = callerpc(&len);
                               aref(&arefblock, bp->pc);
                       }
                       return bp;
               }
               l = &bp->flist;
       }

       qunlock(&area);

       bp = mallocz(sizeof(Block)+len, 1);

       bp->bsz  = len;
       bp->base = (uchar*)bp+sizeof(Block);
       bp->rptr = bp->base+PAD;
       bp->wptr = bp->rptr;
       bp->lim  = bp->base+len;
       ADEBUG {
               bp->pc = callerpc(&len);
               aref(&arefblock, bp->pc);
       }
       return bp;
}

void
freeb(Block *bp)
{
       int sz;
       Block **l, *next;

       qlock(&area);
       while(bp) {
               sz = (bp->bsz>>BPOW)&(NLIST-1);

               l = &area.list[sz];
               bp->flist = *l;
               *l = bp;

               next = bp->next;

               /* to catch use after free */
               bp->rptr = (uchar*)0xdeadbabe;
               bp->wptr = (uchar*)0xdeadbabe;
               bp->next = (Block*)0xdeadbabe;
               bp->list = (Block*)0xdeadbabe;

               ADEBUG aunref(&arefblock, bp->pc);

               bp = next;
       }
       qunlock(&area);
}

/*
*  concatenate a list of blocks into a single one and make sure
*  the result is at least min uchars
*/
Block*
concat(Block *bp)
{
       int len;
       Block *nb, *f;

       nb = allocb(blen(bp));
       for(f = bp; f; f = f->next) {
               len = BLEN(f);
               memmove(nb->wptr, f->rptr, len);
               nb->wptr += len;
       }
       freeb(bp);
       return nb;
}

int
blen(Block *bp)
{
       int len;

       len = 0;
       while(bp) {
               len += BLEN(bp);
               bp = bp->next;
       }
       return len;
}

Block *
pullup(Block *bp, int n)
{
       int i;
       Block *nbp;

       /*
        *  this should almost always be true, the rest it
        *  just for to avoid every caller checking.
        */
       if(BLEN(bp) >= n)
               return bp;

       /*
        *  if not enough room in the first block,
        *  add another to the front of the list.
        */
       if(bp->lim - bp->rptr < n){
               nbp = allocb(n);
               nbp->next = bp;
               bp = nbp;
       }

       /*
        *  copy uchars from the trailing blocks into the first
        */
       n -= BLEN(bp);
       while(nbp = bp->next){
               i = BLEN(nbp);
               if(i >= n) {
                       memmove(bp->wptr, nbp->rptr, n);
                       bp->wptr += n;
                       nbp->rptr += n;
                       return bp;
               }
               else {
                       memmove(bp->wptr, nbp->rptr, i);
                       bp->wptr += i;
                       bp->next = nbp->next;
                       nbp->next = nil;
                       freeb(nbp);
                       n -= i;
               }
       }
       freeb(bp);
       return nil;
}

/*
*  Pad a block to the front with n uchars.  This is used to add protocol
*  headers to the front of blocks.
*/
Block*
padb(Block *bp, int n)
{
       Block *nbp;

       if(bp->rptr-bp->base >= n) {
               bp->rptr -= n;
               return bp;
       }
       else {
               /* fprint(2, "padb: required %d PAD %d\n", n, PAD) = malloc(sizeof(*required %d PAD %d\n", n, PAD))); */
               nbp = allocb(n);
               nbp->wptr = nbp->lim;
               nbp->rptr = nbp->wptr - n;
               nbp->next = bp;
               return nbp;
       }
}

Block *
btrim(Block *bp, int offset, int len)
{
       ulong l;
       Block *nb, *startb;

       if(blen(bp) < offset+len) {
               freeb(bp);
               return nil;
       }

       while((l = BLEN(bp)) < offset) {
               offset -= l;
               nb = bp->next;
               bp->next = nil;
               freeb(bp);
               bp = nb;
       }

       startb = bp;
       bp->rptr += offset;

       while((l = BLEN(bp)) < len) {
               len -= l;
               bp = bp->next;
       }

       bp->wptr -= (BLEN(bp) - len);
       bp->flags |= S_DELIM;

       if(bp->next) {
               freeb(bp->next);
               bp->next = nil;
       }

       return startb;
}

Block*
copyb(Block *bp, int count)
{
       int l;
       Block *nb, *head, **p;

       p = &head;
       head = nil;
       while(bp != nil && count != 0) {
               l = BLEN(bp);
               if(count < l)
                       l = count;
               nb = allocb(l);
               memmove(nb->wptr, bp->rptr, l);
               nb->wptr += l;
               count -= l;
               nb->flags = bp->flags;
               *p = nb;
               p = &nb->next;
               bp = bp->next;
       }
       if(count) {
               nb = allocb(count);
               memset(nb->wptr, 0, count);
               nb->wptr += count;
               nb->flags |= S_DELIM;
               *p = nb;
       }
       if(blen(head) == 0)
               fprint(2, "copyb: zero length\n");

       return head;
}

int
pullb(Block **bph, int count)
{
       Block *bp;
       int n, uchars;

       uchars = 0;
       if(bph == nil)
               return 0;

       while(*bph != nil && count != 0) {
               bp = *bph;
               n = BLEN(bp);
               if(count < n)
                       n = count;
               uchars += n;
               count -= n;
               bp->rptr += n;
               if(BLEN(bp) == 0) {
                       *bph = bp->next;
                       bp->next = nil;
                       freeb(bp);
               }
       }
       return uchars;
}

/*
*  handy routines for keeping track of allocations
*/
static ulong
callerpc(void *a)
{
       return ((ulong*)a)[-1];
}
static void
aref(Aref *ap, ulong pc)
{
       Arefent *a, *e;

       e = &ap->tab[Npc-1];
       qlock(ap);
       for(a = ap->tab; a < e; a++)
               if(a->pc == pc || a->pc == 0)
                       break;
       a->pc = pc;
       a->n++;
       qunlock(ap);
}
static void
aunref(Aref *ap, ulong pc)
{
       Arefent *a, *e;

       e = &ap->tab[Npc-1];
       qlock(ap);
       for(a = ap->tab; a < e; a++)
               if(a->pc == pc || a->pc == 0)
                       break;
       a->n--;
       qunlock(ap);
}