summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api.pod4
-rw-r--r--src/hub/adc.c257
-rw-r--r--src/hub/adc.h6
-rw-r--r--src/hub/chat.c2
-rw-r--r--src/hub/nmdc.c169
-rw-r--r--src/hub/nmdc.h18
-rw-r--r--src/hub/users.c174
-rw-r--r--src/hub/users.h36
8 files changed, 365 insertions, 301 deletions
diff --git a/doc/api.pod b/doc/api.pod
index 71f6b73..d4693fa 100644
--- a/doc/api.pod
+++ b/doc/api.pod
@@ -417,8 +417,8 @@ The C<Flags> field is a bit mask of the following numbers:
16: Set if the user is in active mode (May not always be known on NMDC hubs)
Note that the user list does not only contain users that are B<currently>
-online, but may also include a few users that used to be online or that are
-still connecting. The above C<online> flag is used to indicates this status.
+online, but may also include a few users that have already left the hub. The
+above C<online> flag is used to indicates this status.
More fields and flags may be added in future versions.
diff --git a/src/hub/adc.c b/src/hub/adc.c
index 1382386..3a0819c 100644
--- a/src/hub/adc.c
+++ b/src/hub/adc.c
@@ -25,6 +25,27 @@
#include <tiger.h>
+__KHASH_IMPL(adcsids, static kh_inline, adc_sid_t, int64_t, 1, kh_int_hash_func, kh_int_hash_equal);
+
+static int64_t hub_adc_sid_get(hub_t *h, adc_sid_t sid) {
+ khiter_t k = kh_get(adcsids, h->a.sids, sid);
+ return k == kh_end(h->a.sids) ? -1 : kh_value(h->a.sids, k);
+}
+
+static void hub_adc_sid_set(hub_t *h, adc_sid_t sid, int64_t uid) {
+ int r;
+ khiter_t k = kh_put(adcsids, h->a.sids, sid, &r);
+ kh_value(h->a.sids, k) = uid;
+}
+
+static void hub_adc_sid_del(hub_t *h, adc_sid_t sid) {
+ khiter_t k = kh_get(adcsids, h->a.sids, sid);
+ if(k != kh_end(h->a.sids))
+ kh_del(adcsids, h->a.sids, k);
+}
+
+
+
/* Finalize, send and free an ADC message */
#define cmd_flush(h, msg) do {\
adc_final(msg);\
@@ -170,117 +191,149 @@ static void hub_adc_inf_user_client(hub_user_update_t *d, const char *ve, const
#define ERR(msg) do {\
- cmd_warn(h, cmd, len, msg);\
- hub_user_update_abort(&d);\
- return;\
+ cmd_warn(d->h, cmd, len, msg);\
+ return false;\
} while(0)
+
#define UINT(param, max) do {\
char *end = NULL;\
uint64_t _v = strtoull(p+2, &end, 10);\
if(!p[2] || !end || !adc_isparamend(*end))\
ERR("Invalid integer value");\
- d.u->param = _v >= max ? max : _v;\
+ d->u->param = _v >= max ? max : _v;\
} while(0)
-static void hub_adc_inf_user(hub_t *h, const char *cmd, int len, adc_sid_t sid) {
- const char *p, *ve = NULL, *ap = NULL;
- hub_user_update_t d;
- hub_user_update_init(h, &d, sid == h->a.mysid ? 0 : hub_users_bysid(h, sid));
- d.u->online = true;
- d.u->a.sid = sid;
- bool isreg = false;
-
- for(p=adc_param_start(cmd); p; p=adc_param_next(p)) {
- switch(adc_param_name(p)) {
- case ADC_PARAM('N','I'): free(d.u->nick); d.u->nick = adc_param_namevalue(p); hub_user_update_field(&d, HUBU_NICK ); break;
- case ADC_PARAM('D','E'): free(d.u->desc); d.u->desc = adc_param_namevalue(p); hub_user_update_field(&d, HUBU_DESCRIPTION); break;
- case ADC_PARAM('E','M'): free(d.u->mail); d.u->mail = adc_param_namevalue(p); hub_user_update_field(&d, HUBU_MAIL ); break;
- case ADC_PARAM('V','E'): ve = p; break;
- case ADC_PARAM('A','P'): ap = p; break;
- case ADC_PARAM('S','S'): UINT(sharesize, UINT64_MAX); hub_user_update_field(&d, HUBU_SHARESIZE ); break;
- case ADC_PARAM('S','F'): UINT(sharedfiles, UINT32_MAX); hub_user_update_field(&d, HUBU_SHAREDFILES); break;
- case ADC_PARAM('H','N'): UINT(hnorm, UINT8_MAX); hub_user_update_field(&d, HUBU_HUBS ); break;
- case ADC_PARAM('H','R'): UINT(hreg, UINT8_MAX); hub_user_update_field(&d, HUBU_HUBS ); break;
- case ADC_PARAM('H','O'): UINT(hop, UINT8_MAX); hub_user_update_field(&d, HUBU_HUBS ); break;
- case ADC_PARAM('S','L'): UINT(hop, UINT8_MAX); hub_user_update_field(&d, HUBU_SLOTS ); break;
- case ADC_PARAM('A','S'): UINT(as, UINT32_MAX); hub_user_update_field(&d, HUBU_AS ); break;
- case ADC_PARAM('U','S'): UINT(conn, UINT32_MAX); hub_user_update_field(&d, HUBU_CONN ); break;
- case ADC_PARAM('I','D'): {
- char *cid = adc_param_namevalue(p);
- if(!isbase32_24(cid)) {
- free(cid);
- ERR("Invalid CID");
- }
- if(d.u->id >= 0) {
- cmd_warn(h, cmd, len, "Ignoring repeated/duplicate ID");
- free(cid);
- break;
- }
- base32_decode(cid, d.u->a.cid, 24);
- hub_user_update_field(&d, HUBU_CID);
+static bool hub_adc_inf_user_param(hub_user_update_t *d, const char *cmd, int len, const char *p, const char **ve, const char **ap, bool *isreg) {
+ switch(adc_param_name(p)) {
+ case ADC_PARAM('N','I'): free(d->u->nick); d->u->nick = adc_param_namevalue(p); hub_user_update_field(d, HUBU_NICK ); break;
+ case ADC_PARAM('D','E'): free(d->u->desc); d->u->desc = adc_param_namevalue(p); hub_user_update_field(d, HUBU_DESCRIPTION); break;
+ case ADC_PARAM('E','M'): free(d->u->mail); d->u->mail = adc_param_namevalue(p); hub_user_update_field(d, HUBU_MAIL ); break;
+ case ADC_PARAM('V','E'): *ve = p; break;
+ case ADC_PARAM('A','P'): *ap = p; break;
+ case ADC_PARAM('S','S'): UINT(sharesize, UINT64_MAX); hub_user_update_field(d, HUBU_SHARESIZE ); break;
+ case ADC_PARAM('S','F'): UINT(sharedfiles, UINT32_MAX); hub_user_update_field(d, HUBU_SHAREDFILES); break;
+ case ADC_PARAM('H','N'): UINT(hnorm, UINT8_MAX); hub_user_update_field(d, HUBU_HUBS ); break;
+ case ADC_PARAM('H','R'): UINT(hreg, UINT8_MAX); hub_user_update_field(d, HUBU_HUBS ); break;
+ case ADC_PARAM('H','O'): UINT(hop, UINT8_MAX); hub_user_update_field(d, HUBU_HUBS ); break;
+ case ADC_PARAM('S','L'): UINT(hop, UINT8_MAX); hub_user_update_field(d, HUBU_SLOTS ); break;
+ case ADC_PARAM('A','S'): UINT(as, UINT32_MAX); hub_user_update_field(d, HUBU_AS ); break;
+ case ADC_PARAM('U','S'): UINT(conn, UINT32_MAX); hub_user_update_field(d, HUBU_CONN ); break;
+ case ADC_PARAM('I','D'): {
+ char *cid = adc_param_namevalue(p);
+ if(!isbase32_24(cid)) {
free(cid);
- break;
- }
- case ADC_PARAM('I','4'): {
- char *v = adc_param_namevalue(p);
- int r = inet_pton(AF_INET, v, &d.u->ip4);
- free(v);
- if(r != 1)
- ERR("Invalid IPv4 address");
- hub_user_update_field(&d, HUBU_IP4);
- break;
- }
- case ADC_PARAM('I','6'): {
- char *v = adc_param_namevalue(p);
- int r = inet_pton(AF_INET6, v, &d.u->ip6);
- free(v);
- if(r != 1)
- ERR("Invalid IPv6 address");
- hub_user_update_field(&d, HUBU_IP6);
- break;
+ ERR("Invalid CID");
}
- case ADC_PARAM('C','T'): {
- char *end = NULL;\
- uint64_t _v = strtoull(p+2, &end, 10);\
- if(!p[2] || !end || !adc_isparamend(*end))\
- ERR("Invalid integer value");\
- isreg = _v & (2|4|8|16);
- bool op = _v & (4|8|16);
- if(op != d.u->op)
- hub_user_update_field(&d, HUBU_FLAGS);
- d.u->op = op;
- break;
- }
- case ADC_PARAM('S','U'): {
- char *v = adc_param_namevalue(p);
- bool active = strstr(v, "TCP4") || strstr(v, "TCP6");
- bool tls = strstr(v, "ADC0") || strstr(v, "ADCS");
- if(active != d.u->active || tls != d.u->tls)
- hub_user_update_field(&d, HUBU_FLAGS);
- d.u->active = active;
- d.u->tls = tls;
- free(v);
- break;
- }
- case ADC_PARAM('A','W'): {
- bool aw = !(adc_isparamend(p[2]) || p[2] == '0');
- if(aw != d.u->away)
- hub_user_update_field(&d, HUBU_FLAGS);
- d.u->away = aw;
+ if(d->u->id >= 0) {
+ cmd_warn(d->h, cmd, len, "Ignoring repeated/duplicate ID");
+ free(cid);
break;
}
- /* TODO: U4 */
- }
+ base32_decode(cid, d->u->a.cid, 24);
+ hub_user_update_field(d, HUBU_CID);
+ free(cid);
+ break;
+ }
+ case ADC_PARAM('I','4'): {
+ char *v = adc_param_namevalue(p);
+ int r = inet_pton(AF_INET, v, &d->u->ip4);
+ free(v);
+ if(r != 1)
+ ERR("Invalid IPv4 address");
+ hub_user_update_field(d, HUBU_IP4);
+ break;
+ }
+ case ADC_PARAM('I','6'): {
+ char *v = adc_param_namevalue(p);
+ int r = inet_pton(AF_INET6, v, &d->u->ip6);
+ free(v);
+ if(r != 1)
+ ERR("Invalid IPv6 address");
+ hub_user_update_field(d, HUBU_IP6);
+ break;
}
+ case ADC_PARAM('C','T'): {
+ char *end = NULL;\
+ uint64_t _v = strtoull(p+2, &end, 10);\
+ if(!p[2] || !end || !adc_isparamend(*end))\
+ ERR("Invalid integer value");\
+ *isreg = _v & (2|4|8|16);
+ bool op = _v & (4|8|16);
+ if(op != d->u->op)
+ hub_user_update_field(d, HUBU_FLAGS);
+ d->u->op = op;
+ break;
+ }
+ case ADC_PARAM('S','U'): {
+ char *v = adc_param_namevalue(p);
+ bool active = strstr(v, "TCP4") || strstr(v, "TCP6");
+ bool tls = strstr(v, "ADC0") || strstr(v, "ADCS");
+ if(active != d->u->active || tls != d->u->tls)
+ hub_user_update_field(d, HUBU_FLAGS);
+ d->u->active = active;
+ d->u->tls = tls;
+ free(v);
+ break;
+ }
+ case ADC_PARAM('A','W'): {
+ bool aw = !(adc_isparamend(p[2]) || p[2] == '0');
+ if(aw != d->u->away)
+ hub_user_update_field(d, HUBU_FLAGS);
+ d->u->away = aw;
+ break;
+ }
+ /* TODO: U4 */
+ }
+ return true;
+}
+
+
+static bool hub_adc_inf_user_update(hub_user_update_t *d, const char *cmd, int len, bool *isreg, bool newuser) {
+ const char *p, *ve = NULL, *ap = NULL;
+
+ for(p=adc_param_start(cmd); p; p=adc_param_next(p))
+ if(!hub_adc_inf_user_param(d, cmd, len, p, &ve, &ap, isreg))
+ return false;
if(ve)
- hub_adc_inf_user_client(&d, ve, ap);
+ hub_adc_inf_user_client(d, ve, ap);
- if(!d.u->nick || !*d.u->nick)
+ if(!d->u->nick || !*d->u->nick)
ERR("No or empty nick");
- if(!hub_user_update_done(&d))
- cmd_warn(h, cmd, len, "No CID in INF for new user");
+ if(newuser) {
+ char emptycid[sizeof(d->u->a.cid)] = {};
+ if(memcmp(d->u->a.cid, emptycid, sizeof(d->u->a.cid)) == 0)
+ ERR("No CID in INF for new user");
+ d->u->id = hub_users_genid(d->h, d->u->a.cid, sizeof(d->u->a.cid));
+ }
+ return true;
+}
+
+#undef UINT
+#undef ERR
+
+
+static void hub_adc_inf_user(hub_t *h, const char *cmd, int len, adc_sid_t sid) {
+ bool isreg = false;
+ hub_user_update_t d[1];
+
+ int64_t uid = hub_adc_sid_get(h, sid);
+ hub_user_t *u = uid >= 0 ? hub_users_get(h, uid) : hub_user_new(true);
+ assert(u != NULL);
+
+ hub_user_update_init(h, d, u);
+ d->u->a.sid = sid;
+
+ bool okay = hub_adc_inf_user_update(d, cmd, len, &isreg, uid < 0);
+ if(uid < 0) {
+ if(okay)
+ hub_adc_sid_set(h, sid, u->id);
+ else
+ hub_user_unref(h, u);
+ }
+ if(okay)
+ hub_user_update_done(d, uid < 0);
/* Our own SID comes last, at which point we're in NORMAL */
if(sid == h->a.mysid && h->a.state != ADC_NORMAL) {
@@ -289,13 +342,10 @@ static void hub_adc_inf_user(hub_t *h, const char *cmd, int len, adc_sid_t sid)
h->a.state = ADC_NORMAL;
hub_users_listcomplete(h);
if(isreg)
- hub_manager_settype(h, d.u->op ? HUBM_HOP : HUBM_HREG);
+ hub_manager_settype(h, u->op ? HUBM_HOP : HUBM_HREG);
}
}
-#undef UINT
-#undef ERR
-
static void hub_adc_inf(hub_t *h, const char *cmd, int len) {
adc_sid_t src = adc_source(cmd);
@@ -325,17 +375,18 @@ static void hub_adc_qui(hub_t *h, const char *cmd, int len) {
}
if(sid != h->a.mysid) {
- int64_t id = hub_users_bysid(h, sid);
+ int64_t id = hub_adc_sid_get(h, sid);
if(id == -1)
cmd_warn(h, cmd, len, "QUI for user not on this hub");
else {
- int64_t initid = initiator == -1 ? -2 : hub_users_bysid(h, initiator);
+ int64_t initid = initiator == -1 ? -2 : hub_adc_sid_get(h, initiator);
hub_user_left(h, id, initid, message ? message : "");
}
} else {
/* We've been disconnected. */
hub_hub_seterror(h, redir ? 0 : HUBCD_RECONNECT, redir ? "Redirect" : "Disconnect", message ? message : "Disconnected from hub", redir);
}
+ hub_adc_sid_del(h, sid);
free(message);
}
@@ -349,7 +400,7 @@ static void hub_adc_msg(hub_t *h, const char *cmd, int len) {
adc_sid_t src = adc_source(cmd);
int64_t user = -1;
- if(src >= 0 && (user = hub_users_bysid(h, src)) == -1) {
+ if(src >= 0 && (user = hub_adc_sid_get(h, src)) == -1) {
cmd_warn(h, cmd, len, "MSG from an unknown SID");
return;
}
@@ -365,7 +416,7 @@ static void hub_adc_msg(hub_t *h, const char *cmd, int len) {
}
int64_t group = -1;
- if(pmsid == -2 || (pmsid >= 0 && (group = hub_users_bysid(h, pmsid)) == -1)) {
+ if(pmsid == -2 || (pmsid >= 0 && (group = hub_adc_sid_get(h, pmsid)) == -1)) {
cmd_warn(h, cmd, len, "MSG with unknown SID in PM param");
free(msg);
return;
@@ -379,7 +430,7 @@ static void hub_adc_msg(hub_t *h, const char *cmd, int len) {
/* If we sent a PM and this message is its echo, make sure to set the group
* to whatever SID we sent it to. */
- if(group == 0 && dest != -1 && (group = hub_users_bysid(h, dest)) == -1) {
+ if(group == 0 && dest != -1 && (group = hub_adc_sid_get(h, dest)) == -1) {
cmd_warn(h, cmd, len, "MSG with unknown SID as target");
free(msg);
return;
@@ -632,6 +683,7 @@ static void hub_adc_connected(hub_t *h) {
kstring_t msg = {};
h->a.state = ADC_PROTOCOL;
h->a.havelastinf = false;
+ h->a.sids = kh_init(adcsids);
adc_create(&msg, 'H', ADC_CMD_SUP, 0, 0);
adc_append(&msg, "ADBASE");
@@ -644,6 +696,7 @@ static void hub_adc_connected(hub_t *h) {
static void hub_adc_disconnected(hub_t *h) {
if(h->a.havelastinf)
hub_adc_myinf_free(&h->a.lastinf);
+ kh_destroy(adcsids, h->a.sids);
hub_manager_settype(h, HUBM_HNONE);
}
diff --git a/src/hub/adc.h b/src/hub/adc.h
index 4ac99c2..c154215 100644
--- a/src/hub/adc.h
+++ b/src/hub/adc.h
@@ -35,10 +35,16 @@ typedef struct {
char *EM;
} hub_adc_myinf_t;
+
+/* SID -> uid lookup table */
+__KHASH_TYPE(adcsids, adc_sid_t, int64_t);
+
+
typedef struct {
adc_state_t state;
adc_sid_t mysid;
hub_adc_myinf_t lastinf;
+ kh_adcsids_t *sids;
bool havelastinf : 1;
bool zlibenable : 1;
} hub_adc_t;
diff --git a/src/hub/chat.c b/src/hub/chat.c
index 620e4e8..8ac329e 100644
--- a/src/hub/chat.c
+++ b/src/hub/chat.c
@@ -30,7 +30,7 @@ __KHASH_IMPL(chatgroups, static kh_inline, int64_t, hub_chat_group_t, 1, kh_int6
/* Ref/unref a user/group ID */
static void user_ref(hub_t *h, int64_t user) {
hub_user_t *u = user < 0 ? NULL : hub_users_get(h, user);
- if(u) hub_user_ref(h, u);
+ if(u) hub_user_ref(u);
}
static void user_unref(hub_t *h, int64_t user) {
diff --git a/src/hub/nmdc.c b/src/hub/nmdc.c
index 61abf1d..06ca7f3 100644
--- a/src/hub/nmdc.c
+++ b/src/hub/nmdc.c
@@ -38,27 +38,34 @@
} while(0)
-static int64_t hub_nmdc_userid(hub_t *h, const char *nick, int len) {
- return len == (int)strlen(h->n.nick) && strncmp(h->n.nick, nick, len) == 0 ? 0 : hub_users_genid(h, nick, len);
+
+__KHASH_IMPL(nmdcdefer, static kh_inline, int64_t, hub_nmdc_defer_t, 1, kh_int64_hash_func, kh_int64_hash_equal);
+
+
+static hub_nmdc_defer_t *hub_nmdc_defer_del(hub_t *h, int64_t uid) {
+ khiter_t k = kh_get(nmdcdefer, h->n.defer, uid);
+ if(k == kh_end(h->n.defer))
+ return NULL;
+ /* XXX: The value is used after deleting it from the hash table. This
+ * relies on khash behaviour: It won't shrink the table on delete, nor will
+ * it modify deleted values until their slots are being re-used. */
+ kh_del(nmdcdefer, h->n.defer, k);
+ return &kh_value(h->n.defer, k);
}
-/* Initializes a user_update_t for a specific user name, and returns its uid. */
-static int64_t hub_nmdc_update_init(hub_t *h, hub_user_update_t *d, const char *nick, int len) {
- int64_t uid = hub_nmdc_userid(h, nick, len);
- hub_user_update_init(h, d, uid);
- if(!d->u->nick) {
- char *unick = nmdc_convert("UTF-8", hub_hub_encoding(d->h), nick, len);
- d->u->nick = unick;
- if(strcmp(unick, nick) != 0) {
- d->u->n.nick = malloc(len+1);
- memcpy(d->u->n.nick, nick, len);
- d->u->n.nick[len] = 0;
- }
- hub_user_update_field(d, HUBU_NICK);
- }
+static hub_nmdc_defer_t *hub_nmdc_defer_put(hub_t *h, int64_t uid) {
+ int r;
+ khiter_t k = kh_put(nmdcdefer, h->n.defer, uid, &r);
+ hub_nmdc_defer_t *def = &kh_value(h->n.defer, k);
+ if(r != 0)
+ memset(def, 0, sizeof(hub_nmdc_defer_t));
+ return def;
+}
+
- return uid;
+static int64_t hub_nmdc_userid(hub_t *h, const char *nick, int len) {
+ return len == (int)strlen(h->n.nick) && strncmp(h->n.nick, nick, len) == 0 ? 0 : hub_users_genid(h, nick, len);
}
@@ -161,6 +168,33 @@ static void hub_nmdc_myinfo_update(nmdc_myinfo_t *nfo, hub_user_update_t *d) {
}
+static void hub_nmdc_myinfo_initnewuser(nmdc_myinfo_t *nfo, hub_user_update_t *d, int64_t uid) {
+ d->u->id = uid;
+
+ char *unick = nmdc_convert("UTF-8", hub_hub_encoding(d->h), nfo->nick, strlen(nfo->nick));
+ d->u->nick = unick;
+ if(strcmp(unick, nfo->nick) != 0) {
+ d->u->n.nick = malloc(strlen(nfo->nick)+1);
+ strcpy(d->u->n.nick, nfo->nick);
+ }
+ hub_user_update_field(d, HUBU_NICK);
+
+ /* If we have some deferred info, we can merge it now */
+ hub_nmdc_defer_t *def = hub_nmdc_defer_del(d->h, d->u->id);
+ if(!def)
+ return;
+ if(def->op)
+ hub_user_update_field(d, HUBU_FLAGS);
+ if(!net_ip4_isany(def->ip4))
+ hub_user_update_field(d, HUBU_IP4);
+ if(!net_ip6_isany(def->ip6))
+ hub_user_update_field(d, HUBU_IP6);
+ d->u->op = def->op;
+ d->u->ip4 = def->ip4;
+ d->u->ip6 = def->ip6;
+}
+
+
static void hub_nmdc_myinfo(hub_t *h, const char *cmd, int len) {
nmdc_myinfo_t nfo;
if(!nmdc_myinfo_parse(&nfo, hub_hub_encoding(h), cmd)) {
@@ -168,31 +202,20 @@ static void hub_nmdc_myinfo(hub_t *h, const char *cmd, int len) {
ywarn("%d: Invalid info format: %.*s", h->id, len, cmd);
return;
}
- hub_user_update_t d;
- hub_nmdc_update_init(h, &d, nfo.nick, strlen(nfo.nick));
- bool newuser = d.u->id < 0;
- hub_nmdc_myinfo_update(&nfo, &d);
- nmdc_myinfo_free(&nfo);
+ hub_user_update_t d[1];
+ int64_t uid = hub_nmdc_userid(h, nfo.nick, strlen(nfo.nick));
+ hub_user_t *u = hub_users_get(h, uid);
+ bool newuser = !u || !u->online;
+ if(newuser)
+ u = hub_user_new(false);
+ hub_user_update_init(h, d, u);
- /* If we hadn't received a $MyINFO from this user before, make sure that
- * the users' IP address (obtained from $UserIP) and op flag (obtained from
- * $OpList) are still set in this update. This ensures that they are
- * included in the UserJoined signal. */
- if(!d.u->online) {
- if(d.u->op)
- hub_user_update_field(&d, HUBU_FLAGS);
- if(!net_ip4_isany(d.u->ip4))
- hub_user_update_field(&d, HUBU_IP4);
- if(!net_ip6_isany(d.u->ip6))
- hub_user_update_field(&d, HUBU_IP6);
- d.u->online = true;
- }
+ if(newuser)
+ hub_nmdc_myinfo_initnewuser(&nfo, d, uid);
+ hub_nmdc_myinfo_update(&nfo, d);
- /* This function would fail if there are two users with the same nick (in
- * which case the above _genid() would consider them the same user, thus
- * can't happen), or if no nick was set in the update process (can't happen
- * either, the $MyINFO parser requires a nick to be present). */
- assert(hub_user_update_done(&d));
+ hub_user_update_done(d, newuser);
+ nmdc_myinfo_free(&nfo);
/* User list completion detection: If this is a new user, reset the
* timeout. If this is an update to a user already in the list, then we
@@ -208,6 +231,7 @@ static void hub_nmdc_myinfo(hub_t *h, const char *cmd, int len) {
static void hub_nmdc_quit_reset(hub_t *h) {
if(h->n.quituser != -1) {
+ hub_nmdc_defer_del(h, h->n.quituser);
if(!hub_user_left(h, h->n.quituser, h->n.quitinitiator, h->n.quitmessage ? h->n.quitmessage : ""))
ywarn("%d: Quit for user not on the hub: %"PRIi64, h->id, h->n.quituser);
h->n.quituser = -1;
@@ -300,6 +324,28 @@ static void hub_nmdc_to(hub_t *h, const char *cmd, int len) {
}
+static void hub_nmdc_userip_set(hub_t *h, int64_t uid, const char *ip) {
+ hub_user_t *u = hub_users_get(h, uid);
+
+ /* User is online, modify struct directly */
+ if(u) {
+ hub_user_update_t d;
+ hub_user_update_init(h, &d, u);
+ if(inet_pton(AF_INET, ip, &d.u->ip4) == 1)
+ hub_user_update_field(&d, HUBU_IP4);
+ else if(inet_pton(AF_INET6, ip, &d.u->ip6) == 1)
+ hub_user_update_field(&d, HUBU_IP6);
+ hub_user_update_done(&d, false);
+ return;
+ }
+
+ /* Otherwise, defer this information */
+ hub_nmdc_defer_t *def = hub_nmdc_defer_put(h, uid);
+ if(inet_pton(AF_INET, ip, &def->ip4) != 1)
+ inet_pton(AF_INET6, ip, &def->ip6);
+}
+
+
static void hub_nmdc_userip(hub_t *h, const char *cmd, int len) {
const char *lst = cmd + sizeof "$UserIP";
char buf[40];
@@ -319,17 +365,31 @@ static void hub_nmdc_userip(hub_t *h, const char *cmd, int len) {
int l = end-sep-1 > (int)sizeof(buf)-1 ? (int)sizeof(buf)-1 : end-sep-1;
strncpy(buf, sep+1, l);
buf[l] = 0;
+ hub_nmdc_userip_set(h, hub_nmdc_userid(h, lst, sep-lst), buf);
+ lst = end + (*end == '|' ? 0 : 2);
+ }
+}
+
+static void hub_nmdc_oplist_set(hub_t *h, int64_t uid) {
+ /* If *we* are OP, update our hub counts */
+ if(uid == 0)
+ hub_manager_settype(h, HUBM_HOP);
+
+ /* User is online, modify struct directly */
+ hub_user_t *u = hub_users_get(h, uid);
+ if(u) {
hub_user_update_t d;
- hub_nmdc_update_init(h, &d, lst, sep-lst);
- /* XXX: Does this modify ip4 if it's not a valid IPv6 address? */
- if(inet_pton(AF_INET, buf, &d.u->ip4) == 1)
- hub_user_update_field(&d, HUBU_IP4);
- else if(inet_pton(AF_INET6, buf, &d.u->ip6) == 1)
- hub_user_update_field(&d, HUBU_IP6);
- assert(hub_user_update_done(&d));
- lst = end + (*end == '|' ? 0 : 2);
+ hub_user_update_init(h, &d, u);
+ if(!d.u->op)
+ hub_user_update_field(&d, HUBU_FLAGS);
+ d.u->op = true;
+ hub_user_update_done(&d, false);
+ return;
}
+
+ /* Otherwise, defer this information */
+ hub_nmdc_defer_put(h, uid)->op = true;
}
@@ -348,15 +408,7 @@ static void hub_nmdc_oplist(hub_t *h, const char *cmd, int len) {
nickend += *nickend == '|' ? 0 : 2;
continue;
}
-
- hub_user_update_t d;
- if(!hub_nmdc_update_init(h, &d, lst, nickend-lst))
- hub_manager_settype(h, HUBM_HOP);
- if(d.u->op != true) {
- d.u->op = true;
- hub_user_update_field(&d, HUBU_FLAGS);
- }
- assert(hub_user_update_done(&d));
+ hub_nmdc_oplist_set(h, hub_nmdc_userid(h, lst, nickend-lst));
lst = nickend + (*nickend == '|' ? 0 : 2);
}
}
@@ -634,6 +686,8 @@ static void hub_nmdc_connected(hub_t *h) {
h->n.lastinfo = NULL;
h->n.nick = nmdc_convert(hub_hub_encoding(h), "UTF-8", hub_hub_mynick(h), -1);
+ h->n.defer = kh_init(nmdcdefer);
+
ev_init(&h->n.listtimer, hub_nmdc_userlist_timeout);
h->n.listtimer.data = h;
@@ -649,6 +703,7 @@ static void hub_nmdc_disconnected(hub_t *h) {
free(h->n.nick);
free(h->n.lastinfo);
free(h->n.quitmessage);
+ kh_destroy(nmdcdefer, h->n.defer);
hub_manager_settype(h, HUBM_HNONE);
ev_timer_stop(EV_DEFAULT_UC_ &h->n.listtimer);
ev_timer_stop(EV_DEFAULT_UC_ &h->n.quittimer);
diff --git a/src/hub/nmdc.h b/src/hub/nmdc.h
index ca0b8ff..bc93757 100644
--- a/src/hub/nmdc.h
+++ b/src/hub/nmdc.h
@@ -26,10 +26,28 @@
typedef enum { HUBN_LOCK = 1, HUBN_IDENTIFY = 2, HUBN_VERIFY = 4, HUBN_NORMAL = 8 } hub_nmdc_state_t;
+
+/* Some userinfo is received out-of-order with respect to $MyINFO messages,
+ * such as the op flag ($OpList) and the IP ($UserIP).
+ * If we receive that info before a $MyINFO (i.e. the user isn't officially
+ * considered "online" yet), we have to buffer that information until we can
+ * consider the user as "online". */
+typedef struct {
+ bool op;
+ struct in_addr ip4;
+ struct in6_addr ip6;
+} hub_nmdc_defer_t;
+
+
+/* uid -> defer lookup table. */
+__KHASH_TYPE(nmdcdefer, int64_t, hub_nmdc_defer_t);
+
+
typedef struct {
hub_nmdc_state_t state;
char *lastinfo; /* Last $MyINFO we sent to the hub */
char *nick; /* Our nick, in the hub encoding */
+ kh_nmdcdefer_t *defer;
/* Timer used to detect when we (may) have the complete user list. */
ev_timer listtimer;
/* Short timeout to wait for a "is kicking" or $Quit message for a particular user */
diff --git a/src/hub/users.c b/src/hub/users.c
index 873f233..9b26486 100644
--- a/src/hub/users.c
+++ b/src/hub/users.c
@@ -59,18 +59,13 @@ static bool field_known(uint16_t num) {
__KHASH_IMPL(userlist, static kh_inline, hub_user_t *, char, 0, hub_user_hash, hub_user_equal);
-/* SID -> user lookup table */
-#define hub_user_sid_hash(x) ((khint_t)(x)->a.sid)
-#define hub_user_sid_equal(x, y) ((x)->a.sid == (y)->a.sid)
-__KHASH_IMPL(ulistsid, static kh_inline, hub_user_t *, char, 0, hub_user_sid_hash, hub_user_sid_equal);
-
-
-static hub_user_t *hub_user_new(hub_t *h) {
+hub_user_t *hub_user_new(bool adc) {
hub_user_t *u;
- u = calloc(1, h->proto == &hub_adc ? offsetof(hub_user_t, a) + sizeof((*u).a) : offsetof(hub_user_t, n) + sizeof((*u).n));
- u->adc = h->proto == &hub_adc;
- u->delmark = !h->u.delmark;
+ u = calloc(1, adc ? offsetof(hub_user_t, a) + sizeof((*u).a) : offsetof(hub_user_t, n) + sizeof((*u).n));
+ u->adc = adc;
u->id = -1;
+ u->online = true;
+ u->ref = 1;
u->ip4.s_addr = INADDR_ANY;
memcpy(&u->ip6, &in6addr_any, sizeof(struct in6_addr));
return u;
@@ -88,6 +83,19 @@ static void hub_user_free(hub_user_t *u) {
}
+void hub_user_ref(hub_user_t *u) {
+ u->ref++;
+}
+
+
+void hub_user_unref(hub_t *h, hub_user_t *u) {
+ u->ref--;
+ assert(u->ref != UINT32_MAX);
+ u->delmark = !h->u.delmark;
+ h->u.needsweep = true;
+}
+
+
int64_t hub_users_genid(hub_t *h, const char *data, int len) {
uint8_t res[32]; /* 256 bits for SHA-256 */
char *buf = malloc(len + sizeof(h->u.idsalt));
@@ -131,37 +139,40 @@ static void hub_users_sweep(EV_P_ ev_timer *w, int revents) {
}
-/* Generates u->id and adds it to the user list. Ownership of *u is transfered
- * to the list. This user will overwrite any existing user if there is a
- * collision on the user ID. */
-static void hub_user_add(hub_t *h, bool self, hub_user_t *u) {
+/* Ownership of *u is transfered to the list. Returns true on success, false
+ * if there was a collision - in which case the user object is freed. */
+static bool hub_user_add(hub_t *h, hub_user_t *u) {
int r;
khiter_t k;
+ assert(u->ref == 1);
- u->id = self ? 0 : u->adc ? hub_users_genid(h, u->a.cid, 24)
- : hub_users_genid(h, u->n.nick ? u->n.nick : u->nick, strlen(u->n.nick ? u->n.nick : u->nick));
k = kh_put(userlist, h->u.list, u, &r);
- if(r == 0) {
- assert(!u->ref);
- hub_user_t *oldu = kh_key(h->u.list, k);
- u->ref = oldu->ref;
+ if(r != 0) {
kh_key(h->u.list, k) = u;
+ return true;
+ }
- if(oldu->adc && oldu->online) {
- k = kh_get(ulistsid, h->u.sids, oldu);
- assert(k != kh_end(h->u.sids));
- kh_del(ulistsid, h->u.sids, k);
- }
+ /* User with that ID already exists. This can happen in two cases:
+ * 1. The user has left the hub earlier but hasn't been deleted in a sweep
+ * yet, or still has ref > 0.
+ * 2, There is a collision on the user id and the other user is still
+ * online.
+ * In case (1), we delete the old user and put the new one in place,
+ * copying the reference count.
+ * In case (2), we free the new user and pretend that it was never added.
+ */
+ hub_user_t *oldu = kh_key(h->u.list, k);
+ if(!oldu->online) { /* case (1) */
+ u->ref += oldu->ref;
hub_user_free(oldu);
- } else
kh_key(h->u.list, k) = u;
-
- if(u->adc) {
- assert(u->online);
- k = kh_put(ulistsid, h->u.sids, u, &r);
- assert(r != 0);
- kh_key(h->u.sids, k) = u;
+ return false;
}
+
+ /* case (2) */
+ ywarn("%d: User ID collision between %s and %s (%"PRId64")", h->id, u->nick, oldu->nick, u->id);
+ hub_user_free(u);
+ return false;
}
@@ -239,40 +250,18 @@ static void hub_users_append_struct(hub_user_t *u, uint16_t *fields, int num, DB
}
-void hub_user_ref(hub_t *h, hub_user_t *u) {
- u->ref++;
-}
-
-
-void hub_user_unref(hub_t *h, hub_user_t *u) {
- u->ref--;
- assert(u->ref != UINT32_MAX);
- u->delmark = !h->u.delmark;
- h->u.needsweep = true;
-}
-
-
-void hub_user_update_init(hub_t *h, hub_user_update_t *d, int64_t id) {
- hub_user_t key = {};
- key.id = id;
-
- khiter_t k = id == -1 ? kh_end(h->u.list) : kh_get(userlist, h->u.list, &key);
- d->u = k == kh_end(h->u.list) ? hub_user_new(h) : kh_key(h->u.list, k);
-
+void hub_user_update_init(hub_t *h, hub_user_update_t *d, hub_user_t *user) {
+ assert(user->online);
d->changenum = 0;
+ d->u = user;
d->h = h;
- d->self = id == 0;
- d->oldsharesize = d->u->sharesize;
- d->oldonline = d->u->online;
+ d->oldsharesize = user->sharesize;
}
void hub_user_update_field(hub_user_update_t *d, hub_user_field_t field) {
int i;
- assert(!(field == HUBU_CID && d->u->id >= 0)); /* hub/adc.c should ensure this */
- assert(d->u->adc || !(field == HUBU_NICK && d->u->id >= 0)); /* hub/nmdc.c should ensure this */
-
for(i=0; i<d->changenum; i++)
if(d->changes[i] == field)
break;
@@ -283,42 +272,15 @@ void hub_user_update_field(hub_user_update_t *d, hub_user_field_t field) {
}
-void hub_user_update_abort(hub_user_update_t *d) {
- if(d->u->id < 0)
- hub_user_free(d->u);
-}
-
-
-bool hub_user_update_done(hub_user_update_t *d) {
- int i;
- bool hascid = false, hasnick = false;
- bool new = d->u->id < 0;
+void hub_user_update_done(hub_user_update_t *d, bool newuser) {
hub_t *h = d->h;
+ assert(d->u->id >= 0);
- for(i=0; i<d->changenum; i++) {
- switch(d->changes[i]) {
- case HUBU_CID:
- assert(d->u->id < 0);
- hascid = true;
- break;
- case HUBU_NICK:
- hasnick = true;
- break;
- default:
- break;
- }
- }
-
- if(new) {
- if(d->u->adc ? !hascid : !hasnick) {
- hub_user_update_abort(d);
- return false;
- }
- hub_user_add(h, d->self, d->u);
- }
+ if(newuser)
+ hub_user_add(h, d->u);
- if(h->u.props.UserListComplete && d->changenum > 0 && d->u->online) {
- DBusMessage *sig = dbus_message_new_signal(h->dbo.path, "net.blicky.Globster.HubUsers", !d->oldonline ? "UserJoined" : "UserChanged");
+ if(h->u.props.UserListComplete && d->changenum > 0) {
+ DBusMessage *sig = dbus_message_new_signal(h->dbo.path, "net.blicky.Globster.HubUsers", newuser ? "UserJoined" : "UserChanged");
DBusMessageIter i1, i2;
uint16_t *v = d->changes;
dbus_message_iter_init_append(sig, &i1);
@@ -331,17 +293,13 @@ bool hub_user_update_done(hub_user_update_t *d) {
}
dbo_prop_changed_t props = {};
- if(!d->oldonline && d->u->online) {
+ if(newuser)
dbo_hubu_UserCount_set_batch(&props, &h->u.props, h->u.props.UserCount+1);
- hub_user_ref(h, d->u);
- }
- if(d->oldonline != d->u->online || d->oldsharesize != d->u->sharesize)
+ if(d->oldsharesize != d->u->sharesize)
dbo_hubu_UserShareSize_set_batch(&props, &h->u.props,
- h->u.props.UserShareSize - (d->oldonline ? d->oldsharesize : 0) + (d->u->online ? d->u->sharesize : 0));
+ h->u.props.UserShareSize - d->oldsharesize + d->u->sharesize);
if(h->u.props.UserListComplete)
dbo_prop_changed(props, h, &dbo_hubu_interface, &h->u.props);
-
- return true;
}
@@ -350,12 +308,6 @@ bool hub_user_left(hub_t *h, int64_t id, int64_t initiator, const char *message)
if(!u || !u->online)
return false;
- if(u->adc) {
- khiter_t k = kh_get(ulistsid, h->u.sids, u);
- assert(k != kh_end(h->u.sids));
- kh_del(ulistsid, h->u.sids, k);
- }
-
dbo_prop_changed_t props = {};
if(h->u.props.UserCount > 0)
dbo_hubu_UserCount_set_batch(&props, &h->u.props, h->u.props.UserCount-1);
@@ -373,18 +325,6 @@ bool hub_user_left(hub_t *h, int64_t id, int64_t initiator, const char *message)
}
-/* Returns -1 when the user hasn't been found */
-int64_t hub_users_bysid(hub_t *h, adc_sid_t sid) {
- hub_user_t key = {};
- khiter_t k;
-
- key.a.sid = sid;
- k = kh_get(ulistsid, h->u.sids, &key);
- return k == kh_end(h->u.sids) ? -1 : kh_key(h->u.sids, k)->id;
-}
-
-
-/* Returns NULL if there's no user online with that ID */
hub_user_t *hub_users_get(hub_t *h, int64_t id) {
hub_user_t key = {};
key.id = id;
@@ -477,7 +417,6 @@ void hub_users_init(hub_t *h, const char *salt) {
h->u.delmark = true;
h->u.needsweep = false;
h->u.list = kh_init(userlist);
- h->u.sids = kh_init(ulistsid);
if(salt)
base32_decode(salt, h->u.idsalt, sizeof(h->u.idsalt));
@@ -511,7 +450,6 @@ void hub_users_destroy(hub_t *h) {
hub_user_free(kh_key(h->u.list, k));
kh_destroy(userlist, h->u.list);
- kh_destroy(ulistsid, h->u.sids);
}
const hub_users_vt_t hub_users_vt = dbo_hubu_funcs();
diff --git a/src/hub/users.h b/src/hub/users.h
index 44e8aee..f5ff63c 100644
--- a/src/hub/users.h
+++ b/src/hub/users.h
@@ -94,13 +94,11 @@ typedef struct {
__KHASH_TYPE(userlist, hub_user_t *, char);
-__KHASH_TYPE(ulistsid, hub_user_t *, char);
typedef struct {
dbo_hubu_properties_t props;
kh_userlist_t *list;
- kh_ulistsid_t *sids; /* Only includes "online" users */
char idsalt[32];
ev_timer sweep;
/* A value that alternates at every sweep period. It is used for comparison
@@ -117,31 +115,30 @@ typedef struct {
uint64_t oldsharesize;
uint16_t changes[HUBU_FIELDNUM];
int changenum;
- bool self;
- bool oldonline;
} hub_user_update_t;
int64_t hub_users_genid(hub_t *h, const char *data, int len);
+hub_user_t *hub_user_new(bool adc);
-void hub_user_ref(hub_t *h, hub_user_t *u);
+void hub_user_ref(hub_user_t *u);
void hub_user_unref(hub_t *h, hub_user_t *u);
-/* Call with id = -1 if the user isn't online or its id isn't known */
-void hub_user_update_init(hub_t *h, hub_user_update_t *d, int64_t id);
-
-
-/* Should be called after a field has been modified. Fields that do not have a
- * hub_user_field_t type do not require this notification. The 'online' flag,
- * while part of the flags, does not need it, either. */
+/* Functions to perform a user info update, to be performed by a protocol
+ * handler.
+ * Fields that do not have a hub_user_field_t type may be modified without
+ * requiring a call to hub_user_update_field(). The following fields may NOT
+ * be modified:
+ * online, ref, delmark, id
+ * When adding a new user, first create an object with hub_user_new(), set its
+ * id, and then perform the update as if it is an old user.
+ * Note that the hub_user_t object may be freed in hub_user_update_done(), so
+ * don't keep the pointer around.
+ */
+void hub_user_update_init(hub_t *h, hub_user_update_t *d, hub_user_t *user);
void hub_user_update_field(hub_user_update_t *d, hub_user_field_t field);
-
-void hub_user_update_abort(hub_user_update_t *d);
-
-/* Returns false if it was unable to generate an ID for this user (i.e. no CID
- * was given). */
-bool hub_user_update_done(hub_user_update_t *d);
+void hub_user_update_done(hub_user_update_t *d, bool newuser);
/* Called when a user has left the hub or when we've disconnected. Returns
@@ -149,9 +146,6 @@ bool hub_user_update_done(hub_user_update_t *d);
bool hub_user_left(hub_t *h, int64_t id, int64_t initiator, const char *message);
-/* Returns -1 when the user hasn't been found */
-int64_t hub_users_bysid(hub_t *h, adc_sid_t sid);
-
/* Returns NULL if there's no user online with that ID */
hub_user_t *hub_users_get(hub_t *h, int64_t id);