diff options
Diffstat (limited to 'cast/common/public/receiver_info.cc')
-rw-r--r-- | cast/common/public/receiver_info.cc | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/cast/common/public/receiver_info.cc b/cast/common/public/receiver_info.cc new file mode 100644 index 00000000..ec45efea --- /dev/null +++ b/cast/common/public/receiver_info.cc @@ -0,0 +1,206 @@ +// 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/public/receiver_info.h" + +#include <cctype> +#include <cinttypes> +#include <string> +#include <vector> + +#include "absl/strings/numbers.h" +#include "absl/strings/str_replace.h" +#include "discovery/mdns/public/mdns_constants.h" +#include "util/osp_logging.h" + +namespace openscreen { +namespace cast { +namespace { + +// Maximum size for the receiver model prefix at start of MDNS service instance +// names. Any model names that are larger than this size will be truncated. +const size_t kMaxReceiverModelSize = 20; + +// Build the MDNS instance name for service. This will be the receiver model (up +// to 20 bytes) appended with the virtual receiver ID (receiver UUID) and +// optionally appended with extension at the end to resolve name conflicts. The +// total MDNS service instance name is kept below 64 bytes so it can easily fit +// into a single domain name label. +// +// NOTE: This value is based on what is currently done by Eureka, not what is +// called out in the CastV2 spec. Eureka uses |model|-|uuid|, so the same +// convention will be followed here. That being said, the Eureka receiver does +// not use the instance ID in any way, so the specific calculation used should +// not be important. +std::string CalculateInstanceId(const ReceiverInfo& info) { + // First set the receiver model, truncated to 20 bytes at most. Replace any + // whitespace characters (" ") with hyphens ("-") in the receiver model before + // truncation. + std::string instance_name = + absl::StrReplaceAll(info.model_name, {{" ", "-"}}); + instance_name = std::string(instance_name, 0, kMaxReceiverModelSize); + + // Append the receiver ID to the instance name separated by a single + // '-' character if not empty. Strip all hyphens from the receiver ID prior + // to appending it. + std::string receiver_id = absl::StrReplaceAll(info.unique_id, {{"-", ""}}); + + if (!instance_name.empty()) { + instance_name.push_back('-'); + } + instance_name.append(receiver_id); + + return std::string(instance_name, 0, discovery::kMaxLabelLength); +} + +// Returns the value for the provided |key| in the |txt| record if it exists; +// otherwise, returns an empty string. +std::string GetStringFromRecord(const discovery::DnsSdTxtRecord& txt, + const std::string& key) { + std::string result; + const ErrorOr<discovery::DnsSdTxtRecord::ValueRef> value = txt.GetValue(key); + if (value.is_value()) { + const std::vector<uint8_t>& txt_value = value.value().get(); + result.assign(txt_value.begin(), txt_value.end()); + } + return result; +} + +} // namespace + +const std::string& ReceiverInfo::GetInstanceId() const { + if (instance_id_ == std::string("")) { + instance_id_ = CalculateInstanceId(*this); + } + + return instance_id_; +} + +bool ReceiverInfo::IsValid() const { + return ( + discovery::IsInstanceValid(GetInstanceId()) && port != 0 && + !unique_id.empty() && + discovery::DnsSdTxtRecord::IsValidTxtValue(kUniqueIdKey, unique_id) && + protocol_version >= 2 && + discovery::DnsSdTxtRecord::IsValidTxtValue( + kVersionKey, std::to_string(static_cast<int>(protocol_version))) && + discovery::DnsSdTxtRecord::IsValidTxtValue( + kCapabilitiesKey, std::to_string(capabilities)) && + (status == ReceiverStatus::kIdle || status == ReceiverStatus::kBusy) && + discovery::DnsSdTxtRecord::IsValidTxtValue( + kStatusKey, std::to_string(static_cast<int>(status))) && + discovery::DnsSdTxtRecord::IsValidTxtValue(kModelNameKey, model_name) && + !friendly_name.empty() && + discovery::DnsSdTxtRecord::IsValidTxtValue(kFriendlyNameKey, + friendly_name)); +} + +discovery::DnsSdInstance ReceiverInfoToDnsSdInstance(const ReceiverInfo& info) { + OSP_DCHECK(discovery::IsServiceValid(kCastV2ServiceId)); + OSP_DCHECK(discovery::IsDomainValid(kCastV2DomainId)); + + OSP_DCHECK(info.IsValid()); + + discovery::DnsSdTxtRecord txt; + const bool did_set_everything = + txt.SetValue(kUniqueIdKey, info.unique_id).ok() && + txt.SetValue(kVersionKey, + std::to_string(static_cast<int>(info.protocol_version))) + .ok() && + txt.SetValue(kCapabilitiesKey, std::to_string(info.capabilities)).ok() && + txt.SetValue(kStatusKey, std::to_string(static_cast<int>(info.status))) + .ok() && + txt.SetValue(kModelNameKey, info.model_name).ok() && + txt.SetValue(kFriendlyNameKey, info.friendly_name).ok(); + OSP_DCHECK(did_set_everything); + + return discovery::DnsSdInstance(info.GetInstanceId(), kCastV2ServiceId, + kCastV2DomainId, std::move(txt), info.port); +} + +ErrorOr<ReceiverInfo> DnsSdInstanceEndpointToReceiverInfo( + const discovery::DnsSdInstanceEndpoint& endpoint) { + if (endpoint.service_id() != kCastV2ServiceId) { + return {Error::Code::kParameterInvalid, "Not a Cast receiver."}; + } + + ReceiverInfo record; + for (const IPAddress& address : endpoint.addresses()) { + if (!record.v4_address && address.IsV4()) { + record.v4_address = address; + } else if (!record.v6_address && address.IsV6()) { + record.v6_address = address; + } + } + if (!record.v4_address && !record.v6_address) { + return {Error::Code::kParameterInvalid, + "No IPv4 nor IPv6 address in record."}; + } + record.port = endpoint.port(); + if (record.port == 0) { + return {Error::Code::kParameterInvalid, "Invalid TCP port in record."}; + } + + // 128-bit integer in hexadecimal format. + record.unique_id = GetStringFromRecord(endpoint.txt(), kUniqueIdKey); + if (record.unique_id.empty()) { + return {Error::Code::kParameterInvalid, + "Missing receiver unique ID in record."}; + } + + // Cast protocol version supported. Begins at 2 and is incremented by 1 with + // each version. + std::string a_decimal_number = + GetStringFromRecord(endpoint.txt(), kVersionKey); + if (a_decimal_number.empty()) { + return {Error::Code::kParameterInvalid, + "Missing Cast protocol version in record."}; + } + constexpr int kMinVersion = 2; // According to spec. + constexpr int kMaxVersion = 99; // Implied by spec (field is max of 2 bytes). + int version; + if (!absl::SimpleAtoi(a_decimal_number, &version) || version < kMinVersion || + version > kMaxVersion) { + return {Error::Code::kParameterInvalid, + "Invalid Cast protocol version in record."}; + } + record.protocol_version = static_cast<uint8_t>(version); + + // A bitset of receiver capabilities. + a_decimal_number = GetStringFromRecord(endpoint.txt(), kCapabilitiesKey); + if (a_decimal_number.empty()) { + return {Error::Code::kParameterInvalid, + "Missing receiver capabilities in record."}; + } + if (!absl::SimpleAtoi(a_decimal_number, &record.capabilities)) { + return {Error::Code::kParameterInvalid, + "Invalid receiver capabilities field in record."}; + } + + // Receiver status flag. + a_decimal_number = GetStringFromRecord(endpoint.txt(), kStatusKey); + if (a_decimal_number == "0") { + record.status = ReceiverStatus::kIdle; + } else if (a_decimal_number == "1") { + record.status = ReceiverStatus::kBusy; + } else { + return {Error::Code::kParameterInvalid, + "Missing/Invalid receiver status flag in record."}; + } + + // [Optional] Receiver model name. + record.model_name = GetStringFromRecord(endpoint.txt(), kModelNameKey); + + // The friendly name of the receiver. + record.friendly_name = GetStringFromRecord(endpoint.txt(), kFriendlyNameKey); + if (record.friendly_name.empty()) { + return {Error::Code::kParameterInvalid, + "Missing receiver friendly name in record."}; + } + + return record; +} + +} // namespace cast +} // namespace openscreen |