/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "secutil.h"

typedef enum {
    tagDone,
    lengthDone,
    leafDone,
    compositeDone,
    notDone,
    parseError,
    parseComplete
} ParseState;

typedef unsigned char Byte;
typedef void (*ParseProc)(BERParse *h, unsigned char **buf, int *len);
typedef struct {
    SECArb arb;
    int pos; /* length from global start to item start */
    SECArb *parent;
} ParseStackElem;

struct BERParseStr {
    PLArenaPool *his;
    PLArenaPool *mine;
    ParseProc proc;
    int stackDepth;
    ParseStackElem *stackPtr;
    ParseStackElem *stack;
    int pending; /* bytes remaining to complete this part */
    int pos;     /* running length of consumed characters */
    ParseState state;
    PRBool keepLeaves;
    PRBool derOnly;
    BERFilterProc filter;
    void *filterArg;
    BERNotifyProc before;
    void *beforeArg;
    BERNotifyProc after;
    void *afterArg;
};

#define UNKNOWN -1

static unsigned char
NextChar(BERParse *h, unsigned char **buf, int *len)
{
    unsigned char c = *(*buf)++;
    (*len)--;
    h->pos++;
    if (h->filter)
        (*h->filter)(h->filterArg, &c, 1);
    return c;
}

static void
ParseTag(BERParse *h, unsigned char **buf, int *len)
{
    SECArb *arb = &(h->stackPtr->arb);
    arb->tag = NextChar(h, buf, len);

    PORT_Assert(h->state == notDone);

    /*
     * NOTE: This does not handle the high-tag-number form
     */
    if ((arb->tag & DER_HIGH_TAG_NUMBER) == DER_HIGH_TAG_NUMBER) {
        PORT_SetError(SEC_ERROR_BAD_DER);
        h->state = parseError;
        return;
    }

    h->pending = UNKNOWN;
    arb->length = UNKNOWN;
    if (arb->tag & DER_CONSTRUCTED) {
        arb->body.cons.numSubs = 0;
        arb->body.cons.subs = NULL;
    } else {
        arb->body.item.len = UNKNOWN;
        arb->body.item.data = NULL;
    }

    h->state = tagDone;
}

static void
ParseLength(BERParse *h, unsigned char **buf, int *len)
{
    Byte b;
    SECArb *arb = &(h->stackPtr->arb);

    PORT_Assert(h->state == notDone);

    if (h->pending == UNKNOWN) {
        b = NextChar(h, buf, len);
        if ((b & 0x80) == 0) { /* short form */
            arb->length = b;
            /*
             * if the tag and the length are both zero bytes, then this
             * should be the marker showing end of list for the
             * indefinite length composite
             */
            if (arb->length == 0 && arb->tag == 0)
                h->state = compositeDone;
            else
                h->state = lengthDone;
            return;
        }

        h->pending = b & 0x7f;
        /* 0 implies this is an indefinite length */
        if (h->pending > 4) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            h->state = parseError;
            return;
        }
        arb->length = 0;
    }

    while ((*len > 0) && (h->pending > 0)) {
        b = NextChar(h, buf, len);
        arb->length = (arb->length << 8) + b;
        h->pending--;
    }
    if (h->pending == 0) {
        if (h->derOnly && (arb->length == 0))
            h->state = parseError;
        else
            h->state = lengthDone;
    }
    return;
}

static void
ParseLeaf(BERParse *h, unsigned char **buf, int *len)
{
    int count;
    SECArb *arb = &(h->stackPtr->arb);

    PORT_Assert(h->state == notDone);
    PORT_Assert(h->pending >= 0);

    if (*len < h->pending)
        count = *len;
    else
        count = h->pending;

    if (h->keepLeaves)
        memcpy(arb->body.item.data + arb->body.item.len, *buf, count);
    if (h->filter)
        (*h->filter)(h->filterArg, *buf, count);
    *buf += count;
    *len -= count;
    arb->body.item.len += count;
    h->pending -= count;
    h->pos += count;
    if (h->pending == 0) {
        h->state = leafDone;
    }
    return;
}

static void
CreateArbNode(BERParse *h)
{
    SECArb *arb = PORT_ArenaAlloc(h->his, sizeof(SECArb));

    *arb = h->stackPtr->arb;

    /*
     * Special case closing the root
     */
    if (h->stackPtr == h->stack) {
        PORT_Assert(arb->tag & DER_CONSTRUCTED);
        h->state = parseComplete;
    } else {
        SECArb *parent = h->stackPtr->parent;
        parent->body.cons.subs = DS_ArenaGrow(
            h->his, parent->body.cons.subs,
            (parent->body.cons.numSubs) * sizeof(SECArb *),
            (parent->body.cons.numSubs + 1) * sizeof(SECArb *));
        parent->body.cons.subs[parent->body.cons.numSubs] = arb;
        parent->body.cons.numSubs++;
        h->proc = ParseTag;
        h->state = notDone;
        h->pending = UNKNOWN;
    }
    if (h->after)
        (*h->after)(h->afterArg, arb, h->stackPtr - h->stack, PR_FALSE);
}

SECStatus
BER_ParseSome(BERParse *h, unsigned char *buf, int len)
{
    if (h->state == parseError)
        return PR_TRUE;

    while (len) {
        (*h->proc)(h, &buf, &len);
        if (h->state == parseComplete) {
            PORT_SetError(SEC_ERROR_BAD_DER);
            h->state = parseError;
            return PR_TRUE;
        }
        if (h->state == parseError)
            return PR_TRUE;
        PORT_Assert(h->state != parseComplete);

        if (h->state <= compositeDone) {
            if (h->proc == ParseTag) {
                PORT_Assert(h->state == tagDone);
                h->proc = ParseLength;
                h->state = notDone;
            } else if (h->proc == ParseLength) {
                SECArb *arb = &(h->stackPtr->arb);
                PORT_Assert(h->state == lengthDone || h->state == compositeDone);

                if (h->before)
                    (*h->before)(h->beforeArg, arb,
                                 h->stackPtr - h->stack, PR_TRUE);

                /*
                 * Check to see if this is the end of an indefinite
                 * length composite
                 */
                if (h->state == compositeDone) {
                    SECArb *parent = h->stackPtr->parent;
                    PORT_Assert(parent);
                    PORT_Assert(parent->tag & DER_CONSTRUCTED);
                    if (parent->length != 0) {
                        PORT_SetError(SEC_ERROR_BAD_DER);
                        h->state = parseError;
                        return PR_TRUE;
                    }
                    /*
                     * NOTE: This does not check for an indefinite length
                     * composite being contained inside a definite length
                     * composite. It is not clear that is legal.
                     */
                    h->stackPtr--;
                    CreateArbNode(h);
                } else {
                    h->stackPtr->pos = h->pos;

                    if (arb->tag & DER_CONSTRUCTED) {
                        SECArb *parent;
                        /*
                         * Make sure there is room on the stack before we
                         * stick anything else there.
                         */
                        PORT_Assert(h->stackPtr - h->stack < h->stackDepth);
                        if (h->stackPtr - h->stack == h->stackDepth - 1) {
                            int newDepth = h->stackDepth * 2;
                            h->stack = DS_ArenaGrow(h->mine, h->stack,
                                                    sizeof(ParseStackElem) *
                                                        h->stackDepth,
                                                    sizeof(ParseStackElem) *
                                                        newDepth);
                            h->stackPtr = h->stack + h->stackDepth + 1;
                            h->stackDepth = newDepth;
                        }
                        parent = &(h->stackPtr->arb);
                        h->stackPtr++;
                        h->stackPtr->parent = parent;
                        h->proc = ParseTag;
                        h->state = notDone;
                        h->pending = UNKNOWN;
                    } else {
                        if (arb->length < 0) {
                            PORT_SetError(SEC_ERROR_BAD_DER);
                            h->state = parseError;
                            return PR_TRUE;
                        }
                        arb->body.item.len = 0;
                        if (arb->length > 0 && h->keepLeaves) {
                            arb->body.item.data =
                                PORT_ArenaAlloc(h->his, arb->length);
                        } else {
                            arb->body.item.data = NULL;
                        }
                        h->proc = ParseLeaf;
                        h->state = notDone;
                        h->pending = arb->length;
                    }
                }
            } else {
                ParseStackElem *parent;
                PORT_Assert(h->state = leafDone);
                PORT_Assert(h->proc == ParseLeaf);

                for (;;) {
                    CreateArbNode(h);
                    if (h->stackPtr == h->stack)
                        break;
                    parent = (h->stackPtr - 1);
                    PORT_Assert(parent->arb.tag & DER_CONSTRUCTED);
                    if (parent->arb.length == 0) /* need explicit end */
                        break;
                    if (parent->pos + parent->arb.length > h->pos)
                        break;
                    if (parent->pos + parent->arb.length < h->pos) {
                        PORT_SetError(SEC_ERROR_BAD_DER);
                        h->state = parseError;
                        return PR_TRUE;
                    }
                    h->stackPtr = parent;
                }
            }
        }
    }
    return PR_FALSE;
}
BERParse *
BER_ParseInit(PLArenaPool *arena, PRBool derOnly)
{
    BERParse *h;
    PLArenaPool *temp = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (temp == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }
    h = PORT_ArenaAlloc(temp, sizeof(BERParse));
    if (h == NULL) {
        PORT_FreeArena(temp, PR_FALSE);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }
    h->his = arena;
    h->mine = temp;
    h->proc = ParseTag;
    h->stackDepth = 20;
    h->stack = PORT_ArenaZAlloc(h->mine,
                                sizeof(ParseStackElem) * h->stackDepth);
    h->stackPtr = h->stack;
    h->state = notDone;
    h->pos = 0;
    h->keepLeaves = PR_TRUE;
    h->before = NULL;
    h->after = NULL;
    h->filter = NULL;
    h->derOnly = derOnly;
    return h;
}

SECArb *
BER_ParseFini(BERParse *h)
{
    PLArenaPool *myArena = h->mine;
    SECArb *arb;

    if (h->state != parseComplete) {
        arb = NULL;
    } else {
        arb = PORT_ArenaAlloc(h->his, sizeof(SECArb));
        *arb = h->stackPtr->arb;
    }

    PORT_FreeArena(myArena, PR_FALSE);

    return arb;
}

void
BER_SetFilter(BERParse *h, BERFilterProc proc, void *instance)
{
    h->filter = proc;
    h->filterArg = instance;
}

void
BER_SetLeafStorage(BERParse *h, PRBool keep)
{
    h->keepLeaves = keep;
}

void
BER_SetNotifyProc(BERParse *h, BERNotifyProc proc, void *instance,
                  PRBool beforeData)
{
    if (beforeData) {
        h->before = proc;
        h->beforeArg = instance;
    } else {
        h->after = proc;
        h->afterArg = instance;
    }
}
