Source release 16.3.0
This commit is contained in:
@@ -708,12 +708,10 @@ uint8_t CryptoSession::GetSecurityPatchLevel() {
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
||||
LOGD(
|
||||
"Opening crypto session: requested_security_level: "
|
||||
"requested_security_level = %s",
|
||||
requested_security_level == kLevel3
|
||||
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
||||
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
||||
LOGD("Opening crypto session: requested_security_level = %s",
|
||||
requested_security_level == kLevel3
|
||||
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
||||
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
||||
RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR);
|
||||
if (open_) return NO_ERROR;
|
||||
|
||||
@@ -750,12 +748,13 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
||||
open_ = true;
|
||||
|
||||
// Get System ID and save it.
|
||||
if (GetSystemIdInternal(&system_id_) == NO_ERROR) {
|
||||
result = GetSystemIdInternal(&system_id_);
|
||||
if (result == NO_ERROR) {
|
||||
metrics_->crypto_session_system_id_.Record(system_id_);
|
||||
} else {
|
||||
LOGE("Failed to fetch system ID");
|
||||
metrics_->crypto_session_system_id_.SetError(LOAD_SYSTEM_ID_ERROR);
|
||||
return LOAD_SYSTEM_ID_ERROR;
|
||||
metrics_->crypto_session_system_id_.SetError(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set up request ID
|
||||
@@ -802,35 +801,33 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
||||
CdmSecurityLevel security_level = GetSecurityLevel();
|
||||
if (security_level == kSecurityLevelL1 ||
|
||||
security_level == kSecurityLevelL3) {
|
||||
{
|
||||
// This block cannot use |WithStaticFieldWriteLock| because it needs
|
||||
// to unlock the lock partway through.
|
||||
LOGV("Static field write lock: Open() initializing usage table");
|
||||
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
|
||||
// This block cannot use |WithStaticFieldWriteLock| because it needs
|
||||
// to unlock the lock partway through.
|
||||
LOGV("Static field write lock: Open() initializing usage table");
|
||||
std::unique_lock<shared_mutex> auto_lock(static_field_mutex_);
|
||||
|
||||
UsageTableHeader** header = security_level == kSecurityLevelL1
|
||||
? &usage_table_header_l1_
|
||||
: &usage_table_header_l3_;
|
||||
if (*header == nullptr) {
|
||||
*header = new UsageTableHeader();
|
||||
// Ignore errors since we do not know when a session is opened,
|
||||
// if it is intended to be used for offline/usage session related
|
||||
// or otherwise.
|
||||
auto_lock.unlock();
|
||||
bool is_usage_table_header_inited =
|
||||
(*header)->Init(security_level, this);
|
||||
auto_lock.lock();
|
||||
if (!is_usage_table_header_inited) {
|
||||
delete *header;
|
||||
*header = nullptr;
|
||||
usage_table_header_ = nullptr;
|
||||
return NO_ERROR;
|
||||
}
|
||||
UsageTableHeader** header = security_level == kSecurityLevelL1
|
||||
? &usage_table_header_l1_
|
||||
: &usage_table_header_l3_;
|
||||
if (*header == nullptr) {
|
||||
*header = new UsageTableHeader();
|
||||
// Ignore errors since we do not know when a session is opened,
|
||||
// if it is intended to be used for offline/usage session related
|
||||
// or otherwise.
|
||||
auto_lock.unlock();
|
||||
bool is_usage_table_header_inited =
|
||||
(*header)->Init(security_level, this);
|
||||
auto_lock.lock();
|
||||
if (!is_usage_table_header_inited) {
|
||||
delete *header;
|
||||
*header = nullptr;
|
||||
usage_table_header_ = nullptr;
|
||||
return NO_ERROR;
|
||||
}
|
||||
usage_table_header_ = *header;
|
||||
metrics_->usage_table_header_initial_size_.Record((*header)->size());
|
||||
}
|
||||
}
|
||||
usage_table_header_ = *header;
|
||||
metrics_->usage_table_header_initial_size_.Record((*header)->size());
|
||||
} // End |static_field_mutex_| block.
|
||||
}
|
||||
} else {
|
||||
metrics_->oemcrypto_usage_table_support_.SetError(result);
|
||||
@@ -943,7 +940,7 @@ CdmResponseType CryptoSession::LoadKeys(
|
||||
update_usage_table_after_close_session_ = true;
|
||||
return KEY_ADDED;
|
||||
case OEMCrypto_ERROR_TOO_MANY_KEYS:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_4;
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE:
|
||||
// Handle vendor specific error
|
||||
return NEED_PROVISIONING;
|
||||
@@ -983,7 +980,7 @@ CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message,
|
||||
return LOAD_LICENSE_ERROR;
|
||||
case OEMCrypto_ERROR_TOO_MANY_KEYS:
|
||||
LOGE("Too many keys in license");
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_4;
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1185,16 +1182,15 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
|
||||
|
||||
CdmResponseType CryptoSession::LoadEntitledContentKeys(
|
||||
const std::vector<CryptoKey>& key_array) {
|
||||
OEMCryptoResult sts;
|
||||
WithOecSessionLock("LoadEntitledContentKeys", [&] {
|
||||
sts = key_session_->LoadEntitledContentKeys(key_array);
|
||||
});
|
||||
const OEMCryptoResult sts = WithOecSessionLock(
|
||||
"LoadEntitledContentKeys",
|
||||
[&] { return key_session_->LoadEntitledContentKeys(key_array); });
|
||||
|
||||
switch (sts) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
return KEY_ADDED;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_6;
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
||||
return NOT_AN_ENTITLEMENT_SESSION;
|
||||
case OEMCrypto_KEY_NOT_ENTITLED:
|
||||
@@ -1243,39 +1239,46 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey(
|
||||
// Private.
|
||||
CdmResponseType CryptoSession::SelectKey(const std::string& key_id,
|
||||
CdmCipherMode cipher_mode) {
|
||||
OEMCryptoResult sts;
|
||||
WithOecSessionLock(
|
||||
"SelectKey", [&] { sts = key_session_->SelectKey(key_id, cipher_mode); });
|
||||
const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] {
|
||||
return key_session_->SelectKey(key_id, cipher_mode);
|
||||
});
|
||||
|
||||
switch (sts) {
|
||||
// SelectKey errors.
|
||||
case OEMCrypto_SUCCESS:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_KEY_EXPIRED:
|
||||
return NEED_KEY;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
||||
return INSUFFICIENT_OUTPUT_PROTECTION;
|
||||
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
||||
return ANALOG_OUTPUT_ERROR;
|
||||
case OEMCrypto_ERROR_INVALID_SESSION:
|
||||
return INVALID_SESSION_1;
|
||||
case OEMCrypto_ERROR_NO_DEVICE_KEY:
|
||||
return NO_DEVICE_KEY_1;
|
||||
case OEMCrypto_ERROR_NO_CONTENT_KEY:
|
||||
return NO_CONTENT_KEY_2;
|
||||
case OEMCrypto_KEY_NOT_LOADED: // obsolete.
|
||||
return NO_CONTENT_KEY_3;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_2;
|
||||
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
||||
return UNKNOWN_SELECT_KEY_ERROR_1;
|
||||
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
||||
return SESSION_LOST_STATE_ERROR;
|
||||
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
||||
return SYSTEM_INVALIDATED_ERROR;
|
||||
case OEMCrypto_ERROR_CONTROL_INVALID:
|
||||
case OEMCrypto_ERROR_KEYBOX_INVALID:
|
||||
default:
|
||||
return UNKNOWN_SELECT_KEY_ERROR_2;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
|
||||
return UNKNOWN_SELECT_KEY_ERROR_1;
|
||||
case OEMCrypto_ERROR_ANALOG_OUTPUT:
|
||||
return ANALOG_OUTPUT_ERROR;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
|
||||
return INSUFFICIENT_OUTPUT_PROTECTION;
|
||||
// LoadEntitledContentKeys errors.
|
||||
// |key_session_| may make calls to OEMCrypto_LoadEntitledContentKeys
|
||||
// if the key selected has not yet been loaded.
|
||||
case OEMCrypto_ERROR_INVALID_CONTEXT:
|
||||
return NOT_AN_ENTITLEMENT_SESSION;
|
||||
case OEMCrypto_KEY_NOT_ENTITLED:
|
||||
return NO_MATCHING_ENTITLEMENT_KEY;
|
||||
// Obsolete errors.
|
||||
case OEMCrypto_KEY_NOT_LOADED:
|
||||
return NO_CONTENT_KEY_3;
|
||||
// Catch all else.
|
||||
default:
|
||||
return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,7 +1533,7 @@ CdmResponseType CryptoSession::Decrypt(
|
||||
case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_5;
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_KEY_EXPIRED:
|
||||
return NEED_KEY;
|
||||
case OEMCrypto_ERROR_INVALID_SESSION:
|
||||
@@ -2005,6 +2008,11 @@ bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level,
|
||||
metrics_->oemcrypto_maximum_usage_table_header_size_.Record(
|
||||
*number_of_entries);
|
||||
|
||||
if (*number_of_entries == 0) {
|
||||
// Special value, indicating that the table size is not directly
|
||||
// limited.
|
||||
return true;
|
||||
}
|
||||
return *number_of_entries >= kMinimumUsageTableEntriesSupported;
|
||||
}
|
||||
|
||||
@@ -2349,18 +2357,20 @@ CdmResponseType CryptoSession::GetUsageSupportType(
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::CreateUsageTableHeader(
|
||||
SecurityLevel requested_security_level,
|
||||
CdmUsageTableHeader* usage_table_header) {
|
||||
LOGV("Creating usage table header: id = %u", oec_session_id_);
|
||||
|
||||
LOGV("Creating usage table header: requested_security_level = %s",
|
||||
requested_security_level == kLevel3
|
||||
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
||||
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
||||
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
||||
|
||||
usage_table_header->resize(kEstimatedInitialUsageTableHeader);
|
||||
|
||||
size_t usage_table_header_size = usage_table_header->size();
|
||||
OEMCryptoResult result;
|
||||
WithOecWriteLock("CreateUsageTableHeader Attempt 1", [&] {
|
||||
result = OEMCrypto_CreateUsageTableHeader(
|
||||
requested_security_level_,
|
||||
requested_security_level,
|
||||
reinterpret_cast<uint8_t*>(
|
||||
const_cast<char*>(usage_table_header->data())),
|
||||
&usage_table_header_size);
|
||||
@@ -2371,7 +2381,7 @@ CdmResponseType CryptoSession::CreateUsageTableHeader(
|
||||
usage_table_header->resize(usage_table_header_size);
|
||||
WithOecWriteLock("CreateUsageTableHeader Attempt 2", [&] {
|
||||
result = OEMCrypto_CreateUsageTableHeader(
|
||||
requested_security_level_,
|
||||
requested_security_level,
|
||||
reinterpret_cast<uint8_t*>(
|
||||
const_cast<char*>(usage_table_header->data())),
|
||||
&usage_table_header_size);
|
||||
@@ -2379,22 +2389,28 @@ CdmResponseType CryptoSession::CreateUsageTableHeader(
|
||||
});
|
||||
}
|
||||
|
||||
if (result == OEMCrypto_SUCCESS) {
|
||||
usage_table_header->resize(usage_table_header_size);
|
||||
switch (result) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
usage_table_header->resize(usage_table_header_size);
|
||||
return NO_ERROR;
|
||||
default:
|
||||
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
|
||||
"CreateUsageTableHeader");
|
||||
}
|
||||
|
||||
return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR,
|
||||
"CreateUsageTableHeader");
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::LoadUsageTableHeader(
|
||||
SecurityLevel requested_security_level,
|
||||
const CdmUsageTableHeader& usage_table_header) {
|
||||
LOGV("Loading usage table header: id = %u", oec_session_id_);
|
||||
LOGV("Loading usage table header: requested_security_level = %s",
|
||||
requested_security_level == kLevel3
|
||||
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
||||
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
||||
|
||||
OEMCryptoResult result;
|
||||
WithOecWriteLock("LoadUsageTableHeader", [&] {
|
||||
result = OEMCrypto_LoadUsageTableHeader(
|
||||
requested_security_level_,
|
||||
requested_security_level,
|
||||
reinterpret_cast<const uint8_t*>(usage_table_header.data()),
|
||||
usage_table_header.size());
|
||||
metrics_->oemcrypto_load_usage_table_header_.Increment(result);
|
||||
@@ -2427,6 +2443,48 @@ CdmResponseType CryptoSession::LoadUsageTableHeader(
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
||||
SecurityLevel requested_security_level, uint32_t new_entry_count,
|
||||
CdmUsageTableHeader* usage_table_header) {
|
||||
LOGV("Shrinking usage table header: requested_security_level = %s",
|
||||
requested_security_level == kLevel3
|
||||
? QUERY_VALUE_SECURITY_LEVEL_L3.c_str()
|
||||
: QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str());
|
||||
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
||||
|
||||
size_t usage_table_header_len = 0;
|
||||
OEMCryptoResult result;
|
||||
WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] {
|
||||
result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level,
|
||||
new_entry_count, nullptr,
|
||||
&usage_table_header_len);
|
||||
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
||||
});
|
||||
|
||||
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] {
|
||||
result = OEMCrypto_ShrinkUsageTableHeader(
|
||||
requested_security_level, new_entry_count,
|
||||
reinterpret_cast<uint8_t*>(
|
||||
const_cast<char*>(usage_table_header->data())),
|
||||
&usage_table_header_len);
|
||||
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
||||
});
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_ENTRY_IN_USE:
|
||||
return SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE;
|
||||
default:
|
||||
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR,
|
||||
"ShrinkUsageTableHeader");
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
|
||||
LOGV("Creating usage entry: id = %u", oec_session_id_);
|
||||
|
||||
@@ -2447,7 +2505,7 @@ CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
||||
return SESSION_LOST_STATE_ERROR;
|
||||
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
||||
@@ -2483,18 +2541,19 @@ CdmResponseType CryptoSession::LoadUsageEntry(
|
||||
case OEMCrypto_SUCCESS:
|
||||
case OEMCrypto_WARNING_GENERATION_SKEW:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_INVALID_SESSION:
|
||||
// This case is special, as it could imply that the provided
|
||||
// session ID is invalid (CDM internal bug), or that the entry
|
||||
// being loaded is already in use in a different session.
|
||||
// It is up to the caller to handle this.
|
||||
return LOAD_USAGE_ENTRY_INVALID_SESSION;
|
||||
case OEMCrypto_ERROR_GENERATION_SKEW:
|
||||
return LOAD_USAGE_ENTRY_GENERATION_SKEW;
|
||||
case OEMCrypto_ERROR_SIGNATURE_FAILURE:
|
||||
return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES_3;
|
||||
case OEMCrypto_ERROR_SESSION_LOST_STATE:
|
||||
return SESSION_LOST_STATE_ERROR;
|
||||
case OEMCrypto_ERROR_SYSTEM_INVALIDATED:
|
||||
return SYSTEM_INVALIDATED_ERROR;
|
||||
default:
|
||||
return LOAD_USAGE_ENTRY_UNKNOWN_ERROR;
|
||||
return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR,
|
||||
"LoadUsageEntry");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2540,42 +2599,6 @@ CdmResponseType CryptoSession::UpdateUsageEntry(
|
||||
"UpdateUsageEntry");
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
||||
uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header) {
|
||||
LOGV("Shrinking usage table header: id = %u", oec_session_id_);
|
||||
|
||||
RETURN_IF_NULL(usage_table_header, PARAMETER_NULL);
|
||||
|
||||
size_t usage_table_header_len = 0;
|
||||
OEMCryptoResult result;
|
||||
WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] {
|
||||
result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level_,
|
||||
new_entry_count, nullptr,
|
||||
&usage_table_header_len);
|
||||
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
||||
});
|
||||
|
||||
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
|
||||
WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] {
|
||||
result = OEMCrypto_ShrinkUsageTableHeader(
|
||||
requested_security_level_, new_entry_count,
|
||||
reinterpret_cast<uint8_t*>(
|
||||
const_cast<char*>(usage_table_header->data())),
|
||||
&usage_table_header_len);
|
||||
metrics_->oemcrypto_shrink_usage_table_header_.Increment(result);
|
||||
});
|
||||
}
|
||||
|
||||
if (result == OEMCrypto_SUCCESS) {
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
}
|
||||
|
||||
return MapOEMCryptoResult(result, SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR,
|
||||
"ShrinkUsageTableHeader");
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
|
||||
LOGV("Moving usage entry: id = %u", oec_session_id_);
|
||||
|
||||
@@ -2585,8 +2608,15 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) {
|
||||
metrics_->oemcrypto_move_entry_.Increment(result);
|
||||
});
|
||||
|
||||
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
|
||||
"MoveUsageEntry");
|
||||
switch (result) {
|
||||
case OEMCrypto_ERROR_ENTRY_IN_USE:
|
||||
LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u",
|
||||
new_entry_number);
|
||||
return MOVE_USAGE_ENTRY_DESTINATION_IN_USE;
|
||||
default:
|
||||
return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR,
|
||||
"MoveUsageEntry");
|
||||
}
|
||||
}
|
||||
|
||||
bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output,
|
||||
|
||||
Reference in New Issue
Block a user