blob: 6d27ba078eb298264a3d187334680ee79f1b8ae1 [file] [log] [blame]
// Copyright 2019 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/config.h"
#include <lib/syslog/cpp/macros.h>
#include <sstream>
#include "src/connectivity/network/mdns/service/mdns_names.h"
#include "src/lib/json_parser/rapidjson_validation.h"
namespace mdns {
namespace {
const char kSchema[] = R"({
"type": "object",
"additionalProperties": false,
"properties": {
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"v4_multicast_address": {
"type": "string",
"minLength": 7,
"maxLength": 15
},
"v6_multicast_address": {
"type": "string",
"minLength": 4,
"maxLength": 39
},
"perform_host_name_probe": {
"type": "boolean"
},
"publications": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"service": {
"type": "string",
"minLength": 8,
"maxLength": 22
},
"instance": {
"type": "string",
"minLength": 1,
"maxLength": 63
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"text": {
"type": "array",
"items": {
"type": "string",
"maxLength": 255
}
},
"perform_probe": {
"type": "boolean"
},
"media": {
"type": "string",
"enum": ["wired", "wireless", "both"]
}
},
"required": ["service","port"]
}
}
}
})";
const char kPortKey[] = "port";
const char kV4MultcastAddressKey[] = "v4_multicast_address";
const char kV6MultcastAddressKey[] = "v6_multicast_address";
const char kPerformHostNameProbeKey[] = "perform_host_name_probe";
const char kPublicationsKey[] = "publications";
const char kServiceKey[] = "service";
const char kInstanceKey[] = "instance";
const char kTextKey[] = "text";
const char kPerformProbeKey[] = "perform_probe";
const char kMediaKey[] = "media";
const char kMediaValueWired[] = "wired";
const char kMediaValueWireless[] = "wireless";
const char kMediaValueBoth[] = "both";
} // namespace
// static
const char Config::kConfigDir[] = "/config/data";
void Config::ReadConfigFiles(const std::string& host_name, const std::string& config_dir) {
FX_DCHECK(MdnsNames::IsValidHostName(host_name));
auto schema_result = json_parser::InitSchema(kSchema);
FX_CHECK(schema_result.is_ok()) << schema_result.error_value().ToString();
auto schema = std::move(schema_result.value());
// |ParseFromDirectory| treats a non-existent directory the same as an empty directory, which
// is what we want.
parser_.ParseFromDirectory(config_dir, [this, &schema, &host_name](rapidjson::Document document) {
auto validation_result = json_parser::ValidateSchema(document, schema);
if (validation_result.is_error()) {
parser_.ReportError(validation_result.error_value());
return;
}
IntegrateDocument(document, host_name);
});
}
void Config::IntegrateDocument(const rapidjson::Document& document, const std::string& host_name) {
FX_DCHECK(document.IsObject());
if (document.HasMember(kPortKey)) {
FX_DCHECK(document[kPortKey].IsUint());
FX_DCHECK(document[kPortKey].GetUint() >= 1);
FX_DCHECK(document[kPortKey].GetUint() <= 65535);
addresses_.SetPort(inet::IpPort::From_uint16_t(document[kPortKey].GetUint()));
}
if (document.HasMember(kV4MultcastAddressKey)) {
FX_DCHECK(document[kV4MultcastAddressKey].IsString());
auto address =
inet::IpAddress::FromString(document[kV4MultcastAddressKey].GetString(), AF_INET);
if (!address.is_valid()) {
parser_.ReportError((std::stringstream() << kV4MultcastAddressKey << " value "
<< document[kV4MultcastAddressKey].GetString()
<< " is not a valid IPV4 address.")
.str());
return;
}
addresses_.SetMulticastAddress(address);
}
if (document.HasMember(kV6MultcastAddressKey)) {
FX_DCHECK(document[kV6MultcastAddressKey].IsString());
auto address =
inet::IpAddress::FromString(document[kV6MultcastAddressKey].GetString(), AF_INET6);
if (!address.is_valid()) {
parser_.ReportError((std::stringstream() << kV6MultcastAddressKey << " value "
<< document[kV6MultcastAddressKey].GetString()
<< " is not a valid IPV6 address.")
.str());
return;
}
addresses_.SetMulticastAddress(address);
}
if (document.HasMember(kPerformHostNameProbeKey)) {
FX_DCHECK(document[kPerformHostNameProbeKey].IsBool());
SetPerformHostNameProbe(document[kPerformHostNameProbeKey].GetBool());
if (parser_.HasError()) {
return;
}
}
if (document.HasMember(kPublicationsKey)) {
FX_DCHECK(document[kPublicationsKey].IsArray());
for (auto& item : document[kPublicationsKey].GetArray()) {
IntegratePublication(item, host_name);
if (parser_.HasError()) {
return;
}
}
}
}
void Config::IntegratePublication(const rapidjson::Value& value, const std::string& host_name) {
FX_DCHECK(value.IsObject());
FX_DCHECK(value.HasMember(kServiceKey));
FX_DCHECK(value[kServiceKey].IsString());
FX_DCHECK(value.HasMember(kPortKey));
FX_DCHECK(value[kPortKey].IsUint());
FX_DCHECK(value[kPortKey].GetUint() >= 1);
FX_DCHECK(value[kPortKey].GetUint() <= 65535);
auto service = value[kServiceKey].GetString();
if (!MdnsNames::IsValidServiceName(service)) {
parser_.ReportError((std::stringstream()
<< kServiceKey << " value " << service << " is not a valid service name.")
.str());
return;
}
std::string instance;
if (value.HasMember(kInstanceKey)) {
instance = value[kInstanceKey].GetString();
if (!MdnsNames::IsValidInstanceName(instance)) {
parser_.ReportError((std::stringstream() << kInstanceKey << " value " << instance
<< " is not a valid instance name.")
.str());
return;
}
} else {
instance = host_name;
if (!MdnsNames::IsValidInstanceName(instance)) {
parser_.ReportError((std::stringstream() << "Publication of service " << service
<< " specifies that the host name should be "
"used as the instance name, but "
<< host_name << "is not a valid instance name.")
.str());
return;
}
}
std::vector<std::string> text;
if (value.HasMember(kTextKey)) {
FX_DCHECK(value[kTextKey].IsArray());
for (auto& item : value[kTextKey].GetArray()) {
FX_DCHECK(item.IsString());
if (!MdnsNames::IsValidTextString(item.GetString())) {
parser_.ReportError((std::stringstream() << kTextKey << " item value " << item.GetString()
<< " is not avalid text string.")
.str());
return;
}
text.push_back(item.GetString());
}
}
bool perform_probe = true;
if (value.HasMember(kPerformProbeKey)) {
FX_DCHECK(value[kPerformProbeKey].IsBool());
perform_probe = value[kPerformProbeKey].GetBool();
}
Media media = Media::kBoth;
if (value.HasMember(kMediaKey)) {
FX_DCHECK(value[kMediaKey].IsString());
std::string media_string = value[kMediaKey].GetString();
if (media_string == kMediaValueWired) {
media = Media::kWired;
} else if (media_string == kMediaValueWireless) {
media = Media::kWireless;
} else {
FX_DCHECK(media_string == kMediaValueBoth);
}
}
publications_.emplace_back(Publication{
.service_ = service,
.instance_ = instance,
.publication_ =
Mdns::Publication::Create(inet::IpPort::From_uint16_t(value[kPortKey].GetUint()), text),
.perform_probe_ = perform_probe,
.media_ = media});
}
void Config::SetPerformHostNameProbe(bool perform_host_name_probe) {
if (perform_host_name_probe_.has_value() &&
perform_host_name_probe_.value() != perform_host_name_probe) {
parser_.ReportError(
(std::stringstream() << "Conflicting " << kPerformHostNameProbeKey << " value.").str());
return;
}
perform_host_name_probe_ = perform_host_name_probe;
}
} // namespace mdns