blob: c0e737a56ad9c129ac8593532e010efb5d974330 [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/util/mdns_params.h"
#include <lib/syslog/cpp/macros.h>
#include <functional>
#include <iostream>
#include "src/lib/fxl/strings/split_string.h"
namespace mdns {
namespace {
struct Command {
std::string name_;
MdnsParams::CommandVerb verb_;
uint32_t arg_count_;
std::function<bool(const std::vector<std::string>& args)> action_;
};
static const std::string kTcpSuffix = "._tcp.";
static const std::string kUdpSuffix = "._udp.";
} // namespace
// TODO(dalesat): Remove publish/unpublish commands.
MdnsParams::MdnsParams(const fxl::CommandLine& command_line) {
std::vector<Command> commands{
{"resolve", CommandVerb::kResolve, 1,
[this](const std::vector<std::string>& args) {
return ParseHostName(args[1], &host_name_);
}},
{"subscribe", CommandVerb::kSubscribe, 1,
[this](const std::vector<std::string>& args) {
return ParseServiceName(args[1], &service_name_);
}},
{"respond", CommandVerb::kRespond, 3, [this](const std::vector<std::string>& args) {
if (!ParseServiceName(args[1], &service_name_) ||
!ParseInstanceName(args[2], &instance_name_)) {
return false;
}
if (!Parse(args[3], &port_)) {
std::cout << "'" << args[3] << "' is not a valid port\n\n";
return false;
}
return true;
}}};
is_valid_ = false;
if (command_line.positional_args().empty()) {
Usage();
return;
}
std::string value_string;
if (command_line.GetOptionValue("timeout", &value_string) &&
!Parse(value_string, &timeout_seconds_)) {
std::cout << "'" << value_string << "' is not a valid timeout value\n\n";
Usage();
return;
}
if (command_line.GetOptionValue("text", &value_string) && !Parse(value_string, &text_)) {
std::cout << "'" << value_string << "' is not a valid text value\n\n";
Usage();
return;
}
if (command_line.GetOptionValue("announce", &value_string) && !Parse(value_string, &announce_)) {
std::cout << "'" << value_string << "' is not a valid announce value\n\n";
Usage();
return;
}
for (std::string& subtype : announce_) {
FX_DCHECK(!subtype.empty());
if (subtype[subtype.size() - 1] == '.') {
std::cout << "subtype '" << subtype << "' must not end in '.'\n\n";
Usage();
return;
}
}
const std::string& verb = command_line.positional_args()[0];
for (const Command& command : commands) {
if (verb == command.name_) {
if (command_line.positional_args().size() != command.arg_count_ + 1) {
Usage();
return;
}
if (command.action_ && !command.action_(command_line.positional_args())) {
Usage();
return;
}
command_verb_ = command.verb_;
is_valid_ = true;
}
}
if (!is_valid_) {
Usage();
}
}
void MdnsParams::Usage() {
std::cout << "commands:\n";
std::cout << " resolve <host_name>\n";
std::cout << " subscribe <service_name>\n";
std::cout << " respond <service_name> <instance_name> <port>\n";
std::cout << "options:\n";
std::cout << " --timeout=<seconds> # applies to resolve\n";
std::cout << " --text=<text,...> # applies to respond\n";
std::cout << " --announce=<subtype,...> # applies to respond\n";
std::cout << "options must precede the command\n";
std::cout << "<host_name> and <instance_name> cannot end in '.'\n";
std::cout << "<service_name> must start with '_' and end in '._tcp.' or '._udp.'\n";
}
bool MdnsParams::Parse(const std::string& string_value, uint16_t* out) {
FX_DCHECK(out);
std::istringstream istream(string_value);
return (istream >> *out) && istream.eof();
}
bool MdnsParams::Parse(const std::string& string_value, uint32_t* out) {
FX_DCHECK(out);
std::istringstream istream(string_value);
return (istream >> *out) && istream.eof();
}
bool MdnsParams::Parse(const std::string& string_value, std::vector<std::string>* out) {
FX_DCHECK(out);
if (string_value.empty()) {
return false;
}
std::vector<std::string> result =
fxl::SplitStringCopy(string_value, ",", fxl::kTrimWhitespace, fxl::kSplitWantAll);
for (std::string& s : result) {
if (s.empty()) {
return false;
}
}
*out = result;
return true;
}
bool MdnsParams::ParseHostName(const std::string& string_value, std::string* out) {
FX_DCHECK(out);
if (string_value.empty() || string_value[string_value.size() - 1] == '.') {
std::cout << "'" << string_value << "' is not a valid host name\n\n";
return false;
}
*out = string_value;
return true;
}
bool MdnsParams::ParseServiceName(const std::string& string_value, std::string* out) {
FX_DCHECK(out);
if (string_value.size() <= kTcpSuffix.size() + 1 || string_value.compare(0, 1, "_") != 0 ||
(string_value.compare(string_value.size() - kTcpSuffix.size(), kTcpSuffix.size(),
kTcpSuffix) != 0 &&
string_value.compare(string_value.size() - kUdpSuffix.size(), kUdpSuffix.size(),
kUdpSuffix) != 0)) {
std::cout << "'" << string_value << "' is not a valid service name\n\n";
return false;
}
*out = string_value;
return true;
}
bool MdnsParams::ParseInstanceName(const std::string& string_value, std::string* out) {
FX_DCHECK(out);
if (string_value.empty() || string_value[string_value.size() - 1] == '.') {
std::cout << "'" << string_value << "' is not a instance name\n\n";
return false;
}
*out = string_value;
return true;
}
} // namespace mdns