You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
605 lines
16 KiB
605 lines
16 KiB
/* Copyright (C) Boris Nikolaus, Germany, 1996-1997. All rights reserved. */
|
|
/* Copyright (C) Microsoft Corporation, 1997-1998. All rights reserved. */
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// Module Name: ms_per.c
|
|
//
|
|
// Brief Description:
|
|
// This module contains the routines for the Microsoft
|
|
// ASN.1 encoder and decoder.
|
|
//
|
|
// History:
|
|
// 10/15/97 Lon-Chan Chu (lonchanc)
|
|
// Created.
|
|
//
|
|
// Copyright (c) 1997 Microsoft Corporation
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
#include "precomp.h"
|
|
|
|
#define MLZ_FILE_ZONE ZONE_MSPER
|
|
|
|
|
|
int ASN1PEREncInteger(ASN1encoding_t enc, ASN1int32_t val)
|
|
{
|
|
ASN1uint32_t l = ASN1int32_octets(val);
|
|
ASN1PEREncAlignment(enc);
|
|
if (ASN1PEREncBitVal(enc, 8, l))
|
|
{
|
|
return ASN1PEREncBitVal(enc, l * 8, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecInteger(ASN1decoding_t dec, ASN1int32_t *val)
|
|
{
|
|
ASN1uint32_t l;
|
|
ASN1PERDecAlignment(dec);
|
|
if (ASN1PERDecFragmentedLength(dec, &l))
|
|
{
|
|
return ASN1PERDecS32Val(dec, l * 8, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncUnsignedInteger(ASN1encoding_t enc, ASN1uint32_t val)
|
|
{
|
|
ASN1uint32_t l = ASN1uint32_uoctets(val);
|
|
ASN1PEREncAlignment(enc);
|
|
if (ASN1PEREncBitVal(enc, 8, l))
|
|
{
|
|
return ASN1PEREncBitVal(enc, l * 8, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecUnsignedInteger(ASN1decoding_t dec, ASN1uint32_t *val)
|
|
{
|
|
ASN1uint32_t l;
|
|
ASN1PERDecAlignment(dec);
|
|
if (ASN1PERDecFragmentedLength(dec, &l))
|
|
{
|
|
return ASN1PERDecU32Val(dec, l * 8, val);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncUnsignedShort(ASN1encoding_t enc, ASN1uint32_t val)
|
|
{
|
|
ASN1PEREncAlignment(enc);
|
|
return ASN1PEREncBitVal(enc, 16, val);
|
|
}
|
|
|
|
int ASN1PERDecUnsignedShort(ASN1decoding_t dec, ASN1uint16_t *val)
|
|
{
|
|
ASN1PERDecAlignment(dec);
|
|
return ASN1PERDecU16Val(dec, 16, val);
|
|
}
|
|
|
|
int ASN1PEREncBoolean(ASN1encoding_t enc, ASN1bool_t val)
|
|
{
|
|
return ASN1PEREncBitVal(enc, 1, val ? 1 : 0);
|
|
}
|
|
|
|
int ASN1PERDecBoolean(ASN1decoding_t dec, ASN1bool_t *val)
|
|
{
|
|
DecAssert(dec, sizeof(ASN1bool_t) == sizeof(ASN1uint8_t));
|
|
*val = 0; // in case we change the boolean type
|
|
return ASN1PERDecU8Val(dec, 1, val);
|
|
}
|
|
|
|
__inline int _EncExtensionBitClear(ASN1encoding_t enc)
|
|
{
|
|
return ASN1PEREncBitVal(enc, 1, 0);
|
|
}
|
|
|
|
int ASN1PEREncExtensionBitClear(ASN1encoding_t enc)
|
|
{
|
|
return _EncExtensionBitClear(enc);
|
|
}
|
|
|
|
__inline int _EncExtensionBitSet(ASN1encoding_t enc)
|
|
{
|
|
return ASN1PEREncBitVal(enc, 1, 1);
|
|
}
|
|
|
|
int ASN1PEREncExtensionBitSet(ASN1encoding_t enc)
|
|
{
|
|
return _EncExtensionBitSet(enc);
|
|
}
|
|
|
|
int ASN1PERDecSkipNormallySmallExtensionFragmented(ASN1decoding_t dec)
|
|
{
|
|
ASN1uint32_t e, i;
|
|
if (ASN1PERDecSkipNormallySmallExtension(dec, &e))
|
|
{
|
|
for (i = 0; i < e; i++)
|
|
{
|
|
if (ASN1PERDecSkipFragmented(dec, 8))
|
|
{
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncSimpleChoice(ASN1encoding_t enc, ASN1choice_t ChoiceVal, ASN1int32_t cChoiceBits)
|
|
{
|
|
if (ChoiceVal >= ASN1_CHOICE_BASE)
|
|
{
|
|
ChoiceVal -= ASN1_CHOICE_BASE;
|
|
return (cChoiceBits ? ASN1PEREncBitVal(enc, cChoiceBits, ChoiceVal) : 1);
|
|
}
|
|
EncAssert(enc, FALSE);
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecSimpleChoice(ASN1decoding_t dec, ASN1choice_t *pChoiceVal, ASN1int32_t cChoiceBits)
|
|
{
|
|
DecAssert(dec, cChoiceBits <= sizeof(ASN1choice_t) * 8);
|
|
*pChoiceVal = ASN1_CHOICE_BASE; // default choice
|
|
if (cChoiceBits)
|
|
{
|
|
if (ASN1PERDecU16Val(dec, cChoiceBits, pChoiceVal))
|
|
{
|
|
*pChoiceVal += ASN1_CHOICE_BASE;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int ASN1PEREncSimpleChoiceEx(ASN1encoding_t enc, ASN1choice_t ChoiceVal, ASN1int32_t cChoiceBits)
|
|
{
|
|
if (ChoiceVal >= ASN1_CHOICE_BASE)
|
|
{
|
|
ChoiceVal -= ASN1_CHOICE_BASE;
|
|
if (_EncExtensionBitClear(enc))
|
|
{
|
|
return (cChoiceBits ? ASN1PEREncBitVal(enc, cChoiceBits, ChoiceVal) : 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EncAssert(enc, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecSimpleChoiceEx(ASN1decoding_t dec, ASN1choice_t *pChoiceVal, ASN1int32_t cChoiceBits)
|
|
{
|
|
ASN1uint32_t x;
|
|
DecAssert(dec, cChoiceBits <= sizeof(ASN1choice_t) * 8);
|
|
if (ASN1PERDecExtensionBit(dec, &x))
|
|
{
|
|
if (!x)
|
|
{
|
|
*pChoiceVal = ASN1_CHOICE_BASE; // default choice
|
|
if (cChoiceBits)
|
|
{
|
|
if (ASN1PERDecU16Val(dec, cChoiceBits, pChoiceVal))
|
|
{
|
|
*pChoiceVal += ASN1_CHOICE_BASE;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
*pChoiceVal = ASN1_CHOICE_EXTENSION; // extension choice
|
|
return ASN1PERDecSkipNormallySmall(dec);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncComplexChoice(ASN1encoding_t enc, ASN1choice_t ChoiceVal, ASN1int32_t cChoiceBits, ASN1choice_t ExtensionChoice)
|
|
{
|
|
if (ChoiceVal >= ASN1_CHOICE_BASE)
|
|
{
|
|
ChoiceVal -= ASN1_CHOICE_BASE;
|
|
if (ChoiceVal < ExtensionChoice) // lonchanc: no equal sign
|
|
{
|
|
if (_EncExtensionBitClear(enc))
|
|
{
|
|
if (cChoiceBits)
|
|
{
|
|
return ASN1PEREncBitVal(enc, cChoiceBits, ChoiceVal);
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_EncExtensionBitSet(enc))
|
|
{
|
|
return ASN1PEREncNormallySmall(enc, ChoiceVal - ExtensionChoice);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EncAssert(enc, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecComplexChoice(ASN1decoding_t dec, ASN1choice_t *pChoiceVal, ASN1int32_t cChoiceBits, ASN1choice_t ExtensionChoice)
|
|
{
|
|
ASN1uint32_t x;
|
|
DecAssert(dec, cChoiceBits <= sizeof(ASN1choice_t) * 8);
|
|
if (ASN1PERDecExtensionBit(dec, &x))
|
|
{
|
|
if (!x)
|
|
{
|
|
*pChoiceVal = ASN1_CHOICE_BASE; // default choice
|
|
if (cChoiceBits)
|
|
{
|
|
if (ASN1PERDecU16Val(dec, cChoiceBits, pChoiceVal))
|
|
{
|
|
*pChoiceVal += ASN1_CHOICE_BASE;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (ASN1PERDecN16Val(dec, pChoiceVal))
|
|
{
|
|
*pChoiceVal += ExtensionChoice + ASN1_CHOICE_BASE;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncOctetString_NoSize(ASN1encoding_t enc, ASN1octetstring_t *pOctetStr)
|
|
{
|
|
return ASN1PEREncFragmented(enc, pOctetStr->length, pOctetStr->value, 8);
|
|
}
|
|
|
|
int ASN1PERDecOctetString_NoSize(ASN1decoding_t dec, ASN1octetstring_t *pOctetStr)
|
|
{
|
|
return ASN1PERDecFragmented(dec, &(pOctetStr->length), &(pOctetStr->value), 8);
|
|
}
|
|
|
|
int _PEREncOctetString2
|
|
(
|
|
ASN1encoding_t enc,
|
|
ASN1uint32_t length,
|
|
ASN1octet_t *value,
|
|
ASN1uint32_t nSizeLowerBound,
|
|
ASN1uint32_t nSizeUpperBound,
|
|
ASN1uint32_t cSizeBits
|
|
)
|
|
{
|
|
// fixed size array?
|
|
if (nSizeLowerBound == nSizeUpperBound)
|
|
{
|
|
ASN1uint32_t nSizeLimit = nSizeLowerBound;
|
|
EncAssert(enc, cSizeBits == 0);
|
|
EncAssert(enc, nSizeLimit < 64 * 1024);
|
|
if (length == nSizeLimit)
|
|
{
|
|
if (nSizeLimit > 2)
|
|
{
|
|
ASN1PEREncAlignment(enc);
|
|
}
|
|
return ASN1PEREncBits(enc, nSizeLimit * 8, value);
|
|
}
|
|
EncAssert(enc, 0);
|
|
return 0;
|
|
}
|
|
|
|
// ranged size array
|
|
EncAssert(enc, cSizeBits);
|
|
EncAssert(enc, nSizeLowerBound < nSizeUpperBound);
|
|
if (nSizeLowerBound <= length && length <= nSizeUpperBound)
|
|
{
|
|
if (nSizeUpperBound - nSizeLowerBound < 255) // lonchanc: inherited from TELES
|
|
{
|
|
if (ASN1PEREncBitVal(enc, cSizeBits, length - nSizeLowerBound))
|
|
{
|
|
ASN1PEREncAlignment(enc);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EncAssert(enc, cSizeBits % 8 == 0);
|
|
ASN1PEREncAlignment(enc);
|
|
if (!ASN1PEREncBitVal(enc, cSizeBits, length - nSizeLowerBound))
|
|
return 0;
|
|
}
|
|
return ASN1PEREncBits(enc, length * 8, value);
|
|
}
|
|
EncAssert(enc, 0);
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PEREncOctetString_FixedSize(ASN1encoding_t enc, ASN1octetstring2_t *pOctetStr, ASN1uint32_t nSizeLimit)
|
|
{
|
|
return _PEREncOctetString2(enc, pOctetStr->length, &(pOctetStr->value[0]), nSizeLimit, nSizeLimit, 0);
|
|
}
|
|
|
|
int ASN1PEREncOctetString_FixedSizeEx(ASN1encoding_t enc, ASN1octetstring_t *pOctetStr, ASN1uint32_t nSizeLimit)
|
|
{
|
|
return _PEREncOctetString2(enc, pOctetStr->length, pOctetStr->value, nSizeLimit, nSizeLimit, 0);
|
|
}
|
|
|
|
int ASN1PEREncOctetString_VarSize(ASN1encoding_t enc, ASN1octetstring2_t *pOctetStr, ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
return _PEREncOctetString2(enc, pOctetStr->length, &(pOctetStr->value[0]), nSizeLowerBound, nSizeUpperBound, cSizeBits);
|
|
}
|
|
|
|
int ASN1PEREncOctetString_VarSizeEx(ASN1encoding_t enc, ASN1octetstring_t *pOctetStr, ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
return _PEREncOctetString2(enc, pOctetStr->length, pOctetStr->value, nSizeLowerBound, nSizeUpperBound, cSizeBits);
|
|
}
|
|
|
|
|
|
int _PERDecOctetString2
|
|
(
|
|
ASN1decoding_t dec,
|
|
ASN1uint32_t *length,
|
|
ASN1octet_t **value,
|
|
ASN1uint32_t nSizeLowerBound,
|
|
ASN1uint32_t nSizeUpperBound,
|
|
ASN1uint32_t cSizeBits
|
|
)
|
|
{
|
|
// fixed size array?
|
|
if (nSizeLowerBound == nSizeUpperBound)
|
|
{
|
|
ASN1uint32_t nSizeLimit = nSizeLowerBound;
|
|
DecAssert(dec, cSizeBits == 0);
|
|
DecAssert(dec, nSizeLimit < 64 * 1024);
|
|
*length = nSizeLimit;
|
|
if (nSizeLimit > 2)
|
|
{
|
|
ASN1PERDecAlignment(dec);
|
|
}
|
|
if (NULL == *value)
|
|
{
|
|
// must be unbounded
|
|
*value = (ASN1octet_t *) DecMemAlloc(dec, nSizeLimit + 1);
|
|
if (NULL == *value)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return ASN1PERDecExtension(dec, nSizeLimit * 8, *value);
|
|
}
|
|
|
|
// ranged size array
|
|
DecAssert(dec, cSizeBits);
|
|
DecAssert(dec, nSizeLowerBound < nSizeUpperBound);
|
|
if (nSizeUpperBound - nSizeLowerBound < 255) // lonchanc: inherited from TELES
|
|
{
|
|
if (ASN1PERDecU32Val(dec, cSizeBits, length))
|
|
{
|
|
*length += nSizeLowerBound;
|
|
ASN1PERDecAlignment(dec);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DecAssert(dec, cSizeBits % 8 == 0);
|
|
ASN1PERDecAlignment(dec);
|
|
if (ASN1PERDecU32Val(dec, cSizeBits, length))
|
|
{
|
|
*length += nSizeLowerBound;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
if (*length <= nSizeUpperBound)
|
|
{
|
|
if (NULL == *value)
|
|
{
|
|
*value = (ASN1octet_t *) DecMemAlloc(dec, *length + 1);
|
|
if (NULL == *value)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
return ASN1PERDecExtension(dec, *length * 8, *value);
|
|
}
|
|
DecAssert(dec, 0);
|
|
return 0;
|
|
|
|
}
|
|
|
|
int ASN1PERDecOctetString_FixedSize(ASN1decoding_t dec, ASN1octetstring2_t *pOctetStr, ASN1uint32_t nSizeLimit)
|
|
{
|
|
ASN1octet_t *pData = &(pOctetStr->value[0]);
|
|
return _PERDecOctetString2(dec, &(pOctetStr->length), &pData, nSizeLimit, nSizeLimit, 0);
|
|
}
|
|
|
|
int ASN1PERDecOctetString_FixedSizeEx(ASN1decoding_t dec, ASN1octetstring_t *pOctetStr, ASN1uint32_t nSizeLimit)
|
|
{
|
|
pOctetStr->value = NULL;
|
|
return _PERDecOctetString2(dec, &(pOctetStr->length), &(pOctetStr->value), nSizeLimit, nSizeLimit, 0);
|
|
}
|
|
|
|
int ASN1PERDecOctetString_VarSize(ASN1decoding_t dec, ASN1octetstring2_t *pOctetStr, ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
ASN1octet_t *pData = &(pOctetStr->value[0]);
|
|
return _PERDecOctetString2(dec, &(pOctetStr->length), &pData, nSizeLowerBound, nSizeUpperBound, cSizeBits);
|
|
}
|
|
|
|
int ASN1PERDecOctetString_VarSizeEx(ASN1decoding_t dec, ASN1octetstring_t *pOctetStr, ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
pOctetStr->value = NULL;
|
|
return _PERDecOctetString2(dec, &(pOctetStr->length), &(pOctetStr->value), nSizeLowerBound, nSizeUpperBound, cSizeBits);
|
|
}
|
|
|
|
|
|
|
|
int ASN1PEREncSeqOf_NoSize(ASN1encoding_t enc, ASN1iterator_t **val, ASN1iterator_encfn pfnIterator)
|
|
{
|
|
ASN1uint32_t t;
|
|
ASN1iterator_t *f;
|
|
ASN1uint32_t i;
|
|
ASN1uint32_t j, n = 0x4000;
|
|
EncAssert(enc, NULL != pfnIterator);
|
|
for (t = 0, f = *val; f; f = f->next)
|
|
t++;
|
|
f = *val;
|
|
for (i = 0; i < t;)
|
|
{
|
|
if (ASN1PEREncFragmentedLength(&n, enc, t - i))
|
|
{
|
|
for (j = 0; j < n; i++, j++)
|
|
{
|
|
if (((*pfnIterator)(enc, f)))
|
|
{
|
|
f = f->next;
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return ((n < 0x4000) ? 1 : ASN1PEREncFragmentedLength(&n, enc, 0));
|
|
}
|
|
|
|
int ASN1PERDecSeqOf_NoSize(ASN1decoding_t dec, ASN1iterator_t **val, ASN1iterator_decfn pfnIterator, ASN1uint32_t cbElementSize)
|
|
{
|
|
ASN1iterator_t **f;
|
|
ASN1uint32_t l;
|
|
ASN1uint32_t i;
|
|
ASN1uint32_t n;
|
|
DecAssert(dec, NULL != pfnIterator);
|
|
f = val;
|
|
do {
|
|
if (ASN1PERDecFragmentedLength(dec, &n))
|
|
{
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (NULL != (*f = (ASN1iterator_t *)DecMemAlloc(dec, cbElementSize)))
|
|
{
|
|
if ((*pfnIterator)(dec, *f))
|
|
{
|
|
f = &(*f)->next;
|
|
continue;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
} while (n >= 0x4000);
|
|
*f = NULL;
|
|
return 1;
|
|
}
|
|
|
|
int ASN1PEREncSeqOf_VarSize(ASN1encoding_t enc, ASN1iterator_t **val, ASN1iterator_encfn pfnIterator,
|
|
ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
ASN1uint32_t t;
|
|
ASN1iterator_t *f;
|
|
for (t = 0, f = *val; f; f = f->next)
|
|
t++;
|
|
if (nSizeLowerBound <= t && t <= nSizeUpperBound)
|
|
{
|
|
if (nSizeUpperBound - nSizeLowerBound + 1 >= 256)
|
|
{
|
|
ASN1PEREncAlignment(enc);
|
|
}
|
|
if (ASN1PEREncBitVal(enc, cSizeBits, t - nSizeLowerBound))
|
|
{
|
|
for (f = *val; f; f = f->next)
|
|
{
|
|
if (((*pfnIterator)(enc, f)))
|
|
{
|
|
continue;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EncAssert(enc, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ASN1PERDecSeqOf_VarSize(ASN1decoding_t dec, ASN1iterator_t **val, ASN1iterator_decfn pfnIterator, ASN1uint32_t cbElementSize,
|
|
ASN1uint32_t nSizeLowerBound, ASN1uint32_t nSizeUpperBound, ASN1uint32_t cSizeBits)
|
|
{
|
|
ASN1iterator_t **f;
|
|
ASN1uint32_t l, i;
|
|
|
|
if (nSizeUpperBound - nSizeLowerBound + 1 >= 256)
|
|
{
|
|
ASN1PERDecAlignment(dec);
|
|
}
|
|
if (ASN1PERDecU32Val(dec, cSizeBits, &l))
|
|
{
|
|
l += nSizeLowerBound;
|
|
DecAssert(dec, l <= nSizeUpperBound);
|
|
f = val;
|
|
for (i = 0; i < l; i++)
|
|
{
|
|
if (NULL != (*f = (ASN1iterator_t *)DecMemAlloc(dec, cbElementSize)))
|
|
{
|
|
if ((*pfnIterator)(dec, *f))
|
|
{
|
|
f = &(*f)->next;
|
|
continue;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
*f = NULL;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ASN1PERFreeSeqOf(ASN1iterator_t **val, ASN1iterator_freefn pfnIterator)
|
|
{
|
|
if (val)
|
|
{
|
|
ASN1iterator_t *f, *ff;
|
|
for (f = *val; f; f = ff)
|
|
{
|
|
ff = f->next;
|
|
if (pfnIterator)
|
|
{
|
|
(*pfnIterator)(f);
|
|
}
|
|
MemFree(f);
|
|
}
|
|
}
|
|
}
|
|
|