odk: core serialization structs & functions

odk directory copied from wvgerrit.
branch oemcrypto-v16
commit 0c9a7dc

Bug: 140758896
Test: odk_test
Change-Id: I0c631f771b794468a63e4395f6b9c3b60a1dfd4f
This commit is contained in:
Robert Shih
2019-09-12 23:31:31 -07:00
parent 9ea47dc64a
commit 2443fe807a
22 changed files with 4642 additions and 0 deletions

View File

@@ -0,0 +1,223 @@
/*
* Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
* source code may only be used and distributed under the Widevine Master
* License Agreement.
*/
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <memory>
#include <string>
#include "OEMCryptoCENCCommon.h"
#include "odk.h"
#include "odk_serialize.h"
#include "oec_util.h"
using namespace std;
using namespace oec_util;
typedef std::function<size_t(const uint8_t*, uint8_t*, size_t)> roundtrip_fun;
// @ kdo deserialize; odk derialize
static OEMCryptoResult odk_fun_LicenseRequest(
uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce,
uint32_t session_id, const ODK_LicenseRequest& /*core_license_request*/) {
return ODK_PrepareCoreLicenseRequest(out, SIZE_MAX, size, api_version, nonce,
session_id);
}
static OEMCryptoResult odk_fun_RenewalRequest(
uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce,
uint32_t session_id, const ODK_RenewalRequest& core_renewal) {
// todo: fuzz ODK_ClockValues
ODK_ClockValues clock = {};
uint64_t system_time_seconds = core_renewal.playback_time;
return ODK_PrepareCoreRenewalRequest(out, SIZE_MAX, size, api_version, nonce,
session_id, &clock, system_time_seconds);
}
static OEMCryptoResult odk_fun_ProvisioningRequest(
uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce,
uint32_t session_id, const ODK_ProvisioningRequest& core_provisioning) {
const string& device_id = core_provisioning.device_id;
return ODK_PrepareCoreProvisioningRequest(
out, SIZE_MAX, size, api_version, nonce, session_id,
reinterpret_cast<const uint8_t*>(device_id.data()), device_id.size());
}
template <typename T, typename F, typename G>
static roundtrip_fun kdo_odk(const F& kdo_fun, const G& odk_fun) {
auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size) -> size_t {
string input(reinterpret_cast<const char*>(in), size);
T t = {};
if (!kdo_fun(input, &t)) {
return 0;
}
OEMCryptoResult err =
odk_fun(out, &size, t.api_version, t.nonce, t.session_id, t);
return OEMCrypto_SUCCESS == err ? size : 0;
};
return roundtrip;
}
// @ odk deserialize; kdo serialize
namespace {
struct ODK_Common_Args {
uint32_t api_version;
uint32_t nonce;
uint32_t session_id;
};
struct ODK_ParseLicense_Args {
ODK_Common_Args common;
uint8_t initial_license_load;
uint8_t usage_entry_present;
};
struct ODK_ParseRenewal_Args {
ODK_Common_Args common;
uint64_t system_time;
ODK_TimerLimits timer_limits;
ODK_ClockValues clock_values;
};
struct ODK_ParseProvisioning_Args {
ODK_Common_Args common;
size_t device_id_length;
uint8_t device_id[64];
};
} // namespace
static OEMCryptoResult odk_fun_LicenseResponse(
const uint8_t* message, size_t message_length, uint32_t api_version,
uint32_t nonce, uint32_t session_id, const ODK_ParseLicense_Args* a,
ODK_ParsedLicense& parsed_lic) {
return ODK_ParseLicense(message, message_length, api_version, nonce,
session_id, bool(a->initial_license_load),
bool(a->usage_entry_present), &parsed_lic);
}
static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args,
const ODK_ParsedLicense& parsed_lic,
string* oemcrypto_core_message) {
const auto& common = args->common;
ODK_LicenseRequest core_request{common.api_version, common.nonce,
common.session_id};
return CreateCoreLicenseResponse(parsed_lic, core_request,
oemcrypto_core_message);
}
static OEMCryptoResult odk_fun_RenewalResponse(
const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce,
uint32_t session_id, ODK_ParseRenewal_Args* a,
ODK_RenewalMessage& renewal_msg) {
uint64_t timer_value = 0;
OEMCryptoResult err =
ODK_ParseRenewal(buf, len, api_version, nonce, session_id, a->system_time,
&a->timer_limits, &a->clock_values, &timer_value);
if (OEMCrypto_SUCCESS == err) {
Message* msg = nullptr;
AllocateMessage(&msg, message_block);
InitMessage(msg, const_cast<uint8_t*>(buf), len);
SetSize(msg, len);
Unpack_ODK_RenewalMessage(msg, &renewal_msg);
assert(ValidMessage(msg));
}
return err;
}
static bool kdo_fun_RenewalResponse(const ODK_ParseRenewal_Args* args,
const ODK_RenewalMessage& renewal_msg,
string* oemcrypto_core_message) {
const auto& common = args->common;
ODK_RenewalRequest core_request{common.api_version, common.nonce,
common.session_id, renewal_msg.playback_time};
return CreateCoreRenewalResponse(core_request, oemcrypto_core_message);
}
static OEMCryptoResult odk_fun_ProvisioningResponse(
const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce,
uint32_t session_id, ODK_ParseProvisioning_Args* a,
ODK_ParsedProvisioning& parsed_prov) {
return ODK_ParseProvisioning(buf, len, api_version, nonce, session_id,
a->device_id, a->device_id_length, &parsed_prov);
}
static bool kdo_fun_ProvisioningResponse(
const ODK_ParseProvisioning_Args* args,
const ODK_ParsedProvisioning& parsed_prov, string* oemcrypto_core_message) {
const auto& common = args->common;
assert(args->device_id_length <= sizeof(args->device_id));
ODK_ProvisioningRequest core_request{
common.api_version, common.nonce, common.session_id,
string(reinterpret_cast<const char*>(args->device_id),
args->device_id_length)};
return CreateCoreProvisioningResponse(parsed_prov, core_request,
oemcrypto_core_message);
}
template <typename A, typename T, typename F, typename G>
static roundtrip_fun odk_kdo(const F& odk_fun, const G& kdo_fun) {
auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size) -> size_t {
if (sizeof(A) > size) {
return 0;
}
T t = {};
const uint8_t* buf = in + sizeof(A);
size_t len = size - sizeof(A);
std::shared_ptr<A> _args(new A());
A* args = _args.get();
memcpy(args, in, sizeof(A));
const auto& common = args->common;
OEMCryptoResult err = odk_fun(buf, len, common.api_version, common.nonce,
common.session_id, args, t);
if (err != OEMCrypto_SUCCESS) {
return 0;
}
string oemcrypto_core_message;
if (!kdo_fun(args, t, &oemcrypto_core_message)) {
return 0;
}
assert(oemcrypto_core_message.size() <= size);
memcpy(out, oemcrypto_core_message.data(), oemcrypto_core_message.size());
return oemcrypto_core_message.size();
};
return roundtrip;
}
// @ fuzz raw -> parsed -> raw
static void verify_roundtrip(const uint8_t* in, size_t size,
roundtrip_fun roundtrip) {
std::vector<uint8_t> _out(size);
auto out = _out.data();
size_t n = roundtrip(in, out, size);
assert(!n || (n <= size && 0 == memcmp(in, out, n)));
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
verify_roundtrip(
data, size,
kdo_odk<ODK_LicenseRequest>(ParseLicenseRequest, odk_fun_LicenseRequest));
verify_roundtrip(
data, size,
kdo_odk<ODK_RenewalRequest>(ParseRenewalRequest, odk_fun_RenewalRequest));
verify_roundtrip(data, size,
kdo_odk<ODK_ProvisioningRequest>(
ParseProvisioningRequest, odk_fun_ProvisioningRequest));
verify_roundtrip(data, size,
odk_kdo<ODK_ParseLicense_Args, ODK_ParsedLicense>(
odk_fun_LicenseResponse, kdo_fun_LicenseResponse));
verify_roundtrip(data, size,
odk_kdo<ODK_ParseRenewal_Args, ODK_RenewalMessage>(
odk_fun_RenewalResponse, kdo_fun_RenewalResponse));
verify_roundtrip(
data, size,
odk_kdo<ODK_ParseProvisioning_Args, ODK_ParsedProvisioning>(
odk_fun_ProvisioningResponse, kdo_fun_ProvisioningResponse));
return 0;
}

View File

@@ -0,0 +1,684 @@
/*
* Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
* source code may only be used and distributed under the Widevine Master
* License Agreement.
*/
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fstream>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
#include <endian.h>
#include <gtest/gtest.h>
#include "odk.h"
#include "odk_test.h"
#include "oec_util.h"
using namespace oec_util;
size_t ODK_FieldLength(ODK_FieldType type) {
switch (type) {
case ODK_UINT32:
return sizeof(uint32_t);
case ODK_UINT64:
return sizeof(uint64_t);
case ODK_SUBSTRING:
return sizeof(uint32_t) + sizeof(uint32_t);
case ODK_DEVICEID:
return ODK_DEVICE_ID_LEN_MAX;
case ODK_HASH:
return ODK_SHA256_HASH_SIZE;
default:
return SIZE_MAX;
}
}
size_t ODK_AllocSize(ODK_FieldType type) {
if (type == ODK_SUBSTRING) {
return sizeof(OEMCrypto_Substring);
}
return ODK_FieldLength(type);
}
OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf,
const ODK_Field* const field) {
if (!buf || !field || !field->value) {
return ODK_ERROR_CORE_MESSAGE;
}
switch (field->type) {
case ODK_UINT32: {
uint32_t u32 = htobe32(*static_cast<uint32_t*>(field->value));
memcpy(buf, &u32, sizeof(u32));
break;
}
case ODK_UINT64: {
uint64_t u64 = htobe64(*static_cast<uint64_t*>(field->value));
memcpy(buf, &u64, sizeof(u64));
break;
}
case ODK_SUBSTRING: {
OEMCrypto_Substring* s = static_cast<OEMCrypto_Substring*>(field->value);
uint32_t off = htobe32(s->offset);
uint32_t len = htobe32(s->length);
memcpy(buf, &off, sizeof(off));
memcpy(buf + sizeof(off), &len, sizeof(len));
break;
}
case ODK_DEVICEID:
case ODK_HASH: {
const size_t field_len = ODK_FieldLength(field->type);
const uint8_t* const id = static_cast<uint8_t*>(field->value);
memcpy(buf, id, field_len);
break;
}
default:
return ODK_ERROR_CORE_MESSAGE;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf,
const ODK_Field* const field) {
if (!field || !field->value) {
return ODK_ERROR_CORE_MESSAGE;
}
switch (field->type) {
case ODK_UINT32: {
memcpy(field->value, buf, sizeof(uint32_t));
uint32_t* u32p = static_cast<uint32_t*>(field->value);
*u32p = be32toh(*u32p);
break;
}
case ODK_UINT64: {
memcpy(field->value, buf, sizeof(uint64_t));
uint64_t* u64p = static_cast<uint64_t*>(field->value);
*u64p = be64toh(*u64p);
break;
}
case ODK_SUBSTRING: {
OEMCrypto_Substring* s = static_cast<OEMCrypto_Substring*>(field->value);
uint32_t off = 0;
uint32_t len = 0;
memcpy(&off, buf, sizeof(off));
memcpy(&len, buf + sizeof(off), sizeof(len));
s->offset = be32toh(off);
s->length = be32toh(len);
break;
}
case ODK_DEVICEID:
case ODK_HASH: {
const size_t field_len = ODK_FieldLength(field->type);
uint8_t* const id = static_cast<uint8_t*>(field->value);
memcpy(id, buf, field_len);
break;
}
default:
return ODK_ERROR_CORE_MESSAGE;
}
return OEMCrypto_SUCCESS;
}
/*
* Parameters:
* [in] size_in: buffer size
* [out] size_out: bytes processed
*/
OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf,
const size_t size_in, size_t* size_out,
std::vector<ODK_Field>& fields) {
if (!buf || !size_out) {
return ODK_ERROR_CORE_MESSAGE;
}
size_t off = 0, off2 = 0;
for (size_t i = 0; i < fields.size(); i++) {
if (__builtin_add_overflow(off, ODK_FieldLength(fields[i].type), &off2) ||
off2 > size_in) {
return ODK_ERROR_CORE_MESSAGE;
}
uintptr_t base = reinterpret_cast<uintptr_t>(buf);
if (__builtin_add_overflow(base, off, &base)) {
return ODK_ERROR_CORE_MESSAGE;
}
uint8_t* const buf_off = buf + off;
if (mode == ODK_WRITE) {
ODK_WriteSingleField(buf_off, &fields[i]);
} else if (mode == ODK_READ) {
ODK_ReadSingleField(buf_off, &fields[i]);
} else {
return ODK_ERROR_CORE_MESSAGE;
}
off = off2;
}
*size_out = off;
if (*size_out > size_in) {
return ODK_ERROR_CORE_MESSAGE;
}
return OEMCrypto_SUCCESS;
}
OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in,
size_t* size_out,
std::vector<ODK_Field>& fields) {
return ODK_IterFields(ODK_READ, const_cast<uint8_t*>(buf), size_in, size_out,
fields);
}
OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in,
size_t* size_out,
std::vector<ODK_Field>& fields) {
return ODK_IterFields(ODK_WRITE, buf, size_in, size_out, fields);
}
void expect_eq_buf(const void* s1, const void* s2, size_t n) {
if (memcmp(s1, s2, n)) {
const void* buffers[] = {s1, s2};
for (int i = 0; i < 2; i++) {
char _tmp[] = "/tmp/fileXXXXXX";
mkstemp(_tmp);
std::string tmp(_tmp);
std::fstream out(tmp, std::ios::out | std::ios::binary);
out.write((char*)buffers[i], n);
out.close();
std:
std::cerr << "buffer " << i << " dumped to " << tmp << std::endl;
}
FAIL();
}
}
template <typename T, typename F, typename G>
void ValidateRequest(uint32_t message_type,
std::vector<ODK_Field>& extra_fields,
const F& odk_prepare_func, const G& kdo_parse_func) {
uint32_t message_size = 0;
uint32_t api_version = 16;
uint32_t nonce = 0xdeadbeef;
uint32_t session_id = 0xcafebabe;
ODK_NonceValues nonce_values{api_version, nonce, session_id};
std::vector<ODK_Field> total_fields = {
{ODK_UINT32, &message_type}, {ODK_UINT32, &message_size},
{ODK_UINT32, &api_version}, {ODK_UINT32, &nonce},
{ODK_UINT32, &session_id},
};
total_fields.insert(total_fields.end(), extra_fields.begin(),
extra_fields.end());
for (auto& field : total_fields) {
message_size += ODK_FieldLength(field.type);
}
uint8_t* buf = new uint8_t[message_size]();
uint8_t* buf2 = new uint8_t[message_size]();
size_t bytes_written = message_size;
EXPECT_EQ(OEMCrypto_SUCCESS,
odk_prepare_func(buf, &bytes_written, &nonce_values));
EXPECT_EQ(bytes_written, message_size);
EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX,
&bytes_written, total_fields));
EXPECT_EQ(bytes_written, message_size);
expect_eq_buf(buf, buf2, message_size);
// odk kdo roundtrip
T t = {};
std::string oemcrypto_core_message(reinterpret_cast<char*>(buf),
message_size);
EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t));
nonce_values.api_version = t.api_version;
nonce_values.nonce = t.nonce;
nonce_values.session_id = t.session_id;
EXPECT_EQ(OEMCrypto_SUCCESS,
odk_prepare_func(buf2, &bytes_written, &nonce_values));
EXPECT_EQ(bytes_written, message_size);
expect_eq_buf(buf, buf2, message_size);
delete[] buf;
delete[] buf2;
}
/**
* Template arguments:
* T: kdo input struct
* F: odk deserializer
* G: kdo serializer
*/
template <typename T, typename F, typename G>
void ValidateResponse(uint32_t message_type,
std::vector<ODK_Field>& extra_fields,
const F& odk_parse_func, const G& kdo_prepare_func) {
uint32_t message_size = 0;
uint32_t api_version = 16;
uint32_t nonce = 0xdeadbeef;
uint32_t session_id = 0xcafebabe;
std::vector<ODK_Field> total_fields = {
{ODK_UINT32, &message_type}, {ODK_UINT32, &message_size},
{ODK_UINT32, &api_version}, {ODK_UINT32, &nonce},
{ODK_UINT32, &session_id},
};
uint32_t header_size = 0;
for (auto& field : total_fields) {
header_size += ODK_FieldLength(field.type);
}
total_fields.insert(total_fields.end(), extra_fields.begin(),
extra_fields.end());
for (auto& field : total_fields) {
message_size += ODK_FieldLength(field.type);
}
uint8_t* buf = new uint8_t[message_size]();
uint8_t* zero = new uint8_t[message_size]();
size_t bytes_read = 0, bytes_written = 0;
T t = {};
t.api_version = api_version;
t.nonce = nonce;
t.session_id = session_id;
// serialize input to buf
EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf, SIZE_MAX,
&bytes_written, total_fields));
EXPECT_EQ(bytes_written, message_size);
// zero-out input
EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_READ, zero, bytes_written,
&bytes_read, extra_fields));
EXPECT_TRUE(bytes_written > bytes_read &&
bytes_written - bytes_read == header_size);
// parse buf with odk
ODK_NonceValues nonce_values{api_version, nonce, session_id};
EXPECT_EQ(OEMCrypto_SUCCESS,
odk_parse_func(buf, bytes_written, &nonce_values));
// serialize odk output to oemcrypto_core_message
std::string oemcrypto_core_message;
EXPECT_TRUE(kdo_prepare_func(t, &oemcrypto_core_message));
EXPECT_EQ(bytes_written, message_size);
expect_eq_buf(buf, oemcrypto_core_message.data(), message_size);
delete[] buf;
delete[] zero;
}
TEST(OdkTest, SerializeFields) {
uint32_t x[] = {0, 1, 2};
uint64_t y[] = {3ll << 32, 4ll << 32, 5ll << 32};
OEMCrypto_Substring s = {.offset = 6, .length = 7};
std::vector<ODK_Field> fields = {
{ODK_UINT32, &x[0]}, {ODK_UINT32, &x[1]}, {ODK_UINT32, &x[2]},
{ODK_UINT64, &y[0]}, {ODK_UINT64, &y[1]}, {ODK_UINT64, &y[2]},
{ODK_SUBSTRING, &s},
};
uint8_t buf[1024] = {0};
uint8_t buf2[1024] = {0};
size_t bytes_read = 0, bytes_written = 0;
ODK_IterFields(ODK_WRITE, buf, SIZE_MAX, &bytes_read, fields);
ODK_IterFields(ODK_READ, buf, bytes_read, &bytes_written, fields);
ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, &bytes_read, fields);
expect_eq_buf(buf, buf2, bytes_read);
}
TEST(OdkTest, SerializeFieldsStress) {
const int n = 1024;
std::vector<ODK_Field> fields(n);
std::srand(0);
size_t total_size = 0;
for (int i = 0; i < n; i++) {
fields[i].type = static_cast<ODK_FieldType>(std::rand() %
static_cast<int>(ODK_NUMTYPES));
size_t field_size = ODK_AllocSize(fields[i].type);
fields[i].value = malloc(ODK_AllocSize(fields[i].type));
total_size += ODK_FieldLength(fields[i].type);
}
uint8_t* buf = new uint8_t[total_size];
for (int i = 0; i < total_size; i++) {
buf[i] = std::rand() & 0xff;
}
size_t bytes_read = 0, bytes_written = 0;
uint8_t* buf2 = new uint8_t[total_size];
ODK_IterFields(ODK_READ, buf, total_size, &bytes_read, fields);
EXPECT_EQ(bytes_read, total_size);
ODK_IterFields(ODK_WRITE, buf2, total_size, &bytes_written, fields);
EXPECT_EQ(bytes_written, total_size);
expect_eq_buf(buf, buf2, total_size);
// cleanup
for (int i = 0; i < n; i++) {
free(fields[i].value);
}
delete[] buf;
delete[] buf2;
}
TEST(OdkTest, LicenseRequest) {
std::vector<ODK_Field> empty;
auto odk_prepare_func = [&](uint8_t* const buf, size_t* size,
ODK_NonceValues* nonce_values) {
return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, nonce_values);
};
auto kdo_parse_func = ParseLicenseRequest;
ValidateRequest<ODK_LicenseRequest>(ODK_License_Request_Type, empty,
odk_prepare_func, kdo_parse_func);
}
TEST(OdkTest, RenewalRequest) {
uint64_t system_time_seconds = 0xBADDCAFE000FF1CE;
std::vector<ODK_Field> extra_fields = {
{ODK_UINT64, &system_time_seconds},
};
ODK_ClockValues clock_values = {0};
auto odk_prepare_func = [&](uint8_t* const buf, size_t* size,
const ODK_NonceValues* nonce_values) {
return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values,
&clock_values, system_time_seconds);
};
auto kdo_parse_func = [&](const std::string& oemcrypto_core_message,
ODK_RenewalRequest* core_renewal_request) {
bool ok = ParseRenewalRequest(oemcrypto_core_message, core_renewal_request);
if (ok) {
system_time_seconds = core_renewal_request->playback_time;
}
return ok;
};
ValidateRequest<ODK_RenewalRequest>(ODK_Renewal_Request_Type, extra_fields,
odk_prepare_func, kdo_parse_func);
}
TEST(OdkTest, ProvisionRequest) {
uint32_t device_id_length = DEVICE_ID_MAX / 2;
uint8_t device_id[DEVICE_ID_MAX] = {0};
memset(device_id, 0xff, device_id_length);
std::vector<ODK_Field> extra_fields = {
{ODK_UINT32, &device_id_length},
{ODK_DEVICEID, device_id},
};
auto odk_prepare_func = [&](uint8_t* const buf, size_t* size,
const ODK_NonceValues* nonce_values) {
return ODK_PrepareCoreProvisioningRequest(buf, SIZE_MAX, size, nonce_values,
device_id, device_id_length);
};
auto kdo_parse_func =
[&](const std::string& oemcrypto_core_message,
ODK_ProvisioningRequest* core_provisioning_request) {
bool ok = ParseProvisioningRequest(oemcrypto_core_message,
core_provisioning_request);
if (ok) {
const std::string& device_id_str =
core_provisioning_request->device_id;
device_id_length = device_id_str.size();
memcpy(device_id, device_id_str.data(), device_id_length);
}
return ok;
};
ValidateRequest<ODK_ProvisioningRequest>(ODK_Provisioning_Request_Type,
extra_fields, odk_prepare_func,
kdo_parse_func);
}
TEST(OdkTest, LicenseResponse) {
ODK_ParsedLicense parsed_license = {
.enc_mac_keys_iv = {.offset = 0, .length = 1},
.enc_mac_keys = {.offset = 2, .length = 3},
.pst = {.offset = 4, .length = 5},
.srm_restriction_data = {.offset = 6, .length = 7},
.license_type = 8,
.nonce_required = 0xDEADC0DE,
.timer_limits =
{
.soft_expiry = 9,
.earliest_playback_start_seconds = 10,
.latest_playback_start_seconds = 11,
.initial_playback_duration_seconds = 12,
.renewal_playback_duration_seconds = 13,
.license_duration_seconds = 14,
},
.request_hash = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
.key_array_length = 3,
.key_array =
{
{
.key_id = {.offset = 15, .length = 16},
.key_data_iv = {.offset = 17, .length = 18},
.key_data = {.offset = 19, .length = 20},
.key_control_iv = {.offset = 21, .length = 22},
.key_control = {.offset = 23, .length = 24},
},
{
.key_id = {.offset = 25, .length = 26},
.key_data_iv = {.offset = 27, .length = 28},
.key_data = {.offset = 29, .length = 30},
.key_control_iv = {.offset = 31, .length = 32},
.key_control = {.offset = 33, .length = 34},
},
{
.key_id = {.offset = 35, .length = 36},
.key_data_iv = {.offset = 37, .length = 38},
.key_data = {.offset = 39, .length = 40},
.key_control_iv = {.offset = 41, .length = 42},
.key_control = {.offset = 43, .length = 44},
},
},
};
uint32_t message_type = ODK_License_Response_Type;
std::vector<ODK_Field> extra_fields = {
{ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv},
{ODK_SUBSTRING, &parsed_license.enc_mac_keys},
{ODK_SUBSTRING, &parsed_license.pst},
{ODK_SUBSTRING, &parsed_license.srm_restriction_data},
{ODK_UINT32, &parsed_license.license_type},
{ODK_UINT32, &parsed_license.nonce_required},
{ODK_UINT32, &parsed_license.timer_limits.soft_expiry},
{ODK_UINT64,
&parsed_license.timer_limits.earliest_playback_start_seconds},
{ODK_UINT64, &parsed_license.timer_limits.latest_playback_start_seconds},
{ODK_UINT64,
&parsed_license.timer_limits.initial_playback_duration_seconds},
{ODK_UINT64,
&parsed_license.timer_limits.renewal_playback_duration_seconds},
{ODK_UINT64, &parsed_license.timer_limits.license_duration_seconds},
{ODK_HASH, &parsed_license.request_hash},
{ODK_UINT32, &parsed_license.key_array_length},
{ODK_SUBSTRING, &parsed_license.key_array[0].key_id},
{ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv},
{ODK_SUBSTRING, &parsed_license.key_array[0].key_data},
{ODK_SUBSTRING, &parsed_license.key_array[0].key_control_iv},
{ODK_SUBSTRING, &parsed_license.key_array[0].key_control},
{ODK_SUBSTRING, &parsed_license.key_array[1].key_id},
{ODK_SUBSTRING, &parsed_license.key_array[1].key_data_iv},
{ODK_SUBSTRING, &parsed_license.key_array[1].key_data},
{ODK_SUBSTRING, &parsed_license.key_array[1].key_control_iv},
{ODK_SUBSTRING, &parsed_license.key_array[1].key_control},
{ODK_SUBSTRING, &parsed_license.key_array[2].key_id},
{ODK_SUBSTRING, &parsed_license.key_array[2].key_data_iv},
{ODK_SUBSTRING, &parsed_license.key_array[2].key_data},
{ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv},
{ODK_SUBSTRING, &parsed_license.key_array[2].key_control},
};
uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {};
memcpy(request_hash, parsed_license.request_hash, ODK_SHA256_HASH_SIZE);
auto odk_parse_func = [&](const uint8_t* buf, size_t size,
ODK_NonceValues* nonce_values) {
return ODK_ParseLicense(buf, size + 128, size, 1, 0, request_hash, nullptr,
nullptr, nonce_values, &parsed_license);
};
auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request,
std::string* oemcrypto_core_message) {
return CreateCoreLicenseResponse(parsed_license, core_request,
oemcrypto_core_message);
};
ValidateResponse<ODK_LicenseRequest>(ODK_License_Response_Type, extra_fields,
odk_parse_func, kdo_prepare_func);
}
TEST(OdkTest, RenewalResponse) {
uint64_t system_time = 0xfaceb00c;
uint64_t playback_clock = 11;
uint64_t playback_timer = 12;
uint64_t message_playback_clock = 10;
std::vector<ODK_Field> extra_fields = {
{ODK_UINT64, &message_playback_clock},
};
ODK_TimerLimits timer_limits = {
.soft_expiry = 0,
.earliest_playback_start_seconds = 0,
.latest_playback_start_seconds = 100,
.initial_playback_duration_seconds = 10,
.renewal_playback_duration_seconds = 20,
.license_duration_seconds = 100,
};
ODK_ClockValues clock_values = {
.time_of_license_signed = 0,
.time_of_first_decrypt = system_time - playback_clock,
.time_of_last_decrypt = 0,
.time_when_timer_expires = system_time + playback_timer,
.timer_status = 0,
.status = kUnused,
};
auto odk_parse_func = [&](const uint8_t* buf, size_t size,
ODK_NonceValues* nonce_values) {
OEMCryptoResult err =
ODK_ParseRenewal(buf, size, size, nonce_values, system_time,
&timer_limits, &clock_values, &playback_timer);
EXPECT_EQ(ODK_SET_TIMER, err);
EXPECT_EQ(timer_limits.renewal_playback_duration_seconds, playback_timer);
EXPECT_EQ(clock_values.time_when_timer_expires,
system_time + playback_timer);
// manually restore message_playback_clock since ODK_ParseRenewal doesn't
// generate output
message_playback_clock = 10;
return OEMCrypto_SUCCESS;
};
auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request,
std::string* oemcrypto_core_message) {
core_request.playback_time = message_playback_clock;
return CreateCoreRenewalResponse(core_request, oemcrypto_core_message);
};
ValidateResponse<ODK_RenewalRequest>(ODK_Renewal_Response_Type, extra_fields,
odk_parse_func, kdo_prepare_func);
}
TEST(OdkTest, ProvisionResponse) {
uint32_t device_id_length = DEVICE_ID_MAX / 2;
uint8_t device_id[DEVICE_ID_MAX] = {0};
memset(device_id, 0xff, device_id_length);
ODK_ParsedProvisioning parsed_response = {
.enc_private_key = {.offset = 0, .length = 1},
.enc_private_key_iv = {.offset = 2, .length = 3},
.encrypted_message_key = {.offset = 4, .length = 5},
};
std::vector<ODK_Field> extra_fields = {
{ODK_UINT32, &device_id_length},
{ODK_DEVICEID, device_id},
{ODK_UINT32, &parsed_response.key_type},
{ODK_SUBSTRING, &parsed_response.enc_private_key},
{ODK_SUBSTRING, &parsed_response.enc_private_key_iv},
{ODK_SUBSTRING, &parsed_response.encrypted_message_key},
};
auto odk_parse_func = [&](const uint8_t* buf, size_t size,
ODK_NonceValues* nonce_values) {
// restore device id because it is not part of parsed_response
device_id_length = DEVICE_ID_MAX / 2;
memset(device_id, 0xff, device_id_length);
OEMCryptoResult err =
ODK_ParseProvisioning(buf, size + 16, size, nonce_values, device_id,
device_id_length, &parsed_response);
return err;
};
auto kdo_prepare_func = [&](ODK_ProvisioningRequest& core_request,
std::string* oemcrypto_core_message) {
core_request.device_id.assign(reinterpret_cast<char*>(device_id),
device_id_length);
return CreateCoreProvisioningResponse(parsed_response, core_request,
oemcrypto_core_message);
};
ValidateResponse<ODK_ProvisioningRequest>(ODK_Provisioning_Response_Type,
extra_fields, odk_parse_func,
kdo_prepare_func);
}
TEST(OdkSizeTest, LicenseRequest) {
uint8_t* message = nullptr;
size_t message_length = 0;
size_t core_message_length = 0;
uint32_t api_version = 0;
uint32_t nonce = 0;
uint32_t session_id = 0;
ODK_NonceValues nonce_values{api_version, nonce, session_id};
EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
ODK_PrepareCoreLicenseRequest(message, message_length,
&core_message_length, &nonce_values));
// All messages have at least a five 4-byte fields.
size_t minimum_message_size = 5 * 4;
EXPECT_GE(core_message_length, minimum_message_size);
}
TEST(OdkSizeTest, RenewalRequest) {
uint8_t* message = nullptr;
size_t message_length = 0;
size_t core_message_length = 0;
uint32_t api_version = 0;
uint32_t nonce = 0;
uint32_t session_id = 0;
ODK_ClockValues clock_values = {};
clock_values.time_of_first_decrypt = 10;
uint64_t system_time_seconds = 15;
ODK_NonceValues nonce_values{api_version, nonce, session_id};
EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
ODK_PrepareCoreRenewalRequest(message, message_length,
&core_message_length, &nonce_values,
&clock_values, system_time_seconds));
// All messages have at least a five 4-byte fields.
size_t minimum_message_size = 5 * 4;
EXPECT_GE(core_message_length, minimum_message_size);
}
TEST(OdkSizeTest, ProvisioningRequest) {
uint8_t* message = nullptr;
size_t message_length = 0;
size_t core_message_length = 0;
uint32_t api_version = 0;
uint32_t nonce = 0;
uint32_t session_id = 0;
uint8_t* device_id = nullptr;
uint32_t device_id_length = 0;
ODK_NonceValues nonce_values{api_version, nonce, session_id};
EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
ODK_PrepareCoreProvisioningRequest(
message, message_length, &core_message_length, &nonce_values,
nullptr, device_id_length));
// All messages have at least a five 4-byte fields.
size_t minimum_message_size = 5 * 4;
EXPECT_GE(core_message_length, minimum_message_size);
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
* source code may only be used and distributed under the Widevine Master
* License Agreement.
*/
#ifndef ODK_TEST_H_
#define ODK_TEST_H_
#include "OEMCryptoCENCCommon.h"
typedef enum {
ODK_License_Request_Type = 1,
ODK_License_Response_Type = 2,
ODK_Renewal_Request_Type = 3,
ODK_Renewal_Response_Type = 4,
ODK_Provisioning_Request_Type = 5,
ODK_Provisioning_Response_Type = 6,
} ODK_MessageType;
typedef enum {
ODK_UINT32,
ODK_UINT64,
ODK_SUBSTRING,
ODK_DEVICEID,
ODK_HASH,
ODK_NUMTYPES,
} ODK_FieldType;
typedef enum {
ODK_READ,
ODK_WRITE,
} ODK_FieldMode;
typedef struct {
ODK_FieldType type;
void* value;
} ODK_Field;
#define DEVICE_ID_MAX (64)
#ifdef __cplusplus
extern "C" {
#endif
size_t ODK_FieldLength(ODK_FieldType type);
OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf,
const ODK_Field* const field);
OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf,
const ODK_Field* const field);
OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in,
size_t* size_out, const size_t n,
const ODK_Field* const fields);
OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in,
size_t* size_out, const size_t n,
const ODK_Field* const fields);
#ifdef __cplusplus
}
#endif
#endif // ODK_TEST_H_

View File

@@ -0,0 +1,936 @@
/*
* Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
* source code may only be used and distributed under the Widevine Master
* License Agreement.
*/
#include <gtest/gtest.h>
#include "odk.h"
using ::testing::tuple;
using ::testing::Values;
using ::testing::WithParamInterface;
namespace {
constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff.
} // namespace
namespace odk_test {
struct ServerExpiry {
bool soft_rental;
bool soft_playback;
};
TEST(OdkTimerBasicTest, NullTest) {
// Assert that nullptr does not cause a core dump.
ODK_InitializeClockValues(nullptr, 0u);
ODK_ReloadClockValues(nullptr, 0u, 0u, 0u, kActive, 0u);
ODK_AttemptFirstPlayback(0u, nullptr, nullptr, nullptr);
ODK_UpdateLastPlaybackTime(0, nullptr, nullptr);
ASSERT_TRUE(true);
}
TEST(OdkTimerBasicTest, Init) {
// Verify that basic initialization sets all of the fields.
ODK_ClockValues clock_values;
uint64_t time = 42;
ODK_InitializeClockValues(&clock_values, time);
EXPECT_EQ(clock_values.time_of_license_signed, time);
EXPECT_EQ(clock_values.time_of_first_decrypt, 0);
EXPECT_EQ(clock_values.time_of_last_decrypt, 0);
EXPECT_EQ(clock_values.time_when_timer_expires, 0);
EXPECT_EQ(clock_values.timer_status, 0);
EXPECT_EQ(clock_values.status, kUnused);
}
TEST(OdkTimerBasicTest, Reload) {
// Verify that reloading clock values uses the same values
// for fields that can be saved, and sets others to 0.
ODK_ClockValues clock_values;
uint64_t time = 42u;
uint64_t lic_signed = 1u;
uint64_t first_decrypt = 2u;
uint64_t last_decrypt = 3u;
enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed;
ODK_ReloadClockValues(&clock_values, lic_signed, first_decrypt, last_decrypt,
status, time);
EXPECT_EQ(clock_values.time_of_license_signed, lic_signed);
EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt);
EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt);
EXPECT_EQ(clock_values.time_when_timer_expires, 0u);
EXPECT_EQ(clock_values.timer_status, 0u);
EXPECT_EQ(clock_values.status, status);
}
// ************************************************************************
// Test that we can only start playback within the rental window. This
// simulates requesting a license at rental_clock_start_, and a rental window of
// 50-150s relative to license request, or 100-200s absolute.
class OdkTimerRentalWindow : public ::testing::Test {
public:
OdkTimerRentalWindow() {
rental_clock_start_ = 10000u;
rental_window_start_ = 50u;
rental_window_duration_ = 100u;
}
protected:
void SetUp() override {
::testing::Test::SetUp();
// Start rental clocks at rental_clock_start_.
ODK_InitializeClockValues(&clock_values_, rental_clock_start_);
// Simple license values:
timer_limits_.soft_expiry = false;
timer_limits_.earliest_playback_start_seconds = rental_window_start_;
timer_limits_.latest_playback_start_seconds =
rental_window_start_ + rental_window_duration_;
timer_limits_.initial_playback_duration_seconds = 0;
timer_limits_.renewal_playback_duration_seconds = 0;
timer_limits_.license_duration_seconds = 0;
}
// Simulate reloading a license in a new session. An offline license should
// have several of the clock_value's fields saved into the usage table. When
// it is reloaded those values should be reloaded. From these fields, the
// ODK function can tell if this is the first playback for the license, or
// just the first playback for this session. The key fields that should be
// saved are the status, and the times for license signed, and first and
// last playback times.
void ReloadClock(uint64_t system_time) {
ODK_ClockValues old_clock_values = clock_values_;
// First clear out the old clock values.
ODK_InitializeClockValues(&clock_values_, 0u);
ODK_ReloadClockValues(&clock_values_,
old_clock_values.time_of_license_signed,
old_clock_values.time_of_first_decrypt,
old_clock_values.time_of_last_decrypt,
old_clock_values.status, system_time);
}
uint64_t system_time(uint64_t rental_clock) {
return rental_clock_start_ + rental_clock;
}
ODK_TimerLimits timer_limits_;
ODK_ClockValues clock_values_;
// The rental clock starts when the request is signed.
uint64_t rental_clock_start_;
// start of rental window in seconds since rental clock start.
uint64_t rental_window_start_;
// The "width" of window.
uint64_t rental_window_duration_;
};
TEST_F(OdkTimerRentalWindow, EarlyTest) {
uint64_t timer_value = 0;
// An attempt to start playback before the timer starts is an error.
// We use the TIMER_EXPIRED error to mean both early or late.
const uint64_t bad_start_time =
system_time(timer_limits_.earliest_playback_start_seconds - 10);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_,
&clock_values_, &timer_value));
const uint64_t good_start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(good_start_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0, clock_values_.time_when_timer_expires);
}
TEST_F(OdkTimerRentalWindow, NullTimer) {
// If OEMCrypto passes in a nullpointer, then the ODK should allow this.
uint64_t* timer_value_pointer = nullptr;
const uint64_t bad_start_time =
system_time(timer_limits_.earliest_playback_start_seconds - 10);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_,
&clock_values_, timer_value_pointer));
const uint64_t good_start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(good_start_time, &timer_limits_,
&clock_values_, timer_value_pointer));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0, clock_values_.time_when_timer_expires);
}
// Verify that an attempt to start playback outside the rental window fails.
TEST_F(OdkTimerRentalWindow, LateTest) {
uint64_t timer_value = 0;
// An attempt to start playback before the timer starts is an error.
// We use the TIMER_EXPIRED error to mean both early or late.
const uint64_t start_time = system_time(201);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kUnused, clock_values_.status);
}
// ************************************************************************
// The Hard Rental use case is a strict time limit on playback.
class OdkTimerHardTest : public OdkTimerRentalWindow {
protected:
void SetUp() override {
OdkTimerRentalWindow::SetUp();
timer_limits_.soft_expiry = false; // Hard expiry.
// License duration is 200. The license starts when it was signed at 50,
// so the license is valid from 50-250. The rental window is 100-200 -- as
// inherited from the ODKRentalWindow class.
timer_limits_.license_duration_seconds = 200u;
}
};
TEST_F(OdkTimerHardTest, EarlyTest) {
uint64_t timer_value = 0;
// An attempt to start playback before the timer starts is an error.
// We use the TIMER_EXPIRED error to mean both early or late.
const uint64_t bad_start_time =
system_time(timer_limits_.earliest_playback_start_seconds - 10);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(kUnused, clock_values_.status);
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = false, we should set a timer.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
}
TEST_F(OdkTimerHardTest, NormalTest) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = false, we should set a timer.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 1;
// This play time is outside the rental window. We are allowed to continue
// playback after the rental window expires as long as the first decrypt is
// within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time);
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
// Try to play after the cutoff time is not valid.
const uint64_t late_play_time = cutoff_time + 1;
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// This verifies that the rental window only affects the first load for the
// license, not the first load for the session.
TEST_F(OdkTimerHardTest, Reload) {
uint64_t timer_value = 0;
// An attempt to start playback before the timer starts is an error.
// We use the TIMER_EXPIRED error to mean both early or late.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kUnused, clock_values_.status);
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = false, we should set a timer.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
// We can restart playback before the cutoff time.
const uint64_t valid_restart_time = cutoff_time - 10;
ReloadClock(valid_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
// This restart is outside the rental window. We are allowed to
// restart playback after the rental window expires as long as the first
// decrypt for the license is within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time);
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance);
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 1;
// This play time is outside the rental window. That means we are allowed
// to continue playback after the rental window expires as long as the first
// decrypt is within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time);
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
// Try to play after the cutoff time is not valid.
const uint64_t late_play_time = cutoff_time + 1;
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
TEST_F(OdkTimerHardTest, ReloadLate) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = false, we should set a timer.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
// We can not restart playback after the cutoff time.
const uint64_t late_restart_time = cutoff_time + 10;
ReloadClock(late_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
// Calling UpdateLastPlaybackTimer should not be done, but if it were done,
// it should also fail.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
}
// ************************************************************************
// The Soft Rental use case is a strict time limit on playback.
class OdkTimerSoftTest : public OdkTimerRentalWindow {
protected:
void SetUp() override {
OdkTimerRentalWindow::SetUp();
timer_limits_.soft_expiry = true; // Soft expiry.
// License duration is 200. The license starts when it was signed at 50,
// so the license is valid from 50-250. The rental window is 100-200 -- as
// inherited from the ODKRentalWindow class.
timer_limits_.license_duration_seconds = 200u;
}
};
TEST_F(OdkTimerSoftTest, EarlyTest) {
uint64_t timer_value = 0;
// An attempt to start playback before the timer starts is an error.
// We use the TIMER_EXPIRED error to mean both early or late.
const uint64_t bad_start_time =
system_time(timer_limits_.earliest_playback_start_seconds - 10);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_,
&clock_values_, &timer_value));
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = true, we should not set a timer.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0u, clock_values_.time_when_timer_expires);
}
TEST_F(OdkTimerSoftTest, NormalTest) {
uint64_t timer_value = 0;
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = true, we should not set a timer.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0u, clock_values_.time_when_timer_expires);
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 1;
// This play time is outside the rental window. We are allowed to continue
// playback after the rental window expires as long as the first decrypt is
// within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time);
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
// Try to play after the cutoff time is still valid for soft expiry.
const uint64_t late_play_time = cutoff_time + 1;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
}
// This verifies that the rental window only affects the first load for the
// license, not the first load for the session.
TEST_F(OdkTimerSoftTest, Reload) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = true, we should not set a timer.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0, clock_values_.time_when_timer_expires);
// We can restart playback before the cutoff time.
const uint64_t valid_restart_time = cutoff_time - 10;
ReloadClock(valid_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
// This restart is outside the rental window. We are allowed to
// restart playback after the rental window expires as long as the first
// decrypt for the license is within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time);
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0, clock_values_.time_when_timer_expires);
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 1;
// This play time is outside the rental window. That means we are allowed
// to continue playback after the rental window expires as long as the first
// decrypt is within the rental window.
EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time);
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0u, clock_values_.time_when_timer_expires);
// Try to play after the cutoff time is still valid.
const uint64_t late_play_time = cutoff_time + 1;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0u, clock_values_.time_when_timer_expires);
}
TEST_F(OdkTimerSoftTest, ReloadLate) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t start_time =
system_time(timer_limits_.earliest_playback_start_seconds);
const uint64_t cutoff_time =
system_time(timer_limits_.license_duration_seconds);
// For a soft_expiry = true, we should not set a timer.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
EXPECT_EQ(0u, clock_values_.time_when_timer_expires);
// We can not restart playback after the cutoff time.
const uint64_t late_restart_time = cutoff_time + 10;
ReloadClock(late_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
// Calling UpdateLastPlaybackTimer should not be done, but if it were done,
// it should also fail.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
}
// ************************************************************************
// ************************************************************************
// From the server's point of view, there are two flags that decide soft or
// hard limits: the rental duration, and the playback duration. From
// OEMCrypto's point of view, there is only playback duration. A soft or hard
// rental duration is translated into different rental and license durations.
// The four test classes below all have a 700 rental window and a 200 playback
// duration. We'll use the server description, and then set the OEMCrypto
// restraints in the test SetUp() function. Note, it's easier to use the word
// "day" but really the rental window is 700 seconds, not 7 days. These tests
// have some coverage overlap with the ones above, but it is better to have
// these all grouped together to make sure we cover all of the server team's
// needs.
// ************************************************************************
class Odk7DayTest : public OdkTimerRentalWindow,
public WithParamInterface<ServerExpiry> {
public:
Odk7DayTest() {
rental_window_duration_ = 700;
rental_window_start_ = 100u;
}
protected:
void SetUp() override {
OdkTimerRentalWindow::SetUp();
server_expiry_ = GetParam();
const uint64_t playback_duration = 200;
// Beginning of rental window. it is unusual to start it in the future,
// but it is a supported use case, so we'll test it here.
timer_limits_.earliest_playback_start_seconds = rental_window_start_;
// The rental window is 700 long.
timer_limits_.latest_playback_start_seconds =
timer_limits_.earliest_playback_start_seconds + rental_window_duration_;
// Playback duration is 200 starting from first playback.
timer_limits_.initial_playback_duration_seconds = playback_duration;
if (server_expiry_.soft_rental) {
// The license duration limits any restart. For soft rental window, the
// server will set this to the rental end + playback duration.
// License duration is in seconds since license signed.
timer_limits_.license_duration_seconds =
timer_limits_.latest_playback_start_seconds + playback_duration;
} else {
// The license duration limits any restart. For hard rental window, the
// server will set this to the rental end.
// License duration is in seconds since license signed.
timer_limits_.license_duration_seconds =
timer_limits_.latest_playback_start_seconds;
}
timer_limits_.soft_expiry = server_expiry_.soft_playback;
}
ServerExpiry server_expiry_;
};
TEST_P(Odk7DayTest, StartDay3) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t three_days = 300u;
const uint64_t start_time = system_time(rental_window_start_ + three_days);
const uint64_t cutoff_time =
start_time + timer_limits_.initial_playback_duration_seconds;
if (server_expiry_.soft_playback) {
// If the playback expiry is soft, then the timer is disabled.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_,
&clock_values_, &timer_value));
} else {
// If the playback expiry is hard, then the timer should be set.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
}
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 50;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
if (!server_expiry_.soft_playback) {
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
}
TEST_P(Odk7DayTest, StartDay3Reload) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t three_days = 300u;
const uint64_t start_time = system_time(rental_window_start_ + three_days);
const uint64_t cutoff_time =
start_time + timer_limits_.initial_playback_duration_seconds;
EXPECT_NE(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
// -----------------------------------------------------------------------
// Try to reload and play before the cutoff time is valid.
uint64_t valid_restart_time = cutoff_time - 10;
ReloadClock(valid_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
if (server_expiry_.soft_playback) {
// If the playback expiry is soft, then the timer is disabled.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
} else {
// If the playback expiry is hard, then the timer should be set.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance);
}
// Try to play before the cutoff time is valid.
const uint64_t valid_play_time = cutoff_time - 1;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
if (!server_expiry_.soft_playback) {
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// Try to play after the cutoff time is valid for soft expiry, but not hard.
const uint64_t late_play_time = cutoff_time + 1;
if (server_expiry_.soft_playback) {
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// Last decrypt should change because we were allowed to decrypt.
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
} else {
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// First decrypt should NOT change for either soft or hard.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// -----------------------------------------------------------------------
// Try to reload after the cutoff time is not valid for soft or hard
// playback expiry.
const uint64_t late_restart_time = cutoff_time + 1;
ReloadClock(late_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Calling UpdateLastPlaybackTimer should not be done, but if it were done,
// it should also fail.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should not change.
if (server_expiry_.soft_playback) {
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
} else {
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
}
}
TEST_P(Odk7DayTest, StartDay6Reload) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t six_days = 600u;
const uint64_t start_time = system_time(rental_window_start_ + six_days);
const uint64_t cutoff_time =
server_expiry_.soft_rental
?
// If the rental expiry is soft, we can continue playing and
// reloading for the full playback duration.
start_time + timer_limits_.initial_playback_duration_seconds
// If the rental expiry is hard, we can reload only within the
// rental window.
: system_time(timer_limits_.latest_playback_start_seconds);
// Let's double check that math:
if (server_expiry_.soft_rental) {
// If that was not clear, the cutoff = 100 start of window + 600 start time
// + 200 playback duration...
EXPECT_EQ(system_time(900u), cutoff_time);
} else {
// ...and for hard rental, the cutoff = 100 start of window + 700 rental
// duration.
EXPECT_EQ(system_time(800u), cutoff_time);
}
if (server_expiry_.soft_playback) {
// If the playback expiry is soft, then the timer is disabled.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_,
&clock_values_, &timer_value));
} else {
// If the playback expiry is hard, then the timer should be set.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(start_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance);
}
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt);
// Try to play before the cutoff time is valid.
uint64_t valid_play_time = cutoff_time - 50;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
if (!server_expiry_.soft_playback) {
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// -----------------------------------------------------------------------
// Try to reload and play before the cutoff time is valid.
uint64_t valid_restart_time = cutoff_time - 10;
ReloadClock(valid_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
if (server_expiry_.soft_playback) {
// If the playback expiry is soft, then the timer is disabled.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
} else {
// If the playback expiry is hard, then the timer should be set.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance);
}
// Try to play before the cutoff time is valid.
valid_play_time = cutoff_time - 1;
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_,
&clock_values_));
EXPECT_EQ(kActive, clock_values_.status);
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should change.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
if (!server_expiry_.soft_playback) {
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// Try to play after the cutoff time is valid for soft expiry, but not hard.
const uint64_t late_play_time = cutoff_time + 1;
if (server_expiry_.soft_playback) {
EXPECT_EQ(OEMCrypto_SUCCESS,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// Last decrypt should change because we were allowed to decrypt.
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
} else {
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_,
&clock_values_));
// Last decrypt should NOT change because we were not allowed to decrypt.
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance);
}
// First decrypt should NOT change for either soft or hard.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// -----------------------------------------------------------------------
// Try to reload after the cutoff time is not valid for soft or hard
// playback expiry.
const uint64_t late_restart_time = cutoff_time + 2;
ReloadClock(late_restart_time);
EXPECT_EQ(kActive, clock_values_.status);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Calling UpdateLastPlaybackTimer should not be done, but if it were done,
// it should also fail.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_,
&clock_values_));
// First decrypt should NOT change.
EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt);
// Last decrypt should not change.
if (server_expiry_.soft_playback) {
EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt);
} else {
EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt);
}
}
// This test explicitly shows the difference between hard and soft rental
// expiry. This is not an OEMCrypto concept, but it is used on the serer, and
// this test verifies the logic is handled correctly when we translate it into
// OEMCrypto concepts.
TEST_P(Odk7DayTest, StartDay6ReloadDay7) {
uint64_t timer_value = 0;
// Starting playback within the window should work.
const uint64_t six_days = 600u;
const uint64_t seven_days = 700u;
const uint64_t start_time = system_time(rental_window_start_ + six_days);
EXPECT_NE(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
// Reload time is 1 second after end of rental window.
const uint64_t restart_time =
system_time(timer_limits_.latest_playback_start_seconds + 1);
ReloadClock(restart_time);
if (server_expiry_.soft_rental) {
if (server_expiry_.soft_playback) {
// If the playback expiry is soft, then the timer is disabled.
EXPECT_EQ(ODK_DISABLE_TIMER,
ODK_AttemptFirstPlayback(restart_time, &timer_limits_,
&clock_values_, &timer_value));
} else {
const uint64_t cutoff_time =
start_time + timer_limits_.initial_playback_duration_seconds;
// If the playback expiry is hard, then the timer should be set.
EXPECT_EQ(ODK_SET_TIMER,
ODK_AttemptFirstPlayback(restart_time, &timer_limits_,
&clock_values_, &timer_value));
EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires,
kTolerance);
EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance);
}
} else {
// For hard rental expiry, reloading after the rental window fails.
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(restart_time, &timer_limits_,
&clock_values_, &timer_value));
}
}
TEST_P(Odk7DayTest, StartDay8) {
uint64_t timer_value = 0;
// Starting playback after the rental window should not work.
const uint64_t eight_days = 800u;
const uint64_t start_time = system_time(rental_window_start_ + eight_days);
EXPECT_EQ(ODK_TIMER_EXPIRED,
ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_,
&timer_value));
EXPECT_EQ(kUnused, clock_values_.status);
EXPECT_EQ(0u, clock_values_.time_of_first_decrypt);
EXPECT_EQ(0u, clock_values_.time_of_last_decrypt);
// Calling UpdateLastPlaybackTimer should not be done, but if it were done,
// it should also fail.
EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime(
start_time, &timer_limits_, &clock_values_));
EXPECT_EQ(kUnused, clock_values_.status);
EXPECT_EQ(0u, clock_values_.time_of_first_decrypt);
EXPECT_EQ(0u, clock_values_.time_of_last_decrypt);
}
INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest,
Values(ServerExpiry({true, true}),
ServerExpiry({true, false}),
ServerExpiry({false, true}),
ServerExpiry({false, false})));
// ************************************************************************
// ************************************************************************
// TODO(b/140765031): Cover all tests in Use Cases document.
// Limited Duration License
// 7 day with renewal.
// Streaming with renewal
// Persistent with renewal
} // namespace odk_test