lokinet/llarp/dns/srv_data.hpp
Jason Rhinelander 36792d4337
Fix multi-field < ordering
Lots and lots of places in the code had broken < operators because they
are returning something like:

    foo < other.foo or bar < other.bar;

but this breaks both the strict weak ordering requirements that are
required for the "Compare" requirement for things like
std::map/set/priority_queue.

For example:

    a = {.foo=1, .bar=3}
    b = {.foo=3, .bar=1}

does not have an ordering over a and b (both `a < b` and `b < a` are
satisfied at the same time).

This needs to be instead something like:

    foo < other.foo or (foo == other.foo and bar < other.bar)

but that's a bit clunkier, and it is easier to use std::tie for tuple's
built-in < comparison which does the right thing:

    std::tie(foo, bar) < std::tie(other.foo, other.bar)

(Initially I noticed this in SockAddr/sockaddr_in6, but upon further
investigation this extends to the major of multi-field `operator<`'s.)

This fixes it by using std::tie (or something similar) everywhere we are
doing multi-field inequalities.
2022-10-13 16:29:13 -03:00

109 lines
2.6 KiB
C++

#pragma once
#include "name.hpp"
#include "serialize.hpp"
#include <tuple>
#include <string_view>
#include "llarp/util/status.hpp"
namespace llarp::dns
{
using SRVTuple = std::tuple<std::string, uint16_t, uint16_t, uint16_t, std::string>;
struct SRVData
{
static constexpr size_t TARGET_MAX_SIZE = 200;
std::string service_proto; // service and protocol may as well be together
uint16_t priority;
uint16_t weight;
uint16_t port;
// target string for the SRV record to point to
// options:
// empty - refer to query name
// dot - authoritative "no such service available"
// any other .loki or .snode - target is that .loki or .snode
std::string target;
// do some basic validation on the target string
// note: this is not a conclusive, regex solution,
// but rather some sanity/safety checks
bool
IsValid() const;
SRVTuple
toTuple() const;
auto
toTupleRef() const
{
return std::tie(service_proto, priority, weight, port, target);
}
/// so we can put SRVData in a std::set
bool
operator<(const SRVData& other) const
{
return toTupleRef() < other.toTupleRef();
}
bool
operator==(const SRVData& other) const
{
return toTupleRef() == other.toTupleRef();
}
bool
BEncode(llarp_buffer_t*) const;
bool
BDecode(llarp_buffer_t*);
util::StatusObject
ExtractStatus() const;
static SRVData
fromTuple(SRVTuple tuple);
/* bind-like formatted string for SRV records in config file
*
* format:
* srv=service.proto priority weight port target
*
* exactly one space character between parts.
*
* target can be empty, in which case the space after port should
* be omitted. if this is the case, the target is
* interpreted as the .loki or .snode of the current context.
*
* if target is not empty, it must be either
* - simply a full stop (dot/period) OR
* - a name within the .loki or .snode subdomains. a target
* specified in this manner must not end with a full stop.
*/
bool
fromString(std::string_view srvString);
};
} // namespace llarp::dns
namespace std
{
template <>
struct hash<llarp::dns::SRVData>
{
size_t
operator()(const llarp::dns::SRVData& data) const
{
const std::hash<std::string> h_str{};
const std::hash<uint16_t> h_port{};
return h_str(data.service_proto) ^ (h_str(data.target) << 3) ^ (h_port(data.priority) << 5)
^ (h_port(data.weight) << 7) ^ (h_port(data.port) << 9);
}
};
} // namespace std