OEMCrypto and OPK v20 prerelease initial commit
This commit is contained in:
133
util/src/buffer_reader.cpp
Normal file
133
util/src/buffer_reader.cpp
Normal 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
|
||||
981
util/src/hls_attribute_list.cpp
Normal file
981
util/src/hls_attribute_list.cpp
Normal 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
|
||||
@@ -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
244
util/src/string_utils.cpp
Normal 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
83
util/src/time_struct.h
Normal 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
195
util/src/wv_date_time.cpp
Normal 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
71
util/src/wv_duration.cpp
Normal 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
|
||||
Reference in New Issue
Block a user