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 "discovery/dnssd/public/dns_sd_instance.h"
6
7 #include <algorithm>
8 #include <cctype>
9 #include <utility>
10 #include <vector>
11
12 #include "util/osp_logging.h"
13
14 namespace openscreen {
15 namespace discovery {
16 namespace {
17
18 // Maximum number of octets allowed in a single domain name label.
19 constexpr size_t kMaxLabelLength = 63;
20
IsValidUtf8(const std::string & string)21 bool IsValidUtf8(const std::string& string) {
22 for (size_t i = 0; i < string.size(); i++) {
23 if (string[i] >> 5 == 0x06) { // 110xxxxx 10xxxxxx
24 if (i + 1 >= string.size() || string[++i] >> 6 != 0x02) {
25 return false;
26 }
27 } else if (string[i] >> 4 == 0x0E) { // 1110xxxx 10xxxxxx 10xxxxxx
28 if (i + 2 >= string.size() || string[++i] >> 6 != 0x02 ||
29 string[++i] >> 6 != 0x02) {
30 return false;
31 }
32 } else if (string[i] >> 3 == 0x1E) { // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
33 if (i + 3 >= string.size() || string[++i] >> 6 != 0x02 ||
34 string[++i] >> 6 != 0x02 || string[++i] >> 6 != 0x02) {
35 return false;
36 }
37 } else if ((string[i] & 0x80) != 0x0) { // 0xxxxxxx
38 return false;
39 }
40 }
41 return true;
42 }
43
HasControlCharacters(const std::string & string)44 bool HasControlCharacters(const std::string& string) {
45 for (auto ch : string) {
46 if ((ch >= 0x0 && ch <= 0x1F /* Ascii control characters */) ||
47 ch == 0x7F /* DEL character */) {
48 return true;
49 }
50 }
51 return false;
52 }
53
54 } // namespace
55
DnsSdInstance(std::string instance_id,std::string service_id,std::string domain_id,DnsSdTxtRecord txt,uint16_t port,std::vector<Subtype> subtypes)56 DnsSdInstance::DnsSdInstance(std::string instance_id,
57 std::string service_id,
58 std::string domain_id,
59 DnsSdTxtRecord txt,
60 uint16_t port,
61 std::vector<Subtype> subtypes)
62 : instance_id_(std::move(instance_id)),
63 service_id_(std::move(service_id)),
64 domain_id_(std::move(domain_id)),
65 txt_(std::move(txt)),
66 port_(port),
67 subtypes_(std::move(subtypes)) {
68 OSP_DCHECK(IsInstanceValid(instance_id_))
69 << instance_id_ << " is an invalid instance id";
70 OSP_DCHECK(IsServiceValid(service_id_))
71 << service_id_ << " is an invalid service id";
72 OSP_DCHECK(IsDomainValid(domain_id_))
73 << domain_id_ << " is an invalid domain";
74 for (const Subtype& subtype : subtypes_) {
75 OSP_DCHECK(IsSubtypeValid(subtype)) << subtype << " is an invalid subtype";
76 }
77
78 std::sort(subtypes_.begin(), subtypes_.end());
79 }
80
81 DnsSdInstance::DnsSdInstance(const DnsSdInstance& other) = default;
82
83 DnsSdInstance::DnsSdInstance(DnsSdInstance&& other) = default;
84
85 DnsSdInstance::~DnsSdInstance() = default;
86
87 DnsSdInstance& DnsSdInstance::operator=(const DnsSdInstance& rhs) = default;
88
89 DnsSdInstance& DnsSdInstance::operator=(DnsSdInstance&& rhs) = default;
90
91 // static
IsInstanceValid(const std::string & instance)92 bool IsInstanceValid(const std::string& instance) {
93 // According to RFC6763, Instance names must:
94 // - Be encoded in Net-Unicode (which required UTF-8 formatting).
95 // - NOT contain ASCII control characters
96 // - Be no longer than 63 octets.
97
98 return instance.size() <= kMaxLabelLength &&
99 !HasControlCharacters(instance) && IsValidUtf8(instance);
100 }
101
102 // static
IsServiceValid(const std::string & service)103 bool IsServiceValid(const std::string& service) {
104 // According to RFC6763, the service name "consists of a pair of DNS labels".
105 // "The first label of the pair is an underscore character followed by the
106 // Service Name" and "The second label is either '_tcp' [...] or '_udp'".
107 // According to RFC6335 Section 5.1, the Service Name section must:
108 // Contain from 1 to 15 characters.
109 // - Only contain A-Z, a-Z, 0-9, and the hyphen character.
110 // - Contain at least one letter.
111 // - NOT begin or end with a hyphen.
112 // - NOT contain two adjacent hyphens.
113 if (service.size() > 21 || service.size() < 7) { // Service name size + 6.
114 return false;
115 }
116
117 const std::string protocol = service.substr(service.size() - 5);
118 if (protocol != "._udp" && protocol != "._tcp") {
119 return false;
120 }
121
122 if (service[0] != '_' || service[1] == '-' ||
123 service[service.size() - 6] == '-') {
124 return false;
125 }
126
127 bool last_char_hyphen = false;
128 bool seen_letter = false;
129 for (size_t i = 1; i < service.size() - 5; i++) {
130 if (service[i] == '-') {
131 if (last_char_hyphen) {
132 return false;
133 }
134 last_char_hyphen = true;
135 } else if (std::isalpha(service[i])) {
136 last_char_hyphen = false;
137 seen_letter = true;
138 } else if (std::isdigit(service[i])) {
139 last_char_hyphen = false;
140 } else {
141 return false;
142 }
143 }
144
145 return seen_letter;
146 }
147
148 // static
IsDomainValid(const std::string & domain)149 bool IsDomainValid(const std::string& domain) {
150 // As RFC6763 Section 4.1.3 provides no validation requirements for the domain
151 // section, the following validations are used:
152 // - All labels must be no longer than 63 characters
153 // - Total length must be no more than 256 characters
154 // - Must be encoded using valid UTF8
155 // - Must not include any ASCII control characters
156
157 if (domain.size() > 255) {
158 return false;
159 }
160
161 size_t label_start = 0;
162 for (size_t next_dot = domain.find('.'); next_dot != std::string::npos;
163 next_dot = domain.find('.', label_start)) {
164 if (next_dot - label_start > kMaxLabelLength) {
165 return false;
166 }
167 label_start = next_dot + 1;
168 }
169
170 return !HasControlCharacters(domain) && IsValidUtf8(domain);
171 }
172
173 // static
IsSubtypeValid(const DnsSdInstance::Subtype & subtype)174 bool IsSubtypeValid(const DnsSdInstance::Subtype& subtype) {
175 // As specified in RFC6763 section 9.1, all subtypes may be arbitrary bit
176 // data. Despite this, this implementation has chosen to limit valid subtypes
177 // to only UTF8 character strings. Therefore, the subtype must:
178 // - Be encoded in Net-Unicode (which required UTF-8 formatting).
179 // - NOT contain ASCII control characters
180 // - Be no longer than 63 octets.
181 // - Be of length one label.
182 return subtype.size() <= kMaxLabelLength &&
183 subtype.find('.') == std::string::npos &&
184 !HasControlCharacters(subtype) && IsValidUtf8(subtype);
185 }
186
operator <(const DnsSdInstance & lhs,const DnsSdInstance & rhs)187 bool operator<(const DnsSdInstance& lhs, const DnsSdInstance& rhs) {
188 if (lhs.port_ != rhs.port_) {
189 return lhs.port_ < rhs.port_;
190 }
191
192 int comp = lhs.instance_id_.compare(rhs.instance_id_);
193 if (comp != 0) {
194 return comp < 0;
195 }
196
197 comp = lhs.service_id_.compare(rhs.service_id_);
198 if (comp != 0) {
199 return comp < 0;
200 }
201
202 comp = lhs.domain_id_.compare(rhs.domain_id_);
203 if (comp != 0) {
204 return comp < 0;
205 }
206
207 if (lhs.subtypes_.size() != rhs.subtypes_.size()) {
208 return lhs.subtypes_.size() < rhs.subtypes_.size();
209 }
210
211 for (size_t i = 0; i < lhs.subtypes_.size(); i++) {
212 comp = lhs.subtypes_[i].compare(rhs.subtypes_[i]);
213 if (comp != 0) {
214 return comp < 0;
215 }
216 }
217
218 return lhs.txt_ < rhs.txt_;
219 }
220
221 } // namespace discovery
222 } // namespace openscreen
223