asn1-ut-objid-decode.cpp revision 375c27aef516595a50459787463506d474668e8c
/* $Id$ */
/** @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>