blob: 1510b294ae595ac07629e42b9c68e78ee88fbd19 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/connectivity/network/mdns/service/mdns_names.h"
#include "src/lib/syslog/cpp/logger.h"
namespace mdns {
namespace {
static const std::string kLocalDomainName = "local.";
static const std::string kSubtypeSeparator = "._sub.";
static const std::string kLabelSeparator = ".";
static const std::string kTcpSuffix = "._tcp.";
static const std::string kUdpSuffix = "._udp.";
static constexpr size_t kMaxHostNameLength = 253 - 6; // 6 for local domain.
static constexpr size_t kMaxServiceNameLength = 22;
static constexpr size_t kMaxTextStringLength = 255;
static constexpr size_t kMaxLabelLength = 63;
// Concatenates |strings|.
std::string Concatenate(const std::initializer_list<const std::string*>& strings) {
std::string result;
size_t result_size = 0;
for (auto string : strings) {
FX_DCHECK(string);
result_size += string->length();
}
result.reserve(result_size);
for (auto& string : strings) {
FX_DCHECK(string);
result.append(*string);
}
return result;
}
// Parses a string. Match functions either return true and update the position
// of the parser or return false and leave the position unchanged.
class Parser {
public:
Parser(const std::string& str) : str_(str), pos_(0) {}
// Matches end-of-string.
bool MatchEnd() { return pos_ == str_.length(); }
// Matches a specified string.
bool Match(const std::string& to_match) {
if (pos_ + to_match.length() > str_.length()) {
return false;
}
if (str_.compare(pos_, to_match.length(), to_match) != 0) {
return false;
}
pos_ += to_match.length();
return true;
}
// Matches a DNS label, which must be at the start of string or be preceded by
// a '.'.
bool MatchDnsLabel(std::string* label_out = nullptr) {
if (pos_ == str_.length()) {
return false;
}
size_t new_pos = str_.find(kLabelSeparator, pos_);
if (new_pos == pos_) {
// Zero length.
return false;
} else if (new_pos == std::string::npos) {
new_pos = str_.length();
}
if (new_pos - pos_ > kMaxLabelLength) {
// Too long.
return false;
}
if (label_out) {
*label_out = str_.substr(pos_, new_pos - pos_);
}
pos_ = new_pos;
return true;
}
// Resets the position to the start of the string.
void Restart() { pos_ = 0; }
private:
const std::string& str_;
size_t pos_;
};
} // namespace
// static
const std::string MdnsNames::kAnyServiceFullName = "_services._dns-sd._udp.local.";
// static
std::string MdnsNames::LocalHostFullName(const std::string& host_name) {
FX_DCHECK(IsValidHostName(host_name));
return Concatenate({&host_name, &kLabelSeparator, &kLocalDomainName});
}
// static
std::string MdnsNames::LocalServiceFullName(const std::string& service_name) {
FX_DCHECK(IsValidServiceName(service_name));
return Concatenate({&service_name, &kLocalDomainName});
}
// static
std::string MdnsNames::LocalServiceSubtypeFullName(const std::string& service_name,
const std::string& subtype) {
FX_DCHECK(IsValidServiceName(service_name));
FX_DCHECK(IsValidSubtypeName(subtype));
return Concatenate({&subtype, &kSubtypeSeparator, &service_name, &kLocalDomainName});
}
// static
std::string MdnsNames::LocalInstanceFullName(const std::string& instance_name,
const std::string& service_name) {
FX_DCHECK(IsValidInstanceName(instance_name));
FX_DCHECK(IsValidServiceName(service_name));
return Concatenate({&instance_name, &kLabelSeparator, &service_name, &kLocalDomainName});
}
// static
bool MdnsNames::ExtractInstanceName(const std::string& instance_full_name,
const std::string& service_name, std::string* instance_name) {
FX_DCHECK(IsValidServiceName(service_name));
FX_DCHECK(instance_name);
// instance_name "." service_name kLocalDomainName
Parser parser(instance_full_name);
return parser.MatchDnsLabel(instance_name) && parser.Match(kLabelSeparator) &&
parser.Match(service_name) && parser.Match(kLocalDomainName) && parser.MatchEnd();
}
// static
bool MdnsNames::MatchServiceName(const std::string& name, const std::string& service_name,
std::string* subtype_out) {
FX_DCHECK(IsValidServiceName(service_name));
FX_DCHECK(subtype_out);
// [ subtype kSubtypeSeparator ] service_name kLocalDomainName
Parser parser(name);
if (!parser.MatchDnsLabel(subtype_out) || !parser.Match(kSubtypeSeparator)) {
*subtype_out = "";
parser.Restart();
}
return parser.Match(service_name) && parser.Match(kLocalDomainName) && parser.MatchEnd();
}
// static
bool MdnsNames::IsValidHostName(const std::string& host_name) {
// A host name is one or more labels separated by '.'s. A label is 1..63
// characters long not including separators. A complete host name with
// separators must be at most 247 characters long (253 minus 6 to
// accommodate a ".local" suffix).
if (host_name.length() > kMaxHostNameLength) {
return false;
}
Parser parser(host_name);
if (!parser.MatchDnsLabel()) {
return false;
}
while (!parser.MatchEnd()) {
if (!parser.Match(kLabelSeparator) || !parser.MatchDnsLabel()) {
return false;
}
}
return true;
}
// static
bool MdnsNames::IsValidServiceName(const std::string& service_name) {
// A service name is two labels, both terminated with '.'. The first label
// must be [1..16] characters, and the first character must be '_'. The
// second label must be "_tcp" or "_udp".
if (service_name.empty() || service_name.length() > kMaxServiceNameLength ||
service_name[0] != '_') {
return false;
}
Parser parser(service_name);
return parser.MatchDnsLabel() && (parser.Match(kTcpSuffix) || parser.Match(kUdpSuffix)) &&
parser.MatchEnd();
}
// static
bool MdnsNames::IsValidInstanceName(const std::string& instance_name) {
// Instance names consist of a single label.
return instance_name.length() > 0 && instance_name.length() <= kMaxLabelLength &&
instance_name.find(kLabelSeparator) == std::string::npos;
}
// static
bool MdnsNames::IsValidSubtypeName(const std::string& subtype_name) {
// Subtype names consist of a single label.
return subtype_name.length() > 0 && subtype_name.length() <= kMaxLabelLength &&
subtype_name.find(kLabelSeparator) == std::string::npos;
}
// static
bool MdnsNames::IsValidTextString(const std::string& text_string) {
// Text strings must be at most 255 characters long.
return text_string.length() <= kMaxTextStringLength;
}
} // namespace mdns