OEMCrypto and OPK v20 prerelease initial commit
This commit is contained in:
78
util/include/buffer_reader.h
Normal file
78
util/include/buffer_reader.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018 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_BUFFER_READER_H_
|
||||
#define WVCDM_UTIL_BUFFER_READER_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
|
||||
namespace wvutil {
|
||||
// Annotate a function indicating the caller must examine the return value.
|
||||
// Use like:
|
||||
// int foo() WARN_UNUSED_RESULT;
|
||||
// To explicitly ignore a result, see |ignore_result()| in <base/basictypes.h>.
|
||||
#if defined(COMPILER_GCC)
|
||||
# define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#else
|
||||
# define WARN_UNUSED_RESULT
|
||||
#endif
|
||||
|
||||
class BufferReader {
|
||||
public:
|
||||
BufferReader() = delete;
|
||||
WVCDM_DISALLOW_COPY_AND_MOVE(BufferReader);
|
||||
|
||||
BufferReader(const uint8_t* buf, size_t size)
|
||||
: buf_(buf), size_(buf != nullptr ? size : 0) {}
|
||||
|
||||
// == Accessors ==
|
||||
|
||||
constexpr const uint8_t* data() const { return buf_; }
|
||||
constexpr size_t size() const { return size_; }
|
||||
constexpr size_t pos() const { return pos_; }
|
||||
|
||||
constexpr size_t BytesRemaining() const {
|
||||
return size_ >= pos_ ? size_ - pos_ : 0;
|
||||
}
|
||||
|
||||
constexpr bool HasBytes(size_t count) const {
|
||||
return count <= BytesRemaining();
|
||||
}
|
||||
constexpr bool IsEof() const { return pos_ >= size_; }
|
||||
|
||||
// Read a value from the stream, performing endian correction,
|
||||
// and advance the stream pointer.
|
||||
bool Read1(uint8_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2(uint16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2s(int16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4(uint32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4s(int32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
bool ReadString(std::string* str, size_t count) WARN_UNUSED_RESULT;
|
||||
bool ReadVec(std::vector<uint8_t>* t, size_t count) WARN_UNUSED_RESULT;
|
||||
|
||||
// These variants read a 4-byte integer of the corresponding signedness and
|
||||
// store it in the 8-byte return type.
|
||||
bool Read4Into8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4sInto8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
// Advance the stream by this many bytes.
|
||||
bool SkipBytes(size_t count) WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
const uint8_t* buf_ = nullptr;
|
||||
size_t size_ = 0;
|
||||
size_t pos_ = 0;
|
||||
|
||||
template <typename T>
|
||||
bool Read(T* t) WARN_UNUSED_RESULT;
|
||||
}; // class BufferReader
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_BUFFER_READER_H_
|
||||
@@ -9,18 +9,19 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvutil {
|
||||
#include "wv_timestamp.h"
|
||||
|
||||
namespace wvutil {
|
||||
// Provides time related information. The implementation is platform dependent.
|
||||
class Clock {
|
||||
public:
|
||||
Clock() {}
|
||||
virtual ~Clock() {}
|
||||
Clock() = default;
|
||||
virtual ~Clock() = default;
|
||||
|
||||
// Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC
|
||||
virtual int64_t GetCurrentTime();
|
||||
|
||||
virtual Timestamp GetCurrentTimestamp();
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_CLOCK_H_
|
||||
|
||||
211
util/include/hls_attribute_list.h
Normal file
211
util/include/hls_attribute_list.h
Normal file
@@ -0,0 +1,211 @@
|
||||
// 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_HLS_ATTRIBUTE_LIST_H_
|
||||
#define WVCDM_UTIL_HLS_ATTRIBUTE_LIST_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
|
||||
namespace wvutil {
|
||||
// An HLS Attribute List is a loosely defined HLS tag value
|
||||
// type representing a dictionary of attribute name-value pairs.
|
||||
// No attribute name may appear twice in a valid HLS Attribute
|
||||
// List.
|
||||
//
|
||||
// When serialized, an HLS attribute list appears as a comma
|
||||
// separated list of <name>=<value> pairs, without any line breaks,
|
||||
// and whitespace only within quoted string attribute values.
|
||||
//
|
||||
// The HLS specification defines them as context sensitive-types,
|
||||
// as the format of certain value types are ambiguous with other
|
||||
// value types (ex. something that looks like a hex sequence could
|
||||
// actually be an enum string). The exact value types of attributes
|
||||
// depends on the HLS tag.
|
||||
//
|
||||
// This implementation is intended to be context free when parsing.
|
||||
// Internally, value will be assigned a type based on their
|
||||
// appearance to the most restrictive type; however, accessing as a
|
||||
// particular type will be allowed so long as they match the format
|
||||
// that type. The only exception to this is quoted strings, which
|
||||
// are fully unambiguous with all other types.
|
||||
//
|
||||
// The standard HLS attribute list allows for UTF-8 encoded unicode
|
||||
// characters; however, for Widevine's use case, we only allow
|
||||
// basic ASCII.
|
||||
//
|
||||
// This class is based on RFC 8216 section 4.2.
|
||||
class HlsAttributeList {
|
||||
public:
|
||||
enum ValueType {
|
||||
kUnsetType = 0,
|
||||
// HLS integers are a sequence of 1 to 20 base10 digits
|
||||
// values, which must fit into an unsigned 64-bit integer.
|
||||
// Note:
|
||||
// All integers can appear as an enum string.
|
||||
// All integers can appear as floats; however, precision
|
||||
// restrictions may limit ability to parse accurately.
|
||||
kIntegerType,
|
||||
// HLS hex sequence are a sequence of 1 or more upper case
|
||||
// hexadecimal digits, with the prefix "0x" or "0X".
|
||||
// Note:
|
||||
// All hex sequences can appear as an enum string.
|
||||
// Certain hex sequences can appear as a resolution.
|
||||
kHexSequenceType,
|
||||
// HLS float and sign float are a sequence of base10 digits
|
||||
// with a possible leading negative sign ('-') and at most one
|
||||
// decimal point ('.').
|
||||
// Note:
|
||||
// All floats can appear as an enum string.
|
||||
// Certain floats can appear are an integer.
|
||||
kFloatType,
|
||||
// For simplicity, signed and unsigned floats are internally
|
||||
// treated as the same.
|
||||
kSignedFloatType = kFloatType, // Aliasing
|
||||
// HLS quoted strings are a sequence of printable characters
|
||||
// (except a double quote), or space characters, contained within
|
||||
// a pair of double quotes.
|
||||
// Note: Quote string are unambiguous with all other types.
|
||||
kQuotedStringType,
|
||||
// HLS enum strings are a sequence of any printable characters
|
||||
// (except quotes or commas) and do not contain whitespace.
|
||||
// Note:
|
||||
// Certain enum strings can appear as integers, floats, hex
|
||||
// sequences or resolutions.
|
||||
kEnumStringType,
|
||||
// HLS resolutions are a pair of base10 integers (similar to
|
||||
// integer types) separated by an 'x' character.
|
||||
// Note:
|
||||
// All resolutions can appear as enum strings
|
||||
// Certain resolutions can appear as hex sequences.
|
||||
kResolutionType,
|
||||
};
|
||||
static const char* ValueTypeToString(ValueType type);
|
||||
|
||||
// Checks if the provided |name| is valid HLS attribute name.
|
||||
static bool IsValidName(const std::string& name);
|
||||
|
||||
// Checks if the provided |value| is a valid HLS enumerated
|
||||
// string.
|
||||
static bool IsValidEnumStringValue(const std::string& value);
|
||||
// Checks if the provided |value| is an allowed content
|
||||
// of a quote string (i.e., |value| is the portion that is
|
||||
// to be contained within the double quotes).
|
||||
static bool IsValidQuotedStringValue(const std::string& value);
|
||||
|
||||
// Validator for an HLS unsigned integer representation.
|
||||
// Checks that the value representation |value_rep| conforms
|
||||
// to HLS requirements for a serialized integer.
|
||||
static bool IsValidIntegerRep(const std::string& value_rep);
|
||||
// Parses the provided |integer_rep| as an HLS integer, assign
|
||||
// the parsed integer to |value|.
|
||||
static bool ParseInteger(const std::string& integer_rep, uint64_t* value);
|
||||
|
||||
HlsAttributeList() = default;
|
||||
WVCDM_DEFAULT_COPY_AND_MOVE(HlsAttributeList);
|
||||
|
||||
// == Basic Accessors ==
|
||||
bool IsEmpty() const { return members_.empty(); }
|
||||
size_t Count() const { return members_.size(); }
|
||||
void Clear() { members_.clear(); }
|
||||
|
||||
// == Value Getters ==
|
||||
|
||||
// Returns a list of attribute names.
|
||||
std::vector<std::string> GetNames() const;
|
||||
// Returns true if the provided attribute |name| is contained
|
||||
// within list.
|
||||
bool Contains(const std::string& name) const;
|
||||
|
||||
// Checks if the provided attribute |name| could be the
|
||||
// specified |type|.
|
||||
// Certain value types are ambiguous; so they may be marked
|
||||
// internally as one type, but are still valid forms of a
|
||||
// different type.
|
||||
bool IsType(const std::string& name, ValueType type) const;
|
||||
|
||||
// Gets the attribute value for the provided attribute |name|,
|
||||
// assigning the deserialized value to the output parameter(s).
|
||||
//
|
||||
// If a type is internally stored as a different type, but the format
|
||||
// matches the requested type, it will be allowed.
|
||||
//
|
||||
// Returns true if the attribute value was successfully obtained;
|
||||
// false otherwise (attribute does not exist, or the value could
|
||||
// not be parsed as the specified type).
|
||||
bool GetEnumString(const std::string& name, std::string* value) const;
|
||||
bool GetQuotedString(const std::string& name, std::string* value) const;
|
||||
bool GetHexSequence(const std::string& name, std::string* value) const;
|
||||
bool GetHexSequence(const std::string& name,
|
||||
std::vector<uint8_t>* value) const;
|
||||
bool GetInteger(const std::string& name, uint64_t* value) const;
|
||||
bool GetFloat(const std::string& name, double* value) const;
|
||||
bool GetResolution(const std::string& name, uint64_t* width,
|
||||
uint64_t* height) const;
|
||||
|
||||
// == Value Setters ==
|
||||
|
||||
// The value setters attempt to serializes the provided value
|
||||
// into an HLS attribute value format of the type indicated
|
||||
// by the method name. These methods will overwrite any existing
|
||||
// attribute of the same name.
|
||||
// Setting will be rejected if |name| is not a valid HLS attribute
|
||||
// name, or if the provided |value| is valid (only applicable to
|
||||
// certain types).
|
||||
bool SetEnumString(const std::string& name, const std::string& value);
|
||||
bool SetQuotedString(const std::string& name, const std::string& value);
|
||||
// Note: This implementation will use lower "0x" prefix for
|
||||
// hex sequences.
|
||||
bool SetHexSequence(const std::string& name, const std::string& value);
|
||||
bool SetHexSequence(const std::string& name,
|
||||
const std::vector<uint8_t>& value);
|
||||
bool SetInteger(const std::string& name, uint64_t value);
|
||||
bool SetFloat(const std::string& name, double value);
|
||||
bool SetResolution(const std::string& name, uint64_t width, uint64_t height);
|
||||
|
||||
// Removes the specified attribute. Returns true if the attribute
|
||||
// was removed; false otherwise.
|
||||
bool Remove(const std::string& name);
|
||||
|
||||
// == Parsing / Serialization ==
|
||||
|
||||
// Attempts to parse the provided HLS Attribute List.
|
||||
// Always clears the existing contents, even if the attribute
|
||||
// list cannot be parsed.
|
||||
//
|
||||
// Internally, values are not parsed, but checked to see which
|
||||
// type they resemble. This does not restrict accessing them
|
||||
// as different types.
|
||||
//
|
||||
// Returns true if successfully parsed; false otherwise.
|
||||
bool Parse(const std::string& hls_attr_list_rep);
|
||||
|
||||
// Serializes the contents of the HLS attribute list into a
|
||||
// valid HLS attribute list form.
|
||||
std::string Serialize() const;
|
||||
bool SerializeToStream(std::ostream* out) const;
|
||||
|
||||
// Internally, values are stored in their serialized form
|
||||
// in case their value is ambiguous between two or more types.
|
||||
// The assigned ValueType is intended to be a hint for improve
|
||||
// access speeds.
|
||||
using ValueInfo =
|
||||
std::pair</* type hint */ ValueType, /* value rep */ std::string>;
|
||||
|
||||
private:
|
||||
// Internal utility to obtain the value info.
|
||||
// Silently returns null if the name does not exist.
|
||||
const ValueInfo* GetInfo(const std::string& name) const;
|
||||
|
||||
// Values in |members_| will always be validated before assigned.
|
||||
std::map</* name */ std::string, ValueInfo> members_;
|
||||
}; // class HlsAttributeList
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_HLS_ATTRIBUTE_LIST_H_
|
||||
@@ -1,65 +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.
|
||||
|
||||
#ifndef WVCDM_UTIL_RW_LOCK_H_
|
||||
#define WVCDM_UTIL_RW_LOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "disallow_copy_and_assign.h"
|
||||
#include "util_common.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
// A simple reader-writer mutex implementation that mimics the one from C++17
|
||||
class shared_mutex {
|
||||
public:
|
||||
shared_mutex() : reader_count_(0), has_writer_(false) {}
|
||||
~shared_mutex();
|
||||
|
||||
// These methods take the mutex as a reader. They do not fulfill the
|
||||
// SharedMutex requirement from the C++14 STL, but they fulfill enough of it
|
||||
// to be used with |shared_lock| below.
|
||||
void lock_shared();
|
||||
void unlock_shared();
|
||||
|
||||
// These methods take the mutex as a writer. They fulfill the Mutex
|
||||
// requirement from the C++11 STL so that this mutex can be used with
|
||||
// |std::unique_lock|.
|
||||
void lock() { lock_implementation(false); }
|
||||
bool try_lock() { return lock_implementation(true); }
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
bool lock_implementation(bool abort_if_unavailable);
|
||||
|
||||
uint32_t reader_count_;
|
||||
bool has_writer_;
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable condition_variable_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(shared_mutex);
|
||||
};
|
||||
|
||||
// A simple reader lock implementation that mimics the one from C++14
|
||||
template <typename Mutex>
|
||||
class shared_lock {
|
||||
public:
|
||||
explicit shared_lock(Mutex& lock) : lock_(&lock) { lock_->lock_shared(); }
|
||||
explicit shared_lock(Mutex* lock) : lock_(lock) { lock_->lock_shared(); }
|
||||
~shared_lock() { lock_->unlock_shared(); }
|
||||
|
||||
private:
|
||||
Mutex* lock_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(shared_lock);
|
||||
};
|
||||
|
||||
} // namespace wvutil
|
||||
|
||||
#endif // WVCDM_UTIL_RW_LOCK_H_
|
||||
89
util/include/string_utils.h
Normal file
89
util/include/string_utils.h
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2024 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_STRING_UTILS_H_
|
||||
#define WVCDM_UTIL_STRING_UTILS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Small set of simple string utilities.
|
||||
//
|
||||
// It is inspired by Python strings and Abseil. The CDM tends to not
|
||||
// be too fancy with its use of templated containers; none of the
|
||||
// Abseil container magic is not provided, nor are the fancy templated
|
||||
// configurations.
|
||||
namespace wvutil {
|
||||
|
||||
// Splits a string into several substrings based on the provided
|
||||
// delimiter.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty delimiter will split the string into each character.
|
||||
std::vector<std::string> StringSplit(const std::string& s, char delim);
|
||||
std::vector<std::string> StringSplit(const std::string& s,
|
||||
const std::string& delim);
|
||||
std::vector<std::string> StringSplit(const std::string& s, const char* delim);
|
||||
|
||||
// Joins a list of strings into a single string, using the specified
|
||||
// "glue" character(s) between tokens. Note: |glue| can be empty.
|
||||
std::string StringJoin(const std::vector<std::string>& tokens, char glue);
|
||||
std::string StringJoin(const std::vector<std::string>& tokens,
|
||||
const std::string& glue = "");
|
||||
std::string StringJoin(const std::vector<std::string>& tokens,
|
||||
const char* glue);
|
||||
|
||||
// Counts the number of instances of |needle| sequences found in the
|
||||
// |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty needle will return the length of |haystack| plus 1,
|
||||
// including for an empty string.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
size_t StringCount(const std::string& haystack, char needle);
|
||||
size_t StringCount(const std::string& haystack, const std::string& needle);
|
||||
size_t StringCount(const std::string& haystack, const char* needle);
|
||||
|
||||
// Checks if any instances of |needle| sequences found in the |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty |needle| is always present, even if |haystack| is empty.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
bool StringContains(const std::string& haystack, char needle);
|
||||
bool StringContains(const std::string& haystack, const std::string& needle);
|
||||
bool StringContains(const std::string& haystack, const char* needle);
|
||||
|
||||
// Checks if the |needle| sequences found at the beginning of |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty |needle| is always present, even if |haystack| is empty.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
bool StringStartsWith(const std::string& haystack, char needle);
|
||||
bool StringStartsWith(const std::string& haystack, const std::string& needle);
|
||||
bool StringStartsWith(const std::string& haystack, const char* needle);
|
||||
|
||||
// Checks if the |needle| sequences found at the end of |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty |needle| is always present, even if |haystack| is empty.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
bool StringEndsWith(const std::string& haystack, char needle);
|
||||
bool StringEndsWith(const std::string& haystack, const std::string& needle);
|
||||
bool StringEndsWith(const std::string& haystack, const char* needle);
|
||||
|
||||
// Removes any leading or trailing white space from the provided string.
|
||||
std::string StringTrim(const std::string& s);
|
||||
|
||||
// Checks if the vector of strings contains any instance of the
|
||||
// specified |needle|.
|
||||
//
|
||||
// Note: Unlike the other utilities, an empty |needle| is treated
|
||||
// as a value.
|
||||
bool StringVecContains(const std::vector<std::string>& haystack,
|
||||
const std::string& needle);
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_STRING_UTILS_H_
|
||||
223
util/include/wv_date_time.h
Normal file
223
util/include/wv_date_time.h
Normal file
@@ -0,0 +1,223 @@
|
||||
// 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_DATE_TIME_H_
|
||||
#define WVCDM_UTIL_DATE_TIME_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
#include "wv_duration.h"
|
||||
#include "wv_timestamp.h"
|
||||
|
||||
namespace wvutil {
|
||||
// The DateTime class represents a time point measured in UTC
|
||||
// Gregorian calendar dates and 24-hour timekeeping system.
|
||||
//
|
||||
// Internally, the time is measured in Unix Time (milliseconds
|
||||
// since January 1st, 1970 (epoch)).
|
||||
//
|
||||
// For the CDM, we are not concerned about time points before
|
||||
// January 1st, 1970; this class does not support negative epoch
|
||||
// seconds. In addition, zero is treated as a special value
|
||||
// which indicates an uninitialized time point.
|
||||
//
|
||||
// Min DateTime: 1970-01-01 00:00:00.001 (epoch ms = 1)
|
||||
// Max DateTime: 9999-12-31 23:59:59.999 (epoch ms = 253402300799999)
|
||||
//
|
||||
// Converting from Unix Time to datetime components is a potentially
|
||||
// expensive operation for their amount of utility; to reduce this
|
||||
// overhead date components are calculated once per change, and stored
|
||||
// individually.
|
||||
//
|
||||
// For testing, use the DateTime::Builder class for create DateTime
|
||||
// instances from their individual components.
|
||||
//
|
||||
// The DateTime class provides a PrintTo()/ToString() method which
|
||||
// returns an initialized DateTime in ISO 8601 format without timezone
|
||||
// indicator and with milliseconds only being printed if non-zero.
|
||||
// Use DateTime::Formatter for specific formatting needs.
|
||||
class DateTime {
|
||||
public:
|
||||
constexpr DateTime() = default; // Uninitialized DateTime.
|
||||
WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(DateTime);
|
||||
|
||||
private:
|
||||
// Note: This private section is needed here.
|
||||
// clang++ with -Wundefined-inline will complain about certain
|
||||
// inline members not being declared before use in other inline
|
||||
// methods.
|
||||
template <class ToDuration>
|
||||
constexpr ToDuration EpochFloor() const {
|
||||
return std::chrono::floor<ToDuration>(epoch_milliseconds());
|
||||
}
|
||||
|
||||
public:
|
||||
static DateTime FromTimestamp(const Timestamp& timestamp);
|
||||
|
||||
// Create a DateTime instance from Unix Time in epoch seconds.
|
||||
// Optionally allowed to include |millisecond| (0 to 999).
|
||||
// If |epoch_seconds| or |millisecond| are invalid values, then an
|
||||
// uninitlaized DateTime instance is returned.
|
||||
static DateTime FromUnixSeconds(uint64_t epoch_seconds,
|
||||
uint32_t milliseconds = 0) {
|
||||
return FromTimestamp(
|
||||
Timestamp::FromUnixSeconds(epoch_seconds, milliseconds));
|
||||
}
|
||||
static DateTime FromUnixSeconds(const Seconds& epoch_seconds,
|
||||
uint32_t milliseconds = 0) {
|
||||
return FromTimestamp(
|
||||
Timestamp::FromUnixSeconds(epoch_seconds, milliseconds));
|
||||
}
|
||||
|
||||
// Create a DateTime instance from Unix Time in epoch milliseconds.
|
||||
// If |epoch_milliseconds| is an invalid value (zero), then an
|
||||
// uninitlaized DateTime instance is returned.
|
||||
static DateTime FromUnixMilliseconds(uint64_t epoch_milliseconds) {
|
||||
return FromTimestamp(Timestamp::FromUnixMilliseconds(epoch_milliseconds));
|
||||
}
|
||||
static DateTime FromUnixMilliseconds(const Milliseconds& epoch_milliseconds) {
|
||||
return FromTimestamp(Timestamp::FromUnixMilliseconds(epoch_milliseconds));
|
||||
}
|
||||
|
||||
// Obtain the minimum and maximum representable DateTime.
|
||||
static DateTime Min() { return FromTimestamp(Timestamp::Min()); }
|
||||
static DateTime Max() { return FromTimestamp(Timestamp::Max()); }
|
||||
|
||||
constexpr const Timestamp& timestamp() const { return timestamp_; }
|
||||
|
||||
// == Epoch Accessors ==
|
||||
constexpr Seconds epoch_seconds() const { return timestamp_.epoch_seconds(); }
|
||||
constexpr Milliseconds epoch_milliseconds() const {
|
||||
return timestamp_.epoch_milliseconds();
|
||||
}
|
||||
|
||||
// == Component Accessors ==
|
||||
// Year on Gregorian calendar (1970 - 9999; 0 if unset)
|
||||
constexpr uint32_t year() const { return year_; }
|
||||
// Month of the year (January = 1, December = 12; 0 if unset)
|
||||
constexpr uint32_t month() const { return month_; }
|
||||
// Day of the month (1 - 28/29/30/31; 0 if unset)
|
||||
constexpr uint32_t day() const { return day_; }
|
||||
// Day of the year (1 - 365/366; 0 if unset)
|
||||
constexpr uint32_t day_of_year() const { return day_of_year_; }
|
||||
// Day of the week (1 (Sunday) - 7 (Saturday); 0 if unset)
|
||||
constexpr uint32_t day_of_week() const { return day_of_week_; }
|
||||
|
||||
// Time component accessors.
|
||||
// Hour of day (0 - 23; 0 if unset)
|
||||
constexpr uint32_t hour() const {
|
||||
if (!IsSet()) return 0;
|
||||
return static_cast<uint32_t>(
|
||||
(EpochFloor<Hours>() - EpochFloor<Days>()).count());
|
||||
}
|
||||
// Minute of hour (0 - 59; 0 if unset)
|
||||
constexpr uint32_t minute() const {
|
||||
if (!IsSet()) return 0;
|
||||
return static_cast<uint32_t>(
|
||||
(EpochFloor<Minutes>() - EpochFloor<Hours>()).count());
|
||||
}
|
||||
// Second of minute (0 - 59; 0 if unset)
|
||||
constexpr uint32_t second() const {
|
||||
if (!IsSet()) return 0;
|
||||
return static_cast<uint32_t>(
|
||||
(EpochFloor<Seconds>() - EpochFloor<Minutes>()).count());
|
||||
}
|
||||
// Millisecond of second (0 - 999; 0 if unset)
|
||||
constexpr uint32_t millisecond() const { return timestamp_.milliseconds(); }
|
||||
|
||||
// Checks if the DateTime instance is set.
|
||||
constexpr bool IsSet() const { return timestamp_.IsSet(); }
|
||||
constexpr explicit operator bool() const { return IsSet(); }
|
||||
|
||||
constexpr void Clear() {
|
||||
timestamp_.Clear();
|
||||
ClearComponents();
|
||||
}
|
||||
|
||||
// == Comparison Operators ==
|
||||
constexpr bool IsEqualTo(const DateTime& other) const {
|
||||
return timestamp_.IsEqualTo(other.timestamp_);
|
||||
}
|
||||
constexpr int64_t CompareTo(const DateTime& other) const {
|
||||
return timestamp_.CompareTo(other.timestamp_);
|
||||
}
|
||||
WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(DateTime);
|
||||
|
||||
constexpr bool IsEqualTo(const Timestamp& other) const {
|
||||
return timestamp_.IsEqualTo(other);
|
||||
}
|
||||
constexpr int64_t CompareTo(const Timestamp& other) const {
|
||||
return timestamp_.CompareTo(other);
|
||||
}
|
||||
WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(Timestamp);
|
||||
|
||||
// == Duration Operators ==
|
||||
// Duration-based addition/subtraction operations.
|
||||
// Returns a new DateTime instance with time point adjusted by
|
||||
// the provided Duration.
|
||||
// If current DateTime instance is unset, or if result value
|
||||
// is outside the range of valid DateTime values, then an unset
|
||||
// DateTime is returned.
|
||||
DateTime operator+(const Duration& duration) const;
|
||||
DateTime operator-(const Duration& duration) const;
|
||||
|
||||
// DateTime difference.
|
||||
// Returns the duration between the two provided DateTime
|
||||
// instances.
|
||||
// If either DateTime instance is unset, then the resulting
|
||||
// Duration is zero.
|
||||
Duration operator-(const DateTime& other) const;
|
||||
|
||||
// Duration-based increment/decrement operators.
|
||||
// Increment or decrement the DateTime by the provided Duration
|
||||
// amount.
|
||||
// If current DateTime instance is unset, then no action will
|
||||
// occur. If result value is outside the range of valid DateTime
|
||||
// values, then DateTime will be unset.
|
||||
DateTime& operator+=(const Duration& duration);
|
||||
DateTime& operator-=(const Duration& duration);
|
||||
|
||||
// For initialized DateTime instances, returns the datetime
|
||||
// in ISO 8601 format. Milliseconds are only printed
|
||||
// if non-zero.
|
||||
// An uninitialized DateTime will return an empty string.
|
||||
// Ex:
|
||||
// Without milli: 2024-01-19T14:49:13Z
|
||||
// With milli: 2024-01-19T14:49:13.507Z
|
||||
// Use DateTime::Formatter for more formatting options.
|
||||
bool PrintTo(std::ostream* out) const;
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
// Special constructor.
|
||||
explicit DateTime(const Timestamp& timestamp);
|
||||
|
||||
// Attempts to calculate subcomponents.
|
||||
// Failure to calculate subcomponents will clear |timestamp_|.
|
||||
void UpdateTimestamp(const Timestamp& new_timestamp);
|
||||
|
||||
constexpr void ClearComponents() {
|
||||
year_ = month_ = day_ = day_of_year_ = day_of_week_ = 0;
|
||||
}
|
||||
|
||||
// Source of truth for DateTime class.
|
||||
Timestamp timestamp_;
|
||||
|
||||
// Components (derived from |timestamp_|).
|
||||
uint16_t year_ = 0;
|
||||
uint8_t month_ = 0;
|
||||
uint8_t day_ = 0;
|
||||
uint16_t day_of_year_ = 0;
|
||||
uint8_t day_of_week_ = 0;
|
||||
}; // class DateTime
|
||||
|
||||
// == GTest Printer ==
|
||||
void PrintTo(const DateTime& date_time, std::ostream* out);
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_DATE_TIME_H_
|
||||
286
util/include/wv_duration.h
Normal file
286
util/include/wv_duration.h
Normal file
@@ -0,0 +1,286 @@
|
||||
// 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_WV_DURATION_H_
|
||||
#define WVCDM_UTIL_WV_DURATION_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
|
||||
namespace wvutil {
|
||||
// Wrappers around various std::chono::duration units.
|
||||
using Nanoseconds = std::chrono::nanoseconds;
|
||||
using Microseconds = std::chrono::microseconds;
|
||||
using Milliseconds = std::chrono::milliseconds;
|
||||
using Seconds = std::chrono::seconds;
|
||||
using Minutes = std::chrono::minutes;
|
||||
using Hours = std::chrono::hours;
|
||||
#if __cplusplus >= 202002L // C++20
|
||||
using Days = std::chrono::days;
|
||||
#else
|
||||
// Days is not declared in C++17, the standard C++ library
|
||||
// uses the following implementation for C++20.
|
||||
using Days = std::chrono::duration<int64_t, std::ratio<86400> >;
|
||||
#endif
|
||||
|
||||
// A high-level duration struct for storing a duration and
|
||||
// easily accessing the components.
|
||||
// A duration measures a concrete amount of time between two
|
||||
// different points in time.
|
||||
//
|
||||
// Precision of Duration is in milliseconds.
|
||||
class Duration final {
|
||||
public:
|
||||
constexpr Duration() = default;
|
||||
// Initialize duration from some kind of chrono duration type that
|
||||
// can easily convert to milliseconds.
|
||||
// Compiler will not allow irreconcilable duration types to be used
|
||||
// without caller explicitly chrono::duration_cast<milliseconds>.
|
||||
template <class Rep, class Period>
|
||||
constexpr Duration(const std::chrono::duration<Rep, Period>& total_duration)
|
||||
: total_milliseconds_(total_duration) {}
|
||||
WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(Duration);
|
||||
|
||||
private:
|
||||
// Note: This private section is needed here.
|
||||
// clang++ with -Wundefined-inline will complain about certain
|
||||
// inline members not being declared before use in other inline
|
||||
// methods.
|
||||
|
||||
// Helper function for performing truncate (round towards zero)
|
||||
// operation on chrono::duration types.
|
||||
// The standard C++ chrono library does not provide such a function.
|
||||
template <class ToDuration, class Rep, class Period>
|
||||
static constexpr ToDuration DurationTruncate(
|
||||
const std::chrono::duration<Rep, Period>& duration) {
|
||||
return duration >= std::chrono::duration<Rep, Period>::zero()
|
||||
? std::chrono::floor<ToDuration>(duration)
|
||||
: std::chrono::ceil<ToDuration>(duration);
|
||||
}
|
||||
|
||||
// Helper function for performing truncate on Duration class's
|
||||
// |total_milliseconds_|, returning it in a chrono::duration type
|
||||
// which is specified by template parameter |ToDuration|.
|
||||
template <class ToDuration>
|
||||
constexpr Duration GetTruncateInternal() const {
|
||||
return Duration(DurationTruncate<ToDuration>(total_milliseconds_));
|
||||
}
|
||||
|
||||
public:
|
||||
// == Special Initializers ==
|
||||
|
||||
static constexpr Duration Zero() { return Duration(); }
|
||||
static constexpr Duration FromMilliseconds(int64_t milliseconds) {
|
||||
return Duration(Milliseconds(milliseconds));
|
||||
}
|
||||
static constexpr Duration FromSeconds(int64_t seconds) {
|
||||
return Duration(Seconds(seconds));
|
||||
}
|
||||
|
||||
constexpr bool IsZero() const {
|
||||
return total_milliseconds_ == Milliseconds::zero();
|
||||
}
|
||||
constexpr bool IsNegative() const {
|
||||
return total_milliseconds_ < Milliseconds::zero();
|
||||
}
|
||||
constexpr bool IsPositive() const {
|
||||
return total_milliseconds_ > Milliseconds::zero();
|
||||
}
|
||||
constexpr bool IsNonNegative() const {
|
||||
return total_milliseconds_ >= Milliseconds::zero();
|
||||
}
|
||||
|
||||
// Basic accessor and conversions (rounds down).
|
||||
constexpr Milliseconds total_milliseconds() const {
|
||||
return total_milliseconds_;
|
||||
}
|
||||
constexpr Seconds total_seconds() const {
|
||||
return DurationTruncate<Seconds>(total_milliseconds_);
|
||||
}
|
||||
constexpr Minutes total_minutes() const {
|
||||
return DurationTruncate<Minutes>(total_milliseconds_);
|
||||
}
|
||||
constexpr Hours total_hours() const {
|
||||
return DurationTruncate<Hours>(total_milliseconds_);
|
||||
}
|
||||
constexpr Days total_days() const {
|
||||
return DurationTruncate<Days>(total_milliseconds_);
|
||||
}
|
||||
|
||||
// Access duration absolute components bounded within a
|
||||
// particular unit.
|
||||
// Milliseconds modulo milliseconds per-second (0-999)
|
||||
constexpr uint32_t milliseconds() const {
|
||||
if (IsNegative()) return GetAbsolute().milliseconds();
|
||||
return static_cast<uint32_t>(
|
||||
(total_milliseconds() - total_seconds()).count());
|
||||
}
|
||||
// Seconds modulo seconds per-minute (0-59)
|
||||
constexpr uint32_t seconds() const {
|
||||
if (IsNegative()) return GetAbsolute().seconds();
|
||||
return static_cast<uint32_t>((total_seconds() - total_minutes()).count());
|
||||
}
|
||||
// Minutes modulo minutes per-hour (0-59).
|
||||
constexpr uint32_t minutes() const {
|
||||
if (IsNegative()) return GetAbsolute().minutes();
|
||||
return static_cast<uint32_t>((total_minutes() - total_hours()).count());
|
||||
}
|
||||
// Hours modulo hours per-day (0-23)
|
||||
constexpr uint32_t hours() const {
|
||||
if (IsNegative()) return GetAbsolute().hours();
|
||||
return static_cast<uint32_t>((total_hours() - total_days()).count());
|
||||
}
|
||||
// Days (total).
|
||||
constexpr uint32_t days() const {
|
||||
if (IsNegative()) return GetAbsolute().days();
|
||||
return static_cast<uint32_t>(total_days().count());
|
||||
}
|
||||
|
||||
// Obtain the absolute value.
|
||||
constexpr Duration GetAbsolute() const {
|
||||
return Duration(std::chrono::abs(total_milliseconds_));
|
||||
}
|
||||
|
||||
// Obtain the truncated (math term for rounding towards zero)
|
||||
// value by units.
|
||||
constexpr Duration GetTruncateBySeconds() const {
|
||||
return GetTruncateInternal<Seconds>();
|
||||
}
|
||||
constexpr Duration GetTruncateByMinutes() const {
|
||||
return GetTruncateInternal<Minutes>();
|
||||
}
|
||||
constexpr Duration GetTruncateByHours() const {
|
||||
return GetTruncateInternal<Hours>();
|
||||
}
|
||||
constexpr Duration GetTruncateByDays() const {
|
||||
return GetTruncateInternal<Days>();
|
||||
}
|
||||
|
||||
// Comparison operators (between another Duration class)
|
||||
constexpr bool IsEqualTo(const Duration& other) const {
|
||||
return total_milliseconds_ == other.total_milliseconds_;
|
||||
}
|
||||
constexpr int64_t CompareTo(const Duration& other) const {
|
||||
return static_cast<int64_t>(total_milliseconds_.count() -
|
||||
other.total_milliseconds_.count());
|
||||
}
|
||||
WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(Duration);
|
||||
|
||||
// Comparison operators (between other duration types)
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator==(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ == other;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator!=(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ != other;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator<=(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ <= other;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator<(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ < other;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator>=(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ >= other;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr bool operator>(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return total_milliseconds_ > other;
|
||||
}
|
||||
|
||||
// Unary plus/minus operators
|
||||
constexpr Duration operator+() const { return Duration(total_milliseconds_); }
|
||||
constexpr Duration operator-() const {
|
||||
return Duration(-total_milliseconds_);
|
||||
}
|
||||
|
||||
// Add/subtract operators (between another Duration class)
|
||||
constexpr Duration operator+(const Duration& other) const {
|
||||
return Duration(total_milliseconds_ + other.total_milliseconds_);
|
||||
}
|
||||
constexpr Duration operator-(const Duration& other) const {
|
||||
return Duration(total_milliseconds_ - other.total_milliseconds_);
|
||||
}
|
||||
|
||||
// Add/subtract operators (between other duration types)
|
||||
// Note 1: This is intentionally implemented as a member function to
|
||||
// force the caller the put the Duration class first in the
|
||||
// expression.
|
||||
// Note 2: These operators are not defined for duration types that
|
||||
// smaller than chrono::milliseconds. This could cause
|
||||
// unexpected behavior as operations would be truncated.
|
||||
// Ex: (Duration(5s) + 500us + 500us) = Duration(5s)
|
||||
// Compiler will not allow such operations.
|
||||
template <class Rep, class Period>
|
||||
constexpr Duration operator+(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return Duration(total_milliseconds_ + other);
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr Duration operator-(
|
||||
const std::chrono::duration<Rep, Period>& other) const {
|
||||
return Duration(total_milliseconds_ - other);
|
||||
}
|
||||
|
||||
// Increment/decrement operators (between another Duration class)
|
||||
constexpr Duration& operator+=(const Duration& other) {
|
||||
total_milliseconds_ += other.total_milliseconds_;
|
||||
return *this;
|
||||
}
|
||||
constexpr Duration& operator-=(const Duration& other) {
|
||||
total_milliseconds_ -= other.total_milliseconds_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Increment/decrement operators (between another duration types)
|
||||
// Note: These operators are not defined for duration types that
|
||||
// smaller than chrono::milliseconds (see '+' operator
|
||||
// comment for details).
|
||||
template <class Rep, class Period>
|
||||
constexpr Duration& operator+=(
|
||||
const std::chrono::duration<Rep, Period>& other) {
|
||||
total_milliseconds_ += other;
|
||||
return *this;
|
||||
}
|
||||
template <class Rep, class Period>
|
||||
constexpr Duration& operator-=(
|
||||
const std::chrono::duration<Rep, Period>& other) {
|
||||
total_milliseconds_ -= other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// To string operator.
|
||||
// Converts the Duration to its string representation.
|
||||
// Examples:
|
||||
// Duration::FromMilliseconds(4000123).ToString()
|
||||
// ==> "1h6m40s123ms"
|
||||
// Duration::FromSeconds(4000).ToString()
|
||||
// ==> "1h6m40s"
|
||||
bool PrintTo(std::ostream* out) const;
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
// Internally, the Duration class stores durations in milliseconds.
|
||||
Milliseconds total_milliseconds_ = Milliseconds::zero();
|
||||
}; // class Duration
|
||||
|
||||
// == GTest Printer ==
|
||||
void PrintTo(const Duration& duration, std::ostream* out);
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_WV_DURATION_H_
|
||||
170
util/include/wv_timestamp.h
Normal file
170
util/include/wv_timestamp.h
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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_WV_TIMESTAMP_H_
|
||||
#define WVCDM_UTIL_WV_TIMESTAMP_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
#include "wv_duration.h"
|
||||
|
||||
namespace wvutil {
|
||||
// The Timestamp class is a light-weight representation of a
|
||||
// time point.
|
||||
//
|
||||
// Internally, the time is measured in Unix Time (milliseconds
|
||||
// since January 1st, 1970 UTC (epoch)).
|
||||
//
|
||||
// For this library, we are not concerned about time points before
|
||||
// January 1st, 1970; this class does not support negative epoch
|
||||
// seconds. In addition, zero is treated as a special value
|
||||
// which indicates an uninitialized time point.
|
||||
//
|
||||
// Min Timestamp: 1970-01-01 00:00:00.001 (epoch ms = 1)
|
||||
// Max Timestamp: 9999-12-31 23:59:59.999 (epoch ms = 253402300799999)
|
||||
class Timestamp {
|
||||
public:
|
||||
constexpr Timestamp() = default; // Defaults to "unset".
|
||||
WVCDM_CONSTEXPR_DEFAULT_COPY_AND_MOVE(Timestamp);
|
||||
|
||||
// Create a Timestamp instance from Unix Time in epoch seconds.
|
||||
// Optionally allowed to include |millisecond| (0 to 999).
|
||||
// If |epoch_seconds| or |millisecond| are invalid values, then an
|
||||
// uninitlaized Timestamp instance is returned.
|
||||
static constexpr Timestamp FromUnixSeconds(uint64_t epoch_seconds,
|
||||
uint32_t milliseconds = 0) {
|
||||
if (milliseconds > 999u) return Timestamp();
|
||||
return Timestamp(Seconds(epoch_seconds) + Milliseconds(milliseconds));
|
||||
}
|
||||
static constexpr Timestamp FromUnixSeconds(const Seconds& epoch_seconds,
|
||||
uint32_t milliseconds = 0) {
|
||||
if (milliseconds > 999u) return Timestamp();
|
||||
return Timestamp(epoch_seconds + Milliseconds(milliseconds));
|
||||
}
|
||||
|
||||
// Create a Timestamp instance from Unix Time in epoch milliseconds.
|
||||
// If |epoch_milliseconds| is an invalid value (zero), then an
|
||||
// uninitlaized Timestamp instance is returned.
|
||||
static constexpr Timestamp FromUnixMilliseconds(uint64_t epoch_milliseconds) {
|
||||
return Timestamp(Milliseconds(epoch_milliseconds));
|
||||
}
|
||||
static constexpr Timestamp FromUnixMilliseconds(
|
||||
const Milliseconds& epoch_milliseconds) {
|
||||
return Timestamp(epoch_milliseconds);
|
||||
}
|
||||
|
||||
// Obtain the minimum and maximum representable Timestamp.
|
||||
static constexpr Timestamp Min() { return Timestamp(kMinMsDuration); }
|
||||
static constexpr Timestamp Max() { return Timestamp(kMaxMsDuration); }
|
||||
|
||||
// == General Accessors ==
|
||||
|
||||
// Get the epoch seconds duration (rounding down milliseconds).
|
||||
constexpr Seconds epoch_seconds() const {
|
||||
return std::chrono::floor<Seconds>(epoch_milliseconds_);
|
||||
}
|
||||
// Get the milliseconds fraction of the epoch time (0-999).
|
||||
constexpr uint32_t milliseconds() const {
|
||||
return static_cast<uint32_t>(
|
||||
(epoch_milliseconds_ - epoch_seconds()).count());
|
||||
}
|
||||
|
||||
constexpr Milliseconds epoch_milliseconds() const {
|
||||
return epoch_milliseconds_;
|
||||
}
|
||||
|
||||
// As noted, the timestamp is considered "unset" if zero.
|
||||
constexpr bool IsSet() const {
|
||||
return epoch_milliseconds_ > Milliseconds::zero();
|
||||
}
|
||||
explicit constexpr operator bool() const { return IsSet(); }
|
||||
|
||||
constexpr void Clear() { epoch_milliseconds_ = kUnsetMsDuration; }
|
||||
|
||||
// == Comparisons ==
|
||||
|
||||
constexpr bool IsEqualTo(const Timestamp& other) const {
|
||||
return epoch_milliseconds_ == other.epoch_milliseconds_;
|
||||
}
|
||||
constexpr int64_t CompareTo(const Timestamp& other) const {
|
||||
return static_cast<int64_t>(epoch_milliseconds_.count() -
|
||||
other.epoch_milliseconds_.count());
|
||||
}
|
||||
WVCDM_DEFINE_CONSTEXPR_EQ_AND_CMP_OPERATORS(Timestamp);
|
||||
|
||||
// == Arithmetic Operations ==
|
||||
|
||||
// Duration-based addition/subtraction operations.
|
||||
// Returns a new Timestamp instance with time point adjusted by
|
||||
// the provided Duration.
|
||||
// If current Timestamp instance is unset, or if result value
|
||||
// is outside the range of valid Timestamp values, then an unset
|
||||
// Timestamp is returned.
|
||||
constexpr Timestamp operator+(const Duration& duration) const {
|
||||
if (!IsSet()) return Timestamp();
|
||||
return Timestamp(epoch_milliseconds_ + duration.total_milliseconds());
|
||||
}
|
||||
constexpr Timestamp operator-(const Duration& duration) const {
|
||||
if (!IsSet()) return Timestamp();
|
||||
return Timestamp(epoch_milliseconds_ - duration.total_milliseconds());
|
||||
}
|
||||
|
||||
// Duration-based increment/decrement operators.
|
||||
// Increment or decrement the Timestamp by the provided Duration
|
||||
// amount.
|
||||
// If current Timestamp instance is unset, then no action will
|
||||
// occur. If result value is outside the range of valid Timestamp
|
||||
// values, then Timestamp will be unset.
|
||||
constexpr Timestamp& operator+=(const Duration& duration) {
|
||||
if (!IsSet()) return *this;
|
||||
epoch_milliseconds_ =
|
||||
Normalize(epoch_milliseconds_ + duration.total_milliseconds());
|
||||
return *this;
|
||||
}
|
||||
constexpr Timestamp& operator-=(const Duration& duration) {
|
||||
if (!IsSet()) return *this;
|
||||
epoch_milliseconds_ =
|
||||
Normalize(epoch_milliseconds_ - duration.total_milliseconds());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Timestamp difference.
|
||||
// Returns the duration between the two provided Timestamp
|
||||
// instances.
|
||||
// If either Timestamp instance is unset, then the resulting
|
||||
// Duration is zero.
|
||||
constexpr Duration operator-(const Timestamp& other) const {
|
||||
if (!IsSet() || !other.IsSet()) return Duration();
|
||||
return Duration(epoch_milliseconds_ - other.epoch_milliseconds_);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const uint64_t kMinMs = 1;
|
||||
static constexpr const uint64_t kMaxMs = 253402300799999;
|
||||
static constexpr const Milliseconds kMinMsDuration = Milliseconds(kMinMs);
|
||||
static constexpr const Milliseconds kMaxMsDuration = Milliseconds(kMaxMs);
|
||||
static constexpr const Milliseconds kUnsetMsDuration = Milliseconds::zero();
|
||||
|
||||
static constexpr bool IsInRange(const Milliseconds& epoch_milliseconds) {
|
||||
return epoch_milliseconds >= kMinMsDuration &&
|
||||
epoch_milliseconds <= kMaxMsDuration;
|
||||
}
|
||||
|
||||
// Ensures the provided |epoch_milliseconds| is in range of the
|
||||
// Timestamp class; otherwise returns an unset duration value.
|
||||
static constexpr Milliseconds Normalize(
|
||||
const Milliseconds& epoch_milliseconds) {
|
||||
return IsInRange(epoch_milliseconds) ? epoch_milliseconds
|
||||
: kUnsetMsDuration;
|
||||
}
|
||||
|
||||
constexpr Timestamp(const Milliseconds& epoch_milliseconds)
|
||||
: epoch_milliseconds_(Normalize(epoch_milliseconds)) {}
|
||||
|
||||
Milliseconds epoch_milliseconds_ = kUnsetMsDuration;
|
||||
}; // class Timestamp
|
||||
} // namespace wvutil
|
||||
#endif // WVCDM_UTIL_WV_TIMESTAMP_H_
|
||||
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
|
||||
841
util/test/buffer_reader_test.cpp
Normal file
841
util/test/buffer_reader_test.cpp
Normal file
@@ -0,0 +1,841 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
class BufferReaderTest : public testing::Test {
|
||||
public:
|
||||
template <typename T>
|
||||
void WriteToBuffer(uint8_t* buffer, T v) {
|
||||
for (size_t i = 0; i < sizeof(T); ++i) {
|
||||
size_t insert_at = (sizeof(T) - i) - 1; // reverse the order of i
|
||||
size_t shift_amount = 8 * i;
|
||||
|
||||
buffer[insert_at] = static_cast<uint8_t>((v >> shift_amount) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// populate and validate data by cycling through the alphabet
|
||||
// (lower case) so that it will work for strings and raw bytes
|
||||
|
||||
void PopulateData(uint8_t* dest, size_t byte_count) {
|
||||
for (size_t i = 0; i < byte_count; i++) {
|
||||
dest[i] = static_cast<uint8_t>(i % 26 + 'a');
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateData(const uint8_t* data, size_t byte_count) {
|
||||
for (size_t i = 0; i < byte_count; i++) {
|
||||
if (data[i] != static_cast<uint8_t>(i % 26 + 'a')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateReader(const BufferReader& reader,
|
||||
const uint8_t* expected_address, size_t expected_size,
|
||||
size_t expected_position) {
|
||||
return reader.data() == expected_address &&
|
||||
reader.size() == expected_size && reader.pos() == expected_position;
|
||||
}
|
||||
|
||||
bool CheckRead1(uint8_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint8_t read;
|
||||
|
||||
return reader.Read1(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead2(uint16_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint16_t read;
|
||||
|
||||
return reader.Read2(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead2s(int16_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
int16_t read;
|
||||
|
||||
return reader.Read2s(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead4(uint32_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint32_t read;
|
||||
|
||||
return reader.Read4(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead4s(int32_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
int32_t read;
|
||||
|
||||
return reader.Read4s(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead8(uint64_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint64_t read;
|
||||
|
||||
return reader.Read8(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead8s(int64_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
int64_t read;
|
||||
|
||||
return reader.Read8s(&read) && input == read &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead4Into8(uint32_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint64_t read;
|
||||
return reader.Read4Into8(&read) && read == input &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
|
||||
bool CheckRead4sInto8s(int32_t input) {
|
||||
uint8_t raw_data[sizeof(input)];
|
||||
WriteToBuffer(raw_data, input);
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
int64_t read;
|
||||
return reader.Read4sInto8s(&read) && read == input &&
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(input));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BufferReaderTest, InitializeGoodDataAndGoodSize) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, InitializeGoodDataAndNoSize) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, InitializeNoDataNoSize) {
|
||||
BufferReader reader(nullptr, 0);
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, InitializeNoDataBadSize) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
// Buffer reader should default to a size of 0 when given
|
||||
// NULL data to ensure no reading of bad data
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, HasBytesWithBytes) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
// the reader should have enough bytes from 0 to the size of the buffer
|
||||
for (size_t i = 0; i <= sizeof(raw_data); i++) {
|
||||
ASSERT_TRUE(reader.HasBytes(i));
|
||||
}
|
||||
|
||||
ASSERT_FALSE(reader.HasBytes(sizeof(raw_data) + 1));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, HasBytesWithEmptyBuffer) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
ASSERT_FALSE(reader.HasBytes(1));
|
||||
ASSERT_TRUE(reader.HasBytes(0));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, HasBytesWithNullBuffer) {
|
||||
BufferReader reader(nullptr, 8);
|
||||
|
||||
ASSERT_FALSE(reader.HasBytes(1));
|
||||
ASSERT_TRUE(reader.HasBytes(0));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, HasBytesAfterAllRead) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
for (size_t i = 0; i < sizeof(raw_data); i++) {
|
||||
uint8_t read;
|
||||
ASSERT_TRUE(reader.Read1(&read));
|
||||
}
|
||||
|
||||
ASSERT_FALSE(reader.HasBytes(1));
|
||||
ASSERT_TRUE(reader.HasBytes(0));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(raw_data)));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read1LargeNumber) { ASSERT_TRUE(CheckRead1(0xFF)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read1SmallNumber) { ASSERT_TRUE(CheckRead1(0x0F)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read1Zero) { ASSERT_TRUE(CheckRead1(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read1WithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
uint8_t read;
|
||||
ASSERT_FALSE(reader.Read1(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read1WithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
uint8_t read;
|
||||
ASSERT_FALSE(reader.Read1(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read1WithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read1(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2LargeNumber) { ASSERT_TRUE(CheckRead2(30000)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2SmallNumber) { ASSERT_TRUE(CheckRead2(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2Zero) { ASSERT_TRUE(CheckRead2(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2WithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
uint16_t read;
|
||||
ASSERT_FALSE(reader.Read2(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2WithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
uint16_t read;
|
||||
ASSERT_FALSE(reader.Read2(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2WithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read2(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sLargePositive) {
|
||||
ASSERT_TRUE(CheckRead2s(30000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sSmallPositive) { ASSERT_TRUE(CheckRead2s(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sZero) { ASSERT_TRUE(CheckRead2s(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sSmallNegative) { ASSERT_TRUE(CheckRead2s(-10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sLargeNegative) {
|
||||
ASSERT_TRUE(CheckRead2s(-30000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sWithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
int16_t read;
|
||||
ASSERT_FALSE(reader.Read2s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sWithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
int16_t read;
|
||||
ASSERT_FALSE(reader.Read2s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read2sWithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read2s(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4LargeNumber) {
|
||||
// a number near uint32's max value
|
||||
ASSERT_TRUE(CheckRead4(2000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4SmallNumber) { ASSERT_TRUE(CheckRead4(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Zero) { ASSERT_TRUE(CheckRead4(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4WithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
uint32_t read;
|
||||
ASSERT_FALSE(reader.Read4(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4WithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
uint32_t read;
|
||||
ASSERT_FALSE(reader.Read4(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4WithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read4(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sLargePositive) {
|
||||
// a number near int32's max value
|
||||
ASSERT_TRUE(CheckRead4s(2000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sSmallPositive) { ASSERT_TRUE(CheckRead4s(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sZero) { ASSERT_TRUE(CheckRead4s(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sSmallNegative) { ASSERT_TRUE(CheckRead4s(-10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sLargeNegative) {
|
||||
// a number near int32's max negative value
|
||||
ASSERT_TRUE(CheckRead4s(-2000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sWithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
int32_t read;
|
||||
ASSERT_FALSE(reader.Read4s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sWithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
int32_t read;
|
||||
ASSERT_FALSE(reader.Read4s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sWithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read4s(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8LargeNumber) {
|
||||
// a number near uint64's max value
|
||||
ASSERT_TRUE(CheckRead8(9000000000000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8SmallNumber) { ASSERT_TRUE(CheckRead8(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read8Zero) { ASSERT_TRUE(CheckRead8(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read8WithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
uint64_t read;
|
||||
ASSERT_FALSE(reader.Read8(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8WithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
uint64_t read;
|
||||
ASSERT_FALSE(reader.Read8(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8WithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read8(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sLargePositive) {
|
||||
// a number near int64's max value
|
||||
ASSERT_TRUE(CheckRead8s(9000000000000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sSmallPositive) { ASSERT_TRUE(CheckRead8s(10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sZero) { ASSERT_TRUE(CheckRead8s(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sSmallNegative) { ASSERT_TRUE(CheckRead8s(-10)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sLargeNegative) {
|
||||
// a number near int64's max negative value
|
||||
ASSERT_TRUE(CheckRead8s(-9000000000000000000));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sWithNoData) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, 0);
|
||||
|
||||
int64_t read;
|
||||
ASSERT_FALSE(reader.Read8s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sWithNullBuffer) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
int64_t read;
|
||||
ASSERT_FALSE(reader.Read8s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read8sWithNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read8s(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadString) {
|
||||
uint8_t raw_data[5];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::string read;
|
||||
ASSERT_TRUE(reader.ReadString(&read, sizeof(raw_data)));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(read.length() == sizeof(raw_data));
|
||||
ASSERT_TRUE(ValidateData((const uint8_t*)read.c_str(), read.length()));
|
||||
|
||||
ASSERT_TRUE(
|
||||
ValidateReader(reader, raw_data, sizeof(raw_data), sizeof(raw_data)));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadStringNullSource) {
|
||||
BufferReader reader(nullptr, 5);
|
||||
|
||||
std::string read;
|
||||
ASSERT_FALSE(reader.ReadString(&read, 5));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadStringNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.ReadString(nullptr, 5));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadStringZeroCount) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::string read;
|
||||
ASSERT_TRUE(reader.ReadString(&read, 0));
|
||||
|
||||
ASSERT_TRUE(0 == read.length());
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadStringTooLarge) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::string read;
|
||||
ASSERT_FALSE(reader.ReadString(&read, sizeof(raw_data) * 2));
|
||||
|
||||
ASSERT_TRUE(0 == read.length());
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadVector) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::vector<uint8_t> read;
|
||||
|
||||
ASSERT_TRUE(reader.ReadVec(&read, 4));
|
||||
|
||||
ASSERT_TRUE(read.size() == 4);
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
ASSERT_TRUE(raw_data[i] == read[i]);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 4));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadVectorTooLarge) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::vector<uint8_t> read;
|
||||
|
||||
ASSERT_FALSE(reader.ReadVec(&read, sizeof(raw_data) * 2));
|
||||
|
||||
ASSERT_TRUE(0 == read.size());
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadVectorNullSource) {
|
||||
BufferReader reader(nullptr, 16);
|
||||
|
||||
std::vector<uint8_t> read;
|
||||
ASSERT_FALSE(reader.ReadVec(&read, 4));
|
||||
|
||||
ASSERT_TRUE(0 == read.size());
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadVectorNullReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.ReadVec(nullptr, 4));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, ReadVectorNone) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
std::vector<uint8_t> read;
|
||||
ASSERT_TRUE(reader.ReadVec(&read, 0));
|
||||
|
||||
ASSERT_TRUE(0 == read.size());
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into84Bytes) {
|
||||
ASSERT_TRUE(CheckRead4Into8(0xFFFFFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into83Bytes) {
|
||||
ASSERT_TRUE(CheckRead4Into8(0xFFFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into82Bytes) {
|
||||
ASSERT_TRUE(CheckRead4Into8(0xFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into8Zero) { ASSERT_TRUE(CheckRead4Into8(0)); }
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into8NullSource) {
|
||||
BufferReader reader(nullptr, 4);
|
||||
|
||||
uint64_t read;
|
||||
ASSERT_FALSE(reader.Read4Into8(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into8TooLittleData) {
|
||||
uint8_t raw_data[2];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
uint64_t read;
|
||||
ASSERT_FALSE(reader.Read4Into8(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4Into8NoReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read4Into8(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8s4Bytes) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(0x0FFFFFFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8s3Bytes) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(0xFFFFFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8s2Bytes) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(0xFFFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8s1Bytes) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(0xFF));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8sZero) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8sNegative) {
|
||||
ASSERT_TRUE(CheckRead4sInto8s(-100));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8sNullSource) {
|
||||
BufferReader reader(nullptr, 4);
|
||||
|
||||
int64_t read;
|
||||
ASSERT_FALSE(reader.Read4sInto8s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateReader(reader, nullptr, 0, 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8sTooLittleData) {
|
||||
uint8_t raw_data[2];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
int64_t read;
|
||||
ASSERT_FALSE(reader.Read4sInto8s(&read));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, Read4sInto8sNoReturn) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.Read4sInto8s(nullptr));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, SkipBytesNone) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_TRUE(reader.SkipBytes(0));
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, SkipBytes) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_TRUE(reader.SkipBytes(4));
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 4));
|
||||
}
|
||||
|
||||
TEST_F(BufferReaderTest, SkipBytesTooLarge) {
|
||||
uint8_t raw_data[16];
|
||||
PopulateData(raw_data, sizeof(raw_data));
|
||||
|
||||
BufferReader reader(raw_data, sizeof(raw_data));
|
||||
|
||||
ASSERT_FALSE(reader.SkipBytes(sizeof(raw_data) * 2));
|
||||
|
||||
ASSERT_TRUE(ValidateData(raw_data, sizeof(raw_data)));
|
||||
ASSERT_TRUE(ValidateReader(reader, raw_data, sizeof(raw_data), 0));
|
||||
}
|
||||
} // namespace wvutil
|
||||
1385
util/test/hls_attribute_list_unittest.cpp
Normal file
1385
util/test/hls_attribute_list_unittest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1890
util/test/string_utils_unittest.cpp
Normal file
1890
util/test/string_utils_unittest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,14 @@
|
||||
// Clock - A fake clock just for running tests. This is used when running
|
||||
// OEMCrypto unit tests. It is not used when tests include the CE CDM source
|
||||
// code because that uses the clock in cdm/test_host.cpp instead.
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "clock.h"
|
||||
#include "test_sleep.h"
|
||||
#include "wv_duration.h"
|
||||
#include "wv_timestamp.h"
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
@@ -19,16 +22,19 @@ class FakeClock : public TestSleep::CallBack {
|
||||
public:
|
||||
FakeClock() {
|
||||
auto now = std::chrono::system_clock().now();
|
||||
now_ = now.time_since_epoch() / std::chrono::milliseconds(1);
|
||||
now_ = Timestamp::FromUnixMilliseconds(
|
||||
std::chrono::floor<Milliseconds>(now.time_since_epoch()));
|
||||
TestSleep::AddCallback(this);
|
||||
}
|
||||
~FakeClock() { TestSleep::RemoveCallback(this); }
|
||||
void ElapseTime(int64_t milliseconds) { now_ += milliseconds; }
|
||||
void ElapseTime(int64_t milliseconds) override {
|
||||
now_ += Duration::FromMilliseconds(milliseconds);
|
||||
}
|
||||
|
||||
int64_t now() const { return now_; }
|
||||
Timestamp now() const { return now_; }
|
||||
|
||||
private:
|
||||
int64_t now_;
|
||||
Timestamp now_;
|
||||
};
|
||||
|
||||
FakeClock* g_fake_clock = nullptr;
|
||||
@@ -38,7 +44,12 @@ FakeClock* g_fake_clock = nullptr;
|
||||
int64_t Clock::GetCurrentTime() {
|
||||
TestSleep::SyncFakeClock();
|
||||
if (g_fake_clock == nullptr) g_fake_clock = new FakeClock();
|
||||
return g_fake_clock->now() / 1000;
|
||||
return static_cast<int64_t>(g_fake_clock->now().epoch_seconds().count());
|
||||
}
|
||||
|
||||
Timestamp Clock::GetCurrentTimestamp() {
|
||||
TestSleep::SyncFakeClock();
|
||||
if (g_fake_clock == nullptr) g_fake_clock = new FakeClock();
|
||||
return g_fake_clock->now();
|
||||
}
|
||||
} // namespace wvutil
|
||||
|
||||
464
util/test/wv_date_time_unittest.cpp
Normal file
464
util/test/wv_date_time_unittest.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
// #include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wv_duration.h"
|
||||
#include "wv_timestamp.h"
|
||||
|
||||
namespace wvutil {
|
||||
namespace test {
|
||||
namespace {
|
||||
// This is a Unix time chosen for testing.
|
||||
// Friday, April 19th, 2024 13:36:18.507
|
||||
// April 19th is the 110th day of the year when a leap year.
|
||||
constexpr uint64_t kTestUnixTimeS = 1713533778;
|
||||
constexpr uint64_t kTestUnixTimeMs = 1713533778507;
|
||||
|
||||
constexpr Seconds kTestTimeS = Seconds(kTestUnixTimeS);
|
||||
constexpr Milliseconds kTestTimeMs = Milliseconds(kTestUnixTimeMs);
|
||||
|
||||
constexpr uint32_t kTestYear = 2024;
|
||||
constexpr uint32_t kTestMonth = 4; // April
|
||||
constexpr uint32_t kTestDay = 19;
|
||||
constexpr uint32_t kTestHour = 13;
|
||||
constexpr uint32_t kTestMinute = 36;
|
||||
constexpr uint32_t kTestSecond = 18;
|
||||
constexpr uint32_t kTestMs = 507;
|
||||
constexpr uint32_t kTestDayOfWeek = 6; // Friday
|
||||
constexpr uint32_t kTestDayOfYear = 110;
|
||||
|
||||
constexpr int64_t kOneMinuteS = 60;
|
||||
constexpr int64_t kOneHourS = kOneMinuteS * 60;
|
||||
constexpr int64_t kOneDayS = kOneHourS * 24;
|
||||
constexpr int64_t kOneCommonYearS = kOneDayS * 365;
|
||||
constexpr int64_t kOneLeapYearS = kOneDayS * 366;
|
||||
|
||||
constexpr int64_t kOneSecondMs = 1000;
|
||||
constexpr int64_t kOneMinuteMs = kOneSecondMs * 60;
|
||||
constexpr int64_t kOneHourMs = kOneMinuteMs * 60;
|
||||
constexpr int64_t kOneDayMs = kOneHourMs * 24;
|
||||
|
||||
uint32_t DayOfWeekAddition(uint32_t day_of_week, uint32_t inc) {
|
||||
return ((day_of_week - 1 + inc) % 7) + 1;
|
||||
}
|
||||
|
||||
uint32_t DayOfWeekSubtraction(uint32_t day_of_week, uint32_t dec) {
|
||||
const uint32_t reverse_day_of_week = (8 - day_of_week);
|
||||
const uint32_t reverse_new_day_of_week =
|
||||
DayOfWeekAddition(reverse_day_of_week, dec);
|
||||
return (8 - reverse_new_day_of_week);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(WvDateTimeUtilTest, FromUnixSeconds) {
|
||||
const DateTime datetime = DateTime::FromUnixSeconds(kTestUnixTimeS, kTestMs);
|
||||
ASSERT_TRUE(datetime.IsSet());
|
||||
EXPECT_EQ(datetime.epoch_seconds(), kTestTimeS);
|
||||
EXPECT_EQ(datetime.epoch_milliseconds(), kTestTimeMs);
|
||||
|
||||
EXPECT_EQ(datetime.year(), kTestYear);
|
||||
EXPECT_EQ(datetime.month(), kTestMonth);
|
||||
EXPECT_EQ(datetime.day(), kTestDay);
|
||||
EXPECT_EQ(datetime.day_of_year(), kTestDayOfYear);
|
||||
EXPECT_EQ(datetime.day_of_week(), kTestDayOfWeek);
|
||||
|
||||
EXPECT_EQ(datetime.hour(), kTestHour);
|
||||
EXPECT_EQ(datetime.minute(), kTestMinute);
|
||||
EXPECT_EQ(datetime.second(), kTestSecond);
|
||||
EXPECT_EQ(datetime.millisecond(), kTestMs);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, FromUnixMilliseconds) {
|
||||
const DateTime datetime = DateTime::FromUnixMilliseconds(kTestUnixTimeMs);
|
||||
ASSERT_TRUE(datetime.IsSet());
|
||||
EXPECT_EQ(datetime.epoch_seconds(), kTestTimeS);
|
||||
EXPECT_EQ(datetime.epoch_milliseconds(), kTestTimeMs);
|
||||
|
||||
EXPECT_EQ(datetime.year(), kTestYear);
|
||||
EXPECT_EQ(datetime.month(), kTestMonth);
|
||||
EXPECT_EQ(datetime.day(), kTestDay);
|
||||
EXPECT_EQ(datetime.day_of_year(), kTestDayOfYear);
|
||||
EXPECT_EQ(datetime.day_of_week(), kTestDayOfWeek);
|
||||
|
||||
EXPECT_EQ(datetime.hour(), kTestHour);
|
||||
EXPECT_EQ(datetime.minute(), kTestMinute);
|
||||
EXPECT_EQ(datetime.second(), kTestSecond);
|
||||
EXPECT_EQ(datetime.millisecond(), kTestMs);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Min) {
|
||||
// Thursday, January 1st, 1970 00:00:00.001
|
||||
const DateTime datetime = DateTime::Min();
|
||||
ASSERT_TRUE(datetime.IsSet());
|
||||
EXPECT_EQ(datetime.epoch_seconds(), Seconds(0));
|
||||
EXPECT_EQ(datetime.epoch_milliseconds(), Milliseconds(1));
|
||||
|
||||
EXPECT_EQ(datetime.year(), 1970u);
|
||||
EXPECT_EQ(datetime.month(), 1u);
|
||||
EXPECT_EQ(datetime.day(), 1u);
|
||||
EXPECT_EQ(datetime.day_of_year(), 1u);
|
||||
EXPECT_EQ(datetime.day_of_week(), 5u);
|
||||
|
||||
EXPECT_EQ(datetime.hour(), 0u);
|
||||
EXPECT_EQ(datetime.minute(), 0u);
|
||||
EXPECT_EQ(datetime.second(), 0u);
|
||||
EXPECT_EQ(datetime.millisecond(), 1u);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Max) {
|
||||
constexpr int64_t kMaxEpochTimeS = 253402300799;
|
||||
constexpr int64_t kMaxEpochTimeMs = (kMaxEpochTimeS * 1000) + 999;
|
||||
// Friday, December 31st, 9999 23:59:59.999
|
||||
const DateTime datetime = DateTime::Max();
|
||||
ASSERT_TRUE(datetime.IsSet());
|
||||
EXPECT_EQ(datetime.epoch_seconds(), Seconds(kMaxEpochTimeS));
|
||||
EXPECT_EQ(datetime.epoch_milliseconds(), Milliseconds(kMaxEpochTimeMs));
|
||||
|
||||
EXPECT_EQ(datetime.year(), 9999u);
|
||||
EXPECT_EQ(datetime.month(), 12u);
|
||||
EXPECT_EQ(datetime.day(), 31u);
|
||||
EXPECT_EQ(datetime.day_of_year(), 365u);
|
||||
EXPECT_EQ(datetime.day_of_week(), 6u);
|
||||
|
||||
EXPECT_EQ(datetime.hour(), 23u);
|
||||
EXPECT_EQ(datetime.minute(), 59u);
|
||||
EXPECT_EQ(datetime.second(), 59u);
|
||||
EXPECT_EQ(datetime.millisecond(), 999u);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Clear) {
|
||||
DateTime datetime = DateTime::FromUnixMilliseconds(kTestUnixTimeMs);
|
||||
ASSERT_TRUE(datetime.IsSet());
|
||||
datetime.Clear();
|
||||
|
||||
EXPECT_FALSE(datetime.IsSet());
|
||||
EXPECT_EQ(datetime.year(), 0u);
|
||||
EXPECT_EQ(datetime.month(), 0u);
|
||||
EXPECT_EQ(datetime.day(), 0u);
|
||||
EXPECT_EQ(datetime.day_of_year(), 0u);
|
||||
EXPECT_EQ(datetime.day_of_week(), 0u);
|
||||
|
||||
EXPECT_EQ(datetime.hour(), 0u);
|
||||
EXPECT_EQ(datetime.minute(), 0u);
|
||||
EXPECT_EQ(datetime.second(), 0u);
|
||||
EXPECT_EQ(datetime.millisecond(), 0u);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Comparison) {
|
||||
const DateTime datetime_a = DateTime::Min();
|
||||
const DateTime datetime_b =
|
||||
DateTime::FromUnixMilliseconds(kTestUnixTimeMs - kOneDayMs);
|
||||
const DateTime datetime_c = DateTime::FromUnixMilliseconds(kTestUnixTimeMs);
|
||||
const DateTime datetime_d =
|
||||
DateTime::FromUnixMilliseconds(kTestUnixTimeMs + 1);
|
||||
const DateTime datetime_e = DateTime::Max();
|
||||
|
||||
// Equality
|
||||
EXPECT_EQ(datetime_a, datetime_a);
|
||||
EXPECT_EQ(datetime_b, datetime_b);
|
||||
EXPECT_EQ(datetime_c, datetime_c);
|
||||
EXPECT_EQ(datetime_d, datetime_d);
|
||||
EXPECT_EQ(datetime_e, datetime_e);
|
||||
|
||||
// Inequality.
|
||||
EXPECT_NE(datetime_a, datetime_b);
|
||||
EXPECT_NE(datetime_a, datetime_c);
|
||||
EXPECT_NE(datetime_a, datetime_d);
|
||||
EXPECT_NE(datetime_a, datetime_e);
|
||||
EXPECT_NE(datetime_b, datetime_c);
|
||||
EXPECT_NE(datetime_b, datetime_d);
|
||||
EXPECT_NE(datetime_b, datetime_e);
|
||||
EXPECT_NE(datetime_c, datetime_d);
|
||||
EXPECT_NE(datetime_c, datetime_e);
|
||||
EXPECT_NE(datetime_d, datetime_e);
|
||||
|
||||
// Less than
|
||||
EXPECT_LT(datetime_a, datetime_b);
|
||||
EXPECT_LT(datetime_a, datetime_c);
|
||||
EXPECT_LT(datetime_a, datetime_d);
|
||||
EXPECT_LT(datetime_a, datetime_e);
|
||||
EXPECT_LT(datetime_b, datetime_c);
|
||||
EXPECT_LT(datetime_b, datetime_d);
|
||||
EXPECT_LT(datetime_b, datetime_e);
|
||||
EXPECT_LT(datetime_c, datetime_d);
|
||||
EXPECT_LT(datetime_c, datetime_e);
|
||||
EXPECT_LT(datetime_d, datetime_e);
|
||||
|
||||
// Less than or equal.
|
||||
EXPECT_LE(datetime_a, datetime_a);
|
||||
EXPECT_LE(datetime_a, datetime_b);
|
||||
EXPECT_LE(datetime_a, datetime_c);
|
||||
EXPECT_LE(datetime_a, datetime_d);
|
||||
EXPECT_LE(datetime_a, datetime_e);
|
||||
EXPECT_LE(datetime_b, datetime_b);
|
||||
EXPECT_LE(datetime_b, datetime_c);
|
||||
EXPECT_LE(datetime_b, datetime_d);
|
||||
EXPECT_LE(datetime_b, datetime_e);
|
||||
EXPECT_LE(datetime_c, datetime_c);
|
||||
EXPECT_LE(datetime_c, datetime_d);
|
||||
EXPECT_LE(datetime_c, datetime_e);
|
||||
EXPECT_LE(datetime_d, datetime_d);
|
||||
EXPECT_LE(datetime_d, datetime_e);
|
||||
EXPECT_LE(datetime_e, datetime_e);
|
||||
|
||||
// Greater than
|
||||
EXPECT_GT(datetime_b, datetime_a);
|
||||
EXPECT_GT(datetime_c, datetime_a);
|
||||
EXPECT_GT(datetime_d, datetime_a);
|
||||
EXPECT_GT(datetime_e, datetime_a);
|
||||
EXPECT_GT(datetime_c, datetime_b);
|
||||
EXPECT_GT(datetime_d, datetime_b);
|
||||
EXPECT_GT(datetime_e, datetime_b);
|
||||
EXPECT_GT(datetime_d, datetime_c);
|
||||
EXPECT_GT(datetime_e, datetime_c);
|
||||
EXPECT_GT(datetime_e, datetime_d);
|
||||
|
||||
// Greater than or equal.
|
||||
EXPECT_GE(datetime_a, datetime_a);
|
||||
EXPECT_GE(datetime_b, datetime_a);
|
||||
EXPECT_GE(datetime_c, datetime_a);
|
||||
EXPECT_GE(datetime_d, datetime_a);
|
||||
EXPECT_GE(datetime_e, datetime_a);
|
||||
EXPECT_GE(datetime_b, datetime_b);
|
||||
EXPECT_GE(datetime_c, datetime_b);
|
||||
EXPECT_GE(datetime_d, datetime_b);
|
||||
EXPECT_GE(datetime_e, datetime_b);
|
||||
EXPECT_GE(datetime_c, datetime_c);
|
||||
EXPECT_GE(datetime_d, datetime_c);
|
||||
EXPECT_GE(datetime_e, datetime_c);
|
||||
EXPECT_GE(datetime_d, datetime_d);
|
||||
EXPECT_GE(datetime_e, datetime_d);
|
||||
EXPECT_GE(datetime_e, datetime_e);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Addition_WithDuration) {
|
||||
const DateTime start = DateTime::FromUnixMilliseconds(kTestUnixTimeMs);
|
||||
ASSERT_TRUE(start.IsSet());
|
||||
|
||||
const Duration one_day = Duration::FromMilliseconds(kOneDayMs);
|
||||
const DateTime tomorrow = start + one_day;
|
||||
ASSERT_TRUE(tomorrow.IsSet());
|
||||
|
||||
EXPECT_EQ(tomorrow.epoch_seconds(), kTestTimeS + Seconds(kOneDayS));
|
||||
EXPECT_EQ(tomorrow.epoch_milliseconds(),
|
||||
kTestTimeMs + Milliseconds(kOneDayMs));
|
||||
|
||||
EXPECT_EQ(tomorrow.year(), kTestYear);
|
||||
EXPECT_EQ(tomorrow.month(), kTestMonth);
|
||||
EXPECT_EQ(tomorrow.day(), kTestDay + 1);
|
||||
EXPECT_EQ(tomorrow.day_of_year(), kTestDayOfYear + 1);
|
||||
EXPECT_EQ(tomorrow.day_of_week(), DayOfWeekAddition(kTestDayOfWeek, 1));
|
||||
|
||||
EXPECT_EQ(tomorrow.hour(), kTestHour);
|
||||
EXPECT_EQ(tomorrow.minute(), kTestMinute);
|
||||
EXPECT_EQ(tomorrow.second(), kTestSecond);
|
||||
EXPECT_EQ(tomorrow.millisecond(), kTestMs);
|
||||
|
||||
// Note: This is 30 days for April to May.
|
||||
const Duration one_month = Duration::FromSeconds(kOneDayS * 30);
|
||||
const DateTime next_month = start + one_month;
|
||||
EXPECT_EQ(next_month.year(), kTestYear);
|
||||
EXPECT_EQ(next_month.month(), kTestMonth + 1);
|
||||
EXPECT_EQ(next_month.day(), kTestDay);
|
||||
EXPECT_EQ(next_month.day_of_year(), kTestDayOfYear + 30);
|
||||
EXPECT_EQ(next_month.day_of_week(), DayOfWeekAddition(kTestDayOfWeek, 30));
|
||||
|
||||
EXPECT_EQ(next_month.hour(), kTestHour);
|
||||
EXPECT_EQ(next_month.minute(), kTestMinute);
|
||||
EXPECT_EQ(next_month.second(), kTestSecond);
|
||||
EXPECT_EQ(next_month.millisecond(), kTestMs);
|
||||
|
||||
// Note: This should roll over a day.
|
||||
const uint32_t day_inc_11h = (kTestHour + 11) / 24;
|
||||
const Duration eleven_hours = Duration::FromSeconds(kOneHourS * 11);
|
||||
const DateTime hours_later = start + eleven_hours;
|
||||
EXPECT_EQ(hours_later.year(), kTestYear);
|
||||
EXPECT_EQ(hours_later.month(), kTestMonth);
|
||||
EXPECT_EQ(hours_later.day(), kTestDay + day_inc_11h);
|
||||
EXPECT_EQ(hours_later.day_of_year(), kTestDayOfYear + day_inc_11h);
|
||||
EXPECT_EQ(hours_later.day_of_week(),
|
||||
DayOfWeekAddition(kTestDayOfWeek, day_inc_11h));
|
||||
|
||||
const uint32_t new_hour_11h = (kTestHour + 11) % 24;
|
||||
EXPECT_EQ(hours_later.hour(), new_hour_11h);
|
||||
EXPECT_EQ(hours_later.minute(), kTestMinute);
|
||||
EXPECT_EQ(hours_later.second(), kTestSecond);
|
||||
EXPECT_EQ(hours_later.millisecond(), kTestMs);
|
||||
|
||||
const Duration five_years =
|
||||
Duration::FromSeconds(kOneCommonYearS * 4 + kOneLeapYearS);
|
||||
const DateTime fewer_years_later = start + five_years;
|
||||
EXPECT_EQ(fewer_years_later.year(), kTestYear + 5);
|
||||
EXPECT_EQ(fewer_years_later.month(), kTestMonth);
|
||||
EXPECT_EQ(fewer_years_later.day(), kTestDay);
|
||||
|
||||
EXPECT_EQ(fewer_years_later.hour(), kTestHour);
|
||||
EXPECT_EQ(fewer_years_later.minute(), kTestMinute);
|
||||
EXPECT_EQ(fewer_years_later.second(), kTestSecond);
|
||||
EXPECT_EQ(fewer_years_later.millisecond(), kTestMs);
|
||||
|
||||
const DateTime same_time = start + Duration::Zero();
|
||||
EXPECT_EQ(start, same_time);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Addition_WithDuration_Invalid) {
|
||||
// Addition with an unset date time is not allowed.
|
||||
const DateTime unset;
|
||||
ASSERT_FALSE(unset.IsSet());
|
||||
const Duration one_month = Duration::FromSeconds(kOneDayS * 30);
|
||||
DateTime result = unset + one_month;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
// Addition that causes overflow is not allowed.
|
||||
result = DateTime::Max() + Duration::FromMilliseconds(1);
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
result = DateTime::Min() + Duration::FromSeconds(kOneCommonYearS * 10000);
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Subtraction_WithDuration) {
|
||||
const DateTime start = DateTime::FromUnixMilliseconds(kTestUnixTimeMs);
|
||||
ASSERT_TRUE(start.IsSet());
|
||||
|
||||
const Duration one_day = Duration::FromMilliseconds(kOneDayMs);
|
||||
const DateTime yesterday = start - one_day;
|
||||
ASSERT_TRUE(yesterday.IsSet());
|
||||
|
||||
EXPECT_EQ(yesterday.epoch_seconds(), Seconds(kTestUnixTimeS - kOneDayS));
|
||||
EXPECT_EQ(yesterday.epoch_milliseconds(),
|
||||
Milliseconds(kTestUnixTimeMs - kOneDayMs));
|
||||
|
||||
EXPECT_EQ(yesterday.year(), kTestYear);
|
||||
EXPECT_EQ(yesterday.month(), kTestMonth);
|
||||
EXPECT_EQ(yesterday.day(), kTestDay - 1);
|
||||
EXPECT_EQ(yesterday.day_of_year(), kTestDayOfYear - 1);
|
||||
EXPECT_EQ(yesterday.day_of_week(), DayOfWeekSubtraction(kTestDayOfWeek, 1));
|
||||
|
||||
EXPECT_EQ(yesterday.hour(), kTestHour);
|
||||
EXPECT_EQ(yesterday.minute(), kTestMinute);
|
||||
EXPECT_EQ(yesterday.second(), kTestSecond);
|
||||
EXPECT_EQ(yesterday.millisecond(), kTestMs);
|
||||
|
||||
// Note: This is a 31 days for March.
|
||||
const Duration one_month = Duration::FromSeconds(kOneDayS * 31);
|
||||
const DateTime previous_month = start - one_month;
|
||||
EXPECT_EQ(previous_month.year(), kTestYear);
|
||||
EXPECT_EQ(previous_month.month(), kTestMonth - 1);
|
||||
EXPECT_EQ(previous_month.day(), kTestDay);
|
||||
EXPECT_EQ(previous_month.day_of_year(), kTestDayOfYear - 31);
|
||||
EXPECT_EQ(previous_month.day_of_week(),
|
||||
DayOfWeekSubtraction(kTestDayOfWeek, 31));
|
||||
|
||||
EXPECT_EQ(previous_month.hour(), kTestHour);
|
||||
EXPECT_EQ(previous_month.minute(), kTestMinute);
|
||||
EXPECT_EQ(previous_month.second(), kTestSecond);
|
||||
EXPECT_EQ(previous_month.millisecond(), kTestMs);
|
||||
|
||||
const uint32_t day_dec_14h = (kTestHour < 14) ? 1 : 0;
|
||||
const Duration fourteen_hours = Duration::FromSeconds(kOneHourS * 14);
|
||||
const DateTime hours_earlier = start - fourteen_hours;
|
||||
EXPECT_EQ(yesterday.year(), kTestYear);
|
||||
EXPECT_EQ(yesterday.month(), kTestMonth);
|
||||
EXPECT_EQ(yesterday.day(), kTestDay - day_dec_14h);
|
||||
EXPECT_EQ(yesterday.day_of_year(), kTestDayOfYear - day_dec_14h);
|
||||
EXPECT_EQ(yesterday.day_of_week(),
|
||||
DayOfWeekSubtraction(kTestDayOfWeek, day_dec_14h));
|
||||
|
||||
const uint32_t new_hour_14h =
|
||||
(kTestHour < 14) ? (kTestHour + 10) : kTestHour - 14;
|
||||
EXPECT_EQ(hours_earlier.hour(), new_hour_14h);
|
||||
EXPECT_EQ(hours_earlier.minute(), kTestMinute);
|
||||
EXPECT_EQ(hours_earlier.second(), kTestSecond);
|
||||
EXPECT_EQ(hours_earlier.millisecond(), kTestMs);
|
||||
|
||||
const Duration five_years =
|
||||
Duration::FromSeconds(kOneCommonYearS * 3 + kOneLeapYearS * 2);
|
||||
const DateTime few_years_earlier = start - five_years;
|
||||
EXPECT_EQ(few_years_earlier.year(), kTestYear - 5);
|
||||
EXPECT_EQ(few_years_earlier.month(), kTestMonth);
|
||||
EXPECT_EQ(few_years_earlier.day(), kTestDay);
|
||||
|
||||
EXPECT_EQ(few_years_earlier.hour(), kTestHour);
|
||||
EXPECT_EQ(few_years_earlier.minute(), kTestMinute);
|
||||
EXPECT_EQ(few_years_earlier.second(), kTestSecond);
|
||||
EXPECT_EQ(few_years_earlier.millisecond(), kTestMs);
|
||||
|
||||
const DateTime same_time = start - Duration::Zero();
|
||||
EXPECT_EQ(start, same_time);
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Subtraction_WithDuration_Invalid) {
|
||||
// Subtract with an unset date time is not allowed.
|
||||
const DateTime unset;
|
||||
ASSERT_FALSE(unset.IsSet());
|
||||
const Duration one_month = Duration::FromSeconds(kOneDayS * 30);
|
||||
DateTime result = unset - one_month;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
// Subtract that causes overflow is not allowed.
|
||||
result = DateTime::Min() - Duration::FromMilliseconds(1);
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
result = DateTime::Max() - Duration::FromSeconds(kOneCommonYearS * 10000);
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Difference) {
|
||||
const Duration expected_diff =
|
||||
Duration(Hours(15) + Minutes(6) + Seconds(30) + Milliseconds(123));
|
||||
const DateTime datetime_a =
|
||||
DateTime::FromUnixSeconds(kTestUnixTimeS, kTestMs);
|
||||
const DateTime datetime_b = DateTime::FromUnixMilliseconds(
|
||||
datetime_a.epoch_milliseconds() + expected_diff.total_milliseconds());
|
||||
|
||||
const Duration diff_ab = datetime_b - datetime_a;
|
||||
EXPECT_EQ(diff_ab, expected_diff);
|
||||
|
||||
const Duration diff_ba = datetime_a - datetime_b;
|
||||
EXPECT_EQ(diff_ba, -expected_diff);
|
||||
|
||||
// Very big diff
|
||||
const Duration diff_min_max = DateTime::Max() - DateTime::Min();
|
||||
EXPECT_TRUE(diff_min_max.IsPositive());
|
||||
const Duration diff_max_min = DateTime::Min() - DateTime::Max();
|
||||
EXPECT_TRUE(diff_max_min.IsNegative());
|
||||
EXPECT_EQ(diff_min_max, -diff_max_min);
|
||||
|
||||
// No difference.
|
||||
const Duration diff_aa = datetime_a - datetime_a;
|
||||
EXPECT_TRUE(diff_aa.IsZero());
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, Difference_Invalid) {
|
||||
const DateTime datetime = DateTime::FromUnixSeconds(kTestUnixTimeS, kTestMs);
|
||||
const DateTime unset;
|
||||
|
||||
Duration diff = datetime - unset;
|
||||
EXPECT_TRUE(diff.IsZero());
|
||||
|
||||
diff = unset - datetime;
|
||||
EXPECT_TRUE(diff.IsZero());
|
||||
}
|
||||
|
||||
TEST(WvDateTimeUtilTest, ToString) {
|
||||
// Default should ISO date times with short timezones, and milliseconds
|
||||
// only printed if non-zero.
|
||||
DateTime datetime = DateTime::FromUnixSeconds(kTestUnixTimeS, kTestMs);
|
||||
EXPECT_EQ(datetime.ToString(), "2024-04-19T13:36:18.507Z");
|
||||
datetime = DateTime::FromUnixSeconds(kTestUnixTimeS);
|
||||
EXPECT_EQ(datetime.ToString(), "2024-04-19T13:36:18Z");
|
||||
|
||||
EXPECT_EQ(DateTime::Min().ToString(), "1970-01-01T00:00:00.001Z");
|
||||
EXPECT_EQ(DateTime::Max().ToString(), "9999-12-31T23:59:59.999Z");
|
||||
|
||||
EXPECT_EQ(DateTime().ToString(), "<unset>");
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace wvutil
|
||||
1158
util/test/wv_duration_unittest.cpp
Normal file
1158
util/test/wv_duration_unittest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
300
util/test/wv_timestamp_unittest.cpp
Normal file
300
util/test/wv_timestamp_unittest.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
// 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_timestamp.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "wv_duration.h"
|
||||
|
||||
namespace wvutil {
|
||||
namespace test {
|
||||
namespace {
|
||||
constexpr uint64_t kRawValidEpochSeconds = 1733526346;
|
||||
constexpr Seconds kValidEpochSeconds = Seconds(kRawValidEpochSeconds);
|
||||
|
||||
constexpr uint64_t kRawOutOfRangeEpochSeconds = 253402300800; // Year 10000
|
||||
constexpr Seconds kOutOfRangeEpochSeconds = Seconds(kRawOutOfRangeEpochSeconds);
|
||||
|
||||
constexpr uint64_t kOneS = 1;
|
||||
constexpr uint64_t kOneMinuteS = kOneS * 60;
|
||||
constexpr uint64_t kOneHourS = kOneMinuteS * 60;
|
||||
constexpr uint64_t kOneDayS = kOneHourS * 24;
|
||||
} // namespace
|
||||
|
||||
TEST(WvTimestampUtilTest, DefaultConstructor) {
|
||||
const Timestamp ts;
|
||||
EXPECT_FALSE(ts.IsSet());
|
||||
EXPECT_EQ(ts.epoch_milliseconds(), Milliseconds::zero());
|
||||
EXPECT_EQ(ts.epoch_seconds(), Seconds::zero());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, MinMax) {
|
||||
const Timestamp max = Timestamp::Max();
|
||||
EXPECT_TRUE(max.IsSet());
|
||||
const Timestamp min = Timestamp::Min();
|
||||
EXPECT_TRUE(min.IsSet());
|
||||
EXPECT_LE(min, max);
|
||||
EXPECT_GE(max, min);
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixSeconds) {
|
||||
const Milliseconds epoch_milliseconds =
|
||||
std::chrono::duration_cast<Milliseconds>(kValidEpochSeconds);
|
||||
|
||||
const Timestamp ts = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
EXPECT_TRUE(ts.IsSet());
|
||||
|
||||
EXPECT_EQ(ts.epoch_seconds(), kValidEpochSeconds);
|
||||
EXPECT_EQ(ts.epoch_milliseconds(), epoch_milliseconds);
|
||||
EXPECT_EQ(ts.milliseconds(), 0u);
|
||||
|
||||
EXPECT_EQ(ts, Timestamp::FromUnixSeconds(kRawValidEpochSeconds));
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixSeconds_WithMs) {
|
||||
constexpr uint32_t raw_milliseconds = 123;
|
||||
const Milliseconds epoch_milliseconds =
|
||||
kValidEpochSeconds + Milliseconds(raw_milliseconds);
|
||||
|
||||
const Timestamp ts =
|
||||
Timestamp::FromUnixSeconds(kValidEpochSeconds, raw_milliseconds);
|
||||
EXPECT_TRUE(ts.IsSet());
|
||||
|
||||
EXPECT_EQ(ts.epoch_seconds(), kValidEpochSeconds);
|
||||
EXPECT_EQ(ts.epoch_milliseconds(), epoch_milliseconds);
|
||||
EXPECT_EQ(ts.milliseconds(), raw_milliseconds);
|
||||
|
||||
EXPECT_EQ(
|
||||
ts, Timestamp::FromUnixSeconds(kRawValidEpochSeconds, raw_milliseconds));
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixSeconds_SecondsOutOfRange) {
|
||||
EXPECT_FALSE(Timestamp::FromUnixSeconds(kRawOutOfRangeEpochSeconds).IsSet());
|
||||
EXPECT_FALSE(Timestamp::FromUnixSeconds(kOutOfRangeEpochSeconds).IsSet());
|
||||
|
||||
constexpr Seconds negative_epoch_seconds = Seconds(-1);
|
||||
EXPECT_FALSE(Timestamp::FromUnixSeconds(negative_epoch_seconds).IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixSeconds_MsOutOfRange) {
|
||||
EXPECT_FALSE(Timestamp::FromUnixSeconds(kValidEpochSeconds, 1000).IsSet());
|
||||
EXPECT_FALSE(Timestamp::FromUnixSeconds(kRawValidEpochSeconds, 1000).IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixMilliseconds) {
|
||||
constexpr uint32_t raw_milliseconds = 123;
|
||||
constexpr uint64_t raw_total_milliseconds =
|
||||
(kRawValidEpochSeconds * 1000) + raw_milliseconds;
|
||||
constexpr Milliseconds epoch_milliseconds =
|
||||
Milliseconds(raw_total_milliseconds);
|
||||
|
||||
constexpr Timestamp ts = Timestamp::FromUnixMilliseconds(epoch_milliseconds);
|
||||
|
||||
EXPECT_TRUE(ts.IsSet());
|
||||
|
||||
EXPECT_EQ(ts.epoch_seconds(), kValidEpochSeconds);
|
||||
EXPECT_EQ(ts.epoch_milliseconds(), epoch_milliseconds);
|
||||
EXPECT_EQ(ts.milliseconds(), raw_milliseconds);
|
||||
|
||||
EXPECT_EQ(ts, Timestamp::FromUnixMilliseconds(raw_total_milliseconds));
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, FromUnixMilliseconds_OutOfRange) {
|
||||
constexpr uint64_t raw_total_milliseconds = kRawOutOfRangeEpochSeconds * 1000;
|
||||
constexpr Milliseconds epoch_milliseconds =
|
||||
Milliseconds(raw_total_milliseconds);
|
||||
EXPECT_FALSE(Timestamp::FromUnixMilliseconds(raw_total_milliseconds).IsSet());
|
||||
EXPECT_FALSE(Timestamp::FromUnixMilliseconds(epoch_milliseconds).IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Clear) {
|
||||
Timestamp ts = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
EXPECT_TRUE(ts.IsSet());
|
||||
ts.Clear();
|
||||
EXPECT_FALSE(ts.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Comparison) {
|
||||
constexpr Timestamp ts_a = Timestamp::Min();
|
||||
constexpr Timestamp ts_b =
|
||||
Timestamp::FromUnixSeconds(kRawValidEpochSeconds - kOneDayS);
|
||||
constexpr Timestamp ts_c = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
constexpr Timestamp ts_d =
|
||||
Timestamp::FromUnixSeconds(kValidEpochSeconds, 123);
|
||||
constexpr Timestamp ts_e = Timestamp::Max();
|
||||
|
||||
// Equality
|
||||
EXPECT_EQ(ts_a, ts_a);
|
||||
EXPECT_EQ(ts_b, ts_b);
|
||||
EXPECT_EQ(ts_c, ts_c);
|
||||
EXPECT_EQ(ts_d, ts_d);
|
||||
EXPECT_EQ(ts_e, ts_e);
|
||||
|
||||
// Inequality.
|
||||
EXPECT_NE(ts_a, ts_b);
|
||||
EXPECT_NE(ts_a, ts_c);
|
||||
EXPECT_NE(ts_a, ts_d);
|
||||
EXPECT_NE(ts_a, ts_e);
|
||||
EXPECT_NE(ts_b, ts_c);
|
||||
EXPECT_NE(ts_b, ts_d);
|
||||
EXPECT_NE(ts_b, ts_e);
|
||||
EXPECT_NE(ts_c, ts_d);
|
||||
EXPECT_NE(ts_c, ts_e);
|
||||
EXPECT_NE(ts_d, ts_e);
|
||||
|
||||
// Less than
|
||||
EXPECT_LT(ts_a, ts_b);
|
||||
EXPECT_LT(ts_a, ts_c);
|
||||
EXPECT_LT(ts_a, ts_d);
|
||||
EXPECT_LT(ts_a, ts_e);
|
||||
EXPECT_LT(ts_b, ts_c);
|
||||
EXPECT_LT(ts_b, ts_d);
|
||||
EXPECT_LT(ts_b, ts_e);
|
||||
EXPECT_LT(ts_c, ts_d);
|
||||
EXPECT_LT(ts_c, ts_e);
|
||||
EXPECT_LT(ts_d, ts_e);
|
||||
|
||||
// Less than or equal.
|
||||
EXPECT_LE(ts_a, ts_a);
|
||||
EXPECT_LE(ts_a, ts_b);
|
||||
EXPECT_LE(ts_a, ts_c);
|
||||
EXPECT_LE(ts_a, ts_d);
|
||||
EXPECT_LE(ts_a, ts_e);
|
||||
EXPECT_LE(ts_b, ts_b);
|
||||
EXPECT_LE(ts_b, ts_c);
|
||||
EXPECT_LE(ts_b, ts_d);
|
||||
EXPECT_LE(ts_b, ts_e);
|
||||
EXPECT_LE(ts_c, ts_c);
|
||||
EXPECT_LE(ts_c, ts_d);
|
||||
EXPECT_LE(ts_c, ts_e);
|
||||
EXPECT_LE(ts_d, ts_d);
|
||||
EXPECT_LE(ts_d, ts_e);
|
||||
EXPECT_LE(ts_e, ts_e);
|
||||
|
||||
// Greater than
|
||||
EXPECT_GT(ts_b, ts_a);
|
||||
EXPECT_GT(ts_c, ts_a);
|
||||
EXPECT_GT(ts_d, ts_a);
|
||||
EXPECT_GT(ts_e, ts_a);
|
||||
EXPECT_GT(ts_c, ts_b);
|
||||
EXPECT_GT(ts_d, ts_b);
|
||||
EXPECT_GT(ts_e, ts_b);
|
||||
EXPECT_GT(ts_d, ts_c);
|
||||
EXPECT_GT(ts_e, ts_c);
|
||||
EXPECT_GT(ts_e, ts_d);
|
||||
|
||||
// Greater than or equal.
|
||||
EXPECT_GE(ts_a, ts_a);
|
||||
EXPECT_GE(ts_b, ts_a);
|
||||
EXPECT_GE(ts_c, ts_a);
|
||||
EXPECT_GE(ts_d, ts_a);
|
||||
EXPECT_GE(ts_e, ts_a);
|
||||
EXPECT_GE(ts_b, ts_b);
|
||||
EXPECT_GE(ts_c, ts_b);
|
||||
EXPECT_GE(ts_d, ts_b);
|
||||
EXPECT_GE(ts_e, ts_b);
|
||||
EXPECT_GE(ts_c, ts_c);
|
||||
EXPECT_GE(ts_d, ts_c);
|
||||
EXPECT_GE(ts_e, ts_c);
|
||||
EXPECT_GE(ts_d, ts_d);
|
||||
EXPECT_GE(ts_e, ts_d);
|
||||
EXPECT_GE(ts_e, ts_e);
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Addition_WithDuration) {
|
||||
constexpr Timestamp start = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
constexpr Timestamp tomorrow = start + one_day;
|
||||
constexpr decltype(tomorrow.epoch_seconds().count()) expected_second_count =
|
||||
kRawValidEpochSeconds + kOneDayS;
|
||||
EXPECT_EQ(tomorrow.epoch_seconds().count(), expected_second_count);
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Addition_WithDuration_Unset) {
|
||||
constexpr Timestamp unset;
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
constexpr Timestamp result = unset + one_day;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Addition_WithDuration_Overflow) {
|
||||
Timestamp start = Timestamp::Max();
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
Timestamp result = start + one_day;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
start = Timestamp::Min();
|
||||
constexpr Duration very_long_duration =
|
||||
Duration::FromSeconds(kOneDayS * 365 * 10000);
|
||||
result = start + very_long_duration;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Subtraction_WithDuration) {
|
||||
constexpr Timestamp start = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
constexpr Timestamp yesterday = start - one_day;
|
||||
constexpr decltype(yesterday.epoch_seconds().count()) expected_second_count =
|
||||
kRawValidEpochSeconds - kOneDayS;
|
||||
EXPECT_EQ(yesterday.epoch_seconds().count(), expected_second_count);
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Subtraction_WithDuration_Unset) {
|
||||
constexpr Timestamp unset;
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
constexpr Timestamp result = unset - one_day;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Subtraction_WithDuration_Underflow) {
|
||||
Timestamp start = Timestamp::Min();
|
||||
constexpr Duration one_day = Duration::FromSeconds(kOneDayS);
|
||||
|
||||
Timestamp result = start - one_day;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
|
||||
start = Timestamp::Max();
|
||||
constexpr Duration very_long_duration =
|
||||
Duration::FromSeconds(kOneDayS * 365 * 10000);
|
||||
result = start - very_long_duration;
|
||||
EXPECT_FALSE(result.IsSet());
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Difference) {
|
||||
// 4 days, 3 hours, 2 minutes and 1 second.
|
||||
constexpr int64_t kDifferenceS =
|
||||
1 + 2 * kOneMinuteS + 3 * kOneHourS + 4 * kOneDayS;
|
||||
|
||||
constexpr Timestamp ts_a = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
constexpr Timestamp ts_b =
|
||||
Timestamp::FromUnixSeconds(kRawValidEpochSeconds + kDifferenceS);
|
||||
|
||||
constexpr Duration diff_ab = ts_b - ts_a;
|
||||
EXPECT_EQ(diff_ab.total_seconds().count(), kDifferenceS);
|
||||
|
||||
constexpr Duration diff_ba = ts_a - ts_b;
|
||||
EXPECT_EQ(diff_ba.total_seconds().count(), -kDifferenceS);
|
||||
}
|
||||
|
||||
TEST(WvTimestampUtilTest, Difference_Unset) {
|
||||
constexpr Duration unset_duration;
|
||||
|
||||
constexpr Timestamp ts = Timestamp::FromUnixSeconds(kValidEpochSeconds);
|
||||
constexpr Timestamp unset;
|
||||
|
||||
EXPECT_EQ(ts - unset, unset_duration);
|
||||
EXPECT_EQ(unset - ts, unset_duration);
|
||||
EXPECT_EQ(unset - unset, unset_duration);
|
||||
}
|
||||
} // namespace test
|
||||
} // namespace wvutil
|
||||
Reference in New Issue
Block a user