From b120125155df0a9eb1ec13cba4da435f67bd00e0 Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Sun, 28 Jan 2001 21:44:10 +0000 Subject: [PATCH] Updates. --- CHANGES | 49 +++++++++++ TODO | 7 ++ apps/silcd/command.c | 45 +++++----- apps/silcd/command_reply.c | 15 +++- apps/silcd/command_reply.h | 1 + apps/silcd/idlist.c | 15 ++-- apps/silcd/packet_receive.c | 126 ++++++++++++++++++++++++++-- apps/silcd/packet_send.c | 9 +- apps/silcd/server.c | 64 ++++++++++++-- apps/silcd/server.h | 2 + doc/draft-riikonen-silc-pp-01.nroff | 4 +- lib/silcclient/command.c | 2 +- lib/silcclient/idlist.c | 3 + 13 files changed, 291 insertions(+), 51 deletions(-) diff --git a/CHANGES b/CHANGES index db4eb155..e2e5fb62 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,52 @@ +Sun Jan 28 16:19:49 EET 2001 Pekka Riikonen + + * Fixed JOIN command on client library. Wrong number of arguments + used to crash the client. + + * Added silc_server_channel_has_global function to check whether + channel has global users or not. + + * Added silc_server_channel_has_local function to check whether channel + has locally connected clients on the channel. + + * The silc_server_remove_from_one_channel now checks whether the + channel has global users or not after given client was removed from + the channel. It also checks whether the channel has local clients + on the channel anymore. If it does not have then the channel entry + is removed as it is not needed anymore. + + * The silc_server_notify now checks on JOIN notify whether the joining + client is one of locally connected or global. If it is global then + the channel has now global users on the channel and that is marked + to the channel entry. Also, it now saves the global client to + global list who is joining and JOINs it to the channel. This is + for normal server, that is. + + Changed silc_server_send_notify_on_channel, + silc_server_packet_relay_to_channel and + silc_server_packet_send_to_channel check if we are normal server + and client has router set (ie. global client) do not send the + message to that client, as it is already routed to our router. + + * Implemented LEAVE notify type handling in silc_server_notify + function. + + * Tested LEAVE command in router environment successfully. Tested + with two routers, two servers and two clients. + + * Updated TODO. + + * idlist_find_xxx_by_id routines now dumps the ID on the debug mode. + + * Implemented SIGNOFF notify type handling in silc_server_notify + function. + + * silc_server_remove_id now removes the client entry from all channels + it has joined and thusly sends SIGNOFF notify type. + + * Rewrote the NAMES list generation in server by removing two excess + loops. The lists are created now inside one loop. + Sat Jan 27 22:34:56 EET 2001 Pekka Riikonen * silc_server_remove_channel_user checks now also global list diff --git a/TODO b/TODO index f4819f5d..e60dfdc2 100644 --- a/TODO +++ b/TODO @@ -15,6 +15,13 @@ help is really appreciated - and needed. New features TODO ================= + o Optimization in general. I have not focused to program optimized code + in many circumstances. It has been more important to get this up and + working. A lot must be optimized especially the ID cache. Instead of + using lists some hash tables should be used. The searching by ID and + such must be made a lot faster. If someone would like dedicate their + efforts purely to generate optimized code I'd be appreaciated. + o We should replace all short, int, long, unsigned short, unsigned int, unsigned long with some pre-defined datatypes that really are what we want on all platforms. int16, uint16, int32, uint32 etc. are diff --git a/apps/silcd/command.c b/apps/silcd/command.c index 87eebd88..4cdb2ca4 100644 --- a/apps/silcd/command.c +++ b/apps/silcd/command.c @@ -2829,6 +2829,7 @@ SILC_SERVER_CMD_FUNC(names) char *name_list = NULL, *n; SilcBuffer client_id_list; SilcBuffer client_mode_list; + SilcBuffer idp; SILC_SERVER_COMMAND_CHECK_ARGC(SILC_COMMAND_NAMES, cmd, 1, 2); @@ -2860,12 +2861,20 @@ SILC_SERVER_CMD_FUNC(names) goto out; } - /* Assemble the name list now */ + /* Assemble the lists now */ + name_list = NULL; - len = 0; + len = i = 0; + client_id_list = silc_buffer_alloc((SILC_ID_CLIENT_LEN + 4) * + silc_list_count(channel->user_list)); + silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list)); + client_mode_list = + silc_buffer_alloc(4 * silc_list_count(channel->user_list)); + silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list)); + silc_list_start(channel->user_list); - i = 0; while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { + /* Nickname */ n = chl->client->nickname; if (n) { len2 = strlen(n); @@ -2880,39 +2889,23 @@ SILC_SERVER_CMD_FUNC(names) len++; i++; } - } - if (!name_list) - name_list = ""; - - /* Assemble the Client ID list now */ - client_id_list = silc_buffer_alloc((SILC_ID_CLIENT_LEN + 4) * - silc_list_count(channel->user_list)); - silc_buffer_pull_tail(client_id_list, SILC_BUFFER_END(client_id_list)); - silc_list_start(channel->user_list); - while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { - SilcBuffer idp; + /* Client ID */ idp = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT); - silc_buffer_format(client_id_list, - SILC_STR_UI_XNSTRING(idp->data, idp->len), - SILC_STR_END); + silc_buffer_put(client_id_list, idp->data, idp->len); silc_buffer_pull(client_id_list, idp->len); silc_buffer_free(idp); - } - silc_buffer_push(client_id_list, - client_id_list->data - client_id_list->head); - /* Assemble mode list */ - client_mode_list = silc_buffer_alloc(4 * - silc_list_count(channel->user_list)); - silc_buffer_pull_tail(client_mode_list, SILC_BUFFER_END(client_mode_list)); - silc_list_start(channel->user_list); - while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { + /* Client's mode on channel */ SILC_PUT32_MSB(chl->mode, client_mode_list->data); silc_buffer_pull(client_mode_list, 4); } + silc_buffer_push(client_id_list, + client_id_list->data - client_id_list->head); silc_buffer_push(client_mode_list, client_mode_list->data - client_mode_list->head); + if (!name_list) + name_list = ""; /* Send reply */ packet = silc_command_reply_payload_encode_va(SILC_COMMAND_NAMES, diff --git a/apps/silcd/command_reply.c b/apps/silcd/command_reply.c index d248f83e..15ce098c 100644 --- a/apps/silcd/command_reply.c +++ b/apps/silcd/command_reply.c @@ -51,6 +51,7 @@ SilcServerCommandReply silc_command_reply_list[] = SILC_SERVER_CMD_REPLY(join, JOIN), SILC_SERVER_CMD_REPLY(whois, WHOIS), SILC_SERVER_CMD_REPLY(identify, IDENTIFY), + SILC_SERVER_CMD_REPLY(names, NAMES), { NULL, 0 }, }; @@ -447,7 +448,7 @@ SILC_SERVER_CMD_REPLY_FUNC(join) /* Add new channel */ SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", - (created == 0 ? "created" : "existing"), channel_name, + (created == 0 ? "existing" : "created"), channel_name, silc_id_render(id, SILC_ID_CHANNEL))); /* Add the channel to our local list. */ @@ -486,3 +487,15 @@ SILC_SERVER_CMD_REPLY_FUNC(join) out: silc_server_command_reply_free(cmd); } + +SILC_SERVER_CMD_REPLY_FUNC(names) +{ + SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context; + SilcServer server = cmd->server; + SilcCommandStatus status; + + SILC_LOG_DEBUG(("Start")); + + COMMAND_CHECK_STATUS; + +} diff --git a/apps/silcd/command_reply.h b/apps/silcd/command_reply.h index 9b5479e1..599623df 100644 --- a/apps/silcd/command_reply.h +++ b/apps/silcd/command_reply.h @@ -60,5 +60,6 @@ void silc_server_command_reply_process(SilcServer server, SILC_SERVER_CMD_REPLY_FUNC(join); SILC_SERVER_CMD_REPLY_FUNC(whois); SILC_SERVER_CMD_REPLY_FUNC(identify); +SILC_SERVER_CMD_REPLY_FUNC(names); #endif diff --git a/apps/silcd/idlist.c b/apps/silcd/idlist.c index cd276491..47ada7d4 100644 --- a/apps/silcd/idlist.c +++ b/apps/silcd/idlist.c @@ -116,7 +116,8 @@ silc_idlist_find_server_by_id(SilcIDList id_list, SilcServerID *id, if (!id) return NULL; - SILC_LOG_DEBUG(("Finding server by ID")); + SILC_LOG_DEBUG(("Server ID (%s)", + silc_id_render(id, SILC_ID_SERVER))); if (!silc_idcache_find_by_id_one(id_list->servers, (void *)id, SILC_ID_SERVER, &id_cache)) @@ -313,7 +314,7 @@ silc_idlist_find_client_by_nickname(SilcIDList id_list, char *nickname, SilcIDCacheEntry id_cache = NULL; SilcClientEntry client = NULL; - SILC_LOG_DEBUG(("Finding client by nickname")); + SILC_LOG_DEBUG(("Client by nickname")); if (server) { if (!silc_idcache_find_by_data(id_list->clients, nickname, &list)) @@ -360,7 +361,7 @@ silc_idlist_find_client_by_hash(SilcIDList id_list, char *nickname, SilcClientEntry client = NULL; unsigned char hash[32]; - SILC_LOG_DEBUG(("Finding client by hash")); + SILC_LOG_DEBUG(("Client by hash")); silc_hash_make(md5hash, nickname, strlen(nickname), hash); @@ -408,7 +409,8 @@ silc_idlist_find_client_by_id(SilcIDList id_list, SilcClientID *id, if (!id) return NULL; - SILC_LOG_DEBUG(("Finding client by ID")); + SILC_LOG_DEBUG(("Client ID (%s)", + silc_id_render(id, SILC_ID_CLIENT))); if (!silc_idcache_find_by_id_one(id_list->clients, (void *)id, SILC_ID_CLIENT, &id_cache)) @@ -541,7 +543,7 @@ silc_idlist_find_channel_by_name(SilcIDList id_list, char *name, SilcIDCacheEntry id_cache = NULL; SilcChannelEntry channel; - SILC_LOG_DEBUG(("Finding channel by name")); + SILC_LOG_DEBUG(("Channel by name")); if (!silc_idcache_find_by_data_loose(id_list->channels, name, &list)) return NULL; @@ -575,7 +577,8 @@ silc_idlist_find_channel_by_id(SilcIDList id_list, SilcChannelID *id, if (!id) return NULL; - SILC_LOG_DEBUG(("Finding channel by ID")); + SILC_LOG_DEBUG(("Channel ID (%s)", + silc_id_render(id, SILC_ID_CHANNEL))); if (!silc_idcache_find_by_id_one(id_list->channels, (void *)id, SILC_ID_CHANNEL, &id_cache)) diff --git a/apps/silcd/packet_receive.c b/apps/silcd/packet_receive.c index e3a491d4..f7c59125 100644 --- a/apps/silcd/packet_receive.c +++ b/apps/silcd/packet_receive.c @@ -701,7 +701,7 @@ void silc_server_new_id(SilcServer server, SilcSocketConnection sock, /* Received Remove Channel User packet to remove a user from a channel. Routers notify other routers that user has left a channel. Client must - not send this packet.. Normal server may send this packet but must not + not send this packet. Normal server may send this packet but must not receive it. */ void silc_server_remove_channel_user(SilcServer server, @@ -766,8 +766,8 @@ void silc_server_remove_channel_user(SilcServer server, goto out; } - /* Remove from channel */ - silc_server_remove_from_one_channel(server, sock, channel, client, FALSE); + /* Remove user from channel */ + silc_server_remove_from_one_channel(server, sock, channel, client, TRUE); out: if (tmp1) @@ -838,7 +838,11 @@ void silc_server_notify(SilcServer server, SilcNotifyType type; SilcArgumentPayload args; SilcChannelID *channel_id; + SilcClientID *client_id; SilcChannelEntry channel; + SilcClientEntry client; + unsigned char *tmp; + unsigned int tmp_len; SILC_LOG_DEBUG(("Start")); @@ -867,6 +871,7 @@ void silc_server_notify(SilcServer server, /* * Distribute the notify to local clients on the channel */ + SILC_LOG_DEBUG(("JOIN notify")); channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_type); if (!channel_id) @@ -880,7 +885,40 @@ void silc_server_notify(SilcServer server, goto out; } - channel->global_users = TRUE; + /* Get client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* If the the client is not in local list we check global list (ie. the + channel will be global channel) and if it does not exist then create + entry for the client. */ + client = silc_idlist_find_client_by_id(server->local_list, + client_id, NULL); + if (!client) { + SilcChannelClientEntry chl; + + client = silc_idlist_find_client_by_id(server->global_list, + client_id, NULL); + if (!client) + client = silc_idlist_add_client(server->global_list, NULL, NULL, NULL, + client_id, sock->user_data, sock); + + /* The channel is global now */ + channel->global_users = TRUE; + + /* Now actually JOIN the global client to the channel */ + chl = silc_calloc(1, sizeof(*chl)); + chl->client = client; + chl->channel = channel; + silc_list_add(channel->user_list, chl); + silc_list_add(client->channels, chl); + } else { + silc_free(client_id); + } /* Send to channel */ silc_server_packet_send_to_channel(server, channel, packet->type, FALSE, @@ -889,9 +927,81 @@ void silc_server_notify(SilcServer server, break; case SILC_NOTIFY_TYPE_LEAVE: + /* + * Distribute the notify to local clients on the channel + */ + SILC_LOG_DEBUG(("LEAVE notify")); + + channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_type); + if (!channel_id) + goto out; + + /* Get channel entry */ + channel = silc_idlist_find_channel_by_id(server->local_list, + channel_id, NULL); + if (!channel) { + silc_free(channel_id); + goto out; + } + + /* Get client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) { + silc_free(channel_id); + goto out; + } + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Send to channel */ + silc_server_packet_send_to_channel(server, channel, packet->type, FALSE, + packet->buffer->data, + packet->buffer->len, FALSE); + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, NULL); + if (!client) { + silc_free(channel_id); + goto out; + } + } + silc_free(client_id); + + /* Remove the user from channel */ + silc_server_remove_from_one_channel(server, sock, channel, client, FALSE); break; case SILC_NOTIFY_TYPE_SIGNOFF: + /* + * Distribute the notify to local clients on the channel + */ + SILC_LOG_DEBUG(("SIGNOFF notify")); + + /* Get client ID */ + tmp = silc_argument_get_arg_type(args, 1, &tmp_len); + if (!tmp) + goto out; + client_id = silc_id_payload_parse_id(tmp, tmp_len); + + /* Get client entry */ + client = silc_idlist_find_client_by_id(server->global_list, + client_id, NULL); + if (!client) { + client = silc_idlist_find_client_by_id(server->local_list, + client_id, NULL); + if (!client) + goto out; + } + silc_free(client_id); + + /* Remove the client from all channels */ + silc_server_remove_from_channels(server, NULL, client); + + /* Remove the client entry */ + silc_idlist_del_client(server->global_list, client); break; /* Ignore rest notify types for now */ @@ -1056,6 +1166,8 @@ void silc_server_remove_id(SilcServer server, SilcIdType id_type; void *id, *id_entry; + SILC_LOG_DEBUG(("Start")); + if (sock->type == SILC_SOCKET_TYPE_CLIENT || server->server_type == SILC_SERVER || packet->src_id_type != SILC_ID_SERVER) @@ -1089,11 +1201,15 @@ void silc_server_remove_id(SilcServer server, id_list = server->global_list; /* Remove the ID */ - switch(id_type) { + switch (id_type) { case SILC_ID_CLIENT: id_entry = silc_idlist_find_client_by_id(id_list, (SilcClientID *)id, NULL); if (id_entry) { + /* Remove from channels */ + silc_server_remove_from_channels(server, NULL, id_entry); + + /* Remove the client entry */ silc_idlist_del_client(id_list, (SilcClientEntry)id_entry); SILC_LOG_DEBUG(("Removed client id(%s) from [%s] %s", diff --git a/apps/silcd/packet_send.c b/apps/silcd/packet_send.c index 7c529233..bbae2c24 100644 --- a/apps/silcd/packet_send.c +++ b/apps/silcd/packet_send.c @@ -444,6 +444,9 @@ void silc_server_packet_send_to_channel(SilcServer server, if (server->server_type == SILC_ROUTER && !route) continue; + if (server->server_type == SILC_SERVER && client->router) + continue; + /* Send to locally connected client */ if (client) { @@ -584,7 +587,8 @@ void silc_server_packet_relay_to_channel(SilcServer server, continue; } - /* XXX Check client's mode on the channel. */ + if (server->server_type == SILC_SERVER && client->router) + continue; /* Get data used in packet header encryption, keys and stuff. */ sock = (SilcSocketConnection)client->connection; @@ -884,6 +888,9 @@ void silc_server_send_notify_on_channels(SilcServer server, continue; } + if (server->server_type == SILC_SERVER && client->router) + continue; + /* Send to locally connected client */ if (c) { diff --git a/apps/silcd/server.c b/apps/silcd/server.c index 1aabffbd..21b865e2 100644 --- a/apps/silcd/server.c +++ b/apps/silcd/server.c @@ -1665,9 +1665,41 @@ void silc_server_free_sock_user_data(SilcServer server, sock->user_data = NULL; } -/* Removes client from all channels it has joined. This is used when - client connection is disconnected. If the client on a channel - is last, the channel is removed as well. */ +/* Checks whether given channel has global users. If it does this returns + TRUE and FALSE if there is only locally connected clients on the channel. */ + +int silc_server_channel_has_global(SilcChannelEntry channel) +{ + SilcChannelClientEntry chl; + + silc_list_start(channel->user_list); + while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { + if (chl->client->router) + return TRUE; + } + + return FALSE; +} + +/* Checks whether given channel has locally connected users. If it does this + returns TRUE and FALSE if there is not one locally connected client. */ + +int silc_server_channel_has_local(SilcChannelEntry channel) +{ + SilcChannelClientEntry chl; + + silc_list_start(channel->user_list); + while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) { + if (!chl->client->router) + return TRUE; + } + + return FALSE; +} + +/* Removes client from all channels it has joined. This is used when client + connection is disconnected. If the client on a channel is last, the + channel is removed as well. This sends the SIGNOFF notify types. */ void silc_server_remove_from_channels(SilcServer server, SilcSocketConnection sock, @@ -1703,7 +1735,7 @@ void silc_server_remove_from_channels(SilcServer server, channel globally from SILC network, in this case we will notify that this client has left the channel. */ if (channel->global_users) - silc_server_send_notify_to_channel(server, channel, TRUE, + silc_server_send_notify_to_channel(server, channel, FALSE, SILC_NOTIFY_TYPE_SIGNOFF, 1, clidp->data, clidp->len); @@ -1717,7 +1749,7 @@ void silc_server_remove_from_channels(SilcServer server, /* Send notify to channel about client leaving SILC and thus the entire channel. */ - silc_server_send_notify_to_channel(server, channel, TRUE, + silc_server_send_notify_to_channel(server, channel, FALSE, SILC_NOTIFY_TYPE_SIGNOFF, 1, clidp->data, clidp->len); silc_buffer_free(chidp); @@ -1761,10 +1793,9 @@ int silc_server_remove_from_one_channel(SilcServer server, /* If this client is last one on the channel the channel is removed all together. */ if (silc_list_count(channel->user_list) < 2) { - /* Notify about leaving client if this channel has global users, - ie. the channel is not created locally. */ + /* Notify about leaving client if this channel has global users. */ if (notify && channel->global_users) - silc_server_send_notify_to_channel(server, channel, TRUE, + silc_server_send_notify_to_channel(server, channel, FALSE, SILC_NOTIFY_TYPE_LEAVE, 1, clidp->data, clidp->len); @@ -1777,9 +1808,24 @@ int silc_server_remove_from_one_channel(SilcServer server, silc_list_del(channel->user_list, chl); silc_free(chl); + /* If there is no global users on the channel anymore mark the channel + as local channel. */ + if (server->server_type == SILC_SERVER && + !silc_server_channel_has_global(channel)) + channel->global_users = FALSE; + + /* If tehre is not at least one local user on the channel then we don't + need the channel entry anymore, we can remove it safely. */ + if (server->server_type == SILC_SERVER && + !silc_server_channel_has_local(channel)) { + silc_idlist_del_channel(server->local_list, channel); + silc_buffer_free(clidp); + return FALSE; + } + /* Send notify to channel about client leaving the channel */ if (notify) - silc_server_send_notify_to_channel(server, channel, TRUE, + silc_server_send_notify_to_channel(server, channel, FALSE, SILC_NOTIFY_TYPE_LEAVE, 1, clidp->data, clidp->len); break; diff --git a/apps/silcd/server.h b/apps/silcd/server.h index 52624050..53018b43 100644 --- a/apps/silcd/server.h +++ b/apps/silcd/server.h @@ -89,6 +89,8 @@ void silc_server_close_connection(SilcServer server, SilcSocketConnection sock); void silc_server_free_sock_user_data(SilcServer server, SilcSocketConnection sock); +int silc_server_channel_has_global(SilcChannelEntry channel); +int silc_server_channel_has_local(SilcChannelEntry channel); void silc_server_remove_from_channels(SilcServer server, SilcSocketConnection sock, SilcClientEntry client); diff --git a/doc/draft-riikonen-silc-pp-01.nroff b/doc/draft-riikonen-silc-pp-01.nroff index 5810c45b..4d27be35 100644 --- a/doc/draft-riikonen-silc-pp-01.nroff +++ b/doc/draft-riikonen-silc-pp-01.nroff @@ -712,10 +712,10 @@ List of SILC Packet types are defined as follows. this packet. This packet maybe sent to entity that is indirectly connected to the sender. - When received and the replaced ID is Client ID the server or + When received and the removed ID is Client ID the server or router must distribute SILC_NOTIFY_TYPE_SIGNOFF to the local clients on the channels (if any) of the client whose - ID was changed. However, the notify type must be sent only + ID was removed. However, the notify type must be sent only once per client. Payload of the packet: See section 2.3.25 Remove ID Payload diff --git a/lib/silcclient/command.c b/lib/silcclient/command.c index 29eceb03..f039aac0 100644 --- a/lib/silcclient/command.c +++ b/lib/silcclient/command.c @@ -670,7 +670,7 @@ SILC_CLIENT_CMD_FUNC(join) silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 2, 1, cmd->argv[1], cmd->argv_lens[1], 2, idp->data, idp->len); - else if (cmd->argc == 4) + else if (cmd->argc == 3) /* XXX Buggy */ buffer = silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 3, diff --git a/lib/silcclient/idlist.c b/lib/silcclient/idlist.c index edd2df04..a14dc63e 100644 --- a/lib/silcclient/idlist.c +++ b/lib/silcclient/idlist.c @@ -112,6 +112,9 @@ SilcClientEntry silc_idlist_get_client_by_id(SilcClient client, { SilcIDCacheEntry id_cache; + SILC_LOG_DEBUG(("Finding client by ID (%s)", + silc_id_render(client_id, SILC_ID_CLIENT))); + /* Find ID from cache */ if (!silc_idcache_find_by_id_one(conn->client_cache, client_id, SILC_ID_CLIENT, &id_cache)) { -- 2.43.0