Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s
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:
parent
887c29002f
commit
8c745d18e0
283 changed files with 36788 additions and 0 deletions
343
breadcast-caststream-sys/vendor/openscreen/platform/base/ip_address.cc
vendored
Normal file
343
breadcast-caststream-sys/vendor/openscreen/platform/base/ip_address.cc
vendored
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
// 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 "platform/base/ip_address.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
namespace openscreen {
|
||||
|
||||
IPAddress::IPAddress(Version version, std::span<const uint8_t> bytes)
|
||||
: version_(version) {
|
||||
assert(bytes.size() >= size());
|
||||
std::copy_n(bytes.begin(), size(), bytes_.begin());
|
||||
}
|
||||
|
||||
bool IPAddress::operator==(const IPAddress& o) const {
|
||||
return version_ == o.version_ &&
|
||||
std::equal(bytes_.begin(), bytes_.begin() + size(),
|
||||
o.bytes_.begin()) &&
|
||||
scope_id_ == o.scope_id_;
|
||||
}
|
||||
|
||||
bool IPAddress::operator!=(const IPAddress& o) const {
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
IPAddress::operator bool() const {
|
||||
return std::any_of(bytes_.begin(), bytes_.begin() + size(),
|
||||
[](uint8_t byte) { return byte; });
|
||||
}
|
||||
|
||||
void IPAddress::CopyTo(std::span<uint8_t> bytes) const {
|
||||
assert(bytes.size() >= size());
|
||||
std::copy_n(bytes_.begin(), size(), bytes.begin());
|
||||
}
|
||||
|
||||
bool IPAddress::IsLinkLocal() const {
|
||||
if (!IsV6()) {
|
||||
return false;
|
||||
}
|
||||
// Link-local addresses start with fe80::/10
|
||||
return (bytes_[0] == 0xfe) && ((bytes_[1] & 0xc0) == 0x80);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
ErrorOr<IPAddress> ParseV4(std::string_view s) {
|
||||
uint8_t octets[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i > 0) {
|
||||
if (s.empty() || s.front() != '.') {
|
||||
return Error::Code::kInvalidIPV4Address;
|
||||
}
|
||||
s.remove_prefix(1);
|
||||
}
|
||||
const auto result =
|
||||
std::from_chars(s.data(), s.data() + s.size(), octets[i]);
|
||||
if (result.ec != std::errc()) {
|
||||
return Error::Code::kInvalidIPV4Address;
|
||||
}
|
||||
s.remove_prefix(result.ptr - s.data());
|
||||
}
|
||||
|
||||
if (!s.empty()) {
|
||||
return Error::Code::kInvalidIPV4Address;
|
||||
}
|
||||
|
||||
return IPAddress(octets[0], octets[1], octets[2], octets[3]);
|
||||
}
|
||||
|
||||
// Returns the zero-expansion of a double-colon in `s` if `s` is a
|
||||
// well-formatted IPv6 address. If `s` is ill-formatted, returns *any* string
|
||||
// that is ill-formatted.
|
||||
std::string ExpandIPv6DoubleColon(std::string_view s) {
|
||||
constexpr std::string_view kDoubleColon = "::";
|
||||
const size_t double_colon_position = s.find(kDoubleColon);
|
||||
if (double_colon_position == std::string::npos) {
|
||||
return std::string(s); // Nothing to expand.
|
||||
}
|
||||
if (double_colon_position != s.rfind(kDoubleColon)) {
|
||||
return {}; // More than one occurrence of double colons is illegal.
|
||||
}
|
||||
|
||||
std::ostringstream expanded;
|
||||
const int num_single_colons = std::count(s.begin(), s.end(), ':') - 2;
|
||||
int num_zero_groups_to_insert = 8 - num_single_colons;
|
||||
if (double_colon_position != 0) {
|
||||
// abcd:0123:4567::f000:1
|
||||
// ^^^^^^^^^^^^^^^
|
||||
expanded << s.substr(0, double_colon_position + 1);
|
||||
--num_zero_groups_to_insert;
|
||||
}
|
||||
if (double_colon_position != (s.size() - 2)) {
|
||||
--num_zero_groups_to_insert;
|
||||
}
|
||||
while (--num_zero_groups_to_insert > 0) {
|
||||
expanded << "0:";
|
||||
}
|
||||
expanded << '0';
|
||||
if (double_colon_position != (s.size() - 2)) {
|
||||
// abcd:0123:4567::f000:1
|
||||
// ^^^^^^^
|
||||
expanded << s.substr(double_colon_position + 1);
|
||||
}
|
||||
return expanded.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ErrorOr<IPAddress> ParseV6(std::string_view s) {
|
||||
std::string_view address_part = s;
|
||||
uint32_t scope_id = 0;
|
||||
|
||||
// Handle link-local addresses with scope ID, e.g., fe80::1%eth0
|
||||
const size_t scope_pos = s.find('%');
|
||||
if (scope_pos != std::string::npos) {
|
||||
address_part = s.substr(0, scope_pos);
|
||||
std::string_view scope_name = s.substr(scope_pos + 1);
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
scope_id = if_nametoindex(std::string(scope_name).c_str());
|
||||
#endif
|
||||
if (scope_id == 0) {
|
||||
// If if_nametoindex failed or is not available, try parsing as a number.
|
||||
unsigned int parsed_id = 0;
|
||||
const auto result = std::from_chars(
|
||||
scope_name.data(), scope_name.data() + scope_name.size(), parsed_id);
|
||||
|
||||
if (result.ec == std::errc() &&
|
||||
result.ptr == scope_name.data() + scope_name.size() &&
|
||||
parsed_id > 0) {
|
||||
scope_id = parsed_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (scope_id == 0) {
|
||||
return Error::Code::kInvalidIPV6Address;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string scan_input = ExpandIPv6DoubleColon(address_part);
|
||||
std::string_view scan_view(scan_input);
|
||||
uint16_t hextets[8];
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (i > 0) {
|
||||
if (scan_view.empty() || scan_view.front() != ':') {
|
||||
return Error::Code::kInvalidIPV6Address;
|
||||
}
|
||||
scan_view.remove_prefix(1);
|
||||
}
|
||||
const auto result = std::from_chars(
|
||||
scan_view.data(), scan_view.data() + scan_view.size(), hextets[i], 16);
|
||||
if (result.ec != std::errc()) {
|
||||
return Error::Code::kInvalidIPV6Address;
|
||||
}
|
||||
scan_view.remove_prefix(result.ptr - scan_view.data());
|
||||
}
|
||||
|
||||
if (!scan_view.empty()) {
|
||||
return Error::Code::kInvalidIPV6Address;
|
||||
}
|
||||
|
||||
IPAddress address(hextets);
|
||||
if (scope_id != 0) {
|
||||
if (!address.IsLinkLocal()) {
|
||||
return Error::Code::kInvalidIPV6Address;
|
||||
}
|
||||
address.scope_id_ = scope_id;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<IPAddress> IPAddress::Parse(std::string_view s) {
|
||||
ErrorOr<IPAddress> v4 = ParseV4(s);
|
||||
|
||||
return v4 ? std::move(v4) : ParseV6(s);
|
||||
}
|
||||
|
||||
// static
|
||||
const IPEndpoint IPEndpoint::kAnyV4() {
|
||||
return IPEndpoint{};
|
||||
}
|
||||
|
||||
// static
|
||||
const IPEndpoint IPEndpoint::kAnyV6() {
|
||||
return IPEndpoint{IPAddress::kAnyV6(), 0};
|
||||
}
|
||||
|
||||
IPEndpoint::operator bool() const {
|
||||
return address || port;
|
||||
}
|
||||
|
||||
// static
|
||||
ErrorOr<IPEndpoint> IPEndpoint::Parse(std::string_view s) {
|
||||
// Look for the colon that separates the IP address from the port number. Note
|
||||
// that this check also guards against the case where `s` is the empty string.
|
||||
const auto colon_pos = s.rfind(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
return Error(Error::Code::kParseError, "missing colon separator");
|
||||
}
|
||||
// The colon cannot be the first nor the last character in `s` because that
|
||||
// would mean there is no address part or port part.
|
||||
if (colon_pos == 0) {
|
||||
return Error(Error::Code::kParseError, "missing address before colon");
|
||||
}
|
||||
if (colon_pos == (s.size() - 1)) {
|
||||
return Error(Error::Code::kParseError, "missing port after colon");
|
||||
}
|
||||
|
||||
ErrorOr<IPAddress> address(Error::Code::kParseError);
|
||||
if (s[0] == '[' && s[colon_pos - 1] == ']') {
|
||||
// [abcd:beef:1:1::2600]:8080
|
||||
// ^^^^^^^^^^^^^^^^^^^^^
|
||||
address = ParseV6(s.substr(1, colon_pos - 2));
|
||||
} else {
|
||||
// 127.0.0.1:22
|
||||
// ^^^^^^^^^
|
||||
address = ParseV4(s.substr(0, colon_pos));
|
||||
}
|
||||
if (address.is_error()) {
|
||||
return Error(Error::Code::kParseError, "invalid address part");
|
||||
}
|
||||
|
||||
const std::string_view port_part = s.substr(colon_pos + 1);
|
||||
int port;
|
||||
const auto result = std::from_chars(
|
||||
port_part.data(), port_part.data() + port_part.size(), port);
|
||||
if (result.ec != std::errc() ||
|
||||
result.ptr != port_part.data() + port_part.size() || port < 0 ||
|
||||
port > std::numeric_limits<uint16_t>::max()) {
|
||||
return Error(Error::Code::kParseError, "invalid port part");
|
||||
}
|
||||
|
||||
return IPEndpoint{address.value(), static_cast<uint16_t>(port)};
|
||||
}
|
||||
|
||||
bool operator==(const IPEndpoint& a, const IPEndpoint& b) {
|
||||
return (a.address == b.address) && (a.port == b.port);
|
||||
}
|
||||
|
||||
bool operator!=(const IPEndpoint& a, const IPEndpoint& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
bool IPAddress::operator<(const IPAddress& other) const {
|
||||
if (version() != other.version()) {
|
||||
return version() < other.version();
|
||||
}
|
||||
|
||||
if (IsV4()) {
|
||||
return memcmp(bytes_.data(), other.bytes_.data(), 4) < 0;
|
||||
} else {
|
||||
const int cmp = memcmp(bytes_.data(), other.bytes_.data(), 16);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
return scope_id_ < other.scope_id_;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator<(const IPEndpoint& a, const IPEndpoint& b) {
|
||||
if (a.address != b.address) {
|
||||
return a.address < b.address;
|
||||
}
|
||||
|
||||
return a.port < b.port;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const IPAddress& address) {
|
||||
char separator;
|
||||
size_t values_per_separator;
|
||||
int value_width;
|
||||
if (address.IsV4()) {
|
||||
out << std::dec;
|
||||
separator = '.';
|
||||
values_per_separator = 1;
|
||||
value_width = 0;
|
||||
} else if (address.IsV6()) {
|
||||
out << std::hex << std::setfill('0') << std::right;
|
||||
separator = ':';
|
||||
values_per_separator = 2;
|
||||
value_width = 2;
|
||||
}
|
||||
std::span<const uint8_t> bytes = address.bytes();
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
if (i > 0 && (i % values_per_separator == 0)) {
|
||||
out << separator;
|
||||
}
|
||||
out << std::setw(value_width) << static_cast<int>(bytes[i]);
|
||||
}
|
||||
if (address.IsLinkLocal() && address.GetScopeId() != 0) {
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
char ifname[IF_NAMESIZE];
|
||||
if (if_indextoname(address.GetScopeId(), ifname)) {
|
||||
out << '%' << ifname;
|
||||
} else {
|
||||
out << '%' << address.GetScopeId();
|
||||
}
|
||||
#else
|
||||
out << '%' << address.GetScopeId();
|
||||
#endif
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const IPEndpoint& endpoint) {
|
||||
if (endpoint.address.IsV6()) {
|
||||
out << '[';
|
||||
}
|
||||
out << endpoint.address;
|
||||
if (endpoint.address.IsV6()) {
|
||||
out << ']';
|
||||
}
|
||||
return out << ':' << std::dec << static_cast<int>(endpoint.port);
|
||||
}
|
||||
|
||||
std::string IPEndpoint::ToString() const {
|
||||
std::ostringstream name;
|
||||
name << *this;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
} // namespace openscreen
|
||||
Loading…
Add table
Add a link
Reference in a new issue