Add client information to release and renewal messages
[ Merge of go/wvgerrit/14240 ] Client information is reported in release and renewal messages based on flag in the license. License proto has been updated to match server updates. There are two caveats * Client IDs will be reported unencrypted when usage reports are requested. * Release requests that enable privacy mode (encrypted client IDs) but do not specify a service certificate are not supported. b/19247020 Change-Id: I95e709922122370f310936fbad3d312262128e49
This commit is contained in:
@@ -129,7 +129,10 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
||||
}
|
||||
|
||||
CdmLicense::CdmLicense()
|
||||
: session_(NULL), initialized_(false), clock_(new Clock()) {}
|
||||
: session_(NULL),
|
||||
initialized_(false),
|
||||
renew_with_client_id_(false),
|
||||
clock_(new Clock()) {}
|
||||
|
||||
CdmLicense::CdmLicense(Clock* clock) : session_(NULL), initialized_(false) {
|
||||
if (NULL == clock) {
|
||||
@@ -194,184 +197,23 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string service_certificate;
|
||||
bool privacy_mode_enabled = Properties::UsePrivacyMode(session_id);
|
||||
std::string serialized_service_certificate;
|
||||
if (!Properties::GetServiceCertificate(session_id,
|
||||
&serialized_service_certificate) ||
|
||||
serialized_service_certificate.empty())
|
||||
serialized_service_certificate = service_certificate_;
|
||||
|
||||
if (privacy_mode_enabled && serialized_service_certificate.empty()) {
|
||||
stored_init_data_ = init_data.data();
|
||||
return PrepareServiceCertificateRequest(signed_request, server_url);
|
||||
if (privacy_mode_enabled) {
|
||||
if (!GetServiceCertificate(session_id, &service_certificate)) {
|
||||
stored_init_data_ = init_data.data();
|
||||
return PrepareServiceCertificateRequest(signed_request, server_url);
|
||||
}
|
||||
}
|
||||
|
||||
std::string request_id;
|
||||
session_->GenerateRequestId(&request_id);
|
||||
|
||||
LicenseRequest license_request;
|
||||
ClientIdentification* client_id = license_request.mutable_client_id();
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE);
|
||||
else
|
||||
client_id->set_type(ClientIdentification::KEYBOX);
|
||||
client_id->set_token(token_);
|
||||
|
||||
ClientIdentification_NameValue* client_info;
|
||||
CdmAppParameterMap::const_iterator iter;
|
||||
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(iter->first);
|
||||
client_info->set_value(iter->second);
|
||||
}
|
||||
std::string value;
|
||||
if (Properties::GetCompanyName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kCompanyNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetModelName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kModelNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetArchitectureName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kArchitectureNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetDeviceName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetProductName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kProductNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetBuildInfo(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kBuildInfoKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (session_->GetDeviceUniqueId(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceIdKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetWVCdmVersion(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kWVCdmVersionKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
ClientIdentification_ClientCapabilities* client_capabilities =
|
||||
client_id->mutable_client_capabilities();
|
||||
bool supports_usage_information;
|
||||
if (session_->UsageInformationSupport(&supports_usage_information)) {
|
||||
client_capabilities->set_session_token(supports_usage_information);
|
||||
}
|
||||
|
||||
client_capabilities->set_anti_rollback_usage_table(
|
||||
session_->IsAntiRollbackHwPresent());
|
||||
|
||||
uint32_t api_version = 0;
|
||||
if (session_->GetApiVersion(&api_version)) {
|
||||
client_capabilities->set_oem_crypto_api_version(api_version);
|
||||
}
|
||||
|
||||
CryptoSession::HdcpCapability current_version, max_version;
|
||||
if (session_->GetHdcpCapabilities(¤t_version, &max_version)) {
|
||||
switch (max_version) {
|
||||
case HDCP_NONE:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE);
|
||||
break;
|
||||
case HDCP_V1:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1);
|
||||
break;
|
||||
case HDCP_V2:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2);
|
||||
break;
|
||||
case HDCP_V2_1:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1);
|
||||
break;
|
||||
case HDCP_V2_2:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2);
|
||||
break;
|
||||
case HDCP_NO_DIGITAL_OUTPUT:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NO_DIGITAL_OUTPUT);
|
||||
break;
|
||||
default:
|
||||
LOGW(
|
||||
"CdmLicense::PrepareKeyRequest: unexpected HDCP max capability "
|
||||
"version %d",
|
||||
max_version);
|
||||
}
|
||||
}
|
||||
|
||||
if (privacy_mode_enabled) {
|
||||
EncryptedClientIdentification* encrypted_client_id =
|
||||
license_request.mutable_encrypted_client_id();
|
||||
DeviceCertificate service_certificate;
|
||||
|
||||
if (!service_certificate.ParseFromString(serialized_service_certificate)) {
|
||||
LOGE(
|
||||
"CdmLicense::PrepareKeyRequest: unable to parse retrieved "
|
||||
"service certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service_certificate.type() !=
|
||||
video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) {
|
||||
LOGE(
|
||||
"CdmLicense::PrepareKeyRequest: retrieved certificate not of type"
|
||||
" service, %d",
|
||||
service_certificate.type());
|
||||
return false;
|
||||
}
|
||||
encrypted_client_id->set_service_id(service_certificate.service_id());
|
||||
encrypted_client_id->set_service_certificate_serial_number(
|
||||
service_certificate.serial_number());
|
||||
|
||||
std::string iv(KEY_IV_SIZE, 0);
|
||||
std::string key(KEY_SIZE, 0);
|
||||
|
||||
if (!session_->GetRandom(key.size(), reinterpret_cast<uint8_t*>(&key[0]))) {
|
||||
return false;
|
||||
}
|
||||
if (!session_->GetRandom(iv.size(), reinterpret_cast<uint8_t*>(&iv[0]))) {
|
||||
return false;
|
||||
}
|
||||
std::string id, enc_id, enc_key;
|
||||
client_id->SerializeToString(&id);
|
||||
|
||||
AesCbcKey aes;
|
||||
if (!aes.Init(key)) return false;
|
||||
if (!aes.Encrypt(id, &enc_id, &iv)) return false;
|
||||
|
||||
RsaPublicKey rsa;
|
||||
if (!rsa.Init(service_certificate.public_key())) return false;
|
||||
if (!rsa.Encrypt(key, &enc_key)) return false;
|
||||
|
||||
encrypted_client_id->set_encrypted_client_id_iv(iv);
|
||||
encrypted_client_id->set_encrypted_privacy_key(enc_key);
|
||||
encrypted_client_id->set_encrypted_client_id(enc_id);
|
||||
license_request.clear_client_id();
|
||||
}
|
||||
CdmResponseType status =
|
||||
PrepareClientId(privacy_mode_enabled, service_certificate, app_parameters,
|
||||
&license_request);
|
||||
if (NO_ERROR != status) return false;
|
||||
|
||||
// Content Identification may be a cenc_id, a webm_id or a license_id
|
||||
LicenseRequest_ContentIdentification* content_id =
|
||||
@@ -463,7 +305,9 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data,
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
bool is_renewal, CdmKeyMessage* signed_request, std::string* server_url) {
|
||||
bool is_renewal, const CdmAppParameterMap& app_parameters,
|
||||
const CdmSessionId& session_id, CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized");
|
||||
return LICENSE_PARSER_NOT_INITIALIZED_1;
|
||||
@@ -485,6 +329,22 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
|
||||
license_request.set_request_time(clock_->GetCurrentTime());
|
||||
|
||||
if (renew_with_client_id_) {
|
||||
std::string service_certificate;
|
||||
bool privacy_mode_enabled = Properties::UsePrivacyMode(session_id);
|
||||
if (privacy_mode_enabled) {
|
||||
if (!GetServiceCertificate(session_id, &service_certificate)) {
|
||||
return PrepareServiceCertificateRequest(signed_request, server_url)
|
||||
? KEY_MESSAGE
|
||||
: SERVICE_CERTIFICATE_REQUEST_GENERATE_ERROR;
|
||||
}
|
||||
}
|
||||
CdmResponseType status =
|
||||
PrepareClientId(privacy_mode_enabled, service_certificate,
|
||||
app_parameters, &license_request);
|
||||
if (NO_ERROR != status) return status;
|
||||
}
|
||||
|
||||
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
||||
license_request.mutable_content_id()->mutable_license();
|
||||
LicenseIdentification license_id = policy_engine_->license_id();
|
||||
@@ -653,6 +513,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
|
||||
policy_engine_->SetLicense(license);
|
||||
|
||||
if (license.policy().has_renew_with_client_id()) {
|
||||
renew_with_client_id_ = license.policy().renew_with_client_id();
|
||||
}
|
||||
|
||||
CdmResponseType resp = session_->LoadKeys(
|
||||
signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key,
|
||||
key_array, provider_session_token_);
|
||||
@@ -684,8 +548,19 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
return LICENSE_RESPONSE_PARSE_ERROR_2;
|
||||
}
|
||||
|
||||
if (signed_response.type() == SignedMessage::ERROR_RESPONSE) {
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
switch (signed_response.type()) {
|
||||
case SignedMessage::LICENSE:
|
||||
break;
|
||||
case SignedMessage::SERVICE_CERTIFICATE:
|
||||
return CdmLicense::HandleServiceCertificateResponse(signed_response);
|
||||
case SignedMessage::ERROR_RESPONSE:
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
default:
|
||||
LOGE(
|
||||
"CdmLicense::HandleKeyUpdateResponse: unrecognized signed message "
|
||||
"type: %d",
|
||||
signed_response.type());
|
||||
return INVALID_LICENSE_TYPE;
|
||||
}
|
||||
|
||||
if (!signed_response.has_signature()) {
|
||||
@@ -708,6 +583,10 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
|
||||
policy_engine_->UpdateLicense(license);
|
||||
|
||||
if (license.policy().has_renew_with_client_id()) {
|
||||
renew_with_client_id_ = license.policy().renew_with_client_id();
|
||||
}
|
||||
|
||||
if (!is_renewal) {
|
||||
if (!license.id().has_provider_session_token()) return KEY_ADDED;
|
||||
|
||||
@@ -842,27 +721,32 @@ bool CdmLicense::RestoreLicenseForRelease(
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response)) {
|
||||
LOGE("CdmLicense::RestoreLicenseForRelease: unable to parse signed license"
|
||||
" response");
|
||||
LOGE(
|
||||
"CdmLicense::RestoreLicenseForRelease: unable to parse signed license"
|
||||
" response");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SignedMessage::LICENSE != signed_response.type()) {
|
||||
LOGE("CdmLicense::RestoreLicenseForRelease: unrecognized signed message "
|
||||
"type: %d", signed_response.type());
|
||||
LOGE(
|
||||
"CdmLicense::RestoreLicenseForRelease: unrecognized signed message "
|
||||
"type: %d",
|
||||
signed_response.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!signed_response.has_signature()) {
|
||||
LOGE("CdmLicense::RestoreLicenseForRelease: license response is not"
|
||||
" signed");
|
||||
LOGE(
|
||||
"CdmLicense::RestoreLicenseForRelease: license response is not"
|
||||
" signed");
|
||||
return false;
|
||||
}
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg())) {
|
||||
LOGE("CdmLicense::RestoreLicenseForRelease: unable to parse license"
|
||||
" response");
|
||||
LOGE(
|
||||
"CdmLicense::RestoreLicenseForRelease: unable to parse license"
|
||||
" response");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -994,6 +878,183 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::PrepareClientId(
|
||||
bool encrypt, const std::string& certificate,
|
||||
const CdmAppParameterMap& app_parameters, LicenseRequest* license_request) {
|
||||
ClientIdentification* client_id = license_request->mutable_client_id();
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE);
|
||||
else
|
||||
client_id->set_type(ClientIdentification::KEYBOX);
|
||||
client_id->set_token(token_);
|
||||
|
||||
ClientIdentification_NameValue* client_info;
|
||||
CdmAppParameterMap::const_iterator iter;
|
||||
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(iter->first);
|
||||
client_info->set_value(iter->second);
|
||||
}
|
||||
std::string value;
|
||||
if (Properties::GetCompanyName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kCompanyNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetModelName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kModelNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetArchitectureName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kArchitectureNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetDeviceName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetProductName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kProductNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetBuildInfo(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kBuildInfoKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (session_->GetDeviceUniqueId(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceIdKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetWVCdmVersion(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kWVCdmVersionKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
ClientIdentification_ClientCapabilities* client_capabilities =
|
||||
client_id->mutable_client_capabilities();
|
||||
bool supports_usage_information;
|
||||
if (session_->UsageInformationSupport(&supports_usage_information)) {
|
||||
client_capabilities->set_session_token(supports_usage_information);
|
||||
}
|
||||
|
||||
client_capabilities->set_anti_rollback_usage_table(
|
||||
session_->IsAntiRollbackHwPresent());
|
||||
|
||||
uint32_t api_version = 0;
|
||||
if (session_->GetApiVersion(&api_version)) {
|
||||
client_capabilities->set_oem_crypto_api_version(api_version);
|
||||
}
|
||||
|
||||
CryptoSession::HdcpCapability current_version, max_version;
|
||||
if (session_->GetHdcpCapabilities(¤t_version, &max_version)) {
|
||||
switch (max_version) {
|
||||
case HDCP_NONE:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE);
|
||||
break;
|
||||
case HDCP_V1:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1);
|
||||
break;
|
||||
case HDCP_V2:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2);
|
||||
break;
|
||||
case HDCP_V2_1:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1);
|
||||
break;
|
||||
case HDCP_V2_2:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2);
|
||||
break;
|
||||
case HDCP_NO_DIGITAL_OUTPUT:
|
||||
client_capabilities->set_max_hdcp_version(
|
||||
video_widevine_server::sdk::
|
||||
ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NO_DIGITAL_OUTPUT);
|
||||
break;
|
||||
default:
|
||||
LOGW(
|
||||
"CdmLicense::PrepareClientId: unexpected HDCP max capability "
|
||||
"version %d",
|
||||
max_version);
|
||||
}
|
||||
}
|
||||
|
||||
if (encrypt) {
|
||||
EncryptedClientIdentification* encrypted_client_id =
|
||||
license_request->mutable_encrypted_client_id();
|
||||
DeviceCertificate service_certificate;
|
||||
|
||||
if (!service_certificate.ParseFromString(certificate)) {
|
||||
LOGE(
|
||||
"CdmLicense::PrepareClientId: unable to parse retrieved "
|
||||
"service certificate");
|
||||
return PARSE_SERVICE_CERTIFICATE_ERROR;
|
||||
}
|
||||
|
||||
if (service_certificate.type() !=
|
||||
video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) {
|
||||
LOGE(
|
||||
"CdmLicense::PrepareClientId: retrieved certificate not of type"
|
||||
" service, %d",
|
||||
service_certificate.type());
|
||||
return SERVICE_CERTIFICATE_TYPE_ERROR;
|
||||
}
|
||||
encrypted_client_id->set_service_id(service_certificate.service_id());
|
||||
encrypted_client_id->set_service_certificate_serial_number(
|
||||
service_certificate.serial_number());
|
||||
|
||||
std::string iv(KEY_IV_SIZE, 0);
|
||||
std::string key(KEY_SIZE, 0);
|
||||
|
||||
if (!session_->GetRandom(key.size(), reinterpret_cast<uint8_t*>(&key[0])))
|
||||
return CLIENT_ID_GENERATE_RANDOM_ERROR;
|
||||
if (!session_->GetRandom(iv.size(), reinterpret_cast<uint8_t*>(&iv[0])))
|
||||
return CLIENT_ID_GENERATE_RANDOM_ERROR;
|
||||
std::string id, enc_id, enc_key;
|
||||
client_id->SerializeToString(&id);
|
||||
|
||||
AesCbcKey aes;
|
||||
if (!aes.Init(key)) return CLIENT_ID_AES_INIT_ERROR;
|
||||
if (!aes.Encrypt(id, &enc_id, &iv)) return CLIENT_ID_AES_ENCRYPT_ERROR;
|
||||
|
||||
RsaPublicKey rsa;
|
||||
if (!rsa.Init(service_certificate.public_key()))
|
||||
return CLIENT_ID_RSA_INIT_ERROR;
|
||||
if (!rsa.Encrypt(key, &enc_key)) return CLIENT_ID_RSA_ENCRYPT_ERROR;
|
||||
|
||||
encrypted_client_id->set_encrypted_client_id_iv(iv);
|
||||
encrypted_client_id->set_encrypted_privacy_key(enc_key);
|
||||
encrypted_client_id->set_encrypted_client_id(enc_id);
|
||||
license_request->clear_client_id();
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool CdmLicense::GetServiceCertificate(const CdmSessionId& session_id,
|
||||
std::string* service_certificate) {
|
||||
if (!Properties::GetServiceCertificate(session_id, service_certificate) ||
|
||||
service_certificate->empty())
|
||||
*service_certificate = service_certificate_;
|
||||
|
||||
if (service_certificate->size() > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool CdmLicense::PrepareContentId(const CdmLicenseType license_type,
|
||||
const std::string& request_id,
|
||||
|
||||
Reference in New Issue
Block a user