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