From a27137d30626f65d9ef919140a5a6579eece8eda Mon Sep 17 00:00:00 2001 From: Yorhel Date: Thu, 16 May 2013 09:29:03 +0200 Subject: hub/{adc,nmdc,users}: Moved proto-specific user list management logic This is a partial rewrite of the user list management code. Some protocol-specific logic has been moved out of hub/users and into hub/{adc,nmdc}, in order to simplify the logic in hub/users. In particular: * User ID generation is performed in hub/{adc/nmdc} * The SID to user lookup table has been moved to hub/adc * The deferred-user-online logic has been moved to hub/nmdc. This means that new users are not added to the list in hub/users until they can be considered as "online" These changes fix a number of bugs: * hub/adc previously assumed that, if the user ID was known, the user is also online. This assumption didn't hold for uid=0, which resulted in the users' SID and CID not getting updated on reconnect, breaking the SID -> user lookup and causing a possible assertion failure (followed by a crash) in hub/users on disconnect. * Once a user leaves the hub, its struct object is not re-used later on when the user reconnects. This did happen before, and could cause information from the previous user to be re-added to the new user, even if that information doesn't apply to the new user. hub/users had no way of guarding against this bug because it had no way of telling whether the user had been online before or not, due to the NMDC deferred-info logic being merged into hub/users. Since this is a fairly major change, this commit is likely to introduce a few bugs on its own. Some more testing should reveal that, hopefully. --- doc/api.pod | 4 +- src/hub/adc.c | 257 ++++++++++++++++++++++++++++++++++---------------------- src/hub/adc.h | 6 ++ src/hub/chat.c | 2 +- src/hub/nmdc.c | 169 ++++++++++++++++++++++++------------- src/hub/nmdc.h | 18 ++++ src/hub/users.c | 174 ++++++++++++-------------------------- src/hub/users.h | 36 ++++---- 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 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 -online, but may also include a few users that used to be online or that are -still connecting. The above C flag is used to indicates this status. +online, but may also include a few users that have already left the hub. The +above C 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 +__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; ichangenum; 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; ichangenum; 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); -- cgit v1.2.3