--- a/libpurple/protocols/ircv3/meson.build Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/meson.build Tue Nov 29 00:06:45 2022 -0600
@@ -1,4 +1,5 @@
+ 'purpleircv3capabilities.c', 'purpleircv3connection.c',
'purpleircv3messagehandlers.c',
@@ -8,6 +9,7 @@
+ 'purpleircv3capabilities.h', 'purpleircv3connection.h',
'purpleircv3messagehandlers.h',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/ircv3/purpleircv3capabilities.c Tue Nov 29 00:06:45 2022 -0600
@@ -0,0 +1,452 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. +#include "purpleircv3capabilities.h" +#include "purpleircv3connection.h" +#include "purpleircv3core.h" +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; +static guint signals[N_SIGNALS] = {0, }; +struct _PurpleIRCv3Capabilities { + PurpleIRCv3Connection *connection; +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_set_connection(PurpleIRCv3Capabilities *capabilities, + PurpleIRCv3Connection *connection) + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + if(g_set_object(&capabilities->connection, connection)) { + g_object_notify_by_pspec(G_OBJECT(capabilities), + properties[PROP_CONNECTION]); +/****************************************************************************** + * PurpleIRCv3Capabilities Implementation + *****************************************************************************/ +purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities) + gboolean found = FALSE; + purple_ircv3_capabilities_lookup(capabilities, "cap-notify", &found); + purple_ircv3_capabilities_request(capabilities, "cap-notify"); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_DYNAMIC_TYPE(PurpleIRCv3Capabilities, purple_ircv3_capabilities, +purple_ircv3_capabilities_get_property(GObject *obj, guint param_id, + GValue *value, GParamSpec *pspec) + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_value_set_object(value, + purple_ircv3_capabilities_get_connection(capabilities)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_capabilities_set_property(GObject *obj, guint param_id, + const GValue *value, GParamSpec *pspec) + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + purple_ircv3_capabilities_set_connection(capabilities, + g_value_get_object(value)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_capabilities_dispose(GObject *obj) { + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_clear_object(&capabilities->connection); + G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->dispose(obj); +purple_ircv3_capabilities_finalize(GObject *obj) { + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_hash_table_destroy(capabilities->caps); + g_ptr_array_free(capabilities->requests, TRUE); + G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->finalize(obj); +purple_ircv3_capabilities_init(PurpleIRCv3Capabilities *capabilities) { + capabilities->caps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + capabilities->requests = g_ptr_array_new_full(0, g_free); +purple_ircv3_capabilities_class_finalize(G_GNUC_UNUSED PurpleIRCv3CapabilitiesClass *klass) { +purple_ircv3_capabilities_class_init(PurpleIRCv3CapabilitiesClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->dispose = purple_ircv3_capabilities_dispose; + obj_class->finalize = purple_ircv3_capabilities_finalize; + obj_class->get_property = purple_ircv3_capabilities_get_property; + obj_class->set_property = purple_ircv3_capabilities_set_property; + * PurpleIRCv3Capabilities:connection: + * The PurpleIRCv3Connection object that this capabilities was created + properties[PROP_CONNECTION] = g_param_spec_object( + "connection", "connection", + "The connection this capabilities was created for.", + PURPLE_IRCV3_TYPE_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); + * PurpleIRCv3Capabilities::ready: + * @capabilities: The instance. + * Emitted when @capabilities has finished receiving the list of + * capabilities from the server at startup. + * For dynamically added capabilities see the `added` and `removed` + signals[SIG_READY] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_CALLBACK(purple_ircv3_capabilities_default_ready_cb), + * PurpleIRCv3Capabilities::ack: + * @capabilities: The instance. + * @capability: The capability string. + * Emitted when the server has acknowledged a `CAP REQ` call from + * purple_ircv3_capabilities_request. + * The value of @capability will be the same as the one that was requested. + signals[SIG_ACK] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + * PurpleIRCv3Capabilities::nak: + * @capabilities: The instance. + * @capability: The capability string. + * Emitted when the server has nacked a `CAP REQ` call from + * purple_ircv3_capabilities_request. + * The value of @capability will be the same as the one that was requested. + signals[SIG_NAK] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_handle_list(guint n_params, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Capabilities *capabilities = data; + /* Check if we have more messages coming. */ + if(n_params > 1 && purple_strequal(params[0], "*")) { + parts = g_strsplit(params[1], " ", -1); + parts = g_strsplit(params[0], " ", -1); + /* Add each capability to our hash table, splitting the keys and values. */ + for(int i = 0; parts[i] != NULL; i++) { + char *equals = g_strstr_len(parts[i], -1, "="); + char *key = g_strndup(parts[i], equals - parts[i]); + char *value = g_strdup(equals + 1); + g_hash_table_insert(capabilities->caps, key, value); + g_hash_table_insert(capabilities->caps, g_strdup(parts[i]), NULL); + g_signal_emit(capabilities, signals[SIG_READY], 0, signals[SIG_READY]); + /* If no capabilities were requested after we emitted the ready signal + * we're done with capability negotiation. + if(capabilities->requests->len == 0) { + purple_ircv3_connection_writef(capabilities->connection, +purple_ircv3_capabilities_handle_ack_nak(PurpleIRCv3Capabilities *capabilities, + char *caps = g_strjoinv(" ", params); + gboolean found = FALSE; + g_signal_emit(capabilities, sig, 0, caps); + found = g_ptr_array_find_with_equal_func(capabilities->requests, caps, + g_ptr_array_remove_index(capabilities->requests, index); + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "received CAP %s for unknown capability %s", method, caps); + if(capabilities->requests->len == 0) { + purple_ircv3_connection_writef(capabilities->connection, "CAP END"); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_register(GPluginNativePlugin *plugin) { + purple_ircv3_capabilities_register_type(G_TYPE_MODULE(plugin)); +PurpleIRCv3Capabilities * +purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection) { + PURPLE_IRCV3_TYPE_CAPABILITIES, + "connection", connection, +purple_ircv3_capabilities_message_handler(G_GNUC_UNUSED GHashTable *tags, + G_GNUC_UNUSED const char *source, + G_GNUC_UNUSED const char *command, + PurpleIRCv3Connection *connection = data; + PurpleIRCv3Capabilities *capabilities = NULL; + const char *subcommand = NULL; + GStrv subparams = NULL; + capabilities = purple_ircv3_connection_get_capabilities(connection); + /* Initialize some variables to make it easier to call our sub command + * params[0] is the nick or * if it hasn't been negotiated yet, we don't + * have a need for this, so we ignore it. + * params[1] is the CAP subcommand sent from the server. We use it here + * purely for dispatching to our subcommand handlers. + * params[2] and higher are the parameters to the subcommand. To make the + * code a bit easier all around, we subtract 2 from n_params to remove + * references to the nick and subcommand name. Like wise, we add 2 to the + * params GStrv which will now point to the second item in the array again + * ignoring the nick and subcommand. + subcommand = params[1]; + n_subparams = n_params - 2; + subparams = params + 2; + /* Dispatch the subcommand. */ + if(purple_strequal(subcommand, "LS") || + purple_strequal(subcommand, "LIST")) + return purple_ircv3_capabilities_handle_list(n_subparams, subparams, + } else if(purple_strequal(subcommand, "ACK")) { + return purple_ircv3_capabilities_handle_ack_nak(capabilities, + } else if(purple_strequal(subcommand, "NAK")) { + return purple_ircv3_capabilities_handle_ack_nak(capabilities, + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "No handler for CAP subcommand %s", subcommand); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities) + g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL); + return capabilities->connection; +purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, + const char *capability) + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + g_return_if_fail(capability != NULL); + g_ptr_array_add(capabilities->requests, g_strdup(capability)); + purple_ircv3_connection_writef(capabilities->connection, "CAP REQ :%s", +purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, + const char *name, gboolean *found) + gboolean real_found = FALSE; + g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL); + g_return_val_if_fail(name != NULL, NULL); + real_found = g_hash_table_lookup_extended(capabilities->caps, name, NULL, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/ircv3/purpleircv3capabilities.h Tue Nov 29 00:06:45 2022 -0600
@@ -0,0 +1,87 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. +#ifndef PURPLE_IRCV3_CAPABILITIES_H +#define PURPLE_IRCV3_CAPABILITIES_H +#include <glib-object.h> +#include <gplugin-native.h> +#define PURPLE_IRCV3_TYPE_CAPABILITIES (purple_ircv3_capabilities_get_type()) +G_DECLARE_FINAL_TYPE(PurpleIRCv3Capabilities, purple_ircv3_capabilities, + PURPLE_IRCV3, CAPABILITIES, GObject) +#include "purpleircv3connection.h" +G_GNUC_INTERNAL void purple_ircv3_capabilities_register(GPluginNativePlugin *plugin); +G_GNUC_INTERNAL PurpleIRCv3Capabilities *purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection); +G_GNUC_INTERNAL gboolean purple_ircv3_capabilities_message_handler(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data); + * purple_ircv3_capabilities_get_connection: + * @capabilities: The instance. + * Gets the PurpleIRCv3Connection object that @capabilities was created with. + * Returns: (transfer none): The connection instance. +PurpleIRCv3Connection *purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities); + * purple_ircv3_capabilities_request: + * @capabilities: The instance. + * @capability: The capabilities to request. + * This method will send `CAP REQ @capability` to the server. Listen to the + * `::ack` and `::nak` signals which will contain the contents of @capability + * that was passed in here. +void purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, const char *capability); + * purple_ircv3_capabilities_lookup: + * @capabilities: The instance. + * @name: The name of the capability to look for. + * @found: (out) (nullable): A return address for a boolean on whether the + * capability was advertised or not. + * Gets the value that the @name capability provided if it was advertised. To + * determine if the capability was advertised use the @found parameter. + * Returns: The value of the capability named @name. +const char *purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, const char *name, gboolean *found); +#endif /* PURPLE_IRCV3_CAPABILITIES_H */ --- a/libpurple/protocols/ircv3/purpleircv3connection.c Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3connection.c Tue Nov 29 00:06:45 2022 -0600
@@ -44,7 +44,7 @@
PurpleIRCv3Parser *parser;
+ PurpleIRCv3Capabilities *capabilities; G_DEFINE_DYNAMIC_TYPE(PurpleIRCv3Connection, purple_ircv3_connection,
@@ -104,6 +104,7 @@
+ gboolean parsed = FALSE; line = g_data_input_stream_read_line_finish(istream, result, &length,
@@ -126,7 +127,13 @@
- purple_ircv3_parser_parse(connection->parser, line, &error, connection);
+ parsed = purple_ircv3_parser_parse(connection->parser, line, &error, + g_warning("failed to parse '%s': %s", line, + error != NULL ? error->message : "unknown error"); @@ -314,7 +321,7 @@
purple_ircv3_connection_get_cancellable(connection));
- g_value_set_string(value,
+ g_value_set_object(value, purple_ircv3_connection_get_capabilities(connection));
@@ -324,19 +331,6 @@
-purple_ircv3_connection_set_property(GObject *obj, guint param_id,
- const GValue *value, GParamSpec *pspec)
- // PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
purple_ircv3_connection_dispose(GObject *obj) {
PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
@@ -346,6 +340,7 @@
g_clear_object(&connection->output);
g_clear_object(&connection->connection);
+ g_clear_object(&connection->capabilities); g_clear_object(&connection->parser);
G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->dispose(obj);
@@ -357,8 +352,6 @@
g_clear_pointer(&connection->server_name, g_free);
- g_clear_pointer(&connection->capabilities, g_free);
G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->finalize(obj);
@@ -381,8 +374,9 @@
connection->server_name = g_strdup(userparts[1]);
- /* Finally create our cancellable. */
+ /* Finally create our objects. */ connection->cancellable = g_cancellable_new();
+ connection->capabilities = purple_ircv3_capabilities_new(connection); @@ -399,7 +393,6 @@
PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass);
obj_class->get_property = purple_ircv3_connection_get_property;
- obj_class->set_property = purple_ircv3_connection_set_property;
obj_class->constructed = purple_ircv3_connection_constructed;
obj_class->dispose = purple_ircv3_connection_dispose;
obj_class->finalize = purple_ircv3_connection_finalize;
@@ -430,10 +423,10 @@
- properties[PROP_CAPABILITIES] = g_param_spec_string(
+ properties[PROP_CAPABILITIES] = g_param_spec_object( "capabilities", "capabilities",
"The capabilities that the server supports",
+ PURPLE_IRCV3_TYPE_CAPABILITIES, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
@@ -488,32 +481,9 @@
+PurpleIRCv3Capabilities * purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection) {
g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL);
return connection->capabilities;
-purple_ircv3_connection_append_capabilities(PurpleIRCv3Connection *connection,
- const char *capabilities)
- g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
- g_return_if_fail(capabilities != NULL);
- if(connection->capabilities == NULL) {
- connection->capabilities = g_strdup(capabilities);
- char *tmp = connection->capabilities;
- connection->capabilities = g_strdup_printf("%s %s",
- connection->capabilities,
- g_object_notify_by_pspec(G_OBJECT(connection),
- properties[PROP_CAPABILITIES]);
--- a/libpurple/protocols/ircv3/purpleircv3connection.h Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3connection.h Tue Nov 29 00:06:45 2022 -0600
@@ -33,6 +33,8 @@
G_DECLARE_FINAL_TYPE(PurpleIRCv3Connection, purple_ircv3_connection,
PURPLE_IRCV3, CONNECTION, PurpleConnection)
+#include "purpleircv3capabilities.h" G_GNUC_INTERNAL void purple_ircv3_connection_register(GPluginNativePlugin *plugin);
G_GNUC_INTERNAL PurpleAccount *purple_ircv3_connection_get_account(PurpleIRCv3Connection *connection);
@@ -49,9 +51,7 @@
* Returns: The list of capabilities that the server supports.
-const char *purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection);
-G_GNUC_INTERNAL void purple_ircv3_connection_append_capabilities(PurpleIRCv3Connection *connection, const char *capabilities);
+PurpleIRCv3Capabilities *purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection); --- a/libpurple/protocols/ircv3/purpleircv3core.c Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3core.c Tue Nov 29 00:06:45 2022 -0600
@@ -26,6 +26,7 @@
#include "purpleircv3core.h"
+#include "purpleircv3capabilities.h" #include "purpleircv3connection.h"
#include "purpleircv3protocol.h"
@@ -71,8 +72,9 @@
+ purple_ircv3_capabilities_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + purple_ircv3_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin)); purple_ircv3_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- purple_ircv3_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin));
manager = purple_protocol_manager_get_default();
--- a/libpurple/protocols/ircv3/purpleircv3messagehandlers.c Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3messagehandlers.c Tue Nov 29 00:06:45 2022 -0600
@@ -47,76 +47,6 @@
/******************************************************************************
- *****************************************************************************/
-purple_ircv3_message_handler_cap_list(guint n_params, GStrv params,
- GError **error, gpointer data)
- PurpleIRCv3Connection *connection = data;
- /* Check if we have more messages coming. */
- if(n_params > 1 && purple_strequal(params[0], "*")) {
- purple_ircv3_connection_append_capabilities(connection, params[1]);
- purple_ircv3_connection_append_capabilities(connection, params[0]);
- g_message("**** capabilities-list: %s",
- purple_ircv3_connection_get_capabilities(connection));
- purple_ircv3_connection_writef(connection, "CAP END");
-purple_ircv3_message_handler_cap(GHashTable *tags, const char *source,
- const char *command, guint n_params,
- GStrv params, GError **error, gpointer data)
- const char *subcommand = NULL;
- GStrv subparams = NULL;
- /* Initialize some variables to make it easier to call our sub command
- * params[0] is the nick or * if it hasn't been negotiated yet, we don't
- * have a need for this, so we ignore it.
- * params[1] is the CAP subcommand sent from the server. We use it here
- * purely for dispatching to our subcommand handlers.
- * params[2] and higher are the parameters to the subcommand. To make the
- * code a bit easier all around, we subtract 2 from n_params to remove
- * references to the nick and subcommand name. Like wise, we add 2 to the
- * params GStrv which will now point to the second item in the array again
- * ignoring the nick and subcommand.
- subcommand = params[1];
- n_subparams = n_params - 2;
- subparams = params + 2;
- /* Dispatch the subcommand. */
- if(purple_strequal(subcommand, "LS") ||
- purple_strequal(subcommand, "LIST"))
- return purple_ircv3_message_handler_cap_list(n_subparams, subparams,
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "No handler for CAP subcommand %s", subcommand);
-/******************************************************************************
*****************************************************************************/
--- a/libpurple/protocols/ircv3/purpleircv3messagehandlers.h Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3messagehandlers.h Tue Nov 29 00:06:45 2022 -0600
@@ -51,7 +51,6 @@
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_cap(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data);
G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_fallback(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data);
G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_ping(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data);
G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_privmsg(GHashTable *tags, const char *source, const char *command, guint n_params, GStrv params, GError **error, gpointer data);
--- a/libpurple/protocols/ircv3/purpleircv3parser.c Tue Nov 29 00:05:16 2022 -0600
+++ b/libpurple/protocols/ircv3/purpleircv3parser.c Tue Nov 29 00:06:45 2022 -0600
@@ -18,6 +18,7 @@
#include "purpleircv3parser.h"
+#include "purpleircv3capabilities.h" #include "purpleircv3core.h"
#include "purpleircv3messagehandlers.h"
@@ -398,7 +399,7 @@
purple_ircv3_message_handler_fallback);
purple_ircv3_parser_add_handler(parser, "CAP",
- purple_ircv3_message_handler_cap);
+ purple_ircv3_capabilities_message_handler); purple_ircv3_parser_add_handler(parser, "NOTICE",
purple_ircv3_message_handler_privmsg);
purple_ircv3_parser_add_handler(parser, "PING",