xref: /aosp_15_r20/external/openscreen/cast/common/public/receiver_info.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
1*3f982cf4SFabien Sanglard // Copyright 2019 The Chromium Authors. All rights reserved.
2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be
3*3f982cf4SFabien Sanglard // found in the LICENSE file.
4*3f982cf4SFabien Sanglard 
5*3f982cf4SFabien Sanglard #include "cast/common/public/receiver_info.h"
6*3f982cf4SFabien Sanglard 
7*3f982cf4SFabien Sanglard #include <cctype>
8*3f982cf4SFabien Sanglard #include <cinttypes>
9*3f982cf4SFabien Sanglard #include <string>
10*3f982cf4SFabien Sanglard #include <vector>
11*3f982cf4SFabien Sanglard 
12*3f982cf4SFabien Sanglard #include "absl/strings/numbers.h"
13*3f982cf4SFabien Sanglard #include "absl/strings/str_replace.h"
14*3f982cf4SFabien Sanglard #include "discovery/mdns/public/mdns_constants.h"
15*3f982cf4SFabien Sanglard #include "util/osp_logging.h"
16*3f982cf4SFabien Sanglard 
17*3f982cf4SFabien Sanglard namespace openscreen {
18*3f982cf4SFabien Sanglard namespace cast {
19*3f982cf4SFabien Sanglard namespace {
20*3f982cf4SFabien Sanglard 
21*3f982cf4SFabien Sanglard // Maximum size for the receiver model prefix at start of MDNS service instance
22*3f982cf4SFabien Sanglard // names. Any model names that are larger than this size will be truncated.
23*3f982cf4SFabien Sanglard const size_t kMaxReceiverModelSize = 20;
24*3f982cf4SFabien Sanglard 
25*3f982cf4SFabien Sanglard // Build the MDNS instance name for service. This will be the receiver model (up
26*3f982cf4SFabien Sanglard // to 20 bytes) appended with the virtual receiver ID (receiver UUID) and
27*3f982cf4SFabien Sanglard // optionally appended with extension at the end to resolve name conflicts. The
28*3f982cf4SFabien Sanglard // total MDNS service instance name is kept below 64 bytes so it can easily fit
29*3f982cf4SFabien Sanglard // into a single domain name label.
30*3f982cf4SFabien Sanglard //
31*3f982cf4SFabien Sanglard // NOTE: This value is based on what is currently done by Eureka, not what is
32*3f982cf4SFabien Sanglard // called out in the CastV2 spec. Eureka uses |model|-|uuid|, so the same
33*3f982cf4SFabien Sanglard // convention will be followed here. That being said, the Eureka receiver does
34*3f982cf4SFabien Sanglard // not use the instance ID in any way, so the specific calculation used should
35*3f982cf4SFabien Sanglard // not be important.
CalculateInstanceId(const ReceiverInfo & info)36*3f982cf4SFabien Sanglard std::string CalculateInstanceId(const ReceiverInfo& info) {
37*3f982cf4SFabien Sanglard   // First set the receiver model, truncated to 20 bytes at most. Replace any
38*3f982cf4SFabien Sanglard   // whitespace characters (" ") with hyphens ("-") in the receiver model before
39*3f982cf4SFabien Sanglard   // truncation.
40*3f982cf4SFabien Sanglard   std::string instance_name =
41*3f982cf4SFabien Sanglard       absl::StrReplaceAll(info.model_name, {{" ", "-"}});
42*3f982cf4SFabien Sanglard   instance_name = std::string(instance_name, 0, kMaxReceiverModelSize);
43*3f982cf4SFabien Sanglard 
44*3f982cf4SFabien Sanglard   // Append the receiver ID to the instance name separated by a single
45*3f982cf4SFabien Sanglard   // '-' character if not empty. Strip all hyphens from the receiver ID prior
46*3f982cf4SFabien Sanglard   // to appending it.
47*3f982cf4SFabien Sanglard   std::string receiver_id = absl::StrReplaceAll(info.unique_id, {{"-", ""}});
48*3f982cf4SFabien Sanglard 
49*3f982cf4SFabien Sanglard   if (!instance_name.empty()) {
50*3f982cf4SFabien Sanglard     instance_name.push_back('-');
51*3f982cf4SFabien Sanglard   }
52*3f982cf4SFabien Sanglard   instance_name.append(receiver_id);
53*3f982cf4SFabien Sanglard 
54*3f982cf4SFabien Sanglard   return std::string(instance_name, 0, discovery::kMaxLabelLength);
55*3f982cf4SFabien Sanglard }
56*3f982cf4SFabien Sanglard 
57*3f982cf4SFabien Sanglard // Returns the value for the provided |key| in the |txt| record if it exists;
58*3f982cf4SFabien Sanglard // otherwise, returns an empty string.
GetStringFromRecord(const discovery::DnsSdTxtRecord & txt,const std::string & key)59*3f982cf4SFabien Sanglard std::string GetStringFromRecord(const discovery::DnsSdTxtRecord& txt,
60*3f982cf4SFabien Sanglard                                 const std::string& key) {
61*3f982cf4SFabien Sanglard   std::string result;
62*3f982cf4SFabien Sanglard   const ErrorOr<discovery::DnsSdTxtRecord::ValueRef> value = txt.GetValue(key);
63*3f982cf4SFabien Sanglard   if (value.is_value()) {
64*3f982cf4SFabien Sanglard     const std::vector<uint8_t>& txt_value = value.value().get();
65*3f982cf4SFabien Sanglard     result.assign(txt_value.begin(), txt_value.end());
66*3f982cf4SFabien Sanglard   }
67*3f982cf4SFabien Sanglard   return result;
68*3f982cf4SFabien Sanglard }
69*3f982cf4SFabien Sanglard 
70*3f982cf4SFabien Sanglard }  // namespace
71*3f982cf4SFabien Sanglard 
GetInstanceId() const72*3f982cf4SFabien Sanglard const std::string& ReceiverInfo::GetInstanceId() const {
73*3f982cf4SFabien Sanglard   if (instance_id_ == std::string("")) {
74*3f982cf4SFabien Sanglard     instance_id_ = CalculateInstanceId(*this);
75*3f982cf4SFabien Sanglard   }
76*3f982cf4SFabien Sanglard 
77*3f982cf4SFabien Sanglard   return instance_id_;
78*3f982cf4SFabien Sanglard }
79*3f982cf4SFabien Sanglard 
IsValid() const80*3f982cf4SFabien Sanglard bool ReceiverInfo::IsValid() const {
81*3f982cf4SFabien Sanglard   return (
82*3f982cf4SFabien Sanglard       discovery::IsInstanceValid(GetInstanceId()) && port != 0 &&
83*3f982cf4SFabien Sanglard       !unique_id.empty() &&
84*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(kUniqueIdKey, unique_id) &&
85*3f982cf4SFabien Sanglard       protocol_version >= 2 &&
86*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(
87*3f982cf4SFabien Sanglard           kVersionKey, std::to_string(static_cast<int>(protocol_version))) &&
88*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(
89*3f982cf4SFabien Sanglard           kCapabilitiesKey, std::to_string(capabilities)) &&
90*3f982cf4SFabien Sanglard       (status == ReceiverStatus::kIdle || status == ReceiverStatus::kBusy) &&
91*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(
92*3f982cf4SFabien Sanglard           kStatusKey, std::to_string(static_cast<int>(status))) &&
93*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(kModelNameKey, model_name) &&
94*3f982cf4SFabien Sanglard       !friendly_name.empty() &&
95*3f982cf4SFabien Sanglard       discovery::DnsSdTxtRecord::IsValidTxtValue(kFriendlyNameKey,
96*3f982cf4SFabien Sanglard                                                  friendly_name));
97*3f982cf4SFabien Sanglard }
98*3f982cf4SFabien Sanglard 
ReceiverInfoToDnsSdInstance(const ReceiverInfo & info)99*3f982cf4SFabien Sanglard discovery::DnsSdInstance ReceiverInfoToDnsSdInstance(const ReceiverInfo& info) {
100*3f982cf4SFabien Sanglard   OSP_DCHECK(discovery::IsServiceValid(kCastV2ServiceId));
101*3f982cf4SFabien Sanglard   OSP_DCHECK(discovery::IsDomainValid(kCastV2DomainId));
102*3f982cf4SFabien Sanglard 
103*3f982cf4SFabien Sanglard   OSP_DCHECK(info.IsValid());
104*3f982cf4SFabien Sanglard 
105*3f982cf4SFabien Sanglard   discovery::DnsSdTxtRecord txt;
106*3f982cf4SFabien Sanglard   const bool did_set_everything =
107*3f982cf4SFabien Sanglard       txt.SetValue(kUniqueIdKey, info.unique_id).ok() &&
108*3f982cf4SFabien Sanglard       txt.SetValue(kVersionKey,
109*3f982cf4SFabien Sanglard                    std::to_string(static_cast<int>(info.protocol_version)))
110*3f982cf4SFabien Sanglard           .ok() &&
111*3f982cf4SFabien Sanglard       txt.SetValue(kCapabilitiesKey, std::to_string(info.capabilities)).ok() &&
112*3f982cf4SFabien Sanglard       txt.SetValue(kStatusKey, std::to_string(static_cast<int>(info.status)))
113*3f982cf4SFabien Sanglard           .ok() &&
114*3f982cf4SFabien Sanglard       txt.SetValue(kModelNameKey, info.model_name).ok() &&
115*3f982cf4SFabien Sanglard       txt.SetValue(kFriendlyNameKey, info.friendly_name).ok();
116*3f982cf4SFabien Sanglard   OSP_DCHECK(did_set_everything);
117*3f982cf4SFabien Sanglard 
118*3f982cf4SFabien Sanglard   return discovery::DnsSdInstance(info.GetInstanceId(), kCastV2ServiceId,
119*3f982cf4SFabien Sanglard                                   kCastV2DomainId, std::move(txt), info.port);
120*3f982cf4SFabien Sanglard }
121*3f982cf4SFabien Sanglard 
DnsSdInstanceEndpointToReceiverInfo(const discovery::DnsSdInstanceEndpoint & endpoint)122*3f982cf4SFabien Sanglard ErrorOr<ReceiverInfo> DnsSdInstanceEndpointToReceiverInfo(
123*3f982cf4SFabien Sanglard     const discovery::DnsSdInstanceEndpoint& endpoint) {
124*3f982cf4SFabien Sanglard   if (endpoint.service_id() != kCastV2ServiceId) {
125*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid, "Not a Cast receiver."};
126*3f982cf4SFabien Sanglard   }
127*3f982cf4SFabien Sanglard 
128*3f982cf4SFabien Sanglard   ReceiverInfo record;
129*3f982cf4SFabien Sanglard   for (const IPAddress& address : endpoint.addresses()) {
130*3f982cf4SFabien Sanglard     if (!record.v4_address && address.IsV4()) {
131*3f982cf4SFabien Sanglard       record.v4_address = address;
132*3f982cf4SFabien Sanglard     } else if (!record.v6_address && address.IsV6()) {
133*3f982cf4SFabien Sanglard       record.v6_address = address;
134*3f982cf4SFabien Sanglard     }
135*3f982cf4SFabien Sanglard   }
136*3f982cf4SFabien Sanglard   if (!record.v4_address && !record.v6_address) {
137*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
138*3f982cf4SFabien Sanglard             "No IPv4 nor IPv6 address in record."};
139*3f982cf4SFabien Sanglard   }
140*3f982cf4SFabien Sanglard   record.port = endpoint.port();
141*3f982cf4SFabien Sanglard   if (record.port == 0) {
142*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid, "Invalid TCP port in record."};
143*3f982cf4SFabien Sanglard   }
144*3f982cf4SFabien Sanglard 
145*3f982cf4SFabien Sanglard   // 128-bit integer in hexadecimal format.
146*3f982cf4SFabien Sanglard   record.unique_id = GetStringFromRecord(endpoint.txt(), kUniqueIdKey);
147*3f982cf4SFabien Sanglard   if (record.unique_id.empty()) {
148*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
149*3f982cf4SFabien Sanglard             "Missing receiver unique ID in record."};
150*3f982cf4SFabien Sanglard   }
151*3f982cf4SFabien Sanglard 
152*3f982cf4SFabien Sanglard   // Cast protocol version supported. Begins at 2 and is incremented by 1 with
153*3f982cf4SFabien Sanglard   // each version.
154*3f982cf4SFabien Sanglard   std::string a_decimal_number =
155*3f982cf4SFabien Sanglard       GetStringFromRecord(endpoint.txt(), kVersionKey);
156*3f982cf4SFabien Sanglard   if (a_decimal_number.empty()) {
157*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
158*3f982cf4SFabien Sanglard             "Missing Cast protocol version in record."};
159*3f982cf4SFabien Sanglard   }
160*3f982cf4SFabien Sanglard   constexpr int kMinVersion = 2;   // According to spec.
161*3f982cf4SFabien Sanglard   constexpr int kMaxVersion = 99;  // Implied by spec (field is max of 2 bytes).
162*3f982cf4SFabien Sanglard   int version;
163*3f982cf4SFabien Sanglard   if (!absl::SimpleAtoi(a_decimal_number, &version) || version < kMinVersion ||
164*3f982cf4SFabien Sanglard       version > kMaxVersion) {
165*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
166*3f982cf4SFabien Sanglard             "Invalid Cast protocol version in record."};
167*3f982cf4SFabien Sanglard   }
168*3f982cf4SFabien Sanglard   record.protocol_version = static_cast<uint8_t>(version);
169*3f982cf4SFabien Sanglard 
170*3f982cf4SFabien Sanglard   // A bitset of receiver capabilities.
171*3f982cf4SFabien Sanglard   a_decimal_number = GetStringFromRecord(endpoint.txt(), kCapabilitiesKey);
172*3f982cf4SFabien Sanglard   if (a_decimal_number.empty()) {
173*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
174*3f982cf4SFabien Sanglard             "Missing receiver capabilities in record."};
175*3f982cf4SFabien Sanglard   }
176*3f982cf4SFabien Sanglard   if (!absl::SimpleAtoi(a_decimal_number, &record.capabilities)) {
177*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
178*3f982cf4SFabien Sanglard             "Invalid receiver capabilities field in record."};
179*3f982cf4SFabien Sanglard   }
180*3f982cf4SFabien Sanglard 
181*3f982cf4SFabien Sanglard   // Receiver status flag.
182*3f982cf4SFabien Sanglard   a_decimal_number = GetStringFromRecord(endpoint.txt(), kStatusKey);
183*3f982cf4SFabien Sanglard   if (a_decimal_number == "0") {
184*3f982cf4SFabien Sanglard     record.status = ReceiverStatus::kIdle;
185*3f982cf4SFabien Sanglard   } else if (a_decimal_number == "1") {
186*3f982cf4SFabien Sanglard     record.status = ReceiverStatus::kBusy;
187*3f982cf4SFabien Sanglard   } else {
188*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
189*3f982cf4SFabien Sanglard             "Missing/Invalid receiver status flag in record."};
190*3f982cf4SFabien Sanglard   }
191*3f982cf4SFabien Sanglard 
192*3f982cf4SFabien Sanglard   // [Optional] Receiver model name.
193*3f982cf4SFabien Sanglard   record.model_name = GetStringFromRecord(endpoint.txt(), kModelNameKey);
194*3f982cf4SFabien Sanglard 
195*3f982cf4SFabien Sanglard   // The friendly name of the receiver.
196*3f982cf4SFabien Sanglard   record.friendly_name = GetStringFromRecord(endpoint.txt(), kFriendlyNameKey);
197*3f982cf4SFabien Sanglard   if (record.friendly_name.empty()) {
198*3f982cf4SFabien Sanglard     return {Error::Code::kParameterInvalid,
199*3f982cf4SFabien Sanglard             "Missing receiver friendly name in record."};
200*3f982cf4SFabien Sanglard   }
201*3f982cf4SFabien Sanglard 
202*3f982cf4SFabien Sanglard   return record;
203*3f982cf4SFabien Sanglard }
204*3f982cf4SFabien Sanglard 
205*3f982cf4SFabien Sanglard }  // namespace cast
206*3f982cf4SFabien Sanglard }  // namespace openscreen
207