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