#include "job.hpp"
#include "admin.hpp"
std::vector<char> databuf;
-clientmap client_map = {};
-channelmap channel_map = {};
+clientset client_map = {};
+channelset channel_map = {};
const char *hostname = 0;
void sendbuf(Client *client) {
static void sendbuf_chan(Channel *chan) {
sendbuf_chan_caps(chan, 0, 0);
}
-static void sendbuf_list_sans_self_caps(Client *client, clientmap &ls, int cookie, int positive, int negative) {
+static void sendbuf_list_sans_self_caps(Client *client, clientset &ls, int cookie, int positive, int negative) {
for (auto cl : ls)
if (client != cl) {
if (cookie) {
sendbuf(cl);
}
}
-static void sendbuf_list_sans_self(Client *client, clientmap &ls, int cookie) {
+static void sendbuf_list_sans_self(Client *client, clientset &ls, int cookie) {
sendbuf_list_sans_self_caps(client, ls, cookie, 0, 0);
}
void sendbuf_monitor(const char *nick, int cookie, int positive, int negative)
{
for (auto cl : client_map)
- if (cl->monitor.has(nick)) {
+ if (cl->monitor.contains(nick)) {
if (cookie) {
if (cookie != cl->cookie)
cl->cookie = cookie;
Channel *chan = findChannel(args[0]);
if (!chan)
return err_nosuchchannel(client, args[0]), sendbuf(client), false;
- if (!chan->members.has(client))
+ if (!chan->members.contains(client))
return err_notonchannel(client, chan), sendbuf(client), false;
- chan->o.add(client);
+ chan->o.insert(client);
copy_strf(&databuf, ":cia!cia@cia.gov MODE %s +o %s\r\n", chan->name, client->nick);
sendbuf_chan(chan);
} MATCH_COMMAND("JOIN") {
channel_sendtopic(client, chan, 0);
} else {
// TODO: membership check, maybe?
- if (chan->dmode & DMODE_TOPICPROT && !chan->o.has(client))
+ if (chan->dmode & DMODE_TOPICPROT && !chan->o.contains(client))
return false; // FIXME: error 482
channel_topic(client, chan, args[1], client->nick, get_time());
}
if (!chan)
return err_nosuchchannel(client, args[1]), sendbuf(client), false;
if (chan->dmode & DMODE_INVITEONLY) {
- if (!chan->o.has(client))
+ if (!chan->o.contains(client))
return err_chanoprivsneeded(client, chan), sendbuf(client), false;
} else {
- if (!chan->members.has(client))
+ if (!chan->members.contains(client))
return err_notonchannel(client, chan), sendbuf(client), false;
}
Client *cl = findClient(args[0]);
if (!cl)
return err_nosuchnick(client, args[0]), sendbuf(client), false;
- if (chan->members.has(cl))
+ if (chan->members.contains(cl))
return err_useronchannel(client, cl->nick, chan), sendbuf(client), false;
- chan->invitees.add(cl);
- cl->invites.add(chan);
+ chan->invitees.insert(cl);
+ cl->invites.insert(chan);
rpl_inviting(client, cl, chan);
sendbuf(client);
msg_invite(client, cl, chan);
Channel *chan = findChannel(args[0]);
if (!chan)
return err_nosuchchannel(client, args[0]), sendbuf(client), false;
- if (!chan->o.has(client))
+ if (!chan->o.contains(client))
return err_chanoprivsneeded(client, chan), sendbuf(client), false;
- if (!chan->members.has(client))
+ if (!chan->members.contains(client))
return err_notonchannel(client, chan), sendbuf(client), false;
char *p = args[1];
do {
if (match_list(client, chan->listb) && !match_list(client, chan->liste))
return err_cannotsendtochan(client, chan->name), sendbuf(client), false;
if (chan->dmode & DMODE_INVITEONLY
- && !chan->invitees.has(client) && !match_list(client, chan->listI))
+ && !chan->invitees.contains(client) && !match_list(client, chan->listI))
return err_cannotsendtochan(client, chan->name), sendbuf(client), false;
- if (chan->dmode & DMODE_NOEXTERNAL && !chan->members.has(client))
+ if (chan->dmode & DMODE_NOEXTERNAL && !chan->members.contains(client))
return err_cannotsendtochan(client, chan->name), sendbuf(client), false;
- if (chan->dmode & DMODE_MODERATED && !chan->v.has(client) && !chan->o.has(client))
+ if (chan->dmode & DMODE_MODERATED && !chan->v.contains(client) && !chan->o.contains(client))
return err_cannotsendtochan(client, chan->name), sendbuf(client), false;
switch (admin_hook("sendtochannel", client, 0, chan, args[1])) {
case -1:
Channel *chan = findChannel(nick);
if (!chan)
return err_nosuchchannel(client, nick), sendbuf(client), false;
- if (!chan->o.has(client))
+ if (!chan->o.contains(client))
return false; // TODO: error
if (argc == 2) { // Type A channel modes
int all = !strcmp(args[1], "+0");
return err_notregistered(client), sendbuf(client), false;
rpl_liststart(client), sendbuf(client);
for (auto chan : channel_map)
- if (client->umode & UMODE_OPER || !(chan->dmode & DMODE_SECRET) || chan->members.has(client))
+ if (client->umode & UMODE_OPER || !(chan->dmode & DMODE_SECRET) || chan->members.contains(client))
rpl_list(client, chan), sendbuf(client);
rpl_listend(client), sendbuf(client);
} MATCH_COMMAND("NAMES") {
if ((p = strchr(channel, ',')))
*p++ = '\0';
Channel *chan = findChannel(channel);
- if (chan && (!(chan->dmode & DMODE_SECRET) || chan->members.has(client))) {
+ if (chan && (!(chan->dmode & DMODE_SECRET) || chan->members.contains(client))) {
channel_sendnames(client, chan);
} else {
rpl_endofnames(client, channel);
int visible = client->umode & UMODE_OPER || !(cl->umode & UMODE_INVISIBLE) || client == cl;
if (!visible) {
for (auto chan : cl->channels) {
- if (client->channels.has(chan)) {
+ if (client->channels.contains(chan)) {
visible = 1;
break;
}
if (cl->away)
rpl_away(client, cl), sendbuf(client);
for (auto chan : cl->channels) {
- if ((cl->umode & UMODE_INVISIBLE || chan->dmode & DMODE_SECRET) && !chan->members.has(client))
+ if ((cl->umode & UMODE_INVISIBLE || chan->dmode & DMODE_SECRET) && !chan->members.contains(client))
continue;
rpl_whoischannels(client, cl, chan), sendbuf(client);
}
return rpl_monlistfull(client, MONITOR_LIMIT,nick), sendbuf(client), false;
if ((p = strchr(nick, ',')))
*p++ = '\0';
- client->monitor.add(nick);
- Client *cl = client_map.get(nick);
+ client->monitor.insert(nick);
+ Client *cl = findClientByKey(nick);
if (cl)
rpl_mononline(client, cl), sendbuf(client);
else
char *nick = p;
if ((p = strchr(nick, ',')))
*p++ = '\0';
- client->monitor.remove(nick);
+ client->monitor.erase(nick);
} while (p);
break;
}
break;
case 'S':
for (auto& key : client->monitor) {
- Client *cl = client_map.get(key);
+ Client *cl = findClientByKey(key);
if (cl)
rpl_mononline(client, cl), sendbuf(client);
else
return;
}
- if (Client *other = client_map.get(LowercasingView(nickname)); other) {
+ if (Client *other = findClient(nickname); other) {
if (client != other)
err_nicknameinuse(client, nickname), sendbuf(client);
return;
sendbuf(client);
sendbuf_visible(client);
rpl_monoffline(0, client->key), sendbuf_monitor(client->key, 0, 0, 0);
- client_map.remove(client);
+ client_map.erase(client);
for (auto chan : client->channels) {
- chan->members.rename(client, key);
- chan->o.rename(client, key);
- chan->v.rename(client, key);
+ renameClient(chan->members, client, key);
+ renameClient(chan->o, client, key);
+ renameClient(chan->v, client, key);
+ renameClient(chan->invitees, client, key);
}
delete[] client->nick;
delete[] client->key;
client->nick = mystrdup(nickname);
updateNuh(client);
client->key = mystrdup(key);
- client_map.add(client);
+ client_map.insert(client);
rpl_mononline(0, client), sendbuf_monitor(client->key, 0, 0, 0);
}
/* changes username, hostname or both
sendbuf_list_sans_self_caps(client, chan->members, 0, 0, CAP_EXTENDEDJOIN|CAP_CHGHOST);
msg_join_extended(client, chan);
sendbuf_list_sans_self_caps(client, chan->members, 0, CAP_EXTENDEDJOIN, CAP_CHGHOST);
- if (chan->v.has(client)) {
+ if (chan->v.contains(client)) {
copy_strf(&databuf, ":cia!cia@cia.gov MODE %s +v %s\r\n", chan->name, client->nick);
sendbuf_list_sans_self_caps(client, chan->members, 0, 0, CAP_CHGHOST);
}
- if (chan->o.has(client)) {
+ if (chan->o.contains(client)) {
copy_strf(&databuf, ":cia!cia@cia.gov MODE %s +o %s\r\n", chan->name, client->nick);
sendbuf_list_sans_self_caps(client, chan->members, 0, 0, CAP_CHGHOST);
}
size_t chanlen = strlen(channel);
if (chanlen >= MAX_CHANLEN)
return err_badchanmask(client, channel), sendbuf(client);
- char key[MAX_CHANLEN];
- memcpy(key, channel, chanlen+1);
- stringlower(key);
- Channel *chan = channel_map.get(key);
+ Channel *chan = findChannel(channel);
int newchan = !chan;
if (!chan) {
+ char key[MAX_CHANLEN];
+ memcpy(key, channel, chanlen+1);
+ stringlower(key);
chan = new Channel(channel, key, get_time());
chan->dmode &= ~DMODE_PERSIST;
- chan->o.add(client);
+ chan->o.insert(client);
} else {
- if (chan->members.has(client))
+ if (chan->members.contains(client))
return;
if (chan->chankey && (!chankey || strcmp(chan->chankey, chankey) == 0))
return err_badchannelkey(client, chan), sendbuf(client);
if (match_list(client, chan->listb) && !match_list(client, chan->liste))
return err_bannedfromchan(client, chan), sendbuf(client);
if (chan->dmode & DMODE_INVITEONLY
- && !chan->invitees.has(client) && !match_list(client, chan->listI))
+ && !chan->invitees.contains(client) && !match_list(client, chan->listI))
return err_inviteonlychan(client, chan), sendbuf(client);
}
- chan->members.add(client);
- client->channels.add(chan);
+ chan->members.insert(client);
+ client->channels.insert(chan);
chan->member_count++;
// send JOIN to everyone
msg_join(client, chan);
// TODO: send channel modes to the joining client
// apply +w modes
if (match_list(client, chan->listwo)) {
- chan->o.add(client);
+ chan->o.insert(client);
copy_strf(&databuf, ":cia!cia@cia.gov MODE %s +o %s\r\n", chan->name, client->nick);
sendbuf_chan(chan);
}
if (match_list(client, chan->listwv)) {
- chan->v.add(client);
+ chan->v.insert(client);
copy_strf(&databuf, ":cia!cia@cia.gov MODE %s +v %s\r\n", chan->name, client->nick);
sendbuf_chan(chan);
}
Channel::Channel(const char *channel, const char *key, time_t creation)
:key(mystrdup(key)), name(mystrdup(channel)), creation(creation)
{
- channel_map.add(this);
+ channel_map.insert(this);
}
void channel_part(Client *client, Channel *chan, const char *reason, Client *kicker)
{
- if (!chan->members.has(client))
+ if (!chan->members.contains(client))
return;
if (kicker) {
msg_kick(kicker, client, chan, reason);
msg_part(client, chan, *reason ? reason : NULL);
sendbuf_chan(chan);
}
- client->channels.remove(chan);
- chan->members.remove(client);
- chan->o.remove(client);
- chan->v.remove(client);
+ client->channels.erase(chan);
+ chan->members.erase(client);
+ chan->o.erase(client);
+ chan->v.erase(client);
chan->member_count--;
if (!chan->member_count && !(chan->dmode & DMODE_PERSIST))
delete chan;
}
Channel::~Channel() {
- channel_map.remove(this);
+ channel_map.erase(this);
for (auto cl : invitees)
- cl->invites.remove(this);
+ cl->invites.erase(this);
delete[] key;
delete[] name;
delete[] topic;
break; // TODO: error msg
stringlower(mask);
if (is_plus)
- list->add(mask);
+ list->insert(mask);
else
- list->remove(mask);
+ list->erase(mask);
char which[2] = {*p, 0};
copy_strf(&databuf, ":%s!%s@%s MODE %s %s%s %s\r\n",
n, u, h, chan->name, is_plus?"+":"-", which, mask);
err_nosuchnick(client, nick), sendbuf(client);
break;
}
- if (!chan->members.has(cl)) {
+ if (!chan->members.contains(cl)) {
if (client)
err_usernotinchannel(client, cl->nick, chan), sendbuf(client);
break;
}
- clientmap &clmap = *p == 'o' ? chan->o : chan->v;
+ clientset &clset = *p == 'o' ? chan->o : chan->v;
if (is_plus)
- clmap.add(cl);
+ clset.insert(cl);
else
- clmap.remove(cl);
+ clset.erase(cl);
copy_strf(&databuf, ":%s!%s@%s MODE %s %s%s %s\r\n",
n, u, h, chan->name, is_plus?"+":"-", which, cl->nick);
sendbuf_chan(chan);
chanlog_time(chan->topic_when);
sendbuf_chan(chan);
}
-Client *findClient(const char *nickname) {
- return client_map.get(LowercasingView(nickname));
+Client *findClientByKey(std::string_view key) {
+ auto it = client_map.find(key);
+ return it != client_map.end() ? *it : nullptr;
+}
+Client *findClient(std::string_view nickname) {
+ auto it = client_map.find(LowercasingView(nickname));
+ return it != client_map.end() ? *it : nullptr;
}
-Channel *findChannel(const char *channel) {
- return channel_map.get(LowercasingView(channel));
+Channel *findChannel(std::string_view channel) {
+ auto it = channel_map.find(LowercasingView(channel));
+ return it != channel_map.end() ? *it : nullptr;
}
FlatSet& operator=(FlatSet&&) = delete;
};
-/* A map abstract data type with following properties:
- * - keys are lexicographically-sorted unique NUL-terminated strings
- * - values are non-NULL pointers
- * - a NULL value is synonymous with absence of a key
- * - keys are owned by the map, values are not.
- * - the map can't be modified during traversal
- *
- * A map is created by allocating and zero-initializing an object of type
- * map_t. All map functions take a pointer to map_t.
- */
struct Client;
struct Channel;
constexpr std::string_view to_key(const std::string &v) noexcept {
constexpr std::string_view to_key(const Channel *v) noexcept;
template<typename T>
-struct MapValueLess {
+struct SetValueLess {
using is_transparent = void;
constexpr bool operator()(const T& lhs, const T& rhs) noexcept {
return to_key(lhs) < to_key(rhs);
}
};
-template<typename T>
-struct map_base2 {
- map_base2& operator=(map_base2&&) = delete;
- FlatSet<T, MapValueLess<T>> m;
-
- void clear() {
- m.clear();
- }
-
- using value_type = T;
- auto begin() { return m.begin(); }
- auto end() { return m.end(); }
- auto rbegin() { return m.rbegin(); }
- auto rend() { return m.rend(); }
- size_t size() {
- return m.size();
- }
- template<typename K>
- bool has(const K& k) {
- return m.contains(k);
- }
- template<typename K>
- void add(const K &v) {
- m.insert(v);
- }
- template<typename K>
- void remove(const K &k) {
- m.erase(k);
- }
-
- template<typename K>
- T get(const K& k) {
- auto it = m.find(k);
- return it != m.end() ? *it : T();
- }
-};
-
-struct clientmap : map_base2<Client *> {
- void rename(Client *v, const char *newk) {
- auto it1 = m.find(v);
- if (it1 == m.end())
- return;
- auto it2 = m.lower_bound(std::string_view(newk));
- // Have to to this with a const_cast on the underlying vector, instead
- // of m.extract/m.replace, because it's UB to replace with an unsorted
- // vector. This will be an actual problem in e.g. libcxx with hardening
- // enabled, since they assert that the array is sorted.
- Client **p = const_cast<Client **>(&*it1);
- Client **q = const_cast<Client **>(&*it2);
- if (p < q)
- std::rotate(p, p+1, q);
- else
- std::rotate(q, p, p+1);
- }
-};
-using channelmap = map_base2<Channel *>;
-struct stringset : map_base2<std::string> {
- template<typename K>
- std::string get(const K& k) = delete;
-};
+using clientset = FlatSet<Client *, SetValueLess<Client *>>;
+using channelset = FlatSet<Channel *, SetValueLess<Channel *>>;
+using stringset = FlatSet<std::string, SetValueLess<std::string>>;
+/**
+ * Move a client to a different key position in the set.
+ *
+ * When a user updates the nickname (setNick()) we have to update all sets
+ * referencing the user. Rather than removing and readding the user, we simply
+ * put him at the position of the new key. This temporarily breaks the
+ * invariant of flat_set. The benefit is that we don't have to track the list
+ * of sets we have to readd the user to.
+ *
+ * This function is a no-op when the client isn't a member of the set.
+ */
+inline void renameClient(clientset &s, Client *cl, std::string_view newk) {
+ auto it1 = s.find(cl);
+ if (it1 == s.end())
+ return;
+ auto it2 = s.lower_bound(newk);
+ // Have to to this with a const_cast on the underlying vector, instead
+ // of m.extract/m.replace, because it's UB to replace with an unsorted
+ // vector. This will be an actual problem in e.g. libcxx with hardening
+ // enabled, since they assert that the array is sorted.
+ Client **p = const_cast<Client **>(&*it1);
+ Client **q = const_cast<Client **>(&*it2);
+ if (p < q)
+ std::rotate(p, p+1, q);
+ else
+ std::rotate(q, p, p+1);
+}
#endif