diff --git a/include/ircd/m/id.h b/include/ircd/m/id.h index abed4ed06..aa2d41e7c 100644 --- a/include/ircd/m/id.h +++ b/include/ircd/m/id.h @@ -25,78 +25,127 @@ #pragma once #define HAVE_IRCD_M_ID_H -namespace ircd { -namespace m { - -const size_t USER_ID_BUFSIZE = 256; -const size_t ACCESS_TOKEN_BUFSIZE = 256; - -inline bool -username_valid(const string_view &username) +namespace ircd::m { - return true; + IRCD_M_EXCEPTION(error, INVALID_MXID, http::BAD_REQUEST) + IRCD_M_EXCEPTION(INVALID_MXID, BAD_SIGIL, http::BAD_REQUEST) + + IRCD_OVERLOAD(generate) + + struct id; } -inline bool -mxid_valid(const string_view &mxid) +// +// Interface to a string representing an mxid. The m::id itself is just a +// string_view over some existing data. m::id::buf is an m::id with an +// internal array providing the buffer. +// +struct ircd::m::id +:string_view { - const auto userhost(split(mxid, ':')); - const auto &user(userhost.first); - const auto &host(userhost.second); - if(user.empty() || host.empty()) - return false; + struct event; + struct user; + struct room; + struct alias; + template struct buf; - if(!startswith(user, '@') || user.size() == 1) - return false; - - return true; -} - -inline string_view -username_generate(char *const &buf, - const size_t &max) -{ - const uint32_t num(rand() % 100000U); - const size_t len(snprintf(buf, max, "@guest%u", num)); - return { buf, len }; -} - -inline string_view -access_token_generate(char *const &buf, - const size_t &max) -{ - const int num[] { rand(), rand(), rand() }; - const size_t len(snprintf(buf, max, "charybdis%d%d%d", num[0], num[1], num[2])); - return { buf, len }; -} - -struct id -{ - string_view user; - string_view host; - - template - id(string_view user, string_view host) - :user{std::move(user)} - ,host{std::move(host)} - {} - - id(const string_view &user, const string_view &host, char *const &buf, const size_t &max) - :user{user} - ,host{host} + enum sigil { - char gen_buf[USER_ID_BUFSIZE]; - fmt::snprintf(buf, max, "%s%s:%s", - user.empty() || startswith(user, '@')? "" : "@", - !user.empty()? user : username_generate(gen_buf, sizeof(gen_buf)), - host); + EVENT = '$', + USER = '@', + ROOM = '!', + ALIAS = '#', } + sigil; - template - id(const string_view &user, const string_view &host, char (&buf)[size]) - :id{user, host, buf, size} - {} + // Checks + bool valid() const; // Fully qualified mxid + bool valid_local() const; // Local part is valid (may or may not have host) + + // Extract elements + string_view local() const { return split(*this, ':').first; } + string_view host() const { return split(*this, ':').second; } + string_view name() const { return lstrip(local(), '@'); } + + private: + static string_view generate_random_timebased(const enum sigil &, char *const &buf, const size_t &max); + static string_view generate_random_prefixed(const enum sigil &, const string_view &prefix, char *const &buf, const size_t &max); + + public: + IRCD_USING_OVERLOAD(generate, m::generate); + + id() = default; + id(const string_view &id); + id(const enum sigil &, const string_view &id); + id(const enum sigil &, char *const &buf, const size_t &max, const string_view &id); + id(const enum sigil &, char *const &buf, const size_t &max, const string_view &name, const string_view &host); + id(const enum sigil &, char *const &buf, const size_t &max, const generate_t &, const string_view &host); }; -} // namespace m -} // namespace ircd +namespace ircd::m +{ + bool valid_sigil(const char &c); + bool valid_sigil(const string_view &id); + enum id::sigil sigil(const char &c); + enum id::sigil sigil(const string_view &id); + const char *reflect(const enum id::sigil &); +} + +// +// ID object backed by an internal buffer. Useful for creating or composing +// a new ID. +// +template +struct ircd::m::id::buf +:T +{ + static constexpr const size_t &SIZE{MAX}; + + private: + std::array b; + + public: + template + buf(args&&... a) + :T{b.data(), b.size(), std::forward(a)...} + {} + + buf() = default; +}; + +// +// convenience typedefs +// + +struct ircd::m::id::event +:ircd::m::id +{ + using buf = m::id::buf; + template event(args&&... a) :m::id{EVENT, std::forward(a)...} {} + event() = default; +}; + +struct ircd::m::id::user +:ircd::m::id +{ + using buf = m::id::buf; + template user(args&&... a) :m::id{USER, std::forward(a)...} {} + user() = default; +}; + +struct ircd::m::id::room +:ircd::m::id +{ + using buf = m::id::buf; + template room(args&&... a) :m::id{ROOM, std::forward(a)...} {} + room() = default; +}; + +struct ircd::m::id::alias +:ircd::m::id +{ + using buf = m::id::buf; + template alias(args&&... a) :m::id{ALIAS, std::forward(a)...} {} + alias() = default; +}; diff --git a/ircd/matrix.cc b/ircd/matrix.cc index 3f3f7e750..1cf75eba5 100644 --- a/ircd/matrix.cc +++ b/ircd/matrix.cc @@ -66,3 +66,239 @@ ircd::m::session::operator()(parse::buffer &pb, return object; } + +/////////////////////////////////////////////////////////////////////////////// +// +// m/id.h +// + +ircd::m::id::id(const string_view &id) +:string_view{id} +,sigil{m::sigil(id)} +{ +} + +ircd::m::id::id(const enum sigil &sigil, + const string_view &id) +:string_view{id} +,sigil{sigil} +{ + if(!valid()) + throw INVALID_MXID("Not a valid '%s' mxid", reflect(sigil)); +} + +ircd::m::id::id(const enum sigil &sigil, + char *const &buf, + const size_t &max, + const string_view &id) +:string_view{buf} +,sigil{sigil} +{ + strlcpy(buf, id, max); +} + +ircd::m::id::id(const enum sigil &sigil, + char *const &buf, + const size_t &max, + const string_view &name, + const string_view &host) +:string_view{[&]() -> string_view +{ + if(!max) + return {}; + + size_t len(0); + if(!startswith(name, sigil)) + buf[len++] = char(sigil); + + const auto has_sep + { + std::count(std::begin(name), std::end(name), ':') + }; + + if(!has_sep && host.empty()) + { + len += strlcpy(buf + len, name, max - len); + } + else if(!has_sep && !host.empty()) + { + len += fmt::snprintf(buf + len, max - len, "%s:%s", + name, + host); + } + else if(has_sep == 1 && !host.empty() && !split(name, ':').second.empty()) + { + len += strlcpy(buf + len, name, max - len); + } + else if(has_sep && !host.empty()) + { + throw INVALID_MXID("Not a valid '%s' mxid", reflect(sigil)); + } + + return { buf, len }; +}()} +,sigil{sigil} +{ +} + +ircd::m::id::id(const enum sigil &sigil, + char *const &buf, + const size_t &max, + const generate_t &, + const string_view &host) +:string_view{[&] +{ + char name[64]; switch(sigil) + { + case sigil::USER: + generate_random_prefixed(sigil::USER, "guest", name, sizeof(name)); + break; + + case sigil::ALIAS: + generate_random_prefixed(sigil::ALIAS, "", name, sizeof(name)); + break; + + default: + generate_random_timebased(sigil, name, sizeof(name)); + break; + }; + + const size_t len + { + fmt::snprintf(buf, max, "%s:%s", + name, + host) + }; + + return string_view { buf, len }; +}()} +,sigil{sigil} +{ +} + +bool +ircd::m::id::valid() +const +{ + const auto parts(split(*this, ':')); + const auto &local(parts.first); + const auto &host(parts.second); + + // this valid() requires a full canonical mxid with a host + if(host.empty()) + return false; + + // local requires a sigil plus at least one character + if(local.size() < 2) + return false; + + // local requires the correct sigil type + if(!startswith(local, sigil)) + return false; + + return true; +} + +bool +ircd::m::id::valid_local() +const +{ + const auto parts(split(*this, ':')); + const auto &local(parts.first); + + // local requires a sigil plus at least one character + if(local.size() < 2) + return false; + + // local requires the correct sigil type + if(!startswith(local, sigil)) + return false; + + return true; +} + +ircd::string_view +ircd::m::id::generate_random_prefixed(const enum sigil &sigil, + const string_view &prefix, + char *const &buf, + const size_t &max) +{ + const uint32_t num(rand::integer()); + const size_t len(fmt::snprintf(buf, max, "%c%s%u", char(sigil), prefix, num)); + return { buf, len }; +} + +ircd::string_view +ircd::m::id::generate_random_timebased(const enum sigil &sigil, + char *const &buf, + const size_t &max) +{ + const auto utime(microtime()); + const size_t len(snprintf(buf, max, "%c%zd%06d", char(sigil), utime.first, utime.second)); + return { buf, len }; +} + +enum ircd::m::id::sigil +ircd::m::sigil(const string_view &s) +try +{ + return sigil(s.at(0)); +} +catch(const std::out_of_range &e) +{ + throw BAD_SIGIL("sigil undefined"); +} + +enum ircd::m::id::sigil +ircd::m::sigil(const char &c) +{ + switch(c) + { + case '$': return id::EVENT; + case '@': return id::USER; + case '#': return id::ALIAS; + case '!': return id::ROOM; + } + + throw BAD_SIGIL("'%c' is not a valid sigil", c); +} + +const char * +ircd::m::reflect(const enum id::sigil &c) +{ + switch(c) + { + case id::EVENT: return "EVENT"; + case id::USER: return "USER"; + case id::ALIAS: return "ALIAS"; + case id::ROOM: return "ROOM"; + } + + return "?????"; +} + +bool +ircd::m::valid_sigil(const string_view &s) +try +{ + return valid_sigil(s.at(0)); +} +catch(const std::out_of_range &e) +{ + return false; +} + +bool +ircd::m::valid_sigil(const char &c) +{ + switch(c) + { + case id::EVENT: + case id::USER: + case id::ALIAS: + case id::ROOM: + return true; + } + + return false; +}