VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/CertificateImpl.cpp@ 60342

Last change on this file since 60342 was 60342, checked in by vboxsync, 9 years ago

CertificateImpl.cpp: Removed content free autogenerated comments and documented the workers I added last week.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.1 KB
Line 
1/* $Id: CertificateImpl.cpp 60342 2016-04-05 23:48:43Z vboxsync $ */
2/** @file
3 * ICertificate COM class implementations.
4 */
5
6/*
7 * Copyright (C) 2008-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <iprt/path.h>
19#include <iprt/cpp/utils.h>
20#include <VBox/com/array.h>
21#include <iprt/crypto/x509.h>
22
23#include "ProgressImpl.h"
24#include "CertificateImpl.h"
25#include "AutoCaller.h"
26#include "Global.h"
27#include "Logging.h"
28
29using namespace std;
30
31struct CertificateData
32{
33 CertificateData()
34 : fTrusted(false)
35 , fExpired(false)
36 , fValidX509(false)
37 {
38 RT_ZERO(X509);
39 }
40
41 ~CertificateData()
42 {
43 if (fValidX509)
44 {
45 RTCrX509Certificate_Delete(&X509);
46 RT_ZERO(X509);
47 fValidX509 = false;
48 }
49 }
50
51 /** Whether the certificate is trusted. */
52 bool fTrusted;
53 /** Whether the certificate is trusted. */
54 bool fExpired;
55 /** Valid data in mX509. */
56 bool fValidX509;
57 /** Clone of the X.509 certificate. */
58 RTCRX509CERTIFICATE X509;
59
60private:
61 CertificateData(const CertificateData &rTodo) { AssertFailed(); NOREF(rTodo); }
62 CertificateData &operator=(const CertificateData &rTodo) { AssertFailed(); NOREF(rTodo); return *this; }
63};
64
65struct Certificate::Data
66{
67 Backupable<CertificateData> m;
68};
69
70///////////////////////////////////////////////////////////////////////////////////
71//
72// Certificate constructor / destructor
73//
74// ////////////////////////////////////////////////////////////////////////////////
75
76DEFINE_EMPTY_CTOR_DTOR(Certificate)
77
78HRESULT Certificate::FinalConstruct()
79{
80 return BaseFinalConstruct();
81}
82
83void Certificate::FinalRelease()
84{
85 uninit();
86 BaseFinalRelease();
87}
88
89/**
90 * Initializes a certificate instance.
91 *
92 * @returns COM status code.
93 * @param a_pCert The certificate.
94 * @param a_fTrusted Whether the caller trusts the certificate or not.
95 * @param a_fExpired Whether the caller consideres the certificate to be
96 * expired.
97 */
98HRESULT Certificate::initCertificate(PCRTCRX509CERTIFICATE a_pCert, bool a_fTrusted, bool a_fExpired)
99{
100 HRESULT rc = S_OK;
101 LogFlowThisFuncEnter();
102
103 AutoInitSpan autoInitSpan(this);
104 AssertReturn(autoInitSpan.isOk(), E_FAIL);
105
106 mData = new Data();
107 mData->m.allocate();
108
109 int vrc = RTCrX509Certificate_Clone(&mData->m->X509, a_pCert, &g_RTAsn1DefaultAllocator);
110 if (RT_SUCCESS(vrc))
111 {
112 mData->m->fValidX509 = true;
113 mData->m->fTrusted = a_fTrusted;
114 mData->m->fExpired = a_fExpired;
115 autoInitSpan.setSucceeded();
116 }
117 else
118 rc = Global::vboxStatusCodeToCOM(vrc);
119
120 LogFlowThisFunc(("returns rc=%Rhrc\n", rc));
121 return rc;
122}
123
124void Certificate::uninit()
125{
126 /* Enclose the state transition Ready->InUninit->NotReady */
127 AutoUninitSpan autoUninitSpan(this);
128 if (autoUninitSpan.uninitDone())
129 return;
130
131 mData->m.free();
132 delete mData;
133 mData = NULL;
134}
135
136
137/** @name wrapped ICertificate properties
138 * @{
139 */
140
141HRESULT Certificate::getVersionNumber(CertificateVersion_T *aVersionNumber)
142{
143 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
144
145 Assert(mData->m->fValidX509);
146 switch (mData->m->X509.TbsCertificate.T0.Version.uValue.u)
147 {
148 case RTCRX509TBSCERTIFICATE_V1: *aVersionNumber = (CertificateVersion_T)CertificateVersion_V1; break;
149 case RTCRX509TBSCERTIFICATE_V2: *aVersionNumber = (CertificateVersion_T)CertificateVersion_V2; break;
150 case RTCRX509TBSCERTIFICATE_V3: *aVersionNumber = (CertificateVersion_T)CertificateVersion_V3; break;
151 default: AssertFailed(); *aVersionNumber = (CertificateVersion_T)CertificateVersion_Unknown; break;
152 }
153 return S_OK;
154}
155
156HRESULT Certificate::getSerialNumber(com::Utf8Str &aSerialNumber)
157{
158 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
159
160 Assert(mData->m->fValidX509);
161
162 char szTmp[_2K];
163 int vrc = RTAsn1Integer_ToString(&mData->m->X509.TbsCertificate.SerialNumber, szTmp, sizeof(szTmp), 0, NULL);
164 if (RT_SUCCESS(vrc))
165 aSerialNumber = szTmp;
166 else
167 return Global::vboxStatusCodeToCOM(vrc);
168
169 return S_OK;
170}
171
172HRESULT Certificate::getSignatureAlgorithmOID(com::Utf8Str &aSignatureAlgorithmOID)
173{
174 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
175
176 Assert(mData->m->fValidX509);
177 aSignatureAlgorithmOID = mData->m->X509.TbsCertificate.Signature.Algorithm.szObjId;
178
179 return S_OK;
180}
181
182HRESULT Certificate::getSignatureAlgorithmName(com::Utf8Str &aSignatureAlgorithmName)
183{
184 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
185
186 Assert(mData->m->fValidX509);
187 return i_getAlgorithmName(&mData->m->X509.TbsCertificate.Signature, aSignatureAlgorithmName);
188}
189
190HRESULT Certificate::getIssuerName(std::vector<com::Utf8Str> &aIssuerName)
191{
192 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
193
194 Assert(mData->m->fValidX509);
195 return i_getX509Name(&mData->m->X509.TbsCertificate.Issuer, aIssuerName);
196}
197
198HRESULT Certificate::getSubjectName(std::vector<com::Utf8Str> &aSubjectName)
199{
200 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
201
202 Assert(mData->m->fValidX509);
203 return i_getX509Name(&mData->m->X509.TbsCertificate.Subject, aSubjectName);
204}
205
206HRESULT Certificate::getFriendlyName(com::Utf8Str &aFriendlyName)
207{
208 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
209
210 Assert(mData->m->fValidX509);
211
212 PCRTCRX509NAME pName = &mData->m->X509.TbsCertificate.Subject;
213
214 /*
215 * Enumerate the subject name and pick interesting attributes we can use to
216 * form a name more friendly than the RTCrX509Name_FormatAsString output.
217 */
218 const char *pszOrg = NULL;
219 const char *pszOrgUnit = NULL;
220 const char *pszGivenName = NULL;
221 const char *pszSurname = NULL;
222 const char *pszEmail = NULL;
223 for (uint32_t i = 0; i < pName->cItems; i++)
224 {
225 PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = &pName->paItems[i];
226 for (uint32_t j = 0; j < pRdn->cItems; j++)
227 {
228 PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = &pRdn->paItems[j];
229 AssertContinue(pComponent->Value.enmType == RTASN1TYPE_STRING);
230
231 /* Select interesting components based on the short RDN prefix
232 string (easier to read and write than OIDs, for now). */
233 const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
234 if (pszPrefix)
235 {
236 const char *pszUtf8;
237 int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL);
238 if (RT_SUCCESS(vrc) && *pszUtf8)
239 {
240 if (!strcmp(pszPrefix, "Email"))
241 pszEmail = pszUtf8;
242 else if (!strcmp(pszPrefix, "O"))
243 pszOrg = pszUtf8;
244 else if (!strcmp(pszPrefix, "OU"))
245 pszOrgUnit = pszUtf8;
246 else if (!strcmp(pszPrefix, "S"))
247 pszSurname = pszUtf8;
248 else if (!strcmp(pszPrefix, "G"))
249 pszGivenName = pszUtf8;
250 }
251 }
252 }
253 }
254
255 if (pszGivenName && pszSurname)
256 {
257 if (pszEmail)
258 aFriendlyName = Utf8StrFmt("%s, %s <%s>", pszSurname, pszGivenName, pszEmail);
259 else if (pszOrg)
260 aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrg);
261 else if (pszOrgUnit)
262 aFriendlyName = Utf8StrFmt("%s, %s (%s)", pszSurname, pszGivenName, pszOrgUnit);
263 else
264 aFriendlyName = Utf8StrFmt("%s, %s", pszSurname, pszGivenName);
265 }
266 else if (pszOrg && pszOrgUnit)
267 aFriendlyName = Utf8StrFmt("%s, %s", pszOrg, pszOrgUnit);
268 else if (pszOrg)
269 aFriendlyName = Utf8StrFmt("%s", pszOrg);
270 else if (pszOrgUnit)
271 aFriendlyName = Utf8StrFmt("%s", pszOrgUnit);
272 else
273 {
274 /*
275 * Fall back on unfriendly but accurate.
276 */
277 char szTmp[_8K];
278 RT_ZERO(szTmp);
279 RTCrX509Name_FormatAsString(pName, szTmp, sizeof(szTmp) - 1, NULL);
280 aFriendlyName = szTmp;
281 }
282
283 return S_OK;
284}
285
286HRESULT Certificate::getValidityPeriodNotBefore(com::Utf8Str &aValidityPeriodNotBefore)
287{
288 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
289
290 Assert(mData->m->fValidX509);
291 return i_getTime(&mData->m->X509.TbsCertificate.Validity.NotBefore, aValidityPeriodNotBefore);
292}
293
294HRESULT Certificate::getValidityPeriodNotAfter(com::Utf8Str &aValidityPeriodNotAfter)
295{
296 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
297
298 Assert(mData->m->fValidX509);
299 return i_getTime(&mData->m->X509.TbsCertificate.Validity.NotAfter, aValidityPeriodNotAfter);
300}
301
302HRESULT Certificate::getPublicKeyAlgorithmOID(com::Utf8Str &aPublicKeyAlgorithmOID)
303{
304 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
305
306 Assert(mData->m->fValidX509);
307 aPublicKeyAlgorithmOID = mData->m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId;
308 return S_OK;
309}
310
311HRESULT Certificate::getPublicKeyAlgorithm(com::Utf8Str &aPublicKeyAlgorithm)
312{
313 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
314
315 Assert(mData->m->fValidX509);
316 return i_getAlgorithmName(&mData->m->X509.TbsCertificate.SubjectPublicKeyInfo.Algorithm, aPublicKeyAlgorithm);
317}
318
319HRESULT Certificate::getSubjectPublicKey(std::vector<BYTE> &aSubjectPublicKey)
320{
321
322 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
323 return i_getEncodedBytes(&mData->m->X509.TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.Asn1Core, aSubjectPublicKey);
324}
325
326HRESULT Certificate::getIssuerUniqueIdentifier(com::Utf8Str &aIssuerUniqueIdentifier)
327{
328 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
329
330 return i_getUniqueIdentifier(&mData->m->X509.TbsCertificate.T1.IssuerUniqueId, aIssuerUniqueIdentifier);
331}
332
333HRESULT Certificate::getSubjectUniqueIdentifier(com::Utf8Str &aSubjectUniqueIdentifier)
334{
335 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
336
337 return i_getUniqueIdentifier(&mData->m->X509.TbsCertificate.T2.SubjectUniqueId, aSubjectUniqueIdentifier);
338}
339
340HRESULT Certificate::getCertificateAuthority(BOOL *aCertificateAuthority)
341{
342 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
343
344 *aCertificateAuthority = mData->m->X509.TbsCertificate.T3.pBasicConstraints
345 && mData->m->X509.TbsCertificate.T3.pBasicConstraints->CA.fValue;
346
347 return S_OK;
348}
349
350HRESULT Certificate::getKeyUsage(ULONG *aKeyUsage)
351{
352 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
353
354 *aKeyUsage = mData->m->X509.TbsCertificate.T3.fKeyUsage;
355 return S_OK;
356}
357
358HRESULT Certificate::getExtendedKeyUsage(std::vector<com::Utf8Str> &aExtendedKeyUsage)
359{
360 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
361 NOREF(aExtendedKeyUsage);
362 return E_NOTIMPL;
363}
364
365HRESULT Certificate::getRawCertData(std::vector<BYTE> &aRawCertData)
366{
367 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Getting encoded ASN.1 bytes may make changes to X509. */
368 return i_getEncodedBytes(&mData->m->X509.SeqCore.Asn1Core, aRawCertData);
369}
370
371HRESULT Certificate::getSelfSigned(BOOL *aSelfSigned)
372{
373 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
374
375 Assert(mData->m->fValidX509);
376 *aSelfSigned = RTCrX509Certificate_IsSelfSigned(&mData->m->X509);
377
378 return S_OK;
379}
380
381HRESULT Certificate::getTrusted(BOOL *aTrusted)
382{
383 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
384
385 Assert(mData->m->fValidX509);
386 *aTrusted = mData->m->fTrusted;
387
388 return S_OK;
389}
390
391HRESULT Certificate::getExpired(BOOL *aExpired)
392{
393 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
394 Assert(mData->m->fValidX509);
395 *aExpired = mData->m->fExpired;
396 return S_OK;
397}
398
399/** @} */
400
401/** @name Wrapped ICertificate methods
402 * @{
403 */
404
405HRESULT Certificate::isCurrentlyExpired(BOOL *aResult)
406{
407 AssertReturnStmt(mData->m->fValidX509, *aResult = TRUE, E_UNEXPECTED);
408 RTTIMESPEC Now;
409 *aResult = RTCrX509Validity_IsValidAtTimeSpec(&mData->m->X509.TbsCertificate.Validity, RTTimeNow(&Now)) ? FALSE : TRUE;
410 return S_OK;
411}
412
413HRESULT Certificate::queryInfo(LONG aWhat, com::Utf8Str &aResult)
414{
415 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
416 /* Insurance. */
417 NOREF(aResult);
418 return setError(E_FAIL, "Unknown item %u", aWhat);
419}
420
421/** @} */
422
423
424/** @name Methods extracting COM data from the certificate object
425 * @{
426 */
427
428/**
429 * Translates an algorithm OID into a human readable string, if possible.
430 *
431 * @returns S_OK.
432 * @param a_pAlgId The algorithm.
433 * @param a_rReturn The return string value.
434 * @throws std::bad_alloc
435 */
436HRESULT Certificate::i_getAlgorithmName(PCRTCRX509ALGORITHMIDENTIFIER a_pAlgId, com::Utf8Str &a_rReturn)
437{
438 const char *pszOid = a_pAlgId->Algorithm.szObjId;
439 const char *pszName;
440 if (!pszOid) pszName = "";
441 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_RSA)) pszName = "rsaEncryption";
442 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD2_WITH_RSA)) pszName = "md2WithRSAEncryption";
443 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD4_WITH_RSA)) pszName = "md4WithRSAEncryption";
444 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_MD5_WITH_RSA)) pszName = "md5WithRSAEncryption";
445 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA1_WITH_RSA)) pszName = "sha1WithRSAEncryption";
446 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA224_WITH_RSA)) pszName = "sha224WithRSAEncryption";
447 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA256_WITH_RSA)) pszName = "sha256WithRSAEncryption";
448 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA384_WITH_RSA)) pszName = "sha384WithRSAEncryption";
449 else if (strcmp(pszOid, RTCRX509ALGORITHMIDENTIFIERID_SHA512_WITH_RSA)) pszName = "sha512WithRSAEncryption";
450 else
451 pszName = pszOid;
452 a_rReturn = pszName;
453 return S_OK;
454}
455
456/**
457 * Formats a X.509 name into a string array.
458 *
459 * The name is prefix with a short hand of the relative distinguished name
460 * type followed by an equal sign.
461 *
462 * @returns S_OK.
463 * @param a_pName The X.509 name.
464 * @param a_rReturn The return string array.
465 * @throws std::bad_alloc
466 */
467HRESULT Certificate::i_getX509Name(PCRTCRX509NAME a_pName, std::vector<com::Utf8Str> &a_rReturn)
468{
469 if (RTCrX509Name_IsPresent(a_pName))
470 {
471 for (uint32_t i = 0; i < a_pName->cItems; i++)
472 {
473 PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = &a_pName->paItems[i];
474 for (uint32_t j = 0; j < pRdn->cItems; j++)
475 {
476 PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = &pRdn->paItems[j];
477
478 AssertReturn(pComponent->Value.enmType == RTASN1TYPE_STRING,
479 setErrorVrc(VERR_CR_X509_NAME_NOT_STRING, "VERR_CR_X509_NAME_NOT_STRING"));
480
481 /* Get the prefix for this name component. */
482 const char *pszPrefix = RTCrX509Name_GetShortRdn(&pComponent->Type);
483 AssertStmt(pszPrefix, pszPrefix = pComponent->Type.szObjId);
484
485 /* Get the string. */
486 const char *pszUtf8;
487 int vrc = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, NULL /*pcch*/);
488 AssertRCReturn(vrc, setErrorVrc(vrc, "RTAsn1String_QueryUtf8(%u/%u,,) -> %Rrc", i, j, vrc));
489
490 a_rReturn.push_back(Utf8StrFmt("%s=%s", pszPrefix, pszUtf8));
491 }
492 }
493 }
494 return S_OK;
495}
496
497/**
498 * Translates an ASN.1 timestamp into an ISO timestamp string.
499 *
500 * @returns S_OK.
501 * @param a_pTime The timestamp
502 * @param a_rReturn The return string value.
503 * @throws std::bad_alloc
504 */
505HRESULT Certificate::i_getTime(PCRTASN1TIME a_pTime, com::Utf8Str &a_rReturn)
506{
507 char szTmp[128];
508 if (RTTimeToString(&a_pTime->Time, szTmp, sizeof(szTmp)))
509 {
510 a_rReturn = szTmp;
511 return S_OK;
512 }
513 AssertFailed();
514 return E_FAIL;
515}
516
517/**
518 * Translates a X.509 unique identifier to a string.
519 *
520 * @returns S_OK.
521 * @param a_pUniqueId The unique identifier.
522 * @param a_rReturn The return string value.
523 * @throws std::bad_alloc
524 */
525HRESULT Certificate::i_getUniqueIdentifier(PCRTCRX509UNIQUEIDENTIFIER a_pUniqueId, com::Utf8Str &a_rReturn)
526{
527 /* The a_pUniqueId may not be present! */
528 if (RTCrX509UniqueIdentifier_IsPresent(a_pUniqueId))
529 {
530 void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(a_pUniqueId);
531 size_t const cbData = RTASN1BITSTRING_GET_BYTE_SIZE(a_pUniqueId);
532 size_t const cbFormatted = cbData * 3 - 1 + 1;
533 a_rReturn.reserve(cbFormatted); /* throws */
534 int vrc = RTStrPrintHexBytes(a_rReturn.mutableRaw(), cbFormatted, pvData, cbData, RTSTRPRINTHEXBYTES_F_SEP_COLON);
535 a_rReturn.jolt();
536 AssertRCReturn(vrc, Global::vboxStatusCodeToCOM(vrc));
537 }
538 else
539 Assert(a_rReturn.isEmpty());
540 return S_OK;
541}
542
543/**
544 * Translates any ASN.1 object into a (DER encoded) byte array.
545 *
546 * @returns S_OK.
547 * @param a_pAsn1Obj The ASN.1 object to get the DER encoded bytes for.
548 * @param a_rReturn The return byte vector.
549 * @throws std::bad_alloc
550 */
551HRESULT Certificate::i_getEncodedBytes(PRTASN1CORE a_pAsn1Obj, std::vector<BYTE> &a_rReturn)
552{
553 HRESULT hrc = S_OK;
554 Assert(a_rReturn.size() == 0);
555 if (RTAsn1Core_IsPresent(a_pAsn1Obj))
556 {
557 uint32_t cbEncoded;
558 int vrc = RTAsn1EncodePrepare(a_pAsn1Obj, 0, &cbEncoded, NULL);
559 if (RT_SUCCESS(vrc))
560 {
561 a_rReturn.resize(cbEncoded);
562 Assert(a_rReturn.size() == cbEncoded);
563 if (cbEncoded)
564 {
565 vrc = RTAsn1EncodeToBuffer(a_pAsn1Obj, 0, &a_rReturn.front(), a_rReturn.size(), NULL);
566 if (RT_FAILURE(vrc))
567 hrc = setErrorVrc(vrc, "RTAsn1EncodeToBuffer failed with %Rrc", vrc);
568 }
569 }
570 else
571 hrc = setErrorVrc(vrc, "RTAsn1EncodePrepare failed with %Rrc", vrc);
572 }
573 return hrc;
574}
575
576/** @} */
577
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette