OEMCrypto and OPK v20 prerelease initial commit

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

View 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_

View File

@@ -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_

View 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_

View File

@@ -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_

View 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
View 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
View 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
View 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
View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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

File diff suppressed because it is too large Load Diff

View 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