OEMCrypto and OPK v20 prerelease initial commit

This commit is contained in:
Matt Feddersen
2025-05-20 20:30:59 -07:00
parent 98dfef4389
commit a2b9e085e9
193 changed files with 22480 additions and 3275 deletions

133
util/src/buffer_reader.cpp Normal file
View File

@@ -0,0 +1,133 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "buffer_reader.h"
#include <inttypes.h>
#include <string>
#include <vector>
#include "log.h"
#include "platform.h"
namespace wvutil {
bool BufferReader::Read1(uint8_t* v) {
if (v == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null");
return false;
}
if (!HasBytes(1)) {
LOGV("Parse failure: No bytes available");
return false;
}
*v = buf_[pos_++];
return true;
}
// Internal implementation of multi-byte reads
template <typename T>
bool BufferReader::Read(T* v) {
static constexpr size_t kTypeSize = sizeof(T);
if (v == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null (%s)",
__PRETTY_FUNCTION__);
return false;
}
if (!HasBytes(kTypeSize)) {
LOGV("Parse failure: Not enough bytes (%zu)", kTypeSize);
return false;
}
T tmp = 0;
for (size_t i = 0; i < kTypeSize; i++) {
tmp <<= 8;
// This works for signed values.
tmp += buf_[pos_ + i];
}
pos_ += kTypeSize;
*v = tmp;
return true;
}
bool BufferReader::Read2(uint16_t* v) { return Read(v); }
bool BufferReader::Read2s(int16_t* v) { return Read(v); }
bool BufferReader::Read4(uint32_t* v) { return Read(v); }
bool BufferReader::Read4s(int32_t* v) { return Read(v); }
bool BufferReader::Read8(uint64_t* v) { return Read(v); }
bool BufferReader::Read8s(int64_t* v) { return Read(v); }
bool BufferReader::ReadString(std::string* str, size_t count) {
if (str == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null");
return false;
}
if (!HasBytes(count)) {
LOGV("Parse failure: Not enough bytes (%zu)", count);
return false;
}
str->assign(buf_ + pos_, buf_ + pos_ + count);
pos_ += count;
return true;
}
bool BufferReader::ReadVec(std::vector<uint8_t>* vec, size_t count) {
if (vec == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null");
return false;
}
if (!HasBytes(count)) {
LOGV("Parse failure: Not enough bytes (%zu)", count);
return false;
}
vec->assign(buf_ + pos_, buf_ + pos_ + count);
pos_ += count;
return true;
}
bool BufferReader::SkipBytes(size_t count) {
if (!HasBytes(count)) {
LOGV("Parse failure: Not enough bytes (%zu)", count);
return false;
}
pos_ += count;
return true;
}
bool BufferReader::Read4Into8(uint64_t* v) {
if (v == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null");
return false;
}
uint32_t tmp = 0;
if (!Read4(&tmp)) {
return false;
}
*v = tmp;
return true;
}
bool BufferReader::Read4sInto8s(int64_t* v) {
if (v == nullptr) {
LOGE("Parse failure: Null output parameter when expecting non-null");
return false;
}
// Beware of the need for sign extension.
int32_t tmp = 0;
if (!Read4s(&tmp)) {
return false;
}
*v = tmp;
return true;
}
} // namespace wvutil

View File

@@ -0,0 +1,981 @@
// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "hls_attribute_list.h"
#include <inttypes.h>
#include <algorithm>
#include <iomanip>
#include <map>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "log.h"
#include "string_conversions.h"
#include "wv_class_utils.h"
namespace wvutil {
using HlsValueType = HlsAttributeList::ValueType;
using HlsValueInfo = HlsAttributeList::ValueInfo;
namespace {
// Grammatically significant HLS characters.
constexpr char kDashChar = '-';
constexpr char kDecimalPointChar = '.';
constexpr char kSpaceChar = ' ';
constexpr char kLineFeedChar = '\n';
constexpr char kCarriageReturnChar = '\r';
constexpr char kNameValueSeparatorChar = '=';
constexpr char kCommaChar = ',';
constexpr char kQuoteChar = '"';
constexpr char kMemberSeparatorChar = kCommaChar;
constexpr char kResolutionSplitChar = 'x';
// Note: Hex sequences can start with "0x" or "0X". This
// implementation uses lower "0x" when serializing.
constexpr char kHexSequencePrefix[] = "0x";
// ASCII non-control character points.
constexpr char kNonAsciiControlFirstChar = ' '; // 0x20
constexpr char kNonAsciiControlLastChar = '~'; // 0x7e
constexpr bool IsNonAsciiControl(char ch) {
return ch >= kNonAsciiControlFirstChar && ch <= kNonAsciiControlLastChar;
}
// Character points allowed in HLS Playlist files.
// 1. UTF-8 encoded unicode
// * WV CDM does not support unicode or even UTF-8, we restrict
// it to basic ASCII (0x00 to 0x7f)
// 2. Does not allow for control sequences
// * Defined for values 0x00 to 0x1f and 0x7f to 0x9f, except for
// Line Feed (0x0a) and Carriage Return (0x0d).
// * Our restriction to basic ASCII limits this to 0x00 to 0x1f and 0x7f,
// except for LF and CR.
// 3. Requires Unicode Normalized Form type C (NFC); but not applicable
// to our case.
//
// Ref: RFC 8216, section 4.1.
constexpr bool IsAllowedHlsChar(char ch) {
return IsNonAsciiControl(ch) || ch == kLineFeedChar ||
ch == kCarriageReturnChar;
}
constexpr bool IsUpperAlpha(char ch) { return ch >= 'A' && ch <= 'Z'; }
constexpr bool IsLowerAlpha(char ch) { return ch >= 'a' && ch <= 'z'; }
constexpr bool IsDecimalChar(char ch) { return ch >= '0' && ch <= '9'; }
constexpr bool IsUpperHexadecimalChar(char ch) {
return IsDecimalChar(ch) || (ch >= 'A' && ch <= 'Z');
}
constexpr bool IsWhitespaceChar(char ch) {
return ch == kSpaceChar || ch == kLineFeedChar || ch == kCarriageReturnChar;
}
constexpr bool IsValidNameChar(char ch) {
return IsUpperAlpha(ch) || IsDecimalChar(ch) || ch == kDashChar;
}
constexpr bool IsValidEnumStringChar(char ch) {
return IsAllowedHlsChar(ch) && !IsWhitespaceChar(ch) && ch != kQuoteChar &&
ch != kCommaChar;
}
constexpr bool IsValidQuotedStringValueChar(char ch) {
return IsAllowedHlsChar(ch) && ch != kQuoteChar && ch != kLineFeedChar &&
ch != kCarriageReturnChar;
}
constexpr bool IsValidSignedFloatChar(char ch) {
return ch == kDashChar || ch == kDecimalPointChar || IsDecimalChar(ch);
}
constexpr bool IsValidUnquotedValueChar(char ch) {
return IsValidSignedFloatChar(ch) || IsDecimalChar(ch) ||
IsValidEnumStringChar(ch);
}
constexpr char ToUpper(char ch) {
return IsLowerAlpha(ch) ? (ch + ('A' - 'a')) : ch;
}
// == Type Pattern Matching ==
constexpr size_t kMinIntegerLength = 1;
constexpr size_t kMaxIntegerLength = 20;
// HLS integers must fit within an unsigned 64-bit value.
constexpr char kMaxIntegerRep[] = "18446744073709551615";
// Checks if the serialized attribute value appears like an integer.
bool IsIntegerLike(const std::string& value_rep) {
if (value_rep.size() < kMinIntegerLength ||
value_rep.size() > kMaxIntegerLength)
return false;
if (!std::all_of(value_rep.begin(), value_rep.end(), IsDecimalChar))
return false;
if (value_rep.size() < kMaxIntegerLength) return true;
// Must be able to fit into a uint64_t.
return value_rep <= kMaxIntegerRep;
}
// Checks if the serialized attribute value appears like a hex
// sequence.
bool IsHexSequenceLike(const std::string& value_rep) {
// Must have hex prefix, and at least 1 hex character.
if (value_rep.size() < 3) return false;
// Check prefix ("0x" or "0X").
if (value_rep.front() != '0' || (value_rep[1] != 'x' && value_rep[1] != 'X'))
return false;
return std::all_of(value_rep.begin() + 2, value_rep.end(),
IsUpperHexadecimalChar);
}
// Checks if the serialized attribute value appears like a signed
// float (not this can be used for unsigned floats as well).
bool IsSignedFloatLike(const std::string& value_rep) {
if (value_rep.empty()) return false;
// Initial check that all are valid signed float characters.
if (!std::all_of(value_rep.begin(), value_rep.end(), IsValidSignedFloatChar))
return false;
// Skip initial dash.
const size_t int_start_pos = (value_rep.front() == kDashChar) ? 1 : 0;
if (int_start_pos == value_rep.size()) return false;
// Ensure there are no more dashes.
if (value_rep.find(kDashChar, int_start_pos) != std::string::npos)
return false;
const size_t decimal_pos = value_rep.find(kDecimalPointChar);
// No decimal point implies all other character are decimal digits,
// and is a valid floating point value.
if (decimal_pos == std::string::npos) return true;
// Ensure the decimal is not the first item.
if (decimal_pos == int_start_pos) return false;
// Ensure all values after the first decimal point are
// digits.
if ((decimal_pos + 1) == value_rep.size()) return false;
return std::all_of(value_rep.begin() + decimal_pos + 1, value_rep.end(),
IsDecimalChar);
}
bool IsQuotedStringLike(const std::string& value_rep) {
if (value_rep.size() < 2) return false;
if (value_rep.front() != kQuoteChar || value_rep.back() != kQuoteChar)
return false;
return std::all_of(value_rep.begin() + 1, value_rep.end() - 1,
IsValidQuotedStringValueChar);
}
bool IsEnumStringLike(const std::string& value_rep) {
return HlsAttributeList::IsValidEnumStringValue(value_rep);
}
bool IsResolutionLike(const std::string& value_rep) {
if (value_rep.size() < 3) return false;
// Find the resolution split.
const size_t split_pos = value_rep.find(kResolutionSplitChar);
if (split_pos == std::string::npos) return false;
// Ensure it is not at the beginning or end of the value.
if (split_pos == 0 || (split_pos + 1) == value_rep.size()) return false;
return IsIntegerLike(value_rep.substr(0, split_pos)) &&
IsIntegerLike(value_rep.substr(split_pos + 1));
}
std::string MakeInteger(uint64_t value) { return std::to_string(value); }
std::string MakeSignedFloat(double value) {
// Avoid potential issues with negative zero.
if (value == 0.0 || value == -0.0) return "0";
std::ostringstream out;
out << std::fixed << value;
std::string result = out.str();
// Remove trailing zeros.
while (!result.empty() && result.back() == '0') result.pop_back();
// Check if there were only zeros after the decimal.
if (!result.empty() && result.back() == '.') result.pop_back();
return result;
}
std::string MakeQuoted(const std::string& value) {
std::string result;
result.reserve(value.size() + 2);
result.push_back(kQuoteChar);
result.append(value);
result.push_back(kQuoteChar);
return result;
}
std::string MakeHex(const std::string& data) {
std::string result;
result.reserve(data.size() * 2 + sizeof(kHexSequencePrefix) - 1);
result.append(kHexSequencePrefix);
result.append(b2a_hex(data));
// HLS hex must be upper case.
for (size_t i = 2; i < result.size(); i++) {
result[i] = ToUpper(result[i]);
}
return result;
}
std::string MakeHex(const std::vector<uint8_t>& data) {
std::string result;
result.reserve(data.size() * 2 + sizeof(kHexSequencePrefix) - 1);
result.append(kHexSequencePrefix);
result.append(b2a_hex(data));
return result;
}
std::string MakeResolution(uint64_t width, uint64_t height) {
std::ostringstream out;
out << width << kResolutionSplitChar << height;
return out.str();
}
// Assumes |integer_rep| is a valid HLS integer representation.
bool ParseIntegerInternal(const std::string& integer_rep, uint64_t* value) {
std::istringstream in(integer_rep);
in >> (*value);
return !in.fail();
}
// Assumes |integer_rep| is a valid HLS float representation.
bool ParseFloat(const std::string& float_rep, double* value) {
std::istringstream in(float_rep);
in >> *value;
return !in.fail();
}
// == Tokenizer ==
// The HLS Member Token represents the name-value pair.
// The |value_rep| is the exact representation from the
// HLS-serialized value (quoted values include the quotes).
struct HlsMemberToken {
std::string name;
std::string value_rep;
void Clear() {
name.clear();
value_rep.clear();
}
};
// The HLS Attribute List Reader will reader a character sequence
// representing a serialized HLS Attribute List, and convert them
// into a sequence of name-value tokens.
//
// This performs more like a lexeme tokenizer, rather than a lexical
// tokenizers (without producing structural tokens).
//
// The |name| will be a well-formed name; but values are simply check
// if they match a quoted or unquoted values. The contents of the
// values might not match lexical rules of an HLS value types.
//
// Rough grammar this reader enforces:
// list := BEGIN member (',' member)* END
// member := name '=' value
// name := name-char+
// value := quoted-value | unquoted-value
// quoted-value := '"' quoted-char* '"'
// unquoted-value := unquoted-char+
class HlsAttributeListReader {
public:
// Filler value.
static constexpr char kEocChar = 0;
HlsAttributeListReader() = delete;
WVCDM_DISALLOW_COPY_AND_MOVE(HlsAttributeListReader);
HlsAttributeListReader(const std::string& contents) : contents_(contents) {}
size_t index() const { return index_; }
size_t remaining() const { return contents_.size() - index_; }
bool IsBegin() const { return index_ == 0; }
bool IsEnd() const { return index_ >= contents_.size(); }
void Reset() { index_ = 0; }
// Reads the entire HLS list string, enforcing the following
// grammar:
// list := BEGIN member (',' member)* END
bool ReadMembers(std::vector<HlsMemberToken>* members);
private:
char Peak() const { return IsEnd() ? kEocChar : contents_[index_]; }
char Pop() { return IsEnd() ? kEocChar : contents_[index_++]; }
bool IsNext(char ch) const { return IsEnd() ? false : Peak() == ch; }
bool IsNextQuote() const { return IsNext(kQuoteChar); }
bool IsNextNameValueSeparator() const {
return IsNext(kNameValueSeparatorChar);
}
bool IsNextMemberSeparator() const { return IsNext(kMemberSeparatorChar); }
bool IsNextNameChar() const {
return IsEnd() ? false : IsValidNameChar(Peak());
}
bool IsNextUnquotedValueChar() const {
return IsEnd() ? false : IsValidUnquotedValueChar(Peak());
}
bool IsNextQuotedValueChar() const {
return IsEnd() ? false : IsValidQuotedStringValueChar(Peak());
}
// Reads the next sequence of tokens representing a member.
// On a successful read, the reader index started at the first
// character of an attribute name, and will end with the index at
// the first character after the attribute value (or end).
//
// Enforces the name-value grammar:
// member := name '=' value
//
// Returns true if the member was read correctly, false otherwise.
bool ReadMember(HlsMemberToken* member);
// Reads the next token representing an attribute name.
// On a successful read, the reader index started at the first
// character of the attribute name, and will end with the index at
// the first character after the attribute name.
//
// Returns true if the name was read correctly, false otherwise.
bool ReadName(std::string* name);
// Reads the next token representing a quoted attribute value.
// On a successful read, the reader index started at the first
// character of the attribute value (must be a quote), and will end
// with the index at the first character after the closing quote.
//
// Enforces the quoted-value grammar:
// quoted-value := '"' quoted-char* '"'
//
// The produce value rep will include the beginning and end quote.
// Returns true if the quote was read correctly, false otherwise.
bool ReadQuotedValue(std::string* value_rep);
// Reads the next token representing an unquoted attribute value.
// On a successful read, the reader index started at the first
// character of the attribute value, and will end with the index at
// the first character after the last possible attribute value.
//
// Enforces the unquoted-value grammar:
// unquoted-value := unquoted-char+
//
// Returns true if the quote was read correctly, false otherwise.
bool ReadUnquotedValue(std::string* value_rep);
// Reads the next token representing a value (quoted or unquoted).
// Simply checks the first attribute value character if a quote
// or not, then uses either ReadQuotedValue() or ReadUnquotedValue()
//
// Enforces the value grammar:
// value := quoted-value | unquoted-value
//
// Returns true if the value was read correctly, false otherwise.
bool ReadValue(std::string* value_rep);
const std::string& contents_;
size_t index_ = 0;
}; // class HlsAttributeListReader
bool HlsAttributeListReader::ReadMembers(std::vector<HlsMemberToken>* members) {
members->clear();
if (!IsBegin()) {
LOGV("Not at the beginning of the contents: index = %zu", index());
return false;
}
while (!IsEnd()) {
if (!members->empty()) {
// Check for member separator.
if (!IsNextMemberSeparator()) {
LOGV(
"Expected member separator: char = %c, index = %zu, "
"member_index = %zu",
Peak(), index(), members->size());
members->clear();
return false;
}
Pop();
}
HlsMemberToken member;
if (!ReadMember(&member)) {
LOGV("Failed to read member: member_index = %zu", members->size());
members->clear();
return false;
}
members->push_back(std::move(member));
}
return true;
}
bool HlsAttributeListReader::ReadMember(HlsMemberToken* member) {
member->Clear();
if (!ReadName(&member->name)) {
LOGV("Failed to read name");
member->Clear();
return false;
}
if (!IsNextNameValueSeparator()) {
LOGV("Expected name-value separator: char = %c, index = %zu", Peak(),
index());
member->Clear();
return false;
}
Pop();
if (!ReadValue(&member->value_rep)) {
LOGV("Failed to read value");
member->Clear();
return false;
}
return true;
}
bool HlsAttributeListReader::ReadName(std::string* name) {
name->clear();
while (!IsEnd()) {
if (!IsNextNameChar()) break;
name->push_back(Pop());
}
return !name->empty();
}
bool HlsAttributeListReader::ReadQuotedValue(std::string* value_rep) {
value_rep->clear();
if (!IsNextQuote()) return false;
value_rep->push_back(Pop());
while (!IsEnd()) {
if (IsNextQuote()) {
value_rep->push_back(Pop());
break;
}
if (!IsNextQuotedValueChar()) break;
value_rep->push_back(Pop());
}
// Verify start and end quotes are in the output value.
return value_rep->size() >= 2 && value_rep->front() == kQuoteChar &&
value_rep->back() == kQuoteChar;
}
bool HlsAttributeListReader::ReadUnquotedValue(std::string* value_rep) {
value_rep->clear();
while (!IsEnd()) {
if (!IsNextUnquotedValueChar()) break;
value_rep->push_back(Pop());
}
return !value_rep->empty();
}
bool HlsAttributeListReader::ReadValue(std::string* value_rep) {
if (IsEnd()) {
LOGV("Expected value char, reached end");
return false;
}
if (IsNextQuote()) return ReadQuotedValue(value_rep);
if (IsNextUnquotedValueChar()) return ReadUnquotedValue(value_rep);
LOGV("Expected value start char: char = %c, index = %zu", Peak(), index());
return false;
}
} // namespace
// static
const char* HlsAttributeList::ValueTypeToString(ValueType type) {
switch (type) {
case kUnsetType:
return "Unset";
case kIntegerType:
return "Decimal-Integer";
case kHexSequenceType:
return "Hexadecimal-Sequence";
case kFloatType:
return "Decimal-Floating-Point";
case kQuotedStringType:
return "Quoted-String";
case kEnumStringType:
return "Enumerated-String";
case kResolutionType:
return "Decimal-Resolution";
}
return "<unknown>";
}
// static
bool HlsAttributeList::IsValidName(const std::string& name) {
if (name.empty()) return false;
return std::all_of(name.begin(), name.end(), IsValidNameChar);
}
// static
bool HlsAttributeList::IsValidEnumStringValue(const std::string& value) {
if (value.empty()) return false;
return std::all_of(value.begin(), value.end(), IsValidEnumStringChar);
}
// static
bool HlsAttributeList::IsValidQuotedStringValue(const std::string& value) {
if (value.empty()) return true;
return std::all_of(value.begin(), value.end(), IsValidQuotedStringValueChar);
}
// static
bool HlsAttributeList::IsValidIntegerRep(const std::string& value) {
return IsIntegerLike(value);
}
// static
bool HlsAttributeList::ParseInteger(const std::string& integer_rep,
uint64_t* value) {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
if (!IsIntegerLike(integer_rep)) {
LOGV("Not a valid HLS integer rep: %s",
SafeByteIdToString(integer_rep).c_str());
return false;
}
return ParseIntegerInternal(integer_rep, value);
}
std::vector<std::string> HlsAttributeList::GetNames() const {
if (IsEmpty()) return std::vector<std::string>();
std::vector<std::string> names;
names.reserve(Count());
for (const auto& pair : members_) {
names.push_back(pair.first);
}
std::sort(names.begin(), names.end());
return names;
}
bool HlsAttributeList::Contains(const std::string& name) const {
return members_.find(name) != members_.end();
}
const HlsValueInfo* HlsAttributeList::GetInfo(const std::string& name) const {
const auto it = members_.find(name);
if (it == members_.end()) return nullptr;
return &it->second;
}
bool HlsAttributeList::IsType(const std::string& name, ValueType type) const {
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return kUnsetType;
}
if (info->first == type) return true;
switch (type) {
case kUnsetType:
return false; // Bad caller value.
case kIntegerType:
return IsIntegerLike(info->second);
case kHexSequenceType:
return IsHexSequenceLike(info->second);
case kFloatType:
return IsSignedFloatLike(info->second);
case kQuotedStringType:
return false; // Quoted strings are unambiguous.
case kEnumStringType:
return IsEnumStringLike(info->second);
case kResolutionType:
return IsResolutionLike(info->second);
}
LOGE("Unexpected type: type = %d", static_cast<int>(type));
return false;
}
bool HlsAttributeList::GetEnumString(const std::string& name,
std::string* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first == kEnumStringType) {
value->assign(info->second);
return true;
}
// Certain values could appear as something else, but are still
// valid enum strings.
if (!IsEnumStringLike(info->second)) {
LOGV("Attribute is not enum string: name = %s, type = %s, rep = %s",
name.c_str(), ValueTypeToString(info->first), info->second.c_str());
return false;
}
value->assign(info->second);
return true;
}
bool HlsAttributeList::GetQuotedString(const std::string& name,
std::string* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
// Quote strings are unambiguous to all other value types.
if (info->first != kQuotedStringType) {
LOGV("Attribute is not quoted string: name = %s, type = %s", name.c_str(),
ValueTypeToString(info->first));
return false;
}
const std::string& rep = info->second;
if (rep.size() < 2) {
// Should not have allowed this to happen when
// assigning the value.
LOGE("Internal error: quote string too small: name = %s, rep = %s",
name.c_str(), SafeByteIdToString(rep).c_str());
return false;
}
// Beginning and end quote.
value->assign(rep.begin() + 1, rep.end() - 1);
return true;
}
bool HlsAttributeList::GetHexSequence(const std::string& name,
std::string* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first != kHexSequenceType && !IsHexSequenceLike(info->second)) {
LOGV("Attribute is not hex sequence: name = %s, type = %s", name.c_str(),
ValueTypeToString(info->first));
return false;
}
const std::string& rep = info->second;
// Leading "0x" or "0X";
if (rep.size() < 2) {
LOGE("Internal error: hex sequence is too small: name = %s, rep = %s",
name.c_str(), SafeByteIdToString(rep).c_str());
return false;
}
std::string hex_only = rep.substr(2); // Strip prefix.
if ((hex_only.size() % 2) != 0) {
// HLS attributes are not required to have hex sequence of even
// length. Prepending an '0' for Widevine's hex to bytes converter.
hex_only.insert(0, 1, '0');
}
*value = a2bs_hex(hex_only);
if (value->empty()) {
LOGE("Internal error: failed to decode hex sequence: name = %s, rep = %s",
name.c_str(), SafeByteIdToString(rep).c_str());
return false;
}
return true;
}
bool HlsAttributeList::GetHexSequence(const std::string& name,
std::vector<uint8_t>* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first != kHexSequenceType && !IsHexSequenceLike(info->second)) {
LOGV("Attribute is not hex sequence: name = %s, type = %s", name.c_str(),
ValueTypeToString(info->first));
return false;
}
const std::string& rep = info->second;
// Leading "0x" or "0X";
if (rep.size() < 2) {
LOGE("Internal error: hex sequence is too small: name = %s, rep = %s",
name.c_str(), SafeByteIdToString(rep).c_str());
return false;
}
std::string hex_only = rep.substr(2); // Strip prefix.
if ((hex_only.size() % 2) != 0) {
// HLS attributes are not required to have hex sequence of even
// length. Prepending an '0' for Widevine's hex to bytes converter.
hex_only.insert(0, 1, '0');
}
*value = a2b_hex(hex_only);
if (value->empty()) {
LOGE("Internal error: failed to decode hex sequence: name = %s, rep = %s",
name.c_str(), SafeByteIdToString(rep).c_str());
return false;
}
return true;
}
bool HlsAttributeList::GetInteger(const std::string& name,
uint64_t* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first != kIntegerType && !IsIntegerLike(info->second)) {
LOGV("Attribute is not integer: name = %s, type = %s, rep = %s",
name.c_str(), ValueTypeToString(info->first), info->second.c_str());
return false;
}
if (!ParseIntegerInternal(info->second, value)) {
LOGV("Failed to parse value as integer: name = %s, rep = %s", name.c_str(),
info->second.c_str());
*value = 0;
return false;
}
return true;
}
bool HlsAttributeList::GetFloat(const std::string& name, double* value) const {
if (value == nullptr) {
LOGE("Output |value| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first != kFloatType && !IsSignedFloatLike(info->second)) {
LOGV("Attribute is not float: name = %s, type = %s, rep = %s", name.c_str(),
ValueTypeToString(info->first), info->second.c_str());
return false;
}
if (!ParseFloat(info->second, value)) {
LOGV("Failed to parse value as float: name = %s, rep = %s", name.c_str(),
info->second.c_str());
*value = 0;
return false;
}
return true;
}
bool HlsAttributeList::GetResolution(const std::string& name, uint64_t* width,
uint64_t* height) const {
if (width == nullptr) {
LOGE("Output |width| is null");
return false;
}
if (height == nullptr) {
LOGE("Output |height| is null");
return false;
}
const auto* info = GetInfo(name);
if (info == nullptr) {
LOGV("Attribute does not exist: %s", name.c_str());
return false;
}
if (info->first != kResolutionType && !IsResolutionLike(info->second)) {
LOGV("Attribute is not resolution: name = %s, type = %s, rep = %s",
name.c_str(), ValueTypeToString(info->first), info->second.c_str());
return false;
}
const size_t res_split_pos = info->second.find(kResolutionSplitChar);
if (res_split_pos == std::string::npos) {
LOGE(
"Internal error: resolution is missing resolution split: "
"name = %s, rep = %s",
name.c_str(), info->second.c_str());
return false;
}
if (!ParseIntegerInternal(info->second.substr(0, res_split_pos), width) ||
!ParseIntegerInternal(info->second.substr(res_split_pos + 1), height)) {
LOGV("Failed to parse resolution: name = %s, rep = %s", name.c_str(),
info->second.c_str());
*width = *height = 0;
return false;
}
return true;
}
bool HlsAttributeList::SetEnumString(const std::string& name,
const std::string& value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
if (!IsValidEnumStringValue(value)) {
LOGV("Invalid HLS enum string value: %s", value.c_str());
return false;
}
members_[name] = HlsValueInfo(kEnumStringType, value);
return true;
}
bool HlsAttributeList::SetQuotedString(const std::string& name,
const std::string& value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
if (!IsValidQuotedStringValue(value)) {
LOGV("Invalid HLS quoted string value: %s", value.c_str());
return false;
}
members_[name] = HlsValueInfo(kQuotedStringType, MakeQuoted(value));
return true;
}
bool HlsAttributeList::SetHexSequence(const std::string& name,
const std::string& value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
if (value.empty()) {
LOGV("Hex sequence data cannot be empty: %s", name.c_str());
return false;
}
members_[name] = HlsValueInfo(kHexSequenceType, MakeHex(value));
return true;
}
bool HlsAttributeList::SetHexSequence(const std::string& name,
const std::vector<uint8_t>& value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
if (value.empty()) {
LOGV("Hex sequence data cannot be empty: %s", name.c_str());
return false;
}
members_[name] = HlsValueInfo(kHexSequenceType, MakeHex(value));
return true;
}
bool HlsAttributeList::SetInteger(const std::string& name, uint64_t value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
members_[name] = HlsValueInfo(kIntegerType, MakeInteger(value));
return true;
}
bool HlsAttributeList::SetFloat(const std::string& name, double value) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
members_[name] = HlsValueInfo(kSignedFloatType, MakeSignedFloat(value));
return true;
}
bool HlsAttributeList::SetResolution(const std::string& name, uint64_t width,
uint64_t height) {
if (!IsValidName(name)) {
LOGV("Invalid HLS attribute name: %s", name.c_str());
return false;
}
members_[name] = HlsValueInfo(kResolutionType, MakeResolution(width, height));
return true;
}
bool HlsAttributeList::Remove(const std::string& name) {
return members_.erase(name) != 0;
}
bool HlsAttributeList::Parse(const std::string& hls_attr_list_rep) {
Clear();
if (hls_attr_list_rep.empty()) {
// Technically not an error.
LOGV("Empty HLS attribute list");
return true;
}
std::vector<HlsMemberToken> tokens;
if (!HlsAttributeListReader(hls_attr_list_rep).ReadMembers(&tokens)) {
LOGV("Failed to tokenize");
return false;
}
if (tokens.empty()) return true;
for (auto& token : tokens) {
if (!IsValidName(token.name)) {
// Internal error as the tokenizer should have
// caught this.
LOGE("Invalid name: name = %s, value_rep = %s",
SafeByteIdToString(token.name).c_str(), token.value_rep.c_str());
Clear();
return false;
}
if (Contains(token.name)) {
// HLS specification recommends clients to refuse to parse
// lists with repeated attribute names.
LOGV("HLS list contains repeated name: name = %s, list_rep = %s",
token.name.c_str(), hls_attr_list_rep.c_str());
Clear();
return false;
}
// The "type" is used as a hint, but does not restrict
// how the value may be used.
if (IsQuotedStringLike(token.value_rep)) {
members_.emplace(token.name, HlsValueInfo{kQuotedStringType,
std::move(token.value_rep)});
} else if (IsIntegerLike(token.value_rep)) {
members_.emplace(token.name,
HlsValueInfo{kIntegerType, std::move(token.value_rep)});
} else if (IsSignedFloatLike(token.value_rep)) {
members_.emplace(token.name, HlsValueInfo{kSignedFloatType,
std::move(token.value_rep)});
} else if (IsHexSequenceLike(token.value_rep)) {
members_.emplace(token.name, HlsValueInfo{kHexSequenceType,
std::move(token.value_rep)});
} else if (IsResolutionLike(token.value_rep)) {
members_.emplace(token.name, HlsValueInfo{kResolutionType,
std::move(token.value_rep)});
} else if (IsEnumStringLike(token.value_rep)) {
members_.emplace(token.name, HlsValueInfo{kEnumStringType,
std::move(token.value_rep)});
} else {
// Note: HlsAttributeListReader is a lexeme tokenizer not a
// lexical tokenizer; this is not an internal error.
LOGV("Unrecognized value: name = %s, rep = %s", token.name.c_str(),
SafeByteIdToString(token.value_rep).c_str());
Clear();
return false;
}
}
return true;
}
std::string HlsAttributeList::Serialize() const {
std::ostringstream hls_stream;
if (!SerializeToStream(&hls_stream)) {
// Should not occur.
LOGE("Failed to serialize HLS attribute list");
return std::string();
}
return hls_stream.str();
}
bool HlsAttributeList::SerializeToStream(std::ostream* out) const {
if (out == nullptr) {
LOGE("Output stream is null");
return false;
}
bool first_member = true;
for (const auto& member : members_) {
if (first_member) {
first_member = false;
} else {
*out << kMemberSeparatorChar;
}
*out << member.first << kNameValueSeparatorChar << member.second.second;
}
return true;
}
} // namespace wvutil

View File

@@ -1,60 +0,0 @@
// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "rw_lock.h"
#include "log.h"
namespace wvutil {
shared_mutex::~shared_mutex() {
if (reader_count_ > 0) {
LOGE("shared_mutex destroyed with active readers!");
}
if (has_writer_) {
LOGE("shared_mutex destroyed with an active writer!");
}
}
void shared_mutex::lock_shared() {
std::unique_lock<std::mutex> lock(mutex_);
while (has_writer_) {
condition_variable_.wait(lock);
}
++reader_count_;
}
void shared_mutex::unlock_shared() {
std::unique_lock<std::mutex> lock(mutex_);
--reader_count_;
if (reader_count_ == 0) {
condition_variable_.notify_all();
}
}
bool shared_mutex::lock_implementation(bool abort_if_unavailable) {
std::unique_lock<std::mutex> lock(mutex_);
while (reader_count_ > 0 || has_writer_) {
if (abort_if_unavailable) return false;
condition_variable_.wait(lock);
}
has_writer_ = true;
return true;
}
void shared_mutex::unlock() {
std::unique_lock<std::mutex> lock(mutex_);
has_writer_ = false;
condition_variable_.notify_all();
}
} // namespace wvutil

244
util/src/string_utils.cpp Normal file
View File

@@ -0,0 +1,244 @@
// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "string_utils.h"
#include <algorithm>
#include <string>
#include <string_view>
#include <vector>
#include "log.h"
namespace wvutil {
namespace {
// ASCII whitespace characters.
constexpr char kWhiteSpace[] = " \t\n\r";
constexpr size_t kNotFound = std::string::npos;
// Splits input string |s| into a vector of strings containing
// a single character.
std::vector<std::string> StringSplitAll(const std::string& s) {
std::vector<std::string> tokens;
for (const char& c : s) {
tokens.emplace_back(1, c);
}
return tokens;
}
std::vector<std::string> StringSplitCommon(const std::string& s,
const std::string_view& delim) {
if (s.empty()) return {};
if (delim.empty()) return StringSplitAll(s);
std::vector<std::string> tokens;
size_t start = 0;
size_t end = 0;
while ((end = s.find(delim, start)) != kNotFound) {
tokens.push_back(s.substr(start, end - start));
start = end + delim.size();
}
tokens.push_back(s.substr(start));
return tokens;
}
// Returns the total length of all the |tokens|.
size_t SumOfTokenLengths(const std::vector<std::string>& tokens) {
size_t total_length = 0;
for (const auto& token : tokens) {
total_length += token.size();
}
return total_length;
}
// Special case of StringJoin where the glue is empty.
std::string StringJoinWithoutGlue(const std::vector<std::string>& tokens) {
if (tokens.empty()) return std::string();
const size_t expected_size = SumOfTokenLengths(tokens);
std::string result;
result.reserve(expected_size);
result = tokens.front();
for (size_t i = 1; i < tokens.size(); i++) {
result.append(tokens[i]);
}
return result;
}
std::string StringJoinCommon(const std::vector<std::string>& tokens,
const std::string_view& glue) {
if (tokens.empty()) return std::string();
if (tokens.size() == 1) return tokens.front();
if (glue.empty()) return StringJoinWithoutGlue(tokens);
// Total length of tokens + the glue length between each token.
const size_t expected_size =
SumOfTokenLengths(tokens) + ((tokens.size() - 1) * glue.size());
std::string result;
result.reserve(expected_size);
result = tokens.front();
for (size_t i = 1; i < tokens.size(); i++) {
result.append(glue);
result.append(tokens[i]);
}
return result;
}
size_t StringCountCommon(const std::string& haystack,
const std::string_view& needle) {
// Special case. String libraries count the occurrence of an empty
// string as the length of the haystack + 1. This library does the
// same to remain predictable to those familiar with other string
// libraries.
if (needle.empty()) return haystack.size() + 1;
if (haystack.size() < needle.size()) return 0;
size_t count = 0;
size_t pos = 0;
while ((pos = haystack.find(needle, pos)) != kNotFound) {
count++;
pos += needle.size();
}
return count;
}
bool StringContainsCommon(const std::string& haystack,
const std::string_view& needle) {
if (needle.empty()) return true;
if (haystack.size() < needle.size()) return false;
return haystack.find(needle) != kNotFound;
}
bool StringStartsWithCommon(const std::string& haystack,
const std::string_view& needle) {
if (needle.empty()) return true;
if (haystack.size() < needle.size()) return false;
return std::equal(haystack.begin(), haystack.begin() + needle.size(),
needle.begin(), needle.end());
}
bool StringEndsWithCommon(const std::string& haystack,
const std::string_view& needle) {
if (haystack.size() < needle.size()) return false;
return std::equal(haystack.rbegin(), haystack.rbegin() + needle.size(),
needle.rbegin(), needle.rend());
}
} // namespace
std::vector<std::string> StringSplit(const std::string& s, char delim) {
return StringSplitCommon(s, std::string_view(&delim, 1));
}
std::vector<std::string> StringSplit(const std::string& s,
const std::string& delim) {
return StringSplitCommon(s, std::string_view(delim));
}
std::vector<std::string> StringSplit(const std::string& s, const char* delim) {
if (delim == nullptr) {
LOGE("Input |delim| is null");
return {};
}
return StringSplitCommon(s, std::string_view(delim));
}
std::string StringJoin(const std::vector<std::string>& tokens, char glue) {
return StringJoinCommon(tokens, std::string_view(&glue, 1));
}
std::string StringJoin(const std::vector<std::string>& tokens,
const std::string& glue) {
return StringJoinCommon(tokens, std::string_view(glue));
}
std::string StringJoin(const std::vector<std::string>& tokens,
const char* glue) {
if (glue == nullptr) {
LOGE("Input |glue| is empty");
return std::string();
}
return StringJoinCommon(tokens, std::string_view(glue));
}
size_t StringCount(const std::string& haystack, char needle) {
if (haystack.empty()) return 0;
return std::count(haystack.begin(), haystack.end(), needle);
}
size_t StringCount(const std::string& haystack, const std::string& needle) {
return StringCountCommon(haystack, std::string_view(needle));
}
size_t StringCount(const std::string& haystack, const char* needle) {
if (needle == nullptr) {
LOGE("Input |needle| is null");
return 0;
}
return StringCountCommon(haystack, std::string_view(needle));
}
bool StringContains(const std::string& haystack, char needle) {
if (haystack.empty()) return false;
return haystack.find(needle) != kNotFound;
}
bool StringContains(const std::string& haystack, const std::string& needle) {
return StringContainsCommon(haystack, std::string_view(needle));
}
bool StringContains(const std::string& haystack, const char* needle) {
if (needle == nullptr) {
LOGE("Input |needle| is null");
return false;
}
return StringContainsCommon(haystack, std::string_view(needle));
}
bool StringStartsWith(const std::string& haystack, char needle) {
if (haystack.empty()) return false;
return haystack.front() == needle;
}
bool StringStartsWith(const std::string& haystack, const std::string& needle) {
return StringStartsWithCommon(haystack, std::string_view(needle));
}
bool StringStartsWith(const std::string& haystack, const char* needle) {
if (needle == nullptr) {
LOGE("Input |needle| is null");
return false;
}
return StringStartsWithCommon(haystack, std::string_view(needle));
}
bool StringEndsWith(const std::string& haystack, char needle) {
if (haystack.empty()) return false;
return haystack.back() == needle;
}
bool StringEndsWith(const std::string& haystack, const std::string& needle) {
return StringEndsWithCommon(haystack, std::string_view(needle));
}
bool StringEndsWith(const std::string& haystack, const char* needle) {
if (needle == nullptr) {
LOGE("Input |needle| is null");
return false;
}
return StringEndsWithCommon(haystack, std::string_view(needle));
}
std::string StringTrim(const std::string& s) {
if (s.empty()) return std::string();
const size_t start = s.find_first_not_of(kWhiteSpace);
const size_t end = s.find_last_not_of(kWhiteSpace);
if (start == kNotFound || end == kNotFound)
return std::string(); // All white space.
return s.substr(start, end - start + 1);
}
bool StringVecContains(const std::vector<std::string>& haystack,
const std::string& needle) {
if (haystack.empty()) return false;
return std::find(haystack.begin(), haystack.end(), needle) != haystack.end();
}
} // namespace wvutil

83
util/src/time_struct.h Normal file
View File

@@ -0,0 +1,83 @@
// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WVCDM_UTIL_INTERNAL_TIME_STRUCT_H_
#define WVCDM_UTIL_INTERNAL_TIME_STRUCT_H_
// This is an internal header and should not be included outside
// of the CDM util library.
#ifndef WVCDM_ALLOW_TIME_STRUCT_INCLUDE
# error Internal header, do not include
#endif // WVCDM_ALLOW_TIME_STRUCT_INCLUDE
#include <time.h>
#include "wv_class_utils.h"
namespace wvutil {
namespace internal {
// Wrapper class around a struct tm from the <time.h> header.
// Allows for easy conversion from how the C struct tm represents
// values to how the DateTime represents values.
class TimeStruct {
public:
// struct tm offsets.
static constexpr int kYearOffset = 1900;
static constexpr int kMonthOffset = 1;
static constexpr int kDayOfWeekOffset = 1;
static constexpr int kDayOfYearOffset = 1;
constexpr TimeStruct() = default;
WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(TimeStruct);
constexpr const struct tm& time_parts() const { return time_parts_; }
constexpr void set_time_parts(const struct tm& tp) { time_parts_ = tp; }
// Date fields.
constexpr int year() const { return time_parts_.tm_year + kYearOffset; }
constexpr void set_year(int year) {
time_parts_.tm_year = year - kYearOffset;
}
constexpr int month() const { return time_parts_.tm_mon + kMonthOffset; }
constexpr void set_month(int month) {
time_parts_.tm_mon = month - kMonthOffset;
}
constexpr int day_of_month() const { return time_parts_.tm_mday; }
constexpr void set_day_of_month(int day_of_month) {
time_parts_.tm_mday = day_of_month;
}
// Sunday = 1, Saturday = 7
constexpr int day_of_week() const {
return time_parts_.tm_wday + kDayOfWeekOffset;
}
constexpr void set_day_of_week(int day_of_week) {
time_parts_.tm_wday = day_of_week - kDayOfWeekOffset;
}
// January 1st = 1, December 31st = 365/366
constexpr int day_of_year() const {
return time_parts_.tm_yday + kDayOfYearOffset;
}
constexpr void set_day_of_year(int day_of_year) {
time_parts_.tm_yday = day_of_year - kDayOfYearOffset;
}
// Time fields.
constexpr int hour() const { return time_parts_.tm_hour; }
constexpr void set_hour(int hour) { time_parts_.tm_hour = hour; }
constexpr int minute() const { return time_parts_.tm_min; }
constexpr void set_minute(int minute) { time_parts_.tm_min = minute; }
constexpr int second() const {
// Handle case of leap second.
return (time_parts_.tm_sec >= 60) ? 59 : time_parts_.tm_sec;
}
constexpr void set_second(int second) { time_parts_.tm_sec = second; }
private:
struct tm time_parts_ = {};
}; // class TimeStruct
} // namespace internal
} // namespace wvutil
#endif // WVCDM_UTIL_INTERNAL_TIME_STRUCT_H_

195
util/src/wv_date_time.cpp Normal file
View File

@@ -0,0 +1,195 @@
// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "wv_date_time.h"
#include <errno.h>
#include <inttypes.h>
#include <time.h>
#include <iomanip>
#include <ostream>
#include <sstream>
#include <string>
#include "log.h"
#include "wv_duration.h"
#include "wv_timestamp.h"
#define WVCDM_ALLOW_TIME_STRUCT_INCLUDE
#include "time_struct.h"
#undef WVCDM_ALLOW_TIME_STRUCT_INCLUDE
namespace wvutil {
namespace {
constexpr uint64_t kMaxCTime =
static_cast<uint64_t>(std::numeric_limits<time_t>::max());
} // namespace
// static
DateTime DateTime::FromTimestamp(const Timestamp& timestamp) {
if (!timestamp.IsSet()) {
LOGD("Timestamp is unset");
return DateTime();
}
return DateTime(timestamp);
}
// private
DateTime::DateTime(const Timestamp& timestamp) { UpdateTimestamp(timestamp); }
// private
void DateTime::UpdateTimestamp(const Timestamp& new_timestamp) {
if (!new_timestamp.IsSet()) {
Clear();
return;
}
const uint64_t epoch_seconds =
static_cast<uint64_t>(new_timestamp.epoch_seconds().count());
if (epoch_seconds > kMaxCTime) {
LOGE("Epoch seconds out of range of time_t: %zu",
static_cast<size_t>(epoch_seconds));
Clear();
return;
}
// Convert time point to time parts.
const time_t time_point = static_cast<time_t>(epoch_seconds);
struct tm tp = {};
// gmtime_r() is part POSIX.1-1996 and is safe to assume most
// standard C libraries will include it.
if (::gmtime_r(&time_point, &tp) == nullptr) {
const int saved_errno = errno;
if (saved_errno == EOVERFLOW) {
LOGE("Overflow when converting to time parts: epoch_seconds = %zu",
static_cast<size_t>(epoch_seconds));
} else {
LOGE(
"Failed to convert time point to time parts: "
"epoch_seconds = %zu, errno = %d",
static_cast<size_t>(epoch_seconds), saved_errno);
}
Clear();
return;
}
internal::TimeStruct tm;
tm.set_time_parts(tp);
timestamp_ = new_timestamp;
year_ = tm.year();
month_ = tm.month();
day_ = tm.day_of_month();
day_of_year_ = tm.day_of_year();
day_of_week_ = tm.day_of_week();
}
DateTime DateTime::operator+(const Duration& duration) const {
if (!IsSet()) {
LOGD("DateTime unset: duration = %s", duration.ToString().c_str());
return DateTime();
}
if (duration.IsZero()) return *this;
const Timestamp new_timestamp = timestamp_ + duration;
if (!new_timestamp.IsSet()) {
LOGD("Addition overflow: duration = %s", duration.ToString().c_str());
return DateTime();
}
return DateTime(new_timestamp);
}
DateTime DateTime::operator-(const Duration& duration) const {
if (!IsSet()) {
LOGD("DateTime unset: duration = %s", duration.ToString().c_str());
return DateTime();
}
if (duration.IsZero()) return *this;
const Timestamp new_timestamp = timestamp_ - duration;
if (!new_timestamp.IsSet()) {
LOGD("Subtraction overflow: duration = %s", duration.ToString().c_str());
return DateTime();
}
return DateTime(new_timestamp);
}
Duration DateTime::operator-(const DateTime& other) const {
if (!IsSet() || !other.IsSet()) {
LOGD("Cannot get duration between unset DateTime");
return Duration::Zero();
}
return timestamp_ - other.timestamp_;
}
DateTime& DateTime::operator+=(const Duration& duration) {
if (!IsSet()) {
LOGD("DateTime unset: duration = %s", duration.ToString().c_str());
return *this;
}
if (duration.IsZero()) return *this;
const Timestamp new_timestamp = timestamp_ + duration;
if (!new_timestamp.IsSet()) {
LOGD("Addition overflow: duration = %s", duration.ToString().c_str());
Clear();
return *this;
}
UpdateTimestamp(new_timestamp);
return *this;
}
DateTime& DateTime::operator-=(const Duration& duration) {
if (!IsSet()) {
LOGD("DateTime unset: duration = %s", duration.ToString().c_str());
return *this;
}
if (duration.IsZero()) return *this;
const Timestamp new_timestamp = timestamp_ - duration;
if (!new_timestamp.IsSet()) {
LOGD("Subtraction overflow: duration = %s", duration.ToString().c_str());
Clear();
return *this;
}
UpdateTimestamp(new_timestamp);
return *this;
}
bool DateTime::PrintTo(std::ostream* out) const {
if (out == nullptr) {
LOGE("Output stream is null");
return false;
}
if (!IsSet()) {
*out << "<unset>";
return true;
}
const std::ios_base::fmtflags original_flags(out->flags());
*out << std::dec << std::setfill('0');
// Date
*out << std::setw(4) << year();
*out << '-' << std::setw(2) << month();
*out << '-' << std::setw(2) << day();
// Time
*out << 'T' << std::setw(2) << hour();
*out << ':' << std::setw(2) << minute();
*out << ':' << std::setw(2) << second();
const uint32_t ms = millisecond();
if (ms != 0) {
// For default printer, only include MS if non-zero.
*out << '.' << std::setw(3) << ms;
}
*out << 'Z';
out->flags(original_flags);
return true;
}
std::string DateTime::ToString() const {
std::ostringstream out;
PrintTo(&out);
return out.str();
}
void PrintTo(const DateTime& date_time, std::ostream* out) {
if (out == nullptr) return;
date_time.PrintTo(out);
}
} // namespace wvutil

71
util/src/wv_duration.cpp Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "wv_duration.h"
#include <inttypes.h>
#include <iomanip>
#include <ostream>
#include <sstream>
#include <string>
#include "log.h"
namespace wvutil {
namespace {
// Time unit representations.
constexpr char kMillisUnit[] = "ms";
constexpr char kSecondsUnit[] = "s";
constexpr char kMinutesUnit[] = "m";
constexpr char kHoursUnit[] = "h";
constexpr char kDaysUnit[] = "d";
// Common representation of a zero duration.
constexpr char kZeroRep[] = "0s";
} // namespace
bool Duration::PrintTo(std::ostream* out) const {
if (out == nullptr) {
LOGE("Output stream is null");
return false;
}
if (IsZero()) {
*out << kZeroRep;
return true;
}
if (IsNegative()) {
*out << '-';
return GetAbsolute().PrintTo(out);
}
const std::ios_base::fmtflags original_flags(out->flags());
if (days() != 0) {
*out << days() << kDaysUnit;
}
if (hours() != 0) {
*out << hours() << kHoursUnit;
}
if (minutes() != 0) {
*out << minutes() << kMinutesUnit;
}
if (seconds() != 0) {
*out << seconds() << kSecondsUnit;
}
if (milliseconds() != 0) {
*out << milliseconds() << kMillisUnit;
}
out->flags(original_flags);
return true;
}
std::string Duration::ToString() const {
std::ostringstream out;
PrintTo(&out);
return out.str();
}
// GTest printer.
void PrintTo(const Duration& duration, std::ostream* out) {
if (out == nullptr) return;
duration.PrintTo(out);
}
} // namespace wvutil