Update OEMCrypto calls to use substrings
Merge from master branch of Widevine repo of http://go/wvgerrit/66073 Merge from oemcrypto-v15 branch of Widevine repo of http://go/wvgerrit/64083 As part of the update to v15, LoadKeys, RefreshKeys, and LoadEntitledContentKeys should all use offsets and lengths into the message rather than a pointer for its parameters. The CDM, tests, adapters, and OEMCrypto implementations are changed to reflect this. Test: tested as part of http://go/ag/5501993 Bug: 115874964 Change-Id: I981fa322dec7c565066fd163ca5775dbff71fccf
This commit is contained in:
committed by
Fred Gylys-Colwell
parent
4550979f22
commit
e6439255ba
@@ -239,12 +239,21 @@ bool RangeCheck(const uint8_t* message, uint32_t message_length,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RangeCheck(uint32_t message_length, const OEMCrypto_Substring& substring,
|
||||
bool allow_null) {
|
||||
if (!substring.length) return allow_null;
|
||||
if (substring.offset > message_length) return false;
|
||||
if (substring.offset + substring.length > message_length) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
const uint8_t* signature, size_t signature_length,
|
||||
const uint8_t* enc_mac_key_iv, const uint8_t* enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array, const uint8_t* pst, size_t pst_length,
|
||||
const uint8_t* srm_requirement, OEMCrypto_LicenseType license_type) {
|
||||
OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys,
|
||||
size_t num_keys, const OEMCrypto_KeyObject* key_array,
|
||||
OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data,
|
||||
OEMCrypto_LicenseType license_type) {
|
||||
if (!crypto_engine) {
|
||||
LOGE("OEMCrypto_LoadKeys: OEMCrypto Not Initialized.");
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
@@ -263,20 +272,12 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
// Later on, we use pst_length to verify the the pst is valid. This makes
|
||||
// sure that we aren't given a null string but told it has postiive length.
|
||||
if (pst == NULL && pst_length > 0) {
|
||||
LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_ONCTEXT - null pst.]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
// Range check
|
||||
if (!RangeCheck(message, message_length, enc_mac_keys, 2 * wvoec::MAC_KEY_SIZE,
|
||||
true) ||
|
||||
!RangeCheck(message, message_length, enc_mac_key_iv, wvoec::KEY_IV_SIZE, true) ||
|
||||
!RangeCheck(message, message_length, pst, pst_length, true) ||
|
||||
!RangeCheck(message, message_length, srm_requirement,
|
||||
wvoec::SRM_REQUIREMENT_SIZE, true)) {
|
||||
if (!RangeCheck(message_length, enc_mac_keys_iv, true) ||
|
||||
!RangeCheck(message_length, enc_mac_keys, true) ||
|
||||
!RangeCheck(message_length, pst, true) ||
|
||||
!RangeCheck(message_length, srm_restriction_data, true)) {
|
||||
LOGE(
|
||||
"[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - range "
|
||||
"check.]");
|
||||
@@ -284,16 +285,11 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
if (!RangeCheck(message, message_length, key_array[i].key_id,
|
||||
key_array[i].key_id_length, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_data,
|
||||
key_array[i].key_data_length, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_data_iv,
|
||||
wvoec::KEY_IV_SIZE, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control,
|
||||
wvoec::KEY_CONTROL_SIZE, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control_iv,
|
||||
wvoec::KEY_IV_SIZE, false)) {
|
||||
if (!RangeCheck(message_length, key_array[i].key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i].key_data, false) ||
|
||||
!RangeCheck(message_length, key_array[i].key_data_iv, false) ||
|
||||
!RangeCheck(message_length, key_array[i].key_control, false) ||
|
||||
!RangeCheck(message_length, key_array[i].key_control_iv, false)) {
|
||||
LOGE(
|
||||
"[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT -range "
|
||||
"check %d]",
|
||||
@@ -302,14 +298,14 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
}
|
||||
}
|
||||
return session_ctx->LoadKeys(message, message_length, signature,
|
||||
signature_length, enc_mac_key_iv, enc_mac_keys,
|
||||
num_keys, key_array, pst, pst_length,
|
||||
srm_requirement, license_type);
|
||||
signature_length, enc_mac_keys_iv, enc_mac_keys,
|
||||
num_keys, key_array, pst, srm_restriction_data,
|
||||
license_type);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
OEMCrypto_SESSION session, size_t num_keys,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
|
||||
size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
if (num_keys == 0) {
|
||||
LOGE("[OEMCrypto_LoadEntitledContentKeys(): key_array is empty.");
|
||||
return OEMCrypto_SUCCESS;
|
||||
@@ -327,8 +323,22 @@ extern "C" OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
|
||||
LOGE("[OEMCrypto_LoadEntitledContentKeys(): ERROR_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
if (!RangeCheck(message_length, key_array[i].entitlement_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_id, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_data_iv, false) ||
|
||||
!RangeCheck(message_length, key_array[i].content_key_data, false)) {
|
||||
LOGE(
|
||||
"[OEMCrypto_LoadEntitledContentKeys(): "
|
||||
"OEMCrypto_ERROR_INVALID_CONTEXT -range "
|
||||
"check %d]",
|
||||
i);
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
return session_ctx->LoadEntitledContentKeys(num_keys, key_array);
|
||||
return session_ctx->LoadEntitledContentKeys(message, message_length, num_keys,
|
||||
key_array);
|
||||
}
|
||||
|
||||
extern "C" OEMCryptoResult OEMCrypto_RefreshKeys(
|
||||
@@ -359,12 +369,9 @@ extern "C" OEMCryptoResult OEMCrypto_RefreshKeys(
|
||||
|
||||
// Range check
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
if (!RangeCheck(message, message_length, key_array[i].key_id,
|
||||
key_array[i].key_id_length, true) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control,
|
||||
wvoec::KEY_CONTROL_SIZE, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control_iv,
|
||||
wvoec::KEY_IV_SIZE, true)) {
|
||||
if (!RangeCheck(message_length, key_array[i].key_id, true) ||
|
||||
!RangeCheck(message_length, key_array[i].key_control, false) ||
|
||||
!RangeCheck(message_length, key_array[i].key_control_iv, true)) {
|
||||
LOGE("[OEMCrypto_RefreshKeys(): Range Check %d]", i);
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
@@ -383,24 +390,28 @@ extern "C" OEMCryptoResult OEMCrypto_RefreshKeys(
|
||||
std::vector<uint8_t> key_control;
|
||||
std::vector<uint8_t> key_control_iv;
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
if (key_array[i].key_id != NULL) {
|
||||
key_id.assign(key_array[i].key_id,
|
||||
key_array[i].key_id + key_array[i].key_id_length);
|
||||
key_control.assign(key_array[i].key_control,
|
||||
key_array[i].key_control + wvoec::KEY_CONTROL_SIZE);
|
||||
if (key_array[i].key_control_iv == NULL) {
|
||||
if (key_array[i].key_id.length != 0) {
|
||||
key_id.assign(
|
||||
message + key_array[i].key_id.offset,
|
||||
message + key_array[i].key_id.offset + key_array[i].key_id.length);
|
||||
key_control.assign(
|
||||
message + key_array[i].key_control.offset,
|
||||
message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE);
|
||||
if (key_array[i].key_control_iv.length == 0) {
|
||||
key_control_iv.clear();
|
||||
} else {
|
||||
key_control_iv.assign(key_array[i].key_control_iv,
|
||||
key_array[i].key_control_iv + wvoec::KEY_IV_SIZE);
|
||||
key_control_iv.assign(
|
||||
message + key_array[i].key_control_iv.offset,
|
||||
message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE);
|
||||
}
|
||||
} else {
|
||||
// key_id could be null if special control key type
|
||||
// key_control is not encrypted in this case
|
||||
key_id.clear();
|
||||
key_control_iv.clear();
|
||||
key_control.assign(key_array[i].key_control,
|
||||
key_array[i].key_control + wvoec::KEY_CONTROL_SIZE);
|
||||
key_control.assign(
|
||||
message + key_array[i].key_control.offset,
|
||||
message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE);
|
||||
}
|
||||
|
||||
status = session_ctx->RefreshKey(key_id, key_control, key_control_iv);
|
||||
|
||||
@@ -492,10 +492,11 @@ uint32_t SessionContext::CurrentTimer() {
|
||||
|
||||
OEMCryptoResult SessionContext::LoadKeys(
|
||||
const uint8_t* message, size_t message_length, const uint8_t* signature,
|
||||
size_t signature_length, const uint8_t* enc_mac_key_iv,
|
||||
const uint8_t* enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array, const uint8_t* pst, size_t pst_length,
|
||||
const uint8_t* srm_requirement, OEMCrypto_LicenseType license_type) {
|
||||
size_t signature_length, OEMCrypto_Substring enc_mac_keys_iv,
|
||||
OEMCrypto_Substring enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst,
|
||||
OEMCrypto_Substring srm_restriction_data,
|
||||
OEMCrypto_LicenseType license_type) {
|
||||
// Validate message signature
|
||||
if (!ValidateMessage(message, message_length, signature, signature_length)) {
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
@@ -522,16 +523,16 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
|
||||
StartTimer();
|
||||
|
||||
if (srm_requirement) {
|
||||
if (srm_restriction_data.length != 0) {
|
||||
const std::string kSRMVerificationString = "HDCPDATA";
|
||||
if (memcmp(srm_requirement, kSRMVerificationString.c_str(),
|
||||
kSRMVerificationString.size())) {
|
||||
if (memcmp(message + srm_restriction_data.offset,
|
||||
kSRMVerificationString.c_str(), kSRMVerificationString.size())) {
|
||||
LOGE("SRM Requirement Data has bad verification string: %8s",
|
||||
srm_requirement);
|
||||
message + srm_restriction_data.offset);
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
uint32_t minimum_version =
|
||||
htonl(*reinterpret_cast<const uint32_t*>(srm_requirement + 8));
|
||||
uint32_t minimum_version = htonl(*reinterpret_cast<const uint32_t*>(
|
||||
message + srm_restriction_data.offset + 8));
|
||||
uint16_t current_version = 0;
|
||||
if (OEMCrypto_SUCCESS != ce_->current_srm_version(¤t_version)) {
|
||||
LOGW("[LoadKeys: SRM Version not available.");
|
||||
@@ -563,20 +564,25 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
std::vector<uint8_t> key_control;
|
||||
std::vector<uint8_t> key_control_iv;
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
key_id.assign(key_array[i].key_id,
|
||||
key_array[i].key_id + key_array[i].key_id_length);
|
||||
enc_key_data.assign(key_array[i].key_data,
|
||||
key_array[i].key_data + key_array[i].key_data_length);
|
||||
key_data_iv.assign(key_array[i].key_data_iv,
|
||||
key_array[i].key_data_iv + wvoec::KEY_IV_SIZE);
|
||||
if (key_array[i].key_control == NULL) {
|
||||
key_id.assign(
|
||||
message + key_array[i].key_id.offset,
|
||||
message + key_array[i].key_id.offset + key_array[i].key_id.length);
|
||||
enc_key_data.assign(
|
||||
message + key_array[i].key_data.offset,
|
||||
message + key_array[i].key_data.offset + key_array[i].key_data.length);
|
||||
key_data_iv.assign(
|
||||
message + key_array[i].key_data_iv.offset,
|
||||
message + key_array[i].key_data_iv.offset + wvoec::KEY_IV_SIZE);
|
||||
if (key_array[i].key_control.length == 0) {
|
||||
status = OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
break;
|
||||
}
|
||||
key_control.assign(key_array[i].key_control,
|
||||
key_array[i].key_control + wvoec::KEY_CONTROL_SIZE);
|
||||
key_control_iv.assign(key_array[i].key_control_iv,
|
||||
key_array[i].key_control_iv + wvoec::KEY_IV_SIZE);
|
||||
key_control.assign(
|
||||
message + key_array[i].key_control.offset,
|
||||
message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE);
|
||||
key_control_iv.assign(
|
||||
message + key_array[i].key_control_iv.offset,
|
||||
message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE);
|
||||
|
||||
OEMCryptoResult result =
|
||||
InstallKey(key_id, enc_key_data, key_data_iv, key_control,
|
||||
@@ -590,12 +596,14 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
if (status != OEMCrypto_SUCCESS) return status;
|
||||
|
||||
// enc_mac_key can be NULL if license renewal is not supported
|
||||
if (enc_mac_keys != NULL) {
|
||||
if (enc_mac_keys.length != 0) {
|
||||
// V2.1 license protocol: update mac keys after processing license response
|
||||
const std::vector<uint8_t> enc_mac_keys_str = std::vector<uint8_t>(
|
||||
enc_mac_keys, enc_mac_keys + 2 * wvoec::MAC_KEY_SIZE);
|
||||
message + enc_mac_keys.offset,
|
||||
message + enc_mac_keys.offset + 2 * wvoec::MAC_KEY_SIZE);
|
||||
const std::vector<uint8_t> enc_mac_key_iv_str = std::vector<uint8_t>(
|
||||
enc_mac_key_iv, enc_mac_key_iv + wvoec::KEY_IV_SIZE);
|
||||
message + enc_mac_keys_iv.offset,
|
||||
message + enc_mac_keys_iv.offset + wvoec::KEY_IV_SIZE);
|
||||
|
||||
if (!UpdateMacKeys(enc_mac_keys_str, enc_mac_key_iv_str)) {
|
||||
LOGE("Failed to update mac keys.\n");
|
||||
@@ -606,13 +614,13 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
OEMCryptoResult result = OEMCrypto_SUCCESS;
|
||||
switch (usage_entry_status_) {
|
||||
case kNoUsageEntry:
|
||||
if (pst_length > 0) {
|
||||
if (pst.length > 0) {
|
||||
LOGE("LoadKeys: PST specified but no usage entry loaded.");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
break; // no extra check.
|
||||
case kUsageEntryNew:
|
||||
result = usage_entry_->SetPST(pst, pst_length);
|
||||
result = usage_entry_->SetPST(message + pst.offset, pst.length);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
@@ -622,7 +630,7 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
}
|
||||
break;
|
||||
case kUsageEntryLoaded:
|
||||
if (!usage_entry_->VerifyPST(pst, pst_length)) {
|
||||
if (!usage_entry_->VerifyPST(message + pst.offset, pst.length)) {
|
||||
return OEMCrypto_ERROR_WRONG_PST;
|
||||
}
|
||||
if (!usage_entry_->VerifyMacKeys(mac_key_server_, mac_key_client_)) {
|
||||
@@ -637,7 +645,8 @@ OEMCryptoResult SessionContext::LoadKeys(
|
||||
}
|
||||
|
||||
OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
||||
size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
const uint8_t* message, size_t message_length, size_t num_keys,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array) {
|
||||
if (!key_array) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
@@ -647,9 +656,9 @@ OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
const OEMCrypto_EntitledContentKeyObject* key_data = &key_array[i];
|
||||
std::vector<uint8_t> entitlement_key_id;
|
||||
entitlement_key_id.assign(
|
||||
key_data->entitlement_key_id,
|
||||
key_data->entitlement_key_id + key_data->entitlement_key_id_length);
|
||||
entitlement_key_id.assign(message + key_data->entitlement_key_id.offset,
|
||||
message + key_data->entitlement_key_id.offset +
|
||||
key_data->entitlement_key_id.length);
|
||||
|
||||
const std::vector<uint8_t>* entitlement_key = NULL;
|
||||
if (!session_keys_->GetEntitlementKey(entitlement_key_id,
|
||||
@@ -661,14 +670,14 @@ OEMCryptoResult SessionContext::LoadEntitledContentKeys(
|
||||
std::vector<uint8_t> encrypted_content_key;
|
||||
std::vector<uint8_t> content_key_id;
|
||||
|
||||
iv.assign(key_data->content_key_data_iv,
|
||||
key_data->content_key_data_iv + 16);
|
||||
encrypted_content_key.assign(
|
||||
key_data->content_key_data,
|
||||
key_data->content_key_data + key_data->content_key_data_length);
|
||||
content_key_id.assign(
|
||||
key_data->content_key_id,
|
||||
key_data->content_key_id + key_data->content_key_id_length);
|
||||
iv.assign(message + key_data->content_key_data_iv.offset,
|
||||
message + key_data->content_key_data_iv.offset + 16);
|
||||
encrypted_content_key.assign(message + key_data->content_key_data.offset,
|
||||
message + key_data->content_key_data.offset +
|
||||
key_data->content_key_data.length);
|
||||
content_key_id.assign(message + key_data->content_key_id.offset,
|
||||
message + key_data->content_key_id.offset +
|
||||
key_data->content_key_id.length);
|
||||
if (!DecryptMessage(*entitlement_key, iv, encrypted_content_key,
|
||||
&content_key, 256 /* key size */)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
|
||||
@@ -119,13 +119,14 @@ class SessionContext {
|
||||
uint32_t CurrentTimer(); // (seconds).
|
||||
virtual OEMCryptoResult LoadKeys(
|
||||
const uint8_t* message, size_t message_length, const uint8_t* signature,
|
||||
size_t signature_length, const uint8_t* enc_mac_key_iv,
|
||||
const uint8_t* enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array, const uint8_t* pst,
|
||||
size_t pst_length, const uint8_t* srm_requirement,
|
||||
size_t signature_length, OEMCrypto_Substring enc_mac_keys_iv,
|
||||
OEMCrypto_Substring enc_mac_keys, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst,
|
||||
OEMCrypto_Substring srm_restriction_data,
|
||||
OEMCrypto_LicenseType license_type);
|
||||
OEMCryptoResult LoadEntitledContentKeys(
|
||||
size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
const uint8_t* message, size_t message_length, size_t num_keys,
|
||||
const OEMCrypto_EntitledContentKeyObject* key_array);
|
||||
virtual OEMCryptoResult InstallKey(const KeyId& key_id,
|
||||
const std::vector<uint8_t>& key_data,
|
||||
const std::vector<uint8_t>& key_data_iv,
|
||||
|
||||
@@ -69,7 +69,7 @@ UsageTableEntry::~UsageTableEntry() { usage_table_->ReleaseEntry(data_.index); }
|
||||
OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) {
|
||||
if (pst_length > kMaxPSTLength) return OEMCrypto_ERROR_BUFFER_TOO_LARGE;
|
||||
data_.pst_length = pst_length;
|
||||
if (!pst) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
if (!pst || !pst_length) return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
memcpy(data_.pst, pst, pst_length);
|
||||
data_.time_of_license_received = time(NULL);
|
||||
return OEMCrypto_SUCCESS;
|
||||
@@ -78,7 +78,7 @@ OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) {
|
||||
bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) {
|
||||
if (pst_length > kMaxPSTLength) return false;
|
||||
if (data_.pst_length != pst_length) return false;
|
||||
if (!pst) return false;
|
||||
if (!pst || !pst_length) return false;
|
||||
return 0 == memcmp(pst, data_.pst, pst_length);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user