| // 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 "discovery/dnssd/public/dns_sd_instance.h" |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <utility> |
| #include <vector> |
| |
| #include "util/osp_logging.h" |
| |
| namespace openscreen { |
| namespace discovery { |
| namespace { |
| |
| // Maximum number of octets allowed in a single domain name label. |
| constexpr size_t kMaxLabelLength = 63; |
| |
| bool IsValidUtf8(const std::string& string) { |
| for (size_t i = 0; i < string.size(); i++) { |
| if (string[i] >> 5 == 0x06) { // 110xxxxx 10xxxxxx |
| if (i + 1 >= string.size() || string[++i] >> 6 != 0x02) { |
| return false; |
| } |
| } else if (string[i] >> 4 == 0x0E) { // 1110xxxx 10xxxxxx 10xxxxxx |
| if (i + 2 >= string.size() || string[++i] >> 6 != 0x02 || |
| string[++i] >> 6 != 0x02) { |
| return false; |
| } |
| } else if (string[i] >> 3 == 0x1E) { // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
| if (i + 3 >= string.size() || string[++i] >> 6 != 0x02 || |
| string[++i] >> 6 != 0x02 || string[++i] >> 6 != 0x02) { |
| return false; |
| } |
| } else if ((string[i] & 0x80) != 0x0) { // 0xxxxxxx |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool HasControlCharacters(const std::string& string) { |
| for (auto ch : string) { |
| if ((ch >= 0x0 && ch <= 0x1F /* Ascii control characters */) || |
| ch == 0x7F /* DEL character */) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| DnsSdInstance::DnsSdInstance(std::string instance_id, |
| std::string service_id, |
| std::string domain_id, |
| DnsSdTxtRecord txt, |
| uint16_t port, |
| std::vector<Subtype> subtypes) |
| : instance_id_(std::move(instance_id)), |
| service_id_(std::move(service_id)), |
| domain_id_(std::move(domain_id)), |
| txt_(std::move(txt)), |
| port_(port), |
| subtypes_(std::move(subtypes)) { |
| OSP_DCHECK(IsInstanceValid(instance_id_)) |
| << instance_id_ << " is an invalid instance id"; |
| OSP_DCHECK(IsServiceValid(service_id_)) |
| << service_id_ << " is an invalid service id"; |
| OSP_DCHECK(IsDomainValid(domain_id_)) |
| << domain_id_ << " is an invalid domain"; |
| for (const Subtype& subtype : subtypes_) { |
| OSP_DCHECK(IsSubtypeValid(subtype)) << subtype << " is an invalid subtype"; |
| } |
| |
| std::sort(subtypes_.begin(), subtypes_.end()); |
| } |
| |
| DnsSdInstance::DnsSdInstance(const DnsSdInstance& other) = default; |
| |
| DnsSdInstance::DnsSdInstance(DnsSdInstance&& other) = default; |
| |
| DnsSdInstance::~DnsSdInstance() = default; |
| |
| DnsSdInstance& DnsSdInstance::operator=(const DnsSdInstance& rhs) = default; |
| |
| DnsSdInstance& DnsSdInstance::operator=(DnsSdInstance&& rhs) = default; |
| |
| // static |
| bool IsInstanceValid(const std::string& instance) { |
| // According to RFC6763, Instance names must: |
| // - Be encoded in Net-Unicode (which required UTF-8 formatting). |
| // - NOT contain ASCII control characters |
| // - Be no longer than 63 octets. |
| |
| return instance.size() <= kMaxLabelLength && |
| !HasControlCharacters(instance) && IsValidUtf8(instance); |
| } |
| |
| // static |
| bool IsServiceValid(const std::string& service) { |
| // According to RFC6763, the service name "consists of a pair of DNS labels". |
| // "The first label of the pair is an underscore character followed by the |
| // Service Name" and "The second label is either '_tcp' [...] or '_udp'". |
| // According to RFC6335 Section 5.1, the Service Name section must: |
| // Contain from 1 to 15 characters. |
| // - Only contain A-Z, a-Z, 0-9, and the hyphen character. |
| // - Contain at least one letter. |
| // - NOT begin or end with a hyphen. |
| // - NOT contain two adjacent hyphens. |
| if (service.size() > 21 || service.size() < 7) { // Service name size + 6. |
| return false; |
| } |
| |
| const std::string protocol = service.substr(service.size() - 5); |
| if (protocol != "._udp" && protocol != "._tcp") { |
| return false; |
| } |
| |
| if (service[0] != '_' || service[1] == '-' || |
| service[service.size() - 6] == '-') { |
| return false; |
| } |
| |
| bool last_char_hyphen = false; |
| bool seen_letter = false; |
| for (size_t i = 1; i < service.size() - 5; i++) { |
| if (service[i] == '-') { |
| if (last_char_hyphen) { |
| return false; |
| } |
| last_char_hyphen = true; |
| } else if (std::isalpha(service[i])) { |
| last_char_hyphen = false; |
| seen_letter = true; |
| } else if (std::isdigit(service[i])) { |
| last_char_hyphen = false; |
| } else { |
| return false; |
| } |
| } |
| |
| return seen_letter; |
| } |
| |
| // static |
| bool IsDomainValid(const std::string& domain) { |
| // As RFC6763 Section 4.1.3 provides no validation requirements for the domain |
| // section, the following validations are used: |
| // - All labels must be no longer than 63 characters |
| // - Total length must be no more than 256 characters |
| // - Must be encoded using valid UTF8 |
| // - Must not include any ASCII control characters |
| |
| if (domain.size() > 255) { |
| return false; |
| } |
| |
| size_t label_start = 0; |
| for (size_t next_dot = domain.find('.'); next_dot != std::string::npos; |
| next_dot = domain.find('.', label_start)) { |
| if (next_dot - label_start > kMaxLabelLength) { |
| return false; |
| } |
| label_start = next_dot + 1; |
| } |
| |
| return !HasControlCharacters(domain) && IsValidUtf8(domain); |
| } |
| |
| // static |
| bool IsSubtypeValid(const DnsSdInstance::Subtype& subtype) { |
| // As specified in RFC6763 section 9.1, all subtypes may be arbitrary bit |
| // data. Despite this, this implementation has chosen to limit valid subtypes |
| // to only UTF8 character strings. Therefore, the subtype must: |
| // - Be encoded in Net-Unicode (which required UTF-8 formatting). |
| // - NOT contain ASCII control characters |
| // - Be no longer than 63 octets. |
| // - Be of length one label. |
| return subtype.size() <= kMaxLabelLength && |
| subtype.find('.') == std::string::npos && |
| !HasControlCharacters(subtype) && IsValidUtf8(subtype); |
| } |
| |
| bool operator<(const DnsSdInstance& lhs, const DnsSdInstance& rhs) { |
| if (lhs.port_ != rhs.port_) { |
| return lhs.port_ < rhs.port_; |
| } |
| |
| int comp = lhs.instance_id_.compare(rhs.instance_id_); |
| if (comp != 0) { |
| return comp < 0; |
| } |
| |
| comp = lhs.service_id_.compare(rhs.service_id_); |
| if (comp != 0) { |
| return comp < 0; |
| } |
| |
| comp = lhs.domain_id_.compare(rhs.domain_id_); |
| if (comp != 0) { |
| return comp < 0; |
| } |
| |
| if (lhs.subtypes_.size() != rhs.subtypes_.size()) { |
| return lhs.subtypes_.size() < rhs.subtypes_.size(); |
| } |
| |
| for (size_t i = 0; i < lhs.subtypes_.size(); i++) { |
| comp = lhs.subtypes_[i].compare(rhs.subtypes_[i]); |
| if (comp != 0) { |
| return comp < 0; |
| } |
| } |
| |
| return lhs.txt_ < rhs.txt_; |
| } |
| |
| } // namespace discovery |
| } // namespace openscreen |