/* $Id: asn1-ut-objid-decode.cpp $ */
/** @file
 * IPRT - ASN.1, OBJECT IDENTIFIER Type, Decoder.
 */

/*
 * Copyright (C) 2006-2014 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include "internal/iprt.h"
#include <iprt/asn1.h>

#include <iprt/alloca.h>
#include <iprt/err.h>
#include <iprt/string.h>
#include <iprt/ctype.h>

#include <iprt/formats/asn1.h>


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
static char const g_achDigits[11] = "0123456789";


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId); /* asn1-ut-objid.cpp */


/**
 * Internal worker for RTAsn1ObjId_DecodeAsn1 that formats a component, with a
 * leading dot.
 *
 * @returns VBox status code (caller complains on failure).
 * @param   uValue      The component ID value.
 * @param   ppszObjId   Pointer to the output buffer pointer.
 * @param   pcbObjId    Pointer to the remaining size of the output buffer.
 */
DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId)
{
    /*
     * Format the number backwards.
     */
    char szTmp[32];
    char *psz = &szTmp[sizeof(szTmp) - 1];
    *psz = '\0';

    do
    {
        *--psz = g_achDigits[uValue % 10];
        uValue /= 10;
    } while (uValue > 0);

    /*
     * Do we have enough space?
     * We add a dot and save space for the terminator.
     */
    size_t cchNeeded = &szTmp[sizeof(szTmp) - 1] - psz;
    if (1 + cchNeeded < *pcbObjId)
    {
        *pcbObjId  -= cchNeeded + 1;
        char *pszObjId = *ppszObjId;
        *ppszObjId = pszObjId + cchNeeded + 1;

        *pszObjId = '.';
        memcpy(pszObjId + 1, psz, cchNeeded);
        return VINF_SUCCESS;
    }

    AssertFailed();
    return VERR_ASN1_OBJID_TOO_LONG_STRING_FORM;
}


/**
 * Reads one object ID component, returning it's value and encoded length.
 *
 * @returns The encoded length (positive) on success, negative IPRT status code
 *          on failure.
 * @param   pbContent           The start of the component to parse.
 * @param   cbContent           The number of content bytes left.
 * @param   puValue             Where to return the value.
 */
static int rtAsn1ObjId_ReadComponent(uint8_t const *pbContent, uint32_t cbContent, uint32_t *puValue)
{
    if (cbContent >= 1)
    {
        /* The first byte. */
        uint8_t b = *pbContent;
        if (!(b & 0x80))
        {
            *puValue = b;
            return 1;
        }

        /* Encoded as more than one byte.  Make sure that it's efficently
           encoded as 8.19.2 indicates it must. */
        if (b != 0x80)
        {
            uint32_t off    = 1;
            uint32_t uValue = b & 0x7f;
            while (off < cbContent)
            {
                b = pbContent[off++];
                uValue <<= 7;
                uValue |= b & 0x7f;
                if (!(b & 0x80))
                {
                    *puValue = uValue;
                    return (int)off;
                }

                if (RT_UNLIKELY(uValue & UINT32_C(0x0e000000)))
                    return VERR_ASN1_OBJID_COMPONENT_TOO_BIG;
            }
        }
        return VERR_ASN1_INVALID_OBJID_ENCODING;
    }
    return VERR_NO_DATA;
}


/**
 * This function parses the binary content of an OBJECT IDENTIFIER, check the
 * encoding as well as calculating the storage requirements.
 *
 * @returns IPRT status code
 * @param   pbContent       Pointer to the content.
 * @param   cbContent       The length of the content.
 * @param   pCursor         The cursor (for error reporting).
 * @param   pszErrorTag     The error tag.
 * @param   pcComponents    Where to return the component count.
 * @param   pcchObjId       Where to return the length of the dotted string
 *                          representation.
 */
static int rtAsn1ObjId_PreParse(uint8_t const *pbContent, uint32_t cbContent, PRTASN1CURSOR pCursor, const char *pszErrorTag,
                                uint8_t *pcComponents, uint8_t *pcchObjId)
{
    int rc;
    if (cbContent >= 1 && cbContent < _1K)
    {
        /*
         * Decode the first two numbers.  Monkey business: X*40 + Y
         * Where X is the first number, X in {0,1,2}, and Y is the second
         * one.  The range of Y is {0,...,39} for X in {0,1}, but has a
         * free range for X = 2.
         */
        uint32_t cComponents = 1;
        uint32_t uValue;
        rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
        if (rc > 0)
        {
            uint32_t cchObjId = 1;
            uValue = uValue < 2*40 ? uValue % 40 : uValue - 2*40; /* Y */
            do
            {
                cComponents++;

                /* Figure the encoded string length, binary search fashion. */
                if (uValue < 10000)
                {
                    if (uValue < 100)
                    {
                        if (uValue < 10)
                            cchObjId += 1 + 1;
                        else
                            cchObjId += 1 + 2;
                    }
                    else
                    {
                        if (uValue < 1000)
                            cchObjId += 1 + 3;
                        else
                            cchObjId += 1 + 4;
                    }
                }
                else
                {
                    if (uValue < 1000000)
                    {
                        if (uValue < 100000)
                            cchObjId += 1 + 5;
                        else
                            cchObjId += 1 + 6;
                    }
                    else
                    {
                        if (uValue < 10000000)
                            cchObjId += 1 + 7;
                        else if (uValue < 100000000)
                            cchObjId += 1 + 8;
                        else
                            cchObjId += 1 + 9;
                    }
                }

                /* advance. */
                pbContent += rc;
                cbContent -= rc;
                if (!cbContent)
                {
                    if (cComponents < 128)
                    {
                        if (cchObjId < RT_SIZEOFMEMB(RTASN1OBJID, szObjId))
                        {
                            *pcComponents = cComponents;
                            *pcchObjId    = cchObjId;
                            return VINF_SUCCESS;
                        }
                        return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_LONG_STRING_FORM,
                                                   "Object ID has a too long string form: %#x (max %#x)",
                                                   cchObjId, RT_SIZEOFMEMB(RTASN1OBJID, szObjId));
                    }
                    return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_MANY_COMPONENTS,
                                               "Object ID has too many components: %#x (max 127)", cComponents);
                }

                /* next */
                rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
            } while (rc > 0);
        }
        rc = RTAsn1CursorSetInfo(pCursor, rc, "Bad object ID component #%u encoding: %.*Rhxs",
                                 cComponents, cbContent, pbContent);
    }
    else if (cbContent)
        rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "Object ID content is loo long: %#x", cbContent);
    else
        rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "Zero length object ID content");
    return rc;
}



RTDECL(int) RTAsn1ObjId_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OBJID pThis, const char *pszErrorTag)
{
    int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
    if (RT_SUCCESS(rc))
    {
        rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_OID,
                                            ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "OID");
        if (RT_SUCCESS(rc))
        {
            /*
             * Validate and count things first.
             */
            uint8_t cComponents = 0; /* gcc maybe-crap */
            uint8_t cchObjId = 0;    /* ditto */
            rc = rtAsn1ObjId_PreParse(pCursor->pbCur, pThis->Asn1Core.cb, pCursor, pszErrorTag, &cComponents, &cchObjId);
            if (RT_SUCCESS(rc))
            {
                /*
                 * Allocate memory for the components array, either out of the
                 * string buffer or off the heap.
                 */
                pThis->cComponents = cComponents;
                RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation);
#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
                if (cComponents * sizeof(uint32_t) <= sizeof(pThis->szObjId) - cchObjId - 1)
                    pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - cComponents * sizeof(uint32_t)];
                else
#endif
                    rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->pauComponents,
                                         cComponents * sizeof(pThis->pauComponents[0]));
                if (RT_SUCCESS(rc))
                {
                    uint32_t *pauComponents = (uint32_t *)pThis->pauComponents;

                    /*
                     * Deal with the two first components first since they are
                     * encoded in a weird way to save a byte.
                     */
                    uint8_t const  *pbContent = pCursor->pbCur;
                    uint32_t        cbContent = pThis->Asn1Core.cb;
                    uint32_t        uValue;
                    rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); AssertRC(rc);
                    if (RT_SUCCESS(rc))
                    {
                        pbContent += rc;
                        cbContent -= rc;

                        if (uValue < 80)
                        {
                            pauComponents[0] = uValue / 40;
                            pauComponents[1] = uValue % 40;
                        }
                        else
                        {
                            pauComponents[0] = 2;
                            pauComponents[1] = uValue - 2*40;
                        }

                        char  *pszObjId    = &pThis->szObjId[0];
                        *pszObjId++        = g_achDigits[pauComponents[0]];
                        size_t cbObjIdLeft = cchObjId + 1 - 1;

                        rc = rtAsn1ObjId_InternalFormatComponent(pauComponents[1], &pszObjId, &cbObjIdLeft); AssertRC(rc);
                        if (RT_SUCCESS(rc))
                        {
                            /*
                             * The other components are encoded in less complicated manner.
                             */
                            for (uint32_t i = 2; i < cComponents; i++)
                            {
                                rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
                                AssertRCBreak(rc);
                                pbContent += rc;
                                cbContent -= rc;
                                pauComponents[i] = uValue;
                                rc = rtAsn1ObjId_InternalFormatComponent(uValue, &pszObjId, &cbObjIdLeft);
                                AssertRCBreak(rc);
                            }
                            if (RT_SUCCESS(rc))
                            {
                                Assert(cbObjIdLeft == 1);
                                *pszObjId = '\0';

                                RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
                                pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
                                pThis->Asn1Core.pOps = &g_RTAsn1ObjId_Vtable;
                                return VINF_SUCCESS;
                            }
                        }
                    }
                }
            }
        }
    }
    RT_ZERO(*pThis);
    return rc;
}



/*
 * Generate code for the associated collection types.
 */
#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-objid-template.h"
#include <iprt/asn1-generator-internal-header.h>
#include <iprt/asn1-generator-asn1-decoder.h>

