diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 16:35:47 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 15:45:54 +0000 |
commit | 32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch) | |
tree | eeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/third_party/openscreen | |
parent | 99677208ff3b216fdfec551fbe548da5520cd6fb (diff) | |
download | qtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz |
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/third_party/openscreen')
126 files changed, 3707 insertions, 1454 deletions
diff --git a/chromium/third_party/openscreen/src/BUILD.gn b/chromium/third_party/openscreen/src/BUILD.gn index bb698dc7d47..16a82c88a48 100644 --- a/chromium/third_party/openscreen/src/BUILD.gn +++ b/chromium/third_party/openscreen/src/BUILD.gn @@ -5,14 +5,6 @@ import("//build_overrides/build.gni") import("osp/build/config/services.gni") -declare_args() { - # Set to true to force building the standalone receiver on Mac. It's currently - # disabled due to build bot struggles, but works fine on local, recent clang - # installations. - # TODO(crbug.com/openscreen/86): Remove when the Mac bots have been upgraded. - force_build_standalone_receiver = false -} - # All compilable non-test targets in the repository (both executables and # source_sets). group("gn_all") { @@ -28,8 +20,6 @@ group("gn_all") { "discovery:dnssd", "discovery:mdns", "discovery:public", - "osp", - "osp/msgs", "platform", "third_party/abseil", "third_party/boringssl", @@ -40,36 +30,37 @@ group("gn_all") { "util", ] - if (use_mdns_responder) { - deps += [ "osp/impl/discovery/mdns:mdns_demo" ] - } - - if (use_chromium_quic) { + # Mac OS X 10.15 is incompatible with the current version of QUIC. + if (!is_mac) { deps += [ - "third_party/chromium_quic", - "third_party/chromium_quic:quic_demo_server", - "third_party/chromium_quic:quic_streaming_playback_controller", + "osp", + "osp/msgs", ] - } - if (use_chromium_quic && use_mdns_responder) { - deps += [ "osp:osp_demo" ] + if (use_mdns_responder) { + deps += [ "osp/impl/discovery/mdns:mdns_demo" ] + } + + if (use_chromium_quic) { + deps += [ + "third_party/chromium_quic", + "third_party/chromium_quic:quic_demo_server", + "third_party/chromium_quic:quic_streaming_playback_controller", + ] + } + + if (use_chromium_quic && use_mdns_responder) { + deps += [ "osp:osp_demo" ] + } } if (!build_with_chromium) { deps += [ + "cast/standalone_receiver:cast_receiver", + "cast/standalone_sender:cast_sender", "third_party/protobuf:protoc($host_toolchain)", "third_party/zlib", ] - - # TODO(crbug.com/openscreen/86): Build for Mac too once the mac buildbot - # compiler is upgraded. - if (!is_mac || force_build_standalone_receiver) { - deps += [ - "cast/standalone_receiver:cast_receiver", - "cast/standalone_sender:cast_sender", - ] - } } } @@ -81,25 +72,34 @@ source_set("openscreen_unittests_all") { "cast/sender:unittests", "cast/streaming:unittests", "cast/test:unittests", - "discovery:unittests", - "osp:unittests", - "osp/msgs:unittests", "platform:unittests", "third_party/abseil", "util:unittests", ] if (!build_with_chromium && is_posix) { - public_deps += [ "cast/test:make_crl_tests($host_toolchain)" ] + public_deps += [ + "cast/test:make_crl_tests($host_toolchain)", + + # TODO(crbug.com/1132604): Discovery unittests fail in Chrome. + "discovery:unittests", + ] } - if (use_mdns_responder) { + if (!is_mac) { public_deps += [ - "osp/impl/discovery/mdns:unittests", - - # Currently this target only includes mDNS tests. - "osp/impl/testing:unittests", + "osp:unittests", + "osp/msgs:unittests", ] + + if (use_mdns_responder) { + public_deps += [ + "osp/impl/discovery/mdns:unittests", + + # Currently this target only includes mDNS tests. + "osp/impl/testing:unittests", + ] + } } } diff --git a/chromium/third_party/openscreen/src/DEPS b/chromium/third_party/openscreen/src/DEPS index 9ed742d681d..4834cf41c2b 100644 --- a/chromium/third_party/openscreen/src/DEPS +++ b/chromium/third_party/openscreen/src/DEPS @@ -66,7 +66,7 @@ deps = { 'third_party/jsoncpp/src': { 'url': Var('chromium_git') + '/external/github.com/open-source-parsers/jsoncpp.git' + - '@' + 'd2e6a971f4544c55b8e3b25cf96db266971b778f', # version 1.9.2 + '@' + '9059f5cad030ba11d37818847443a53918c327b1', # version 1.9.4 'condition': 'not build_with_chromium', }, diff --git a/chromium/third_party/openscreen/src/PRESUBMIT.py b/chromium/third_party/openscreen/src/PRESUBMIT.py index 6aa28248eac..f2426335af2 100755 --- a/chromium/third_party/openscreen/src/PRESUBMIT.py +++ b/chromium/third_party/openscreen/src/PRESUBMIT.py @@ -91,7 +91,7 @@ def _CommonChecks(input_api, output_api): def CheckChangeOnUpload(input_api, output_api): - input_api.DEFAULT_BLOCK_LIST = _EXCLUDED_PATHS; + input_api.DEFAULT_FILES_TO_SKIP = _EXCLUDED_PATHS; results = [] results.extend(_CommonChecks(input_api, output_api)) results.extend( @@ -100,7 +100,7 @@ def CheckChangeOnUpload(input_api, output_api): def CheckChangeOnCommit(input_api, output_api): - input_api.DEFAULT_BLOCK_LIST = _EXCLUDED_PATHS; + input_api.DEFAULT_FILES_TO_SKIP = _EXCLUDED_PATHS; results = [] results.extend(_CommonChecks(input_api, output_api)) return results diff --git a/chromium/third_party/openscreen/src/build/config/BUILD.gn b/chromium/third_party/openscreen/src/build/config/BUILD.gn index a68031e88c5..7309ad8b9c9 100644 --- a/chromium/third_party/openscreen/src/build/config/BUILD.gn +++ b/chromium/third_party/openscreen/src/build/config/BUILD.gn @@ -254,3 +254,12 @@ config("sysroot_runtime_libraries") { } } } + +config("operating_system_defines") { + defines = [] + if (is_linux) { + defines += [ "OS_LINUX" ] + } else if (is_mac) { + defines += [ "MAC_OSX" ] + } +} diff --git a/chromium/third_party/openscreen/src/build/config/BUILDCONFIG.gn b/chromium/third_party/openscreen/src/build/config/BUILDCONFIG.gn index 0fa9693520d..3b1a06a1a0a 100644 --- a/chromium/third_party/openscreen/src/build/config/BUILDCONFIG.gn +++ b/chromium/third_party/openscreen/src/build/config/BUILDCONFIG.gn @@ -161,6 +161,7 @@ _shared_binary_target_configs = [ "//build/config:compiler_cpu_abi", "//build/config:default_optimization", "//build/config:sysroot_runtime_libraries", + "//build/config:operating_system_defines", ] # Apply that default list to the binary target types. diff --git a/chromium/third_party/openscreen/src/build/config/sysroot.gni b/chromium/third_party/openscreen/src/build/config/sysroot.gni index deecdecacf3..339bfd9b0f4 100644 --- a/chromium/third_party/openscreen/src/build/config/sysroot.gni +++ b/chromium/third_party/openscreen/src/build/config/sysroot.gni @@ -1,6 +1,6 @@ # Copyright 2019 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file +# found in the LICENSE file. # This header file defines the "sysroot" variable which is the absolute path # of the sysroot. If no sysroot applies, the variable will be an empty string. diff --git a/chromium/third_party/openscreen/src/build/scripts/sysroot_ld_path.py b/chromium/third_party/openscreen/src/build/scripts/sysroot_ld_path.py index 8c65861046f..85873812144 100755 --- a/chromium/third_party/openscreen/src/build/scripts/sysroot_ld_path.py +++ b/chromium/third_party/openscreen/src/build/scripts/sysroot_ld_path.py @@ -2,7 +2,7 @@ # Copyright 2019 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file +# found in the LICENSE file. # Replacement for the deprecated sysroot_ld_path.sh implementation in Chrome. """ diff --git a/chromium/third_party/openscreen/src/cast/README.md b/chromium/third_party/openscreen/src/cast/README.md index 1b890c5b5b6..a501703ba87 100644 --- a/chromium/third_party/openscreen/src/cast/README.md +++ b/chromium/third_party/openscreen/src/cast/README.md @@ -3,3 +3,31 @@ libcast is an open source implementation of the Cast procotol supporting Cast applications and streaming to Cast-compatible devices. +## Using the standalone implementations + +To run the standalone sender and receivers together, first you need to install +the following dependencies: FFMPEG, LibVPX, LibOpus, LibSDL2, as well as their +headers (frequently in a seperate -dev package). From here, you need to generate +a RSA private key and create a self signed certificate with that key. + +From there, after building Open Screen the `cast_sender` and `cast_receiver` +executables should be ready to use: +``` + $ /path/to/out/Default/cast_sender -s <certificate> <path/to/video> + ... + $ /path/to/out/Default/cast_receiver <interface> -p <private_key> -s <certificate> +``` + +When running on Mac OS X, also pass the `-x` flag to the cast receiver to +disable DNS-SD/mDNS, since Open Screen does not currently integrate with +Bonjour. + +When connecting to a receiver that's not running on the loopback interface +(typically `lo` or `lo0`), pass the `-r <receiver IP endpoint>` flag to the +`cast_sender` binary. + +An archive containing test running scripts, a video, and a generated RSA +key and certificate is available from google storage. Note that it may require +modification to work on your specific work environment: + +https://storage.googleapis.com/openscreen_standalone/cast_streaming_demo.tar.gz diff --git a/chromium/third_party/openscreen/src/cast/common/BUILD.gn b/chromium/third_party/openscreen/src/cast/common/BUILD.gn index 07ac9b710b2..ba1e67fcd16 100644 --- a/chromium/third_party/openscreen/src/cast/common/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/common/BUILD.gn @@ -19,9 +19,7 @@ source_set("certificate") { "certificate/types.cc", "certificate/types.h", ] - public_deps = [ - "../../third_party/boringssl", - ] + public_deps = [ "../../third_party/boringssl" ] deps = [ "../../platform", @@ -34,6 +32,8 @@ source_set("certificate") { source_set("channel") { sources = [ "channel/cast_socket.cc", + "channel/cast_socket_message_port.cc", + "channel/cast_socket_message_port.h", "channel/connection_namespace_handler.cc", "channel/connection_namespace_handler.h", "channel/message_framer.cc", @@ -50,9 +50,7 @@ source_set("channel") { "public/cast_socket.h", ] - deps = [ - "certificate/proto:certificate_proto", - ] + deps = [ "certificate/proto:certificate_proto" ] public_deps = [ "../../platform", @@ -64,6 +62,7 @@ source_set("channel") { source_set("public") { sources = [ + "public/message_port.h", "public/service_info.cc", "public/service_info.h", ] @@ -81,9 +80,7 @@ if (!build_with_chromium) { testonly = true if (!is_mac) { - sources = [ - "discovery/e2e_test/tests.cc", - ] + sources = [ "discovery/e2e_test/tests.cc" ] } deps = [ @@ -112,9 +109,11 @@ source_set("test_helpers") { ":channel", ":public", "../../platform:test", + "../../testing/util", "../../third_party/abseil", "../../third_party/boringssl", "../../third_party/googletest:gmock", + "../../third_party/googletest:gtest", ] deps = [ "../../platform", @@ -152,18 +151,12 @@ source_set("unittests") { "channel/proto:channel_proto", ] - data = [ - "../../test/data/cast/common/certificate/", - ] + data = [ "../../test/data/cast/common/certificate/" ] } openscreen_fuzzer_test("message_framer_fuzzer") { - sources = [ - "channel/message_framer_fuzzer.cc", - ] - deps = [ - ":channel", - ] + sources = [ "channel/message_framer_fuzzer.cc" ] + deps = [ ":channel" ] seed_corpus = "channel/message_framer_fuzzer_seeds" diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.cc b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.cc index 569d22b1379..e4c689f806d 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.cc +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.cc @@ -14,6 +14,7 @@ #include <vector> #include "cast/common/certificate/types.h" +#include "util/crypto/pem_helpers.h" #include "util/osp_logging.h" namespace openscreen { @@ -95,7 +96,8 @@ bssl::UniquePtr<ASN1_BIT_STRING> GetKeyUsage(X509* cert) { Error::Code VerifyCertificateChain(const std::vector<CertPathStep>& path, uint32_t step_index, - const DateTime& time) { + const DateTime& time, + TrustStore::Mode mode) { // Default max path length is the number of intermediate certificates. int max_pathlen = path.size() - 2; @@ -132,33 +134,37 @@ Error::Code VerifyCertificateChain(const std::vector<CertPathStep>& path, } } - // Check that basicConstraints is present, specifies the CA bit, and use - // pathLenConstraint if present. - const int basic_constraints_index = - X509_get_ext_by_NID(issuer, NID_basic_constraints, -1); - if (basic_constraints_index == -1) { - return Error::Code::kErrCertsVerifyGeneric; - } - X509_EXTENSION* const basic_constraints_extension = - X509_get_ext(issuer, basic_constraints_index); - bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints{ - reinterpret_cast<BASIC_CONSTRAINTS*>( - X509V3_EXT_d2i(basic_constraints_extension))}; + // Certificates issued by a valid CA authority shall have the + // basicConstraints property present with the CA bit set. Self-signed + // certificates do not have this property present. + if (mode == TrustStore::Mode::kStrict) { + const int basic_constraints_index = + X509_get_ext_by_NID(issuer, NID_basic_constraints, -1); + if (basic_constraints_index == -1) { + return Error::Code::kErrCertsVerifyGeneric; + } - if (!basic_constraints || !basic_constraints->ca) { - return Error::Code::kErrCertsVerifyGeneric; - } + X509_EXTENSION* const basic_constraints_extension = + X509_get_ext(issuer, basic_constraints_index); + bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints{ + reinterpret_cast<BASIC_CONSTRAINTS*>( + X509V3_EXT_d2i(basic_constraints_extension))}; - if (basic_constraints->pathlen) { - if (basic_constraints->pathlen->length != 1) { + if (!basic_constraints || !basic_constraints->ca) { return Error::Code::kErrCertsVerifyGeneric; - } else { - const int pathlen = *basic_constraints->pathlen->data; - if (pathlen < 0) { + } + + if (basic_constraints->pathlen) { + if (basic_constraints->pathlen->length != 1) { return Error::Code::kErrCertsVerifyGeneric; - } - if (pathlen < max_pathlen) { - max_pathlen = pathlen; + } else { + const int pathlen = *basic_constraints->pathlen->data; + if (pathlen < 0) { + return Error::Code::kErrCertsVerifyGeneric; + } + if (pathlen < max_pathlen) { + max_pathlen = pathlen; + } } } } @@ -355,6 +361,21 @@ bool GetCertValidTimeRange(X509* cert, return times_valid; } +// static +TrustStore TrustStore::CreateInstanceFromPemFile(absl::string_view file_path, + TrustStore::Mode mode) { + TrustStore store; + + std::vector<std::string> certs = ReadCertificatesFromPemFile(file_path); + for (const auto& der_cert : certs) { + const uint8_t* data = (const uint8_t*)der_cert.data(); + store.certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size())); + } + + store.mode = mode; + return store; +} + bool VerifySignedData(const EVP_MD* digest, EVP_PKEY* public_key, const ConstDataSpan& data, @@ -374,7 +395,7 @@ Error FindCertificatePath(const std::vector<std::string>& der_certs, CertificatePathResult* result_path, TrustStore* trust_store) { if (der_certs.empty()) { - return Error::Code::kErrCertsMissing; + return Error(Error::Code::kErrCertsMissing, "Missing DER certificates"); } bssl::UniquePtr<X509>& target_cert = result_path->target_cert; @@ -500,7 +521,7 @@ Error FindCertificatePath(const std::vector<std::string>& der_certs, if (last_error == Error::Code::kNone) { OSP_DVLOG << "FindCertificatePath: Failed after trying all " "certificate paths, no matches"; - return Error::Code::kErrCertsVerifyGeneric; + return Error::Code::kErrCertsVerifyUntrustedCert; } return last_error; } else { @@ -512,7 +533,8 @@ Error FindCertificatePath(const std::vector<std::string>& der_certs, } if (path_cert_in_trust_store) { - last_error = VerifyCertificateChain(path, path_index, time); + last_error = + VerifyCertificateChain(path, path_index, time, trust_store->mode); if (last_error != Error::Code::kNone) { CertPathStep& last_step = path[path_index++]; trust_store_index = last_step.trust_store_index; diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.h b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.h index f8424b6d1c0..9264418ef25 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.h +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_internal.h @@ -7,15 +7,32 @@ #include <openssl/x509.h> +#include <string> #include <vector> +#include "absl/strings/string_view.h" #include "platform/base/error.h" - namespace openscreen { namespace cast { struct TrustStore { + enum class Mode { + // In strict mode, only certificates signed by a CA will be accepted as + // part of authentication. Note that if a self-signed certificate is placed + // in a strict mode TrustStore, it cannot be used for authentication. + kStrict, + + // In allow self signed mode, certificates signed by an arbitrary private + // key that have been placed in this trust store will be allowed. Note + // that certificates must still otherwise be valid. + kAllowSelfSigned + }; + + static TrustStore CreateInstanceFromPemFile(absl::string_view file_path, + Mode mode = Mode::kStrict); + std::vector<bssl::UniquePtr<X509>> certs; + Mode mode = Mode::kStrict; }; // Adds a trust anchor given a DER-encoded certificate from static diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_unittest.cc b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_unittest.cc index f7e21d84379..53b6f05f284 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_cert_validator_unittest.cc @@ -12,6 +12,7 @@ #include "gtest/gtest.h" #include "openssl/pem.h" #include "platform/test/paths.h" +#include "util/crypto/pem_helpers.h" namespace openscreen { namespace cast { @@ -51,8 +52,7 @@ void RunTest(Error::Code expected_result, const DateTime& time, TrustStoreDependency trust_store_dependency, const std::string& optional_signed_data_file_name) { - std::vector<std::string> certs = - testing::ReadCertificatesFromPemFile(certs_file_name); + std::vector<std::string> certs = ReadCertificatesFromPemFile(certs_file_name); TrustStore* trust_store; std::unique_ptr<TrustStore> fake_trust_store; @@ -94,7 +94,10 @@ void RunTest(Error::Code expected_result, // Test that the context is good. EXPECT_EQ(expected_common_name, context->GetCommonName()); -#define DATA_SPAN_FROM_LITERAL(s) ConstDataSpan{(uint8_t*)s, sizeof(s) - 1} +#define DATA_SPAN_FROM_LITERAL(s) \ + ConstDataSpan{const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(s)), \ + sizeof(s) - 1} + // Test verification of some invalid signatures. EXPECT_FALSE(context->VerifySignatureOverData( DATA_SPAN_FROM_LITERAL("bogus signature"), @@ -233,7 +236,7 @@ TEST(VerifyCastDeviceCertTest, Fugu) { // This is invalid because it does not chain to a trust anchor. TEST(VerifyCastDeviceCertTest, Unchained) { std::string data_path = GetSpecificTestDataPath(); - RunTest(Error::Code::kErrCertsVerifyGeneric, "", + RunTest(Error::Code::kErrCertsVerifyUntrustedCert, "", CastDeviceCertPolicy::kUnrestricted, data_path + "certificates/unchained.pem", AprilFirst2016(), TRUST_STORE_BUILTIN, ""); diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_crl_unittest.cc b/chromium/third_party/openscreen/src/cast/common/certificate/cast_crl_unittest.cc index fe65cce3cfa..c4d3bfc43a8 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_crl_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_crl_unittest.cc @@ -99,9 +99,11 @@ bool RunTest(const DeviceCertTest& test_case) { std::unique_ptr<TrustStore> crl_trust_store; std::unique_ptr<TrustStore> cast_trust_store; if (test_case.use_test_trust_anchors()) { - crl_trust_store = testing::CreateTrustStoreFromPemFile( + crl_trust_store = std::make_unique<TrustStore>(); + cast_trust_store = std::make_unique<TrustStore>(); + *crl_trust_store = TrustStore::CreateInstanceFromPemFile( GetSpecificTestDataPath() + "certificates/cast_crl_test_root_ca.pem"); - cast_trust_store = testing::CreateTrustStoreFromPemFile( + *cast_trust_store = TrustStore::CreateInstanceFromPemFile( GetSpecificTestDataPath() + "certificates/cast_test_root_ca.pem"); EXPECT_FALSE(crl_trust_store->certs.empty()); diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.cc b/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.cc index 93db49ba034..d8ec513c0f0 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.cc +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.cc @@ -4,6 +4,9 @@ #include "cast/common/certificate/cast_trust_store.h" +#include <utility> + +#include "util/crypto/pem_helpers.h" #include "util/osp_logging.h" namespace openscreen { @@ -48,6 +51,16 @@ CastTrustStore* CastTrustStore::CreateInstanceForTest( return store_; } +// static +CastTrustStore* CastTrustStore::CreateInstanceFromPemFile( + absl::string_view file_path, + TrustStore::Mode mode) { + OSP_DCHECK(!store_); + store_ = new CastTrustStore(); + store_->trust_store_ = TrustStore::CreateInstanceFromPemFile(file_path, mode); + return store_; +} + CastTrustStore::CastTrustStore() { trust_store_.certs.emplace_back(MakeTrustAnchor(kCastRootCaDer)); trust_store_.certs.emplace_back(MakeTrustAnchor(kEurekaRootCaDer)); @@ -57,6 +70,9 @@ CastTrustStore::CastTrustStore(const std::vector<uint8_t>& trust_anchor_der) { trust_store_.certs.emplace_back(MakeTrustAnchor(trust_anchor_der)); } +CastTrustStore::CastTrustStore(TrustStore trust_store) + : trust_store_(std::move(trust_store)) {} + CastTrustStore::~CastTrustStore() = default; // static diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.h b/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.h index 801d9274a74..7bd7595594d 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.h +++ b/chromium/third_party/openscreen/src/cast/common/certificate/cast_trust_store.h @@ -7,6 +7,7 @@ #include <vector> +#include "absl/strings/string_view.h" #include "cast/common/certificate/cast_cert_validator_internal.h" namespace openscreen { @@ -20,8 +21,13 @@ class CastTrustStore { static CastTrustStore* CreateInstanceForTest( const std::vector<uint8_t>& trust_anchor_der); + static CastTrustStore* CreateInstanceFromPemFile( + absl::string_view file_path, + TrustStore::Mode mode = TrustStore::Mode::kStrict); + CastTrustStore(); explicit CastTrustStore(const std::vector<uint8_t>& trust_anchor_der); + explicit CastTrustStore(TrustStore trust_store); CastTrustStore(const CastTrustStore&) = delete; ~CastTrustStore(); CastTrustStore& operator=(const CastTrustStore&) = delete; diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.cc b/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.cc index 113a4bc4e26..09bf26b287f 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.cc +++ b/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.cc @@ -17,58 +17,6 @@ namespace openscreen { namespace cast { namespace testing { -std::vector<std::string> ReadCertificatesFromPemFile( - absl::string_view filename) { - FILE* fp = fopen(filename.data(), "r"); - if (!fp) { - return {}; - } - std::vector<std::string> certs; - char* name; - char* header; - unsigned char* data; - long length; - while (PEM_read(fp, &name, &header, &data, &length) == 1) { - if (absl::StartsWith(name, "CERTIFICATE")) { - certs.emplace_back((char*)data, length); - } - OPENSSL_free(name); - OPENSSL_free(header); - OPENSSL_free(data); - } - fclose(fp); - return certs; -} - -bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename) { - FILE* fp = fopen(filename.data(), "r"); - if (!fp) { - return nullptr; - } - bssl::UniquePtr<EVP_PKEY> pkey; - char* name; - char* header; - unsigned char* data; - long length; - while (PEM_read(fp, &name, &header, &data, &length) == 1) { - if (absl::StartsWith(name, "RSA PRIVATE KEY")) { - OSP_DCHECK(!pkey); - CBS cbs; - CBS_init(&cbs, data, length); - RSA* rsa = RSA_parse_private_key(&cbs); - if (rsa) { - pkey.reset(EVP_PKEY_new()); - EVP_PKEY_assign_RSA(pkey.get(), rsa); - } - } - OPENSSL_free(name); - OPENSSL_free(header); - OPENSSL_free(data); - } - fclose(fp); - return pkey; -} - SignatureTestData::SignatureTestData() : message{nullptr, 0}, sha1{nullptr, 0}, sha256{nullptr, 0} {} @@ -85,7 +33,7 @@ SignatureTestData ReadSignatureTestData(absl::string_view filename) { char* name; char* header; unsigned char* data; - long length; + long length; // NOLINT while (PEM_read(fp, &name, &header, &data, &length) == 1) { if (strcmp(name, "MESSAGE") == 0) { OSP_DCHECK(!result.message.data); @@ -112,19 +60,6 @@ SignatureTestData ReadSignatureTestData(absl::string_view filename) { return result; } -std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile( - absl::string_view filename) { - std::unique_ptr<TrustStore> store = std::make_unique<TrustStore>(); - - std::vector<std::string> certs = - testing::ReadCertificatesFromPemFile(filename); - for (const auto& der_cert : certs) { - const uint8_t* data = (const uint8_t*)der_cert.data(); - store->certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size())); - } - return store; -} - } // namespace testing } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.h b/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.h index c1ff9a25f78..30715971560 100644 --- a/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.h +++ b/chromium/third_party/openscreen/src/cast/common/certificate/testing/test_helpers.h @@ -18,10 +18,6 @@ namespace openscreen { namespace cast { namespace testing { -std::vector<std::string> ReadCertificatesFromPemFile( - absl::string_view filename); -bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename); - class SignatureTestData { public: SignatureTestData(); @@ -34,9 +30,6 @@ class SignatureTestData { SignatureTestData ReadSignatureTestData(absl::string_view filename); -std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile( - absl::string_view filename); - } // namespace testing } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/common/channel/cast_message_handler.h b/chromium/third_party/openscreen/src/cast/common/channel/cast_message_handler.h index cd0d13e690d..e478d156e17 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/cast_message_handler.h +++ b/chromium/third_party/openscreen/src/cast/common/channel/cast_message_handler.h @@ -17,6 +17,7 @@ class CastMessageHandler { public: virtual ~CastMessageHandler() = default; + // |socket| is null if the source of the message is a local peer. virtual void OnMessage(VirtualConnectionRouter* router, CastSocket* socket, ::cast::channel::CastMessage message) = 0; diff --git a/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.cc b/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.cc new file mode 100644 index 00000000000..b6d65123ae7 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.cc @@ -0,0 +1,103 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/common/channel/cast_socket_message_port.h" + +#include <utility> + +#include "cast/common/channel/message_util.h" +#include "cast/common/channel/proto/cast_channel.pb.h" +#include "cast/common/channel/virtual_connection.h" +#include "cast/common/channel/virtual_connection_manager.h" + +namespace openscreen { +namespace cast { + +CastSocketMessagePort::CastSocketMessagePort(VirtualConnectionRouter* router) + : router_(router) {} + +CastSocketMessagePort::~CastSocketMessagePort() { + ResetClient(); +} + +// NOTE: we assume here that this message port is already the client for +// the passed in socket, so leave the socket's client unchanged. However, +// since sockets should map one to one with receiver sessions, we reset our +// client. The consumer of this message port should call SetClient with the new +// message port client after setting the socket. +void CastSocketMessagePort::SetSocket(WeakPtr<CastSocket> socket) { + ResetClient(); + socket_ = socket; +} + +int CastSocketMessagePort::GetSocketId() { + return ToCastSocketId(socket_.get()); +} + +void CastSocketMessagePort::SetClient(MessagePort::Client* client, + std::string client_sender_id) { + ResetClient(); + + client_ = client; + client_sender_id_ = std::move(client_sender_id); + router_->AddHandlerForLocalId(client_sender_id_, this); +} + +void CastSocketMessagePort::ResetClient() { + if (!client_) { + return; + } + + client_ = nullptr; + router_->RemoveHandlerForLocalId(client_sender_id_); + router_->manager()->RemoveConnectionsByLocalId( + client_sender_id_, VirtualConnection::CloseReason::kClosedBySelf); + client_sender_id_.clear(); +} + +void CastSocketMessagePort::PostMessage( + const std::string& destination_sender_id, + const std::string& message_namespace, + const std::string& message) { + if (!client_) { + OSP_DLOG_WARN << "Not posting message due to nullptr client_"; + return; + } + + if (!socket_) { + client_->OnError(Error::Code::kAlreadyClosed); + return; + } + + VirtualConnection connection{client_sender_id_, destination_sender_id, + socket_->socket_id()}; + if (!router_->manager()->GetConnectionData(connection)) { + router_->manager()->AddConnection(connection, + VirtualConnection::AssociatedData{}); + } + + const Error send_error = router_->Send( + std::move(connection), MakeSimpleUTF8Message(message_namespace, message)); + if (!send_error.ok()) { + client_->OnError(std::move(send_error)); + } +} + +void CastSocketMessagePort::OnMessage(VirtualConnectionRouter* router, + CastSocket* socket, + ::cast::channel::CastMessage message) { + OSP_DCHECK(router == router_); + OSP_DCHECK(socket_.get() == socket); + OSP_DVLOG << "Received a cast socket message"; + if (!client_) { + OSP_DLOG_WARN << "Dropping message due to nullptr client_"; + return; + } + + client_->OnMessage(message.source_id(), message.namespace_(), + message.payload_utf8()); +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.h b/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.h new file mode 100644 index 00000000000..4dbd141c65d --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/common/channel/cast_socket_message_port.h @@ -0,0 +1,55 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_COMMON_CHANNEL_CAST_SOCKET_MESSAGE_PORT_H_ +#define CAST_COMMON_CHANNEL_CAST_SOCKET_MESSAGE_PORT_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "cast/common/channel/cast_message_handler.h" +#include "cast/common/channel/virtual_connection_router.h" +#include "cast/common/public/cast_socket.h" +#include "cast/common/public/message_port.h" +#include "util/weak_ptr.h" + +namespace openscreen { +namespace cast { + +class CastSocketMessagePort : public MessagePort, public CastMessageHandler { + public: + // The router is expected to outlive this message port. + explicit CastSocketMessagePort(VirtualConnectionRouter* router); + ~CastSocketMessagePort() override; + + void SetSocket(WeakPtr<CastSocket> socket); + + // Returns current socket identifier, or -1 if not connected. + int GetSocketId(); + + // MessagePort overrides. + void SetClient(MessagePort::Client* client, + std::string client_sender_id) override; + void ResetClient() override; + void PostMessage(const std::string& destination_sender_id, + const std::string& message_namespace, + const std::string& message) override; + + // CastMessageHandler overrides. + void OnMessage(VirtualConnectionRouter* router, + CastSocket* socket, + ::cast::channel::CastMessage message) override; + + private: + VirtualConnectionRouter* const router_; + std::string client_sender_id_; + MessagePort::Client* client_ = nullptr; + WeakPtr<CastSocket> socket_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_COMMON_CHANNEL_CAST_SOCKET_MESSAGE_PORT_H_ diff --git a/chromium/third_party/openscreen/src/cast/common/channel/connection_namespace_handler.cc b/chromium/third_party/openscreen/src/cast/common/channel/connection_namespace_handler.cc index 396b5d53159..a449dcbddd8 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/connection_namespace_handler.cc +++ b/chromium/third_party/openscreen/src/cast/common/channel/connection_namespace_handler.cc @@ -4,7 +4,9 @@ #include "cast/common/channel/connection_namespace_handler.h" +#include <string> #include <type_traits> +#include <utility> #include "absl/types/optional.h" #include "cast/common/channel/message_util.h" @@ -138,7 +140,7 @@ void ConnectionNamespaceHandler::HandleConnect(VirtualConnectionRouter* router, VirtualConnection virtual_conn{std::move(message.destination_id()), std::move(message.source_id()), - socket->socket_id()}; + ToCastSocketId(socket)}; if (!vc_policy_->IsConnectionAllowed(virtual_conn)) { SendClose(router, std::move(virtual_conn)); return; @@ -187,7 +189,11 @@ void ConnectionNamespaceHandler::HandleConnect(VirtualConnectionRouter* router, data.max_protocol_version = VirtualConnection::ProtocolVersion::kV2_1_0; } - data.ip_fragment = socket->GetSanitizedIpAddress(); + if (socket) { + data.ip_fragment = socket->GetSanitizedIpAddress(); + } else { + data.ip_fragment = {}; + } OSP_DVLOG << "Connection opened: " << virtual_conn.local_id << ", " << virtual_conn.peer_id << ", " << virtual_conn.socket_id; @@ -208,7 +214,7 @@ void ConnectionNamespaceHandler::HandleClose(VirtualConnectionRouter* router, Json::Value parsed_message) { VirtualConnection virtual_conn{std::move(message.destination_id()), std::move(message.source_id()), - socket->socket_id()}; + ToCastSocketId(socket)}; if (!vc_manager_->GetConnectionData(virtual_conn)) { return; } diff --git a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection.h b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection.h index 04f3ba06ab4..6f8b2cb833a 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection.h +++ b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection.h @@ -97,6 +97,8 @@ struct VirtualConnection { // generated and intended to be unique within that device. // - GUID-style hex string: Random string identifying a particular receiver // app on the device. + // + // Additionally, |peer_id| can be an asterisk when broadcast-sending. std::string local_id; std::string peer_id; int socket_id; diff --git a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.cc b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.cc index 74efcd89e34..140ca138758 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.cc +++ b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.cc @@ -4,6 +4,8 @@ #include "cast/common/channel/virtual_connection_router.h" +#include <utility> + #include "cast/common/channel/cast_message_handler.h" #include "cast/common/channel/message_util.h" #include "cast/common/channel/proto/cast_channel.pb.h" @@ -55,7 +57,11 @@ void VirtualConnectionRouter::CloseSocket(int id) { Error VirtualConnectionRouter::Send(VirtualConnection virtual_conn, CastMessage message) { - // TODO(btolsch): Check for broadcast message. + if (virtual_conn.peer_id == kBroadcastId) { + return BroadcastFromLocalPeer(std::move(virtual_conn.local_id), + std::move(message)); + } + if (!IsTransportNamespace(message.namespace_()) && !vc_manager_->GetConnectionData(virtual_conn)) { return Error::Code::kNoActiveConnection; @@ -69,8 +75,33 @@ Error VirtualConnectionRouter::Send(VirtualConnection virtual_conn, return it->second.socket->Send(message); } +Error VirtualConnectionRouter::BroadcastFromLocalPeer( + std::string local_id, + ::cast::channel::CastMessage message) { + message.set_source_id(std::move(local_id)); + message.set_destination_id(kBroadcastId); + + // Broadcast to local endpoints. + for (const auto& entry : endpoints_) { + if (entry.first != message.source_id()) { + entry.second->OnMessage(this, nullptr, message); + } + } + + // Broadcast to remote endpoints. If an Error occurs, continue broadcasting, + // and later return the first Error that occurred. + Error error; + for (const auto& entry : sockets_) { + auto result = entry.second.socket->Send(message); + if (!result.ok() && error.ok()) { + error = std::move(result); + } + } + return error; +} + void VirtualConnectionRouter::OnError(CastSocket* socket, Error error) { - int id = socket->socket_id(); + const int id = socket->socket_id(); auto it = sockets_.find(id); if (it != sockets_.end()) { vc_manager_->RemoveConnectionsBySocketId(id, VirtualConnection::kUnknown); @@ -83,17 +114,23 @@ void VirtualConnectionRouter::OnError(CastSocket* socket, Error error) { void VirtualConnectionRouter::OnMessage(CastSocket* socket, CastMessage message) { - // TODO(btolsch): Check for broadcast message. - VirtualConnection virtual_conn{message.destination_id(), message.source_id(), - socket->socket_id()}; - if (!IsTransportNamespace(message.namespace_()) && - !vc_manager_->GetConnectionData(virtual_conn)) { - return; - } + OSP_DCHECK(socket); + const std::string& local_id = message.destination_id(); - auto it = endpoints_.find(local_id); - if (it != endpoints_.end()) { - it->second->OnMessage(this, socket, std::move(message)); + if (local_id == kBroadcastId) { + for (const auto& entry : endpoints_) { + entry.second->OnMessage(this, socket, message); + } + } else { + if (!IsTransportNamespace(message.namespace_()) && + !vc_manager_->GetConnectionData(VirtualConnection{ + local_id, message.source_id(), socket->socket_id()})) { + return; + } + auto it = endpoints_.find(local_id); + if (it != endpoints_.end()) { + it->second->OnMessage(this, socket, std::move(message)); + } } } diff --git a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.h b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.h index 3f549b1e6aa..1bbf2bc160f 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.h +++ b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router.h @@ -62,11 +62,16 @@ class VirtualConnectionRouter final : public CastSocket::Client { Error Send(VirtualConnection virtual_conn, ::cast::channel::CastMessage message); + Error BroadcastFromLocalPeer(std::string local_id, + ::cast::channel::CastMessage message); + // CastSocket::Client overrides. void OnError(CastSocket* socket, Error error) override; void OnMessage(CastSocket* socket, ::cast::channel::CastMessage message) override; + VirtualConnectionManager* manager() { return vc_manager_; } + private: struct SocketWithHandler { std::unique_ptr<CastSocket> socket; diff --git a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router_unittest.cc b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router_unittest.cc index 6b1f0055ac5..b05d10e3a60 100644 --- a/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/common/channel/virtual_connection_router_unittest.cc @@ -4,6 +4,9 @@ #include "cast/common/channel/virtual_connection_router.h" +#include <utility> + +#include "cast/common/channel/message_util.h" #include "cast/common/channel/proto/cast_channel.pb.h" #include "cast/common/channel/testing/fake_cast_socket.h" #include "cast/common/channel/testing/mock_cast_message_handler.h" @@ -19,35 +22,43 @@ namespace { using ::cast::channel::CastMessage; using ::testing::_; using ::testing::Invoke; +using ::testing::SaveArg; +using ::testing::WithArg; class VirtualConnectionRouterTest : public ::testing::Test { public: void SetUp() override { - socket_ = fake_cast_socket_pair_.socket.get(); - router_.TakeSocket(&mock_error_handler_, - std::move(fake_cast_socket_pair_.socket)); + local_socket_ = fake_cast_socket_pair_.socket.get(); + local_router_.TakeSocket(&mock_error_handler_, + std::move(fake_cast_socket_pair_.socket)); + + remote_socket_ = fake_cast_socket_pair_.peer_socket.get(); + remote_router_.TakeSocket(&mock_error_handler_, + std::move(fake_cast_socket_pair_.peer_socket)); } protected: - CastSocket& peer_socket() { return *fake_cast_socket_pair_.peer_socket; } - FakeCastSocketPair fake_cast_socket_pair_; - CastSocket* socket_; + CastSocket* local_socket_; + CastSocket* remote_socket_; MockSocketErrorHandler mock_error_handler_; - MockCastMessageHandler mock_message_handler_; - VirtualConnectionManager manager_; - VirtualConnectionRouter router_{&manager_}; + VirtualConnectionManager local_manager_; + VirtualConnectionRouter local_router_{&local_manager_}; + + VirtualConnectionManager remote_manager_; + VirtualConnectionRouter remote_router_{&remote_manager_}; }; } // namespace TEST_F(VirtualConnectionRouterTest, LocalIdHandler) { - router_.AddHandlerForLocalId("receiver-1234", &mock_message_handler_); - manager_.AddConnection( - VirtualConnection{"receiver-1234", "sender-9873", socket_->socket_id()}, - {}); + MockCastMessageHandler mock_message_handler; + local_router_.AddHandlerForLocalId("receiver-1234", &mock_message_handler); + local_manager_.AddConnection(VirtualConnection{"receiver-1234", "sender-9873", + local_socket_->socket_id()}, + {}); CastMessage message; message.set_protocol_version( @@ -57,22 +68,25 @@ TEST_F(VirtualConnectionRouterTest, LocalIdHandler) { message.set_destination_id("receiver-1234"); message.set_payload_type(CastMessage::STRING); message.set_payload_utf8("cnlybnq"); - EXPECT_CALL(mock_message_handler_, OnMessage(_, socket_, _)); - EXPECT_TRUE(peer_socket().Send(message).ok()); + EXPECT_CALL(mock_message_handler, OnMessage(_, local_socket_, _)); + EXPECT_TRUE(remote_socket_->Send(message).ok()); - EXPECT_CALL(mock_message_handler_, OnMessage(_, socket_, _)); - EXPECT_TRUE(peer_socket().Send(message).ok()); + EXPECT_CALL(mock_message_handler, OnMessage(_, local_socket_, _)); + EXPECT_TRUE(remote_socket_->Send(message).ok()); message.set_destination_id("receiver-4321"); - EXPECT_CALL(mock_message_handler_, OnMessage(_, _, _)).Times(0); - EXPECT_TRUE(peer_socket().Send(message).ok()); + EXPECT_CALL(mock_message_handler, OnMessage(_, _, _)).Times(0); + EXPECT_TRUE(remote_socket_->Send(message).ok()); + + local_router_.RemoveHandlerForLocalId("receiver-1234"); } TEST_F(VirtualConnectionRouterTest, RemoveLocalIdHandler) { - router_.AddHandlerForLocalId("receiver-1234", &mock_message_handler_); - manager_.AddConnection( - VirtualConnection{"receiver-1234", "sender-9873", socket_->socket_id()}, - {}); + MockCastMessageHandler mock_message_handler; + local_router_.AddHandlerForLocalId("receiver-1234", &mock_message_handler); + local_manager_.AddConnection(VirtualConnection{"receiver-1234", "sender-9873", + local_socket_->socket_id()}, + {}); CastMessage message; message.set_protocol_version( @@ -82,18 +96,27 @@ TEST_F(VirtualConnectionRouterTest, RemoveLocalIdHandler) { message.set_destination_id("receiver-1234"); message.set_payload_type(CastMessage::STRING); message.set_payload_utf8("cnlybnq"); - EXPECT_CALL(mock_message_handler_, OnMessage(_, socket_, _)); - EXPECT_TRUE(peer_socket().Send(message).ok()); + EXPECT_CALL(mock_message_handler, OnMessage(_, local_socket_, _)); + EXPECT_TRUE(remote_socket_->Send(message).ok()); + + local_router_.RemoveHandlerForLocalId("receiver-1234"); - router_.RemoveHandlerForLocalId("receiver-1234"); + EXPECT_CALL(mock_message_handler, OnMessage(_, local_socket_, _)).Times(0); + EXPECT_TRUE(remote_socket_->Send(message).ok()); - EXPECT_CALL(mock_message_handler_, OnMessage(_, socket_, _)).Times(0); - EXPECT_TRUE(peer_socket().Send(message).ok()); + local_router_.RemoveHandlerForLocalId("receiver-1234"); } TEST_F(VirtualConnectionRouterTest, SendMessage) { - manager_.AddConnection( - VirtualConnection{"receiver-1234", "sender-4321", socket_->socket_id()}, + local_manager_.AddConnection(VirtualConnection{"receiver-1234", "sender-4321", + local_socket_->socket_id()}, + {}); + + MockCastMessageHandler destination; + remote_router_.AddHandlerForLocalId("sender-4321", &destination); + remote_manager_.AddConnection( + VirtualConnection{"sender-4321", "receiver-1234", + remote_socket_->socket_id()}, {}); CastMessage message; @@ -104,30 +127,159 @@ TEST_F(VirtualConnectionRouterTest, SendMessage) { message.set_destination_id("sender-4321"); message.set_payload_type(CastMessage::STRING); message.set_payload_utf8("cnlybnq"); - EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _)) - .WillOnce(Invoke([](CastSocket* socket, CastMessage message) { - EXPECT_EQ(message.namespace_(), "zrqvn"); - EXPECT_EQ(message.source_id(), "receiver-1234"); - EXPECT_EQ(message.destination_id(), "sender-4321"); - ASSERT_EQ(message.payload_type(), - ::cast::channel::CastMessage_PayloadType_STRING); - EXPECT_EQ(message.payload_utf8(), "cnlybnq"); - })); - router_.Send( - VirtualConnection{"receiver-1234", "sender-4321", socket_->socket_id()}, - std::move(message)); + ASSERT_TRUE(message.IsInitialized()); + + EXPECT_CALL(destination, OnMessage(&remote_router_, remote_socket_, _)) + .WillOnce( + WithArg<2>(Invoke([&message](CastMessage message_at_destination) { + ASSERT_TRUE(message_at_destination.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), + message_at_destination.SerializeAsString()); + }))); + local_router_.Send(VirtualConnection{"receiver-1234", "sender-4321", + local_socket_->socket_id()}, + message); } TEST_F(VirtualConnectionRouterTest, CloseSocketRemovesVirtualConnections) { - manager_.AddConnection( - VirtualConnection{"receiver-1234", "sender-4321", socket_->socket_id()}, - {}); + local_manager_.AddConnection(VirtualConnection{"receiver-1234", "sender-4321", + local_socket_->socket_id()}, + {}); + + EXPECT_CALL(mock_error_handler_, OnClose(local_socket_)).Times(1); - int id = socket_->socket_id(); - router_.CloseSocket(id); - EXPECT_FALSE(manager_.GetConnectionData( + int id = local_socket_->socket_id(); + local_router_.CloseSocket(id); + EXPECT_FALSE(local_manager_.GetConnectionData( VirtualConnection{"receiver-1234", "sender-4321", id})); } +// Tests that VirtualConnectionRouter::Send() broadcasts a message from a local +// source to both: 1) all other local peers; and 2) all remote peers. +TEST_F(VirtualConnectionRouterTest, BroadcastsFromLocalSource) { + // Local peers. + MockCastMessageHandler alice, bob; + local_router_.AddHandlerForLocalId("alice", &alice); + local_router_.AddHandlerForLocalId("bob", &bob); + + // Remote peers. + MockCastMessageHandler charlie, dave, eve; + remote_router_.AddHandlerForLocalId("charlie", &charlie); + remote_router_.AddHandlerForLocalId("dave", &dave); + remote_router_.AddHandlerForLocalId("eve", &eve); + + // The local broadcaster, which should never receive her own messages. + MockCastMessageHandler wendy; + local_router_.AddHandlerForLocalId("wendy", &wendy); + EXPECT_CALL(wendy, OnMessage(_, _, _)).Times(0); + + CastMessage message; + message.set_protocol_version( + ::cast::channel::CastMessage_ProtocolVersion_CASTV2_1_0); + message.set_namespace_("zrqvn"); + message.set_payload_type(CastMessage::STRING); + message.set_payload_utf8("cnlybnq"); + + CastMessage message_alice_got, message_bob_got, message_charlie_got, + message_dave_got, message_eve_got; + EXPECT_CALL(alice, OnMessage(&local_router_, nullptr, _)) + .WillOnce(SaveArg<2>(&message_alice_got)) + .RetiresOnSaturation(); + EXPECT_CALL(bob, OnMessage(&local_router_, nullptr, _)) + .WillOnce(SaveArg<2>(&message_bob_got)) + .RetiresOnSaturation(); + EXPECT_CALL(charlie, OnMessage(&remote_router_, remote_socket_, _)) + .WillOnce(SaveArg<2>(&message_charlie_got)) + .RetiresOnSaturation(); + EXPECT_CALL(dave, OnMessage(&remote_router_, remote_socket_, _)) + .WillOnce(SaveArg<2>(&message_dave_got)) + .RetiresOnSaturation(); + EXPECT_CALL(eve, OnMessage(&remote_router_, remote_socket_, _)) + .WillOnce(SaveArg<2>(&message_eve_got)) + .RetiresOnSaturation(); + ASSERT_TRUE(local_router_.BroadcastFromLocalPeer("wendy", message).ok()); + + // Confirm message data is correct. + message.set_source_id("wendy"); + message.set_destination_id(kBroadcastId); + ASSERT_TRUE(message.IsInitialized()); + ASSERT_TRUE(message_alice_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_alice_got.SerializeAsString()); + ASSERT_TRUE(message_bob_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_bob_got.SerializeAsString()); + ASSERT_TRUE(message_charlie_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), + message_charlie_got.SerializeAsString()); + ASSERT_TRUE(message_dave_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_dave_got.SerializeAsString()); + ASSERT_TRUE(message_eve_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_eve_got.SerializeAsString()); + + // Remove one local peer and one remote peer, and confirm only the correct + // entities receive a broadcast message. + local_router_.RemoveHandlerForLocalId("bob"); + remote_router_.RemoveHandlerForLocalId("charlie"); + EXPECT_CALL(alice, OnMessage(&local_router_, nullptr, _)).Times(1); + EXPECT_CALL(bob, OnMessage(_, _, _)).Times(0); + EXPECT_CALL(charlie, OnMessage(_, _, _)).Times(0); + EXPECT_CALL(dave, OnMessage(&remote_router_, remote_socket_, _)).Times(1); + EXPECT_CALL(eve, OnMessage(&remote_router_, remote_socket_, _)).Times(1); + ASSERT_TRUE(local_router_.BroadcastFromLocalPeer("wendy", message).ok()); +} + +// Tests that VirtualConnectionRouter::OnMessage() broadcasts a message from a +// remote source to all local peers. +TEST_F(VirtualConnectionRouterTest, BroadcastsFromRemoteSource) { + // Local peers. + MockCastMessageHandler alice, bob, charlie; + local_router_.AddHandlerForLocalId("alice", &alice); + local_router_.AddHandlerForLocalId("bob", &bob); + local_router_.AddHandlerForLocalId("charlie", &charlie); + + // The remote broadcaster, which should never receive her own messages. + MockCastMessageHandler wendy; + remote_router_.AddHandlerForLocalId("wendy", &wendy); + EXPECT_CALL(wendy, OnMessage(_, _, _)).Times(0); + + CastMessage message; + message.set_protocol_version( + ::cast::channel::CastMessage_ProtocolVersion_CASTV2_1_0); + message.set_namespace_("zrqvn"); + message.set_payload_type(CastMessage::STRING); + message.set_payload_utf8("cnlybnq"); + + CastMessage message_alice_got, message_bob_got, message_charlie_got; + EXPECT_CALL(alice, OnMessage(&local_router_, local_socket_, _)) + .WillOnce(SaveArg<2>(&message_alice_got)) + .RetiresOnSaturation(); + EXPECT_CALL(bob, OnMessage(&local_router_, local_socket_, _)) + .WillOnce(SaveArg<2>(&message_bob_got)) + .RetiresOnSaturation(); + EXPECT_CALL(charlie, OnMessage(&local_router_, local_socket_, _)) + .WillOnce(SaveArg<2>(&message_charlie_got)) + .RetiresOnSaturation(); + ASSERT_TRUE(remote_router_.BroadcastFromLocalPeer("wendy", message).ok()); + + // Confirm message data is correct. + message.set_source_id("wendy"); + message.set_destination_id(kBroadcastId); + ASSERT_TRUE(message.IsInitialized()); + ASSERT_TRUE(message_alice_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_alice_got.SerializeAsString()); + ASSERT_TRUE(message_bob_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), message_bob_got.SerializeAsString()); + ASSERT_TRUE(message_charlie_got.IsInitialized()); + EXPECT_EQ(message.SerializeAsString(), + message_charlie_got.SerializeAsString()); + + // Remove one local peer, and confirm only the two remaining local peers + // receive a broadcast message from the remote source. + local_router_.RemoveHandlerForLocalId("bob"); + EXPECT_CALL(alice, OnMessage(&local_router_, local_socket_, _)).Times(1); + EXPECT_CALL(bob, OnMessage(_, _, _)).Times(0); + EXPECT_CALL(charlie, OnMessage(&local_router_, local_socket_, _)).Times(1); + ASSERT_TRUE(remote_router_.BroadcastFromLocalPeer("wendy", message).ok()); +} + } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/common/public/cast_socket.h b/chromium/third_party/openscreen/src/cast/common/public/cast_socket.h index d7ac683ffb1..2a67b6590fc 100644 --- a/chromium/third_party/openscreen/src/cast/common/public/cast_socket.h +++ b/chromium/third_party/openscreen/src/cast/common/public/cast_socket.h @@ -79,6 +79,11 @@ class CastSocket : public TlsConnection::Client { WeakPtrFactory<CastSocket> weak_factory_{this}; }; +// Returns socket->socket_id() if |socket| is not null, otherwise 0. +inline int ToCastSocketId(CastSocket* socket) { + return socket ? socket->socket_id() : 0; +} + } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/common/public/message_port.h b/chromium/third_party/openscreen/src/cast/common/public/message_port.h new file mode 100644 index 00000000000..0e62dfe6e90 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/common/public/message_port.h @@ -0,0 +1,41 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_COMMON_PUBLIC_MESSAGE_PORT_H_ +#define CAST_COMMON_PUBLIC_MESSAGE_PORT_H_ + +#include <string> + +#include "platform/base/error.h" + +namespace openscreen { +namespace cast { + +// This interface is intended to provide an abstraction for communicating +// cast messages across a pipe with guaranteed delivery. This is used to +// decouple the cast streaming receiver and sender sessions from the +// network implementation. +class MessagePort { + public: + class Client { + public: + virtual void OnMessage(const std::string& source_sender_id, + const std::string& message_namespace, + const std::string& message) = 0; + virtual void OnError(Error error) = 0; + }; + + virtual ~MessagePort() = default; + virtual void SetClient(Client* client, std::string client_sender_id) = 0; + virtual void ResetClient() = 0; + + virtual void PostMessage(const std::string& destination_sender_id, + const std::string& message_namespace, + const std::string& message) = 0; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_COMMON_PUBLIC_MESSAGE_PORT_H_ diff --git a/chromium/third_party/openscreen/src/cast/receiver/BUILD.gn b/chromium/third_party/openscreen/src/cast/receiver/BUILD.gn index 68db5e04015..b9a6573422c 100644 --- a/chromium/third_party/openscreen/src/cast/receiver/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/receiver/BUILD.gn @@ -9,6 +9,8 @@ source_set("channel") { "channel/message_util.cc", "channel/message_util.h", "channel/receiver_socket_factory.cc", + "channel/static_credentials.cc", + "channel/static_credentials.h", "public/receiver_socket_factory.h", ] @@ -46,9 +48,7 @@ source_set("test_helpers") { source_set("unittests") { testonly = true - sources = [ - "channel/device_auth_namespace_handler_unittest.cc", - ] + sources = [ "channel/device_auth_namespace_handler_unittest.cc" ] deps = [ ":channel", @@ -61,7 +61,5 @@ source_set("unittests") { "../common/channel/proto:channel_proto", ] - data = [ - "../../test/data/cast/receiver/channel/", - ] + data = [ "../../test/data/cast/receiver/channel/" ] } diff --git a/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler.cc b/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler.cc index 239459a0032..17aca182bfb 100644 --- a/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler.cc +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler.cc @@ -6,6 +6,9 @@ #include <openssl/evp.h> +#include <memory> +#include <utility> + #include "cast/common/certificate/cast_cert_validator.h" #include "cast/common/channel/message_util.h" #include "cast/common/channel/proto/cast_channel.pb.h" @@ -54,6 +57,9 @@ DeviceAuthNamespaceHandler::~DeviceAuthNamespaceHandler() = default; void DeviceAuthNamespaceHandler::OnMessage(VirtualConnectionRouter* router, CastSocket* socket, CastMessage message) { + if (!socket) { + return; // Don't handle auth messages from local senders. That's nonsense. + } if (message.payload_type() != ::cast::channel::CastMessage_PayloadType_BINARY) { return; diff --git a/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler_unittest.cc b/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler_unittest.cc index 3934b96e4ce..edfc807bafe 100644 --- a/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/device_auth_namespace_handler_unittest.cc @@ -4,6 +4,9 @@ #include "cast/receiver/channel/device_auth_namespace_handler.h" +#include <utility> + +#include "cast/common/certificate/testing/test_helpers.h" #include "cast/common/channel/message_util.h" #include "cast/common/channel/proto/cast_channel.pb.h" #include "cast/common/channel/testing/fake_cast_socket.h" @@ -11,6 +14,7 @@ #include "cast/common/channel/virtual_connection_manager.h" #include "cast/common/channel/virtual_connection_router.h" #include "cast/common/public/cast_socket.h" +#include "cast/receiver/channel/static_credentials.h" #include "cast/receiver/channel/testing/device_auth_test_helpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/static_credentials.cc b/chromium/third_party/openscreen/src/cast/receiver/channel/static_credentials.cc index 9980f20caf7..73a5d95fda6 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/static_credentials.cc +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/static_credentials.cc @@ -2,16 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "cast/standalone_receiver/static_credentials.h" +#include "cast/receiver/channel/static_credentials.h" #include <openssl/mem.h> +#include <openssl/pem.h> +#include <cstdio> #include <memory> #include <string> #include <utility> #include <vector> -#include "cast/standalone_receiver/private_key_der.h" #include "platform/base/tls_credentials.h" #include "util/crypto/certificate_utils.h" #include "util/osp_logging.h" @@ -20,46 +21,26 @@ namespace openscreen { namespace cast { namespace { +using FileUniquePtr = std::unique_ptr<FILE, decltype(&fclose)>; + constexpr int kThreeDaysInSeconds = 3 * 24 * 60 * 60; constexpr auto kCertificateDuration = std::chrono::seconds(kThreeDaysInSeconds); -} // namespace - -StaticCredentialsProvider::StaticCredentialsProvider() = default; -StaticCredentialsProvider::StaticCredentialsProvider( - DeviceCredentials device_creds, - std::vector<uint8_t> tls_cert_der) - : device_creds(std::move(device_creds)), - tls_cert_der(std::move(tls_cert_der)) {} - -StaticCredentialsProvider::StaticCredentialsProvider( - StaticCredentialsProvider&&) = default; -StaticCredentialsProvider& StaticCredentialsProvider::operator=( - StaticCredentialsProvider&&) = default; -StaticCredentialsProvider::~StaticCredentialsProvider() = default; - ErrorOr<GeneratedCredentials> GenerateCredentials( - absl::string_view device_certificate_id) { - GeneratedCredentials credentials; - - bssl::UniquePtr<EVP_PKEY> root_key = GenerateRsaKeyPair(); + std::string device_certificate_id, + EVP_PKEY* root_key, + X509* root_cert) { + OSP_CHECK(root_key); + OSP_CHECK(root_cert); bssl::UniquePtr<EVP_PKEY> intermediate_key = GenerateRsaKeyPair(); bssl::UniquePtr<EVP_PKEY> device_key = GenerateRsaKeyPair(); - OSP_CHECK(root_key); OSP_CHECK(intermediate_key); OSP_CHECK(device_key); - ErrorOr<bssl::UniquePtr<X509>> root_cert_or_error = - CreateSelfSignedX509Certificate("Cast Root CA", kCertificateDuration, - *root_key, GetWallTimeSinceUnixEpoch(), - true); - OSP_CHECK(root_cert_or_error); - bssl::UniquePtr<X509> root_cert = std::move(root_cert_or_error.value()); - ErrorOr<bssl::UniquePtr<X509>> intermediate_cert_or_error = CreateSelfSignedX509Certificate( "Cast Intermediate", kCertificateDuration, *intermediate_key, - GetWallTimeSinceUnixEpoch(), true, root_cert.get(), root_key.get()); + GetWallTimeSinceUnixEpoch(), true, root_cert, root_key); OSP_CHECK(intermediate_cert_or_error); bssl::UniquePtr<X509> intermediate_cert = std::move(intermediate_cert_or_error.value()); @@ -72,7 +53,7 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( OSP_CHECK(device_cert_or_error); bssl::UniquePtr<X509> device_cert = std::move(device_cert_or_error.value()); - // NOTE: Device cert chain plumbing + serialization. + // Device cert chain plumbing + serialization. DeviceCredentials device_creds; device_creds.private_key = std::move(device_key); @@ -88,12 +69,12 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( i2d_X509(intermediate_cert.get(), &out); device_creds.certs.emplace_back(std::move(cert_serial)); - cert_length = i2d_X509(root_cert.get(), nullptr); + cert_length = i2d_X509(root_cert, nullptr); std::vector<uint8_t> trust_anchor_der(cert_length); out = &trust_anchor_der[0]; - i2d_X509(root_cert.get(), &out); + i2d_X509(root_cert, &out); - // NOTE: TLS key pair + certificate generation. + // TLS key pair + certificate generation. bssl::UniquePtr<EVP_PKEY> tls_key = GenerateRsaKeyPair(); OSP_CHECK_EQ(EVP_PKEY_id(tls_key.get()), EVP_PKEY_RSA); ErrorOr<bssl::UniquePtr<X509>> tls_cert_or_error = @@ -102,7 +83,7 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( OSP_CHECK(tls_cert_or_error); bssl::UniquePtr<X509> tls_cert = std::move(tls_cert_or_error.value()); - // NOTE: TLS private key serialization. + // TLS private key serialization. RSA* rsa_key = EVP_PKEY_get0_RSA(tls_key.get()); size_t pkey_len = 0; uint8_t* pkey_bytes = nullptr; @@ -111,7 +92,7 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( std::vector<uint8_t> tls_key_serial(pkey_bytes, pkey_bytes + pkey_len); OPENSSL_free(pkey_bytes); - // NOTE: TLS public key serialization. + // TLS public key serialization. pkey_len = 0; pkey_bytes = nullptr; OSP_CHECK(RSA_public_key_to_bytes(&pkey_bytes, &pkey_len, rsa_key)); @@ -119,7 +100,7 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( std::vector<uint8_t> tls_pub_serial(pkey_bytes, pkey_bytes + pkey_len); OPENSSL_free(pkey_bytes); - // NOTE: TLS cert serialization. + // TLS cert serialization. cert_length = 0; cert_length = i2d_X509(tls_cert.get(), nullptr); OSP_CHECK_GT(cert_length, 0); @@ -127,13 +108,75 @@ ErrorOr<GeneratedCredentials> GenerateCredentials( out = &tls_cert_serial[0]; i2d_X509(tls_cert.get(), &out); + auto provider = std::make_unique<StaticCredentialsProvider>( + std::move(device_creds), tls_cert_serial); return GeneratedCredentials{ - std::make_unique<StaticCredentialsProvider>(std::move(device_creds), - tls_cert_serial), + std::move(provider), TlsCredentials{std::move(tls_key_serial), std::move(tls_pub_serial), std::move(tls_cert_serial)}, std::move(trust_anchor_der)}; } +bssl::UniquePtr<X509> GenerateRootCert(const EVP_PKEY& root_key) { + ErrorOr<bssl::UniquePtr<X509>> root_cert_or_error = + CreateSelfSignedX509Certificate("Cast Root CA", kCertificateDuration, + root_key, GetWallTimeSinceUnixEpoch(), + true); + OSP_CHECK(root_cert_or_error); + return std::move(root_cert_or_error.value()); +} +} // namespace + +StaticCredentialsProvider::StaticCredentialsProvider() = default; +StaticCredentialsProvider::StaticCredentialsProvider( + DeviceCredentials device_creds, + std::vector<uint8_t> tls_cert_der) + : device_creds(std::move(device_creds)), + tls_cert_der(std::move(tls_cert_der)) {} + +StaticCredentialsProvider::StaticCredentialsProvider( + StaticCredentialsProvider&&) = default; +StaticCredentialsProvider& StaticCredentialsProvider::operator=( + StaticCredentialsProvider&&) = default; +StaticCredentialsProvider::~StaticCredentialsProvider() = default; + +ErrorOr<GeneratedCredentials> GenerateCredentials( + const std::string& device_certificate_id) { + bssl::UniquePtr<EVP_PKEY> root_key = GenerateRsaKeyPair(); + OSP_CHECK(root_key); + + bssl::UniquePtr<X509> root_cert = GenerateRootCert(*root_key); + OSP_CHECK(root_cert); + + return GenerateCredentials(device_certificate_id, root_key.get(), + root_cert.get()); +} + +ErrorOr<GeneratedCredentials> GenerateCredentials( + const std::string& device_certificate_id, + const std::string& private_key_path, + const std::string& server_certificate_path) { + OSP_CHECK(!private_key_path.empty() && !server_certificate_path.empty()); + + FileUniquePtr key_file(fopen(private_key_path.c_str(), "r"), &fclose); + if (!key_file) { + return Error(Error::Code::kParameterInvalid, + "Missing private key file path"); + } + bssl::UniquePtr<EVP_PKEY> root_key(PEM_read_PrivateKey( + key_file.get(), nullptr /* x */, nullptr /* cb */, nullptr /* u */)); + + FileUniquePtr cert_file(fopen(server_certificate_path.c_str(), "r"), &fclose); + if (!cert_file) { + return Error(Error::Code::kParameterInvalid, + "Missing server certificate file path"); + } + bssl::UniquePtr<X509> root_cert(PEM_read_X509( + cert_file.get(), nullptr /* x */, nullptr /* cb */, nullptr /* u */)); + + return GenerateCredentials(device_certificate_id, root_key.get(), + root_cert.get()); +} + } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/static_credentials.h b/chromium/third_party/openscreen/src/cast/receiver/channel/static_credentials.h index 4707f5f40af..97b90cc882c 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/static_credentials.h +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/static_credentials.h @@ -2,13 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CAST_STANDALONE_RECEIVER_STATIC_CREDENTIALS_H_ -#define CAST_STANDALONE_RECEIVER_STATIC_CREDENTIALS_H_ +#ifndef CAST_RECEIVER_CHANNEL_STATIC_CREDENTIALS_H_ +#define CAST_RECEIVER_CHANNEL_STATIC_CREDENTIALS_H_ #include <memory> +#include <string> #include <vector> #include "absl/strings/string_view.h" +#include "cast/common/certificate/cast_cert_validator_internal.h" #include "cast/receiver/channel/device_auth_namespace_handler.h" #include "platform/base/error.h" #include "platform/base/tls_credentials.h" @@ -52,9 +54,14 @@ struct GeneratedCredentials { // stored in private_key_der.h. The certificate is valid for // kCertificateDuration from when this function is called. ErrorOr<GeneratedCredentials> GenerateCredentials( - absl::string_view device_certificate_id); + const std::string& device_certificate_id); + +ErrorOr<GeneratedCredentials> GenerateCredentials( + const std::string& device_certificate_id, + const std::string& private_key_path, + const std::string& server_certificate_path); } // namespace cast } // namespace openscreen -#endif // CAST_STANDALONE_RECEIVER_STATIC_CREDENTIALS_H_ +#endif // CAST_RECEIVER_CHANNEL_STATIC_CREDENTIALS_H_ diff --git a/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.cc b/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.cc index 51d7ebaa32a..77237dad648 100644 --- a/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.cc +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.cc @@ -4,7 +4,12 @@ #include "cast/receiver/channel/testing/device_auth_test_helpers.h" +#include <string> +#include <utility> + +#include "cast/common/certificate/testing/test_helpers.h" #include "gtest/gtest.h" +#include "util/crypto/pem_helpers.h" namespace openscreen { namespace cast { @@ -15,10 +20,9 @@ void InitStaticCredentialsFromFiles(StaticCredentialsProvider* creds, absl::string_view privkey_filename, absl::string_view chain_filename, absl::string_view tls_filename) { - auto private_key = testing::ReadKeyFromPemFile(privkey_filename); + auto private_key = ReadKeyFromPemFile(privkey_filename); ASSERT_TRUE(private_key); - std::vector<std::string> certs = - testing::ReadCertificatesFromPemFile(chain_filename); + std::vector<std::string> certs = ReadCertificatesFromPemFile(chain_filename); ASSERT_GT(certs.size(), 1u); // Use the root of the chain as the trust store for the test. @@ -35,7 +39,7 @@ void InitStaticCredentialsFromFiles(StaticCredentialsProvider* creds, std::move(certs), std::move(private_key), std::string()}; const std::vector<std::string> tls_cert = - testing::ReadCertificatesFromPemFile(tls_filename); + ReadCertificatesFromPemFile(tls_filename); ASSERT_EQ(tls_cert.size(), 1u); data = reinterpret_cast<const uint8_t*>(tls_cert[0].data()); if (parsed_cert) { diff --git a/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.h b/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.h index 65ddccf3b9e..6d9b03ffe81 100644 --- a/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.h +++ b/chromium/third_party/openscreen/src/cast/receiver/channel/testing/device_auth_test_helpers.h @@ -10,29 +10,12 @@ #include <vector> #include "absl/strings/string_view.h" -#include "cast/common/certificate/testing/test_helpers.h" #include "cast/receiver/channel/device_auth_namespace_handler.h" +#include "cast/receiver/channel/static_credentials.h" namespace openscreen { namespace cast { -class StaticCredentialsProvider final - : public DeviceAuthNamespaceHandler::CredentialsProvider { - public: - StaticCredentialsProvider() = default; - ~StaticCredentialsProvider() = default; - - absl::Span<const uint8_t> GetCurrentTlsCertAsDer() override { - return absl::Span<uint8_t>(tls_cert_der); - } - const DeviceCredentials& GetCurrentDeviceCredentials() override { - return device_creds; - } - - DeviceCredentials device_creds; - std::vector<uint8_t> tls_cert_der; -}; - void InitStaticCredentialsFromFiles(StaticCredentialsProvider* creds, bssl::UniquePtr<X509>* parsed_cert, TrustStore* fake_trust_store, diff --git a/chromium/third_party/openscreen/src/cast/sender/cast_platform_client.cc b/chromium/third_party/openscreen/src/cast/sender/cast_platform_client.cc index 4d59c65b2b0..224a58a4515 100644 --- a/chromium/third_party/openscreen/src/cast/sender/cast_platform_client.cc +++ b/chromium/third_party/openscreen/src/cast/sender/cast_platform_client.cc @@ -4,7 +4,9 @@ #include "cast/sender/cast_platform_client.h" +#include <memory> #include <random> +#include <utility> #include "absl/strings/str_cat.h" #include "cast/common/channel/virtual_connection_manager.h" @@ -22,6 +24,8 @@ static constexpr std::chrono::seconds kRequestTimeout = std::chrono::seconds(5); namespace { +// TODO(miu): This is duplicated in another teammate's WIP CL. De-dupe this by +// placing the utility in cast/common. std::string MakeRandomSenderId() { static auto& rd = *new std::random_device(); static auto& gen = *new std::mt19937(rd()); @@ -149,8 +153,9 @@ void CastPlatformClient::OnMessage(VirtualConnectionRouter* router, if (request_id) { auto entry = std::find_if( socket_id_by_device_id_.begin(), socket_id_by_device_id_.end(), - [socket](const std::pair<std::string, int>& entry) { - return entry.second == socket->socket_id(); + [socket_id = + ToCastSocketId(socket)](const std::pair<std::string, int>& entry) { + return entry.second == socket_id; }); if (entry != socket_id_by_device_id_.end()) { HandleResponse(entry->first, request_id.value(), dict); diff --git a/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util.cc b/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util.cc index 10cbdc45ad1..cb1ced692f8 100644 --- a/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util.cc +++ b/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util.cc @@ -7,6 +7,7 @@ #include <openssl/rand.h> #include <algorithm> +#include <memory> #include "cast/common/certificate/cast_cert_validator.h" #include "cast/common/certificate/cast_cert_validator_internal.h" @@ -29,13 +30,13 @@ namespace { #define PARSE_ERROR_PREFIX "Failed to parse auth message: " // The maximum number of days a cert can live for. -const int kMaxSelfSignedCertLifetimeInDays = 4; +constexpr int kMaxSelfSignedCertLifetimeInDays = 4; // The size of the nonce challenge in bytes. -const int kNonceSizeInBytes = 16; +constexpr int kNonceSizeInBytes = 16; // The number of hours after which a nonce is regenerated. -long kNonceExpirationTimeInHours = 24; +constexpr int kNonceExpirationTimeInHours = 24; // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply // message. @@ -122,6 +123,9 @@ Error MapToOpenscreenError(Error::Code error, bool crl_required) { case Error::Code::kErrCertsRestrictions: return Error(Error::Code::kCastV2CertNotSignedByTrustedCa, "Failed certificate restrictions."); + case Error::Code::kErrCertsVerifyUntrustedCert: + return Error(Error::Code::kCastV2CertNotSignedByTrustedCa, + "Failed with untrusted certificate."); case Error::Code::kErrCrlInvalid: // This error is only encountered if |crl_required| is true. OSP_DCHECK(crl_required); diff --git a/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util_unittest.cc b/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util_unittest.cc index 0365541900c..acdb07a2add 100644 --- a/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/sender/channel/cast_auth_util_unittest.cc @@ -15,6 +15,7 @@ #include "platform/api/time.h" #include "platform/test/paths.h" #include "testing/util/read_file.h" +#include "util/crypto/pem_helpers.h" #include "util/osp_logging.h" namespace openscreen { @@ -124,7 +125,7 @@ class CastAuthUtilTest : public ::testing::Test { static AuthResponse CreateAuthResponse( std::vector<uint8_t>* signed_data, ::cast::channel::HashAlgorithm digest_algorithm) { - std::vector<std::string> chain = testing::ReadCertificatesFromPemFile( + std::vector<std::string> chain = ReadCertificatesFromPemFile( GetSpecificTestDataPath() + "certificates/chromecast_gen1.pem"); OSP_CHECK(!chain.empty()); @@ -292,7 +293,7 @@ TEST_F(CastAuthUtilTest, VerifySenderNonceMissing) { } TEST_F(CastAuthUtilTest, VerifyTLSCertificateSuccess) { - std::vector<std::string> tls_cert_der = testing::ReadCertificatesFromPemFile( + std::vector<std::string> tls_cert_der = ReadCertificatesFromPemFile( data_path_ + "certificates/test_tls_cert.pem"); std::string& der_cert = tls_cert_der[0]; const uint8_t* data = (const uint8_t*)der_cert.data(); @@ -310,7 +311,7 @@ TEST_F(CastAuthUtilTest, VerifyTLSCertificateSuccess) { } TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooEarly) { - std::vector<std::string> tls_cert_der = testing::ReadCertificatesFromPemFile( + std::vector<std::string> tls_cert_der = ReadCertificatesFromPemFile( data_path_ + "certificates/test_tls_cert.pem"); std::string& der_cert = tls_cert_der[0]; const uint8_t* data = (const uint8_t*)der_cert.data(); @@ -331,7 +332,7 @@ TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooEarly) { } TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooLate) { - std::vector<std::string> tls_cert_der = testing::ReadCertificatesFromPemFile( + std::vector<std::string> tls_cert_der = ReadCertificatesFromPemFile( data_path_ + "certificates/test_tls_cert.pem"); std::string& der_cert = tls_cert_der[0]; const uint8_t* data = (const uint8_t*)der_cert.data(); @@ -392,16 +393,16 @@ ErrorOr<CastDeviceCertPolicy> TestVerifyRevocation( // Runs a single test case. bool RunTest(const DeviceCertTest& test_case) { - std::unique_ptr<TrustStore> crl_trust_store; - std::unique_ptr<TrustStore> cast_trust_store; + TrustStore crl_trust_store; + TrustStore cast_trust_store; if (test_case.use_test_trust_anchors()) { - crl_trust_store = testing::CreateTrustStoreFromPemFile( + crl_trust_store = TrustStore::CreateInstanceFromPemFile( GetSpecificTestDataPath() + "certificates/cast_crl_test_root_ca.pem"); - cast_trust_store = testing::CreateTrustStoreFromPemFile( + cast_trust_store = TrustStore::CreateInstanceFromPemFile( GetSpecificTestDataPath() + "certificates/cast_test_root_ca.pem"); - EXPECT_FALSE(crl_trust_store->certs.empty()); - EXPECT_FALSE(cast_trust_store->certs.empty()); + EXPECT_FALSE(crl_trust_store.certs.empty()); + EXPECT_FALSE(cast_trust_store.certs.empty()); } std::vector<std::string> certificate_chain; @@ -421,9 +422,9 @@ bool RunTest(const DeviceCertTest& test_case) { ErrorOr<CastDeviceCertPolicy> result(CastDeviceCertPolicy::kUnrestricted); switch (test_case.expected_result()) { case ::cast::certificate::PATH_VERIFICATION_FAILED: - result = TestVerifyRevocation( - certificate_chain, crl_bundle, verification_time, false, - cast_trust_store.get(), crl_trust_store.get()); + result = + TestVerifyRevocation(certificate_chain, crl_bundle, verification_time, + false, &cast_trust_store, &cast_trust_store); EXPECT_EQ(result.error().code(), Error::Code::kCastV2CertNotSignedByTrustedCa); return result.error().code() == @@ -431,9 +432,9 @@ bool RunTest(const DeviceCertTest& test_case) { case ::cast::certificate::CRL_VERIFICATION_FAILED: // Fall-through intended. case ::cast::certificate::REVOCATION_CHECK_FAILED_WITHOUT_CRL: - result = TestVerifyRevocation( - certificate_chain, crl_bundle, verification_time, true, - cast_trust_store.get(), crl_trust_store.get()); + result = + TestVerifyRevocation(certificate_chain, crl_bundle, verification_time, + true, &cast_trust_store, &cast_trust_store); EXPECT_EQ(result.error().code(), Error::Code::kErrCrlInvalid); return result.error().code() == Error::Code::kErrCrlInvalid; case ::cast::certificate::CRL_EXPIRED_AFTER_INITIAL_VERIFICATION: @@ -441,15 +442,15 @@ bool RunTest(const DeviceCertTest& test_case) { // certificate is verified. return true; case ::cast::certificate::REVOCATION_CHECK_FAILED: - result = TestVerifyRevocation( - certificate_chain, crl_bundle, verification_time, true, - cast_trust_store.get(), crl_trust_store.get()); + result = + TestVerifyRevocation(certificate_chain, crl_bundle, verification_time, + true, &cast_trust_store, &cast_trust_store); EXPECT_EQ(result.error().code(), Error::Code::kErrCertsRevoked); return result.error().code() == Error::Code::kErrCertsRevoked; case ::cast::certificate::SUCCESS: - result = TestVerifyRevocation( - certificate_chain, crl_bundle, verification_time, false, - cast_trust_store.get(), crl_trust_store.get()); + result = + TestVerifyRevocation(certificate_chain, crl_bundle, verification_time, + false, &cast_trust_store, &cast_trust_store); EXPECT_EQ(result.error().code(), Error::Code::kCastV2SignedBlobsMismatch); return result.error().code() == Error::Code::kCastV2SignedBlobsMismatch; case ::cast::certificate::UNSPECIFIED: diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/BUILD.gn b/chromium/third_party/openscreen/src/cast/standalone_receiver/BUILD.gn index 46ab0cad3c7..454c933b2e7 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/BUILD.gn @@ -9,33 +9,26 @@ import("//build_overrides/build.gni") # standalone platform implementation; since this is itself a standalone # application. if (!build_with_chromium) { - source_set("standalone_receiver") { - sources = [ - "cast_agent.cc", - "cast_agent.h", - "cast_socket_message_port.cc", - "cast_socket_message_port.h", - "static_credentials.cc", - "static_credentials.h", - "streaming_playback_controller.cc", - "streaming_playback_controller.h", - ] + shared_sources = [ + "cast_agent.cc", + "cast_agent.h", + "streaming_playback_controller.cc", + "streaming_playback_controller.h", + ] - deps = [ - "../../platform", - "../../third_party/jsoncpp", - "../common:public", - "../common/channel/proto:channel_proto", - "../receiver:channel", - "../streaming:receiver", - ] + shared_deps = [ + "../common:public", + "../streaming:receiver", + ] + + have_external_libs = have_ffmpeg && have_libsdl2 - defines = [] - include_dirs = [] - lib_dirs = [] - libs = [] - if (have_ffmpeg && have_libsdl2) { - defines += [ "CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS" ] + if (have_external_libs) { + source_set("standalone_receiver_sdl") { + sources = shared_sources + deps = shared_deps + + defines = [ "CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS" ] sources += [ "avcodec_glue.h", "decoder.cc", @@ -49,23 +42,29 @@ if (!build_with_chromium) { "sdl_video_player.cc", "sdl_video_player.h", ] - include_dirs += ffmpeg_include_dirs + libsdl2_include_dirs - lib_dirs += ffmpeg_lib_dirs + libsdl2_lib_dirs - libs += ffmpeg_libs + libsdl2_libs - } else { - sources += [ - "dummy_player.cc", - "dummy_player.h", - ] + include_dirs = ffmpeg_include_dirs + libsdl2_include_dirs + lib_dirs = ffmpeg_lib_dirs + libsdl2_lib_dirs + libs = ffmpeg_libs + libsdl2_libs } } + source_set("standalone_receiver_dummy") { + sources = shared_sources + deps = shared_deps + + sources += [ + "dummy_player.cc", + "dummy_player.h", + ] + } + source_set("e2e_tests") { testonly = true + sources = [ "cast_agent_integration_tests.cc" ] deps = [ - ":standalone_receiver", + ":standalone_receiver_dummy", "../../third_party/boringssl", "../../third_party/googletest:gtest", "../receiver:channel", @@ -75,9 +74,12 @@ if (!build_with_chromium) { executable("cast_receiver") { sources = [ "main.cc" ] - deps = [ - ":standalone_receiver", - "../receiver:channel", - ] + deps = [ "../receiver:channel" ] + + if (have_external_libs) { + deps += [ ":standalone_receiver_sdl" ] + } else { + deps += [ ":standalone_receiver_dummy" ] + } } } diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.cc index a80827e4fca..791029a3435 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.cc @@ -11,14 +11,15 @@ #include <vector> #include "absl/strings/str_cat.h" +#include "cast/common/channel/cast_socket_message_port.h" #include "cast/common/channel/message_util.h" -#include "cast/standalone_receiver/cast_socket_message_port.h" #include "cast/streaming/constants.h" #include "cast/streaming/offer_messages.h" #include "platform/base/tls_credentials.h" #include "platform/base/tls_listen_options.h" #include "util/json/json_serialization.h" #include "util/osp_logging.h" +#include "util/trace_logging.h" namespace openscreen { namespace cast { @@ -31,12 +32,12 @@ const TlsListenOptions kDefaultListenOptions{kDefaultMaxBacklogSize}; CastAgent::CastAgent( TaskRunner* task_runner, - InterfaceInfo interface, + const InterfaceInfo& interface, DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider, TlsCredentials tls_credentials) : task_runner_(task_runner), credentials_provider_(credentials_provider), - tls_credentials_(tls_credentials) { + tls_credentials_(std::move(tls_credentials)) { const IPAddress address = interface.GetIpAddressV4() ? interface.GetIpAddressV4() : interface.GetIpAddressV6(); @@ -50,19 +51,21 @@ CastAgent::CastAgent( CastAgent::~CastAgent() = default; Error CastAgent::Start() { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver); OSP_CHECK(!current_session_); - auth_handler_ = MakeSerialDelete<DeviceAuthNamespaceHandler>( - task_runner_, credentials_provider_); - router_ = MakeSerialDelete<VirtualConnectionRouter>(task_runner_, - &connection_manager_); - router_->AddHandlerForLocalId(kPlatformReceiverId, auth_handler_.get()); - socket_factory_ = MakeSerialDelete<ReceiverSocketFactory>(task_runner_, this, - router_.get()); - task_runner_->PostTask([this] { wake_lock_ = ScopedWakeLock::Create(task_runner_); + auth_handler_ = MakeSerialDelete<DeviceAuthNamespaceHandler>( + task_runner_, credentials_provider_); + router_ = MakeSerialDelete<VirtualConnectionRouter>(task_runner_, + &connection_manager_); + message_port_ = + MakeSerialDelete<CastSocketMessagePort>(task_runner_, router_.get()); + router_->AddHandlerForLocalId(kPlatformReceiverId, auth_handler_.get()); + socket_factory_ = MakeSerialDelete<ReceiverSocketFactory>( + task_runner_, this, router_.get()); connection_factory_ = SerialDeletePtr<TlsConnectionFactory>( task_runner_, TlsConnectionFactory::CreateFactory(socket_factory_.get(), task_runner_) @@ -90,18 +93,19 @@ Error CastAgent::Stop() { void CastAgent::OnConnected(ReceiverSocketFactory* factory, const IPEndpoint& endpoint, std::unique_ptr<CastSocket> socket) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver); if (current_session_) { OSP_LOG_WARN << "Already connected, dropping peer at: " << endpoint; return; } OSP_LOG_INFO << "Received connection from peer at: " << endpoint; - message_port_.SetSocket(socket->GetWeakPtr()); + message_port_->SetSocket(socket->GetWeakPtr()); router_->TakeSocket(this, std::move(socket)); controller_ = std::make_unique<StreamingPlaybackController>(task_runner_, this); current_session_ = std::make_unique<ReceiverSession>( - controller_.get(), environment_.get(), &message_port_, + controller_.get(), environment_.get(), message_port_.get(), ReceiverSession::Preferences{}); } @@ -156,10 +160,10 @@ void CastAgent::OnPlaybackError(StreamingPlaybackController* controller, } void CastAgent::StopCurrentSession() { - controller_.reset(); current_session_.reset(); - router_->CloseSocket(message_port_.GetSocketId()); - message_port_.SetSocket(nullptr); + controller_.reset(); + router_->CloseSocket(message_port_->GetSocketId()); + message_port_->SetSocket(nullptr); } } // namespace cast diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.h index 4ed23b5b5bc..db8cf668dc3 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.h +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent.h @@ -10,13 +10,13 @@ #include <memory> #include <vector> +#include "cast/common/channel/cast_socket_message_port.h" #include "cast/common/channel/virtual_connection_manager.h" #include "cast/common/channel/virtual_connection_router.h" #include "cast/common/public/cast_socket.h" #include "cast/receiver/channel/device_auth_namespace_handler.h" +#include "cast/receiver/channel/static_credentials.h" #include "cast/receiver/public/receiver_socket_factory.h" -#include "cast/standalone_receiver/cast_socket_message_port.h" -#include "cast/standalone_receiver/static_credentials.h" #include "cast/standalone_receiver/streaming_playback_controller.h" #include "cast/streaming/environment.h" #include "cast/streaming/receiver_session.h" @@ -45,7 +45,7 @@ class CastAgent final : public ReceiverSocketFactory::Client, public: CastAgent( TaskRunner* task_runner, - InterfaceInfo interface, + const InterfaceInfo& interface, DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider, TlsCredentials tls_credentials); ~CastAgent(); @@ -87,7 +87,6 @@ class CastAgent final : public ReceiverSocketFactory::Client, TaskRunner* const task_runner_; IPEndpoint receive_endpoint_; DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider_; - CastSocketMessagePort message_port_; TlsCredentials tls_credentials_; // Member variables set as part of starting up. @@ -95,6 +94,7 @@ class CastAgent final : public ReceiverSocketFactory::Client, SerialDeletePtr<TlsConnectionFactory> connection_factory_; VirtualConnectionManager connection_manager_; SerialDeletePtr<VirtualConnectionRouter> router_; + SerialDeletePtr<CastSocketMessagePort> message_port_; SerialDeletePtr<ReceiverSocketFactory> socket_factory_; SerialDeletePtr<ScopedWakeLock> wake_lock_; diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent_integration_tests.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent_integration_tests.cc index 919711abeb4..94341f437d4 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent_integration_tests.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_agent_integration_tests.cc @@ -6,9 +6,9 @@ #include "cast/common/certificate/testing/test_helpers.h" #include "cast/common/channel/virtual_connection_manager.h" #include "cast/common/channel/virtual_connection_router.h" +#include "cast/receiver/channel/static_credentials.h" #include "cast/sender/public/sender_socket_factory.h" #include "cast/standalone_receiver/cast_agent.h" -#include "cast/standalone_receiver/static_credentials.h" #include "gtest/gtest.h" #include "platform/api/serial_delete_ptr.h" #include "platform/api/time.h" @@ -120,7 +120,7 @@ class CastAgentIntegrationTest : public ::testing::Test { SerialDeletePtr<TlsConnectionFactory> sender_tls_factory_; }; -TEST_F(CastAgentIntegrationTest, StartsListeningProperly) { +TEST_F(CastAgentIntegrationTest, CanConnect) { absl::optional<InterfaceInfo> loopback = GetLoopbackInterfaceForTesting(); ASSERT_TRUE(loopback.has_value()); diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.cc deleted file mode 100644 index 6f3c55c8ae8..00000000000 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.cc +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "cast/standalone_receiver/cast_socket_message_port.h" - -#include <utility> - -#include "cast/common/channel/proto/cast_channel.pb.h" - -namespace openscreen { -namespace cast { - -CastSocketMessagePort::CastSocketMessagePort() = default; -CastSocketMessagePort::~CastSocketMessagePort() = default; - -// NOTE: we assume here that this message port is already the client for -// the passed in socket, so leave the socket's client unchanged. However, -// since sockets should map one to one with receiver sessions, we reset our -// client. The consumer of this message port should call SetClient with the new -// message port client after setting the socket. -void CastSocketMessagePort::SetSocket(WeakPtr<CastSocket> socket) { - client_ = nullptr; - socket_ = socket; -} - -int CastSocketMessagePort::GetSocketId() { - return socket_ ? socket_->socket_id() : -1; -} - -void CastSocketMessagePort::SetClient(MessagePort::Client* client) { - client_ = client; -} - -void CastSocketMessagePort::PostMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) { - ::cast::channel::CastMessage cast_message; - cast_message.set_source_id(sender_id.data(), sender_id.size()); - cast_message.set_namespace_(message_namespace.data(), - message_namespace.size()); - cast_message.set_payload_utf8(message.data(), message.size()); - - if (!socket_) { - client_->OnError(Error::Code::kAlreadyClosed); - return; - } - Error error = socket_->Send(cast_message); - if (!error.ok()) { - client_->OnError(error); - } -} - -} // namespace cast -} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.h deleted file mode 100644 index 67d037e96b6..00000000000 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/cast_socket_message_port.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CAST_STANDALONE_RECEIVER_CAST_SOCKET_MESSAGE_PORT_H_ -#define CAST_STANDALONE_RECEIVER_CAST_SOCKET_MESSAGE_PORT_H_ - -#include <memory> -#include <string> -#include <vector> - -#include "cast/common/public/cast_socket.h" -#include "cast/streaming/receiver_session.h" -#include "util/weak_ptr.h" - -namespace openscreen { -namespace cast { - -class CastSocketMessagePort : public MessagePort { - public: - CastSocketMessagePort(); - ~CastSocketMessagePort() override; - - void SetSocket(WeakPtr<CastSocket> socket); - - // Returns current socket identifier, or -1 if not connected. - int GetSocketId(); - - // MessagePort overrides. - void SetClient(MessagePort::Client* client) override; - void PostMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) override; - - private: - MessagePort::Client* client_ = nullptr; - WeakPtr<CastSocket> socket_; -}; - -} // namespace cast -} // namespace openscreen - -#endif // CAST_STANDALONE_RECEIVER_CAST_SOCKET_MESSAGE_PORT_H_ diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/main.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/main.cc index 44a1a0a09bf..803b3414026 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/main.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/main.cc @@ -10,8 +10,8 @@ #include "absl/strings/str_cat.h" #include "cast/common/public/service_info.h" +#include "cast/receiver/channel/static_credentials.h" #include "cast/standalone_receiver/cast_agent.h" -#include "cast/standalone_receiver/static_credentials.h" #include "cast/streaming/ssrc.h" #include "discovery/common/config.h" #include "discovery/common/reporting_client.h" @@ -52,7 +52,10 @@ struct DiscoveryState { ErrorOr<std::unique_ptr<DiscoveryState>> StartDiscovery( TaskRunner* task_runner, - const InterfaceInfo& interface) { + const InterfaceInfo& interface, + const std::string& friendly_name, + const std::string& model_name) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver); discovery::Config config; discovery::Config::NetworkInfo::AddressFamilies supported_address_families = @@ -80,10 +83,8 @@ ErrorOr<std::unique_ptr<DiscoveryState>> StartDiscovery( interface.hardware_address.end(), [](int e) { return e > 0; })); info.unique_id = HexEncode(interface.hardware_address); - - // TODO(jophba): add command line arguments to set these fields. - info.model_name = "cast_standalone_receiver"; - info.friendly_name = "Cast Standalone Receiver"; + info.friendly_name = friendly_name; + info.model_name = model_name; state->publisher = std::make_unique<discovery::DnsSdServicePublisher<ServiceInfo>>( @@ -96,21 +97,18 @@ ErrorOr<std::unique_ptr<DiscoveryState>> StartDiscovery( return state; } -void StartCastAgent(TaskRunnerImpl* task_runner, - InterfaceInfo interface, - GeneratedCredentials* creds) { - CastAgent agent(task_runner, interface, creds->provider.get(), - creds->tls_credentials); - const auto error = agent.Start(); +std::unique_ptr<CastAgent> StartCastAgent(TaskRunnerImpl* task_runner, + const InterfaceInfo& interface, + GeneratedCredentials* creds) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver); + auto agent = std::make_unique<CastAgent>( + task_runner, interface, creds->provider.get(), creds->tls_credentials); + const auto error = agent->Start(); if (!error.ok()) { OSP_LOG_ERROR << "Error occurred while starting agent: " << error; - return; + agent.reset(); } - - // Run the event loop until an exit is requested (e.g., the video player GUI - // window is closed, a SIGINT or SIGTERM is received, or whatever other - // appropriate user indication that shutdown is requested). - task_runner->RunUntilSignaled(); + return agent; } void LogUsage(const char* argv0) { @@ -121,8 +119,18 @@ usage: )" << argv0 options: interface Specifies the network interface to bind to. The interface is - looked up from the system interface registry. This argument is - mandatory, as it must be known for publishing discovery. + looked up from the system interface registry. + Mandatory, as it must be known for publishing discovery. + + -p, --private-key=path-to-key: Path to OpenSSL-generated private key to be + used for TLS authentication. + + -s, --server-certificate=path-to-cert: Path to PEM file containing a + server certificate to be used for TLS authentication. + + -f, --friendly-name: Friendly name to be used for device discovery. + + -m, --model-name: Model name to be used for device discovery. -t, --tracing: Enable performance tracing logging. @@ -142,6 +150,15 @@ InterfaceInfo GetInterfaceInfoFromName(const char* name) { break; } } + + if (interface_info.name.empty()) { + auto error_or_info = GetLoopbackInterfaceForTesting(); + if (error_or_info.has_value()) { + if (error_or_info.value().name == name) { + interface_info = std::move(error_or_info.value()); + } + } + } OSP_CHECK(!interface_info.name.empty()) << "Invalid interface specified."; return interface_info; } @@ -152,29 +169,61 @@ int RunStandaloneReceiver(int argc, char* argv[]) { // being exposed, consider if it applies to the standalone receiver, // standalone sender, osp demo, and test_main argument options. const struct option kArgumentOptions[] = { + {"private-key", required_argument, nullptr, 'p'}, + {"server-certificate", required_argument, nullptr, 's'}, + {"friendly-name", required_argument, nullptr, 'f'}, + {"model-name", required_argument, nullptr, 'm'}, {"tracing", no_argument, nullptr, 't'}, {"verbose", no_argument, nullptr, 'v'}, {"help", no_argument, nullptr, 'h'}, + + // Discovery is enabled by default, however there are cases where it + // needs to be disabled, such as on Mac OS X. + {"disable-discovery", no_argument, nullptr, 'x'}, {nullptr, 0, nullptr, 0}}; bool is_verbose = false; + bool discovery_enabled = true; + std::string private_key_path; + std::string server_certificate_path; + std::string friendly_name = "Cast Standalone Receiver"; + std::string model_name = "cast_standalone_receiver"; std::unique_ptr<openscreen::TextTraceLoggingPlatform> trace_logger; int ch = -1; - while ((ch = getopt_long(argc, argv, "tvh", kArgumentOptions, nullptr)) != - -1) { + while ((ch = getopt_long(argc, argv, "p:s:f:m:tvhx", kArgumentOptions, + nullptr)) != -1) { switch (ch) { + case 'p': + private_key_path = optarg; + break; + case 's': + server_certificate_path = optarg; + break; + case 'f': + friendly_name = optarg; + break; + case 'm': + friendly_name = optarg; + break; case 't': trace_logger = std::make_unique<openscreen::TextTraceLoggingPlatform>(); break; case 'v': is_verbose = true; break; + case 'x': + discovery_enabled = false; + break; case 'h': LogUsage(argv[0]); return 1; } } - InterfaceInfo interface_info = GetInterfaceInfoFromName(argv[optind]); + if (private_key_path.empty() != server_certificate_path.empty()) { + OSP_LOG_ERROR << "If a private key or server certificate path is provided, " + "both are required."; + return 1; + } SetLogLevel(is_verbose ? openscreen::LogLevel::kVerbose : openscreen::LogLevel::kInfo); @@ -182,20 +231,50 @@ int RunStandaloneReceiver(int argc, char* argv[]) { PlatformClientPosix::Create(milliseconds(50), milliseconds(50), std::unique_ptr<TaskRunnerImpl>(task_runner)); - auto discovery_state = StartDiscovery(task_runner, interface_info); - OSP_CHECK(discovery_state.is_value()) << "Failed to start discovery."; + // Post tasks to kick-off the CastAgent and, if successful, start discovery to + // make this standalone receiver visible to senders on the network. + std::unique_ptr<DiscoveryState> discovery_state; + std::unique_ptr<CastAgent> cast_agent; + const char* interface_name = argv[optind]; + OSP_CHECK(interface_name && strlen(interface_name) > 0) + << "No interface name provided."; + + std::string device_id = + absl::StrCat("Standalone Receiver on ", interface_name); + ErrorOr<GeneratedCredentials> creds = Error::Code::kEVPInitializationError; + if (private_key_path.empty()) { + creds = GenerateCredentials(device_id); + } else { + creds = GenerateCredentials(device_id, private_key_path, + server_certificate_path); + } + OSP_CHECK(creds.is_value()) << creds.error(); + task_runner->PostTask( + [&, interface = GetInterfaceInfoFromName(interface_name)] { + cast_agent = StartCastAgent(task_runner, interface, &(creds.value())); + OSP_CHECK(cast_agent) << "Failed to start CastAgent."; + + if (discovery_enabled) { + auto result = + StartDiscovery(task_runner, interface, friendly_name, model_name); + OSP_CHECK(result.is_value()) << "Failed to start discovery."; + discovery_state = std::move(result.value()); + } + }); - auto creds = GenerateCredentials( - absl::StrCat("Standalone Receiver on ", argv[optind])); - OSP_CHECK(creds.is_value()); + // Run the event loop until an exit is requested (e.g., the video player GUI + // window is closed, a SIGINT or SIGTERM is received, or whatever other + // appropriate user indication that shutdown is requested). + task_runner->RunUntilSignaled(); - // Runs until the process is interrupted. Safe to pass |task_runner| as it - // will not be destroyed by ShutDown() until this exits. - StartCastAgent(task_runner, interface_info, &(creds.value())); + // Shutdown the Cast Agent and discovery-related entities. This may cause one + // or more tasks to be posted, and so the TaskRunner is spun to give them a + // chance to execute. + discovery_state.reset(); + cast_agent.reset(); + task_runner->PostTask([task_runner] { task_runner->RequestStopSoon(); }); + task_runner->RunUntilStopped(); - // The task runner must be deleted after all serial delete pointers, such - // as the one stored in the discovery state. - discovery_state.value().reset(); PlatformClientPosix::ShutDown(); return 0; } diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/private.der b/chromium/third_party/openscreen/src/cast/standalone_receiver/private.der Binary files differdeleted file mode 100644 index 48aa17b0151..00000000000 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/private.der +++ /dev/null diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/private_key_der.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/private_key_der.h deleted file mode 100644 index 0d9a328f01c..00000000000 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/private_key_der.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CAST_STANDALONE_RECEIVER_PRIVATE_KEY_DER_H_ -#define CAST_STANDALONE_RECEIVER_PRIVATE_KEY_DER_H_ - -#include <array> - -namespace openscreen { -namespace cast { - -// Important note about private keys and security: For example usage purposes, -// we have checked in a default private key here. However, in a production -// environment keys should never be checked into source control. This is an -// example self-signed private key for TLS. -// -// Generated using the following command: -// $ xxd -i <path/to/private_key.der> -std::array<uint8_t, 1192> kPrivateKeyDer = { - 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, - 0xb8, 0x07, 0xbb, 0x3f, 0xde, 0x47, 0xba, 0xec, 0x7a, 0xc2, 0x6e, 0xe5, - 0x44, 0x4a, 0xa8, 0x50, 0xd9, 0xef, 0xc1, 0x67, 0xc1, 0x5e, 0x14, 0xc0, - 0x1a, 0x1f, 0x8e, 0x83, 0xb9, 0xb2, 0x5d, 0x2d, 0x74, 0x12, 0x4b, 0x43, - 0x3c, 0xa6, 0xfb, 0xc4, 0x6c, 0xb0, 0xab, 0xda, 0xfb, 0xfd, 0x51, 0x18, - 0xc1, 0xc1, 0x22, 0x05, 0xf5, 0x2b, 0x10, 0xe4, 0x84, 0x1b, 0xa7, 0xdc, - 0xe6, 0xd0, 0xf5, 0x64, 0xa7, 0xc7, 0x6f, 0x1a, 0x85, 0xf8, 0xc4, 0xfb, - 0xbf, 0x17, 0x54, 0xaa, 0x11, 0x29, 0xfe, 0xda, 0x33, 0x9e, 0xfa, 0xb1, - 0x86, 0x9b, 0xf4, 0xcd, 0xf5, 0xe1, 0x3f, 0x7b, 0x3b, 0xad, 0xd9, 0xe2, - 0xc7, 0x6e, 0x4f, 0x1e, 0xa8, 0x13, 0x22, 0xa2, 0x7a, 0xcf, 0xe1, 0x8a, - 0x06, 0xf3, 0x28, 0x3a, 0xdc, 0xd3, 0x8c, 0x24, 0xa6, 0xe0, 0xd3, 0x5a, - 0x23, 0x21, 0x53, 0x02, 0x7d, 0x08, 0x30, 0xcb, 0xf1, 0x21, 0xca, 0x72, - 0x69, 0x49, 0x6e, 0x0f, 0xbc, 0x03, 0x7e, 0x0e, 0x60, 0x5d, 0x92, 0x08, - 0x3f, 0x04, 0x76, 0x62, 0x2d, 0x4b, 0xeb, 0x61, 0xaa, 0xe6, 0xcd, 0x2f, - 0x28, 0x24, 0xb0, 0xe8, 0xa5, 0xfe, 0x89, 0x90, 0xb8, 0xa4, 0x60, 0x6e, - 0x4c, 0x8c, 0x2f, 0xa3, 0xae, 0x72, 0xf2, 0x42, 0xb9, 0xc9, 0xa2, 0x6f, - 0x91, 0xbc, 0x75, 0x3b, 0x35, 0xb7, 0xe6, 0x24, 0xcb, 0x80, 0x8a, 0x34, - 0xfa, 0x9d, 0xf1, 0x7c, 0x88, 0x98, 0x09, 0x7b, 0x50, 0x56, 0xa5, 0x84, - 0x9c, 0x5f, 0x6c, 0x6e, 0x10, 0xfa, 0x95, 0xb6, 0xbf, 0xe4, 0xb0, 0x55, - 0x29, 0x3d, 0xe6, 0x3d, 0x14, 0xd7, 0x70, 0x17, 0xd8, 0xd3, 0xaa, 0xaf, - 0x4f, 0x15, 0x99, 0x63, 0xd0, 0x74, 0xfc, 0xb0, 0x6b, 0x66, 0x28, 0x02, - 0xd1, 0xbb, 0x01, 0x57, 0x02, 0xfe, 0x52, 0xe2, 0x0b, 0xbd, 0x8c, 0x0a, - 0x87, 0x8b, 0x60, 0xe9, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, - 0x01, 0x00, 0xaa, 0x19, 0x7b, 0x5a, 0x6d, 0x7a, 0x9f, 0xac, 0x35, 0x4b, - 0xc2, 0x74, 0xe7, 0xca, 0x9a, 0x09, 0x21, 0x68, 0x1a, 0xbc, 0x6c, 0x5f, - 0x29, 0x8e, 0xe6, 0x96, 0x84, 0x83, 0xfd, 0x00, 0x80, 0x5f, 0xa3, 0x09, - 0xc5, 0xc7, 0x40, 0x28, 0x98, 0x4d, 0xd6, 0xa8, 0xf6, 0x30, 0x52, 0xfa, - 0xb2, 0x1a, 0xcf, 0xfc, 0x54, 0x16, 0x6d, 0xa6, 0x80, 0xd6, 0xb7, 0xc5, - 0x58, 0x43, 0x36, 0x95, 0xae, 0x3c, 0x7b, 0x58, 0x3b, 0xb9, 0xa8, 0x5b, - 0x68, 0xb7, 0xc8, 0xc9, 0x27, 0xd8, 0x8a, 0x44, 0xe6, 0xeb, 0x89, 0x0b, - 0x49, 0x6d, 0x0d, 0x9e, 0xd9, 0x88, 0x05, 0xdd, 0x4d, 0x6f, 0xfa, 0x99, - 0x96, 0xeb, 0xa6, 0xaa, 0xaf, 0x37, 0x06, 0xe3, 0xa8, 0xff, 0xc5, 0xc4, - 0xa0, 0x13, 0x94, 0x98, 0xec, 0x76, 0x7b, 0xe6, 0x8d, 0x82, 0xd3, 0x3c, - 0xbc, 0x1e, 0x74, 0x9a, 0x38, 0xbf, 0xf4, 0x11, 0xbe, 0x07, 0x32, 0x2d, - 0x16, 0x2c, 0xf2, 0x5d, 0x24, 0x38, 0x70, 0xfb, 0x90, 0x8a, 0x38, 0xd6, - 0x17, 0xe1, 0x66, 0x92, 0x38, 0x06, 0x97, 0xb3, 0x07, 0xfd, 0x77, 0xe2, - 0xe7, 0x49, 0xae, 0x5a, 0xbc, 0xe5, 0xa8, 0xca, 0xe1, 0x0f, 0xb6, 0x4c, - 0x05, 0x73, 0x3f, 0x11, 0xd0, 0xf9, 0x1e, 0xba, 0x53, 0x48, 0xf5, 0xaf, - 0x28, 0x5b, 0xea, 0x12, 0x63, 0xbc, 0x84, 0xa7, 0x5f, 0x2e, 0x1d, 0x3e, - 0x02, 0x54, 0x58, 0xed, 0x2b, 0x42, 0xf9, 0xc6, 0x0c, 0xd4, 0x24, 0x77, - 0x1a, 0x2c, 0xbf, 0x75, 0x92, 0xf7, 0xcb, 0xd4, 0x58, 0x2f, 0x88, 0x2d, - 0xe8, 0x16, 0xca, 0xe5, 0x25, 0xe8, 0x5b, 0xbd, 0x53, 0x26, 0x23, 0xe0, - 0xa9, 0x35, 0x4d, 0xdb, 0x51, 0x85, 0x63, 0x20, 0xad, 0x61, 0xd2, 0x6d, - 0xbf, 0x01, 0x7d, 0x04, 0x44, 0x02, 0x96, 0x92, 0x36, 0x19, 0xed, 0xd1, - 0xd8, 0x16, 0x86, 0x06, 0xd4, 0x81, 0x02, 0x81, 0x81, 0x00, 0xe1, 0xa6, - 0xca, 0xb3, 0xef, 0xfe, 0x9f, 0xd6, 0xac, 0x58, 0x5c, 0x17, 0x88, 0xaf, - 0x4d, 0x85, 0x29, 0x50, 0x1f, 0x66, 0x90, 0x9b, 0x81, 0xb6, 0x82, 0x0d, - 0xc3, 0x5a, 0xa8, 0x8a, 0x2b, 0x7f, 0x58, 0x9b, 0x07, 0xe6, 0x64, 0xf7, - 0x1c, 0x77, 0x9d, 0x53, 0x97, 0xa0, 0x33, 0x14, 0x6e, 0x77, 0x1e, 0xe3, - 0x00, 0x0f, 0xb2, 0xb1, 0x69, 0x25, 0x3d, 0x63, 0x3c, 0xe1, 0xbb, 0x41, - 0x74, 0x97, 0x2d, 0x5e, 0x14, 0x79, 0x93, 0x38, 0x15, 0xbe, 0x52, 0x74, - 0x64, 0xc0, 0xfd, 0x22, 0x8e, 0xd7, 0xc9, 0xfb, 0x66, 0x55, 0xce, 0x5b, - 0x6a, 0x6f, 0x00, 0xed, 0x03, 0x7e, 0x4b, 0x9c, 0x4b, 0x8b, 0x3a, 0x50, - 0x65, 0x0d, 0x70, 0x9b, 0xdb, 0xf7, 0x1f, 0xd7, 0x66, 0x7a, 0xd1, 0x1e, - 0xa0, 0x8f, 0xe6, 0x03, 0x12, 0x18, 0x52, 0x25, 0x41, 0xa7, 0xb9, 0x8e, - 0x75, 0x63, 0x11, 0xd2, 0x63, 0xd7, 0x02, 0x81, 0x81, 0x00, 0xd0, 0xc7, - 0xe9, 0x97, 0x38, 0x33, 0x95, 0xbd, 0x18, 0xa5, 0x0a, 0x68, 0xab, 0xba, - 0x5e, 0x3e, 0x1f, 0x16, 0x86, 0xc0, 0x50, 0x09, 0xab, 0x52, 0xb7, 0x62, - 0x4e, 0x34, 0xb1, 0xc1, 0xd3, 0xb5, 0xf4, 0xe0, 0x04, 0x30, 0xa6, 0xdd, - 0x4a, 0xba, 0x7c, 0x59, 0xed, 0xd7, 0x76, 0xd3, 0x02, 0xe7, 0x05, 0x18, - 0x00, 0xdb, 0x65, 0xf2, 0x82, 0xe4, 0xfa, 0xbf, 0x9d, 0xad, 0x1a, 0x56, - 0x7b, 0x5e, 0xef, 0xff, 0x9b, 0xe5, 0x2f, 0x7c, 0xdd, 0x50, 0x53, 0x2b, - 0x6b, 0xc0, 0xac, 0x7b, 0x21, 0x8d, 0xc3, 0x39, 0xfe, 0xd0, 0x1a, 0xed, - 0xd1, 0xb6, 0x56, 0xda, 0x9e, 0x87, 0x9a, 0x6a, 0x69, 0x81, 0x29, 0x81, - 0x75, 0x69, 0xa6, 0x25, 0xc2, 0xf7, 0x5a, 0x94, 0x97, 0x6a, 0x7a, 0xf9, - 0x6c, 0xbe, 0x43, 0x76, 0x34, 0xba, 0x0c, 0x50, 0x6d, 0x22, 0xe8, 0xa6, - 0x9c, 0x80, 0x62, 0x87, 0xc9, 0x3f, 0x02, 0x81, 0x80, 0x78, 0xaf, 0x47, - 0x1c, 0x63, 0x90, 0x30, 0x16, 0x95, 0x88, 0x90, 0x80, 0x79, 0xb7, 0x20, - 0x63, 0xc6, 0xcb, 0xb6, 0x6f, 0x99, 0x89, 0xc2, 0x1f, 0x45, 0x81, 0x6c, - 0xe9, 0x10, 0xd9, 0x0d, 0x18, 0x87, 0xe0, 0x2a, 0xa2, 0x7b, 0x7f, 0x7a, - 0x77, 0x32, 0xea, 0xa1, 0x5e, 0xa9, 0xd3, 0x14, 0x9d, 0x9b, 0x24, 0x57, - 0x45, 0x0e, 0x12, 0x3a, 0xa5, 0x13, 0x26, 0xff, 0x49, 0xcf, 0x67, 0xdb, - 0x9e, 0x7b, 0x42, 0x24, 0xfb, 0x3c, 0xd4, 0xb3, 0x34, 0x5e, 0x4f, 0x28, - 0x0f, 0xdb, 0x92, 0xdf, 0x08, 0xe4, 0x5b, 0x13, 0xc9, 0x72, 0x9b, 0x8b, - 0xda, 0x20, 0x89, 0xa2, 0xe3, 0xaa, 0x36, 0xc6, 0x64, 0x89, 0x64, 0xb4, - 0x17, 0x33, 0x11, 0xf8, 0xdc, 0x3b, 0xe8, 0x6d, 0x43, 0xe4, 0x92, 0x57, - 0xd7, 0x7e, 0x72, 0x47, 0xfc, 0x3f, 0xfa, 0xf3, 0x19, 0x6c, 0x71, 0x97, - 0xb0, 0xcb, 0xb8, 0x55, 0x73, 0x02, 0x81, 0x81, 0x00, 0x98, 0xf1, 0xfa, - 0x73, 0x67, 0x1e, 0x93, 0x11, 0x45, 0xde, 0x91, 0xb3, 0x80, 0x2a, 0x35, - 0x23, 0xf9, 0x0e, 0x3d, 0x84, 0xe0, 0x9d, 0x54, 0xbe, 0x71, 0xcd, 0x38, - 0x51, 0x6d, 0xee, 0xfa, 0x33, 0x0f, 0xc2, 0x94, 0x0f, 0x38, 0x0e, 0x60, - 0xd2, 0x20, 0x8a, 0x98, 0xac, 0x01, 0x46, 0x2f, 0x98, 0x21, 0xa9, 0x25, - 0xe7, 0x93, 0xd5, 0x86, 0x82, 0x4c, 0x16, 0xd7, 0x61, 0x9a, 0x2b, 0xc4, - 0x91, 0x15, 0xec, 0x00, 0xbe, 0x72, 0x7d, 0x5c, 0x7b, 0x9d, 0x91, 0xef, - 0x8b, 0xe4, 0x4f, 0x07, 0x93, 0x9c, 0x72, 0xfd, 0xf2, 0x61, 0xe7, 0xda, - 0x7b, 0x63, 0x41, 0x20, 0x65, 0x62, 0x7f, 0x95, 0xee, 0xa3, 0x03, 0x4d, - 0x8a, 0x29, 0xc6, 0xfb, 0xfe, 0xcc, 0x82, 0x92, 0x31, 0xd5, 0x08, 0xa7, - 0xda, 0xf1, 0xfc, 0xc4, 0x3f, 0x8f, 0x09, 0xd4, 0x09, 0x80, 0xb9, 0x9d, - 0x68, 0x87, 0xc5, 0xc5, 0x6d, 0x02, 0x81, 0x80, 0x1f, 0xd9, 0x20, 0xde, - 0xba, 0xcd, 0x63, 0x34, 0x4f, 0x9f, 0xbb, 0x05, 0x0a, 0x8d, 0x20, 0xe1, - 0x66, 0x41, 0x2f, 0xae, 0xc7, 0xfa, 0x5d, 0xfd, 0xb7, 0x2a, 0x0f, 0xa6, - 0x6d, 0xf3, 0xad, 0x65, 0x54, 0x75, 0x2c, 0x26, 0x1e, 0xac, 0x1f, 0x24, - 0x4c, 0x83, 0xe3, 0x28, 0x08, 0x60, 0x74, 0xfe, 0xa9, 0x53, 0x36, 0x1e, - 0xb3, 0x39, 0x9d, 0xe7, 0x49, 0x03, 0x66, 0x61, 0xe8, 0xd4, 0xf4, 0xd8, - 0x65, 0x57, 0x01, 0xed, 0xaa, 0x7b, 0x6b, 0x04, 0xa2, 0x5f, 0xe1, 0x67, - 0xe6, 0x06, 0x7c, 0x84, 0x2a, 0x7d, 0x53, 0x03, 0x1c, 0x9c, 0x82, 0x08, - 0x37, 0x07, 0xaf, 0x77, 0xe0, 0x99, 0x69, 0xce, 0x01, 0x5a, 0x85, 0x4b, - 0x27, 0xb9, 0xb2, 0x20, 0x8c, 0xa5, 0xb9, 0x42, 0x2f, 0xad, 0x56, 0xdd, - 0xb9, 0x0d, 0x23, 0x05, 0x53, 0x5a, 0x26, 0x3a, 0xe2, 0x17, 0x58, 0x79, - 0x96, 0x8c, 0x5a, 0x05}; - -} // namespace cast -} // namespace openscreen - -#endif // CAST_STANDALONE_RECEIVER_PRIVATE_KEY_DER_H_ diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.cc index 3392b706615..c460a7edb58 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.cc @@ -55,12 +55,12 @@ void InterleaveAudioSamples(const uint8_t* const planes[], SDLAudioPlayer::SDLAudioPlayer(ClockNowFunctionPtr now_function, TaskRunner* task_runner, Receiver* receiver, - const std::string& codec_name, + AudioCodec codec, std::function<void()> error_callback) : SDLPlayerBase(now_function, task_runner, receiver, - codec_name, + CodecToString(codec), std::move(error_callback), kAudioMediaType) {} diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.h index 8788d1f5c05..b21ce2cc5bf 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.h +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_audio_player.h @@ -22,7 +22,7 @@ class SDLAudioPlayer final : public SDLPlayerBase { SDLAudioPlayer(ClockNowFunctionPtr now_function, TaskRunner* task_runner, Receiver* receiver, - const std::string& codec_name, + AudioCodec codec, std::function<void()> error_callback); ~SDLAudioPlayer() final; diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.cc index ab2f1327cf1..fdc3c06545e 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.cc @@ -10,6 +10,7 @@ #include "absl/types/span.h" #include "cast/standalone_receiver/avcodec_glue.h" +#include "cast/streaming/constants.h" #include "cast/streaming/encoded_frame.h" #include "util/big_endian.h" #include "util/chrono_helpers.h" diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.h index 7338edab1b0..4e268e8ade3 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.h +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_player_base.h @@ -13,6 +13,7 @@ #include "cast/standalone_receiver/decoder.h" #include "cast/standalone_receiver/sdl_glue.h" +#include "cast/streaming/message_fields.h" #include "cast/streaming/receiver.h" #include "platform/api/task_runner.h" #include "platform/api/time.h" diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.cc index dc540e50a9f..999545de5a4 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.cc @@ -5,6 +5,7 @@ #include "cast/standalone_receiver/sdl_video_player.h" #include <sstream> +#include <utility> #include "cast/standalone_receiver/avcodec_glue.h" #include "util/osp_logging.h" @@ -20,13 +21,13 @@ constexpr char kVideoMediaType[] = "video"; SDLVideoPlayer::SDLVideoPlayer(ClockNowFunctionPtr now_function, TaskRunner* task_runner, Receiver* receiver, - const std::string& codec_name, + VideoCodec codec, SDL_Renderer* renderer, std::function<void()> error_callback) : SDLPlayerBase(now_function, task_runner, receiver, - codec_name, + CodecToString(codec), std::move(error_callback), kVideoMediaType), renderer_(renderer) { diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.h b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.h index 24b3496ccc0..609c860c3b4 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.h +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/sdl_video_player.h @@ -8,6 +8,7 @@ #include <string> #include "cast/standalone_receiver/sdl_player_base.h" +#include "cast/streaming/constants.h" namespace openscreen { namespace cast { @@ -21,7 +22,7 @@ class SDLVideoPlayer final : public SDLPlayerBase { SDLVideoPlayer(ClockNowFunctionPtr now_function, TaskRunner* task_runner, Receiver* receiver, - const std::string& codec_name, + VideoCodec codec_name, SDL_Renderer* renderer, std::function<void()> error_callback); diff --git a/chromium/third_party/openscreen/src/cast/standalone_receiver/streaming_playback_controller.cc b/chromium/third_party/openscreen/src/cast/standalone_receiver/streaming_playback_controller.cc index e45a281700c..3438c2ed027 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_receiver/streaming_playback_controller.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_receiver/streaming_playback_controller.cc @@ -53,8 +53,6 @@ StreamingPlaybackController::StreamingPlaybackController( } #endif // defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS) -// TODO(jophba): add async tracing to streaming implementation for exposing -// how long the OFFER/ANSWER and receiver startup takes. void StreamingPlaybackController::OnNegotiated( const ReceiverSession* session, ReceiverSession::ConfiguredReceivers receivers) { @@ -62,26 +60,25 @@ void StreamingPlaybackController::OnNegotiated( #if defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS) if (receivers.audio) { audio_player_ = std::make_unique<SDLAudioPlayer>( - &Clock::now, task_runner_, receivers.audio->receiver, - receivers.audio->selected_stream.stream.codec_name, [this] { + &Clock::now, task_runner_, receivers.audio_receiver, + receivers.audio_config.codec, [this] { client_->OnPlaybackError(this, audio_player_->error_status()); }); } if (receivers.video) { video_player_ = std::make_unique<SDLVideoPlayer>( - &Clock::now, task_runner_, receivers.video->receiver, - receivers.video->selected_stream.stream.codec_name, renderer_.get(), - [this] { + &Clock::now, task_runner_, receivers.video_receiver, + receivers.video_config.codec, renderer_.get(), [this] { client_->OnPlaybackError(this, video_player_->error_status()); }); } #else - if (receivers.audio) { - audio_player_ = std::make_unique<DummyPlayer>(receivers.audio->receiver); + if (receivers.audio_receiver) { + audio_player_ = std::make_unique<DummyPlayer>(receivers.audio_receiver); } - if (receivers.video) { - video_player_ = std::make_unique<DummyPlayer>(receivers.video->receiver); + if (receivers.video_receiver) { + video_player_ = std::make_unique<DummyPlayer>(receivers.video_receiver); } #endif // defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS) } diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/BUILD.gn b/chromium/third_party/openscreen/src/cast/standalone_sender/BUILD.gn index 85e0f1b047b..c2bd427bc4a 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_sender/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/BUILD.gn @@ -9,25 +9,40 @@ import("//build_overrides/build.gni") # standalone platform implementation; since this is itself a standalone # application. if (!build_with_chromium) { - executable("cast_sender") { - sources = [ - "main.cc", - ] + declare_args() { + have_libs = have_ffmpeg && have_libopus && have_libvpx + } + + config("standalone_external_libs") { + defines = [] + if (have_libs) { + defines += [ "CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS" ] + } + } + + source_set("standalone_sender") { deps = [ "../../platform", + "../../third_party/jsoncpp", "../../util", + "../common:public", + "../common/channel/proto:channel_proto", + "../sender:channel", "../streaming:sender", ] - defines = [] + sources = [] include_dirs = [] lib_dirs = [] libs = [] if (have_ffmpeg && have_libopus && have_libvpx) { - defines += [ "CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS" ] sources += [ "ffmpeg_glue.cc", "ffmpeg_glue.h", + "looping_file_cast_agent.cc", + "looping_file_cast_agent.h", + "looping_file_sender.cc", + "looping_file_sender.h", "simulated_capturer.cc", "simulated_capturer.h", "streaming_opus_encoder.cc", @@ -40,5 +55,21 @@ if (!build_with_chromium) { lib_dirs += ffmpeg_lib_dirs + libopus_lib_dirs + libvpx_lib_dirs libs += ffmpeg_libs + libopus_libs + libvpx_libs } + + public_configs = [ + "../../build:openscreen_include_dirs", + ":standalone_external_libs", + ] + } + + executable("cast_sender") { + sources = [ "main.cc" ] + + deps = [ + ":standalone_sender", + "../../third_party/jsoncpp", + "../common/channel/proto:channel_proto", + "../streaming:common", + ] } } diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/constants.h b/chromium/third_party/openscreen/src/cast/standalone_sender/constants.h new file mode 100644 index 00000000000..b7534ee53ac --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/constants.h @@ -0,0 +1,33 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STANDALONE_SENDER_CONSTANTS_H_ +#define CAST_STANDALONE_SENDER_CONSTANTS_H_ + +#include "util/chrono_helpers.h" + +namespace openscreen { +namespace cast { + +// How often should the congestion control logic re-evaluate the target encode +// bitrates? +constexpr milliseconds kCongestionCheckInterval{500}; + +// Above what available bandwidth should the high-quality audio bitrate be used? +constexpr int kHighBandwidthThreshold = 5 << 20; // 5 Mbps. + +// How often should the file position (media timestamp) be updated on the +// console? +constexpr milliseconds kConsoleUpdateInterval{100}; + +// What is the default maximum bitrate setting? +constexpr int kDefaultMaxBitrate = 5 << 20; // 5 Mbps. + +// What is the minimum amount of bandwidth required? +constexpr int kMinRequiredBitrate = 384 << 10; // 384 kbps. + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STANDALONE_SENDER_CONSTANTS_H_ diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.cc b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.cc new file mode 100644 index 00000000000..a7e99fdb60c --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.cc @@ -0,0 +1,152 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/standalone_sender/looping_file_cast_agent.h" + +#include <string> +#include <utility> +#include <vector> + +#include "cast/standalone_sender/looping_file_sender.h" +#include "cast/streaming/capture_recommendations.h" +#include "cast/streaming/constants.h" +#include "cast/streaming/offer_messages.h" +#include "util/trace_logging.h" + +namespace openscreen { +namespace cast { +namespace { + +using DeviceMediaPolicy = SenderSocketFactory::DeviceMediaPolicy; + +} // namespace + +LoopingFileCastAgent::LoopingFileCastAgent(TaskRunner* task_runner) + : task_runner_(task_runner) { + router_ = MakeSerialDelete<VirtualConnectionRouter>(task_runner_, + &connection_manager_); + message_port_ = + MakeSerialDelete<CastSocketMessagePort>(task_runner_, router_.get()); + socket_factory_ = + MakeSerialDelete<SenderSocketFactory>(task_runner_, this, task_runner_); + connection_factory_ = SerialDeletePtr<TlsConnectionFactory>( + task_runner_, + TlsConnectionFactory::CreateFactory(socket_factory_.get(), task_runner_) + .release()); + socket_factory_->set_factory(connection_factory_.get()); +} + +LoopingFileCastAgent::~LoopingFileCastAgent() = default; + +void LoopingFileCastAgent::Connect(ConnectionSettings settings) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + connection_settings_ = std::move(settings); + const auto policy = connection_settings_->should_include_video + ? DeviceMediaPolicy::kIncludesVideo + : DeviceMediaPolicy::kAudioOnly; + + task_runner_->PostTask([this, policy] { + wake_lock_ = ScopedWakeLock::Create(task_runner_); + socket_factory_->Connect(connection_settings_->receiver_endpoint, policy, + router_.get()); + }); +} + +void LoopingFileCastAgent::Stop() { + task_runner_->PostTask([this] { + StopCurrentSession(); + + connection_factory_.reset(); + connection_settings_.reset(); + socket_factory_.reset(); + wake_lock_.reset(); + }); +} + +void LoopingFileCastAgent::OnConnected(SenderSocketFactory* factory, + const IPEndpoint& endpoint, + std::unique_ptr<CastSocket> socket) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + if (current_session_) { + OSP_LOG_WARN << "Already connected, dropping peer at: " << endpoint; + return; + } + + OSP_LOG_INFO << "Received connection from peer at: " << endpoint; + message_port_->SetSocket(socket->GetWeakPtr()); + router_->TakeSocket(this, std::move(socket)); + CreateAndStartSession(); +} + +void LoopingFileCastAgent::OnError(SenderSocketFactory* factory, + const IPEndpoint& endpoint, + Error error) { + OSP_LOG_ERROR << "Cast agent received socket factory error: " << error; + StopCurrentSession(); +} + +void LoopingFileCastAgent::OnClose(CastSocket* cast_socket) { + OSP_VLOG << "Cast agent socket closed."; + StopCurrentSession(); +} + +void LoopingFileCastAgent::OnError(CastSocket* socket, Error error) { + OSP_LOG_ERROR << "Cast agent received socket error: " << error; + StopCurrentSession(); +} + +void LoopingFileCastAgent::OnNegotiated( + const SenderSession* session, + SenderSession::ConfiguredSenders senders, + capture_recommendations::Recommendations capture_recommendations) { + if (senders.audio_sender == nullptr || senders.video_sender == nullptr) { + OSP_LOG_ERROR << "Missing either audio or video, so exiting..."; + return; + } + + OSP_VLOG << "Successfully negotiated with sender."; + + file_sender_ = std::make_unique<LoopingFileSender>( + task_runner_, connection_settings_->path_to_file.c_str(), + connection_settings_->receiver_endpoint, senders, + connection_settings_->max_bitrate); +} + +// Currently, we just kill the session if an error is encountered. +void LoopingFileCastAgent::OnError(const SenderSession* session, Error error) { + OSP_LOG_ERROR << "Cast agent received sender session error: " << error; + StopCurrentSession(); +} + +void LoopingFileCastAgent::CreateAndStartSession() { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + environment_ = + std::make_unique<Environment>(&Clock::now, task_runner_, IPEndpoint{}); + current_session_ = std::make_unique<SenderSession>( + connection_settings_->receiver_endpoint.address, this, environment_.get(), + message_port_.get()); + + AudioCaptureConfig audio_config; + VideoCaptureConfig video_config; + // Use default display resolution of 1080P. + video_config.resolutions.emplace_back(DisplayResolution{}); + + OSP_VLOG << "Starting session negotiation."; + const Error negotiation_error = + current_session_->Negotiate({audio_config}, {video_config}); + if (!negotiation_error.ok()) { + OSP_LOG_ERROR << "Failed to negotiate a session: " << negotiation_error; + } +} + +void LoopingFileCastAgent::StopCurrentSession() { + current_session_.reset(); + environment_.reset(); + file_sender_.reset(); + router_->CloseSocket(message_port_->GetSocketId()); + message_port_->SetSocket(nullptr); +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.h b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.h new file mode 100644 index 00000000000..abe91c96da1 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_cast_agent.h @@ -0,0 +1,119 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STANDALONE_SENDER_LOOPING_FILE_CAST_AGENT_H_ +#define CAST_STANDALONE_SENDER_LOOPING_FILE_CAST_AGENT_H_ + +#include <openssl/x509.h> + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "cast/common/channel/cast_socket_message_port.h" +#include "cast/common/channel/virtual_connection_manager.h" +#include "cast/common/channel/virtual_connection_router.h" +#include "cast/common/public/cast_socket.h" +#include "cast/sender/public/sender_socket_factory.h" +#include "cast/standalone_sender/looping_file_sender.h" +#include "cast/streaming/environment.h" +#include "cast/streaming/sender_session.h" +#include "platform/api/scoped_wake_lock.h" +#include "platform/api/serial_delete_ptr.h" +#include "platform/base/error.h" +#include "platform/base/interface_info.h" +#include "platform/impl/task_runner.h" + +namespace openscreen { +namespace cast { + +// This class manages sender connections, starting with listening over TLS for +// connection attempts, constructing SenderSessions when OFFER messages are +// received, and linking Senders to the output decoder and SDL visualizer. +class LoopingFileCastAgent final + : public SenderSocketFactory::Client, + public VirtualConnectionRouter::SocketErrorHandler, + public SenderSession::Client { + public: + explicit LoopingFileCastAgent(TaskRunner* task_runner); + ~LoopingFileCastAgent(); + + struct ConnectionSettings { + // The endpoint of the receiver we wish to connect to. Eventually this + // will come from discovery, instead of an endpoint here. + IPEndpoint receiver_endpoint; + + // The path to the file that we want to play. + std::string path_to_file; + + // The maximum bitrate. Default value means a reasonable default will be + // selected. + int max_bitrate = 0; + + // Whether the stream should include video, or just be audio only. + bool should_include_video = true; + + // Whether we should use the hacky RTP stream IDs for legacy android + // receivers, or if we should use the proper values. + bool use_android_rtp_hack = true; + }; + + void Connect(ConnectionSettings settings); + void Stop(); + + // SenderSocketFactory::Client overrides. + void OnConnected(SenderSocketFactory* factory, + const IPEndpoint& endpoint, + std::unique_ptr<CastSocket> socket) override; + void OnError(SenderSocketFactory* factory, + const IPEndpoint& endpoint, + Error error) override; + + // VirtualConnectionRouter::SocketErrorHandler overrides. + void OnClose(CastSocket* cast_socket) override; + void OnError(CastSocket* socket, Error error) override; + + // SenderSession::Client overrides. + void OnNegotiated(const SenderSession* session, + SenderSession::ConfiguredSenders senders, + capture_recommendations::Recommendations + capture_recommendations) override; + void OnError(const SenderSession* session, Error error) override; + + private: + // Once we have a connection to the receiver we need to create and start + // a sender session. This method results in the OFFER/ANSWER exchange + // being completed and a session should be started. + void CreateAndStartSession(); + + // Helper for stopping the current session. This is useful for when we don't + // want to completely stop (e.g. an issue with a specific Sender) but need + // to terminate the current connection. + void StopCurrentSession(); + + // Member variables set as part of construction. + VirtualConnectionManager connection_manager_; + TaskRunner* const task_runner_; + SerialDeletePtr<VirtualConnectionRouter> router_; + SerialDeletePtr<CastSocketMessagePort> message_port_; + SerialDeletePtr<SenderSocketFactory> socket_factory_; + SerialDeletePtr<TlsConnectionFactory> connection_factory_; + + // Member variables set as part of starting up. + std::unique_ptr<Environment> environment_; + absl::optional<ConnectionSettings> connection_settings_; + SerialDeletePtr<ScopedWakeLock> wake_lock_; + + // Member variables set as part of a sender connection. + // NOTE: currently we only support a single sender connection and a + // single streaming session. + std::unique_ptr<SenderSession> current_session_; + std::unique_ptr<LoopingFileSender> file_sender_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STANDALONE_SENDER_LOOPING_FILE_CAST_AGENT_H_ diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.cc b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.cc new file mode 100644 index 00000000000..e4b795b8eb4 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.cc @@ -0,0 +1,189 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/standalone_sender/looping_file_sender.h" + +#include "util/trace_logging.h" + +namespace openscreen { +namespace cast { + +LoopingFileSender::LoopingFileSender(TaskRunner* task_runner, + const char* path, + const IPEndpoint& remote_endpoint, + SenderSession::ConfiguredSenders senders, + int max_bitrate) + : env_(&Clock::now, task_runner), + path_(path), + packet_router_(&env_), + max_bitrate_(max_bitrate), + audio_encoder_(senders.audio_sender->config().channels, + StreamingOpusEncoder::kDefaultCastAudioFramesPerSecond, + senders.audio_sender), + video_encoder_(StreamingVp8Encoder::Parameters{}, + env_.task_runner(), + senders.video_sender), + next_task_(env_.now_function(), env_.task_runner()), + console_update_task_(env_.now_function(), env_.task_runner()) { + env_.set_remote_endpoint(remote_endpoint); + // Opus and Vp8 are the default values for the config, and if these are set + // to a different value that means we offered a codec that we do not + // support, which is a developer error. + OSP_CHECK(senders.audio_config.codec == AudioCodec::kOpus); + OSP_CHECK(senders.video_config.codec == VideoCodec::kVp8); + OSP_LOG_INFO << "Streaming to " << remote_endpoint << "..."; + OSP_LOG_INFO << "Max allowed media bitrate (audio + video) will be " + << max_bitrate_; + bandwidth_being_utilized_ = max_bitrate_ / 2; + UpdateEncoderBitrates(); + + next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately); +} + +LoopingFileSender::~LoopingFileSender() = default; + +void LoopingFileSender::UpdateEncoderBitrates() { + if (bandwidth_being_utilized_ >= kHighBandwidthThreshold) { + audio_encoder_.UseHighQuality(); + } else { + audio_encoder_.UseStandardQuality(); + } + video_encoder_.SetTargetBitrate(bandwidth_being_utilized_ - + audio_encoder_.GetBitrate()); +} + +void LoopingFileSender::ControlForNetworkCongestion() { + bandwidth_estimate_ = packet_router_.ComputeNetworkBandwidth(); + if (bandwidth_estimate_ > 0) { + // Don't ever try to use *all* of the network bandwidth! However, don't go + // below the absolute minimum requirement either. + constexpr double kGoodNetworkCitizenFactor = 0.8; + const int usable_bandwidth = std::max<int>( + kGoodNetworkCitizenFactor * bandwidth_estimate_, kMinRequiredBitrate); + + // See "congestion control" discussion in the class header comments for + // BandwidthEstimator. + if (usable_bandwidth > bandwidth_being_utilized_) { + constexpr double kConservativeIncrease = 1.1; + bandwidth_being_utilized_ = std::min<int>( + bandwidth_being_utilized_ * kConservativeIncrease, usable_bandwidth); + } else { + bandwidth_being_utilized_ = usable_bandwidth; + } + + // Repsect the user's maximum bitrate setting. + bandwidth_being_utilized_ = + std::min(bandwidth_being_utilized_, max_bitrate_); + + UpdateEncoderBitrates(); + } else { + // There is no current bandwidth estimate. So, nothing should be adjusted. + } + + next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); }, + kCongestionCheckInterval); +} + +void LoopingFileSender::SendFileAgain() { + OSP_LOG_INFO << "Sending " << path_ << " (starts in one second)..."; + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + + OSP_DCHECK_EQ(num_capturers_running_, 0); + num_capturers_running_ = 2; + capture_start_time_ = latest_frame_time_ = env_.now() + seconds(1); + audio_capturer_.emplace(&env_, path_, audio_encoder_.num_channels(), + audio_encoder_.sample_rate(), capture_start_time_, + this); + video_capturer_.emplace(&env_, path_, capture_start_time_, this); + + next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); }, + kCongestionCheckInterval); + console_update_task_.Schedule([this] { UpdateStatusOnConsole(); }, + capture_start_time_); +} + +void LoopingFileSender::OnAudioData(const float* interleaved_samples, + int num_samples, + Clock::time_point capture_time) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + latest_frame_time_ = std::max(capture_time, latest_frame_time_); + audio_encoder_.EncodeAndSend(interleaved_samples, num_samples, capture_time); +} + +void LoopingFileSender::OnVideoFrame(const AVFrame& av_frame, + Clock::time_point capture_time) { + TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneSender); + latest_frame_time_ = std::max(capture_time, latest_frame_time_); + StreamingVp8Encoder::VideoFrame frame{}; + frame.width = av_frame.width - av_frame.crop_left - av_frame.crop_right; + frame.height = av_frame.height - av_frame.crop_top - av_frame.crop_bottom; + frame.yuv_planes[0] = av_frame.data[0] + av_frame.crop_left + + av_frame.linesize[0] * av_frame.crop_top; + frame.yuv_planes[1] = av_frame.data[1] + av_frame.crop_left / 2 + + av_frame.linesize[1] * av_frame.crop_top / 2; + frame.yuv_planes[2] = av_frame.data[2] + av_frame.crop_left / 2 + + av_frame.linesize[2] * av_frame.crop_top / 2; + for (int i = 0; i < 3; ++i) { + frame.yuv_strides[i] = av_frame.linesize[i]; + } + // TODO(miu): Add performance metrics visual overlay (based on Stats + // callback). + video_encoder_.EncodeAndSend(frame, capture_time, {}); +} + +void LoopingFileSender::UpdateStatusOnConsole() { + const Clock::duration elapsed = latest_frame_time_ - capture_start_time_; + const auto seconds_part = to_seconds(elapsed); + const auto millis_part = to_microseconds(elapsed - seconds_part); + // The control codes here attempt to erase the current line the cursor is + // on, and then print out the updated status text. If the terminal does not + // support simple ANSI escape codes, the following will still work, but + // there might sometimes be old status lines not getting erased (i.e., just + // partially overwritten). + fprintf(stdout, + "\r\x1b[2K\rLoopingFileSender: At %01" PRId64 + ".%03ds in file (est. network bandwidth: %d kbps). \n", + static_cast<int64_t>(seconds_part.count()), + static_cast<int>(millis_part.count()), bandwidth_estimate_ / 1024); + fflush(stdout); + + console_update_task_.ScheduleFromNow([this] { UpdateStatusOnConsole(); }, + kConsoleUpdateInterval); +} + +void LoopingFileSender::OnEndOfFile(SimulatedCapturer* capturer) { + OSP_LOG_INFO << "The " << ToTrackName(capturer) + << " capturer has reached the end of the media stream."; + --num_capturers_running_; + if (num_capturers_running_ == 0) { + console_update_task_.Cancel(); + next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately); + } +} + +void LoopingFileSender::OnError(SimulatedCapturer* capturer, + std::string message) { + OSP_LOG_ERROR << "The " << ToTrackName(capturer) + << " has failed: " << message; + --num_capturers_running_; + // If both fail, the application just pauses. This accounts for things like + // "file not found" errors. However, if only one track fails, then keep + // going. +} + +const char* LoopingFileSender::ToTrackName(SimulatedCapturer* capturer) const { + const char* which; + if (capturer == &*audio_capturer_) { + which = "audio"; + } else if (capturer == &*video_capturer_) { + which = "video"; + } else { + OSP_NOTREACHED(); + which = ""; + } + return which; +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.h b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.h new file mode 100644 index 00000000000..782c7fdf958 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/looping_file_sender.h @@ -0,0 +1,87 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STANDALONE_SENDER_LOOPING_FILE_SENDER_H_ +#define CAST_STANDALONE_SENDER_LOOPING_FILE_SENDER_H_ + +#include <algorithm> +#include <string> + +#include "cast/standalone_sender/constants.h" +#include "cast/standalone_sender/simulated_capturer.h" +#include "cast/standalone_sender/streaming_opus_encoder.h" +#include "cast/standalone_sender/streaming_vp8_encoder.h" +#include "cast/streaming/sender_session.h" + +namespace openscreen { +namespace cast { + +// Plays the media file at a given path over and over again, transcoding and +// streaming its audio/video. +class LoopingFileSender final : public SimulatedAudioCapturer::Client, + public SimulatedVideoCapturer::Client { + public: + LoopingFileSender(TaskRunner* task_runner, + const char* path, + const IPEndpoint& remote_endpoint, + SenderSession::ConfiguredSenders senders, + int max_bitrate); + + ~LoopingFileSender() final; + + private: + void UpdateEncoderBitrates(); + void ControlForNetworkCongestion(); + void SendFileAgain(); + + // SimulatedAudioCapturer overrides. + void OnAudioData(const float* interleaved_samples, + int num_samples, + Clock::time_point capture_time) final; + + // SimulatedVideoCapturer overrides; + void OnVideoFrame(const AVFrame& av_frame, + Clock::time_point capture_time) final; + + void UpdateStatusOnConsole(); + + // SimulatedCapturer overrides. + void OnEndOfFile(SimulatedCapturer* capturer) final; + void OnError(SimulatedCapturer* capturer, std::string message) final; + + const char* ToTrackName(SimulatedCapturer* capturer) const; + + // Holds the required injected dependencies (clock, task runner) used for Cast + // Streaming, and owns the UDP socket over which all communications occur with + // the remote's Receivers. + Environment env_; + + // The path to the media file to stream over and over. + const char* const path_; + + // The packet router allows both the Audio Sender and the Video Sender to + // share the same UDP socket. + SenderPacketRouter packet_router_; + + const int max_bitrate_; // Passed by the user on the command line. + int bandwidth_estimate_ = 0; + int bandwidth_being_utilized_; + + StreamingOpusEncoder audio_encoder_; + StreamingVp8Encoder video_encoder_; + + int num_capturers_running_ = 0; + Clock::time_point capture_start_time_{}; + Clock::time_point latest_frame_time_{}; + absl::optional<SimulatedAudioCapturer> audio_capturer_; + absl::optional<SimulatedVideoCapturer> video_capturer_; + + Alarm next_task_; + Alarm console_update_task_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STANDALONE_SENDER_LOOPING_FILE_SENDER_H_ diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/main.cc b/chromium/third_party/openscreen/src/cast/standalone_sender/main.cc index ef417132eb5..02c2b4ee8c4 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_sender/main.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/main.cc @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "platform/impl/logging.h" + +#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) #include <getopt.h> #include <chrono> @@ -12,6 +15,9 @@ #include <iostream> #include <sstream> +#include "cast/common/certificate/cast_trust_store.h" +#include "cast/standalone_sender/constants.h" +#include "cast/standalone_sender/looping_file_cast_agent.h" #include "cast/streaming/constants.h" #include "cast/streaming/environment.h" #include "cast/streaming/sender.h" @@ -21,315 +27,18 @@ #include "platform/api/time.h" #include "platform/base/error.h" #include "platform/base/ip_address.h" -#include "platform/impl/logging.h" #include "platform/impl/platform_client_posix.h" #include "platform/impl/task_runner.h" #include "platform/impl/text_trace_logging_platform.h" #include "util/alarm.h" #include "util/chrono_helpers.h" -#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) -#include "cast/standalone_sender/simulated_capturer.h" -#include "cast/standalone_sender/streaming_opus_encoder.h" -#include "cast/standalone_sender/streaming_vp8_encoder.h" -#endif - namespace openscreen { namespace cast { namespace { -//////////////////////////////////////////////////////////////////////////////// -// Sender Configuration -// -// The values defined here are constants that correspond to the standalone Cast -// Receiver app. In a production environment, these should ABSOLUTELY NOT be -// fixed! Instead a sender↔receiver OFFER/ANSWER exchange should establish them. - -// In a production environment, this would start-out at some initial value -// appropriate to the networking environment, and then be adjusted by the -// application as: 1) the TYPE of the content changes (interactive, low-latency -// versus smooth, higher-latency buffered video watching); and 2) the networking -// environment reliability changes. -constexpr milliseconds kTargetPlayoutDelay = kDefaultTargetPlayoutDelay; - -const SessionConfig kSampleAudioAnswerConfig{ - /* .sender_ssrc = */ 1, - /* .receiver_ssrc = */ 2, - /* .rtp_timebase = */ 48000, - /* .channels = */ 2, - /* .target_playout_delay */ kTargetPlayoutDelay, - /* .aes_secret_key = */ - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - 0x0c, 0x0d, 0x0e, 0x0f}, - /* .aes_iv_mask = */ - {0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, 0x70, 0x60, 0x50, 0x40, - 0x30, 0x20, 0x10, 0x00}, -}; - -const SessionConfig kSampleVideoAnswerConfig{ - /* .sender_ssrc = */ 50001, - /* .receiver_ssrc = */ 50002, - /* .rtp_timebase = */ static_cast<int>(kVideoTimebase::den), - /* .channels = */ 1, - /* .target_playout_delay */ kTargetPlayoutDelay, - /* .aes_secret_key = */ - {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, - 0x1c, 0x1d, 0x1e, 0x1f}, - /* .aes_iv_mask = */ - {0xf1, 0xe1, 0xd1, 0xc1, 0xb1, 0xa1, 0x91, 0x81, 0x71, 0x61, 0x51, 0x41, - 0x31, 0x21, 0x11, 0x01}, -}; - -// End of Sender Configuration. -//////////////////////////////////////////////////////////////////////////////// - -// What is the minimum amount of bandwidth required? -constexpr int kMinRequiredBitrate = 384 << 10; // 384 kbps. - -// What is the default maximum bitrate setting? -constexpr int kDefaultMaxBitrate = 5 << 20; // 5 Mbps. - -#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) - -// Above what available bandwidth should the high-quality audio bitrate be used? -constexpr int kHighBandwidthThreshold = 5 << 20; // 5 Mbps. - -// How often should the file position (media timestamp) be updated on the -// console? -constexpr milliseconds kConsoleUpdateInterval{100}; - -// How often should the congestion control logic re-evaluate the target encode -// bitrates? -constexpr milliseconds kCongestionCheckInterval{500}; - -// Plays the media file at a given path over and over again, transcoding and -// streaming its audio/video. -class LoopingFileSender final : public SimulatedAudioCapturer::Client, - public SimulatedVideoCapturer::Client { - public: - LoopingFileSender(TaskRunner* task_runner, - const char* path, - const IPEndpoint& remote_endpoint, - int max_bitrate, - bool use_android_rtp_hack) - : env_(&Clock::now, task_runner), - path_(path), - packet_router_(&env_), - max_bitrate_(max_bitrate), - audio_sender_(&env_, - &packet_router_, - kSampleAudioAnswerConfig, - use_android_rtp_hack - ? RtpPayloadType::kAudioHackForAndroidTV - : RtpPayloadType::kAudioOpus), - video_sender_(&env_, - &packet_router_, - kSampleVideoAnswerConfig, - use_android_rtp_hack - ? RtpPayloadType::kVideoHackForAndroidTV - : RtpPayloadType::kVideoVp8), - audio_encoder_(kSampleAudioAnswerConfig.channels, - StreamingOpusEncoder::kDefaultCastAudioFramesPerSecond, - &audio_sender_), - video_encoder_(StreamingVp8Encoder::Parameters{}, - env_.task_runner(), - &video_sender_), - next_task_(env_.now_function(), env_.task_runner()), - console_update_task_(env_.now_function(), env_.task_runner()) { - env_.set_remote_endpoint(remote_endpoint); - OSP_LOG_INFO << "Streaming to " << remote_endpoint << "..."; - - if (use_android_rtp_hack) { - OSP_LOG_INFO << "Using RTP payload types for older Android TV receivers."; - } - - OSP_LOG_INFO << "Max allowed media bitrate (audio + video) will be " - << max_bitrate_; - bandwidth_being_utilized_ = max_bitrate_ / 2; - UpdateEncoderBitrates(); - - next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately); - } - - ~LoopingFileSender() final = default; - - private: - void UpdateEncoderBitrates() { - if (bandwidth_being_utilized_ >= kHighBandwidthThreshold) { - audio_encoder_.UseHighQuality(); - } else { - audio_encoder_.UseStandardQuality(); - } - video_encoder_.SetTargetBitrate(bandwidth_being_utilized_ - - audio_encoder_.GetBitrate()); - } - - void ControlForNetworkCongestion() { - bandwidth_estimate_ = packet_router_.ComputeNetworkBandwidth(); - if (bandwidth_estimate_ > 0) { - // Don't ever try to use *all* of the network bandwidth! However, don't go - // below the absolute minimum requirement either. - constexpr double kGoodNetworkCitizenFactor = 0.8; - const int usable_bandwidth = std::max<int>( - kGoodNetworkCitizenFactor * bandwidth_estimate_, kMinRequiredBitrate); - - // See "congestion control" discussion in the class header comments for - // BandwidthEstimator. - if (usable_bandwidth > bandwidth_being_utilized_) { - constexpr double kConservativeIncrease = 1.1; - bandwidth_being_utilized_ = - std::min<int>(bandwidth_being_utilized_ * kConservativeIncrease, - usable_bandwidth); - } else { - bandwidth_being_utilized_ = usable_bandwidth; - } - - // Repsect the user's maximum bitrate setting. - bandwidth_being_utilized_ = - std::min(bandwidth_being_utilized_, max_bitrate_); - - UpdateEncoderBitrates(); - } else { - // There is no current bandwidth estimate. So, nothing should be adjusted. - } - - next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); }, - kCongestionCheckInterval); - } - - void SendFileAgain() { - OSP_LOG_INFO << "Sending " << path_ << " (starts in one second)..."; - - OSP_DCHECK_EQ(num_capturers_running_, 0); - num_capturers_running_ = 2; - capture_start_time_ = latest_frame_time_ = env_.now() + seconds(1); - audio_capturer_.emplace(&env_, path_, audio_encoder_.num_channels(), - audio_encoder_.sample_rate(), capture_start_time_, - this); - video_capturer_.emplace(&env_, path_, capture_start_time_, this); - - next_task_.ScheduleFromNow([this] { ControlForNetworkCongestion(); }, - kCongestionCheckInterval); - console_update_task_.Schedule([this] { UpdateStatusOnConsole(); }, - capture_start_time_); - } - - void OnAudioData(const float* interleaved_samples, - int num_samples, - Clock::time_point capture_time) final { - latest_frame_time_ = std::max(capture_time, latest_frame_time_); - audio_encoder_.EncodeAndSend(interleaved_samples, num_samples, - capture_time); - } - - void OnVideoFrame(const AVFrame& av_frame, - Clock::time_point capture_time) final { - latest_frame_time_ = std::max(capture_time, latest_frame_time_); - StreamingVp8Encoder::VideoFrame frame{}; - frame.width = av_frame.width - av_frame.crop_left - av_frame.crop_right; - frame.height = av_frame.height - av_frame.crop_top - av_frame.crop_bottom; - frame.yuv_planes[0] = av_frame.data[0] + av_frame.crop_left + - av_frame.linesize[0] * av_frame.crop_top; - frame.yuv_planes[1] = av_frame.data[1] + av_frame.crop_left / 2 + - av_frame.linesize[1] * av_frame.crop_top / 2; - frame.yuv_planes[2] = av_frame.data[2] + av_frame.crop_left / 2 + - av_frame.linesize[2] * av_frame.crop_top / 2; - for (int i = 0; i < 3; ++i) { - frame.yuv_strides[i] = av_frame.linesize[i]; - } - // TODO(miu): Add performance metrics visual overlay (based on Stats - // callback). - video_encoder_.EncodeAndSend(frame, capture_time, {}); - } - - void UpdateStatusOnConsole() { - const Clock::duration elapsed = latest_frame_time_ - capture_start_time_; - const auto seconds_part = to_seconds(elapsed); - const auto millis_part = to_microseconds(elapsed - seconds_part); - // The control codes here attempt to erase the current line the cursor is - // on, and then print out the updated status text. If the terminal does not - // support simple ANSI escape codes, the following will still work, but - // there might sometimes be old status lines not getting erased (i.e., just - // partially overwritten). - fprintf(stdout, - "\r\x1b[2K\rAt %01" PRId64 - ".%03ds in file (est. network bandwidth: %d kbps). ", - static_cast<int64_t>(seconds_part.count()), - static_cast<int>(millis_part.count()), bandwidth_estimate_ / 1024); - fflush(stdout); - - console_update_task_.ScheduleFromNow([this] { UpdateStatusOnConsole(); }, - kConsoleUpdateInterval); - } - - void OnEndOfFile(SimulatedCapturer* capturer) final { - OSP_LOG_INFO << "The " << ToTrackName(capturer) - << " capturer has reached the end of the media stream."; - --num_capturers_running_; - if (num_capturers_running_ == 0) { - console_update_task_.Cancel(); - next_task_.Schedule([this] { SendFileAgain(); }, Alarm::kImmediately); - } - } - - void OnError(SimulatedCapturer* capturer, std::string message) final { - OSP_LOG_ERROR << "The " << ToTrackName(capturer) - << " has failed: " << message; - --num_capturers_running_; - // If both fail, the application just pauses. This accounts for things like - // "file not found" errors. However, if only one track fails, then keep - // going. - } - - const char* ToTrackName(SimulatedCapturer* capturer) const { - const char* which; - if (capturer == &*audio_capturer_) { - which = "audio"; - } else if (capturer == &*video_capturer_) { - which = "video"; - } else { - OSP_NOTREACHED(); - which = ""; - } - return which; - } - - // Holds the required injected dependencies (clock, task runner) used for Cast - // Streaming, and owns the UDP socket over which all communications occur with - // the remote's Receivers. - Environment env_; - - // The path to the media file to stream over and over. - const char* const path_; - - // The packet router allows both the Audio Sender and the Video Sender to - // share the same UDP socket. - SenderPacketRouter packet_router_; - - const int max_bitrate_; // Passed by the user on the command line. - int bandwidth_estimate_ = 0; - int bandwidth_being_utilized_; - - Sender audio_sender_; - Sender video_sender_; - - StreamingOpusEncoder audio_encoder_; - StreamingVp8Encoder video_encoder_; - - int num_capturers_running_ = 0; - Clock::time_point capture_start_time_{}; - Clock::time_point latest_frame_time_{}; - absl::optional<SimulatedAudioCapturer> audio_capturer_; - absl::optional<SimulatedVideoCapturer> video_capturer_; - - Alarm next_task_; - Alarm console_update_task_; -}; - -#endif // defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) - IPEndpoint GetDefaultEndpoint() { - return IPEndpoint{IPAddress::kV4LoopbackAddress(), kDefaultCastStreamingPort}; + return IPEndpoint{IPAddress::kV4LoopbackAddress(), kDefaultCastPort}; } void LogUsage(const char* argv0) { @@ -349,6 +58,11 @@ void LogUsage(const char* argv0) { Default if not set: )" << kDefaultMaxBitrate << R"(. + -s, --server-certificate=path-to-cert + Specifies the path to the server certificate used by the receiver. + If omitted, only connections to receivers using an official + Google-signed cast certificate chain will be permitted. + -a, --android-hack: Use the wrong RTP payload types, for compatibility with older Android TV receivers. @@ -369,6 +83,7 @@ int StandaloneSenderMain(int argc, char* argv[]) { const struct option kArgumentOptions[] = { {"remote", required_argument, nullptr, 'r'}, {"max-bitrate", required_argument, nullptr, 'm'}, + {"server-certificate", required_argument, nullptr, 's'}, {"android-hack", no_argument, nullptr, 'a'}, {"tracing", no_argument, nullptr, 't'}, {"verbose", no_argument, nullptr, 'v'}, @@ -377,12 +92,13 @@ int StandaloneSenderMain(int argc, char* argv[]) { bool is_verbose = false; IPEndpoint remote_endpoint = GetDefaultEndpoint(); + std::string server_certificate_path; [[maybe_unused]] bool use_android_rtp_hack = false; [[maybe_unused]] int max_bitrate = kDefaultMaxBitrate; std::unique_ptr<TextTraceLoggingPlatform> trace_logger; int ch = -1; - while ((ch = getopt_long(argc, argv, "r:atvh", kArgumentOptions, nullptr)) != - -1) { + while ((ch = getopt_long(argc, argv, "r:m:s:atvh", kArgumentOptions, + nullptr)) != -1) { switch (ch) { case 'r': { const ErrorOr<IPEndpoint> parsed_endpoint = IPEndpoint::Parse(optarg); @@ -409,6 +125,9 @@ int StandaloneSenderMain(int argc, char* argv[]) { return 1; } break; + case 's': + server_certificate_path = optarg; + break; case 'a': use_android_rtp_hack = true; break; @@ -437,37 +156,43 @@ int StandaloneSenderMain(int argc, char* argv[]) { return 1; } -#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) + if (!server_certificate_path.empty()) { + CastTrustStore::CreateInstanceFromPemFile( + server_certificate_path, TrustStore::Mode::kAllowSelfSigned); + } auto* const task_runner = new TaskRunnerImpl(&Clock::now); PlatformClientPosix::Create(Clock::duration{50}, Clock::duration{50}, std::unique_ptr<TaskRunnerImpl>(task_runner)); - { - LoopingFileSender file_sender(task_runner, path, remote_endpoint, - max_bitrate, use_android_rtp_hack); - // Run the event loop until SIGINT (e.g., CTRL-C at the console) or SIGTERM - // are signaled. - task_runner->RunUntilSignaled(); - } + std::unique_ptr<LoopingFileCastAgent> cast_agent; + task_runner->PostTask([&] { + cast_agent = std::make_unique<LoopingFileCastAgent>(task_runner); + cast_agent->Connect({remote_endpoint, path, max_bitrate, + true /* should_include_video */, + use_android_rtp_hack}); + }); - PlatformClientPosix::ShutDown(); - -#else - - OSP_LOG_INFO - << "It compiled! However, you need to configure the build to point to " - "external libraries in order to build a useful app."; - -#endif // defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) + // Run the event loop until SIGINT (e.g., CTRL-C at the console) or + // SIGTERM are signaled. + task_runner->RunUntilSignaled(); + PlatformClientPosix::ShutDown(); return 0; } } // namespace } // namespace cast } // namespace openscreen +#endif int main(int argc, char* argv[]) { +#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS) return openscreen::cast::StandaloneSenderMain(argc, argv); +#else + OSP_LOG_ERROR + << "It compiled! However, you need to configure the build to point to " + "external libraries in order to build a useful app."; + return 1; +#endif } diff --git a/chromium/third_party/openscreen/src/cast/standalone_sender/streaming_opus_encoder.cc b/chromium/third_party/openscreen/src/cast/standalone_sender/streaming_opus_encoder.cc index ef9cc577564..b41b2a82934 100644 --- a/chromium/third_party/openscreen/src/cast/standalone_sender/streaming_opus_encoder.cc +++ b/chromium/third_party/openscreen/src/cast/standalone_sender/streaming_opus_encoder.cc @@ -9,6 +9,8 @@ #include <algorithm> #include <chrono> +#include "util/chrono_helpers.h" + namespace openscreen { namespace cast { diff --git a/chromium/third_party/openscreen/src/cast/streaming/BUILD.gn b/chromium/third_party/openscreen/src/cast/streaming/BUILD.gn index e384705be9e..b29ac6505c8 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/streaming/BUILD.gn @@ -9,6 +9,7 @@ source_set("common") { sources = [ "answer_messages.cc", "answer_messages.h", + "capture_configs.h", "capture_recommendations.cc", "capture_recommendations.h", "clock_drift_smoother.cc", @@ -23,6 +24,8 @@ source_set("common") { "frame_crypto.h", "frame_id.cc", "frame_id.h", + "message_fields.cc", + "message_fields.h", "ntp_time.cc", "ntp_time.h", "offer_messages.cc", @@ -48,6 +51,8 @@ source_set("common") { public_deps = [ "../../third_party/abseil", "../../third_party/boringssl", + "../common:channel", + "../common/certificate/proto:certificate_proto", ] deps = [ @@ -95,9 +100,13 @@ source_set("sender") { "sender_packet_router.h", "sender_report_builder.cc", "sender_report_builder.h", + "sender_session.cc", + "sender_session.h", ] public_deps = [ ":common" ] + + deps = [ "../../util" ] } source_set("unittests") { @@ -112,6 +121,7 @@ source_set("unittests") { "expanded_value_base_unittest.cc", "frame_collector_unittest.cc", "frame_crypto_unittest.cc", + "message_fields_unittest.cc", "mock_compound_rtcp_parser_client.h", "mock_environment.cc", "mock_environment.h", @@ -127,8 +137,10 @@ source_set("unittests") { "rtp_time_unittest.cc", "sender_packet_router_unittest.cc", "sender_report_unittest.cc", + "sender_session_unittest.cc", "sender_unittest.cc", "ssrc_unittest.cc", + "testing/simple_message_port.h", ] deps = [ diff --git a/chromium/third_party/openscreen/src/cast/streaming/capture_configs.h b/chromium/third_party/openscreen/src/cast/streaming/capture_configs.h new file mode 100644 index 00000000000..fd99c17cba5 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/capture_configs.h @@ -0,0 +1,83 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STREAMING_CAPTURE_CONFIGS_H_ +#define CAST_STREAMING_CAPTURE_CONFIGS_H_ + +#include <string> +#include <vector> + +#include "cast/streaming/constants.h" + +namespace openscreen { +namespace cast { + +// A configuration set that can be used by the sender to capture audio, and the +// receiver to playback audio. Used by Cast Streaming to provide an offer to the +// receiver. +struct AudioCaptureConfig { + // Audio codec represented by this configuration. + AudioCodec codec = AudioCodec::kOpus; + + // Number of channels used by this configuration. + int channels = kDefaultAudioChannels; + + // Average bit rate in bits per second used by this configuration. A value + // of "zero" suggests that the bitrate should be automatically selected by + // the sender. + int bit_rate = 0; + + // Sample rate for audio RTP timebase. + int sample_rate = kDefaultAudioSampleRate; + + // Target playout delay in milliseconds. + std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay; +}; + +// Display resolution in pixels. +struct DisplayResolution { + // Width in pixels. + int width = 1920; + + // Height in pixels. + int height = 1080; +}; + +// Frame rates are expressed as a rational number, and must be positive. +struct FrameRate { + // For simple cases, the frame rate may be provided by simply setting the + // number to the desired value, e.g. 30 or 60FPS. Some common frame rates like + // 23.98 FPS (for NTSC compatibility) are represented as fractions, in this + // case 24000/1001. + int numerator = kDefaultFrameRate; + int denominator = 1; +}; + +// A configuration set that can be used by the sender to capture video, as +// well as the receiver to playback video. Used by Cast Streaming to provide an +// offer to the receiver. +struct VideoCaptureConfig { + // Video codec represented by this configuration. + VideoCodec codec = VideoCodec::kVp8; + + // Maximum frame rate in frames per second. + FrameRate max_frame_rate; + + // Number specifying the maximum bit rate for this stream. A value of + // zero means that the maximum bit rate should be automatically selected by + // the sender. + int max_bit_rate = 0; + + // Resolutions to be offered to the receiver. At least one resolution + // must be provided. + std::vector<DisplayResolution> resolutions; + + // Target playout delay in milliseconds. + std::chrono::milliseconds target_playout_delay = kDefaultTargetPlayoutDelay; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STREAMING_CAPTURE_CONFIGS_H_ diff --git a/chromium/third_party/openscreen/src/cast/streaming/compound_rtcp_builder.cc b/chromium/third_party/openscreen/src/cast/streaming/compound_rtcp_builder.cc index fed38a32c87..7a133331f54 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/compound_rtcp_builder.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/compound_rtcp_builder.cc @@ -277,7 +277,7 @@ void CompoundRtcpBuilder::AppendCastFeedbackAckFields( if (!acks_for_next_packet_.empty()) { OSP_DCHECK(AreElementsSortedAndUnique(acks_for_next_packet_)); const FrameId first_frame_id = checkpoint_frame_id_ + 2; - for (const FrameId frame_id : acks_for_next_packet_) { + for (const FrameId& frame_id : acks_for_next_packet_) { const int bit_index = frame_id - first_frame_id; if (bit_index < 0) { continue; diff --git a/chromium/third_party/openscreen/src/cast/streaming/constants.h b/chromium/third_party/openscreen/src/cast/streaming/constants.h index 4a8d526e072..65cb2a1a9b9 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/constants.h +++ b/chromium/third_party/openscreen/src/cast/streaming/constants.h @@ -49,7 +49,33 @@ constexpr int kRequiredNetworkPacketSize = 256; // The spec declares RTP timestamps must always have a timebase of 90000 ticks // per second for video. -using kVideoTimebase = std::ratio<1, 90000>; +constexpr int kRtpVideoTimebase = 90000; + +// Minimum resolution is 320x240. +constexpr int kMinVideoHeight = 240; +constexpr int kMinVideoWidth = 320; + +// The default frame rate for capture options is 30FPS. +constexpr int kDefaultFrameRate = 30; + +// The default audio sample rate is 48kHz, slightly higher than standard +// consumer audio. +constexpr int kDefaultAudioSampleRate = 48000; + +// The default audio number of channels is set to stereo. +constexpr int kDefaultAudioChannels = 2; + +// TODO(jophba): migrate to discovering a randomly generated streaming +// sender id. This will require communicating the ID to the sender so that +// it can send messages appropriately. +constexpr char kDefaultStreamingReceiverSenderId[] = "receiver-12345"; + +// Codecs known and understood by cast senders and receivers. Note: receivers +// are required to implement the following codecs to be Cast V2 compliant: H264, +// VP8, AAC, Opus. Senders have to implement at least one codec for audio and +// video to start a session. +enum class AudioCodec { kAac, kOpus }; +enum class VideoCodec { kH264, kVp8, kHevc, kVp9 }; } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.cc b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.cc index 4d506d5eb7e..ca39e95015c 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.cc @@ -5,12 +5,14 @@ #include "cast/streaming/frame_crypto.h" #include <random> +#include <utility> #include "openssl/crypto.h" #include "openssl/err.h" #include "openssl/rand.h" #include "util/big_endian.h" #include "util/crypto/openssl_util.h" +#include "util/crypto/random_bytes.h" namespace openscreen { namespace cast { @@ -102,18 +104,5 @@ void FrameCrypto::EncryptCommon(FrameId frame_id, aes_nonce.data(), ecount_buf.data(), &block_offset); } -// static -std::array<uint8_t, 16> FrameCrypto::GenerateRandomBytes() { - std::array<uint8_t, 16> result; - const int return_code = RAND_bytes(result.data(), sizeof(result)); - if (return_code != 1) { - ClearOpenSSLERRStack(CURRENT_LOCATION); - OSP_LOG_FATAL - << "Failure when generating random bytes; unsafe to continue."; - OSP_NOTREACHED(); - } - return result; -} - } // namespace cast } // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.h b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.h index 35ee9787cb1..a86153e3e73 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.h +++ b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto.h @@ -72,9 +72,6 @@ class FrameCrypto { return encrypted_frame.data.size(); } - // Returns random bytes from a cryptographically-secure RNG source. - static std::array<uint8_t, 16> GenerateRandomBytes(); - private: // The 244-byte AES_KEY struct, derived from the |aes_key| passed to the ctor, // and initialized by boringssl's AES_set_encrypt_key() function. diff --git a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto_unittest.cc index 7ac8d5cf2e5..a845ed0333d 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/frame_crypto_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/frame_crypto_unittest.cc @@ -9,6 +9,7 @@ #include <vector> #include "gtest/gtest.h" +#include "util/crypto/random_bytes.h" namespace openscreen { namespace cast { @@ -28,8 +29,8 @@ TEST(FrameCryptoTest, EncryptsAndDecryptsFrames) { frame1.frame_id = frame0.frame_id + 1; frame1.data = frame0.data; - const std::array<uint8_t, 16> key = FrameCrypto::GenerateRandomBytes(); - const std::array<uint8_t, 16> iv = FrameCrypto::GenerateRandomBytes(); + const std::array<uint8_t, 16> key = GenerateRandomBytes16(); + const std::array<uint8_t, 16> iv = GenerateRandomBytes16(); EXPECT_NE(0, memcmp(key.data(), iv.data(), sizeof(key))); const FrameCrypto crypto(key, iv); diff --git a/chromium/third_party/openscreen/src/cast/streaming/message_fields.cc b/chromium/third_party/openscreen/src/cast/streaming/message_fields.cc new file mode 100644 index 00000000000..d55285194af --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/message_fields.cc @@ -0,0 +1,71 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/streaming/message_fields.h" + +#include <array> +#include <utility> + +#include "util/osp_logging.h" + +namespace openscreen { +namespace cast { +namespace { + +constexpr std::array<std::pair<const char*, AudioCodec>, 2> kAudioCodecNames{ + {{"aac", AudioCodec::kAac}, {"opus", AudioCodec::kOpus}}}; + +constexpr std::array<std::pair<const char*, VideoCodec>, 4> kVideoCodecNames{ + {{"h264", VideoCodec::kH264}, + {"vp8", VideoCodec::kVp8}, + {"hevc", VideoCodec::kHevc}, + {"vp9", VideoCodec::kVp9}}}; + +constexpr char kUnknownCodecError[] = "Codec not accounted for in name array."; + +template <typename T, size_t size> +const char* GetCodecName( + const std::array<std::pair<const char*, T>, size>& codecs, + T codec) { + for (auto pair : codecs) { + if (pair.second == codec) { + return pair.first; + } + } + OSP_NOTREACHED() << kUnknownCodecError; + return {}; +} + +template <typename T, size_t size> +T GetCodec(const std::array<std::pair<const char*, T>, size>& codecs, + absl::string_view name) { + for (auto pair : codecs) { + if (pair.first == name) { + return pair.second; + } + } + OSP_NOTREACHED() << kUnknownCodecError; + return {}; +} + +} // namespace + +const char* CodecToString(AudioCodec codec) { + return GetCodecName(kAudioCodecNames, codec); +} + +AudioCodec StringToAudioCodec(absl::string_view name) { + return GetCodec(kAudioCodecNames, name); +} + +const char* CodecToString(VideoCodec codec) { + return GetCodecName(kVideoCodecNames, codec); +} + +VideoCodec StringToVideoCodec(absl::string_view name) { + return GetCodec(kVideoCodecNames, name); +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/message_fields.h b/chromium/third_party/openscreen/src/cast/streaming/message_fields.h new file mode 100644 index 00000000000..ef7b742f5c2 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/message_fields.h @@ -0,0 +1,51 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STREAMING_MESSAGE_FIELDS_H_ +#define CAST_STREAMING_MESSAGE_FIELDS_H_ + +#include <string> + +#include "absl/strings/string_view.h" +#include "cast/streaming/constants.h" + +namespace openscreen { +namespace cast { + +/// NOTE: Constants here are all taken from the Cast V2: Mirroring Control +/// Protocol specification. + +// Namespace for OFFER/ANSWER messages. +constexpr char kCastWebrtcNamespace[] = "urn:x-cast:com.google.cast.webrtc"; + +// JSON message field values specific to the Sender Session. +constexpr char kMessageType[] = "type"; +constexpr char kMessageTypeOffer[] = "OFFER"; +constexpr char kMessageTypeAnswer[] = "ANSWER"; + +// List of OFFER message fields. +constexpr char kOfferMessageBody[] = "offer"; +constexpr char kKeyType[] = "type"; +constexpr char kSequenceNumber[] = "seqNum"; + +/// ANSWER message fields. +constexpr char kAnswerMessageBody[] = "answer"; +constexpr char kResult[] = "result"; +constexpr char kResultOk[] = "ok"; +constexpr char kResultError[] = "error"; +constexpr char kErrorMessageBody[] = "error"; +constexpr char kErrorCode[] = "code"; +constexpr char kErrorDescription[] = "description"; + +// Conversion methods for codec message fields. +const char* CodecToString(AudioCodec codec); +AudioCodec StringToAudioCodec(absl::string_view name); + +const char* CodecToString(VideoCodec codec); +VideoCodec StringToVideoCodec(absl::string_view name); + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STREAMING_MESSAGE_FIELDS_H_ diff --git a/chromium/third_party/openscreen/src/cast/streaming/message_fields_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/message_fields_unittest.cc new file mode 100644 index 00000000000..f7eedf9c12f --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/message_fields_unittest.cc @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/streaming/message_fields.h" + +#include <array> +#include <cstring> +#include <vector> + +#include "gtest/gtest.h" + +namespace openscreen { +namespace cast { +namespace { + +// NOTE: We don't do an exhaustive check of all values here, to avoid +// unnecessary duplication, but want to ensure that lookup is working properly. +TEST(MessageFieldsTest, CanParseEnumToString) { + EXPECT_STREQ("aac", CodecToString(AudioCodec::kAac)); + EXPECT_STREQ("vp8", CodecToString(VideoCodec::kVp8)); +} + +TEST(MessageFieldsTest, CanStringToEnum) { + EXPECT_EQ(AudioCodec::kOpus, StringToAudioCodec("opus")); + EXPECT_EQ(VideoCodec::kHevc, StringToVideoCodec("hevc")); +} + +TEST(MessageFieldsTest, Identity) { + EXPECT_STREQ("opus", CodecToString(StringToAudioCodec("opus"))); + EXPECT_STREQ("vp8", CodecToString(StringToVideoCodec("vp8"))); +} + +} // namespace +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/message_port.h b/chromium/third_party/openscreen/src/cast/streaming/message_port.h deleted file mode 100644 index f44a808dbf1..00000000000 --- a/chromium/third_party/openscreen/src/cast/streaming/message_port.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CAST_STREAMING_MESSAGE_PORT_H_ -#define CAST_STREAMING_MESSAGE_PORT_H_ - -#include "absl/strings/string_view.h" -#include "platform/base/error.h" - -namespace openscreen { -namespace cast { - -// This interface is intended to provide an abstraction for communicating -// cast messages across a pipe with guaranteed delivery. This is used to -// decouple the cast receiver session (and potentially other classes) from any -// network implementation. -class MessagePort { - public: - class Client { - public: - virtual void OnMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) = 0; - virtual void OnError(Error error) = 0; - }; - - virtual ~MessagePort() = default; - virtual void SetClient(Client* client) = 0; - virtual void PostMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) = 0; -}; - -} // namespace cast -} // namespace openscreen - -#endif // CAST_STREAMING_MESSAGE_PORT_H_ diff --git a/chromium/third_party/openscreen/src/cast/streaming/offer_messages.cc b/chromium/third_party/openscreen/src/cast/streaming/offer_messages.cc index ff6845a31f7..179037e604e 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/offer_messages.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/offer_messages.cc @@ -295,7 +295,7 @@ ErrorOr<Json::Value> Stream::ToJson() const { root["targetDelay"] = static_cast<int>(target_delay.count()); root["aesKey"] = HexEncode(aes_key); root["aesIvMask"] = HexEncode(aes_iv_mask); - root["ReceiverRtcpEventLog"] = receiver_rtcp_event_log; + root["receiverRtcpEventLog"] = receiver_rtcp_event_log; root["receiverRtcpDscp"] = receiver_rtcp_dscp; root["timeBase"] = "1/" + std::to_string(rtp_timebase); return root; @@ -371,6 +371,9 @@ ErrorOr<Json::Value> VideoStream::ToJson() const { // static ErrorOr<Offer> Offer::Parse(const Json::Value& root) { + if (!root.isObject()) { + return json::CreateParseError("null offer"); + } CastMode cast_mode = CastMode::Parse(root["castMode"].asString()); const ErrorOr<bool> get_status = json::ParseBool(root, "receiverGetStatus"); diff --git a/chromium/third_party/openscreen/src/cast/streaming/packet_receive_stats_tracker_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/packet_receive_stats_tracker_unittest.cc index eb1b45c7118..3d2f21e932b 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/packet_receive_stats_tracker_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/packet_receive_stats_tracker_unittest.cc @@ -15,8 +15,6 @@ namespace openscreen { namespace cast { namespace { -constexpr int kSomeRtpTimebase = static_cast<int>(kVideoTimebase::den); - // Returns a RtcpReportBlock with all fields set to known values to see how the // fields are modified by functions called during the tests. RtcpReportBlock GetSentinel() { @@ -66,7 +64,7 @@ RtcpReportBlock GetSentinel() { } while (false) TEST(PacketReceiveStatsTrackerTest, DoesNotPopulateReportWithoutData) { - PacketReceiveStatsTracker tracker(kSomeRtpTimebase); + PacketReceiveStatsTracker tracker(kRtpVideoTimebase); RtcpReportBlock report = GetSentinel(); tracker.PopulateNextReport(&report); EXPECT_FIELDS_NOT_POPULATED(report); @@ -78,7 +76,7 @@ TEST(PacketReceiveStatsTrackerTest, PopulatesReportWithOnePacketTracked) { RtpTimeTicks() + RtpTimeDelta::FromTicks(42); constexpr auto kArrivalTime = Clock::time_point() + seconds(3600); - PacketReceiveStatsTracker tracker(kSomeRtpTimebase); + PacketReceiveStatsTracker tracker(kRtpVideoTimebase); tracker.OnReceivedValidRtpPacket(kSequenceNumber, kRtpTimestamp, kArrivalTime); @@ -99,14 +97,14 @@ TEST(PacketReceiveStatsTrackerTest, WhenReceivingAllPackets) { RtpTimeTicks() + RtpTimeDelta::FromTicks(42); constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(3600); - PacketReceiveStatsTracker tracker(kSomeRtpTimebase); + PacketReceiveStatsTracker tracker(kRtpVideoTimebase); // Record 10 packets arrived exactly one second apart with media timestamps // also exactly one second apart. for (int i = 0; i < 10; ++i) { tracker.OnReceivedValidRtpPacket( kFirstSequenceNumber + i, - kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kSomeRtpTimebase) * i, + kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i, kFirstArrivalTime + seconds(i)); } @@ -133,7 +131,7 @@ TEST(PacketReceiveStatsTrackerTest, WhenReceivingAboutHalfThePackets) { RtpTimeTicks() + RtpTimeDelta::FromTicks(99); constexpr auto kFirstArrivalTime = Clock::time_point() + seconds(8888); - PacketReceiveStatsTracker tracker(kSomeRtpTimebase); + PacketReceiveStatsTracker tracker(kRtpVideoTimebase); // Record 10 packet arrivals whose sequence numbers step by 2, which should // indicate half of the packets didn't arrive. @@ -143,7 +141,7 @@ TEST(PacketReceiveStatsTrackerTest, WhenReceivingAboutHalfThePackets) { for (int i = 0; i < 10; ++i) { tracker.OnReceivedValidRtpPacket( kFirstSequenceNumber + (i * 2 + 1), - kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kSomeRtpTimebase) * i, + kFirstRtpTimestamp + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * i, kFirstArrivalTime + seconds(i)); } @@ -168,13 +166,13 @@ TEST(PacketReceiveStatsTrackerTest, ComputesJitterCorrectly) { // timestamps are progressing 2 seconds forward. Thus, the jitter calculation // should gradually converge towards a difference of one second. constexpr auto kTrueJitter = Clock::to_duration(seconds(1)); - PacketReceiveStatsTracker tracker(kSomeRtpTimebase); + PacketReceiveStatsTracker tracker(kRtpVideoTimebase); Clock::duration last_diff = Clock::duration::max(); for (int i = 0; i < 100; ++i) { tracker.OnReceivedValidRtpPacket( kFirstSequenceNumber + i, kFirstRtpTimestamp + - RtpTimeDelta::FromTicks(kSomeRtpTimebase) * (i * 2), + RtpTimeDelta::FromTicks(kRtpVideoTimebase) * (i * 2), kFirstArrivalTime + seconds(i)); // Expect that the jitter is becoming closer to the actual value in each @@ -182,7 +180,7 @@ TEST(PacketReceiveStatsTrackerTest, ComputesJitterCorrectly) { RtcpReportBlock report; tracker.PopulateNextReport(&report); const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>( - kSomeRtpTimebase); + kRtpVideoTimebase); EXPECT_LT(diff, last_diff); last_diff = diff; } @@ -193,8 +191,8 @@ TEST(PacketReceiveStatsTrackerTest, ComputesJitterCorrectly) { // to that value. RtcpReportBlock report; tracker.PopulateNextReport(&report); - const auto diff = - kTrueJitter - report.jitter.ToDuration<Clock::duration>(kSomeRtpTimebase); + const auto diff = kTrueJitter - report.jitter.ToDuration<Clock::duration>( + kRtpVideoTimebase); constexpr auto kMaxDiffAtEnd = Clock::to_duration(milliseconds(2)); EXPECT_NEAR(0, diff.count(), kMaxDiffAtEnd.count()); } diff --git a/chromium/third_party/openscreen/src/cast/streaming/receiver.cc b/chromium/third_party/openscreen/src/cast/streaming/receiver.cc index d4c86da437c..d62d625147a 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/receiver.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/receiver.cc @@ -29,9 +29,10 @@ namespace cast { Receiver::Receiver(Environment* environment, ReceiverPacketRouter* packet_router, - const SessionConfig& config) + SessionConfig config) : now_(environment->now_function()), packet_router_(packet_router), + config_(config), rtcp_session_(config.sender_ssrc, config.receiver_ssrc, now_()), rtcp_parser_(&rtcp_session_), rtcp_builder_(&rtcp_session_), diff --git a/chromium/third_party/openscreen/src/cast/streaming/receiver.h b/chromium/third_party/openscreen/src/cast/streaming/receiver.h index b4c53868b03..abdc1852bf0 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/receiver.h +++ b/chromium/third_party/openscreen/src/cast/streaming/receiver.h @@ -25,6 +25,7 @@ #include "cast/streaming/rtcp_session.h" #include "cast/streaming/rtp_packet_parser.h" #include "cast/streaming/sender_report_parser.h" +#include "cast/streaming/session_config.h" #include "cast/streaming/ssrc.h" #include "platform/api/time.h" #include "util/alarm.h" @@ -34,7 +35,6 @@ namespace cast { struct EncodedFrame; class ReceiverPacketRouter; -struct SessionConfig; // The Cast Streaming Receiver, a peer corresponding to some Cast Streaming // Sender at the other end of a network link. @@ -125,11 +125,12 @@ class Receiver { // is started). Receiver(Environment* environment, ReceiverPacketRouter* packet_router, - const SessionConfig& config); + SessionConfig config); ~Receiver(); - Ssrc ssrc() const { return rtcp_session_.receiver_ssrc(); } + const SessionConfig& config() const { return config_; } int rtp_timebase() const { return rtp_timebase_; } + Ssrc ssrc() const { return rtcp_session_.receiver_ssrc(); } // Set the Consumer receiving notifications when new frames are ready for // consumption. Frames received before this method is called will remain in @@ -257,6 +258,7 @@ class Receiver { const ClockNowFunctionPtr now_; ReceiverPacketRouter* const packet_router_; + const SessionConfig config_; RtcpSession rtcp_session_; SenderReportParser rtcp_parser_; CompoundRtcpBuilder rtcp_builder_; diff --git a/chromium/third_party/openscreen/src/cast/streaming/receiver_session.cc b/chromium/third_party/openscreen/src/cast/streaming/receiver_session.cc index e692904f1f4..4cabbc853d8 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/receiver_session.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/receiver_session.cc @@ -4,14 +4,16 @@ #include "cast/streaming/receiver_session.h" +#include <algorithm> #include <chrono> #include <string> #include <utility> #include "absl/strings/match.h" #include "absl/strings/numbers.h" +#include "cast/common/public/message_port.h" #include "cast/streaming/environment.h" -#include "cast/streaming/message_port.h" +#include "cast/streaming/message_fields.h" #include "cast/streaming/offer_messages.h" #include "cast/streaming/receiver.h" #include "util/json/json_helpers.h" @@ -20,63 +22,12 @@ namespace openscreen { namespace cast { -/// NOTE: Constants here are all taken from the Cast V2: Mirroring Control -// JSON message field values specific to the Receiver Session. -static constexpr char kMessageTypeOffer[] = "OFFER"; - -// List of OFFER message fields. -static constexpr char kOfferMessageBody[] = "offer"; -static constexpr char kKeyType[] = "type"; -static constexpr char kSequenceNumber[] = "seqNum"; - -/// Protocol specification: http://goto.google.com/mirroring-control-protocol -// TODO(jophba): document the protocol in a public repository. -static constexpr char kMessageKeyType[] = "type"; -static constexpr char kMessageTypeAnswer[] = "ANSWER"; - -/// ANSWER message fields. -static constexpr char kAnswerMessageBody[] = "answer"; -static constexpr char kResult[] = "result"; -static constexpr char kResultOk[] = "ok"; -static constexpr char kResultError[] = "error"; -static constexpr char kErrorMessageBody[] = "error"; -static constexpr char kErrorCode[] = "code"; -static constexpr char kErrorDescription[] = "description"; - // Using statements for constructor readability. using Preferences = ReceiverSession::Preferences; using ConfiguredReceivers = ReceiverSession::ConfiguredReceivers; namespace { -std::string CodecToString(ReceiverSession::AudioCodec codec) { - switch (codec) { - case ReceiverSession::AudioCodec::kAac: - return "aac"; - case ReceiverSession::AudioCodec::kOpus: - return "opus"; - default: - OSP_NOTREACHED() << "Codec not accounted for in switch statement."; - return {}; - } -} - -std::string CodecToString(ReceiverSession::VideoCodec codec) { - switch (codec) { - case ReceiverSession::VideoCodec::kH264: - return "h264"; - case ReceiverSession::VideoCodec::kVp8: - return "vp8"; - case ReceiverSession::VideoCodec::kHevc: - return "hevc"; - case ReceiverSession::VideoCodec::kVp9: - return "vp9"; - default: - OSP_NOTREACHED() << "Codec not accounted for in switch statement."; - return {}; - } -} - template <typename Stream, typename Codec> const Stream* SelectStream(const std::vector<Codec>& preferred_codecs, const std::vector<Stream>& offered_streams) { @@ -95,7 +46,7 @@ const Stream* SelectStream(const std::vector<Codec>& preferred_codecs, // Helper method that creates an invalid Answer response. Json::Value CreateInvalidAnswerMessage(Error error) { Json::Value message_root; - message_root[kMessageKeyType] = kMessageTypeAnswer; + message_root[kMessageType] = kMessageTypeAnswer; message_root[kResult] = kResultError; message_root[kErrorMessageBody][kErrorCode] = static_cast<int>(error.code()); message_root[kErrorMessageBody][kErrorDescription] = error.message(); @@ -107,12 +58,16 @@ Json::Value CreateInvalidAnswerMessage(Error error) { Json::Value CreateAnswerMessage(const Answer& answer) { OSP_DCHECK(answer.IsValid()); Json::Value message_root; - message_root[kMessageKeyType] = kMessageTypeAnswer; + message_root[kMessageType] = kMessageTypeAnswer; message_root[kAnswerMessageBody] = answer.ToJson(); message_root[kResult] = kResultOk; return message_root; } +DisplayResolution ToDisplayResolution(const Resolution& resolution) { + return DisplayResolution{resolution.width, resolution.height}; +} + } // namespace ReceiverSession::Client::~Client() = default; @@ -147,17 +102,17 @@ ReceiverSession::ReceiverSession(Client* const client, OSP_DCHECK(message_port_); OSP_DCHECK(environment_); - message_port_->SetClient(this); + message_port_->SetClient(this, kDefaultStreamingReceiverSenderId); } ReceiverSession::~ReceiverSession() { ResetReceivers(Client::kEndOfSession); - message_port_->SetClient(nullptr); + message_port_->ResetClient(); } -void ReceiverSession::OnMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) { +void ReceiverSession::OnMessage(const std::string& sender_id, + const std::string& message_namespace, + const std::string& message) { ErrorOr<Json::Value> message_json = json::Parse(message); if (!message_json) { @@ -167,7 +122,6 @@ void ReceiverSession::OnMessage(absl::string_view sender_id, } OSP_DVLOG << "Received a message: " << message; - // TODO(jophba): add sender connected/disconnected messaging. int sequence_number; if (!json::ParseAndValidateInt(message_json.value()[kSequenceNumber], &sequence_number)) { @@ -185,12 +139,6 @@ void ReceiverSession::OnMessage(absl::string_view sender_id, sequence_number}; if (key == kMessageTypeOffer) { parsed_message.body = std::move(message_json.value()[kOfferMessageBody]); - if (parsed_message.body.isNull()) { - client_->OnError(this, Error(Error::Code::kJsonParseError, - "Received offer missing offer body")); - OSP_DLOG_WARN << "Invalid message offer body"; - return; - } OnOffer(&parsed_message); } } @@ -204,6 +152,9 @@ void ReceiverSession::OnOffer(Message* message) { if (!offer) { client_->OnError(this, offer.error()); OSP_DLOG_WARN << "Could not parse offer" << offer.error(); + message->body = CreateInvalidAnswerMessage( + Error(Error::Code::kParseError, "Failed to parse malformed OFFER")); + SendMessage(message); return; } @@ -249,16 +200,14 @@ void ReceiverSession::OnOffer(Message* message) { SendMessage(message); } -std::pair<SessionConfig, std::unique_ptr<Receiver>> -ReceiverSession::ConstructReceiver(const Stream& stream) { +std::unique_ptr<Receiver> ReceiverSession::ConstructReceiver( + const Stream& stream) { SessionConfig config = {stream.ssrc, stream.ssrc + 1, stream.rtp_timebase, stream.channels, stream.target_delay, stream.aes_key, stream.aes_iv_mask}; - auto receiver = - std::make_unique<Receiver>(environment_, &packet_router_, config); - - return std::make_pair(std::move(config), std::move(receiver)); + return std::make_unique<Receiver>(environment_, &packet_router_, + std::move(config)); } ConfiguredReceivers ReceiverSession::SpawnReceivers(const AudioStream* audio, @@ -266,25 +215,45 @@ ConfiguredReceivers ReceiverSession::SpawnReceivers(const AudioStream* audio, OSP_DCHECK(audio || video); ResetReceivers(Client::kRenegotiated); - absl::optional<ConfiguredReceiver<AudioStream>> audio_receiver; - absl::optional<ConfiguredReceiver<VideoStream>> video_receiver; - + AudioCaptureConfig audio_config; + absl::optional<ConfiguredReceiver<AudioStream>> deprecated_audio; if (audio) { - auto audio_pair = ConstructReceiver(audio->stream); - current_audio_receiver_ = std::move(audio_pair.second); - audio_receiver.emplace(ConfiguredReceiver<AudioStream>{ - current_audio_receiver_.get(), std::move(audio_pair.first), *audio}); + current_audio_receiver_ = ConstructReceiver(audio->stream); + audio_config = AudioCaptureConfig{ + StringToAudioCodec(audio->stream.codec_name), audio->stream.channels, + audio->bit_rate, audio->stream.rtp_timebase, + audio->stream.target_delay}; + deprecated_audio.emplace(ConfiguredReceiver<AudioStream>{ + current_audio_receiver_.get(), current_audio_receiver_->config(), + *audio}); } + VideoCaptureConfig video_config; + absl::optional<ConfiguredReceiver<VideoStream>> deprecated_video; if (video) { - auto video_pair = ConstructReceiver(video->stream); - current_video_receiver_ = std::move(video_pair.second); - video_receiver.emplace(ConfiguredReceiver<VideoStream>{ - current_video_receiver_.get(), std::move(video_pair.first), *video}); + current_video_receiver_ = ConstructReceiver(video->stream); + std::vector<DisplayResolution> display_resolutions; + std::transform(video->resolutions.begin(), video->resolutions.end(), + std::back_inserter(display_resolutions), + ToDisplayResolution); + video_config = + VideoCaptureConfig{StringToVideoCodec(video->stream.codec_name), + FrameRate{video->max_frame_rate.numerator, + video->max_frame_rate.denominator}, + video->max_bit_rate, std::move(display_resolutions), + video->stream.target_delay}; + deprecated_video.emplace(ConfiguredReceiver<VideoStream>{ + current_video_receiver_.get(), current_video_receiver_->config(), + *video}); } - return ConfiguredReceivers{std::move(audio_receiver), - std::move(video_receiver)}; + return ConfiguredReceivers{ + current_audio_receiver_.get(), std::move(audio_config), + current_video_receiver_.get(), std::move(video_config), + + // TODO(crbug.com/1132109): Remove deprecated ConfiguredReceiver fields + // after downstream migration + std::move(deprecated_audio), std::move(deprecated_video)}; } void ReceiverSession::ResetReceivers(Client::ReceiversDestroyingReason reason) { diff --git a/chromium/third_party/openscreen/src/cast/streaming/receiver_session.h b/chromium/third_party/openscreen/src/cast/streaming/receiver_session.h index c9fb691190b..a45add7c014 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/receiver_session.h +++ b/chromium/third_party/openscreen/src/cast/streaming/receiver_session.h @@ -10,13 +10,11 @@ #include <utility> #include <vector> -// TODO(jophba): remove public abseil dependencies. Will require modifying -// either Optional or ConfiguredReceivers, as the compiler currently has an -// error. #include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "cast/common/public/message_port.h" #include "cast/streaming/answer_messages.h" -#include "cast/streaming/message_port.h" +#include "cast/streaming/capture_configs.h" #include "cast/streaming/offer_messages.h" #include "cast/streaming/receiver_packet_router.h" #include "cast/streaming/session_config.h" @@ -25,17 +23,14 @@ namespace openscreen { namespace cast { -class CastSocket; class Environment; class Receiver; -class VirtualConnectionRouter; -struct VirtualConnection; class ReceiverSession final : public MessagePort::Client { public: - // A small helper struct that contains all of the information necessary for - // a configured receiver, including a receiver, its session config, and the - // stream selected from the OFFER message to instantiate the receiver. + // DEPRECATED. + // TODO(crbug.com/1132097): Remove deprecated ConfiguredReceiver fields after + // downstream migration template <typename T> struct ConfiguredReceiver { Receiver* receiver; @@ -54,10 +49,19 @@ class ReceiverSession final : public MessagePort::Client { // ReceiverSession, not the Client, and references to these pointers must be // cleared before a call to Client::OnReceiversDestroying() returns. - // If the receiver is audio- or video-only, either of the receivers - // may be nullptr. However, in the majority of cases they will be populated. - // TODO(jophba): remove AudioStream, VideoStream from public API. - // TODO(jophba): remove absl::optional from public API. + // If the receiver is audio- or video-only, or we failed to negotiate + // an acceptable session configuration with the sender, then either of the + // receivers may be nullptr. In this case, the associated config is default + // initialized and should be ignored. + Receiver* audio_receiver; + AudioCaptureConfig audio_config; + + Receiver* video_receiver; + VideoCaptureConfig video_config; + + // DEPRECATED + // TODO(crbug.com/1132097): Remove deprecated ConfiguredReceiver fields + // after downstream migration absl::optional<ConfiguredReceiver<AudioStream>> audio; absl::optional<ConfiguredReceiver<VideoStream>> video; }; @@ -89,11 +93,6 @@ class ReceiverSession final : public MessagePort::Client { virtual ~Client(); }; - // The embedder has the option of providing a list of prioritized - // preferences for selecting from the offer. - enum class AudioCodec { kAac, kOpus }; - enum class VideoCodec { kH264, kVp8, kHevc, kVp9 }; - // Note: embedders are required to implement the following // codecs to be Cast V2 compliant: H264, VP8, AAC, Opus. struct Preferences { @@ -131,9 +130,9 @@ class ReceiverSession final : public MessagePort::Client { ~ReceiverSession(); // MessagePort::Client overrides - void OnMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) override; + void OnMessage(const std::string& sender_id, + const std::string& message_namespace, + const std::string& message) override; void OnError(Error error) override; private: @@ -148,8 +147,7 @@ class ReceiverSession final : public MessagePort::Client { void OnOffer(Message* message); // Used by SpawnReceivers to generate a receiver for a specific stream. - std::pair<SessionConfig, std::unique_ptr<Receiver>> ConstructReceiver( - const Stream& stream); + std::unique_ptr<Receiver> ConstructReceiver(const Stream& stream); // Creates a set of configured receivers from a given pair of audio and // video streams. NOTE: either audio or video may be null, but not both. diff --git a/chromium/third_party/openscreen/src/cast/streaming/receiver_session_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/receiver_session_unittest.cc index 3e6f794516b..d7fbd7da5f0 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/receiver_session_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/receiver_session_unittest.cc @@ -7,6 +7,8 @@ #include <utility> #include "cast/streaming/mock_environment.h" +#include "cast/streaming/receiver.h" +#include "cast/streaming/testing/simple_message_port.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "platform/base/ip_address.h" @@ -200,6 +202,20 @@ constexpr char kInvalidJsonOfferMessage[] = R"({ } })"; +constexpr char kMissingMandatoryFieldOfferMessage[] = R"({ + "type": "OFFER", + "seqNum": 1337 +})"; + +constexpr char kMissingSeqNumOfferMessage[] = R"({ + "type": "OFFER", + "offer": { + "castMode": "mirroring", + "receiverGetStatus": true, + "supportedStreams": [] + } +})"; + constexpr char kValidJsonInvalidFormatOfferMessage[] = R"({ "type": "OFFER", "seqNum": 1337, @@ -230,37 +246,6 @@ constexpr char kInvalidTypeMessage[] = R"({ "seqNum": 1337 })"; -class SimpleMessagePort : public MessagePort { - public: - ~SimpleMessagePort() override {} - void SetClient(MessagePort::Client* client) override { client_ = client; } - - void ReceiveMessage(absl::string_view message) { - ASSERT_NE(client_, nullptr); - client_->OnMessage("sender-id", "namespace", message); - } - - void ReceiveError(Error error) { - ASSERT_NE(client_, nullptr); - client_->OnError(error); - } - - void PostMessage(absl::string_view sender_id, - absl::string_view message_namespace, - absl::string_view message) override { - posted_messages_.emplace_back(std::move(message)); - } - - MessagePort::Client* client() const { return client_; } - const std::vector<std::string> posted_messages() const { - return posted_messages_; - } - - private: - MessagePort::Client* client_ = nullptr; - std::vector<std::string> posted_messages_; -}; - class FakeClient : public ReceiverSession::Client { public: MOCK_METHOD(void, @@ -328,31 +313,23 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithDefaultPreferences) { EXPECT_CALL(client_, OnNegotiated(session_.get(), _)) .WillOnce([](const ReceiverSession* session_, ReceiverSession::ConfiguredReceivers cr) { - EXPECT_TRUE(cr.audio); - EXPECT_EQ(cr.audio.value().receiver_config.sender_ssrc, 19088747u); - EXPECT_EQ(cr.audio.value().receiver_config.receiver_ssrc, 19088748u); - EXPECT_EQ(cr.audio.value().receiver_config.channels, 2); - EXPECT_EQ(cr.audio.value().receiver_config.rtp_timebase, 48000); + EXPECT_TRUE(cr.audio_receiver); + EXPECT_EQ(cr.audio_receiver->config().sender_ssrc, 19088747u); + EXPECT_EQ(cr.audio_receiver->config().receiver_ssrc, 19088748u); + EXPECT_EQ(cr.audio_receiver->config().channels, 2); + EXPECT_EQ(cr.audio_receiver->config().rtp_timebase, 48000); // We should have chosen opus - EXPECT_EQ(cr.audio.value().selected_stream.stream.index, 1337); - EXPECT_EQ(cr.audio.value().selected_stream.stream.type, - Stream::Type::kAudioSource); - EXPECT_EQ(cr.audio.value().selected_stream.stream.codec_name, "opus"); - EXPECT_EQ(cr.audio.value().selected_stream.stream.channels, 2); - - EXPECT_TRUE(cr.video); - EXPECT_EQ(cr.video.value().receiver_config.sender_ssrc, 19088745u); - EXPECT_EQ(cr.video.value().receiver_config.receiver_ssrc, 19088746u); - EXPECT_EQ(cr.video.value().receiver_config.channels, 1); - EXPECT_EQ(cr.video.value().receiver_config.rtp_timebase, 90000); + EXPECT_EQ(cr.audio_config.codec, AudioCodec::kOpus); + + EXPECT_TRUE(cr.video_receiver); + EXPECT_EQ(cr.video_receiver->config().sender_ssrc, 19088745u); + EXPECT_EQ(cr.video_receiver->config().receiver_ssrc, 19088746u); + EXPECT_EQ(cr.video_receiver->config().channels, 1); + EXPECT_EQ(cr.video_receiver->config().rtp_timebase, 90000); // We should have chosen vp8 - EXPECT_EQ(cr.video.value().selected_stream.stream.index, 31338); - EXPECT_EQ(cr.video.value().selected_stream.stream.type, - Stream::Type::kVideoSource); - EXPECT_EQ(cr.video.value().selected_stream.stream.codec_name, "vp8"); - EXPECT_EQ(cr.video.value().selected_stream.stream.channels, 1); + EXPECT_EQ(cr.video_config.codec, VideoCodec::kVp8); }); EXPECT_CALL(client_, OnReceiversDestroying(session_.get(), @@ -393,25 +370,25 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithDefaultPreferences) { TEST_F(ReceiverSessionTest, CanNegotiateWithCustomCodecPreferences) { ReceiverSession session( &client_, environment_.get(), message_port_.get(), - ReceiverSession::Preferences{{ReceiverSession::VideoCodec::kVp9}, - {ReceiverSession::AudioCodec::kOpus}}); + ReceiverSession::Preferences{{VideoCodec::kVp9}, {AudioCodec::kOpus}}); InSequence s; EXPECT_CALL(client_, OnNegotiated(&session, _)) .WillOnce([](const ReceiverSession* session_, ReceiverSession::ConfiguredReceivers cr) { - EXPECT_TRUE(cr.audio); - EXPECT_EQ(cr.audio.value().receiver_config.sender_ssrc, 19088747u); - EXPECT_EQ(cr.audio.value().receiver_config.receiver_ssrc, 19088748u); - EXPECT_EQ(cr.audio.value().receiver_config.channels, 2); - EXPECT_EQ(cr.audio.value().receiver_config.rtp_timebase, 48000); - - EXPECT_TRUE(cr.video); - // We should have chosen vp9 - EXPECT_EQ(cr.video.value().receiver_config.sender_ssrc, 19088743u); - EXPECT_EQ(cr.video.value().receiver_config.receiver_ssrc, 19088744u); - EXPECT_EQ(cr.video.value().receiver_config.channels, 1); - EXPECT_EQ(cr.video.value().receiver_config.rtp_timebase, 90000); + EXPECT_TRUE(cr.audio_receiver); + EXPECT_EQ(cr.audio_receiver->config().sender_ssrc, 19088747u); + EXPECT_EQ(cr.audio_receiver->config().receiver_ssrc, 19088748u); + EXPECT_EQ(cr.audio_receiver->config().channels, 2); + EXPECT_EQ(cr.audio_receiver->config().rtp_timebase, 48000); + EXPECT_EQ(cr.audio_config.codec, AudioCodec::kOpus); + + EXPECT_TRUE(cr.video_receiver); + EXPECT_EQ(cr.video_receiver->config().sender_ssrc, 19088743u); + EXPECT_EQ(cr.video_receiver->config().receiver_ssrc, 19088744u); + EXPECT_EQ(cr.video_receiver->config().channels, 1); + EXPECT_EQ(cr.video_receiver->config().rtp_timebase, 90000); + EXPECT_EQ(cr.video_config.codec, VideoCodec::kVp9); }); EXPECT_CALL(client_, OnReceiversDestroying( &session, ReceiverSession::Client::kEndOfSession)); @@ -433,12 +410,11 @@ TEST_F(ReceiverSessionTest, CanNegotiateWithCustomConstraints) { absl::optional<AspectRatio>(AspectRatio{16, 9}), absl::optional<AspectRatioConstraint>(AspectRatioConstraint::kFixed)}); - ReceiverSession session( - &client_, environment_.get(), message_port_.get(), - ReceiverSession::Preferences{{ReceiverSession::VideoCodec::kVp9}, - {ReceiverSession::AudioCodec::kOpus}, - std::move(constraints), - std::move(display)}); + ReceiverSession session(&client_, environment_.get(), message_port_.get(), + ReceiverSession::Preferences{{VideoCodec::kVp9}, + {AudioCodec::kOpus}, + std::move(constraints), + std::move(display)}); InSequence s; EXPECT_CALL(client_, OnNegotiated(&session, _)); @@ -564,27 +540,46 @@ TEST_F(ReceiverSessionTest, HandlesNoValidStreams) { TEST_F(ReceiverSessionTest, HandlesMalformedOffer) { // Note that unlike when we simply don't select any streams, when the offer - // is actually completely invalid we call OnError. - EXPECT_CALL(client_, - OnError(session_.get(), Error(Error::Code::kJsonParseError))); + // is not valid JSON we actually have no way of knowing it's an offer at all, + // so we call OnError and do not reply with an Answer. + EXPECT_CALL(client_, OnError(session_.get(), _)); message_port_->ReceiveMessage(kInvalidJsonOfferMessage); } +TEST_F(ReceiverSessionTest, HandlesMissingSeqNumInOffer) { + // If the OFFER is missing a sequence number it gets rejected before being + // parsed as an OFFER, since the sender expects all messages to come back + // with a sequence number. + message_port_->ReceiveMessage(kMissingSeqNumOfferMessage); +} + +TEST_F(ReceiverSessionTest, HandlesOfferMissingMandatoryFields) { + // If the OFFER is missing mandatory fields, we notify the client as well as + // reply with an error-case Answer. + EXPECT_CALL(client_, OnError(session_.get(), _)); + + message_port_->ReceiveMessage(kMissingMandatoryFieldOfferMessage); + const auto& messages = message_port_->posted_messages(); + EXPECT_EQ(1u, messages.size()); + + auto message_body = json::Parse(messages[0]); + ExpectIsErrorAnswerMessage(message_body); +} + TEST_F(ReceiverSessionTest, HandlesImproperlyFormattedOffer) { - EXPECT_CALL(client_, - OnError(session_.get(), - Error(Error::Code::kJsonParseError, - "Failed to parse supported streams in offer"))); + EXPECT_CALL(client_, OnError(session_.get(), _)); message_port_->ReceiveMessage(kValidJsonInvalidFormatOfferMessage); + const auto& messages = message_port_->posted_messages(); + EXPECT_EQ(1u, messages.size()); + + auto message_body = json::Parse(messages[0]); + ExpectIsErrorAnswerMessage(message_body); } TEST_F(ReceiverSessionTest, HandlesNullOffer) { - EXPECT_CALL(client_, OnError(session_.get(), - Error(Error::Code::kJsonParseError, - "Received offer missing offer body"))); - + EXPECT_CALL(client_, OnError(session_.get(), _)); message_port_->ReceiveMessage(kNullJsonOfferMessage); } diff --git a/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.cc b/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.cc index 215b9cfcec0..d64773d5c34 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.cc @@ -7,6 +7,14 @@ namespace openscreen { namespace cast { +RtpPayloadType GetPayloadType(AudioCodec codec) { + return RtpPayloadType::kAudioHackForAndroidTV; +} + +RtpPayloadType GetPayloadType(VideoCodec codec) { + return RtpPayloadType::kVideoHackForAndroidTV; +} + bool IsRtpPayloadType(uint8_t raw_byte) { switch (static_cast<RtpPayloadType>(raw_byte)) { case RtpPayloadType::kAudioOpus: diff --git a/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.h b/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.h index 335c06a04d1..82c91c31a7d 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.h +++ b/chromium/third_party/openscreen/src/cast/streaming/rtp_defines.h @@ -7,6 +7,8 @@ #include <stdint.h> +#include "cast/streaming/constants.h" + namespace openscreen { namespace cast { @@ -96,10 +98,21 @@ enum class RtpPayloadType : uint8_t { // video to be 96; regardless of the codecs actually being used. This is // definitely out-of-spec, and inconsistent with the audio versus video range // of values, but must be taken into account for backwards-compatibility. + // TODO(crbug.com/1127978): RTP payload types need to represent actual type, + // as well as have options for new codecs like VP9. kAudioHackForAndroidTV = 127, kVideoHackForAndroidTV = 96, }; +// NOTE: currently we match the legacy Chrome sender's behavior of always +// sending the audio and video hacks for AndroidTV, however we should migrate +// to using proper rtp payload types. New payload types for new codecs, such +// as VP9, should also be defined. +// TODO(crbug.com/1127978): RTP payload types need to represent actual type, +// as well as have options for new codecs like VP9. +RtpPayloadType GetPayloadType(AudioCodec codec); +RtpPayloadType GetPayloadType(VideoCodec codec); + // Returns true if the |raw_byte| can be type-casted to a RtpPayloadType, and is // also not RtpPayloadType::kNull. The caller should mask the byte, to select // the lower 7 bits, if applicable. diff --git a/chromium/third_party/openscreen/src/cast/streaming/rtp_packetizer_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/rtp_packetizer_unittest.cc index 8b4710659d6..1c3cd97a481 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/rtp_packetizer_unittest.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/rtp_packetizer_unittest.cc @@ -14,6 +14,7 @@ #include "cast/streaming/ssrc.h" #include "gtest/gtest.h" #include "util/chrono_helpers.h" +#include "util/crypto/random_bytes.h" namespace openscreen { namespace cast { @@ -126,8 +127,7 @@ class RtpPacketizerTest : public testing::Test { // The RtpPacketizer instance under test, plus some surrounding dependencies // to generate its input and examine its output. const Ssrc ssrc_{GenerateSsrc(true)}; - const FrameCrypto crypto_{FrameCrypto::GenerateRandomBytes(), - FrameCrypto::GenerateRandomBytes()}; + const FrameCrypto crypto_{GenerateRandomBytes16(), GenerateRandomBytes16()}; RtpPacketizer packetizer_{kPayloadType, ssrc_, kMaxRtpPacketSizeForIpv4UdpOnEthernet}; RtpPacketParser parser_{ssrc_}; diff --git a/chromium/third_party/openscreen/src/cast/streaming/sender.cc b/chromium/third_party/openscreen/src/cast/streaming/sender.cc index 3713a199683..dcb2f04b12b 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/sender.cc +++ b/chromium/third_party/openscreen/src/cast/streaming/sender.cc @@ -20,9 +20,10 @@ using openscreen::operator<<; // For std::chrono::duration logging. Sender::Sender(Environment* environment, SenderPacketRouter* packet_router, - const SessionConfig& config, + SessionConfig config, RtpPayloadType rtp_payload_type) - : packet_router_(packet_router), + : config_(config), + packet_router_(packet_router), rtcp_session_(config.sender_ssrc, config.receiver_ssrc, environment->now()), diff --git a/chromium/third_party/openscreen/src/cast/streaming/sender.h b/chromium/third_party/openscreen/src/cast/streaming/sender.h index 33ea5227318..7e82711729b 100644 --- a/chromium/third_party/openscreen/src/cast/streaming/sender.h +++ b/chromium/third_party/openscreen/src/cast/streaming/sender.h @@ -21,6 +21,7 @@ #include "cast/streaming/rtp_time.h" #include "cast/streaming/sender_packet_router.h" #include "cast/streaming/sender_report_builder.h" +#include "cast/streaming/session_config.h" #include "platform/api/time.h" #include "util/yet_another_bit_vector.h" @@ -28,7 +29,6 @@ namespace openscreen { namespace cast { class Environment; -struct SessionConfig; // The Cast Streaming Sender, a peer corresponding to some Cast Streaming // Receiver at the other end of a network link. See class level comments for @@ -116,11 +116,12 @@ class Sender final : public SenderPacketRouter::Sender, // Sender. It is simply passed along to a Receiver in the RTP packet stream. Sender(Environment* environment, SenderPacketRouter* packet_router, - const SessionConfig& config, + SessionConfig config, RtpPayloadType rtp_payload_type); ~Sender() final; + const SessionConfig& config() const { return config_; } Ssrc ssrc() const { return rtcp_session_.sender_ssrc(); } int rtp_timebase() const { return rtp_timebase_; } @@ -251,6 +252,7 @@ class Sender final : public SenderPacketRouter::Sender, pending_frames_.size()]; } + const SessionConfig config_; SenderPacketRouter* const packet_router_; RtcpSession rtcp_session_; CompoundRtcpParser rtcp_parser_; diff --git a/chromium/third_party/openscreen/src/cast/streaming/sender_session.cc b/chromium/third_party/openscreen/src/cast/streaming/sender_session.cc new file mode 100644 index 00000000000..897e7560aeb --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/sender_session.cc @@ -0,0 +1,397 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/streaming/sender_session.h" + +#include <openssl/rand.h> +#include <stdint.h> + +#include <algorithm> +#include <chrono> +#include <iterator> +#include <limits> +#include <random> +#include <string> +#include <utility> + +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "cast/common/public/message_port.h" +#include "cast/streaming/capture_recommendations.h" +#include "cast/streaming/environment.h" +#include "cast/streaming/message_fields.h" +#include "cast/streaming/offer_messages.h" +#include "cast/streaming/sender.h" +#include "util/crypto/random_bytes.h" +#include "util/json/json_helpers.h" +#include "util/json/json_serialization.h" +#include "util/osp_logging.h" + +namespace openscreen { +namespace cast { + +namespace { + +AudioStream CreateStream(int index, const AudioCaptureConfig& config) { + return AudioStream{ + Stream{index, + Stream::Type::kAudioSource, + config.channels, + CodecToString(config.codec), + GetPayloadType(config.codec), + GenerateSsrc(true /*high_priority*/), + config.target_playout_delay, + GenerateRandomBytes16(), + GenerateRandomBytes16(), + false /* receiver_rtcp_event_log */, + {} /* receiver_rtcp_dscp */, + config.sample_rate}, + (config.bit_rate >= capture_recommendations::kDefaultAudioMinBitRate) + ? config.bit_rate + : capture_recommendations::kDefaultAudioMaxBitRate}; +} + +Resolution ToResolution(const DisplayResolution& display_resolution) { + return Resolution{display_resolution.width, display_resolution.height}; +} + +VideoStream CreateStream(int index, const VideoCaptureConfig& config) { + std::vector<Resolution> resolutions; + std::transform(config.resolutions.begin(), config.resolutions.end(), + std::back_inserter(resolutions), ToResolution); + + constexpr int kVideoStreamChannelCount = 1; + return VideoStream{ + Stream{index, + Stream::Type::kVideoSource, + kVideoStreamChannelCount, + CodecToString(config.codec), + GetPayloadType(config.codec), + GenerateSsrc(false /*high_priority*/), + config.target_playout_delay, + GenerateRandomBytes16(), + GenerateRandomBytes16(), + false /* receiver_rtcp_event_log */, + {} /* receiver_rtcp_dscp */, + kRtpVideoTimebase}, + SimpleFraction{config.max_frame_rate.numerator, + config.max_frame_rate.denominator}, + (config.max_bit_rate > + capture_recommendations::kDefaultVideoBitRateLimits.minimum) + ? config.max_bit_rate + : capture_recommendations::kDefaultVideoBitRateLimits.maximum, + {}, // protection + {}, // profile + {}, // protection + std::move(resolutions), + {} /* error_recovery mode, always "castv2" */ + }; +} + +template <typename S, typename C> +void CreateStreamList(int offset_index, + const std::vector<C>& configs, + std::vector<S>* out) { + out->reserve(configs.size()); + for (size_t i = 0; i < configs.size(); ++i) { + out->emplace_back(CreateStream(i + offset_index, configs[i])); + } +} + +Offer CreateOffer(const std::vector<AudioCaptureConfig>& audio_configs, + const std::vector<VideoCaptureConfig>& video_configs) { + Offer offer{ + {CastMode::Type::kMirroring}, + false /* supports_wifi_status_reporting */, + {} /* audio_streams */, + {} /* video_streams */ + }; + + // NOTE here: IDs will always follow the pattern: + // [0.. audio streams... N - 1][N.. video streams.. K] + CreateStreamList(0, audio_configs, &offer.audio_streams); + CreateStreamList(audio_configs.size(), video_configs, &offer.video_streams); + + return offer; +} + +bool IsValidAudioCaptureConfig(const AudioCaptureConfig& config) { + return config.channels >= 1 && config.bit_rate >= 0; +} + +bool IsValidResolution(const DisplayResolution& resolution) { + return resolution.width > kMinVideoWidth && + resolution.height > kMinVideoHeight; +} + +bool IsValidVideoCaptureConfig(const VideoCaptureConfig& config) { + return config.max_frame_rate.numerator > 0 && + config.max_frame_rate.denominator > 0 && + ((config.max_bit_rate == 0) || + (config.max_bit_rate >= + capture_recommendations::kDefaultVideoBitRateLimits.minimum)) && + !config.resolutions.empty() && + std::all_of(config.resolutions.begin(), config.resolutions.end(), + IsValidResolution); +} + +bool AreAllValid(const std::vector<AudioCaptureConfig>& audio_configs, + const std::vector<VideoCaptureConfig>& video_configs) { + return std::all_of(audio_configs.begin(), audio_configs.end(), + IsValidAudioCaptureConfig) && + std::all_of(video_configs.begin(), video_configs.end(), + IsValidVideoCaptureConfig); +} + +int GenerateSessionId() { + static auto& rd = *new std::random_device(); + static auto& gen = *new std::mt19937(rd()); + static auto& dist = + *new std::uniform_int_distribution<>(1, std::numeric_limits<int>::max()); + + return dist(gen); +} +} // namespace + +SenderSession::Client::~Client() = default; + +SenderSession::SenderSession(IPAddress remote_address, + Client* const client, + Environment* environment, + MessagePort* message_port) + : session_id_(GenerateSessionId()), + remote_address_(remote_address), + client_(client), + environment_(environment), + message_port_(message_port), + packet_router_(environment_) { + OSP_DCHECK(session_id_ > 0); + OSP_DCHECK(client_); + OSP_DCHECK(message_port_); + OSP_DCHECK(environment_); + + message_port_->SetClient(this, "sender-" + std::to_string(session_id_)); +} + +SenderSession::~SenderSession() { + message_port_->ResetClient(); +} + +Error SenderSession::Negotiate(std::vector<AudioCaptureConfig> audio_configs, + std::vector<VideoCaptureConfig> video_configs) { + // Negotiating with no streams doesn't make any sense. + if (audio_configs.empty() && video_configs.empty()) { + return Error(Error::Code::kParameterInvalid, + "Need at least one audio or video config to negotiate."); + } + if (!AreAllValid(audio_configs, video_configs)) { + return Error(Error::Code::kParameterInvalid, "Invalid configs provided."); + } + + Offer offer = CreateOffer(audio_configs, video_configs); + ErrorOr<Json::Value> json_offer = offer.ToJson(); + if (json_offer.is_error()) { + return std::move(json_offer.error()); + } + + current_negotiation_ = std::unique_ptr<Negotiation>(new Negotiation{ + std::move(offer), std::move(audio_configs), std::move(video_configs)}); + + Json::Value message_body; + message_body[kMessageType] = kMessageTypeOffer; + message_body[kOfferMessageBody] = std::move(json_offer.value()); + + Message message; + // Currently we don't have a way to discover the ID of the receiver we + // are connected to, since we have to send the first message. + // TODO(jophba): migrate to discovered receiver ID when available. + message.sender_id = kDefaultStreamingReceiverSenderId; + message.message_namespace = kCastWebrtcNamespace; + message.body = std::move(message_body); + SendMessage(&message); + return Error::None(); +} + +void SenderSession::OnMessage(const std::string& sender_id, + const std::string& message_namespace, + const std::string& message) { + ErrorOr<Json::Value> message_json = json::Parse(message); + if (!message_json) { + OSP_DLOG_WARN << "Received an invalid message: " << message + << ", dropping."; + return; + } + + std::string key; + if (!json::ParseAndValidateString(message_json.value()[kKeyType], &key)) { + OSP_DLOG_WARN << "Received message with invalid message key, dropping."; + return; + } + + if (receiver_sender_id_.empty()) { + receiver_sender_id_ = sender_id; + } else if (receiver_sender_id_ != sender_id) { + OSP_DLOG_WARN << "Received message from unknown sender ID: " << sender_id + << ", dropping."; + return; + } + + OSP_DVLOG << "Received a message: " << message; + if (key == kMessageTypeAnswer) { + if (message_namespace != kCastWebrtcNamespace) { + OSP_DLOG_INFO << "Received answer from invalid namespace: " + << message_namespace; + return; + } + if (!current_negotiation_) { + OSP_DLOG_INFO << "Received answer but not currently negotiating."; + return; + } + + int sequence_number; + if (!json::ParseAndValidateInt(message_json.value()[kSequenceNumber], + &sequence_number)) { + OSP_DLOG_WARN << "Received invalid message sequence number, dropping."; + return; + } + + if (sequence_number != current_sequence_number_) { + OSP_DLOG_WARN << "Received a stale answer message, dropping."; + return; + } else if (sequence_number > current_sequence_number_) { + OSP_DLOG_WARN + << "Received an answer with an unexpected sequence number, dropping."; + return; + } + + const Json::Value body = + std::move(message_json.value()[kAnswerMessageBody]); + if (body.isObject()) { + OnAnswer(body); + } else { + client_->OnError( + this, Error(Error::Code::kJsonParseError, "Failed to parse answer")); + OSP_DLOG_WARN + << "Received message with invalid answer message body, dropping."; + } + } + current_negotiation_.reset(); +} + +void SenderSession::OnError(Error error) { + OSP_DLOG_WARN << "SenderSession message port error: " << error; +} + +void SenderSession::OnAnswer(const Json::Value& message_body) { + Answer answer; + if (!Answer::ParseAndValidate(message_body, &answer)) { + client_->OnError(this, Error(Error::Code::kJsonParseError, + "Received invalid answer message")); + OSP_DLOG_WARN << "Received invalid answer message"; + return; + } + + ConfiguredSenders senders = SpawnSenders(answer); + + // If we didn't select any senders, the negotiation was unsuccessful. + if (senders.audio_sender == nullptr && senders.video_sender == nullptr) { + return; + } + client_->OnNegotiated(this, std::move(senders), + capture_recommendations::GetRecommendations(answer)); +} + +std::unique_ptr<Sender> SenderSession::CreateSender(Ssrc receiver_ssrc, + const Stream& stream, + RtpPayloadType type) { + SessionConfig config{ + stream.ssrc, receiver_ssrc, stream.rtp_timebase, stream.channels, + stream.target_delay, stream.aes_key, stream.aes_iv_mask}; + + return std::make_unique<Sender>(environment_, &packet_router_, + std::move(config), type); +} + +void SenderSession::SpawnAudioSender(ConfiguredSenders* senders, + Ssrc receiver_ssrc, + int send_index, + int config_index) { + const AudioCaptureConfig& config = + current_negotiation_->audio_configs[config_index]; + const RtpPayloadType payload_type = GetPayloadType(config.codec); + for (const AudioStream& stream : current_negotiation_->offer.audio_streams) { + if (stream.stream.index == send_index) { + current_audio_sender_ = + CreateSender(receiver_ssrc, stream.stream, payload_type); + senders->audio_sender = current_audio_sender_.get(); + senders->audio_config = config; + break; + } + } +} + +void SenderSession::SpawnVideoSender(ConfiguredSenders* senders, + Ssrc receiver_ssrc, + int send_index, + int config_index) { + const VideoCaptureConfig& config = + current_negotiation_->video_configs[config_index]; + const RtpPayloadType payload_type = GetPayloadType(config.codec); + for (const VideoStream& stream : current_negotiation_->offer.video_streams) { + if (stream.stream.index == send_index) { + current_video_sender_ = + CreateSender(receiver_ssrc, stream.stream, payload_type); + senders->video_sender = current_video_sender_.get(); + senders->video_config = config; + break; + } + } +} + +SenderSession::ConfiguredSenders SenderSession::SpawnSenders( + const Answer& answer) { + OSP_DCHECK(current_negotiation_); + + // Although we already have a message port set up with the TLS + // address of the receiver, we don't know where to send the seperate UDP + // stream until we get the ANSWER message here. + environment_->set_remote_endpoint( + IPEndpoint{remote_address_, static_cast<uint16_t>(answer.udp_port)}); + + ConfiguredSenders senders; + for (size_t i = 0; i < answer.send_indexes.size(); ++i) { + const Ssrc receiver_ssrc = answer.ssrcs[i]; + const size_t send_index = static_cast<size_t>(answer.send_indexes[i]); + + const auto audio_size = current_negotiation_->audio_configs.size(); + const auto video_size = current_negotiation_->video_configs.size(); + if (send_index < audio_size) { + SpawnAudioSender(&senders, receiver_ssrc, send_index, send_index); + } else if (send_index < (audio_size + video_size)) { + SpawnVideoSender(&senders, receiver_ssrc, send_index, + send_index - audio_size); + } + } + return senders; +} + +void SenderSession::SendMessage(Message* message) { + message->body[kSequenceNumber] = ++current_sequence_number_; + + auto body_or_error = json::Stringify(message->body); + if (body_or_error.is_value()) { + OSP_DVLOG << "Sending message: SENDER[" << message->sender_id + << "], NAMESPACE[" << message->message_namespace << "], BODY:\n" + << body_or_error.value(); + message_port_->PostMessage(message->sender_id, message->message_namespace, + body_or_error.value()); + } else { + OSP_DLOG_WARN << "Sending message failed with error:\n" + << body_or_error.error(); + client_->OnError(this, body_or_error.error()); + } +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/sender_session.h b/chromium/third_party/openscreen/src/cast/streaming/sender_session.h new file mode 100644 index 00000000000..a38ac422c21 --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/sender_session.h @@ -0,0 +1,179 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STREAMING_SENDER_SESSION_H_ +#define CAST_STREAMING_SENDER_SESSION_H_ + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "cast/common/public/message_port.h" +#include "cast/streaming/answer_messages.h" +#include "cast/streaming/capture_configs.h" +#include "cast/streaming/offer_messages.h" +#include "cast/streaming/sender.h" +#include "cast/streaming/sender_packet_router.h" +#include "cast/streaming/session_config.h" +#include "json/value.h" +#include "util/json/json_serialization.h" + +namespace openscreen { +namespace cast { + +namespace capture_recommendations { +struct Recommendations; +} + +class Environment; +class Sender; + +class SenderSession final : public MessagePort::Client { + public: + // Upon successful negotiation, a set of configured senders is constructed + // for handling audio and video. Note that either sender may be null. + struct ConfiguredSenders { + // In practice, we may have 0, 1, or 2 senders configured, depending + // on if the device supports audio and video, and if we were able to + // successfully negotiate a sender configuration. + + // If the sender is audio- or video-only, either of the senders + // may be nullptr. However, in the majority of cases they will be populated. + Sender* audio_sender; + AudioCaptureConfig audio_config; + + Sender* video_sender; + VideoCaptureConfig video_config; + }; + + // The embedder should provide a client for handling the negotiation. + // When the negotiation is complete, the OnNegotiated callback is called. + class Client { + public: + // Called when a new set of senders has been negotiated. This may be + // called multiple times during a session, once for every time Negotiate() + // is called on the SenderSession object. The negotation call also includes + // capture recommendations that can be used by the sender to provide + // an optimal video stream for the receiver. + virtual void OnNegotiated( + const SenderSession* session, + ConfiguredSenders senders, + capture_recommendations::Recommendations capture_recommendations) = 0; + + // Called whenever an error occurs. Ends the ongoing session, and the caller + // must call Negotiate() again if they wish to re-establish streaming. + virtual void OnError(const SenderSession* session, Error error) = 0; + + protected: + virtual ~Client(); + }; + + // The SenderSession assumes that the passed in client, environment, and + // message port persist for at least the lifetime of the SenderSession. If + // one of these classes needs to be reset, a new SenderSession should be + // created. + SenderSession(IPAddress remote_address, + Client* const client, + Environment* environment, + MessagePort* message_port); + SenderSession(const SenderSession&) = delete; + SenderSession(SenderSession&&) = delete; + SenderSession& operator=(const SenderSession&) = delete; + SenderSession& operator=(SenderSession&&) = delete; + ~SenderSession(); + + // Starts an OFFER/ANSWER exchange with the already configured receiver + // over the message port. The caller should assume any configured senders + // become invalid when calling this method. + Error Negotiate(std::vector<AudioCaptureConfig> audio_configs, + std::vector<VideoCaptureConfig> video_configs); + + // MessagePort::Client overrides + void OnMessage(const std::string& sender_id, + const std::string& message_namespace, + const std::string& message) override; + void OnError(Error error) override; + + private: + struct Message { + std::string sender_id; + std::string message_namespace; + int sequence_number = 0; + Json::Value body; + }; + + // We store the current negotiation, so that when we get an answer from the + // receiver we can line up the selected streams with the original + // configuration. + struct Negotiation { + Offer offer; + + std::vector<AudioCaptureConfig> audio_configs; + std::vector<VideoCaptureConfig> video_configs; + }; + + // Specific message type handler methods. + void OnAnswer(const Json::Value& message_body); + + // Used by SpawnSenders to generate a sender for a specific stream. + std::unique_ptr<Sender> CreateSender(Ssrc receiver_ssrc, + const Stream& stream, + RtpPayloadType type); + + // Helper methods for spawning specific senders from the Answer message. + void SpawnAudioSender(ConfiguredSenders* senders, + Ssrc receiver_ssrc, + int send_index, + int config_index); + void SpawnVideoSender(ConfiguredSenders* senders, + Ssrc receiver_ssrc, + int send_index, + int config_index); + + // Spawn a set of configured senders from the currently stored negotiation. + ConfiguredSenders SpawnSenders(const Answer& answer); + + // Sends a message over the message port. + void SendMessage(Message* message); + + // The cast session ID for this session. + const int session_id_; + + // The sender ID of the Receiver for this session. + std::string receiver_sender_id_; + + // The remote address of the receiver we are communicating with. Used + // for both TLS and UDP traffic. + const IPAddress remote_address_; + + // The embedder is expected to provide us a client for notifications about + // negotiations and errors, a valid cast environment, and a messaging + // port for communicating to the Receiver over TLS. + Client* const client_; + Environment* const environment_; + MessagePort* const message_port_; + + // The packet router used for messaging across all senders. + SenderPacketRouter packet_router_; + + // Each negotiation has its own sequence number, and the receiver replies + // with the same sequence number that we send. Each message to the receiver + // advances our current sequence number. + int current_sequence_number_ = 0; + + // The current negotiation. If present, we are expected an ANSWER from + // the receiver. If not present, any provided ANSWERS are rejected. + std::unique_ptr<Negotiation> current_negotiation_; + + // If the negotiation has succeeded, we store the current audio and video + // senders used for this session. Either or both may be nullptr. + std::unique_ptr<Sender> current_audio_sender_; + std::unique_ptr<Sender> current_video_sender_; +}; // namespace cast + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STREAMING_SENDER_SESSION_H_ diff --git a/chromium/third_party/openscreen/src/cast/streaming/sender_session_unittest.cc b/chromium/third_party/openscreen/src/cast/streaming/sender_session_unittest.cc new file mode 100644 index 00000000000..7fd428bed0b --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/sender_session_unittest.cc @@ -0,0 +1,421 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cast/streaming/sender_session.h" + +#include <cstdio> +#include <utility> + +#include "cast/streaming/capture_configs.h" +#include "cast/streaming/capture_recommendations.h" +#include "cast/streaming/mock_environment.h" +#include "cast/streaming/testing/simple_message_port.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "platform/base/ip_address.h" +#include "platform/test/fake_clock.h" +#include "platform/test/fake_task_runner.h" +#include "util/chrono_helpers.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrictMock; + +namespace openscreen { +namespace cast { + +namespace { +constexpr char kMalformedAnswerMessage[] = R"({ + "type": "ANSWER", + "seqNum": 1, + "answer": { + "castMode": "mirroring", + "udpPort": 1234, + "sendIndexes": [1, 3], + "ssrcs": [1, 2] +})"; + +constexpr char kValidJsonInvalidFormatAnswerMessage[] = R"({ + "type": "ANSWER", + "seqNum": 1, + "answer-2": { + "castMode": "mirroring", + "udpPort": 1234, + "sendIndexes": [1, 3], + "ssrcs": [1, 2] + } +})"; + +constexpr char kValidJsonInvalidAnswerMessage[] = R"({ + "type": "ANSWER", + "seqNum": 1, + "answer": { + "castMode": "mirroring", + "udpPort": -1234, + "sendIndexes": [1, 3], + "ssrcs": [1, 2] + } +})"; + +constexpr char kMissingAnswerMessage[] = R"({ + "type": "ANSWER", + "seqNum": 1 +})"; + +constexpr char kInvalidSequenceNumberMessage[] = R"({ + "type": "ANSWER", + "seqNum": "not actually a number" +})"; + +constexpr char kUnknownTypeMessage[] = R"({ + "type": "ANSWER_VERSION_2", + "seqNum": 1 +})"; + +constexpr char kInvalidTypeMessage[] = R"({ + "type": 39, + "seqNum": 1 +})"; + +const AudioCaptureConfig kAudioCaptureConfigInvalidChannels{ + AudioCodec::kAac, -1 /* channels */, 44000 /* bit_rate */, + 96000 /* sample_rate */ +}; + +const AudioCaptureConfig kAudioCaptureConfigValid{ + AudioCodec::kOpus, 5 /* channels */, 32000 /* bit_rate */, + 44000 /* sample_rate */ +}; + +const VideoCaptureConfig kVideoCaptureConfigMissingResolutions{ + VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */, + std::vector<DisplayResolution>{}}; + +const VideoCaptureConfig kVideoCaptureConfigInvalid{ + VideoCodec::kHevc, FrameRate{60, 1}, -300000 /* max_bit_rate */, + std::vector<DisplayResolution>{DisplayResolution{1920, 1080}, + DisplayResolution{1280, 720}}}; + +const VideoCaptureConfig kVideoCaptureConfigValid{ + VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */, + std::vector<DisplayResolution>{DisplayResolution{1280, 720}, + DisplayResolution{1920, 1080}}}; + +const VideoCaptureConfig kVideoCaptureConfigValidSimplest{ + VideoCodec::kHevc, FrameRate{60, 1}, 300000 /* max_bit_rate */, + std::vector<DisplayResolution>{DisplayResolution{1920, 1080}}}; + +class FakeClient : public SenderSession::Client { + public: + MOCK_METHOD(void, + OnNegotiated, + (const SenderSession*, + SenderSession::ConfiguredSenders, + capture_recommendations::Recommendations), + (override)); + MOCK_METHOD(void, OnError, (const SenderSession*, Error error), (override)); +}; + +} // namespace + +class SenderSessionTest : public ::testing::Test { + public: + SenderSessionTest() : clock_(Clock::time_point{}), task_runner_(&clock_) {} + + std::unique_ptr<MockEnvironment> MakeEnvironment() { + auto environment_ = std::make_unique<NiceMock<MockEnvironment>>( + &FakeClock::now, &task_runner_); + ON_CALL(*environment_, GetBoundLocalEndpoint()) + .WillByDefault( + Return(IPEndpoint{IPAddress::Parse("127.0.0.1").value(), 12345})); + return environment_; + } + + void SetUp() { + message_port_ = std::make_unique<SimpleMessagePort>(); + environment_ = MakeEnvironment(); + session_ = std::make_unique<SenderSession>(IPAddress::kV4LoopbackAddress(), + &client_, environment_.get(), + message_port_.get()); + } + + protected: + StrictMock<FakeClient> client_; + FakeClock clock_; + std::unique_ptr<MockEnvironment> environment_; + std::unique_ptr<SimpleMessagePort> message_port_; + std::unique_ptr<SenderSession> session_; + FakeTaskRunner task_runner_; +}; + +TEST_F(SenderSessionTest, RegistersSelfOnMessagePort) { + EXPECT_EQ(message_port_->client(), session_.get()); +} + +TEST_F(SenderSessionTest, ComplainsIfNoConfigsToOffer) { + const Error error = session_->Negotiate(std::vector<AudioCaptureConfig>{}, + std::vector<VideoCaptureConfig>{}); + + EXPECT_EQ(error, + Error(Error::Code::kParameterInvalid, + "Need at least one audio or video config to negotiate.")); +} + +TEST_F(SenderSessionTest, ComplainsIfInvalidAudioCaptureConfig) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigInvalidChannels}, + std::vector<VideoCaptureConfig>{}); + + EXPECT_EQ(error, + Error(Error::Code::kParameterInvalid, "Invalid configs provided.")); +} + +TEST_F(SenderSessionTest, ComplainsIfInvalidVideoCaptureConfig) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigInvalid}); + EXPECT_EQ(error, + Error(Error::Code::kParameterInvalid, "Invalid configs provided.")); +} + +TEST_F(SenderSessionTest, ComplainsIfMissingResolutions) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigMissingResolutions}); + EXPECT_EQ(error, + Error(Error::Code::kParameterInvalid, "Invalid configs provided.")); +} + +TEST_F(SenderSessionTest, SendsOfferWithZeroBitrateOptions) { + VideoCaptureConfig video_config = kVideoCaptureConfigValid; + video_config.max_bit_rate = 0; + AudioCaptureConfig audio_config = kAudioCaptureConfigValid; + audio_config.bit_rate = 0; + + const Error error = + session_->Negotiate(std::vector<AudioCaptureConfig>{audio_config}, + std::vector<VideoCaptureConfig>{video_config}); + EXPECT_TRUE(error.ok()); + + const auto& messages = message_port_->posted_messages(); + ASSERT_EQ(1u, messages.size()); + auto message_body = json::Parse(messages[0]); + ASSERT_TRUE(message_body.is_value()); + const Json::Value offer = std::move(message_body.value()); + EXPECT_EQ("OFFER", offer["type"].asString()); +} + +TEST_F(SenderSessionTest, SendsOfferWithSimpleVideoOnly) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + EXPECT_TRUE(error.ok()); + + const auto& messages = message_port_->posted_messages(); + ASSERT_EQ(1u, messages.size()); + auto message_body = json::Parse(messages[0]); + ASSERT_TRUE(message_body.is_value()); + const Json::Value offer = std::move(message_body.value()); + EXPECT_EQ("OFFER", offer["type"].asString()); +} + +TEST_F(SenderSessionTest, SendsOfferAudioOnly) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{}); + EXPECT_TRUE(error.ok()); + + const auto& messages = message_port_->posted_messages(); + ASSERT_EQ(1u, messages.size()); + auto message_body = json::Parse(messages[0]); + ASSERT_TRUE(message_body.is_value()); + const Json::Value offer = std::move(message_body.value()); + EXPECT_EQ("OFFER", offer["type"].asString()); +} + +TEST_F(SenderSessionTest, SendsOfferMessage) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + const auto& messages = message_port_->posted_messages(); + ASSERT_EQ(1u, messages.size()); + + auto message_body = json::Parse(messages[0]); + ASSERT_TRUE(message_body.is_value()); + const Json::Value offer = std::move(message_body.value()); + EXPECT_EQ("OFFER", offer["type"].asString()); + EXPECT_LT(0, offer["seqNum"].asInt()); + + const Json::Value& offer_body = offer["offer"]; + ASSERT_FALSE(offer_body.isNull()); + ASSERT_TRUE(offer_body.isObject()); + EXPECT_EQ("mirroring", offer_body["castMode"].asString()); + EXPECT_EQ(false, offer_body["receiverGetStatus"].asBool()); + + const Json::Value& streams = offer_body["supportedStreams"]; + EXPECT_TRUE(streams.isArray()); + EXPECT_EQ(2u, streams.size()); + + const Json::Value& audio_stream = streams[0]; + EXPECT_EQ("opus", audio_stream["codecName"].asString()); + EXPECT_EQ(0, audio_stream["index"].asInt()); + EXPECT_EQ(32u, audio_stream["aesKey"].asString().length()); + EXPECT_EQ(32u, audio_stream["aesIvMask"].asString().length()); + EXPECT_EQ(5, audio_stream["channels"].asInt()); + EXPECT_LT(0u, audio_stream["ssrc"].asUInt()); + EXPECT_EQ(127, audio_stream["rtpPayloadType"].asInt()); + + const Json::Value& video_stream = streams[1]; + EXPECT_EQ("hevc", video_stream["codecName"].asString()); + EXPECT_EQ(1, video_stream["index"].asInt()); + EXPECT_EQ(32u, video_stream["aesKey"].asString().length()); + EXPECT_EQ(32u, video_stream["aesIvMask"].asString().length()); + EXPECT_EQ(1, video_stream["channels"].asInt()); + EXPECT_LT(0u, video_stream["ssrc"].asUInt()); + EXPECT_EQ(96, video_stream["rtpPayloadType"].asInt()); +} + +TEST_F(SenderSessionTest, HandlesValidAnswer) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + ASSERT_TRUE(error.ok()); + + const auto& messages = message_port_->posted_messages(); + ASSERT_EQ(1u, messages.size()); + auto message_body = json::Parse(messages[0]); + ASSERT_TRUE(message_body.is_value()); + const Json::Value offer = std::move(message_body.value()); + EXPECT_EQ("OFFER", offer["type"].asString()); + EXPECT_LT(0, offer["seqNum"].asInt()); + + const Json::Value& offer_body = offer["offer"]; + ASSERT_FALSE(offer_body.isNull()); + ASSERT_TRUE(offer_body.isObject()); + const Json::Value& streams = offer_body["supportedStreams"]; + EXPECT_TRUE(streams.isArray()); + EXPECT_EQ(2u, streams.size()); + + const Json::Value& audio_stream = streams[0]; + const int audio_index = audio_stream["index"].asInt(); + const int audio_ssrc = audio_stream["ssrc"].asUInt(); + + const Json::Value& video_stream = streams[1]; + const int video_index = video_stream["index"].asInt(); + const int video_ssrc = video_stream["ssrc"].asUInt(); + + constexpr size_t kAnswerSize = 512u; + char answer[kAnswerSize]; + snprintf(answer, kAnswerSize, R"({ "type": "ANSWER", + "seqNum": %d, + "answer": { + "castMode": "mirroring", + "udpPort": 1234, + "sendIndexes": [%d, %d], + "ssrcs": [%d, %d] + } + })", + offer["seqNum"].asInt(), audio_index, video_index, audio_ssrc + 1, + video_ssrc + 1); + + // We should have responded with an on negotiated call + EXPECT_CALL(client_, OnNegotiated(session_.get(), _, _)); + message_port_->ReceiveMessage(answer); +} + +TEST_F(SenderSessionTest, HandlesInvalidNamespace) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + message_port_->ReceiveMessage(kValidJsonInvalidAnswerMessage, + "random-namespace"); +} + +TEST_F(SenderSessionTest, HandlesMalformedAnswer) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + // Note that unlike when we simply don't select any streams, when the answer + // is actually malformed we have no way of knowing it was an answer at all, + // so we just drop it without error. + message_port_->ReceiveMessage(kMalformedAnswerMessage); +} + +TEST_F(SenderSessionTest, HandlesImproperlyFormattedAnswer) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + EXPECT_CALL(client_, + OnError(session_.get(), Error(Error::Code::kJsonParseError, + "Failed to parse answer"))); + message_port_->ReceiveMessage(kValidJsonInvalidFormatAnswerMessage); +} + +TEST_F(SenderSessionTest, HandlesInvalidAnswer) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + EXPECT_CALL(client_, OnError(session_.get(), + Error(Error::Code::kJsonParseError, + "Received invalid answer message"))); + message_port_->ReceiveMessage(kValidJsonInvalidAnswerMessage); +} + +TEST_F(SenderSessionTest, HandlesNullAnswer) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + EXPECT_TRUE(error.ok()); + EXPECT_CALL(client_, + OnError(session_.get(), Error(Error::Code::kJsonParseError, + "Failed to parse answer"))); + message_port_->ReceiveMessage(kMissingAnswerMessage); +} + +TEST_F(SenderSessionTest, HandlesInvalidSequenceNumber) { + const Error error = session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + // We should just discard messages with an invalid sequence number. + message_port_->ReceiveMessage(kInvalidSequenceNumberMessage); +} + +TEST_F(SenderSessionTest, HandlesUnknownTypeMessage) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + // We should just discard messages with an unknown message type. + message_port_->ReceiveMessage(kUnknownTypeMessage); +} + +TEST_F(SenderSessionTest, HandlesInvalidTypeMessage) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + // We should just discard messages with an invalid message type. + message_port_->ReceiveMessage(kInvalidTypeMessage); +} + +TEST_F(SenderSessionTest, DoesntCrashOnMessagePortError) { + session_->Negotiate( + std::vector<AudioCaptureConfig>{kAudioCaptureConfigValid}, + std::vector<VideoCaptureConfig>{kVideoCaptureConfigValid}); + + message_port_->ReceiveError(Error(Error::Code::kUnknownError)); +} + +} // namespace cast +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/cast/streaming/testing/simple_message_port.h b/chromium/third_party/openscreen/src/cast/streaming/testing/simple_message_port.h new file mode 100644 index 00000000000..7d91291859d --- /dev/null +++ b/chromium/third_party/openscreen/src/cast/streaming/testing/simple_message_port.h @@ -0,0 +1,64 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CAST_STREAMING_TESTING_SIMPLE_MESSAGE_PORT_H_ +#define CAST_STREAMING_TESTING_SIMPLE_MESSAGE_PORT_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "cast/common/public/message_port.h" +#include "cast/streaming/message_fields.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace openscreen { +namespace cast { + +class SimpleMessagePort : public MessagePort { + public: + ~SimpleMessagePort() override {} + void SetClient(MessagePort::Client* client, + std::string client_sender_id) override { + client_ = client; + } + + void ResetClient() override { client_ = nullptr; } + + void ReceiveMessage(const std::string& message) { + ReceiveMessage(kCastWebrtcNamespace, message); + } + + void ReceiveMessage(const std::string& namespace_, + const std::string message) { + ASSERT_NE(client_, nullptr); + client_->OnMessage("sender-1234", namespace_, message); + } + + void ReceiveError(Error error) { + ASSERT_NE(client_, nullptr); + client_->OnError(error); + } + + void PostMessage(const std::string& sender_id, + const std::string& message_namespace, + const std::string& message) override { + posted_messages_.emplace_back(message); + } + + MessagePort::Client* client() const { return client_; } + const std::vector<std::string> posted_messages() const { + return posted_messages_; + } + + private: + MessagePort::Client* client_ = nullptr; + std::vector<std::string> posted_messages_; +}; + +} // namespace cast +} // namespace openscreen + +#endif // CAST_STREAMING_TESTING_SIMPLE_MESSAGE_PORT_H_ diff --git a/chromium/third_party/openscreen/src/cast/test/BUILD.gn b/chromium/third_party/openscreen/src/cast/test/BUILD.gn index 67bae8abf6e..71c808c6510 100644 --- a/chromium/third_party/openscreen/src/cast/test/BUILD.gn +++ b/chromium/third_party/openscreen/src/cast/test/BUILD.gn @@ -6,9 +6,7 @@ import("//build_overrides/build.gni") source_set("unittests") { testonly = true - sources = [ - "device_auth_test.cc", - ] + sources = [ "device_auth_test.cc" ] deps = [ "../../platform:test", @@ -27,9 +25,7 @@ source_set("unittests") { if (is_posix && !build_with_chromium) { source_set("e2e_tests") { testonly = true - sources = [ - "cast_socket_e2e_test.cc", - ] + sources = [ "cast_socket_e2e_test.cc" ] deps = [ "../../platform", @@ -41,16 +37,13 @@ if (is_posix && !build_with_chromium) { "../common:channel", "../common:test_helpers", "../receiver:channel", - "../receiver:test_helpers", "../sender:channel", ] } executable("make_crl_tests") { testonly = true - sources = [ - "make_crl_tests.cc", - ] + sources = [ "make_crl_tests.cc" ] deps = [ "../../platform:test", diff --git a/chromium/third_party/openscreen/src/discovery/common/config.h b/chromium/third_party/openscreen/src/discovery/common/config.h index 7bcdcac75b3..b1ef731ac28 100644 --- a/chromium/third_party/openscreen/src/discovery/common/config.h +++ b/chromium/third_party/openscreen/src/discovery/common/config.h @@ -5,6 +5,8 @@ #ifndef DISCOVERY_COMMON_CONFIG_H_ #define DISCOVERY_COMMON_CONFIG_H_ +#include <vector> + #include "platform/base/interface_info.h" namespace openscreen { @@ -90,6 +92,10 @@ struct Config { // prevent a malicious or misbehaving mDNS client from causing the memory // used by mDNS to grow in an unbounded fashion. int querier_max_records_cached = 1024; + + // Sets the querier to ignore all NSEC negative response records received as + // responses to outgoing queries. + bool ignore_nsec_responses = false; }; inline Config::NetworkInfo::AddressFamilies operator&( diff --git a/chromium/third_party/openscreen/src/discovery/dnssd/impl/dns_data_graph.cc b/chromium/third_party/openscreen/src/discovery/dnssd/impl/dns_data_graph.cc index 2b46fa7aa3a..036c7985627 100644 --- a/chromium/third_party/openscreen/src/discovery/dnssd/impl/dns_data_graph.cc +++ b/chromium/third_party/openscreen/src/discovery/dnssd/impl/dns_data_graph.cc @@ -384,8 +384,7 @@ void DnsDataGraphImpl::StartTracking(const DomainName& domain, ScopedCallbackHandler creation_handler = GetScopedCreationHandler(std::move(on_start_tracking)); - auto pair = - nodes_.emplace(domain, std::make_unique<Node>(std::move(domain), this)); + auto pair = nodes_.emplace(domain, std::make_unique<Node>(domain, this)); OSP_DCHECK(pair.second); OSP_DCHECK(nodes_.find(domain) != nodes_.end()); diff --git a/chromium/third_party/openscreen/src/discovery/dnssd/impl/querier_impl.cc b/chromium/third_party/openscreen/src/discovery/dnssd/impl/querier_impl.cc index 94f54fc91c4..b98feff3dce 100644 --- a/chromium/third_party/openscreen/src/discovery/dnssd/impl/querier_impl.cc +++ b/chromium/third_party/openscreen/src/discovery/dnssd/impl/querier_impl.cc @@ -227,8 +227,7 @@ void QuerierImpl::StartQuery(const std::string& service, Callback* callback) { // Start tracking the new callback const ServiceKey key(service, kLocalDomain); - auto it = - callback_map_.emplace(std::move(key), std::vector<Callback*>{}).first; + auto it = callback_map_.emplace(key, std::vector<Callback*>{}).first; it->second.push_back(callback); const DomainName domain = key.GetName(); diff --git a/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance.cc b/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance.cc index 2c1382b2fb2..6761f7685a8 100644 --- a/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance.cc +++ b/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance.cc @@ -133,8 +133,11 @@ bool IsServiceValid(const std::string& service) { } last_char_hyphen = true; } else if (std::isalpha(service[i])) { + last_char_hyphen = false; seen_letter = true; - } else if (!std::isdigit(service[i])) { + } else if (std::isdigit(service[i])) { + last_char_hyphen = false; + } else { return false; } } diff --git a/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance_unittest.cc b/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance_unittest.cc index 3479204123d..3395938db77 100644 --- a/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance_unittest.cc +++ b/chromium/third_party/openscreen/src/discovery/dnssd/public/dns_sd_instance_unittest.cc @@ -114,6 +114,10 @@ TEST(DnsSdInstanceTests, ServiceProtocolNameFormatting) { EXPECT_FALSE(IsServiceValid("_0a1b--c02d._udp")); EXPECT_FALSE(IsServiceValid("_0a--1._udp")); EXPECT_FALSE(IsServiceValid("_a--b._udp")); + + // Multiple, non-adjacent hyphens. + EXPECT_TRUE(IsServiceValid("_a-b-c._udp")); + EXPECT_TRUE(IsServiceValid("_a-1-c._udp")); } TEST(DnsSdInstanceTests, DomainDotPositions) { diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier.cc index 2ae7260e1e1..5d961bd45ae 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier.cc @@ -459,6 +459,11 @@ void MdnsQuerier::ProcessRecord(const MdnsRecord& record) { return; } + // Ignore NSEC records if the embedder has configured us to do so. + if (config_.ignore_nsec_responses && record.dns_type() == DnsType::kNSEC) { + return; + } + // Get the types which the received record is associated with. In most cases // this will only be the type of the provided record, but in the case of // NSEC records this will be all records which the record dictates the @@ -635,8 +640,7 @@ void MdnsQuerier::ProcessCallbacks(const MdnsRecord& record, void MdnsQuerier::AddQuestion(const MdnsQuestion& question) { auto tracker = std::make_unique<MdnsQuestionTracker>( - std::move(question), sender_, task_runner_, now_function_, random_delay_, - config_); + question, sender_, task_runner_, now_function_, random_delay_, config_); MdnsQuestionTracker* ptr = tracker.get(); questions_.emplace(question.name(), std::move(tracker)); diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier_unittest.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier_unittest.cc index e6742203b76..afa0cfc6a8b 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier_unittest.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_querier_unittest.cc @@ -5,6 +5,7 @@ #include "discovery/mdns/mdns_querier.h" #include <memory> +#include <utility> #include "discovery/common/config.h" #include "discovery/common/testing/mock_reporting_client.h" @@ -550,6 +551,31 @@ TEST_F(MdnsQuerierTest, CorrectCallbackCalledWhenNsecRecordReplacesNonNsec) { EXPECT_TRUE(ContainsRecord(querier.get(), nsec_record_created_, DnsType::kA)); } +TEST_F(MdnsQuerierTest, + NoCallbackCalledWhenNsecRecordWouldReplaceNonNsecButNsecDisabled) { + config_.ignore_nsec_responses = true; + std::unique_ptr<MdnsQuerier> querier = CreateQuerier(); + + // Set up so an A record has been received + StrictMock<MockRecordChangedCallback> callback; + querier->StartQuery(DomainName{"testing", "local"}, DnsType::kA, + DnsClass::kIN, &callback); + EXPECT_CALL(callback, + OnRecordChanged(record0_created_, RecordChangedEvent::kCreated)); + auto packet = CreatePacketWithRecord(record0_created_); + receiver_.OnRead(&socket_, std::move(packet)); + testing::Mock::VerifyAndClearExpectations(&callback); + ASSERT_TRUE(ContainsRecord(querier.get(), record0_created_, DnsType::kA)); + EXPECT_FALSE( + ContainsRecord(querier.get(), nsec_record_created_, DnsType::kA)); + + packet = CreatePacketWithRecord(nsec_record_created_); + receiver_.OnRead(&socket_, std::move(packet)); + EXPECT_TRUE(ContainsRecord(querier.get(), record0_created_, DnsType::kA)); + EXPECT_FALSE( + ContainsRecord(querier.get(), nsec_record_created_, DnsType::kA)); +} + TEST_F(MdnsQuerierTest, CorrectCallbackCalledWhenNonNsecRecordReplacesNsec) { std::unique_ptr<MdnsQuerier> querier = CreateQuerier(); diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.cc index c6aa926b6be..bd38e81be23 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.cc @@ -322,8 +322,8 @@ bool MdnsReader::Read(MdnsQuestion* out) { return false; } -bool MdnsReader::Read(MdnsMessage* out) { - OSP_DCHECK(out); +ErrorOr<MdnsMessage> MdnsReader::Read() { + MdnsMessage out; Cursor cursor(this); Header header; std::vector<MdnsQuestion> questions; @@ -334,26 +334,26 @@ bool MdnsReader::Read(MdnsMessage* out) { Read(header.answer_count, &answers) && Read(header.authority_record_count, &authority_records) && Read(header.additional_record_count, &additional_records)) { - // TODO(yakimakha): Skip messages with non-zero opcode and rcode. - // One way to do this is to change the method signature to return - // ErrorOr<MdnsMessage> and return different error codes for failure to read - // and for messages that were read successfully but are non-conforming. + if (!IsValidFlagsSection(header.flags)) { + return Error::Code::kMdnsNonConformingFailure; + } + ErrorOr<MdnsMessage> message = MdnsMessage::TryCreate( header.id, GetMessageType(header.flags), questions, answers, authority_records, additional_records); if (message.is_error()) { - return false; + return std::move(message.error()); } - *out = std::move(message.value()); + out = std::move(message.value()); if (IsMessageTruncated(header.flags)) { - out->set_truncated(); + out.set_truncated(); } cursor.Commit(); - return true; + return out; } - return false; + return Error::Code::kMdnsReadFailure; } bool MdnsReader::Read(IPAddress::Version version, IPAddress* out) { diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.h b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.h index bfe7eb04f22..2902e288059 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.h +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader.h @@ -5,9 +5,11 @@ #ifndef DISCOVERY_MDNS_MDNS_READER_H_ #define DISCOVERY_MDNS_MDNS_READER_H_ +#include <utility> #include <vector> #include "discovery/mdns/mdns_records.h" +#include "platform/base/error.h" #include "util/big_endian.h" namespace openscreen { @@ -34,14 +36,16 @@ class MdnsReader : public BigEndianReader { bool Read(PtrRecordRdata* out); bool Read(TxtRecordRdata* out); bool Read(NsecRecordRdata* out); + // Reads a DNS resource record with its RDATA. // The correct type of RDATA to be read is determined by the type // specified in the record. bool Read(MdnsRecord* out); bool Read(MdnsQuestion* out); + // Reads multiple mDNS questions and records that are a part of // a mDNS message being read. - bool Read(MdnsMessage* out); + ErrorOr<MdnsMessage> Read(); private: struct NsecBitMapField { diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_fuzztest.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_fuzztest.cc index e28ff92de0f..9a97c89b564 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_fuzztest.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_fuzztest.cc @@ -5,10 +5,15 @@ #include "discovery/common/config.h" #include "discovery/mdns/mdns_reader.h" +namespace openscreen { +namespace discovery { +void Fuzz(const uint8_t* data, size_t size) { + MdnsReader reader(Config{}, data, size); + reader.Read(); +} +} // namespace discovery +} // namespace openscreen extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - openscreen::discovery::Config config; - openscreen::discovery::MdnsReader reader(config, data, size); - openscreen::discovery::MdnsMessage message; - reader.Read(&message); + openscreen::discovery::Fuzz(data, size); return 0; } diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_unittest.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_unittest.cc index 2882c8e2d26..08062e5d9b7 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_unittest.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_reader_unittest.cc @@ -30,6 +30,17 @@ void TestReadEntrySucceeds(const uint8_t* data, EXPECT_EQ(reader.remaining(), UINT64_C(0)); } +template <> +void TestReadEntrySucceeds<MdnsMessage>(const uint8_t* data, + size_t size, + const MdnsMessage& expected) { + MdnsReader reader(Config{}, data, size); + const ErrorOr<MdnsMessage> message = reader.Read(); + EXPECT_TRUE(message.is_value()); + EXPECT_EQ(message.value(), expected); + EXPECT_EQ(reader.remaining(), UINT64_C(0)); +} + template <class T> void TestReadEntryFails(const uint8_t* data, size_t size) { Config config; @@ -41,6 +52,17 @@ void TestReadEntryFails(const uint8_t* data, size_t size) { EXPECT_EQ(reader.offset(), UINT64_C(0)); } +template <> +void TestReadEntryFails<MdnsMessage>(const uint8_t* data, size_t size) { + Config config; + MdnsReader reader(config, data, size); + const ErrorOr<MdnsMessage> message = reader.Read(); + EXPECT_TRUE(message.is_error()); + + // There should be no side effects for failing to read an entry. The + // underlying pointer should not have changed. + EXPECT_EQ(reader.offset(), UINT64_C(0)); +} } // namespace TEST(MdnsReaderTest, ReadDomainName) { diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_receiver.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_receiver.cc index bb8634d2198..09982dd858c 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_receiver.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_receiver.cc @@ -66,15 +66,19 @@ void MdnsReceiver::OnRead(UdpSocket* socket, TRACE_SCOPED(TraceCategory::kMdns, "MdnsReceiver::OnRead"); MdnsReader reader(config_, packet.data(), packet.size()); - MdnsMessage message; - if (!reader.Read(&message)) { - OSP_DVLOG << "mDNS message failed to parse..."; + const ErrorOr<MdnsMessage> message = reader.Read(); + if (message.is_error()) { + if (message.error().code() == Error::Code::kMdnsNonConformingFailure) { + OSP_DVLOG << "mDNS message dropped due to invalid rcode or opcode..."; + } else { + OSP_DVLOG << "mDNS message failed to parse..."; + } return; } - if (message.type() == MessageType::Response) { + if (message.value().type() == MessageType::Response) { for (ResponseClient* client : response_clients_) { - client->OnMessageReceived(message); + client->OnMessageReceived(message.value()); } if (response_clients_.empty()) { OSP_DVLOG @@ -82,7 +86,7 @@ void MdnsReceiver::OnRead(UdpSocket* socket, } } else { if (query_callback_) { - query_callback_(message, packet.source()); + query_callback_(message.value(), packet.source()); } else { OSP_DVLOG << "mDNS query message dropped. No query client registered..."; } diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_records.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_records.cc index eadbff9c31c..6eed677bfd4 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_records.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_records.cc @@ -47,27 +47,29 @@ bool IsGreaterThan(const Rdata& lhs, const Rdata& rhs) { const RDataType& lhs_cast = absl::get<RDataType>(lhs); const RDataType& rhs_cast = absl::get<RDataType>(rhs); - size_t lhs_size = lhs_cast.MaxWireSize(); - size_t rhs_size = rhs_cast.MaxWireSize(); - size_t min_size = std::min(lhs_size, rhs_size); + // The Extra 2 in length is from the record size that Write() prepends to the + // result. + const size_t lhs_size = lhs_cast.MaxWireSize() + 2; + const size_t rhs_size = rhs_cast.MaxWireSize() + 2; uint8_t lhs_bytes[lhs_size]; uint8_t rhs_bytes[rhs_size]; MdnsWriter lhs_writer(lhs_bytes, lhs_size); MdnsWriter rhs_writer(rhs_bytes, rhs_size); - lhs_writer.Write(lhs_cast); - rhs_writer.Write(rhs_cast); - for (size_t i = 0; i < min_size; i++) { + const bool lhs_write = lhs_writer.Write(lhs_cast); + const bool rhs_write = rhs_writer.Write(rhs_cast); + OSP_DCHECK(lhs_write); + OSP_DCHECK(rhs_write); + + // Skip the size bits. + const size_t min_size = std::min(lhs_writer.offset(), rhs_writer.offset()); + for (size_t i = 2; i < min_size; i++) { if (lhs_bytes[i] != rhs_bytes[i]) { return lhs_bytes[i] > rhs_bytes[i]; } } - if (lhs_size == rhs_size) { - return false; - } - return lhs_size > rhs_size; } @@ -619,6 +621,15 @@ bool MdnsRecord::operator>(const MdnsRecord& rhs) const { // "lexicographically later" is performed by first comparing the record class, // then the record type, then raw comparison of the binary content of the // rdata without regard for meaning or structure. + // NOTE: Per RFC, the TTL is not included in this comparison. + if (name() != rhs.name()) { + return name() > rhs.name(); + } + + if (record_type() != rhs.record_type()) { + return record_type() == RecordType::kUnique; + } + if (dns_class() != rhs.dns_class()) { return dns_class() > rhs.dns_class(); } diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_records_unittest.cc b/chromium/third_party/openscreen/src/discovery/mdns/mdns_records_unittest.cc index 395c9259c5d..f30b8845fd9 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_records_unittest.cc +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_records_unittest.cc @@ -541,38 +541,61 @@ TEST(MdnsRecordTest, Construct) { } TEST(MdnsRecordTest, Compare) { - MdnsRecord record1(DomainName{"hostname", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kShared, kTtl, - PtrRecordRdata(DomainName{"testing", "local"})); - MdnsRecord record2(DomainName{"hostname", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kShared, kTtl, - PtrRecordRdata(DomainName{"testing", "local"})); - MdnsRecord record3(DomainName{"othername", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kShared, kTtl, - PtrRecordRdata(DomainName{"testing", "local"})); - MdnsRecord record4(DomainName{"hostname", "local"}, DnsType::kA, - DnsClass::kIN, RecordType::kShared, kTtl, - ARecordRdata(IPAddress{8, 8, 8, 8})); - MdnsRecord record5(DomainName{"hostname", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kUnique, kTtl, - PtrRecordRdata(DomainName{"testing", "local"})); - MdnsRecord record6(DomainName{"hostname", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kShared, - std::chrono::seconds(200), - PtrRecordRdata(DomainName{"testing", "local"})); - MdnsRecord record7(DomainName{"hostname", "local"}, DnsType::kPTR, - DnsClass::kIN, RecordType::kShared, kTtl, - PtrRecordRdata(DomainName{"device", "local"})); + const MdnsRecord record1(DomainName{"hostname", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kShared, kTtl, + PtrRecordRdata(DomainName{"testing", "local"})); + const MdnsRecord record2(DomainName{"hostname", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kShared, kTtl, + PtrRecordRdata(DomainName{"testing", "local"})); + const MdnsRecord record3(DomainName{"othername", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kShared, kTtl, + PtrRecordRdata(DomainName{"testing", "local"})); + const MdnsRecord record4(DomainName{"hostname", "local"}, DnsType::kA, + DnsClass::kIN, RecordType::kShared, kTtl, + ARecordRdata(IPAddress{8, 8, 8, 8})); + const MdnsRecord record5(DomainName{"hostname", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kUnique, kTtl, + PtrRecordRdata(DomainName{"testing", "local"})); + const MdnsRecord record6(DomainName{"hostname", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kShared, + std::chrono::seconds(200), + PtrRecordRdata(DomainName{"testing", "local"})); + const MdnsRecord record7(DomainName{"hostname", "local"}, DnsType::kPTR, + DnsClass::kIN, RecordType::kShared, kTtl, + PtrRecordRdata(DomainName{"device", "local"})); + const MdnsRecord record8( + DomainName{"testing", "local"}, DnsType::kNSEC, DnsClass::kIN, + RecordType::kUnique, std::chrono::seconds(120), + NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kA)); + const MdnsRecord record9( + DomainName{"testing", "local"}, DnsType::kNSEC, DnsClass::kIN, + RecordType::kUnique, std::chrono::seconds(120), + NsecRecordRdata(DomainName{"testing", "local"}, DnsType::kAAAA)); EXPECT_EQ(record1, record2); - EXPECT_NE(record1, record3); - EXPECT_NE(record1, record4); - EXPECT_NE(record1, record5); + + // Account for intentional differences between > / < and = / !=. This is + // unfortunate but required difference for > / < per RFC. EXPECT_NE(record1, record6); - EXPECT_NE(record1, record7); + ASSERT_FALSE(record1 > record6); + ASSERT_FALSE(record6 > record1); + + std::vector<const MdnsRecord*> records_sorted{ + &record4, &record7, &record1, &record5, &record3, &record8, &record9}; + for (size_t i = 0; i < records_sorted.size(); i++) { + for (size_t j = i + 1; j < records_sorted.size(); j++) { + EXPECT_NE(*records_sorted[i], *records_sorted[j]) + << "failure for i=" << i << " , j=" << j; + EXPECT_GT(*records_sorted[j], *records_sorted[i]) + << "failure for i=" << i << " , j=" << j; + EXPECT_LT(*records_sorted[i], *records_sorted[j]) + << "failure for i=" << i << " , j=" << j; + } + } EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( - {record1, record2, record3, record4, record5, record6, record7})); + {record1, record2, record3, record4, record5, record6, record7, record8, + record9})); } TEST(MdnsRecordTest, CopyAndMove) { diff --git a/chromium/third_party/openscreen/src/discovery/mdns/mdns_trackers.h b/chromium/third_party/openscreen/src/discovery/mdns/mdns_trackers.h index 6cb863a94a1..58e0ccdcabf 100644 --- a/chromium/third_party/openscreen/src/discovery/mdns/mdns_trackers.h +++ b/chromium/third_party/openscreen/src/discovery/mdns/mdns_trackers.h @@ -5,6 +5,7 @@ #ifndef DISCOVERY_MDNS_MDNS_TRACKERS_H_ #define DISCOVERY_MDNS_MDNS_TRACKERS_H_ +#include <tuple> #include <unordered_map> #include <vector> @@ -82,7 +83,7 @@ class MdnsTracker { MdnsSender* const sender_; TaskRunner* const task_runner_; const ClockNowFunctionPtr now_function_; - Alarm send_alarm_; // TODO(yakimakha): Use cancelable task when available + Alarm send_alarm_; MdnsRandom* const random_delay_; TrackerType tracker_type_; diff --git a/chromium/third_party/openscreen/src/infra/config/global/cr-buildbucket.cfg b/chromium/third_party/openscreen/src/infra/config/global/cr-buildbucket.cfg index 2ecf7291081..7a60c48faba 100644 --- a/chromium/third_party/openscreen/src/infra/config/global/cr-buildbucket.cfg +++ b/chromium/third_party/openscreen/src/infra/config/global/cr-buildbucket.cfg @@ -91,18 +91,17 @@ builder_mixins { # NOTE: The OS version here will determine which version of XCode is being # used. Relevant links; so you and I never have to spend hours finding this - # stuff all over again to fix things like https://crbug.com/openscreen/86: + # stuff all over again to fix things like https://crbug.com/openscreen/86 # # 1. The recipe code that uses the "osx_sdk" recipe module: # - # https://cs.chromium.org/chromium/build/scripts/slave/recipes - # /openscreen.py?rcl=671f9f1c5f5bef81d0a39973aa8729cc83bb290e&l=74 + # https://cs.chromium.org/chromium/build/scripts/slave/recipes/openscreen.py?rcl=671f9f1c5f5bef81d0a39973aa8729cc83bb290e&l=74 # # 2. The XCode version look-up table in the "osx_sdk" recipe module: # - # https://cs.chromium.org/chromium/tools/depot_tools/recipes/recipe_modules - # /osx_sdk/api.py?rcl=fe18a43d590a5eac0d58e7e555b024746ba290ad&l=26 - dimensions: "os:Mac-10.13" + # https://cs.chromium.org/chromium/tools/depot_tools/recipes/recipe_modules/osx_sdk/api.py?l=32 + # + dimensions: "os:Mac-10.15" caches: { # Cache for mac_toolchain tool and XCode.app used in recipes. @@ -140,7 +139,7 @@ builder_mixins { name: "chromium" recipe: { name: "chromium" - properties: "mastername:client.openscreen.chromium" + properties: "builder_group:client.openscreen.chromium" } } diff --git a/chromium/third_party/openscreen/src/infra/config/global/refs.cfg b/chromium/third_party/openscreen/src/infra/config/global/refs.cfg deleted file mode 100644 index b287e225083..00000000000 --- a/chromium/third_party/openscreen/src/infra/config/global/refs.cfg +++ /dev/null @@ -1,6 +0,0 @@ -# Refs configuration file. The documentation of the format can be found -# at https://luci-config.appspot.com/schemas/projects:refs.cfg. -refs { - name: "refs/heads/master" - config_path: "infra/config/branch" -} diff --git a/chromium/third_party/openscreen/src/platform/api/udp_socket.cc b/chromium/third_party/openscreen/src/platform/api/udp_socket.cc index 2da7a73f429..47eba8bdd22 100644 --- a/chromium/third_party/openscreen/src/platform/api/udp_socket.cc +++ b/chromium/third_party/openscreen/src/platform/api/udp_socket.cc @@ -1,6 +1,6 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file +// found in the LICENSE file. #include "platform/api/udp_socket.h" diff --git a/chromium/third_party/openscreen/src/platform/base/error.cc b/chromium/third_party/openscreen/src/platform/base/error.cc index 05a0c070d12..f05c1e79863 100644 --- a/chromium/third_party/openscreen/src/platform/base/error.cc +++ b/chromium/third_party/openscreen/src/platform/base/error.cc @@ -110,6 +110,10 @@ std::ostream& operator<<(std::ostream& os, const Error::Code& code) { return os << "SocketSendFailure"; case Error::Code::kMdnsRegisterFailure: return os << "MdnsRegisterFailure"; + case Error::Code::kMdnsReadFailure: + return os << "MdnsReadFailure"; + case Error::Code::kMdnsNonConformingFailure: + return os << "kMdnsNonConformingFailure"; case Error::Code::kParseError: return os << "ParseError"; case Error::Code::kUnknownMessageType: @@ -154,6 +158,8 @@ std::ostream& operator<<(std::ostream& os, const Error::Code& code) { return os << "ErrCertsDateInvalid"; case Error::Code::kErrCertsVerifyGeneric: return os << "ErrCertsVerifyGeneric"; + case Error::Code::kErrCertsVerifyUntrustedCert: + return os << "kErrCertsVerifyUntrustedCert"; case Error::Code::kErrCrlInvalid: return os << "ErrCrlInvalid"; case Error::Code::kErrCertsRevoked: diff --git a/chromium/third_party/openscreen/src/platform/base/error.h b/chromium/third_party/openscreen/src/platform/base/error.h index 55588ac0ebb..919c6575de0 100644 --- a/chromium/third_party/openscreen/src/platform/base/error.h +++ b/chromium/third_party/openscreen/src/platform/base/error.h @@ -63,7 +63,10 @@ class Error { kSocketReadFailure, kSocketSendFailure, + // MDNS errors. kMdnsRegisterFailure, + kMdnsReadFailure, + kMdnsNonConformingFailure, kParseError, kUnknownMessageType, @@ -117,6 +120,9 @@ class Error { // The certificate failed to chain to a trusted root. kErrCertsVerifyGeneric, + // The certificate was not found in the trust store. + kErrCertsVerifyUntrustedCert, + // The CRL is missing or failed to verify. kErrCrlInvalid, diff --git a/chromium/third_party/openscreen/src/platform/base/trace_logging_types.h b/chromium/third_party/openscreen/src/platform/base/trace_logging_types.h index 64149a7eeb1..257feaff073 100644 --- a/chromium/third_party/openscreen/src/platform/base/trace_logging_types.h +++ b/chromium/third_party/openscreen/src/platform/base/trace_logging_types.h @@ -60,7 +60,8 @@ struct TraceCategory { kSsl = 0x01 << 2, kPresentation = 0x01 << 3, kStandaloneReceiver = 0x01 << 4, - kDiscovery = 0x01 << 5 + kDiscovery = 0x01 << 5, + kStandaloneSender = 0x01 << 6, }; }; diff --git a/chromium/third_party/openscreen/src/platform/impl/socket_handle.h b/chromium/third_party/openscreen/src/platform/impl/socket_handle.h index fca8192e941..4e6934c3f6c 100644 --- a/chromium/third_party/openscreen/src/platform/impl/socket_handle.h +++ b/chromium/third_party/openscreen/src/platform/impl/socket_handle.h @@ -1,6 +1,6 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file +// found in the LICENSE file. #ifndef PLATFORM_IMPL_SOCKET_HANDLE_H_ #define PLATFORM_IMPL_SOCKET_HANDLE_H_ diff --git a/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.cc b/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.cc index dfc9c5bb34c..35e1fd06506 100644 --- a/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.cc +++ b/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.cc @@ -1,6 +1,6 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file +// found in the LICENSE file. #include "platform/impl/socket_handle_posix.h" diff --git a/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.h b/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.h index 13f977c33d8..f3d8b7a175e 100644 --- a/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.h +++ b/chromium/third_party/openscreen/src/platform/impl/socket_handle_posix.h @@ -1,6 +1,6 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file +// found in the LICENSE file. #ifndef PLATFORM_IMPL_SOCKET_HANDLE_POSIX_H_ #define PLATFORM_IMPL_SOCKET_HANDLE_POSIX_H_ diff --git a/chromium/third_party/openscreen/src/platform/impl/task_runner.cc b/chromium/third_party/openscreen/src/platform/impl/task_runner.cc index d8f941c372d..f306e792130 100644 --- a/chromium/third_party/openscreen/src/platform/impl/task_runner.cc +++ b/chromium/third_party/openscreen/src/platform/impl/task_runner.cc @@ -1,6 +1,6 @@ // Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file +// found in the LICENSE file. #include "platform/impl/task_runner.h" @@ -78,6 +78,7 @@ void TaskRunnerImpl::RunUntilStopped() { task_runner_thread_id_ = std::this_thread::get_id(); is_running_ = true; + OSP_DVLOG << "Running tasks until stopped..."; // Main loop: Run until the |is_running_| flag is set back to false by the // "quit task" posted by RequestStopSoon(), or the process received a // termination signal. @@ -91,6 +92,7 @@ void TaskRunnerImpl::RunUntilStopped() { } } + OSP_DVLOG << "Finished running, entering flushing phase..."; // Flushing phase: Ensure all immediately-runnable tasks are run before // returning. Since running some tasks might cause more immediately-runnable // tasks to be posted, loop until there is no more work. @@ -103,7 +105,7 @@ void TaskRunnerImpl::RunUntilStopped() { while (GrabMoreRunnableTasks()) { RunRunnableTasks(); } - + OSP_DVLOG << "Finished flushing..."; task_runner_thread_id_ = std::thread::id(); } @@ -118,6 +120,7 @@ void TaskRunnerImpl::RunUntilSignaled() { std::signal(SIGINT, old_sigint_handler); std::signal(SIGTERM, old_sigterm_handler); + OSP_DVLOG << "Received SIGNIT or SIGTERM, setting state to not running..."; g_signal_state = kNotRunning; } diff --git a/chromium/third_party/openscreen/src/platform/impl/udp_socket_posix.cc b/chromium/third_party/openscreen/src/platform/impl/udp_socket_posix.cc index 5e60dd0d723..393e272743c 100644 --- a/chromium/third_party/openscreen/src/platform/impl/udp_socket_posix.cc +++ b/chromium/third_party/openscreen/src/platform/impl/udp_socket_posix.cc @@ -13,6 +13,7 @@ #include <sys/types.h> #include <unistd.h> +#include <algorithm> #include <cstring> #include <memory> #include <sstream> @@ -29,6 +30,11 @@ namespace openscreen { namespace { +// 64 KB is the maximum possible UDP datagram size. +#if !defined(OS_LINUX) +constexpr int kMaxUdpBufferSize = 64 << 10; +#endif + constexpr bool IsPowerOf2(uint32_t x) { return (x > 0) && ((x & (x - 1)) == 0); } @@ -372,29 +378,53 @@ bool IsPacketInfo<in6_pktinfo>(cmsghdr* cmh) { } template <class SockAddrType, class PktInfoType> -Error ReceiveMessageInternal(int fd, UdpPacket* packet) { +ErrorOr<UdpPacket> ReceiveMessageInternal(int fd) { + int upper_bound_bytes; +#if defined(OS_LINUX) + // This should return the exact size of the next message. + upper_bound_bytes = recv(fd, nullptr, 0, MSG_PEEK | MSG_TRUNC); + if (upper_bound_bytes == -1) { + return ChooseError(errno, Error::Code::kSocketReadFailure); + } +#elif defined(MAC_OSX) + // Can't use MSG_TRUNC in recv(). Use the FIONREAD ioctl() to get an + // upper-bound. + if (ioctl(fd, FIONREAD, &upper_bound_bytes) == -1 || upper_bound_bytes < 0) { + return ChooseError(errno, Error::Code::kSocketReadFailure); + } + upper_bound_bytes = std::min(upper_bound_bytes, kMaxUdpBufferSize); +#else // Other POSIX platforms (neither MSG_TRUNC nor FIONREAD available). + upper_bound_bytes = kMaxUdpBufferSize; +#endif + + UdpPacket packet(upper_bound_bytes); + msghdr msg = {}; SockAddrType sa; - iovec iov = {packet->data(), packet->size()}; - alignas(alignof(cmsghdr)) uint8_t control_buffer[1024]; - msghdr msg; msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); + iovec iov = {packet.data(), packet.size()}; msg.msg_iov = &iov; msg.msg_iovlen = 1; + + // Although we don't do anything with the control buffer, on Linux + // it is required for the message to be properly read. +#if defined(OS_LINUX) + alignas(alignof(cmsghdr)) uint8_t control_buffer[1024]; msg.msg_control = control_buffer; msg.msg_controllen = sizeof(control_buffer); - msg.msg_flags = 0; - - ssize_t bytes_received = recvmsg(fd, &msg, 0); +#endif + const ssize_t bytes_received = recvmsg(fd, &msg, 0); if (bytes_received == -1) { + OSP_DVLOG << "Failed to read from socket."; return ChooseError(errno, Error::Code::kSocketReadFailure); } - - OSP_DCHECK_EQ(static_cast<size_t>(bytes_received), packet->size()); + // We may not populate the entire packet. + OSP_DCHECK_LE(static_cast<size_t>(bytes_received), packet.size()); + packet.resize(bytes_received); IPEndpoint source_endpoint = {.address = GetIPAddressFromSockAddr(sa), .port = GetPortFromFromSockAddr(sa)}; - packet->set_source(std::move(source_endpoint)); + packet.set_source(std::move(source_endpoint)); // For multicast sockets, the packet's original destination address may be // the host address (since we called bind()) but it may also be a @@ -412,11 +442,11 @@ Error ReceiveMessageInternal(int fd, UdpPacket* packet) { IPEndpoint destination_endpoint = { .address = GetIPAddressFromPktInfo(*pktinfo), .port = GetPortFromFromSockAddr(sa)}; - packet->set_destination(std::move(destination_endpoint)); + packet.set_destination(std::move(destination_endpoint)); break; } } - return Error::Code::kNone; + return std::move(packet); } } // namespace @@ -436,32 +466,15 @@ void UdpSocketPosix::ReceiveMessage() { return; } - ssize_t bytes_available = recv(handle_.fd, nullptr, 0, MSG_PEEK | MSG_TRUNC); - if (bytes_available == -1) { - task_runner_->PostTask( - [weak_this = weak_factory_.GetWeakPtr(), - error = - ChooseError(errno, Error::Code::kSocketReadFailure)]() mutable { - if (auto* self = weak_this.get()) { - if (auto* client = self->client_) { - client->OnRead(self, std::move(error)); - } - } - }); - return; - } - UdpPacket packet(bytes_available); - packet.set_socket(this); - Error result = Error::Code::kUnknownError; + ErrorOr<UdpPacket> read_result = Error::Code::kUnknownError; switch (local_endpoint_.address.version()) { case UdpSocket::Version::kV4: { - result = - ReceiveMessageInternal<sockaddr_in, in_pktinfo>(handle_.fd, &packet); + read_result = ReceiveMessageInternal<sockaddr_in, in_pktinfo>(handle_.fd); break; } case UdpSocket::Version::kV6: { - result = ReceiveMessageInternal<sockaddr_in6, in6_pktinfo>(handle_.fd, - &packet); + read_result = + ReceiveMessageInternal<sockaddr_in6, in6_pktinfo>(handle_.fd); break; } default: { @@ -469,21 +482,16 @@ void UdpSocketPosix::ReceiveMessage() { } } - task_runner_->PostTask( - [weak_this = weak_factory_.GetWeakPtr(), - read_result = result.ok() - ? ErrorOr<UdpPacket>(std::move(packet)) - : ErrorOr<UdpPacket>(std::move(result))]() mutable { - if (auto* self = weak_this.get()) { - if (auto* client = self->client_) { - client->OnRead(self, std::move(read_result)); - } - } - }); + task_runner_->PostTask([weak_this = weak_factory_.GetWeakPtr(), + read_result = std::move(read_result)]() mutable { + if (auto* self = weak_this.get()) { + if (auto* client = self->client_) { + client->OnRead(self, std::move(read_result)); + } + } + }); } -// TODO(yakimakha): Consider changing the interface to accept UdpPacket as -// an input parameter. void UdpSocketPosix::SendMessage(const void* data, size_t length, const IPEndpoint& dest) { diff --git a/chromium/third_party/openscreen/src/third_party/abseil/BUILD.gn b/chromium/third_party/openscreen/src/third_party/abseil/BUILD.gn index a4ca3d53bcc..e3879cd4386 100644 --- a/chromium/third_party/openscreen/src/third_party/abseil/BUILD.gn +++ b/chromium/third_party/openscreen/src/third_party/abseil/BUILD.gn @@ -6,9 +6,7 @@ import("//build_overrides/build.gni") if (build_with_chromium) { source_set("abseil") { - public_deps = [ - "//third_party/abseil-cpp:absl", - ] + public_deps = [ "//third_party/abseil-cpp:absl" ] } } else { config("abseil_config") { @@ -19,9 +17,16 @@ if (build_with_chromium) { cflags = [ "-Wno-sign-compare", "-Wno-extra-semi", + "-Wno-range-loop-analysis", ] + + if (is_mac) { + cflags += [ "-Wno-range-loop-analysis" ] + } } + # NOTE: StrFormat is specifically excluded from the Abseil source set due + # to binary size concerns. source_set("abseil") { sources = [ "src/absl/base/attributes.h", @@ -74,7 +79,6 @@ if (build_with_chromium) { "src/absl/strings/numbers.h", "src/absl/strings/str_cat.cc", "src/absl/strings/str_cat.h", - "src/absl/strings/str_format.h", "src/absl/strings/str_join.h", "src/absl/strings/str_replace.cc", "src/absl/strings/str_replace.h", diff --git a/chromium/third_party/openscreen/src/third_party/jsoncpp/BUILD.gn b/chromium/third_party/openscreen/src/third_party/jsoncpp/BUILD.gn index 63df151ca98..bc96a235e0e 100644 --- a/chromium/third_party/openscreen/src/third_party/jsoncpp/BUILD.gn +++ b/chromium/third_party/openscreen/src/third_party/jsoncpp/BUILD.gn @@ -6,16 +6,11 @@ import("//build_overrides/build.gni") if (build_with_chromium) { source_set("jsoncpp") { - public_deps = [ - "//third_party/jsoncpp", - ] + public_deps = [ "//third_party/jsoncpp" ] } } else { config("jsoncpp_config") { - include_dirs = [ - "//third_party/jsoncpp/src/include", - "//third_party/jsoncpp/generated", - ] + include_dirs = [ "//third_party/jsoncpp/src/include" ] cflags_cc = [ "-Wno-implicit-fallthrough" ] @@ -26,22 +21,15 @@ if (build_with_chromium) { source_set("jsoncpp") { sources = [ - # Note: the generated/version.h file is a build artifact of JsonCpp's - # meson/CMake build toolchains. When updating the DEPS, build JsonCpp - # and copy the generated version.h to this location. You also need to - # update the include guard to match our PRESUBMIT rule, from - # JSON_VERSION_H_INCLUDED to THIRD_PARTY_JSONCPP_GENERATED_VERSION_H_ - "generated/version.h", - - # All of the other files are from the actual JsonCpp repository. - "src/include/json/assertions.h", + "src/include/json/allocator.h", "src/include/json/autolink.h", "src/include/json/config.h", - "src/include/json/features.h", "src/include/json/forwards.h", "src/include/json/json.h", + "src/include/json/json_features.h", "src/include/json/reader.h", "src/include/json/value.h", + "src/include/json/version.h", "src/include/json/writer.h", "src/src/lib_json/json_reader.cpp", "src/src/lib_json/json_tool.h", diff --git a/chromium/third_party/openscreen/src/third_party/jsoncpp/generated/version.h b/chromium/third_party/openscreen/src/third_party/jsoncpp/generated/version.h deleted file mode 100644 index 4a79e5539fd..00000000000 --- a/chromium/third_party/openscreen/src/third_party/jsoncpp/generated/version.h +++ /dev/null @@ -1,22 +0,0 @@ -// DO NOT EDIT. This file (and "version") is a template used by the build system -// (either CMake or Meson) to generate a "version.h" header file. -#ifndef THIRD_PARTY_JSONCPP_GENERATED_VERSION_H_ -#define THIRD_PARTY_JSONCPP_GENERATED_VERSION_H_ - -#define JSONCPP_VERSION_STRING "1.9.0" -#define JSONCPP_VERSION_MAJOR 1 -#define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 0 -#define JSONCPP_VERSION_QUALIFIER -#define JSONCPP_VERSION_HEXA \ - ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ - (JSONCPP_VERSION_PATCH << 8)) - -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY -#endif -#define JSONCPP_USING_SECURE_MEMORY 0 -// If non-zero, the library zeroes any memory that it has allocated before -// it frees its memory. - -#endif // THIRD_PARTY_JSONCPP_GENERATED_VERSION_H_ diff --git a/chromium/third_party/openscreen/src/util/BUILD.gn b/chromium/third_party/openscreen/src/util/BUILD.gn index 7eaf3d3072b..2fdd24c0b79 100644 --- a/chromium/third_party/openscreen/src/util/BUILD.gn +++ b/chromium/third_party/openscreen/src/util/BUILD.gn @@ -30,6 +30,10 @@ source_set("util") { "crypto/digest_sign.h", "crypto/openssl_util.cc", "crypto/openssl_util.h", + "crypto/pem_helpers.cc", + "crypto/pem_helpers.h", + "crypto/random_bytes.cc", + "crypto/random_bytes.h", "crypto/rsa_private_key.cc", "crypto/rsa_private_key.h", "crypto/secure_hash.cc", @@ -83,6 +87,7 @@ source_set("unittests") { sources = [ "alarm_unittest.cc", "big_endian_unittest.cc", + "crypto/random_bytes_unittest.cc", "crypto/rsa_private_key_unittest.cc", "crypto/secure_hash_unittest.cc", "crypto/sha2_unittest.cc", diff --git a/chromium/third_party/openscreen/src/util/crypto/openssl_util.cc b/chromium/third_party/openscreen/src/util/crypto/openssl_util.cc index 580ab3a91cc..505ac71585d 100644 --- a/chromium/third_party/openscreen/src/util/crypto/openssl_util.cc +++ b/chromium/third_party/openscreen/src/util/crypto/openssl_util.cc @@ -10,6 +10,7 @@ #include <stddef.h> #include <stdint.h> +#include <sstream> #include <string> #include <utility> @@ -68,7 +69,14 @@ Error GetSSLError(const SSL* ssl, int return_code) { return Error::None(); } - std::string message = ERR_reason_error_string(ERR_get_error()); + // Create error message w/ unwind of error stack + original SSL error string. + std::stringstream msg; + msg << "boringssl error (" << error_code + << "): " << SSL_error_description(error_code); + while (uint32_t packed_error = ERR_get_error()) { + msg << "\nerr stack: " << ERR_reason_error_string(packed_error); + } + std::string message = msg.str(); switch (error_code) { case SSL_ERROR_ZERO_RETURN: return Error(Error::Code::kSocketClosedFailure, std::move(message)); diff --git a/chromium/third_party/openscreen/src/util/crypto/pem_helpers.cc b/chromium/third_party/openscreen/src/util/crypto/pem_helpers.cc new file mode 100644 index 00000000000..a9dcf1247bc --- /dev/null +++ b/chromium/third_party/openscreen/src/util/crypto/pem_helpers.cc @@ -0,0 +1,70 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "util/crypto/pem_helpers.h" + +#include <openssl/bytestring.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <stdio.h> +#include <string.h> + +#include "absl/strings/match.h" +#include "util/osp_logging.h" + +namespace openscreen { + +std::vector<std::string> ReadCertificatesFromPemFile( + absl::string_view filename) { + FILE* fp = fopen(filename.data(), "r"); + if (!fp) { + return {}; + } + std::vector<std::string> certs; + char* name; + char* header; + unsigned char* data; + long length; // NOLINT + while (PEM_read(fp, &name, &header, &data, &length) == 1) { + if (absl::StartsWith(name, "CERTIFICATE")) { + certs.emplace_back(reinterpret_cast<char*>(data), length); + } + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); + } + fclose(fp); + return certs; +} + +bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename) { + FILE* fp = fopen(filename.data(), "r"); + if (!fp) { + return nullptr; + } + bssl::UniquePtr<EVP_PKEY> pkey; + char* name; + char* header; + unsigned char* data; + long length; // NOLINT + while (PEM_read(fp, &name, &header, &data, &length) == 1) { + if (absl::StartsWith(name, "RSA PRIVATE KEY")) { + OSP_DCHECK(!pkey); + CBS cbs; + CBS_init(&cbs, data, length); + RSA* rsa = RSA_parse_private_key(&cbs); + if (rsa) { + pkey.reset(EVP_PKEY_new()); + EVP_PKEY_assign_RSA(pkey.get(), rsa); + } + } + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); + } + fclose(fp); + return pkey; +} + +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/util/crypto/pem_helpers.h b/chromium/third_party/openscreen/src/util/crypto/pem_helpers.h new file mode 100644 index 00000000000..6012b06915b --- /dev/null +++ b/chromium/third_party/openscreen/src/util/crypto/pem_helpers.h @@ -0,0 +1,24 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UTIL_CRYPTO_PEM_HELPERS_H_ +#define UTIL_CRYPTO_PEM_HELPERS_H_ + +#include <openssl/evp.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" + +namespace openscreen { + +std::vector<std::string> ReadCertificatesFromPemFile( + absl::string_view filename); + +bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename); + +} // namespace openscreen + +#endif // UTIL_CRYPTO_PEM_HELPERS_H_ diff --git a/chromium/third_party/openscreen/src/util/crypto/random_bytes.cc b/chromium/third_party/openscreen/src/util/crypto/random_bytes.cc new file mode 100644 index 00000000000..6a4c9dcbec6 --- /dev/null +++ b/chromium/third_party/openscreen/src/util/crypto/random_bytes.cc @@ -0,0 +1,23 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// 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.begin(), result.size()); + return result; +} + +void GenerateRandomBytes(uint8_t* out, int len) { + // Working cryptography is mandatory for our library to run. + OSP_CHECK(RAND_bytes(out, len) == 1); +} + +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/util/crypto/random_bytes.h b/chromium/third_party/openscreen/src/util/crypto/random_bytes.h new file mode 100644 index 00000000000..3cb2fa8efa6 --- /dev/null +++ b/chromium/third_party/openscreen/src/util/crypto/random_bytes.h @@ -0,0 +1,17 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// 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> + +namespace openscreen { + +std::array<uint8_t, 16> GenerateRandomBytes16(); +void GenerateRandomBytes(uint8_t* out, int len); + +} // namespace openscreen + +#endif // UTIL_CRYPTO_RANDOM_BYTES_H_ diff --git a/chromium/third_party/openscreen/src/util/crypto/random_bytes_unittest.cc b/chromium/third_party/openscreen/src/util/crypto/random_bytes_unittest.cc new file mode 100644 index 00000000000..8129d604129 --- /dev/null +++ b/chromium/third_party/openscreen/src/util/crypto/random_bytes_unittest.cc @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// 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 <algorithm> +#include <utility> + +#include "gtest/gtest.h" + +namespace openscreen { +namespace { + +struct NonZero { + NonZero() = default; + bool operator()(int n) const { return n > 0; } +}; + +} // namespace + +TEST(RandomBytesTest, CanGenerateRandomBytes) { + std::array<uint8_t, 4> bytes; + GenerateRandomBytes(bytes.begin(), bytes.size()); + + NonZero pred; + ASSERT_TRUE(std::any_of(bytes.begin(), bytes.end(), pred)); +} + +TEST(RandomBytesTest, CanGenerate16RandomBytes) { + std::array<uint8_t, 16> bytes = GenerateRandomBytes16(); + + NonZero pred; + ASSERT_TRUE(std::any_of(bytes.begin(), bytes.end(), pred)); +} + +TEST(RandomBytesTest, KeysAreNotIdentical) { + constexpr int kNumKeys = 100; + constexpr int kKeyLength = 100; + std::array<std::array<uint8_t, kKeyLength>, kNumKeys> keys; + for (int i = 0; i < kNumKeys; ++i) { + GenerateRandomBytes(keys[i].begin(), kKeyLength); + } + + std::sort(std::begin(keys), std::end(keys)); + ASSERT_TRUE(std::adjacent_find(std::begin(keys), std::end(keys)) == + std::end(keys)); +} + +} // namespace openscreen diff --git a/chromium/third_party/openscreen/src/util/yet_another_bit_vector_unittest.cc b/chromium/third_party/openscreen/src/util/yet_another_bit_vector_unittest.cc index 98951fe0d38..c67e4371103 100644 --- a/chromium/third_party/openscreen/src/util/yet_another_bit_vector_unittest.cc +++ b/chromium/third_party/openscreen/src/util/yet_another_bit_vector_unittest.cc @@ -20,12 +20,10 @@ constexpr uint8_t kBitPatterns[] = {0b00000000, 0b11111111, 0b01010101, // These are used for testing various vector sizes, begins/ends of ranges, etc. // They will exercise both the "inlined storage" (size <= 64 case) and // "heap-allocated storage" cases. These are all of the prime numbers less than -// 200, and also any non-negative multiples of 64 less than 200. -const int kTestSizes[] = {0, 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, - 29, 31, 37, 41, 43, 47, 53, 59, 61, 64, 67, - 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 128, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 192, 193, 197, 199}; +// 100, and also any non-negative multiples of 64 less than 192. +const int kTestSizes[] = {0, 1, 2, 3, 5, 7, 11, 13, 17, 19, + 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + 64, 67, 71, 73, 79, 83, 89, 97, 127, 128}; // Returns a subspan of |kTestSizes| that contains all values in the range // [first,last]. |