Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s

Builds out the full v1 scope: a vendored+patched openscreen subset for
low-latency Cast Streaming (Mirroring receiver 0F5096E8) alongside the
existing Cast V2/HLS and new DLNA/AVTransport casting paths, breadcastd's
Idle/Casting state machine with a private IPC socket, the breadcast GTK4
popup as a thin IPC client, and bread.cast.*/bread.command.cast.* breadd
integration (device discovery, start/stop, mirroring lifecycle events).
Also adds bakery/systemd/Forgejo CI packaging.

Validated end-to-end against a real Chromecast/Google TV: negotiated
Cast Streaming session, live pipeline playback, and daemon+GUI click-to-cast/
stop through the actual popup.
This commit is contained in:
Breadway 2026-08-03 09:07:21 +08:00
parent 887c29002f
commit 8c745d18e0
283 changed files with 36788 additions and 0 deletions

View file

@ -0,0 +1,131 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/alarm.h"
#include <algorithm>
#include "util/osp_logging.h"
namespace openscreen {
class Alarm::CancelableFunctor {
public:
explicit CancelableFunctor(Alarm* alarm) : alarm_(alarm) {
OSP_CHECK(alarm_);
OSP_CHECK(!alarm_->queued_fire_);
alarm_->queued_fire_ = this;
}
~CancelableFunctor() { Cancel(); }
CancelableFunctor(CancelableFunctor&& other) : alarm_(other.alarm_) {
other.alarm_ = nullptr;
if (alarm_) {
OSP_CHECK_EQ(alarm_->queued_fire_, &other);
alarm_->queued_fire_ = this;
}
}
CancelableFunctor& operator=(CancelableFunctor&& other) {
Cancel();
alarm_ = other.alarm_;
other.alarm_ = nullptr;
if (alarm_) {
OSP_CHECK_EQ(alarm_->queued_fire_, &other);
alarm_->queued_fire_ = this;
}
return *this;
}
void operator()() noexcept {
if (alarm_) {
Alarm* alarm = alarm_;
OSP_CHECK_EQ(alarm->queued_fire_, this);
alarm->queued_fire_ = nullptr;
alarm_ = nullptr;
alarm->TryInvoke();
}
}
void Cancel() {
if (alarm_) {
OSP_CHECK_EQ(alarm_->queued_fire_, this);
alarm_->queued_fire_ = nullptr;
alarm_ = nullptr;
}
}
private:
raw_ptr<Alarm> alarm_;
};
Alarm::Alarm(ClockNowFunctionPtr now_function, TaskRunner& task_runner)
: now_function_(now_function), task_runner_(task_runner) {
OSP_CHECK(now_function_);
}
Alarm::~Alarm() {
if (queued_fire_) {
queued_fire_->Cancel();
OSP_CHECK(!queued_fire_);
}
}
void Alarm::Cancel() {
scheduled_task_ = TaskRunner::Task();
}
void Alarm::ScheduleWithTask(TaskRunner::Task task,
Clock::time_point desired_alarm_time) {
OSP_CHECK(task.valid());
scheduled_task_ = std::move(task);
const Clock::time_point now = now_function_();
alarm_time_ = std::max(now, desired_alarm_time);
// Ensure that a later firing will occur, and not too late.
if (queued_fire_) {
if (next_fire_time_ <= alarm_time_) {
return;
}
queued_fire_->Cancel();
OSP_CHECK(!queued_fire_);
}
InvokeLater(now, alarm_time_);
}
void Alarm::InvokeLater(Clock::time_point now, Clock::time_point fire_time) {
OSP_CHECK(!queued_fire_);
next_fire_time_ = fire_time;
// Note: Instantiating the CancelableFunctor below sets |this->queued_fire_|.
task_runner_->PostTaskWithDelay(CancelableFunctor(this), fire_time - now);
}
void Alarm::TryInvoke() {
if (!scheduled_task_.valid()) {
return; // This Alarm was canceled in the meantime.
}
// If this is an early firing, re-schedule for later. This happens if
// Schedule() was called again before this firing had occurred.
const Clock::time_point now = now_function_();
if (now < alarm_time_) {
InvokeLater(now, alarm_time_);
return;
}
// Move the client Task to the stack before executing, just in case the task
// itself: a) calls any Alarm methods re-entrantly, or b) causes the
// destruction of this Alarm instance.
// WARNING: `this` is not valid after here!
TaskRunner::Task task = std::move(scheduled_task_);
task();
}
// static
constexpr Clock::time_point Alarm::kImmediately;
} // namespace openscreen

View file

@ -0,0 +1,109 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_ALARM_H_
#define UTIL_ALARM_H_
#include <utility>
#include "platform/api/task_runner.h"
#include "platform/api/time.h"
#include "util/raw_ptr.h"
#include "util/raw_ref.h"
namespace openscreen {
// A simple mechanism for running one Task in the future, but also allow for
// canceling the Task before it runs and/or re-scheduling a replacement Task to
// run at a different time. This mechanism is also scoped to its lifetime: if an
// Alarm is destroyed while it is scheduled, the Task is automatically canceled.
// It is safe for the client's Task to make re-entrant calls into all Alarm
// methods.
//
// Example use case: When using a TaskRunner, an object can safely schedule a
// callback into one of its instance methods (without the possibility of the
// Task executing after the object is destroyed).
//
// Design: In order to support efficient, arbitrary canceling and re-scheduling
// by the client, the Alarm posts a cancelable functor to the TaskRunner which,
// when invoked, then checks to see whether the Alarm instance still exists and,
// if so, calls its TryInvoke() method. The TryInvoke() method then determines:
// a) whether the invocation time of the client's Task has changed; and b)
// whether the Alarm was canceled in the meantime. From this, it either: a) does
// nothing; b) re-posts a new cancelable functor to the TaskRunner, to try
// running the client's Task later; or c) runs the client's Task.
class Alarm {
public:
Alarm(ClockNowFunctionPtr now_function, TaskRunner& task_runner);
~Alarm();
// The design requires that Alarm instances not be copied or moved.
Alarm(const Alarm&) = delete;
Alarm& operator=(const Alarm&) = delete;
Alarm(Alarm&&) noexcept = delete;
Alarm& operator=(Alarm&&) noexcept = delete;
// Schedule the `functor` to be invoked at `alarm_time`. If this Alarm was
// already scheduled, the prior scheduling is canceled. The Functor can be any
// callable target (e.g., function, lambda-expression, std::bind result,
// etc.). If `alarm_time` is on or before "now," such as kImmediately, it is
// scheduled to run as soon as possible.
template <typename Functor>
inline void Schedule(Functor functor, Clock::time_point alarm_time) {
ScheduleWithTask(TaskRunner::Task(std::move(functor)), alarm_time);
}
// Same as Schedule(), but invoke the functor at the given `delay` after right
// now.
template <typename Functor>
inline void ScheduleFromNow(Functor functor, Clock::duration delay) {
ScheduleWithTask(TaskRunner::Task(std::move(functor)),
now_function_() + delay);
}
// Cancels an already-scheduled task from running, or no-op.
void Cancel();
// See comments for Schedule(). Generally, callers will want to call
// Schedule() instead of this, for more-convenient caller-side syntax, unless
// they already have a Task to pass-in.
void ScheduleWithTask(TaskRunner::Task task, Clock::time_point alarm_time);
// A special time_point value representing "as soon as possible."
static constexpr Clock::time_point kImmediately = Clock::time_point::min();
private:
// A move-only functor that holds a raw pointer back to `this` and can be
// canceled before its call operator is invoked. When canceled, its call
// operator becomes a no-op.
class CancelableFunctor;
// Posts a delayed call to TryInvoke() to the TaskRunner.
void InvokeLater(Clock::time_point now, Clock::time_point fire_time);
// Examines whether to invoke the client's Task now; or try again later; or
// just do nothing. See class-level design comments.
void TryInvoke();
const ClockNowFunctionPtr now_function_;
const raw_ref<TaskRunner> task_runner_;
// This is the task the client wants to have run at a specific point-in-time.
// This is NOT the task that Alarm provides to the TaskRunner.
TaskRunner::Task scheduled_task_;
Clock::time_point alarm_time_{};
// When non-null, there is a task in the TaskRunner's queue that will call
// TryInvoke() some time in the future. This member is exclusively maintained
// by the CancelableFunctor class methods.
raw_ptr<CancelableFunctor> queued_fire_;
// When the CancelableFunctor is scheduled to run. It may possibly execute
// later than this, if the TaskRunner is falling behind.
Clock::time_point next_fire_time_{};
};
} // namespace openscreen
#endif // UTIL_ALARM_H_

View file

@ -0,0 +1,65 @@
// LOCAL PATCH (breadcast): upstream implements this on top of
// third_party/modp_b64, which isn't fetched by a plain shallow clone of
// openscreen (it's pulled in separately via gclient/DEPS). This is a
// same-interface reimplementation on top of OpenSSL's EVP_Encode/DecodeBlock
// instead, since system OpenSSL is already a build dependency here. See
// vendor/openscreen/PATCHES.md.
#include "util/base64.h"
#include <openssl/evp.h>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace openscreen::base64 {
std::string Encode(ByteView input) {
return Encode(std::string_view(reinterpret_cast<const char*>(input.data()),
input.size()));
}
std::string Encode(std::string_view input) {
const auto* data = reinterpret_cast<const unsigned char*>(input.data());
// EVP_EncodeBlock's output is 4*ceil(n/3) bytes plus a NUL terminator it
// writes but doesn't count in the returned length.
std::string out((4 * ((input.size() + 2) / 3)) + 1, '\0');
const int output_size = EVP_EncodeBlock(
reinterpret_cast<unsigned char*>(out.data()), data,
static_cast<int>(input.size()));
out.resize(static_cast<size_t>(output_size));
return out;
}
bool Decode(std::string_view input, std::vector<uint8_t>* output) {
if (input.size() % 4 != 0) {
return false;
}
std::vector<uint8_t> out((input.size() / 4) * 3);
if (!out.empty()) {
const int decoded_size = EVP_DecodeBlock(
out.data(), reinterpret_cast<const unsigned char*>(input.data()),
static_cast<int>(input.size()));
if (decoded_size < 0) {
return false;
}
// EVP_DecodeBlock doesn't strip padding from the output size -- trim the
// 1-2 bytes corresponding to trailing '=' padding characters, matching
// the caller-visible behavior of a normal base64 decoder.
size_t padding = 0;
if (input.size() >= 2) {
if (input[input.size() - 1] == '=') {
++padding;
}
if (input[input.size() - 2] == '=') {
++padding;
}
}
out.resize(static_cast<size_t>(decoded_size) - padding);
}
*output = std::move(out);
return true;
}
} // namespace openscreen::base64

View file

@ -0,0 +1,32 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_BASE64_H_
#define UTIL_BASE64_H_
#include <stdint.h>
#include <string>
#include <string_view>
#include <vector>
#include "platform/base/error.h"
#include "platform/base/span.h"
namespace openscreen::base64 {
// Encodes the input binary data in base64.
std::string Encode(ByteView input);
// Encodes the input string in base64.
std::string Encode(std::string_view input);
// Decodes the base64 input string. Returns true if successful and false
// otherwise. The output string is only modified if successful. The decoding can
// be done in-place.
bool Decode(std::string_view input, std::vector<uint8_t>* output);
} // namespace openscreen::base64
#endif // UTIL_BASE64_H_

View file

@ -0,0 +1,47 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/big_endian.h"
namespace openscreen {
BigEndianReader::BigEndianReader(ByteView buffer) : BigEndianBuffer(buffer) {}
BigEndianReader::BigEndianReader(const uint8_t* buffer, size_t length)
: BigEndianBuffer(buffer, length) {}
bool BigEndianReader::Read(size_t length, void* out) {
return Read(ByteBuffer(static_cast<uint8_t*>(out), length));
}
bool BigEndianReader::Read(ByteBuffer out) {
ByteView view = remaining_span();
if (view.size() >= out.size()) {
std::copy(view.begin(), view.begin() + out.size(), out.begin());
Skip(out.size());
return true;
}
return false;
}
BigEndianWriter::BigEndianWriter(ByteBuffer buffer) : BigEndianBuffer(buffer) {}
BigEndianWriter::BigEndianWriter(uint8_t* buffer, size_t length)
: BigEndianBuffer(buffer, length) {}
bool BigEndianWriter::Write(const void* buffer, size_t length) {
return Write(ByteView(static_cast<const uint8_t*>(buffer), length));
}
bool BigEndianWriter::Write(ByteView buffer) {
ByteBuffer view = remaining_span();
if (view.size() >= buffer.size()) {
std::copy(buffer.begin(), buffer.end(), view.begin());
Skip(buffer.size());
return true;
}
return false;
}
} // namespace openscreen

View file

@ -0,0 +1,255 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_BIG_ENDIAN_H_
#define UTIL_BIG_ENDIAN_H_
#include <stdint.h>
#include <algorithm>
#include <cstring>
#include <type_traits>
#include "platform/base/span.h"
#include "util/raw_ptr.h"
namespace openscreen {
////////////////////////////////////////////////////////////////////////////////
// Note: All of the functions here are defined inline, as any half-decent
// compiler will optimize them to a single integer constant or single
// instruction on most architectures.
////////////////////////////////////////////////////////////////////////////////
// Returns true if this code is running on a big-endian architecture.
inline bool IsBigEndianArchitecture() {
const uint16_t kTestWord = 0x0100;
uint8_t bytes[sizeof(kTestWord)];
memcpy(bytes, &kTestWord, sizeof(bytes));
return !!bytes[0];
}
namespace internal {
template <int size>
struct MakeSizedUnsignedInteger;
template <>
struct MakeSizedUnsignedInteger<1> {
using type = uint8_t;
};
template <>
struct MakeSizedUnsignedInteger<2> {
using type = uint16_t;
};
template <>
struct MakeSizedUnsignedInteger<4> {
using type = uint32_t;
};
template <>
struct MakeSizedUnsignedInteger<8> {
using type = uint64_t;
};
template <int size>
inline typename MakeSizedUnsignedInteger<size>::type ByteSwap(
typename MakeSizedUnsignedInteger<size>::type x) {
static_assert(size <= 8,
"ByteSwap() specialization missing in " __FILE__
". "
"Are you trying to use an integer larger than 64 bits?");
}
template <>
inline uint8_t ByteSwap<1>(uint8_t x) {
return x;
}
#if defined(__clang__) || defined(__GNUC__)
template <>
inline uint64_t ByteSwap<8>(uint64_t x) {
return __builtin_bswap64(x);
}
template <>
inline uint32_t ByteSwap<4>(uint32_t x) {
return __builtin_bswap32(x);
}
template <>
inline uint16_t ByteSwap<2>(uint16_t x) {
return __builtin_bswap16(x);
}
#elif defined(_MSC_VER)
template <>
inline uint64_t ByteSwap<8>(uint64_t x) {
return _byteswap_uint64(x);
}
template <>
inline uint32_t ByteSwap<4>(uint32_t x) {
return _byteswap_ulong(x);
}
template <>
inline uint16_t ByteSwap<2>(uint16_t x) {
return _byteswap_ushort(x);
}
#else
#include <byteswap.h>
template <>
inline uint64_t ByteSwap<8>(uint64_t x) {
return bswap_64(x);
}
template <>
inline uint32_t ByteSwap<4>(uint32_t x) {
return bswap_32(x);
}
template <>
inline uint16_t ByteSwap<2>(uint16_t x) {
return bswap_16(x);
}
#endif
} // namespace internal
// Returns the bytes of `x` in reverse order. This is only defined for 16-, 32-,
// and 64-bit unsigned integers.
template <typename Integer>
inline std::enable_if_t<std::is_unsigned<Integer>::value, Integer> ByteSwap(
Integer x) {
return internal::ByteSwap<sizeof(Integer)>(x);
}
// Read a POD integer from `src` in big-endian byte order, returning the integer
// in native byte order.
template <typename Integer>
inline Integer ReadBigEndian(const void* src) {
Integer result;
memcpy(&result, src, sizeof(result));
if (!IsBigEndianArchitecture()) {
result = ByteSwap<typename std::make_unsigned<Integer>::type>(result);
}
return result;
}
// Write a POD integer `val` to `dest` in big-endian byte order.
template <typename Integer>
inline void WriteBigEndian(Integer val, void* dest) {
if (!IsBigEndianArchitecture()) {
val = ByteSwap<typename std::make_unsigned<Integer>::type>(val);
}
memcpy(dest, &val, sizeof(val));
}
template <class T>
class BigEndianBuffer {
public:
class Cursor {
public:
explicit Cursor(BigEndianBuffer* buffer)
: buffer_(buffer), origin_offset_(buffer_->offset()) {}
Cursor(const Cursor& other) = delete;
Cursor(Cursor&& other) noexcept = delete;
~Cursor() { buffer_->set_offset(origin_offset_); }
Cursor& operator=(const Cursor& other) = delete;
Cursor& operator=(Cursor&& other) noexcept = delete;
void Commit() { origin_offset_ = buffer_->offset(); }
size_t origin_offset() const { return origin_offset_; }
T* origin() const { return buffer_->begin() + origin_offset_; }
size_t delta() const { return buffer_->offset() - origin_offset_; }
private:
raw_ptr<BigEndianBuffer<T>> buffer_;
size_t origin_offset_;
};
bool Skip(size_t length) {
if (length > remaining()) {
return false;
}
offset_ += length;
return true;
}
Span<T> buffer() const { return buffer_; }
Span<T> remaining_span() const { return buffer_.subspan(offset_); }
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
T* begin() const { return buffer_.data(); }
T* current() const { return buffer_.data() + offset_; }
T* end() const { return buffer_.data() + buffer_.size(); }
size_t length() const { return buffer_.size(); }
size_t remaining() const { return buffer_.size() - offset_; }
size_t offset() const { return offset_; }
explicit BigEndianBuffer(Span<T> buffer) : buffer_(buffer) {}
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
BigEndianBuffer(T* buffer, size_t length) : buffer_(buffer, length) {}
BigEndianBuffer(const BigEndianBuffer&) = delete;
BigEndianBuffer& operator=(const BigEndianBuffer&) = delete;
protected:
void set_offset(size_t offset) { offset_ = offset; }
private:
Span<T> buffer_;
size_t offset_ = 0;
};
class BigEndianReader : public BigEndianBuffer<const uint8_t> {
public:
explicit BigEndianReader(ByteView buffer);
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
BigEndianReader(const uint8_t* buffer, size_t length);
template <typename T>
bool Read(T* out) {
ByteView view = remaining_span();
if (view.size() >= sizeof(T)) {
*out = ReadBigEndian<T>(view.data());
Skip(sizeof(T));
return true;
}
return false;
}
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
bool Read(size_t length, void* out);
bool Read(ByteBuffer out);
};
class BigEndianWriter : public BigEndianBuffer<uint8_t> {
public:
explicit BigEndianWriter(ByteBuffer buffer);
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
BigEndianWriter(uint8_t* buffer, size_t length);
template <typename T>
bool Write(T value) {
ByteBuffer view = remaining_span();
if (view.size() >= sizeof(T)) {
WriteBigEndian<T>(value, view.data());
Skip(sizeof(T));
return true;
}
return false;
}
// TODO(crbug.com/520101123): Remove unsafe raw pointer and length methods.
bool Write(const void* buffer, size_t length);
bool Write(ByteView buffer);
};
} // namespace openscreen
#endif // UTIL_BIG_ENDIAN_H_

View file

@ -0,0 +1,34 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/bit_vector.h"
#include <algorithm>
namespace openscreen {
BitVector::BitVector(size_t size, Fill fill) {
Resize(size, fill);
}
void BitVector::Resize(size_t size, Fill fill) {
size_ = size;
v_.assign((size + kBitsPerWord - 1) / kBitsPerWord,
fill ? ~uint64_t{0} : uint64_t{0});
if (fill && size % kBitsPerWord != 0) {
v_.back() &= (uint64_t{1} << (size % kBitsPerWord)) - 1;
}
}
size_t BitVector::FindFirstSet() const {
for (size_t i = 0; i < v_.size(); ++i) {
if (v_[i] != 0) {
size_t pos = i * kBitsPerWord + std::countr_zero(v_[i]);
return (pos < size_) ? pos : size_;
}
}
return size_;
}
} // namespace openscreen

View file

@ -0,0 +1,66 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_BIT_VECTOR_H_
#define UTIL_BIT_VECTOR_H_
#include <stddef.h>
#include <stdint.h>
#include <bit>
#include <limits>
#include <vector>
#include "util/osp_logging.h"
namespace openscreen {
// A simple dynamic bit vector implementation using C++20 <bit> and std::vector.
// This is used for tracking packet transmission flags in the Sender.
class BitVector {
public:
enum Fill : bool { SET = true, CLEARED = false };
BitVector() noexcept = default;
BitVector(size_t size, Fill fill);
~BitVector() = default;
BitVector(BitVector&& other) noexcept = default;
BitVector& operator=(BitVector&& other) noexcept = default;
BitVector(const BitVector& other) = default;
BitVector& operator=(const BitVector& other) = default;
[[nodiscard]] size_t size() const noexcept { return size_; }
void Resize(size_t size, Fill fill);
void Set(size_t pos) {
OSP_CHECK_LT(pos, size_);
v_[pos / kBitsPerWord] |= (uint64_t{1} << (pos % kBitsPerWord));
}
void Clear(size_t pos) {
OSP_CHECK_LT(pos, size_);
v_[pos / kBitsPerWord] &= ~(uint64_t{1} << (pos % kBitsPerWord));
}
[[nodiscard]] bool IsSet(size_t pos) const {
OSP_CHECK_LT(pos, size_);
return (v_[pos / kBitsPerWord] >> (pos % kBitsPerWord)) & 1;
}
[[nodiscard]] size_t FindFirstSet() const;
private:
static constexpr size_t kBitsPerWord = std::numeric_limits<uint64_t>::digits;
std::vector<uint64_t> v_;
size_t size_ = 0;
};
} // namespace openscreen
#endif // UTIL_BIT_VECTOR_H_

View file

@ -0,0 +1,50 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_CHRONO_HELPERS_H_
#define UTIL_CHRONO_HELPERS_H_
#include <chrono>
// This file is a collection of helpful utilities and using statement for
// working with std::chrono. In practice we previously defined these frequently,
// this header allows for a single set of convenience statements.
namespace openscreen {
using hours = std::chrono::hours;
using microseconds = std::chrono::microseconds;
using milliseconds = std::chrono::milliseconds;
using nanoseconds = std::chrono::nanoseconds;
using seconds = std::chrono::seconds;
// Casting statements. Note that duration_cast is not a type, it's a function,
// so its behavior is different than the using statements above.
template <typename D>
static constexpr hours to_hours(D d) {
return std::chrono::duration_cast<hours>(d);
}
template <typename D>
static constexpr microseconds to_microseconds(D d) {
return std::chrono::duration_cast<microseconds>(d);
}
template <typename D>
static constexpr milliseconds to_milliseconds(D d) {
return std::chrono::duration_cast<milliseconds>(d);
}
template <typename D>
static constexpr nanoseconds to_nanoseconds(D d) {
return std::chrono::duration_cast<nanoseconds>(d);
}
template <typename D>
static constexpr seconds to_seconds(D d) {
return std::chrono::duration_cast<seconds>(d);
}
} // namespace openscreen
#endif // UTIL_CHRONO_HELPERS_H_

View file

@ -0,0 +1,62 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/crypto/openssl_util.h"
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <stddef.h>
#include <stdint.h>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include "util/osp_logging.h"
namespace openscreen {
namespace {
// Callback routine for OpenSSL to print error messages. `str` is a
// nullptr-terminated string of length `len` containing diagnostic information
// such as the library, function and reason for the error, the file and line
// where the error originated, plus potentially any context-specific
// information about the error. `context` contains a pointer to user-supplied
// data, which is currently unused.
// If this callback returns a value <= 0, OpenSSL will stop processing the
// error queue and return, otherwise it will continue calling this function
// until all errors have been removed from the queue.
int OpenSSLErrorCallback(const char* str, size_t len, void* context) {
OSP_DVLOG << "\t" << std::string_view(str, len);
return 1;
}
} // namespace
void EnsureOpenSSLInit() {
// LOCAL PATCH (breadcast): upstream calls OPENSSL_init_ssl() here, but
// this vendored subset never touches libssl (no TLS -- see
// ../../../PATCHES.md), so this just does the general libcrypto init
// instead. Safe to call repeatedly; OpenSSL 3.x makes this optional
// anyway, but frame_crypto.cc's key setup wants it done up front.
OPENSSL_init_crypto(0, nullptr);
}
void ClearOpenSSLERRStack(const Location& location) {
if (OSP_DCHECK_IS_ON()) {
uint32_t error_num = ERR_peek_error();
if (error_num == 0) {
return;
}
OSP_DVLOG << "OpenSSL ERR_get_error stack from " << location;
ERR_print_errors_cb(&OpenSSLErrorCallback, nullptr);
} else {
ERR_clear_error();
}
}
} // namespace openscreen

View file

@ -0,0 +1,61 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_CRYPTO_OPENSSL_UTIL_H_
#define UTIL_CRYPTO_OPENSSL_UTIL_H_
#include <stddef.h>
#include <cstring>
#include "platform/base/error.h"
#include "platform/base/location.h"
// LOCAL PATCH (breadcast): dropped SSLErrorCodeToError()/GetSSLError(),
// which format an SSL_get_error() result and reference BoringSSL's
// SSL_error_description() (not part of system OpenSSL's public API). Unused
// here -- the TLS CASTV2 control channel is handled by the existing
// rust_cast-based Rust code, not this vendored openscreen subset, which only
// needs the general ERR_*/AES pieces. See vendor/openscreen/PATCHES.md.
namespace openscreen {
// Initialize OpenSSL if it isn't already initialized. This must be called
// before any other OpenSSL functions though it is safe and cheap to call this
// multiple times.
// This function is thread-safe, and OpenSSL will only ever be initialized once.
// OpenSSL will be properly shut down on program exit.
// Multiple sequential calls to EnsureOpenSSLInit or EnsureOpenSSLCleanup are
// ignored by OpenSSL itself.
void EnsureOpenSSLInit();
// Drains the OpenSSL ERR_get_error stack. On a debug build the error codes
// are send to VLOG(1), on a release build they are disregarded. In most
// cases you should pass CURRENT_LOCATION as the `location`.
void ClearOpenSSLERRStack(const Location& location);
// Place an instance of this class on the call stack to automatically clear
// the OpenSSL error stack on function exit.
class OpenSSLErrStackTracer {
public:
// Pass CURRENT_LOCATION as `location`, to help track the source of OpenSSL
// error messages. Note any diagnostic emitted will be tagged with the
// location of the constructor call as it's not possible to trace a
// destructor's callsite.
explicit OpenSSLErrStackTracer(const Location& location)
: location_(location) {
EnsureOpenSSLInit();
}
OpenSSLErrStackTracer(const OpenSSLErrStackTracer&) = delete;
OpenSSLErrStackTracer(OpenSSLErrStackTracer&&) noexcept = delete;
OpenSSLErrStackTracer& operator=(const OpenSSLErrStackTracer&) = delete;
OpenSSLErrStackTracer& operator=(OpenSSLErrStackTracer&&) = delete;
~OpenSSLErrStackTracer() { ClearOpenSSLERRStack(location_); }
private:
const Location location_;
};
} // namespace openscreen
#endif // UTIL_CRYPTO_OPENSSL_UTIL_H_

View file

@ -0,0 +1,23 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/crypto/random_bytes.h"
#include "openssl/rand.h"
#include "util/osp_logging.h"
namespace openscreen {
std::array<uint8_t, 16> GenerateRandomBytes16() {
std::array<uint8_t, 16> result;
GenerateRandomBytes(result);
return result;
}
void GenerateRandomBytes(ByteBuffer out) {
// Working cryptography is mandatory for our library to run.
OSP_CHECK(RAND_bytes(out.data(), out.size()) == 1);
}
} // namespace openscreen

View file

@ -0,0 +1,20 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_CRYPTO_RANDOM_BYTES_H_
#define UTIL_CRYPTO_RANDOM_BYTES_H_
#include <array>
#include <cstdint>
#include "platform/base/span.h"
namespace openscreen {
std::array<uint8_t, 16> GenerateRandomBytes16();
void GenerateRandomBytes(ByteBuffer out);
} // namespace openscreen
#endif // UTIL_CRYPTO_RANDOM_BYTES_H_

View file

@ -0,0 +1,51 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file contains helpers for working with enums that require
// both enum->string and string->enum conversions.
#ifndef UTIL_ENUM_NAME_TABLE_H_
#define UTIL_ENUM_NAME_TABLE_H_
#include <array>
#include <string_view>
#include <utility>
#include "platform/base/error.h"
#include "util/osp_logging.h"
#include "util/string_util.h"
namespace openscreen {
inline constexpr char kUnknownEnumError[] = "Enum value not in array";
template <typename Enum, size_t Size>
using EnumNameTable = std::array<std::pair<const char*, Enum>, Size>;
// Get the name of an enum from the enum value.
template <typename Enum, size_t Size>
ErrorOr<const char*> GetEnumName(const EnumNameTable<Enum, Size>& map,
Enum enum_) {
for (auto pair : map) {
if (pair.second == enum_) {
return pair.first;
}
}
return Error(Error::Code::kParameterInvalid, kUnknownEnumError);
}
// Get the value of an enum from the enum name.
template <typename Enum, size_t Size>
ErrorOr<Enum> GetEnum(const EnumNameTable<Enum, Size>& map,
std::string_view name) {
for (auto pair : map) {
if (::openscreen::string_util::EqualsIgnoreCase(pair.first, name)) {
return pair.second;
}
}
return Error(Error::Code::kParameterInvalid, kUnknownEnumError);
}
} // namespace openscreen
#endif // UTIL_ENUM_NAME_TABLE_H_

View file

@ -0,0 +1,66 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_FLAT_MAP_H_
#define UTIL_FLAT_MAP_H_
#include <algorithm>
#include <initializer_list>
#include <map>
#include <utility>
#include <vector>
#include "util/osp_logging.h"
namespace openscreen {
// For small numbers of elements, a vector is much more efficient than a
// map or unordered_map due to not needing hashing. FlatMap allows for
// using map-like syntax but is backed by a std::vector, combining all the
// performance of a vector with the convenience of a map.
//
// NOTE: this class allows usage of const char* as Key or Value types, but
// it is generally recommended that you use std::string, or std::string_view
// for literals. string_view is similarly efficient to a raw char* pointer,
// but gives sizing and equality operators, among other features.
template <class Key, class Value>
class FlatMap final : public std::vector<std::pair<Key, Value>> {
public:
FlatMap(std::initializer_list<std::pair<Key, Value>> init_list)
: std::vector<std::pair<Key, Value>>(init_list) {}
FlatMap() = default;
FlatMap(const FlatMap&) = default;
FlatMap(FlatMap&&) noexcept = default;
FlatMap& operator=(const FlatMap&) = default;
FlatMap& operator=(FlatMap&&) = default;
~FlatMap() = default;
// Accessors that wrap std::find_if, and return an iterator to the key value
// pair.
decltype(auto) find(const Key& key) {
return std::find_if(
this->begin(), this->end(),
[key](const std::pair<Key, Value>& pair) { return key == pair.first; });
}
decltype(auto) find(const Key& key) const {
return const_cast<FlatMap<Key, Value>*>(this)->find(key);
}
// Remove an entry from the map. Returns an iterator pointing to the new
// location of the element that followed the last element erased by the
// function call. This is the container end if the operation erased the last
// element in the sequence.
decltype(auto) erase_key(const Key& key) {
auto it = find(key);
if (it == this->end()) {
return this->end();
}
return static_cast<std::vector<std::pair<Key, Value>>*>(this)->erase(it);
}
};
} // namespace openscreen
#endif // UTIL_FLAT_MAP_H_

View file

@ -0,0 +1,55 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_HASHING_H_
#define UTIL_HASHING_H_
#include <cstdint>
#include <utility>
#include <vector>
namespace openscreen {
// This value is taken from absl::Hash implementation.
inline constexpr uint64_t kDefaultSeed = UINT64_C(0xc3a5c85c97cb3127);
// Computes the aggregate hash of the provided hashable objects.
// Seed must initially use a large prime between 2^63 and 2^64 as a starting
// value, or the result of a previous call to this function.
template <typename... T>
uint64_t ComputeAggregateHash(uint64_t original_seed, const T&... objs) {
auto hash_combiner = [](uint64_t current_seed,
uint64_t hash_value) -> uint64_t {
static const uint64_t kMultiplier = UINT64_C(0x9ddfea08eb382d69);
uint64_t a = (hash_value ^ current_seed) * kMultiplier;
a ^= (a >> 47);
uint64_t b = (current_seed ^ a) * kMultiplier;
b ^= (b >> 47);
b *= kMultiplier;
return b;
};
uint64_t result = original_seed;
std::vector<uint64_t> hashes = {std::hash<T>()(objs)...};
for (uint64_t hash : hashes) {
result = hash_combiner(result, hash);
}
return result;
}
template <typename... T>
uint64_t ComputeAggregateHash(const T&... objs) {
return ComputeAggregateHash(kDefaultSeed, objs...);
}
struct PairHash {
template <typename TFirst, typename TSecond>
size_t operator()(const std::pair<TFirst, TSecond>& pair) const {
return ComputeAggregateHash(pair.first, pair.second);
}
};
} // namespace openscreen
#endif // UTIL_HASHING_H_

View file

@ -0,0 +1,67 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_INTEGER_DIVISION_H_
#define UTIL_INTEGER_DIVISION_H_
#include <type_traits>
namespace openscreen {
// Returns CEIL(num ÷ denom). `denom` must not equal zero. This function is
// compatible with any integer-like type, including the integer-based
// std::chrono duration types.
//
// Optimization note: See DividePositivesRoundingUp().
template <typename Integer>
constexpr auto DivideRoundingUp(Integer num, Integer denom) {
if (denom < Integer{0}) {
num *= -1;
denom *= -1;
}
if (num < Integer{0}) {
return num / denom;
}
return (num + denom - Integer{1}) / denom;
}
// Same as DivideRoundingUp(), except is more-efficient for hot code paths that
// know `num` is always greater or equal to zero, and `denom` is always greater
// than zero.
template <typename Integer>
constexpr Integer DividePositivesRoundingUp(Integer num, Integer denom) {
return DivideRoundingUp<typename std::make_unsigned<Integer>::type>(num,
denom);
}
// Divides `num` by `denom`, and rounds to the nearest integer (exactly halfway
// between integers will round to the higher integer). This function is
// compatible with any integer-like type, including the integer-based
// std::chrono duration types.
//
// Optimization note: See DividePositivesRoundingNearest().
template <typename Integer>
constexpr auto DivideRoundingNearest(Integer num, Integer denom) {
if (denom < Integer{0}) {
num *= -1;
denom *= -1;
}
if (num < Integer{0}) {
return (num - ((denom - Integer{1}) / 2)) / denom;
}
return (num + (denom / 2)) / denom;
}
// Same as DivideRoundingNearest(), except is more-efficient for hot code paths
// that know `num` is always greater or equal to zero, and `denom` is always
// greater than zero.
template <typename Integer>
constexpr Integer DividePositivesRoundingNearest(Integer num, Integer denom) {
return DivideRoundingNearest<typename std::make_unsigned<Integer>::type>(
num, denom);
}
} // namespace openscreen
#endif // UTIL_INTEGER_DIVISION_H_

View file

@ -0,0 +1,205 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_JSON_JSON_HELPERS_H_
#define UTIL_JSON_JSON_HELPERS_H_
#include <chrono>
#include <cmath>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "json/value.h"
#include "platform/base/error.h"
#include "util/chrono_helpers.h"
#include "util/json/json_serialization.h"
#include "util/simple_fraction.h"
// This file contains helper methods for parsing JSON, in an attempt to
// reduce boilerplate code when working with JsonCpp.
namespace openscreen::json {
inline bool TryParseBool(const Json::Value& value, bool* out) {
if (!value.isBool()) {
return false;
}
*out = value.asBool();
return true;
}
// A general note about parsing primitives. "Validation" in this context
// generally means ensuring that the values are non-negative, excepting doubles
// which may be negative in some cases.
inline bool TryParseDouble(const Json::Value& value,
double* out,
bool allow_negative = false) {
if (!value.isDouble()) {
return false;
}
const double d = value.asDouble();
if (std::isnan(d)) {
return false;
}
if (!allow_negative && d < 0) {
return false;
}
*out = d;
return true;
}
inline bool TryParseInt(const Json::Value& value, int* out) {
if (!value.isInt()) {
return false;
}
int i = value.asInt();
if (i < 0) {
return false;
}
*out = i;
return true;
}
inline bool TryParseUint(const Json::Value& value, uint32_t* out) {
if (!value.isUInt()) {
return false;
}
*out = value.asUInt();
return true;
}
inline bool TryParseString(const Json::Value& value, std::string* out) {
if (!value.isString()) {
return false;
}
*out = value.asString();
return true;
}
// We want to be more robust when we parse fractions then just
// allowing strings, this will parse numeral values such as
// value: 50 as well as value: "50" and value: "100/2".
inline bool TryParseSimpleFraction(const Json::Value& value,
SimpleFraction* out) {
if (value.isInt()) {
int parsed = value.asInt();
if (parsed < 0) {
return false;
}
*out = SimpleFraction{parsed, 1};
return true;
}
if (value.isString()) {
auto fraction_or_error = SimpleFraction::FromString(value.asString());
if (!fraction_or_error) {
return false;
}
if (!fraction_or_error.value().is_positive() ||
!fraction_or_error.value().is_defined()) {
return false;
}
*out = std::move(fraction_or_error.value());
return true;
}
return false;
}
inline bool TryParseMilliseconds(const Json::Value& value, milliseconds* out) {
int out_ms;
if (!TryParseInt(value, &out_ms) || out_ms < 0) {
return false;
}
*out = milliseconds(out_ms);
return true;
}
template <typename T>
using Parser = std::function<bool(const Json::Value&, T*)>;
// NOTE: array parsing methods reset the output vector to an empty vector in
// any error case. This is especially useful for optional arrays.
template <typename T>
bool TryParseArray(const Json::Value& value,
Parser<T> parser,
std::vector<T>* out) {
out->clear();
if (!value.isArray() || value.empty()) {
return false;
}
out->reserve(value.size());
for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
T v;
if (!parser(value[i], &v)) {
out->clear();
return false;
}
out->push_back(v);
}
return true;
}
inline bool TryParseIntArray(const Json::Value& value, std::vector<int>* out) {
return TryParseArray<int>(value, TryParseInt, out);
}
inline bool TryParseUintArray(const Json::Value& value,
std::vector<uint32_t>* out) {
return TryParseArray<uint32_t>(value, TryParseUint, out);
}
inline bool TryParseStringArray(const Json::Value& value,
std::vector<std::string>* out) {
return TryParseArray<std::string>(value, TryParseString, out);
}
inline bool TryParseNestedStringArray(
const Json::Value& value,
std::vector<std::vector<std::string>>* out) {
return TryParseArray<std::vector<std::string>>(value, TryParseStringArray,
out);
}
template <typename T>
Json::Value PrimitiveVectorToJson(const std::vector<T>& vec) {
Json::Value array(Json::ValueType::arrayValue);
array.resize(vec.size());
for (Json::Value::ArrayIndex i = 0; i < vec.size(); ++i) {
array[i] = Json::Value(vec[i]);
}
return array;
}
inline Json::Value NestedStringArrayToJson(
const std::vector<std::vector<std::string>>& vec) {
Json::Value array(Json::ValueType::arrayValue);
array.resize(vec.size());
for (Json::Value::ArrayIndex i = 0; i < vec.size(); ++i) {
array[i] = PrimitiveVectorToJson(vec[i]);
}
return array;
}
inline bool Contains(const Json::Value& array, std::string_view value) {
if (!array.isArray()) {
return false;
}
for (const Json::Value& entry : array) {
if (entry.isString() && entry.asString() == value) {
return true;
}
}
return false;
}
} // namespace openscreen::json
#endif // UTIL_JSON_JSON_HELPERS_H_

View file

@ -0,0 +1,62 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/json/json_serialization.h"
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "json/reader.h"
#include "json/writer.h"
#include "platform/base/error.h"
#include "util/osp_logging.h"
namespace openscreen::json {
ErrorOr<Json::Value> Parse(std::string_view document) {
Json::CharReaderBuilder builder;
Json::CharReaderBuilder::strictMode(&builder.settings_);
if (document.empty()) {
return ErrorOr<Json::Value>(Error::Code::kJsonParseError, "empty document");
}
Json::Value root_node;
std::string error_msg;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
const bool succeeded = reader->parse(&*document.begin(), &*document.end(),
&root_node, &error_msg);
if (!succeeded) {
return ErrorOr<Json::Value>(Error::Code::kJsonParseError, error_msg);
}
return root_node;
}
ErrorOr<std::string> Stringify(const Json::Value& value) {
Json::StreamWriterBuilder factory;
#ifndef _DEBUG
// Default is to "pretty print" the output JSON in a human readable
// format. On non-debug builds, we can remove pretty printing by simply
// getting rid of all indentation.
factory["indentation"] = "";
#endif
std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
std::ostringstream stream;
writer->write(value, &stream);
if (!stream) {
// Note: jsoncpp doesn't give us more information about what actually
// went wrong, just says to "check the stream". However, failures on
// the stream should be rare, as we do not throw any errors in the jsoncpp
// library.
return ErrorOr<std::string>(Error::Code::kJsonWriteError, "Invalid stream");
}
return stream.str();
}
} // namespace openscreen::json

View file

@ -0,0 +1,24 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_JSON_JSON_SERIALIZATION_H_
#define UTIL_JSON_JSON_SERIALIZATION_H_
#include <string>
#include <string_view>
#include "json/value.h"
#include "platform/base/error.h"
namespace openscreen {
namespace json {
ErrorOr<Json::Value> Parse(std::string_view value);
ErrorOr<std::string> Stringify(const Json::Value& value);
} // namespace json
} // namespace openscreen
#endif // UTIL_JSON_JSON_SERIALIZATION_H_

View file

@ -0,0 +1,43 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/json/json_value.h"
namespace openscreen {
std::optional<int> MaybeGetInt(const Json::Value& message,
const char* first,
const char* last) {
const Json::Value* value = message.find(first, last);
std::optional<int> result;
if (value && value->isInt()) {
result = value->asInt();
}
return result;
}
std::optional<std::string_view> MaybeGetString(const Json::Value& message) {
if (message.isString()) {
const char* begin = nullptr;
const char* end = nullptr;
message.getString(&begin, &end);
if (begin && end >= begin) {
return std::string_view(begin, end - begin);
}
}
return std::nullopt;
}
std::optional<std::string_view> MaybeGetString(const Json::Value& message,
const char* first,
const char* last) {
const Json::Value* value = message.find(first, last);
std::optional<std::string_view> result;
if (value && value->isString()) {
return MaybeGetString(*value);
}
return result;
}
} // namespace openscreen

View file

@ -0,0 +1,29 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_JSON_JSON_VALUE_H_
#define UTIL_JSON_JSON_VALUE_H_
#include <optional>
#include <string_view>
#include "json/value.h"
#define JSON_EXPAND_FIND_CONSTANT_ARGS(s) (s), ((s) + sizeof(s) - 1)
namespace openscreen {
std::optional<int> MaybeGetInt(const Json::Value& message,
const char* first,
const char* last);
std::optional<std::string_view> MaybeGetString(const Json::Value& message);
std::optional<std::string_view> MaybeGetString(const Json::Value& message,
const char* first,
const char* last);
} // namespace openscreen
#endif // UTIL_JSON_JSON_VALUE_H_

View file

@ -0,0 +1,79 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_NO_DESTRUCTOR_H_
#define UTIL_NO_DESTRUCTOR_H_
#include <new>
#include <type_traits>
#include <utility>
namespace openscreen {
// Helper type to create a function-local static variable of type `T` when `T`
// has a non-trivial destructor. Storing a `T` in a `NoDestructor<T>` will
// prevent `~T()` from running, even when the variable goes out of scope.
//
// Useful when a variable has static storage duration but its type has a
// non-trivial destructor. Using a function-local static variable prevents
// global constructors, while using `NoDestructor<T>` prevents global
// destructors.
//
// ## Example Usage
//
// const std::string& GetDefaultText() {
// // Required since `static const std::string` requires a global destructor.
// static const openscreen::NoDestructor<std::string> s("Hello world!");
// return *s;
// }
template <typename T>
class NoDestructor {
public:
static_assert(!(std::is_trivially_constructible_v<T> &&
std::is_trivially_destructible_v<T>),
"T is trivially constructible and destructible; please use a "
"constinit object of type T directly instead");
static_assert(
!std::is_trivially_destructible_v<T>,
"T is trivially destructible; please use a function-local static "
"of type T directly instead");
// Not constexpr; just write static constexpr T x = ...; if the value should
// be a constexpr.
template <typename... Args>
explicit NoDestructor(Args&&... args) {
new (storage_) T(std::forward<Args>(args)...);
}
// Allows copy and move construction of the contained type, to allow
// construction from an initializer list, e.g. for std::vector.
explicit NoDestructor(const T& x) { new (storage_) T(x); }
explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }
NoDestructor(const NoDestructor&) = delete;
NoDestructor& operator=(const NoDestructor&) = delete;
~NoDestructor() = default;
const T& operator*() const { return *get(); }
T& operator*() { return *get(); }
const T* operator->() const { return get(); }
T* operator->() { return get(); }
const T* get() const { return reinterpret_cast<const T*>(storage_); }
T* get() { return reinterpret_cast<T*>(storage_); }
private:
alignas(T) char storage_[sizeof(T)];
#if defined(LEAK_SANITIZER)
T* storage_ptr_ = reinterpret_cast<T*>(storage_);
#endif // defined(LEAK_SANITIZER)
};
} // namespace openscreen
#endif // UTIL_NO_DESTRUCTOR_H_

View file

@ -0,0 +1,145 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_OSP_LOGGING_H_
#define UTIL_OSP_LOGGING_H_
#include <sstream>
#include <string_view>
#include <utility>
#include "platform/api/logging.h"
namespace openscreen::internal {
// The stream-based logging macros below are adapted from Chromium's
// base/logging.h.
class LogMessage {
public:
LogMessage(LogLevel level, const char* file, int line)
: level_(level), file_(file), line_(line) {}
~LogMessage() {
LogWithLevel(level_, file_, line_, std::move(stream_));
if (level_ == LogLevel::kFatal) {
Break();
}
}
std::ostream& stream() { return stream_; }
protected:
const LogLevel level_;
// The file here comes from the __FILE__ macro, which should persist while
// we are doing the logging. Hence, keeping it unmanaged here and not
// creating a copy should be safe.
const char* const file_;
const int line_;
std::stringstream stream_;
};
// Used by the OSP_LAZY_STREAM macro to return void after evaluating an ostream
// chain expression.
class Voidify {
public:
void operator&(std::ostream&) {}
};
} // namespace openscreen::internal
#define OSP_LAZY_STREAM(condition, stream) \
!(condition) ? (void)0 : openscreen::internal::Voidify() & (stream)
#define OSP_LOG_IS_ON(level_enum) \
openscreen::IsLoggingOn(openscreen::LogLevel::level_enum, \
std::string_view(__FILE__, std::size(__FILE__)))
#define OSP_LOG_STREAM(level_enum) \
openscreen::internal::LogMessage(openscreen::LogLevel::level_enum, __FILE__, \
__LINE__) \
.stream()
#define OSP_VLOG \
OSP_LAZY_STREAM(OSP_LOG_IS_ON(kVerbose), OSP_LOG_STREAM(kVerbose))
#define OSP_LOG_INFO \
OSP_LAZY_STREAM(OSP_LOG_IS_ON(kInfo), OSP_LOG_STREAM(kInfo))
#define OSP_LOG_WARN \
OSP_LAZY_STREAM(OSP_LOG_IS_ON(kWarning), OSP_LOG_STREAM(kWarning))
#define OSP_LOG_ERROR \
OSP_LAZY_STREAM(OSP_LOG_IS_ON(kError), OSP_LOG_STREAM(kError))
#define OSP_LOG_FATAL \
OSP_LAZY_STREAM(OSP_LOG_IS_ON(kFatal), OSP_LOG_STREAM(kFatal))
#define OSP_VLOG_IF(condition) !(condition) ? (void)0 : OSP_VLOG
#define OSP_LOG_IF(level, condition) !(condition) ? (void)0 : OSP_LOG_##level
#define OSP_CHECK(condition) \
OSP_LOG_IF(FATAL, !(condition)) << "OSP_CHECK(" << #condition << ") failed: "
#define OSP_CHECK_EQ(a, b) \
OSP_CHECK((a) == (b)) << (a) << " vs. " << (b) << ": "
#define OSP_CHECK_NE(a, b) \
OSP_CHECK((a) != (b)) << (a) << " vs. " << (b) << ": "
#define OSP_CHECK_LT(a, b) OSP_CHECK((a) < (b)) << (a) << " vs. " << (b) << ": "
#define OSP_CHECK_LE(a, b) \
OSP_CHECK((a) <= (b)) << (a) << " vs. " << (b) << ": "
#define OSP_CHECK_GT(a, b) OSP_CHECK((a) > (b)) << (a) << " vs. " << (b) << ": "
#define OSP_CHECK_GE(a, b) \
OSP_CHECK((a) >= (b)) << (a) << " vs. " << (b) << ": "
#if defined(_DEBUG) || defined(DCHECK_ALWAYS_ON)
#define OSP_DCHECK_IS_ON() 1
#define OSP_DCHECK(condition) OSP_CHECK(condition)
#define OSP_DCHECK_EQ(a, b) OSP_CHECK_EQ(a, b)
#define OSP_DCHECK_NE(a, b) OSP_CHECK_NE(a, b)
#define OSP_DCHECK_LT(a, b) OSP_CHECK_LT(a, b)
#define OSP_DCHECK_LE(a, b) OSP_CHECK_LE(a, b)
#define OSP_DCHECK_GT(a, b) OSP_CHECK_GT(a, b)
#define OSP_DCHECK_GE(a, b) OSP_CHECK_GE(a, b)
#else
#define OSP_DCHECK_IS_ON() 0
// When DCHECKs are off, nothing will be logged. Use that fact to make
// references to the `condition` expression (or `a` and `b`) so the compiler
// won't emit unused variable warnings/errors when DCHECKs are turned off.
#define OSP_EAT_STREAM OSP_LOG_IF(FATAL, false)
#define OSP_DCHECK(condition) OSP_EAT_STREAM << !(condition)
#define OSP_DCHECK_EQ(a, b) OSP_EAT_STREAM << !((a) == (b))
#define OSP_DCHECK_NE(a, b) OSP_EAT_STREAM << !((a) != (b))
#define OSP_DCHECK_LT(a, b) OSP_EAT_STREAM << !((a) < (b))
#define OSP_DCHECK_LE(a, b) OSP_EAT_STREAM << !((a) <= (b))
#define OSP_DCHECK_GT(a, b) OSP_EAT_STREAM << !((a) > (b))
#define OSP_DCHECK_GE(a, b) OSP_EAT_STREAM << !((a) >= (b))
#endif
#define OSP_DVLOG OSP_VLOG_IF(OSP_DCHECK_IS_ON())
#define OSP_DLOG_INFO OSP_LOG_IF(INFO, OSP_DCHECK_IS_ON())
#define OSP_DLOG_WARN OSP_LOG_IF(WARN, OSP_DCHECK_IS_ON())
#define OSP_DLOG_ERROR OSP_LOG_IF(ERROR, OSP_DCHECK_IS_ON())
#define OSP_DLOG_FATAL OSP_LOG_IF(FATAL, OSP_DCHECK_IS_ON())
#define OSP_DVLOG_IF(condition) OSP_VLOG_IF(OSP_DCHECK_IS_ON() && (condition))
#define OSP_DLOG_IF(level, condition) \
OSP_LOG_IF(level, OSP_DCHECK_IS_ON() && (condition))
// Log when unimplemented code points are reached: If verbose logging is turned
// on, log always. Otherwise, just attempt to log once.
#define OSP_UNIMPLEMENTED() \
if (OSP_LOG_IS_ON(kVerbose)) { \
OSP_LOG_STREAM(kVerbose) << __func__ << ": UNIMPLEMENTED() hit."; \
} else { \
static bool needs_warning = true; \
if (needs_warning) { \
OSP_LOG_WARN << __func__ << ": UNIMPLEMENTED() hit."; \
needs_warning = false; \
} \
}
// Since Break() is annotated as noreturn, this will properly signal to the
// compiler that this code is truly not reached (and thus doesn't need a return
// statement for non-void returning functions/methods).
#define OSP_NOTREACHED() \
{ \
OSP_LOG_FATAL << __func__ << ": NOTREACHED() hit."; \
Break(); \
}
#endif // UTIL_OSP_LOGGING_H_

View file

@ -0,0 +1,441 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_RAW_PTR_H_
#define UTIL_RAW_PTR_H_
// This header implements a conditional `raw_ptr` template.
//
// In Chromium builds (when `BUILD_WITH_CHROMIUM` is defined), it aliases
// Chromium's `base::raw_ptr` (MiraclePtr / BackupRefPtr). This allows Open
// Screen code to benefit from Chromium's UAF protection when running inside
// Chrome.
//
// In standalone builds (e.g., embedded/IoT builds where dependencies must be
// minimized and overhead must be zero), it provides a zero-overhead,
// dependency-free polyfill that behaves like a standard raw pointer but
// enforces initialization to `nullptr`.
//
// Note: Traits (like DanglingUntriaged or AllowPtrArithmetic) are intentionally
// not supported in Open Screen to ensure code safety and compatibility.
#if defined(BUILD_WITH_CHROMIUM)
#include "partition_alloc/pointers/raw_ptr.h" // nogncheck
namespace openscreen {
// Alias the Chromium implementation, restricting it to not use traits.
template <typename T>
using raw_ptr = ::base::raw_ptr<T>;
} // namespace openscreen
#else // !defined(BUILD_WITH_CHROMIUM)
#include <cstddef>
#include <functional>
#include <iosfwd>
#include <memory>
#include <type_traits>
#include <utility>
// Optimization macros to ensure the polyfill truly has zero overhead at the ABI
// level.
#if defined(__clang__)
#define OPENSCREEN_TRIVIAL_ABI [[clang::trivial_abi]]
#else
#define OPENSCREEN_TRIVIAL_ABI
#endif
#if defined(_MSC_VER)
#define OPENSCREEN_ALWAYS_INLINE __forceinline
#elif defined(__GNUC__) || defined(__clang__)
#define OPENSCREEN_ALWAYS_INLINE __attribute__((always_inline)) inline
#else
#define OPENSCREEN_ALWAYS_INLINE inline
#endif
namespace openscreen {
// Standalone polyfill for `raw_ptr`.
// It has zero runtime overhead compared to a raw pointer and compiles away.
template <typename T>
class OPENSCREEN_TRIVIAL_ABI raw_ptr {
public:
// Safety: auto-initialize to nullptr.
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr() noexcept : ptr_(nullptr) {}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(
std::nullptr_t) noexcept // NOLINT(runtime/explicit)
: ptr_(nullptr) {}
// Implicit conversion from raw pointer.
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(
T* ptr) noexcept // NOLINT(runtime/explicit)
: ptr_(ptr) {}
// Copy and Move constructors.
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(const raw_ptr& other) noexcept =
default;
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(raw_ptr&& other) noexcept
: ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
// Templated copy/move constructors for upcasting (Derived -> Base).
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U*, T*> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(
const raw_ptr<U>& other) noexcept // NOLINT(runtime/explicit)
: ptr_(other.get()) {}
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U*, T*> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr(
raw_ptr<U>&& other) noexcept // NOLINT(runtime/explicit)
: ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
// Destructor.
OPENSCREEN_ALWAYS_INLINE constexpr ~raw_ptr() noexcept { ptr_ = nullptr; }
// Assignment operators.
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(
const raw_ptr& other) noexcept = default;
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(
raw_ptr&& other) noexcept {
if (this != &other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(T* ptr) noexcept {
ptr_ = ptr;
return *this;
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(
std::nullptr_t) noexcept {
ptr_ = nullptr;
return *this;
}
// Templated assignment operators for upcasting.
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U*, T*> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(
const raw_ptr<U>& other) noexcept {
ptr_ = other.get();
return *this;
}
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U*, T*> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator=(
raw_ptr<U>&& other) noexcept {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
return *this;
}
// Pointer operations.
OPENSCREEN_ALWAYS_INLINE constexpr T* get() const noexcept { return ptr_; }
// Disable operator* for void types to prevent illegal void* dereferences and
// void& signatures.
template <typename U = T, typename = std::enable_if_t<!std::is_void_v<U> > >
OPENSCREEN_ALWAYS_INLINE constexpr U& operator*() const noexcept {
return *ptr_;
}
OPENSCREEN_ALWAYS_INLINE constexpr T* operator->() const noexcept {
return ptr_;
}
OPENSCREEN_ALWAYS_INLINE constexpr operator T*() const noexcept {
return ptr_;
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator+=(
ptrdiff_t delta) noexcept {
ptr_ += delta;
return *this;
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr& operator-=(
ptrdiff_t delta) noexcept {
ptr_ -= delta;
return *this;
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr operator+(
ptrdiff_t delta) const noexcept {
return raw_ptr(ptr_ + delta);
}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ptr operator-(
ptrdiff_t delta) const noexcept {
return raw_ptr(ptr_ - delta);
}
OPENSCREEN_ALWAYS_INLINE constexpr explicit operator bool() const noexcept {
return ptr_ != nullptr;
}
// Swap helper.
OPENSCREEN_ALWAYS_INLINE friend constexpr void swap(raw_ptr& lhs,
raw_ptr& rhs) noexcept {
std::swap(lhs.ptr_, rhs.ptr_);
}
// Comparison operators (raw_ptr OP raw_ptr<U>).
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ == rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ != rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ < rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ > rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<=(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ <= rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>=(
const raw_ptr& lhs,
const raw_ptr<U>& rhs) noexcept {
return lhs.ptr_ >= rhs.ptr_;
}
// Comparison operators (raw_ptr OP U*).
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ == rhs;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ != rhs;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ < rhs;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ > rhs;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<=(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ <= rhs;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>=(const raw_ptr& lhs,
U* rhs) noexcept {
return lhs.ptr_ >= rhs;
}
// Comparison operators (U* OP raw_ptr).
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs == rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs != rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs < rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs > rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<=(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs <= rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>=(
U* lhs,
const raw_ptr& rhs) noexcept {
return lhs >= rhs.ptr_;
}
// Comparison operators (raw_ptr OP nullptr).
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ == nullptr;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ != nullptr;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ < nullptr;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ > nullptr;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<=(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ <= nullptr;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>=(
const raw_ptr& lhs,
std::nullptr_t) noexcept {
return lhs.ptr_ >= nullptr;
}
// Comparison operators (nullptr OP raw_ptr).
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr == rhs.ptr_;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr != rhs.ptr_;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr < rhs.ptr_;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr > rhs.ptr_;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator<=(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr <= rhs.ptr_;
}
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator>=(
std::nullptr_t,
const raw_ptr& rhs) noexcept {
return nullptr >= rhs.ptr_;
}
// Stream output helper.
template <typename CharT, typename Traits>
OPENSCREEN_ALWAYS_INLINE friend std::basic_ostream<CharT, Traits>& operator<<(
std::basic_ostream<CharT, Traits>& os,
const raw_ptr& ptr) {
return os << ptr.ptr_;
}
private:
template <typename U>
friend class raw_ptr;
#if defined(__clang__)
[[clang::annotate("raw_ptr_exclusion")]]
#endif
T* ptr_ = nullptr;
};
} // namespace openscreen
namespace std {
// Override so map/set lookups work correctly.
template <typename T>
struct less<openscreen::raw_ptr<T> > {
using is_transparent = void;
bool operator()(const openscreen::raw_ptr<T>& lhs,
const openscreen::raw_ptr<T>& rhs) const {
return lhs < rhs;
}
bool operator()(T* lhs, const openscreen::raw_ptr<T>& rhs) const {
return lhs < rhs.get();
}
bool operator()(const openscreen::raw_ptr<T>& lhs, T* rhs) const {
return lhs.get() < rhs;
}
};
// Override so unordered_map/unordered_set lookups work correctly.
template <typename T>
struct hash<openscreen::raw_ptr<T> > {
using argument_type = openscreen::raw_ptr<T>;
using result_type = std::size_t;
result_type operator()(argument_type const& ptr) const {
return hash<T*>()(ptr.get());
}
};
// Required for algorithms like std::to_address to unpack the pointer.
template <typename T>
struct pointer_traits<openscreen::raw_ptr<T> > {
using pointer = openscreen::raw_ptr<T>;
using element_type = T;
using difference_type = ptrdiff_t;
template <typename U>
using rebind = openscreen::raw_ptr<U>;
static constexpr pointer pointer_to(element_type& r) noexcept {
return pointer(&r);
}
static constexpr element_type* to_address(pointer p) noexcept {
return p.get();
}
};
} // namespace std
#endif // !defined(BUILD_WITH_CHROMIUM)
#endif // UTIL_RAW_PTR_H_

View file

@ -0,0 +1,122 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_RAW_REF_H_
#define UTIL_RAW_REF_H_
#if defined(BUILD_WITH_CHROMIUM)
#include "partition_alloc/pointers/raw_ref.h" // nogncheck
namespace openscreen {
template <typename T>
using raw_ref = ::base::raw_ref<T>;
} // namespace openscreen
#else // !defined(BUILD_WITH_CHROMIUM)
#include <type_traits>
#include <utility>
#include "util/raw_ptr.h"
namespace openscreen {
template <typename T>
class raw_ref;
namespace internal {
template <typename T>
struct is_raw_ref : std::false_type {};
template <typename T>
struct is_raw_ref<raw_ref<T> > : std::true_type {};
} // namespace internal
template <typename T>
class OPENSCREEN_TRIVIAL_ABI raw_ref {
public:
OPENSCREEN_ALWAYS_INLINE constexpr explicit raw_ref(T& ref) noexcept
: ptr_(&ref) {}
template <typename U,
typename = std::enable_if_t<
!internal::is_raw_ref<std::decay_t<U> >::value &&
std::is_convertible_v<U&, T&> > >
OPENSCREEN_ALWAYS_INLINE constexpr explicit raw_ref(U& ref) noexcept
: ptr_(&ref) {}
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref(const raw_ref& other) noexcept =
default;
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref(raw_ref&& other) noexcept =
default;
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U&, T&> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref(const raw_ref<U>& other) noexcept
: ptr_(other.ptr_) {}
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U&, T&> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref(raw_ref<U>&& other) noexcept
: ptr_(std::move(other.ptr_)) {}
~raw_ref() = default;
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref& operator=(
const raw_ref& other) noexcept = default;
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref& operator=(
raw_ref&& other) noexcept = default;
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U&, T&> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref& operator=(
const raw_ref<U>& other) noexcept {
ptr_ = other.ptr_;
return *this;
}
template <typename U,
typename = std::enable_if_t<std::is_convertible_v<U&, T&> > >
OPENSCREEN_ALWAYS_INLINE constexpr raw_ref& operator=(
raw_ref<U>&& other) noexcept {
ptr_ = std::move(other.ptr_);
return *this;
}
OPENSCREEN_ALWAYS_INLINE constexpr T& get() const noexcept { return *ptr_; }
OPENSCREEN_ALWAYS_INLINE constexpr T* operator->() const noexcept {
return ptr_.get();
}
OPENSCREEN_ALWAYS_INLINE constexpr T& operator*() const noexcept {
return *ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator==(
const raw_ref& lhs,
const raw_ref<U>& rhs) noexcept {
return lhs.ptr_ == rhs.ptr_;
}
template <typename U>
OPENSCREEN_ALWAYS_INLINE friend constexpr bool operator!=(
const raw_ref& lhs,
const raw_ref<U>& rhs) noexcept {
return lhs.ptr_ != rhs.ptr_;
}
private:
template <typename U>
friend class raw_ref;
raw_ptr<T> ptr_;
};
} // namespace openscreen
#endif // !defined(BUILD_WITH_CHROMIUM)
#endif // UTIL_RAW_REF_H_

View file

@ -0,0 +1,34 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/read_file.h"
#include <stdio.h>
namespace openscreen {
std::string ReadEntireFileToString(std::string_view filename) {
FILE* file = fopen(filename.data(), "r");
if (file == nullptr) {
return {};
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file); // NOLINT
fseek(file, 0, SEEK_SET);
std::string contents(file_size, 0);
int bytes_read = 0;
while (bytes_read < file_size) {
size_t ret = fread(&contents[bytes_read], 1, file_size - bytes_read, file);
if (ret == 0 && ferror(file)) {
return {};
} else {
bytes_read += ret;
}
}
fclose(file);
return contents;
}
} // namespace openscreen

View file

@ -0,0 +1,17 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_READ_FILE_H_
#define UTIL_READ_FILE_H_
#include <string>
#include <string_view>
namespace openscreen {
std::string ReadEntireFileToString(std::string_view filename);
} // namespace openscreen
#endif // UTIL_READ_FILE_H_

View file

@ -0,0 +1,145 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_SATURATE_CAST_H_
#define UTIL_SATURATE_CAST_H_
#include <cmath>
#include <limits>
#include <type_traits>
namespace openscreen {
// Case 0: When To and From are the same type, saturate_cast<> is pass-through.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_same<std::remove_cv<To>, std::remove_cv<From>>::value,
To>
saturate_cast(From from) {
return from;
}
// Because of the way C++ signed versus unsigned comparison works (i.e., the
// type promotion strategy employed), extra care must be taken to range-check
// the input value. For example, if the current architecture is 32-bits, then
// any int32_t compared with a uint32_t will NOT promote to a int64_t↔int64_t
// comparison. Instead, it will become a uint32_t↔uint32_t comparison (!),
// which will sometimes produce invalid results.
// Case 1: "From" and "To" are either both signed, or are both unsigned. In
// this case, the smaller of the two types will be promoted to match the
// larger's size, and a valid comparison will be made.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
(std::is_signed<From>::value == std::is_signed<To>::value),
To>
saturate_cast(From from) {
if (from <= std::numeric_limits<To>::min()) {
return std::numeric_limits<To>::min();
}
if (from >= std::numeric_limits<To>::max()) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 2: "From" is signed, but "To" is unsigned.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
std::is_signed<From>::value && !std::is_signed<To>::value,
To>
saturate_cast(From from) {
if (from <= From{0}) {
return To{0};
}
if (static_cast<std::make_unsigned_t<From>>(from) >=
std::numeric_limits<To>::max()) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 3: "From" is unsigned, but "To" is signed.
template <typename To, typename From>
constexpr std::enable_if_t<
std::is_integral<From>::value && std::is_integral<To>::value &&
!std::is_signed<From>::value && std::is_signed<To>::value,
To>
saturate_cast(From from) {
if (from >= static_cast<typename std::make_unsigned_t<To>>(
std::numeric_limits<To>::max())) {
return std::numeric_limits<To>::max();
}
return static_cast<To>(from);
}
// Case 4: "From" is a floating-point type, and "To" is an integer type (signed
// or unsigned). The result is truncated, per the usual C++ float-to-int
// conversion rules.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
std::is_integral<To>::value,
To>
saturate_cast(From from) {
// Note: It's invalid to compare the argument against
// std::numeric_limits<To>::max() because the latter, an integer value, will
// be type-promoted to the floating-point type. The problem is that the
// conversion is imprecise, as "max int" might not be exactly representable as
// a floating-point value (depending on the actual types of From and To).
//
// Thus, the strategy is to compare only floating-point values/constants to
// determine whether the bounds of the range of integers has been exceeded.
// Two assumptions here: 1) "To" is either unsigned, or is a 2's complement
// signed integer type. 2) "From" is a floating-point type that can exactly
// represent all powers of 2 within its value range.
static_assert((~To(1) + To(1)) == To(-1), "assumed 2's complement integers");
constexpr From kMaxIntPlusOne =
From(To(1) << (std::numeric_limits<To>::digits - 1)) * From(2);
constexpr From kMaxInt = kMaxIntPlusOne - 1;
// Note: In some cases, the kMaxInt constant will equal kMaxIntPlusOne because
// there isn't an exact floating-point representation for 2^N - 1. That said,
// the following upper-bound comparison is still valid because all
// floating-point values less than 2^N would also be less than 2^N - 1.
if (from >= kMaxInt) {
return std::numeric_limits<To>::max();
}
if (std::is_signed<To>::value) {
constexpr From kMinInt = -kMaxIntPlusOne;
if (from <= kMinInt) {
return std::numeric_limits<To>::min();
}
} else /* if To is unsigned */ {
if (from <= From(0)) {
return To(0);
}
}
return static_cast<To>(from);
}
// Like saturate_cast<>, but rounds to the nearest integer instead of
// truncating.
template <typename To, typename From>
constexpr std::enable_if_t<std::is_floating_point<From>::value &&
std::is_integral<To>::value,
To>
rounded_saturate_cast(From from) {
const To saturated = saturate_cast<To>(from);
if (saturated == std::numeric_limits<To>::min() ||
saturated == std::numeric_limits<To>::max()) {
return saturated;
}
static_assert(sizeof(To) <= sizeof(decltype(llround(from))),
"No version of lround() for the required range of values.");
if (sizeof(To) <= sizeof(decltype(lround(from)))) {
return static_cast<To>(lround(from));
}
return static_cast<To>(llround(from));
}
} // namespace openscreen
#endif // UTIL_SATURATE_CAST_H_

View file

@ -0,0 +1,12 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/scoped_wake_lock.h"
namespace openscreen {
ScopedWakeLock::ScopedWakeLock() = default;
ScopedWakeLock::~ScopedWakeLock() = default;
} // namespace openscreen

View file

@ -0,0 +1,46 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_SCOPED_WAKE_LOCK_H_
#define UTIL_SCOPED_WAKE_LOCK_H_
#include <memory>
#include "platform/api/task_runner.h"
#include "platform/api/task_runner_deleter.h"
namespace openscreen {
// Ensures that the device does not got to sleep. This is used, for example,
// while Open Screen is communicating with peers over the network for things
// like media streaming.
//
// The wake lock is RAII: It is automatically engaged when the ScopedWakeLock is
// created and released when the ScopedWakeLock is destroyed. Open Screen code
// may sometimes create multiple instances. In that case, the wake lock should
// be engaged upon creating the first instance, and then held until all
// instances have been destroyed.
//
// TODO(issuetracker.google.com/288311411): Implement for Linux.
class ScopedWakeLock;
using ScopedWakeLockPtr = std::unique_ptr<ScopedWakeLock, TaskRunnerDeleter>;
class ScopedWakeLock {
public:
static ScopedWakeLockPtr Create(TaskRunner& task_runner);
// Instances are not copied nor moved.
ScopedWakeLock(const ScopedWakeLock&) = delete;
ScopedWakeLock(ScopedWakeLock&&) noexcept = delete;
ScopedWakeLock& operator=(const ScopedWakeLock&) = delete;
ScopedWakeLock& operator=(ScopedWakeLock&&) noexcept = delete;
ScopedWakeLock();
virtual ~ScopedWakeLock();
};
} // namespace openscreen
#endif // UTIL_SCOPED_WAKE_LOCK_H_

View file

@ -0,0 +1,52 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/simple_fraction.h"
#include <cmath>
#include <limits>
#include <string>
#include <vector>
#include "util/osp_logging.h"
#include "util/string_parse.h"
#include "util/string_util.h"
#include "util/stringprintf.h"
namespace openscreen {
// static
ErrorOr<SimpleFraction> SimpleFraction::FromString(std::string_view value) {
if (value.size() > 0 && value.at(0) == '/') {
return Error::Code::kParameterInvalid;
}
std::vector<std::string_view> fields = string_util::Split(value, '/');
if (fields.size() != 1 && fields.size() != 2) {
return Error::Code::kParameterInvalid;
}
int numerator;
int denominator = 1;
if (!string_parse::ParseAsciiNumber(fields[0], numerator)) {
return Error::Code::kParameterInvalid;
}
if (fields.size() == 2) {
if (!string_parse::ParseAsciiNumber(fields[1], denominator)) {
return Error::Code::kParameterInvalid;
}
}
return SimpleFraction(numerator, denominator);
}
std::string SimpleFraction::ToString() const {
if (denominator_ == 1) {
return std::to_string(numerator_);
}
return StringFormat("{}/{}", numerator_, denominator_);
}
} // namespace openscreen

View file

@ -0,0 +1,73 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_SIMPLE_FRACTION_H_
#define UTIL_SIMPLE_FRACTION_H_
#include <cmath>
#include <limits>
#include <string>
#include <string_view>
#include "platform/base/error.h"
namespace openscreen {
// SimpleFraction is used to represent simple (or "common") fractions, composed
// of a rational number written a/b where a and b are both integers.
// Some helpful notes on SimpleFraction assumptions/limitations:
// 1. SimpleFraction does not perform reductions. 2/4 != 1/2, and -1/-1 != 1/1.
// 2. denominator = 0 is considered undefined.
// 3. numerator = saturates range to int min or int max
// 4. A SimpleFraction is "positive" if and only if it is defined and at least
// equal to zero. Since reductions are not performed, -1/-1 is negative.
class SimpleFraction {
public:
static ErrorOr<SimpleFraction> FromString(std::string_view value);
std::string ToString() const;
constexpr SimpleFraction() = default;
constexpr SimpleFraction(int numerator) // NOLINT
: numerator_(numerator) {}
constexpr SimpleFraction(int numerator, int denominator)
: numerator_(numerator), denominator_(denominator) {}
constexpr SimpleFraction(const SimpleFraction&) = default;
constexpr SimpleFraction(SimpleFraction&&) noexcept = default;
constexpr SimpleFraction& operator=(const SimpleFraction&) = default;
constexpr SimpleFraction& operator=(SimpleFraction&&) = default;
~SimpleFraction() = default;
constexpr bool operator==(const SimpleFraction& other) const {
return numerator_ == other.numerator_ && denominator_ == other.denominator_;
}
constexpr bool operator!=(const SimpleFraction& other) const {
return !(*this == other);
}
constexpr bool is_defined() const { return denominator_ != 0; }
constexpr bool is_positive() const {
return (numerator_ >= 0) && (denominator_ > 0);
}
constexpr explicit operator double() const {
if (denominator_ == 0) {
return nan("");
}
return static_cast<double>(numerator_) / static_cast<double>(denominator_);
}
constexpr int numerator() const { return numerator_; }
constexpr int denominator() const { return denominator_; }
private:
int numerator_ = 0;
int denominator_ = 1;
};
} // namespace openscreen
#endif // UTIL_SIMPLE_FRACTION_H_

View file

@ -0,0 +1,20 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/std_util.h"
#include <algorithm>
#include <cctype>
#include <string>
#include "util/osp_logging.h"
namespace openscreen {
std::string& RemoveWhitespace(std::string& s) {
s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end());
return s;
}
} // namespace openscreen

View file

@ -0,0 +1,104 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_STD_UTIL_H_
#define UTIL_STD_UTIL_H_
#include <stddef.h>
#include <algorithm>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "util/stringprintf.h"
namespace openscreen {
template <typename T, size_t N>
constexpr size_t countof(T (&array)[N]) {
return N;
}
// Removes ALL whitespace in place from the string, based on the present C
// locale. This includes spaces, tabs, and returns. This is useful for string
// comparisons where whitespace doesn't matter, or, in the case of JSON
// serialization, is dependent on build configuration and other settings.
std::string& RemoveWhitespace(std::string& s);
template <typename Key, typename Value>
void RemoveValueFromMap(std::map<Key, Value*>* map, Value* value) {
for (auto it = map->begin(); it != map->end();) {
if (it->second == value) {
it = map->erase(it);
} else {
++it;
}
}
}
template <typename ForwardIteratingContainer>
bool AreElementsSortedAndUnique(const ForwardIteratingContainer& c) {
return std::is_sorted(c.begin(), c.end()) &&
std::adjacent_find(c.begin(), c.end()) == c.end();
}
template <typename RandomAccessContainer>
void SortAndDedupeElements(RandomAccessContainer* c) {
std::sort(c->begin(), c->end());
const auto new_end = std::unique(c->begin(), c->end());
c->erase(new_end, c->end());
}
// Append the provided elements together into a single vector. This can be
// useful when creating a vector of variadic templates in the ctor.
//
// This is the base case for the recursion
template <typename T>
std::vector<T>&& Append(std::vector<T>&& so_far) {
return std::move(so_far);
}
// This is the recursive call. Depending on the number of remaining elements, it
// either calls into itself or into the above base case.
template <typename T, typename TFirst, typename... TOthers>
std::vector<T>&& Append(std::vector<T>&& so_far,
TFirst&& new_element,
TOthers&&... new_elements) {
so_far.push_back(std::move(new_element));
return Append(std::move(so_far), std::move(new_elements)...);
}
// Creates an empty vector with `size` elements reserved. Intended to be used as
// GetEmptyVectorOfSize<T>(sizeof...(variadic_input))
template <typename T>
std::vector<T> GetVectorWithCapacity(size_t size) {
std::vector<T> results;
results.reserve(size);
return results;
}
// Returns true if an element equal to `element` is found in `container`.
// C.begin() must return an iterator to the beginning of C and C.end() must
// return an iterator to the end.
template <typename C, typename E>
bool Contains(const C& container, const E& element) {
return std::find(container.begin(), container.end(), element) !=
container.end();
}
// Returns true if any element in `container` returns true for `predicate`.
// C.begin() must return an iterator to the beginning of C and C.end() must
// return an iterator to the end.
template <typename C, typename P>
bool ContainsIf(const C& container, P predicate) {
return std::find_if(container.begin(), container.end(),
std::move(predicate)) != container.end();
}
} // namespace openscreen
#endif // UTIL_STD_UTIL_H_

View file

@ -0,0 +1,32 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_STRING_PARSE_H_
#define UTIL_STRING_PARSE_H_
#include <charconv>
#include <optional>
#include <string_view>
#include <system_error>
#include "platform/base/type_util.h"
namespace openscreen::string_parse {
// Parses `number` into the numeric type `result` and returns true if
// successful. `number` must be an ASCII representation of an integer or
// floating point value, and `result` must be compatible with the resulting
// value. If `number` cannot be parsed, then returns false.
template <typename T, typename = internal::EnableIfArithmetic<T>>
bool ParseAsciiNumber(std::string_view number, T& result) {
if (number.empty())
return false;
auto [unused_ptr, error_code] =
std::from_chars(number.data(), &number.back() + 1, result);
return error_code == std::errc();
}
} // namespace openscreen::string_parse
#endif // UTIL_STRING_PARSE_H_

View file

@ -0,0 +1,160 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/string_util.h"
#include <functional>
#include <ranges>
namespace openscreen::string_util {
namespace internal {
// clang-format off
// Array of bitfields holding character information. Note that bitfields for all
// characters above ASCII 127 are zero-initialized.
// Position Meaning
// -------- -------
// 1 alphabetic
// 2 alphanumeric
// 3 whitespace
// 4 punctuation
// 5 tab or space
// 6 control character
// 7 hex digit
const unsigned char kPropertyBits[256] = {
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x00
0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x10
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, // 0x20
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, // 0x30
0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x40
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x50
0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10,
0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05, // 0x60
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, // 0x70
0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40,
};
// Array of characters for the ascii_tolower() function.
const char kToLower[256] = {
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
'\x40', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f',
'\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87',
'\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f',
'\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97',
'\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f',
'\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7',
'\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf',
'\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7',
'\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf',
'\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7',
'\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf',
'\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7',
'\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf',
'\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7',
'\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef',
'\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7',
'\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff',
};
// Array of characters for the ascii_toupper() function.
const char kToUpper[256] = {
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
'\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
'\x60', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f',
'\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87',
'\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f',
'\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97',
'\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f',
'\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7',
'\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf',
'\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7',
'\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf',
'\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7',
'\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf',
'\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7',
'\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf',
'\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7',
'\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef',
'\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7',
'\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff',
};
// clang-format on
} // namespace internal
void AsciiStrToLower(std::string& s) {
for (auto& c : s)
c = ascii_tolower(c);
}
std::string AsciiStrToLower(std::string_view s) {
std::string result(s);
AsciiStrToLower(result);
return result;
}
void AsciiStrToUpper(std::string& s) {
for (auto& c : s)
c = ascii_toupper(c);
}
std::string AsciiStrToUpper(std::string_view s) {
std::string result(s);
AsciiStrToUpper(result);
return result;
}
[[nodiscard]] bool EqualsIgnoreCase(std::string_view a, std::string_view b) {
// std::ranges::equal checks size() automatically for random-access ranges
// like std::string_view.
return std::ranges::equal(
a, b, std::equal_to<>{}, // 1. The Predicate: Compare for equality
ascii_tolower, // 2. Projection for piece1
ascii_tolower // 3. Projection for piece2
);
}
[[nodiscard]] std::vector<std::string_view> Split(std::string_view value,
char delim) {
auto tokens = value | std::views::split(delim) |
std::views::filter([](auto&& r) { return !r.empty(); });
std::vector<std::string_view> result;
for (auto&& token : tokens) {
result.emplace_back(token.begin(), token.end());
}
return result;
}
} // namespace openscreen::string_util

View file

@ -0,0 +1,142 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_STRING_UTIL_H_
#define UTIL_STRING_UTIL_H_
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include <numeric>
#include <ranges>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
// String query and manipulation utilities.
// TODO(jophba): remove nested string_util namespace.
namespace openscreen::string_util {
namespace internal {
extern const unsigned char kPropertyBits[256];
extern const char kToLower[256];
extern const char kToUpper[256];
} // namespace internal
// Determines whether `c` is a valid ASCII alphabetic character code.
inline bool ascii_isalpha(unsigned char c) {
return (internal::kPropertyBits[c] & 0x01) != 0;
}
// Determines whether `c` is a valid ASCII decimal digit (i.e. [0-9]).
inline bool ascii_isdigit(unsigned char c) {
return '0' <= c && c <= '9';
}
// Determines whether `c` is a valid ASCII lower case hexadecimal digit
// (i.e. [a-fA-F0-9]).
inline bool ascii_islowerhex(unsigned char c) {
return ascii_isdigit(c) || ('a' <= c && c <= 'f');
}
// Determines whether `c` is a valid ASCII hexadecimal digit (i.e. [a-fA-F0-9]).
inline bool ascii_ishex(unsigned char c) {
return ascii_islowerhex(c) || ('A' <= c && c <= 'F');
}
// Determines whether `c` is a valid, printable ASCII digit.
inline bool ascii_isprint(unsigned char c) {
return c >= 32 && c < 127;
}
// Determines whether `c` is a whitespace character
// (space, tab, vertical tab, formfeed, linefeed, or carriage return).
inline bool ascii_isspace(unsigned char c) {
return (internal::kPropertyBits[c] & 0x08) != 0;
}
// If `c` is an upper case ASCII character, returns its lower case equivalent.
// Otherwise, returns `c` unchanged.
inline char ascii_tolower(unsigned char c) {
return internal::kToLower[c];
}
// Converts `s` to lowercase.
void AsciiStrToLower(std::string& s);
// Creates a lowercase string from a given string_view.
std::string AsciiStrToLower(std::string_view s);
inline char ascii_toupper(unsigned char c) {
return internal::kToUpper[c];
}
// Converts `s` to uppercase.
void AsciiStrToUpper(std::string& s);
// Creates a uppercase string from a given string_view.
std::string AsciiStrToUpper(std::string_view s);
// Returns whether given ASCII strings `a` and `b` are equal, ignoring
// case in the comparison.
[[nodiscard]] bool EqualsIgnoreCase(std::string_view a, std::string_view b);
// Returns std::string_view with whitespace stripped from the beginning of the
// given string_view.
inline std::string_view StripLeadingAsciiWhitespace(std::string_view str) {
auto it = std::find_if_not(str.cbegin(), str.cend(), ascii_isspace);
return str.substr(static_cast<size_t>(it - str.begin()));
}
// Concatenates arguments into a single string.
[[nodiscard]] constexpr std::string StrCat(
std::initializer_list<std::string_view> pieces) {
// Prefer a loop over std::accumulate since it is not constexpr in C++20.
size_t length = 0;
for (const auto& piece : pieces) {
length += piece.size();
}
std::string result;
result.reserve(length);
for (const auto& piece : pieces) {
result.append(piece);
}
return result;
}
// Splits `value` into tokens separated by `delim`. Leading and trailing
// delimeters are stripped, and multiple consecutive delimeters are treated as
// one.
[[nodiscard]] std::vector<std::string_view> Split(std::string_view value,
char delim);
template <std::ranges::input_range R>
[[nodiscard]] std::string Join(R&& range, std::string_view delimeter = ", ") {
if (std::ranges::empty(range)) {
return {};
}
std::stringstream ss;
ss << range.front();
for (auto element : range | std::views::drop(1)) {
ss << delimeter << element;
}
return ss.str();
}
// Returns a string made by concatenating the strings iterated by `[begin,
// end)`, each separated by `delim`.
template <typename Iterator>
[[nodiscard]] std::string Join(Iterator begin,
Iterator end,
std::string_view delimeter = ", ") {
return Join(std::ranges::subrange{begin, end}, delimeter);
}
} // namespace openscreen::string_util
#endif // UTIL_STRING_UTIL_H_

View file

@ -0,0 +1,29 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/stringprintf.h"
#include <cstdarg>
#include <cstdio>
#include <iomanip>
#include <sstream>
#include "util/osp_logging.h"
namespace openscreen {
std::string HexEncode(const uint8_t* bytes, size_t len) {
return HexEncode(ByteView(bytes, len));
}
std::string HexEncode(ByteView bytes) {
std::ostringstream hex_dump;
hex_dump << std::setfill('0') << std::hex;
for (uint8_t byte : bytes) {
hex_dump << std::setw(2) << static_cast<int>(byte);
}
return hex_dump.str();
}
} // namespace openscreen

View file

@ -0,0 +1,33 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_STRINGPRINTF_H_
#define UTIL_STRINGPRINTF_H_
#include <stdint.h>
#include <format>
#include <ostream>
#include <string>
#include <utility>
#include "platform/base/span.h"
namespace openscreen {
// TODO(crbug.com/364687926): remove and replace with direct calls to
// std::format now that we are on C++20.
template <typename... Args>
[[nodiscard]] std::string StringFormat(std::format_string<Args...> fmt,
Args&&... args) {
return std::format(fmt, std::forward<Args>(args)...);
}
// Returns a hex string representation of the given `bytes`.
std::string HexEncode(const uint8_t* bytes, size_t len);
std::string HexEncode(ByteView bytes);
} // namespace openscreen
#endif // UTIL_STRINGPRINTF_H_

View file

@ -0,0 +1,50 @@
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_THREAD_ANNOTATIONS_H_
#define UTIL_THREAD_ANNOTATIONS_H_
#if defined(__clang__) && !defined(SWIG)
#define OSP_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define OSP_THREAD_ANNOTATION_ATTRIBUTE__(x)
#endif
#define OSP_GUARDED_BY(x) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define OSP_PT_GUARDED_BY(x) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define OSP_EXCLUSIVE_LOCKS_REQUIRED(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
#define OSP_SHARED_LOCKS_REQUIRED(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
#define OSP_EXCLUSIVE_LOCK_FUNCTION(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
#define OSP_SHARED_LOCK_FUNCTION(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
#define OSP_UNLOCK_FUNCTION(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
#define OSP_LOCKS_EXCLUDED(...) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
#define OSP_LOCK_RETURNED(x) \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#define OSP_LOCKABLE \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(lockable)
#define OSP_SCOPED_LOCKABLE \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
#define OSP_NO_THREAD_SAFETY_ANALYSIS \
OSP_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
#endif // UTIL_THREAD_ANNOTATIONS_H_

View file

@ -0,0 +1,282 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_TRACE_LOGGING_H_
#define UTIL_TRACE_LOGGING_H_
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "platform/base/trace_logging_types.h"
// All compile-time macros for tracing.
// NOTE: The ternary operator is used here to ensure that the TraceLogger object
// is only constructed if tracing is enabled, but at the same time is created in
// the caller's scope. The C++ standards guide guarantees that the constructor
// should only be called when IsTraceLoggingEnabled(...) evaluates to true.
// static_cast calls are used because if the type of the result of the ternary
// operator does not match the expected type, temporary storage is used for the
// created object, which results in an extra call to the constructor and
// destructor of the tracing objects.
//
// Further details about how these macros are used can be found in
// docs/trace_logging.md.
#if defined(ENABLE_TRACE_LOGGING)
#define INCLUDING_FROM_UTIL_TRACE_LOGGING_H_
#include "util/trace_logging/macro_support.h"
#undef INCLUDING_FROM_UTIL_TRACE_LOGGING_H_
#define TRACE_SET_RESULT(result) \
do { \
if (TRACE_IS_ENABLED(openscreen::TraceCategory::kAny)) { \
openscreen::internal::ScopedTraceOperation::set_result(result); \
} \
} while (false)
#define TRACE_SET_HIERARCHY(ids) TRACE_SET_HIERARCHY_INTERNAL(__LINE__, ids)
#define TRACE_HIERARCHY \
(TRACE_IS_ENABLED(openscreen::TraceCategory::kAny) \
? openscreen::internal::ScopedTraceOperation::hierarchy() \
: openscreen::TraceIdHierarchy::Empty())
#define TRACE_CURRENT_ID \
(TRACE_IS_ENABLED(openscreen::TraceCategory::kAny) \
? openscreen::internal::ScopedTraceOperation::current_id() \
: kEmptyTraceId)
#define TRACE_ROOT_ID \
(TRACE_IS_ENABLED(openscreen::TraceCategory::kAny) \
? openscreen::internal::ScopedTraceOperation::root_id() \
: kEmptyTraceId)
namespace openscreen::internal {
template <typename T>
std::string ToString(T&& val) {
using DecayT = std::decay_t<T>;
if constexpr (std::is_constructible_v<std::string, T>) {
return std::string(std::forward<T>(val));
} else if constexpr (std::is_arithmetic_v<DecayT>) {
return std::to_string(val);
} else {
std::ostringstream oss;
oss << val;
return oss.str();
}
}
// Helper to extract a flow ID from various types (arithmetic or wrappers like
// FrameId).
template <typename T>
constexpr uint64_t ToFlowId(const T& val) {
if constexpr (std::is_arithmetic_v<T>) {
return static_cast<uint64_t>(val);
} else {
// Assume it's a numeric wrapper like FrameId with a .value() method.
return static_cast<uint64_t>(val.value());
}
}
} // namespace openscreen::internal
template <typename V1 = std::string, typename V2 = std::string>
inline std::vector<openscreen::TraceEvent::Argument> ToArgumentArray(
const char* argname = nullptr,
V1&& argval = V1(),
const char* argname_two = nullptr,
V2&& argval_two = V2()) {
std::vector<openscreen::TraceEvent::Argument> out;
if (argname) {
out.emplace_back(argname,
openscreen::internal::ToString(std::forward<V1>(argval)));
}
if (argname_two) {
out.emplace_back(argname_two, openscreen::internal::ToString(
std::forward<V2>(argval_two)));
}
return out;
}
// Synchronous Trace Macros.
//
// Scoped traces with no arguments.
#define TRACE_SCOPED(category, name, ...) \
TRACE_SCOPED_INTERNAL(__LINE__, category, name, ToArgumentArray(), \
##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED(category, ...) \
TRACE_SCOPED(category, __PRETTY_FUNCTION__, ##__VA_ARGS__)
// Scoped traces with one argument.
#define TRACE_SCOPED1(category, name, argname, argval, ...) \
TRACE_SCOPED_INTERNAL(__LINE__, category, name, \
ToArgumentArray(argname, argval), ##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED1(category, argname, argval, ...) \
TRACE_SCOPED1(category, __PRETTY_FUNCTION__, argname, argval, ##__VA_ARGS__)
// Scoped traces with two arguments.
#define TRACE_SCOPED2(category, name, argname, argval, argname_two, \
argval_two, ...) \
TRACE_SCOPED_INTERNAL( \
__LINE__, category, name, \
ToArgumentArray(argname, argval, argname_two, argval_two), \
##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED2(category, argname, argval, argname_two, \
argval_two, ...) \
TRACE_SCOPED2(category, __PRETTY_FUNCTION__, argname, argval, argname_two, \
argval_two, ##__VA_ARGS__)
// Asynchronous Trace Macros.
#define TRACE_ASYNC_START(category, name, ...) \
TRACE_ASYNC_START_INTERNAL(__LINE__, category, name, ToArgumentArray(), \
##__VA_ARGS__)
#define TRACE_ASYNC_START1(category, name, argname, argval, ...) \
TRACE_ASYNC_START_INTERNAL(__LINE__, category, name, \
ToArgumentArray(argname, argval), ##__VA_ARGS__)
#define TRACE_ASYNC_START2(category, name, argname, argval, argname_two, \
argval_two, ...) \
TRACE_ASYNC_START_INTERNAL( \
__LINE__, category, name, \
ToArgumentArray(argname, argval, argname_two, argval_two), \
##__VA_ARGS__)
#define TRACE_ASYNC_END(category, id, result) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceAsyncEnd( \
__LINE__, __FILE__, id, result) \
: false
// Flow events are used to link trace events across different threads or
// processes. Flows are linked by their flow_id.
// - Flows can span across different trace categories.
// - If a TRACE_FLOW_BEGIN is missing (e.g. because the embedder didn't
// instrument it),
// the first TRACE_FLOW_STEP encountered will effectively start the flow
// visualization.
#define TRACE_FLOW_BEGIN(category, name, flow_id) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowBegin) \
: false
#define TRACE_FLOW_STEP(category, name, flow_id) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowStep) \
: false
#define TRACE_FLOW_END(category, name, flow_id) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowEnd) \
: false
#define TRACE_FLOW_BEGIN_WITH_TIME(category, name, flow_id, timestamp) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowBegin, timestamp) \
: false
#define TRACE_FLOW_STEP_WITH_TIME(category, name, flow_id, timestamp) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowStep, timestamp) \
: false
#define TRACE_FLOW_END_WITH_TIME(category, name, flow_id, timestamp) \
TRACE_IS_ENABLED(category) \
? openscreen::internal::ScopedTraceOperation::TraceFlow( \
category, name, __FILE__, __LINE__, \
openscreen::internal::ToFlowId(flow_id), \
openscreen::FlowType::kFlowEnd, timestamp) \
: false
#define TRACE_FLOW_DEFAULT_BEGIN(category, flow_id) \
TRACE_FLOW_BEGIN(category, __PRETTY_FUNCTION__, flow_id)
#define TRACE_FLOW_DEFAULT_STEP(category, flow_id) \
TRACE_FLOW_STEP(category, __PRETTY_FUNCTION__, flow_id)
#define TRACE_FLOW_DEFAULT_END(category, flow_id) \
TRACE_FLOW_END(category, __PRETTY_FUNCTION__, flow_id)
#else // ENABLE_TRACE_LOGGING not defined
namespace openscreen::internal {
// Consumes `args` (to avoid "warn unused variable" errors at compile time), and
// provides a "void" result type in the macros below.
template <typename... Args>
inline void DoNothingForTracing(Args... args) {}
} // namespace openscreen::internal
#define TRACE_SET_RESULT(result) \
openscreen::internal::DoNothingForTracing(result)
#define TRACE_SET_HIERARCHY(ids) openscreen::internal::DoNothingForTracing(ids)
#define TRACE_HIERARCHY openscreen::TraceIdHierarchy::Empty()
#define TRACE_CURRENT_ID openscreen::kEmptyTraceId
#define TRACE_ROOT_ID openscreen::kEmptyTraceId
#define TRACE_SCOPED(category, name, ...) \
openscreen::internal::DoNothingForTracing(category, name, ##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED(category, ...) \
TRACE_SCOPED(category, __PRETTY_FUNCTION__, ##__VA_ARGS__)
#define TRACE_SCOPED1(category, name, argname, argval, ...) \
openscreen::internal::DoNothingForTracing(category, name, argname, argval, \
##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED1(category, argname, argval, ...) \
TRACE_SCOPED1(category, __PRETTY_FUNCTION__, argname, argval, ##__VA_ARGS__)
#define TRACE_SCOPED2(category, name, argname, argval, argname_two, \
argval_two, ...) \
openscreen::internal::DoNothingForTracing( \
category, name, argname, argval, argname_two, argval_two, ##__VA_ARGS__)
#define TRACE_DEFAULT_SCOPED2(category, argname, argval, argname_two, \
argval_two, ...) \
TRACE_SCOPED2(category, __PRETTY_FUNCTION__, argname, argval, argname_two, \
argval_two, ##__VA_ARGS__)
#define TRACE_ASYNC_START(category, name, ...) \
openscreen::internal::DoNothingForTracing(category, name, ##__VA_ARGS__)
#define TRACE_ASYNC_END(category, id, result) \
openscreen::internal::DoNothingForTracing(category, id, result)
#define TRACE_FLOW_BEGIN(category, name, flow_id) \
openscreen::internal::DoNothingForTracing(category, name, flow_id)
#define TRACE_FLOW_STEP(category, name, flow_id) \
openscreen::internal::DoNothingForTracing(category, name, flow_id)
#define TRACE_FLOW_END(category, name, flow_id) \
openscreen::internal::DoNothingForTracing(category, name, flow_id)
#define TRACE_FLOW_BEGIN_WITH_TIME(category, name, flow_id, timestamp) \
openscreen::internal::DoNothingForTracing(category, name, flow_id, timestamp)
#define TRACE_FLOW_STEP_WITH_TIME(category, name, flow_id, timestamp) \
openscreen::internal::DoNothingForTracing(category, name, flow_id, timestamp)
#define TRACE_FLOW_END_WITH_TIME(category, name, flow_id, timestamp) \
openscreen::internal::DoNothingForTracing(category, name, flow_id, timestamp)
#define TRACE_FLOW_DEFAULT_BEGIN(category, flow_id) \
openscreen::internal::DoNothingForTracing(category, flow_id)
#define TRACE_FLOW_DEFAULT_STEP(category, flow_id) \
openscreen::internal::DoNothingForTracing(category, flow_id)
#define TRACE_FLOW_DEFAULT_END(category, flow_id) \
openscreen::internal::DoNothingForTracing(category, flow_id)
#endif // defined(ENABLE_TRACE_LOGGING)
#endif // UTIL_TRACE_LOGGING_H_

View file

@ -0,0 +1,84 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_
#define UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_
#ifndef INCLUDING_FROM_UTIL_TRACE_LOGGING_H_
#error "Do not include this header directly. Use util/trace_logging.h."
#endif
#ifndef ENABLE_TRACE_LOGGING
#error "BUG: This file should not have been reached."
#endif
#include "platform/api/trace_logging_platform.h"
#include "platform/base/trace_logging_activation.h"
#include "platform/base/trace_logging_types.h"
#include "util/trace_logging/scoped_trace_operations.h"
// Helper macros. These are used to simplify the macros below.
// NOTE: These cannot be #undef'd or they will stop working outside this file.
// NOTE: Two of these below macros are intentionally the same. This is to work
// around optimizations in the C++ Precompiler.
#define TRACE_INTERNAL_CONCAT(a, b) a##b
#define TRACE_INTERNAL_CONCAT_CONST(a, b) TRACE_INTERNAL_CONCAT(a, b)
#define TRACE_INTERNAL_UNIQUE_VAR_NAME(a) \
TRACE_INTERNAL_CONCAT_CONST(a, __LINE__)
namespace openscreen::internal {
inline bool IsTraceLoggingEnabled(TraceCategory category) {
const CurrentTracingDestination destination;
return destination && destination->IsTraceLoggingEnabled(category);
}
} // namespace openscreen::internal
#define TRACE_IS_ENABLED(category) \
openscreen::internal::IsTraceLoggingEnabled(category)
// Internal logging macros.
#define TRACE_SET_HIERARCHY_INTERNAL(line, ids) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
tracing_storage, line)[sizeof(openscreen::internal::TraceIdSetter)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(openscreen::TraceCategory::kAny) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::TraceIdSetter>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(tracing_storage, line), \
ids) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::TraceIdSetter>::Empty()
#define TRACE_SCOPED_INTERNAL(line, category, name, ...) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
tracing_storage, \
line)[sizeof(openscreen::internal::SynchronousTraceLogger)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(category) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::SynchronousTraceLogger>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(tracing_storage, line), \
category, name, __FILE__, __LINE__, ##__VA_ARGS__) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::SynchronousTraceLogger>::Empty()
#define TRACE_ASYNC_START_INTERNAL(line, category, name, ...) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
temp_storage, \
line)[sizeof(openscreen::internal::AsynchronousTraceLogger)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(category) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::AsynchronousTraceLogger>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(temp_storage, line), \
category, name, __FILE__, __LINE__, ##__VA_ARGS__) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::AsynchronousTraceLogger>::Empty()
#endif // UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_

View file

@ -0,0 +1,160 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/trace_logging/scoped_trace_operations.h"
#include "platform/api/trace_logging_platform.h"
#include "platform/base/trace_logging_activation.h"
#include "util/osp_logging.h"
#if defined(ENABLE_TRACE_LOGGING)
namespace openscreen::internal {
// static
bool ScopedTraceOperation::TraceAsyncEnd(const uint32_t line,
const char* file,
TraceId id,
Error::Code e) {
const CurrentTracingDestination destination;
if (destination) {
TraceEvent end_event;
end_event.start_time = Clock::now();
end_event.line_number = line;
end_event.file_name = file;
end_event.ids.current = id;
end_event.result = e;
destination->LogAsyncEnd(std::move(end_event));
return true;
}
return false;
}
// static
bool ScopedTraceOperation::TraceFlow(
TraceCategory category,
const char* name,
const char* file,
uint32_t line,
uint64_t flow_id,
FlowType type,
std::optional<Clock::time_point> timestamp) {
const CurrentTracingDestination destination;
if (destination) {
const auto start_time = timestamp ? *timestamp : Clock::now();
TraceEvent event(category, start_time, name, file, line);
event.flow_ids.push_back(flow_id);
destination->LogFlow(std::move(event), type);
return true;
}
return false;
}
ScopedTraceOperation::ScopedTraceOperation(TraceId trace_id,
TraceId parent_id,
TraceId root_id) {
if (traces_ == nullptr) {
// Create the stack if it doesnt' exist.
traces_ = new TraceStack();
// Create a new root node. This will re-call this constructor and add the
// root node to the stack before proceeding with the original node.
root_node_ = new TraceIdSetter(TraceIdHierarchy::Empty());
OSP_CHECK(!traces_->empty());
}
// Setting trace id fields.
root_id_ = root_id != kUnsetTraceId ? root_id : traces_->top()->root_id_;
parent_id_ =
parent_id != kUnsetTraceId ? parent_id : traces_->top()->trace_id_;
trace_id_ =
trace_id != kUnsetTraceId ? trace_id : trace_id_counter_.fetch_add(1);
// Add this item to the stack.
traces_->push(this);
OSP_CHECK_LT(traces_->size(), 1024);
}
ScopedTraceOperation::~ScopedTraceOperation() {
OSP_CHECK(traces_ != nullptr && !traces_->empty());
OSP_CHECK_EQ(traces_->top(), this);
traces_->pop();
// If there's only one item left, it must be the root node. Deleting the root
// node will re-call this destructor and delete the traces_ stack.
if (traces_->size() == 1) {
OSP_CHECK_EQ(traces_->top(), root_node_);
delete root_node_;
root_node_ = nullptr;
} else if (traces_->empty()) {
delete traces_;
traces_ = nullptr;
}
}
// static
thread_local ScopedTraceOperation::TraceStack* ScopedTraceOperation::traces_ =
nullptr;
// static
thread_local ScopedTraceOperation* ScopedTraceOperation::root_node_ = nullptr;
// static
std::atomic<std::uint64_t> ScopedTraceOperation::trace_id_counter_{
uint64_t{0x01} << (sizeof(TraceId) * 8 - 1)};
TraceLoggerBase::TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceId current,
TraceId parent,
TraceId root)
: ScopedTraceOperation(current, parent, root),
event_(category, Clock::now(), name, file, line) {
event_.arguments = std::move(arguments);
event_.TruncateStrings();
}
TraceLoggerBase::TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceIdHierarchy ids)
: TraceLoggerBase(category,
name,
file,
line,
std::move(arguments),
ids.current,
ids.parent,
ids.root) {}
SynchronousTraceLogger::~SynchronousTraceLogger() {
const CurrentTracingDestination destination;
if (destination) {
const auto end_time = Clock::now();
event_.ids = to_hierarchy();
destination->LogTrace(event_, end_time);
}
}
AsynchronousTraceLogger::~AsynchronousTraceLogger() {
const CurrentTracingDestination destination;
if (destination) {
event_.ids = to_hierarchy();
destination->LogAsyncStart(event_);
}
}
TraceIdSetter::TraceIdSetter(TraceIdHierarchy ids)
: ScopedTraceOperation(ids.current, ids.parent, ids.root) {}
TraceIdSetter::~TraceIdSetter() = default;
} // namespace openscreen::internal
#endif // defined(ENABLE_TRACE_LOGGING)

View file

@ -0,0 +1,224 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_
#define UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_
#include <atomic>
#include <cstring>
#include <memory>
#include <optional>
#include <stack>
#include <utility>
#include <vector>
#include "platform/api/time.h"
#include "platform/api/trace_logging_platform.h"
#include "platform/base/error.h"
#include "platform/base/trace_logging_types.h"
#include "util/osp_logging.h"
#if defined(ENABLE_TRACE_LOGGING)
namespace openscreen::internal {
// A base class for all trace logging objects which will create new entries in
// the Trace Hierarchy.
// 1) The sharing of all static and thread_local variables across template
// specializations.
// 2) Including all children in the same traces vector.
class ScopedTraceOperation {
public:
// Define the destructor to remove this item from the stack when it's
// destroyed.
virtual ~ScopedTraceOperation();
ScopedTraceOperation(const ScopedTraceOperation&) = delete;
ScopedTraceOperation(ScopedTraceOperation&&) noexcept = delete;
ScopedTraceOperation& operator=(const ScopedTraceOperation&) = delete;
ScopedTraceOperation& operator=(ScopedTraceOperation&&) = delete;
// Getters the current Trace Hierarchy. If the traces_ stack hasn't been
// created yet, return as if the empty root node is there.
static TraceId current_id() {
return traces_ == nullptr ? kEmptyTraceId : traces_->top()->trace_id_;
}
static TraceId root_id() {
return traces_ == nullptr ? kEmptyTraceId : traces_->top()->root_id_;
}
static TraceIdHierarchy hierarchy() {
if (traces_ == nullptr) {
return TraceIdHierarchy::Empty();
}
return traces_->top()->to_hierarchy();
}
// Static method to set the result of the most recent trace.
static void set_result(const Error& error) { set_result(error.code()); }
static void set_result(Error::Code error) {
if (traces_ == nullptr) {
return;
}
traces_->top()->SetTraceResult(error);
}
// Traces the end of an asynchronous call.
// NOTE: This returns a bool rather than a void because it keeps the syntax of
// the ternary operator in the macros simpler.
static bool TraceAsyncEnd(const uint32_t line,
const char* file,
TraceId id,
Error::Code e);
// Traces a flow event.
static bool TraceFlow(
TraceCategory category,
const char* name,
const char* file,
uint32_t line,
uint64_t flow_id,
FlowType type,
std::optional<Clock::time_point> timestamp = std::nullopt);
protected:
// Sets the result of this trace log.
// NOTE: this must be define in this class rather than TraceLogger so that it
// can be called on traces.back() without a potentially unsafe cast or type
// checking at runtime.
virtual void SetTraceResult(Error::Code error) = 0;
// Constructor to set all trace id information.
ScopedTraceOperation(TraceId current_id = kUnsetTraceId,
TraceId parent_id = kUnsetTraceId,
TraceId root_id = kUnsetTraceId);
// Current TraceId information.
TraceId trace_id_;
TraceId parent_id_;
TraceId root_id_;
TraceIdHierarchy to_hierarchy() { return {trace_id_, parent_id_, root_id_}; }
private:
// NOTE: A std::vector is used for backing the stack because it provides the
// best perf. Further perf improvement could be achieved later by swapping
// this out for a circular buffer once OSP supports that. Additional details
// can be found here:
// https://www.codeproject.com/Articles/1185449/Performance-of-a-Circular-Buffer-vs-Vector-Deque-a
using TraceStack =
std::stack<ScopedTraceOperation*, std::vector<ScopedTraceOperation*>>;
// Counter to pick IDs when it is not provided.
static std::atomic<std::uint64_t> trace_id_counter_;
// The LIFO stack of TraceLoggers currently being watched by this
// thread.
static thread_local TraceStack* traces_;
static thread_local ScopedTraceOperation* root_node_;
};
// The class which does actual trace logging.
class TraceLoggerBase : public ScopedTraceOperation {
public:
TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments = {},
TraceId current = kUnsetTraceId,
TraceId parent = kUnsetTraceId,
TraceId root = kUnsetTraceId);
TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceIdHierarchy ids);
TraceLoggerBase(const TraceLoggerBase&) = delete;
TraceLoggerBase(TraceLoggerBase&&) noexcept = delete;
TraceLoggerBase& operator=(const TraceLoggerBase&) = delete;
TraceLoggerBase& operator=(TraceLoggerBase&&) = delete;
protected:
// Set the result.
void SetTraceResult(Error::Code error) override { event_.result = error; }
TraceEvent event_;
};
class SynchronousTraceLogger : public TraceLoggerBase {
public:
using TraceLoggerBase::TraceLoggerBase;
SynchronousTraceLogger(const SynchronousTraceLogger&) = delete;
SynchronousTraceLogger(SynchronousTraceLogger&&) noexcept = delete;
SynchronousTraceLogger& operator=(const SynchronousTraceLogger&) = delete;
SynchronousTraceLogger& operator=(SynchronousTraceLogger&&) = delete;
~SynchronousTraceLogger() override;
};
class AsynchronousTraceLogger : public TraceLoggerBase {
public:
using TraceLoggerBase::TraceLoggerBase;
AsynchronousTraceLogger(const AsynchronousTraceLogger&) = delete;
AsynchronousTraceLogger(AsynchronousTraceLogger&&) noexcept = delete;
AsynchronousTraceLogger& operator=(const AsynchronousTraceLogger&) = delete;
AsynchronousTraceLogger& operator=(AsynchronousTraceLogger&&) = delete;
~AsynchronousTraceLogger() override;
};
// Inserts a fake element into the ScopedTraceOperation stack to set
// the current TraceId Hierarchy manually.
class TraceIdSetter final : public ScopedTraceOperation {
public:
explicit TraceIdSetter(TraceIdHierarchy ids);
TraceIdSetter(const TraceIdSetter&) = delete;
TraceIdSetter(TraceIdSetter&&) noexcept = delete;
TraceIdSetter& operator=(const TraceIdSetter&) = delete;
TraceIdSetter& operator=(TraceIdSetter&&) = delete;
~TraceIdSetter() final;
// Creates a new TraceIdSetter to set the full TraceId Hierarchy to default
// values and does not push it to the traces stack.
static TraceIdSetter* CreateStackRootNode();
private:
// Implement abstract method for use in Macros.
void SetTraceResult(Error::Code error) {}
};
// This helper object allows us to delete objects allocated on the stack in a
// unique_ptr.
template <class T>
class TraceInstanceHelper {
private:
class TraceOperationOnStackDeleter {
public:
void operator()(T* ptr) { ptr->~T(); }
};
using TraceInstanceWrapper = std::unique_ptr<T, TraceOperationOnStackDeleter>;
public:
template <typename... Args>
static TraceInstanceWrapper Create(uint8_t storage[sizeof(T)], Args... args) {
return TraceInstanceWrapper(new (storage) T(std::forward<Args&&>(args)...));
}
static TraceInstanceWrapper Empty() { return TraceInstanceWrapper(); }
};
} // namespace openscreen::internal
#endif // defined(ENABLE_TRACE_LOGGING)
#endif // UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_

View file

@ -0,0 +1,127 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/uuid.h"
#include <stddef.h>
#include <stdint.h>
#include <ostream>
#include "util/big_endian.h"
#include "util/crypto/random_bytes.h"
#include "util/hashing.h"
#include "util/osp_logging.h"
#include "util/string_util.h"
#include "util/stringprintf.h"
namespace openscreen {
namespace {
constexpr bool IsHyphenPosition(size_t i) {
return i == 8 || i == 13 || i == 18 || i == 23;
}
// Returns a canonical Uuid string given that `input` is validly formatted
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, such that x is a hexadecimal digit.
// If `strict`, x must be a lower-case hexadecimal digit.
std::string GetCanonicalUuidInternal(std::string_view input, bool strict) {
constexpr size_t kUuidLength = 36;
if (input.length() != kUuidLength) {
return {};
}
std::string lowercase;
lowercase.resize(kUuidLength);
for (size_t i = 0; i < input.length(); ++i) {
auto current = input[i];
if (IsHyphenPosition(i)) {
if (current != '-') {
return {};
}
lowercase[i] = '-';
} else {
if (strict ? !string_util::ascii_islowerhex(current)
: !string_util::ascii_ishex(current)) {
return {};
}
lowercase[i] = static_cast<char>(string_util::ascii_tolower(current));
}
}
return lowercase;
}
} // namespace
// static
Uuid Uuid::GenerateRandomV4() {
return FormatRandomDataAsV4Impl(GenerateRandomBytes16());
}
// static
Uuid Uuid::FormatRandomDataAsV4Impl(ByteView input) {
OSP_CHECK_EQ(input.size(), kGuidV4InputLength);
auto first_u64 = ReadBigEndian<uint64_t>(input.first(8).data());
auto second_u64 = ReadBigEndian<uint64_t>(input.last(8).data());
// Set the Uuid to version 4 as described in RFC 4122, section 4.4.
// The format of Uuid version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, a, b].
// Clear the version bits and set the version to 4:
first_u64 &= 0xffffffff'ffff0fffULL;
first_u64 |= 0x00000000'00004000ULL;
// Clear bit 65 and set bit 64, to set the 'var' field to 0b10 per RFC 9562
// section 5.4.
second_u64 &= 0x3fffffff'ffffffffULL;
second_u64 |= 0x80000000'00000000ULL;
Uuid uuid;
uuid.lowercase_ =
StringFormat("{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
static_cast<uint32_t>(first_u64 >> 32),
static_cast<uint32_t>((first_u64 >> 16) & 0x0000'ffff),
static_cast<uint32_t>(first_u64 & 0x0000'ffff),
static_cast<uint32_t>(second_u64 >> 48),
second_u64 & 0x0000'ffff'ffff'ffffULL);
return uuid;
}
// static
Uuid Uuid::ParseCaseInsensitive(std::string_view input) {
Uuid uuid;
uuid.lowercase_ = GetCanonicalUuidInternal(input, /*strict=*/false);
return uuid;
}
// static
Uuid Uuid::ParseLowercase(std::string_view input) {
Uuid uuid;
uuid.lowercase_ = GetCanonicalUuidInternal(input, /*strict=*/true);
return uuid;
}
Uuid::Uuid() = default;
Uuid::Uuid(const Uuid& other) = default;
Uuid::Uuid(Uuid&& other) noexcept = default;
Uuid& Uuid::operator=(const Uuid& other) = default;
Uuid& Uuid::operator=(Uuid&& other) = default;
const std::string& Uuid::AsLowercaseString() const {
return lowercase_;
}
std::ostream& operator<<(std::ostream& out, const Uuid& uuid) {
return out << uuid.AsLowercaseString();
}
size_t UuidHash::operator()(const Uuid& uuid) const {
return ComputeAggregateHash(uuid.AsLowercaseString());
}
} // namespace openscreen

View file

@ -0,0 +1,81 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_UUID_H_
#define UTIL_UUID_H_
#include <stdint.h>
#include <compare>
#include <iosfwd>
#include <string>
#include <string_view>
#include "platform/base/span.h"
namespace openscreen {
// UUID implementation strongly based off of Chromium's base::Uuid
// implementation. Provides securely generated random Uuids as well as parsing
// logic for inputted UUIDs.
class Uuid {
public:
// Length in bytes of the input required to format the input as a Uuid in the
// form of version 4.
static constexpr size_t kGuidV4InputLength = 16;
// Generate a 128-bit random Uuid in the form of version 4. see RFC 4122,
// section 4.4. The format of Uuid version 4 must be
// xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where y is one of [8, 9, a, b]. The
// hexadecimal values "a" through "f" are output as lower case characters.
static Uuid GenerateRandomV4();
// Returns a valid Uuid if the input string conforms to the Uuid format, and
// an invalid Uuid otherwise. Accepts both lower case and upper case hex
// characters.
static Uuid ParseCaseInsensitive(std::string_view input);
// Similar to ParseCaseInsensitive(), but all hexadecimal values "a" through
// "f" must be lower case characters.
static Uuid ParseLowercase(std::string_view input);
// Constructs an invalid Uuid.
Uuid();
Uuid(const Uuid& other);
Uuid(Uuid&& other) noexcept;
Uuid& operator=(const Uuid& other);
Uuid& operator=(Uuid&& other);
bool is_valid() const { return !lowercase_.empty(); }
// Returns the Uuid in a lowercase string format if it is valid, and an empty
// string otherwise. The returned value is guaranteed to be parsed by
// ParseLowercase().
const std::string& AsLowercaseString() const;
// Invalid Uuids are equal.
friend bool operator==(const Uuid&, const Uuid&) = default;
// Uuids are 128bit chunks of data so must be indistinguishable if equivalent.
friend std::strong_ordering operator<=>(const Uuid&, const Uuid&) = default;
private:
static Uuid FormatRandomDataAsV4Impl(ByteView input);
// The lowercase form of the Uuid. Empty for invalid Uuids.
std::string lowercase_;
};
// For runtime usage only. Do not store the result of this hash, as it may
// change in the future.
struct UuidHash {
size_t operator()(const Uuid& uuid) const;
};
// Stream operator so Uuid objects can be used in logging statements.
std::ostream& operator<<(std::ostream& out, const Uuid& uuid);
} // namespace openscreen
#endif // UTIL_UUID_H_

View file

@ -0,0 +1,217 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_WEAK_PTR_H_
#define UTIL_WEAK_PTR_H_
#include <memory>
#include <utility>
#include "util/osp_logging.h"
namespace openscreen {
// Weak pointers are pointers to an object that do not affect its lifetime,
// and which may be invalidated (i.e. reset to nullptr) by the object, or its
// owner, at any time; most commonly when the object is about to be deleted.
//
// Weak pointers are useful when an object needs to be accessed safely by one
// or more objects other than its owner, and those callers can cope with the
// object vanishing and e.g. tasks posted to it being silently dropped.
// Reference-counting such an object would complicate the ownership graph and
// make it harder to reason about the object's lifetime.
//
// EXAMPLE:
//
// class Controller {
// public:
// void SpawnWorker() { new Worker(weak_factory_.GetWeakPtr()); }
// void WorkComplete(const Result& result) { ... }
// private:
// // Member variables should appear before the WeakPtrFactory, to ensure
// // that any WeakPtrs to Controller are invalidated before its members
// // variable's destructors are executed, rendering them invalid.
// WeakPtrFactory<Controller> weak_factory_{this};
// };
//
// class Worker {
// public:
// explicit Worker(WeakPtr<Controller> controller)
// : controller_(std::move(controller)) {}
// private:
// void DidCompleteAsynchronousProcessing(const Result& result) {
// if (controller_)
// controller_->WorkComplete(result);
// delete this;
// }
// const WeakPtr<Controller> controller_;
// };
//
// With this implementation a caller may use SpawnWorker() to dispatch multiple
// Workers and subsequently delete the Controller, without waiting for all
// Workers to have completed.
//
// ------------------------- IMPORTANT: Thread-safety -------------------------
//
// Generally, Open Screen code is meant to be single-threaded. For the few
// exceptional cases, the following is relevant:
//
// WeakPtrs may be created from WeakPtrFactory, and also duplicated/moved on any
// thread/sequence. However, they may only be dereferenced on the same
// thread/sequence that will ultimately execute the WeakPtrFactory destructor or
// call InvalidateWeakPtrs(). Otherwise, use-during-free or use-after-free is
// possible.
//
// openscreen::WeakPtr and WeakPtrFactory are similar, but not identical, to
// Chromium's base::WeakPtrFactory. Open Screen WeakPtrs may be safely created
// from WeakPtrFactory on any thread/sequence, since they are backed by the
// thread-safe bookkeeping of std::shared_ptr<>.
template <typename T>
class WeakPtrFactory;
template <typename T>
class WeakPtr {
public:
WeakPtr() = default;
~WeakPtr() = default;
// Copy/Move constructors and assignment operators.
WeakPtr(const WeakPtr& other) : impl_(other.impl_) {}
WeakPtr(WeakPtr&& other) noexcept : impl_(std::move(other.impl_)) {}
WeakPtr& operator=(const WeakPtr& other) {
impl_ = other.impl_;
return *this;
}
WeakPtr& operator=(WeakPtr&& other) noexcept {
impl_ = std::move(other.impl_);
return *this;
}
// Create/Assign from nullptr.
WeakPtr(std::nullptr_t) {} // NOLINT
WeakPtr& operator=(std::nullptr_t) {
impl_.reset();
return *this;
}
// Copy/Move constructors and assignment operators with upcast conversion.
template <typename U>
WeakPtr(const WeakPtr<U>& other) : impl_(other.as_std_weak_ptr()) {}
template <typename U>
WeakPtr(WeakPtr<U>&& other) noexcept
: impl_(std::move(other).as_std_weak_ptr()) {}
template <typename U>
WeakPtr& operator=(const WeakPtr<U>& other) {
impl_ = other.as_std_weak_ptr();
return *this;
}
template <typename U>
WeakPtr& operator=(WeakPtr<U>&& other) noexcept {
impl_ = std::move(other).as_std_weak_ptr();
return *this;
}
// Accessors.
T* get() const { return impl_.lock().get(); }
T& operator*() const {
T* const pointer = get();
OSP_CHECK(pointer);
return *pointer;
}
T* operator->() const {
T* const pointer = get();
OSP_CHECK(pointer);
return pointer;
}
// Allow conditionals to test validity, e.g. if (weak_ptr) {...}
explicit operator bool() const { return get() != nullptr; }
// Conversion to std::weak_ptr<T>. It is unsafe to convert in the other
// direction. See comments for private constructors, below.
const std::weak_ptr<T>& as_std_weak_ptr() const& { return impl_; }
std::weak_ptr<T> as_std_weak_ptr() && { return std::move(impl_); }
private:
friend class WeakPtrFactory<T>;
// Called by WeakPtrFactory<T> and the WeakPtr<T> upcast conversion
// constructors and assigners. These are purposely not being exposed publicly
// because that would allow a WeakPtr<T> to be valid/invalid by a different
// ownership/threading model than the intended one (see top-level comments).
template <typename U>
explicit WeakPtr(const std::weak_ptr<U>& other) : impl_(other) {}
template <typename U>
explicit WeakPtr(std::weak_ptr<U>&& other) noexcept
: impl_(std::move(other)) {}
std::weak_ptr<T> impl_;
};
// Allow callers to compare WeakPtrs against nullptr to test validity.
template <typename T>
bool operator!=(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
return weak_ptr.get() != nullptr;
}
template <typename T>
bool operator!=(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
return weak_ptr.get() != nullptr;
}
template <typename T>
bool operator==(const WeakPtr<T>& weak_ptr, std::nullptr_t) {
return weak_ptr.get() == nullptr;
}
template <typename T>
bool operator==(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
return weak_ptr == nullptr;
}
template <typename T>
class WeakPtrFactory {
public:
explicit WeakPtrFactory(T* instance) { Reset(instance); }
WeakPtrFactory(WeakPtrFactory&& other) noexcept = default;
WeakPtrFactory& operator=(WeakPtrFactory&& other) noexcept = default;
// Thread-safe: WeakPtrs may be created on any thread/seuence. They may also
// be copied and moved on any thread/sequence. However, they MUST only be
// dereferenced on the same thread/sequence that calls the destructor or
// InvalidateWeakPtrs().
WeakPtr<T> GetWeakPtr() const {
return WeakPtr<T>(std::weak_ptr<T>(bookkeeper_));
}
// Destruction and Invalidation: These must be called on the same
// thread/sequence that dereferences any WeakPtrs to avoid use-after-free
// bugs.
~WeakPtrFactory() = default;
void InvalidateWeakPtrs() { Reset(bookkeeper_.get()); }
private:
WeakPtrFactory(const WeakPtrFactory& other) = delete;
WeakPtrFactory& operator=(const WeakPtrFactory& other) = delete;
void Reset(T* instance) {
// T is owned externally to WeakPtrFactory. Thus, provide a no-op Deleter.
bookkeeper_ = {instance, [](T*) {}};
}
// Manages the std::weak_ptr's referring to T. Does not own T.
std::shared_ptr<T> bookkeeper_;
};
} // namespace openscreen
#endif // UTIL_WEAK_PTR_H_