commit e4bf966a5e6fa5334aded16fc34b6c462fb375db
parent e2193576bb83a8cbcb1e752784b1121001864d94
Author: Jacki <jacki@thejackimonster.de>
Date: Sat, 8 Jun 2024 02:28:29 +0200
Implement functionality of subscriptions to discourses, reading and writing data
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
11 files changed, 1053 insertions(+), 4 deletions(-)
diff --git a/include/gnunet/gnunet_chat_lib.h b/include/gnunet/gnunet_chat_lib.h
@@ -37,13 +37,14 @@
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_time_lib.h>
#include <gnunet/gnunet_util_lib.h>
+#include <stdint.h>
/**
* @def GNUNET_CHAT_VERSION The major and minor version should be identical to
* the #GNUNET_MESSENGER_VERSION of the GNUnet Messenger
* service while the patch version is independent.
*/
-#define GNUNET_CHAT_VERSION 0x000000040000L
+#define GNUNET_CHAT_VERSION 0x000000050000L
#define GNUNET_CHAT_VERSION_MAJOR ((GNUNET_CHAT_VERSION >> 32L) & 0xFFFFL)
#define GNUNET_CHAT_VERSION_MINOR ((GNUNET_CHAT_VERSION >> 16L) & 0xFFFFL)
@@ -87,12 +88,12 @@ enum GNUNET_CHAT_MessageKind
/**
* The kind to inform that something went wrong.
*/
- GNUNET_CHAT_KIND_WARNING = 1, /**< GNUNET_CHAT_KIND_WARNING */
+ GNUNET_CHAT_KIND_WARNING = 1, /**< GNUNET_CHAT_KIND_WARNING */
/**
* The kind to inform that the list of accounts was refreshed.
*/
- GNUNET_CHAT_KIND_REFRESH = 2, /**< GNUNET_CHAT_KIND_REFRESH */
+ GNUNET_CHAT_KIND_REFRESH = 2, /**< GNUNET_CHAT_KIND_REFRESH */
/**
* The kind to inform that the application can be used.
@@ -176,6 +177,16 @@ enum GNUNET_CHAT_MessageKind
GNUNET_CHAT_KIND_SHARED_ATTRIBUTES = 18, /**< GNUNET_CHAT_KIND_SHARED_ATTRIBUTES */
/**
+ * The kind to inform that a discourse was updated.
+ */
+ GNUNET_CHAT_KIND_DISCOURSE = 19, /**< GNUNET_CHAT_KIND_DISCOURSE */
+
+ /**
+ * The kind to describe a data message from a discourse.
+ */
+ GNUNET_CHAT_KIND_DATA = 20, /**< GNUNET_CHAT_KIND_DATA */
+
+ /**
* An unknown kind of message.
*/
GNUNET_CHAT_KIND_UNKNOWN = 0 /**< GNUNET_CHAT_KIND_UNKNOWN */
@@ -232,6 +243,11 @@ struct GNUNET_CHAT_File;
struct GNUNET_CHAT_Invitation;
/**
+ * Struct of a chat discourse.
+ */
+struct GNUNET_CHAT_Discourse;
+
+/**
* Iterator over chat accounts of a specific chat handle.
*
* @param[in,out] cls Closure from #GNUNET_CHAT_iterate_accounts
@@ -342,7 +358,7 @@ typedef enum GNUNET_GenericReturnValue
* Iterator over chat contacts in a specific chat group.
*
* @param[in,out] cls Closure from #GNUNET_CHAT_group_iterate_contacts
- * @param[in,out] group Chat group
+ * @param[in] group Chat group
* @param[in,out] contact Chat contact
* @return #GNUNET_YES if we should continue to iterate, #GNUNET_NO otherwise.
*/
@@ -449,6 +465,31 @@ typedef void
uint64_t size);
/**
+ * Iterator over chat discourses in a specific chat context.
+ *
+ * @param[in,out] cls Closure from #GNUNET_CHAT_context_iterate_discourses
+ * @param[in,out] context Chat context
+ * @param[in,out] discourse Chat discourse
+ */
+typedef enum GNUNET_GenericReturnValue
+(*GNUNET_CHAT_DiscourseCallback) (void *cls,
+ struct GNUNET_CHAT_Context *context,
+ struct GNUNET_CHAT_Discourse *discourse);
+
+/**
+ * Iterator over chat contacts in a specific chat discourse.
+ *
+ * @param[in,out] cls Closure from #GNUNET_CHAT_discourse_iterate_contacts
+ * @param[in] discourse Chat discourse
+ * @param[in,out] contact Chat contact
+ * @return #GNUNET_YES if we should continue to iterate, #GNUNET_NO otherwise.
+ */
+typedef enum GNUNET_GenericReturnValue
+(*GNUNET_CHAT_DiscourseContactCallback) (void *cls,
+ const struct GNUNET_CHAT_Discourse *discourse,
+ struct GNUNET_CHAT_Contact *contact);
+
+/**
* Start a chat handle with a certain configuration.
*
* A custom callback for warnings and message events can be provided optionally
@@ -1276,6 +1317,18 @@ GNUNET_CHAT_context_send_tag (struct GNUNET_CHAT_Context *context,
const char *tag);
/**
+ * Opens a chat discourse under a specific <i>id</i> in a given chat
+ * <i>context</i> to send data live to other contacts.
+ *
+ * @param[in,out] context Chat context
+ * @param[in] id Discourse id
+ * @return Chat discourse
+ */
+struct GNUNET_CHAT_Discourse*
+GNUNET_CHAT_context_open_discourse (struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_ShortHashCode *id);
+
+/**
* Iterates through the contacts of a given chat <i>context</i> with a selected
* callback and custom closure.
*
@@ -1304,6 +1357,20 @@ GNUNET_CHAT_context_iterate_files (struct GNUNET_CHAT_Context *context,
void *cls);
/**
+ * Iterates through the discourses of a given chat <i>context</i> with a
+ * selected callback and custom closure.
+ *
+ * @param[in,out] context Chat context
+ * @param[in] callback Callback for file iteration (optional)
+ * @param[in,out] cls Closure for file iteration (optional)
+ * @return Amount of discourses iterated or #GNUNET_SYSERR on failure
+ */
+int
+GNUNET_CHAT_context_iterate_discourses (struct GNUNET_CHAT_Context *context,
+ GNUNET_CHAT_DiscourseCallback callback,
+ void *cls);
+
+/**
* Returns the kind of a given <i>message</i> to determine its content and
* related usage.
*
@@ -1464,6 +1531,17 @@ struct GNUNET_CHAT_Invitation*
GNUNET_CHAT_message_get_invitation (const struct GNUNET_CHAT_Message *message);
/**
+ * Returns the discourse of a given <i>message</i> if its kind is
+ * #GNUNET_CHAT_KIND_DISCOURSE or #GNUNET_CHAT_KIND_DATA,
+ * otherwise it returns NULL.
+ *
+ * @param[in] message Message
+ * @return The discourse of message or NULL
+ */
+struct GNUNET_CHAT_Discourse*
+GNUNET_CHAT_message_get_discourse (const struct GNUNET_CHAT_Message *message);
+
+/**
* Returns the target message of an operation represented by a given
* <i>message</i> if its kind is #GNUNET_CHAT_KIND_DELETION, otherwise it
* returns NULL.
@@ -1500,6 +1578,32 @@ GNUNET_CHAT_message_iterate_tags (const struct GNUNET_CHAT_Message *message,
void *cls);
/**
+ * Returns the amount of data from a given <i>message</i> if its kind
+ * is #GNUNET_CHAT_KIND_DATA, otherwise it returns NULL.
+ *
+ * @param[in] message Message
+ * @return The amount of data or zero
+ */
+uint64_t
+GNUNET_CHAT_message_available (const struct GNUNET_CHAT_Message *message);
+
+/**
+ * Reads the data from a given <i>message</i> if its kind is
+ * #GNUNET_CHAT_KIND_DATA into a specific <i>data</i> buffer
+ * up to a selected <i>size</i>.
+ *
+ * @param[in] message Message
+ * @param[out] data Data buffer
+ * @param[in] size Data size
+ * @return #GNUNET_OK on success, #GNUNET_NO if there's missing data
+ * to read, otherwise #GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_message_read (const struct GNUNET_CHAT_Message *message,
+ char *data,
+ uint64_t size);
+
+/**
* Returns the name of a given <i>file</i> handle.
*
* @param[in] file File handle
@@ -1720,6 +1824,62 @@ GNUNET_CHAT_invitation_is_accepted (const struct GNUNET_CHAT_Invitation *invitat
enum GNUNET_GenericReturnValue
GNUNET_CHAT_invitation_is_rejected (const struct GNUNET_CHAT_Invitation *invitation);
+/**
+ * Returns the discourse id of a given chat <i>discourse</i>.
+ *
+ * @param[in] discourse Chat discourse
+ * @return Discourse id
+ */
+const struct GNUNET_ShortHashCode*
+GNUNET_CHAT_discourse_get_id (const struct GNUNET_CHAT_Discourse *discourse);
+
+/**
+ * Returns whether a chat <i>discourse</i> is currently open.
+ *
+ * @param[in] discourse Chat discourse
+ * @return #GNUNET_YES if the discourse is open, #GNUNET_SYSERR on failure and
+ * #GNUNET_NO otherwise.
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_discourse_is_open (const struct GNUNET_CHAT_Discourse *discourse);
+
+/**
+ * Closes a given chat <i>discourse</i> to stop receiving any data messages
+ * from the specific discourse.
+ *
+ * @param[in,out] discourse Chat discourse
+ */
+void
+GNUNET_CHAT_discourse_close (struct GNUNET_CHAT_Discourse *discourse);
+
+/**
+ * Sends messages to a given chat <i>discourse</i> containing the
+ * specified <i>data</i> of a custom <i>size</i> in bytes.
+ *
+ * @param[in,out] discourse Chat discourse
+ * @param[in] data Data buffer
+ * @param[in] size Data size
+ * @return #GNUNET_OK on success, otherwise #GNUNET_SYSERR
+ */
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_discourse_write (struct GNUNET_CHAT_Discourse *discourse,
+ const char *data,
+ uint64_t size);
+
+/**
+ * Iterates through the subscribed chat contacts of a given chat <i>discourse</i>
+ * with a selected callback and custom closure.
+ *
+ * @param[in,out] discourse Chat discourse
+ * @param[in] callback Callback for contact iteration (optional)
+ * @param[in,out] cls Closure for contact iteration (optional)
+ * @return Amount of contacts iterated or #GNUNET_SYSERR on failure
+ */
+int
+GNUNET_CHAT_discourse_iterate_contacts (const struct GNUNET_CHAT_Discourse *discourse,
+ GNUNET_CHAT_DiscourseContactCallback callback,
+ void *cls);
+
/**@}*/
#endif /* GNUNET_CHAT_LIB_H_ */
diff --git a/src/gnunet_chat_context.c b/src/gnunet_chat_context.c
@@ -65,6 +65,8 @@ init_new_context (struct GNUNET_CHAT_Context *context,
initial_map_size, GNUNET_NO);
context->files = GNUNET_CONTAINER_multihashmap_create(
initial_map_size, GNUNET_NO);
+ context->discourses = GNUNET_CONTAINER_multishortmap_create(
+ initial_map_size, GNUNET_NO);
context->user_pointer = NULL;
@@ -157,6 +159,7 @@ context_destroy (struct GNUNET_CHAT_Context *context)
GNUNET_CONTAINER_multihashmap_destroy(context->taggings);
GNUNET_CONTAINER_multihashmap_destroy(context->invites);
GNUNET_CONTAINER_multihashmap_destroy(context->files);
+ GNUNET_CONTAINER_multishortmap_destroy(context->discourses);
if (context->topic)
GNUNET_free(context->topic);
diff --git a/src/gnunet_chat_context.h b/src/gnunet_chat_context.h
@@ -55,6 +55,7 @@ struct GNUNET_CHAT_Context
struct GNUNET_CONTAINER_MultiHashMap *taggings;
struct GNUNET_CONTAINER_MultiHashMap *invites;
struct GNUNET_CONTAINER_MultiHashMap *files;
+ struct GNUNET_CONTAINER_MultiShortmap *discourses;
struct GNUNET_MESSENGER_Room *room;
const struct GNUNET_MESSENGER_Contact *contact;
diff --git a/src/gnunet_chat_discourse.c b/src/gnunet_chat_discourse.c
@@ -0,0 +1,153 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file gnunet_chat_discourse.c
+ */
+
+#include "gnunet_chat_discourse.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_scheduler_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+
+struct GNUNET_CHAT_Discourse*
+discourse_create (struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_ShortHashCode *id)
+{
+ GNUNET_assert((context) && (id));
+
+ struct GNUNET_CHAT_Discourse *discourse = GNUNET_new(struct GNUNET_CHAT_Discourse);
+
+ discourse->context = context;
+
+ GNUNET_memcpy(&(discourse->id), id, sizeof (struct GNUNET_ShortHashCode));
+
+ discourse->head = NULL;
+ discourse->tail = NULL;
+
+ return discourse;
+}
+
+static void
+discourse_remove_subscription (void *cls)
+{
+ struct GNUNET_CHAT_DiscourseSubscription *sub = cls;
+ struct GNUNET_CHAT_Discourse *discourse = sub->discourse;
+
+ GNUNET_CONTAINER_DLL_remove(
+ discourse->head,
+ discourse->tail,
+ sub
+ );
+
+ GNUNET_free(sub);
+}
+
+void
+discourse_destroy (struct GNUNET_CHAT_Discourse *discourse)
+{
+ GNUNET_assert(discourse);
+
+ while (discourse->head)
+ discourse_remove_subscription (discourse->head);
+
+ GNUNET_free(discourse);
+}
+
+void
+discourse_subscribe (struct GNUNET_CHAT_Discourse *discourse,
+ struct GNUNET_CHAT_Contact *contact,
+ const struct GNUNET_TIME_Absolute timestamp,
+ const struct GNUNET_TIME_Relative time)
+{
+ GNUNET_assert((discourse) && (contact));
+
+ if (GNUNET_TIME_absolute_cmp(timestamp, <, GNUNET_TIME_absolute_get()))
+ return;
+
+ struct GNUNET_CHAT_DiscourseSubscription *sub;
+ for (sub = discourse->head; sub; sub = sub->next)
+ if (sub->contact == contact)
+ break;
+
+ if (!sub)
+ {
+ sub = GNUNET_new(struct GNUNET_CHAT_DiscourseSubscription);
+
+ sub->prev = NULL;
+ sub->next = NULL;
+
+ sub->discourse = discourse;
+ sub->contact = contact;
+
+ GNUNET_CONTAINER_DLL_insert(
+ discourse->head,
+ discourse->tail,
+ sub
+ );
+ }
+ else if (sub->task)
+ GNUNET_SCHEDULER_cancel(sub->task);
+
+ sub->start = timestamp;
+ sub->end = GNUNET_TIME_absolute_add(timestamp, time);
+
+ sub->task = GNUNET_SCHEDULER_add_at(
+ sub->end,
+ discourse_remove_subscription,
+ sub
+ );
+}
+
+void
+discourse_unsubscribe (struct GNUNET_CHAT_Discourse *discourse,
+ struct GNUNET_CHAT_Contact *contact,
+ const struct GNUNET_TIME_Absolute timestamp,
+ const struct GNUNET_TIME_Relative delay)
+{
+ GNUNET_assert((discourse) && (contact));
+
+ struct GNUNET_CHAT_DiscourseSubscription *sub;
+ for (sub = discourse->head; sub; sub = sub->next)
+ if (sub->contact == contact)
+ break;
+
+ if ((!sub) || (GNUNET_TIME_absolute_cmp(sub->start, >, timestamp)))
+ return;
+
+ const struct GNUNET_TIME_Absolute exit = GNUNET_TIME_absolute_add(
+ timestamp, delay
+ );
+
+ if (GNUNET_TIME_absolute_cmp(exit, <, sub->end))
+ sub->end = exit;
+
+ if (sub->task)
+ GNUNET_SCHEDULER_cancel(sub->task);
+
+ if (GNUNET_TIME_absolute_cmp(sub->end, <, GNUNET_TIME_absolute_get()))
+ discourse_remove_subscription(sub);
+ else
+ sub->task = GNUNET_SCHEDULER_add_at(
+ sub->end,
+ discourse_remove_subscription,
+ sub
+ );
+}
diff --git a/src/gnunet_chat_discourse.h b/src/gnunet_chat_discourse.h
@@ -0,0 +1,114 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file gnunet_chat_discourse.h
+ */
+
+#ifndef GNUNET_CHAT_DISCOURSE_H_
+#define GNUNET_CHAT_DISCOURSE_H_
+
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_messenger_service.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+
+#include "gnunet_chat_util.h"
+
+struct GNUNET_CHAT_Contact;
+struct GNUNET_CHAT_Context;
+
+struct GNUNET_CHAT_DiscourseSubscription
+{
+ struct GNUNET_CHAT_DiscourseSubscription *prev;
+ struct GNUNET_CHAT_DiscourseSubscription *next;
+
+ struct GNUNET_CHAT_Discourse *discourse;
+
+ struct GNUNET_TIME_Absolute start;
+ struct GNUNET_TIME_Absolute end;
+
+ struct GNUNET_CHAT_Contact *contact;
+ struct GNUNET_SCHEDULER_Task *task;
+};
+
+struct GNUNET_CHAT_Discourse
+{
+ struct GNUNET_CHAT_Context *context;
+
+ struct GNUNET_ShortHashCode id;
+
+ struct GNUNET_CHAT_DiscourseSubscription *head;
+ struct GNUNET_CHAT_DiscourseSubscription *tail;
+};
+
+/**
+ * Creates a chat discourse within a chat <i>context</i>
+ * with a selected discourse <i>id</i>.
+ *
+ * @param[in,out] context Chat context
+ * @param[in] id Discourse id
+ * @return New chat discourse
+ */
+struct GNUNET_CHAT_Discourse*
+discourse_create (struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_ShortHashCode *id);
+
+/**
+ * Destroys a chat <i>discourse</i> and frees its memory.
+ *
+ * @param[in,out] discourse Chat discourse
+ */
+void
+discourse_destroy (struct GNUNET_CHAT_Discourse *discourse);
+
+/**
+ * Updates the subscription of a specific chat <i>contact</i>
+ * to a given chat <i>discourse</i> with a selected
+ * <i>timestamp</i> and relative <i>time</i> window.
+ *
+ * @param[in,out] discourse Chat discourse
+ * @param[in,out] contact Chat contact
+ * @param[in] timestamp Timestamp
+ * @param[in] time Time window
+ */
+void
+discourse_subscribe (struct GNUNET_CHAT_Discourse *discourse,
+ struct GNUNET_CHAT_Contact *contact,
+ const struct GNUNET_TIME_Absolute timestamp,
+ const struct GNUNET_TIME_Relative time);
+
+/**
+ * Ends the subscription of a specific chat <i>contact</i>
+ * to a given chat <i>discourse</i> at a selected
+ * <i>timestamp</i> with a potential <i>delay</i>.
+ *
+ * @param[in,out] discourse Chat discourse
+ * @param[in,out] contact Chat contact
+ * @param[in] timestamp Timestamp
+ * @param[in] delay Delay
+ */
+void
+discourse_unsubscribe (struct GNUNET_CHAT_Discourse *discourse,
+ struct GNUNET_CHAT_Contact *contact,
+ const struct GNUNET_TIME_Absolute timestamp,
+ const struct GNUNET_TIME_Relative delay);
+
+#endif /* GNUNET_CHAT_DISCOURSE_H_ */
diff --git a/src/gnunet_chat_handle_intern.c b/src/gnunet_chat_handle_intern.c
@@ -24,6 +24,7 @@
#include "gnunet_chat_contact.h"
#include "gnunet_chat_context.h"
+#include "gnunet_chat_discourse.h"
#include "gnunet_chat_file.h"
#include "gnunet_chat_group.h"
#include "gnunet_chat_handle.h"
@@ -890,6 +891,42 @@ skip_msg_handing:
);
break;
}
+ case GNUNET_MESSENGER_KIND_SUBSCRIBE:
+ {
+ const struct GNUNET_ShortHashCode *id = &(message->msg->body.subscribe.discourse);
+ struct GNUNET_CHAT_Discourse *discourse = GNUNET_CONTAINER_multishortmap_get(
+ context->discourses, id
+ );
+
+ if (! discourse)
+ {
+ discourse = discourse_create(context, id);
+
+ if (GNUNET_OK != GNUNET_CONTAINER_multishortmap_put(context->discourses,
+ id, discourse, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))
+ {
+ discourse_destroy(discourse);
+ break;
+ }
+ }
+
+ if (GNUNET_MESSENGER_FLAG_SUBSCRIPTION_UNSUBSCRIBE & message->msg->body.subscribe.flags)
+ discourse_unsubscribe(
+ discourse,
+ contact,
+ GNUNET_TIME_absolute_ntoh(message->msg->header.timestamp),
+ GNUNET_TIME_relative_ntoh(message->msg->body.subscribe.time)
+ );
+ else
+ discourse_subscribe(
+ discourse,
+ contact,
+ GNUNET_TIME_absolute_ntoh(message->msg->header.timestamp),
+ GNUNET_TIME_relative_ntoh(message->msg->body.subscribe.time)
+ );
+
+ break;
+ }
default:
break;
}
diff --git a/src/gnunet_chat_lib.c b/src/gnunet_chat_lib.c
@@ -31,6 +31,7 @@
#include <gnunet/gnunet_reclaim_service.h>
#include <gnunet/gnunet_scheduler_lib.h>
#include <gnunet/gnunet_time_lib.h>
+#include <gnunet/gnunet_util_lib.h>
#include <libgen.h>
#include <stdint.h>
#include <string.h>
@@ -40,6 +41,7 @@
#include "gnunet_chat_contact.h"
#include "gnunet_chat_context.h"
+#include "gnunet_chat_discourse.h"
#include "gnunet_chat_file.h"
#include "gnunet_chat_group.h"
#include "gnunet_chat_handle.h"
@@ -1899,6 +1901,54 @@ GNUNET_CHAT_context_send_tag (struct GNUNET_CHAT_Context *context,
}
+struct GNUNET_CHAT_Discourse*
+GNUNET_CHAT_context_open_discourse (struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_ShortHashCode *id)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!context) || (!(context->discourses)) || (!(context->room)) || (!id))
+ return NULL;
+
+ struct GNUNET_CHAT_Discourse *discourse = GNUNET_CONTAINER_multishortmap_get(
+ context->discourses, id
+ );
+
+ if (!discourse)
+ {
+ discourse = discourse_create(context, id);
+
+ if (GNUNET_OK != GNUNET_CONTAINER_multishortmap_put(context->discourses,
+ id, discourse, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST))
+ {
+ discourse_destroy(discourse);
+ return NULL;
+ }
+ }
+
+ struct GNUNET_MESSENGER_Message msg;
+ memset(&msg, 0, sizeof(msg));
+
+ msg.header.kind = GNUNET_MESSENGER_KIND_SUBSCRIBE;
+ GNUNET_memcpy(&(msg.body.subscribe.discourse), id, sizeof(struct GNUNET_ShortHashCode));
+
+ const struct GNUNET_TIME_Relative subscribtion_time = GNUNET_TIME_relative_multiply(
+ GNUNET_TIME_relative_get_second_(), 10
+ );
+
+ msg.body.subscribe.time = GNUNET_TIME_relative_hton(subscribtion_time);
+ msg.body.subscribe.flags = GNUNET_MESSENGER_FLAG_SUBSCRIPTION_KEEP_ALIVE;
+
+ GNUNET_MESSENGER_send_message(
+ context->room,
+ &msg,
+ NULL
+ );
+
+ return discourse;
+}
+
+
int
GNUNET_CHAT_context_iterate_messages (struct GNUNET_CHAT_Context *context,
GNUNET_CHAT_ContextMessageCallback callback,
@@ -2235,6 +2285,32 @@ GNUNET_CHAT_message_get_invitation (const struct GNUNET_CHAT_Message *message)
}
+struct GNUNET_CHAT_Discourse*
+GNUNET_CHAT_message_get_discourse (const struct GNUNET_CHAT_Message *message)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!message) || (GNUNET_YES != message_has_msg(message)) ||
+ (!(message->context)))
+ return NULL;
+
+ struct GNUNET_CHAT_Discourse *discourse;
+
+ if (GNUNET_MESSENGER_KIND_SUBSCRIBE == message->msg->header.kind)
+ discourse = GNUNET_CONTAINER_multishortmap_get(
+ message->context->discourses,
+ &(message->msg->body.subscribe.discourse));
+ else if (GNUNET_MESSENGER_KIND_TALK == message->msg->header.kind)
+ discourse = GNUNET_CONTAINER_multishortmap_get(
+ message->context->discourses,
+ &(message->msg->body.talk.discourse));
+ else
+ discourse = NULL;
+
+ return discourse;
+}
+
+
const struct GNUNET_CHAT_Message*
GNUNET_CHAT_message_get_target (const struct GNUNET_CHAT_Message *message)
{
@@ -2294,6 +2370,49 @@ GNUNET_CHAT_message_iterate_tags (const struct GNUNET_CHAT_Message *message,
}
+uint64_t
+GNUNET_CHAT_message_available (const struct GNUNET_CHAT_Message *message)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!message) || (GNUNET_YES != message_has_msg(message)))
+ return 0;
+
+ if (GNUNET_MESSENGER_KIND_TALK == message->msg->header.kind)
+ return message->msg->body.talk.length;
+ else
+ return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_message_read (const struct GNUNET_CHAT_Message *message,
+ char *data,
+ uint64_t size)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!message) || (GNUNET_YES != message_has_msg(message)))
+ return GNUNET_SYSERR;
+
+ if (GNUNET_MESSENGER_KIND_TALK != message->msg->header.kind)
+ return GNUNET_SYSERR;
+
+ const uint64_t available = message->msg->body.talk.length;
+
+ if (available < size)
+ return GNUNET_NO;
+
+ GNUNET_memcpy(
+ data,
+ message->msg->body.talk.data,
+ size
+ );
+
+ return GNUNET_OK;
+}
+
+
const char*
GNUNET_CHAT_file_get_name (const struct GNUNET_CHAT_File *file)
{
@@ -2753,3 +2872,144 @@ GNUNET_CHAT_invitation_is_rejected (const struct GNUNET_CHAT_Invitation *invitat
else
return GNUNET_NO;
}
+
+
+const struct GNUNET_ShortHashCode*
+GNUNET_CHAT_discourse_get_id (const struct GNUNET_CHAT_Discourse *discourse)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if (!discourse)
+ return NULL;
+
+ return &(discourse->id);
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_discourse_is_open (const struct GNUNET_CHAT_Discourse *discourse)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if (!discourse)
+ return GNUNET_SYSERR;
+
+ struct GNUNET_CHAT_DiscourseSubscription *sub;
+ for (sub = discourse->head; sub; sub = sub->next)
+ {
+ if (GNUNET_TIME_absolute_cmp(sub->end, <, GNUNET_TIME_absolute_get()))
+ continue;
+
+ if (GNUNET_YES == sub->contact->owned)
+ return GNUNET_YES;
+ }
+
+ return GNUNET_NO;
+}
+
+
+void
+GNUNET_CHAT_discourse_close (struct GNUNET_CHAT_Discourse *discourse)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!discourse) || (!(discourse->context)) || (!(discourse->context->room)))
+ return;
+
+ struct GNUNET_MESSENGER_Message msg;
+ memset(&msg, 0, sizeof(msg));
+
+ msg.header.kind = GNUNET_MESSENGER_KIND_SUBSCRIBE;
+
+ GNUNET_memcpy(
+ &(msg.body.subscribe.discourse),
+ &(discourse->id),
+ sizeof(struct GNUNET_ShortHashCode)
+ );
+
+ msg.body.subscribe.time = GNUNET_TIME_relative_hton(GNUNET_TIME_relative_get_zero_());
+ msg.body.subscribe.flags = GNUNET_MESSENGER_FLAG_SUBSCRIPTION_UNSUBSCRIBE;
+
+ GNUNET_MESSENGER_send_message(
+ discourse->context->room,
+ &msg,
+ NULL
+ );
+}
+
+
+enum GNUNET_GenericReturnValue
+GNUNET_CHAT_discourse_write (struct GNUNET_CHAT_Discourse *discourse,
+ const char *data,
+ uint64_t size)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if ((!discourse) || (!data) || (!(discourse->context)) ||
+ (!(discourse->context->room)))
+ return GNUNET_SYSERR;
+
+ static const uint64_t max_size = (uint16_t) (
+ GNUNET_MAX_MESSAGE_SIZE - GNUNET_MIN_MESSAGE_SIZE -
+ sizeof (struct GNUNET_MESSENGER_Message)
+ );
+
+ struct GNUNET_MESSENGER_Message msg;
+ memset(&msg, 0, sizeof(msg));
+
+ msg.header.kind = GNUNET_MESSENGER_KIND_TALK;
+ msg.body.talk.data = GNUNET_malloc(size > max_size? max_size : size);
+
+ GNUNET_memcpy(
+ &(msg.body.talk.discourse),
+ &(discourse->id),
+ sizeof(struct GNUNET_ShortHashCode)
+ );
+
+ while (size > 0)
+ {
+ msg.body.talk.length = (uint16_t) (size > max_size? max_size : size);
+
+ GNUNET_memcpy(
+ msg.body.talk.data,
+ data,
+ msg.body.talk.length
+ );
+
+ size -= msg.body.talk.length;
+ data += msg.body.talk.length;
+
+ GNUNET_MESSENGER_send_message(discourse->context->room, &msg, NULL);
+ }
+
+ GNUNET_free(msg.body.talk.data);
+ return GNUNET_OK;
+}
+
+
+int
+GNUNET_CHAT_discourse_iterate_contacts (const struct GNUNET_CHAT_Discourse *discourse,
+ GNUNET_CHAT_DiscourseContactCallback callback,
+ void *cls)
+{
+ GNUNET_CHAT_VERSION_ASSERT();
+
+ if (! discourse)
+ return GNUNET_SYSERR;
+
+ int result = 0;
+
+ struct GNUNET_CHAT_DiscourseSubscription *sub;
+ for (sub = discourse->head; sub; sub = sub->next)
+ {
+ if (GNUNET_TIME_absolute_cmp(sub->end, <, GNUNET_TIME_absolute_get()))
+ continue;
+
+ if (callback)
+ callback(cls, discourse, sub->contact);
+
+ result++;
+ }
+
+ return result;
+}
diff --git a/src/gnunet_chat_util.c b/src/gnunet_chat_util.c
@@ -461,6 +461,10 @@ util_message_kind_from_kind (enum GNUNET_MESSENGER_MessageKind kind)
return GNUNET_CHAT_KIND_SHARED_ATTRIBUTES;
case GNUNET_MESSENGER_KIND_TAG:
return GNUNET_CHAT_KIND_TAG;
+ case GNUNET_MESSENGER_KIND_SUBSCRIBE:
+ return GNUNET_CHAT_KIND_DISCOURSE;
+ case GNUNET_MESSENGER_KIND_TALK:
+ return GNUNET_CHAT_KIND_DATA;
default:
return GNUNET_CHAT_KIND_UNKNOWN;
}
diff --git a/src/meson.build b/src/meson.build
@@ -24,6 +24,7 @@ gnunetchat_sources = files([
'gnunet_chat_account.c', 'gnunet_chat_account.h',
'gnunet_chat_contact.c', 'gnunet_chat_contact.h',
'gnunet_chat_context.c', 'gnunet_chat_context.h',
+ 'gnunet_chat_discourse.c', 'gnunet_chat_discourse.h',
'gnunet_chat_file.c', 'gnunet_chat_file.h',
'gnunet_chat_group.c', 'gnunet_chat_group.h',
'gnunet_chat_handle.c', 'gnunet_chat_handle.h',
diff --git a/tests/meson.build b/tests/meson.build
@@ -68,8 +68,18 @@ test_gnunet_chat_attribute = executable(
extra_files: 'test_gnunet_chat.h',
)
+test_gnunet_chat_discourse = executable(
+ 'test_gnunet_chat_discourse.test',
+ 'test_gnunet_chat_discourse.c',
+ dependencies: test_deps,
+ link_with: gnunetchat_lib,
+ include_directories: tests_include,
+ extra_files: 'test_gnunet_chat.h',
+)
+
test('test_gnunet_chat_handle', test_gnunet_chat_handle, depends: gnunetchat_lib)
test('test_gnunet_chat_lobby', test_gnunet_chat_lobby, depends: gnunetchat_lib)
test('test_gnunet_chat_file', test_gnunet_chat_file, depends: gnunetchat_lib)
test('test_gnunet_chat_message', test_gnunet_chat_message, depends: gnunetchat_lib)
test('test_gnunet_chat_attribute', test_gnunet_chat_attribute, depends: gnunetchat_lib)
+test('test_gnunet_chat_discourse', test_gnunet_chat_discourse, depends: gnunetchat_lib)
diff --git a/tests/test_gnunet_chat_discourse.c b/tests/test_gnunet_chat_discourse.c
@@ -0,0 +1,306 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file test_gnunet_chat_discourse.c
+ */
+
+#include "test_gnunet_chat.h"
+#include <check.h>
+#include <gnunet/gnunet_chat_lib.h>
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_messenger_service.h>
+
+#define TEST_OPEN_ID "gnunet_chat_discourse_open"
+#define TEST_OPEN_GROUP "gnunet_chat_discourse_open_group"
+#define TEST_OPEN_DISCOURSE "gnunet_chat_discourse_open_discourse"
+#define TEST_WRITE_ID "gnunet_chat_discourse_write"
+#define TEST_WRITE_GROUP "gnunet_chat_discourse_write_group"
+#define TEST_WRITE_DISCOURSE "gnunet_chat_discourse_write_discourse"
+
+enum GNUNET_GenericReturnValue
+on_gnunet_chat_discourse_open_msg(void *cls,
+ struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_CHAT_Message *message)
+{
+ struct GNUNET_CHAT_Handle *handle = *(
+ (struct GNUNET_CHAT_Handle**) cls
+ );
+
+ ck_assert_ptr_nonnull(handle);
+ ck_assert_ptr_nonnull(message);
+
+ const struct GNUNET_CHAT_Account *account;
+ account = GNUNET_CHAT_message_get_account(message);
+
+ const char *name = GNUNET_CHAT_get_name(handle);
+ struct GNUNET_ShortHashCode discourse_id;
+
+ struct GNUNET_CHAT_Discourse *discourse;
+ discourse = GNUNET_CHAT_message_get_discourse(message);
+
+ switch (GNUNET_CHAT_message_get_kind(message))
+ {
+ case GNUNET_CHAT_KIND_WARNING:
+ ck_abort_msg("%s\n", GNUNET_CHAT_message_get_text(message));
+ break;
+ case GNUNET_CHAT_KIND_REFRESH:
+ break;
+ case GNUNET_CHAT_KIND_LOGIN:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+ ck_assert_ptr_nonnull(name);
+ ck_assert_str_eq(name, TEST_OPEN_ID);
+
+ GNUNET_CHAT_group_create(handle, TEST_OPEN_GROUP);
+ break;
+ case GNUNET_CHAT_KIND_LOGOUT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+ ck_assert_int_eq(GNUNET_CHAT_account_delete(
+ handle,
+ TEST_OPEN_ID
+ ), GNUNET_OK);
+ break;
+ case GNUNET_CHAT_KIND_CREATED_ACCOUNT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+
+ GNUNET_CHAT_connect(handle, account);
+ break;
+ case GNUNET_CHAT_KIND_DELETED_ACCOUNT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+
+ GNUNET_CHAT_stop(handle);
+ break;
+ case GNUNET_CHAT_KIND_UPDATE_ACCOUNT:
+ break;
+ case GNUNET_CHAT_KIND_UPDATE_CONTEXT:
+ break;
+ case GNUNET_CHAT_KIND_JOIN:
+ ck_assert_ptr_nonnull(context);
+ ck_assert_ptr_null(discourse);
+
+ GNUNET_memcpy(
+ &discourse_id,
+ TEST_OPEN_DISCOURSE,
+ sizeof(discourse_id)
+ );
+
+ discourse = GNUNET_CHAT_context_open_discourse(
+ context,
+ &discourse_id
+ );
+
+ ck_assert_ptr_nonnull(discourse);
+ ck_assert_int_eq(GNUNET_CHAT_discourse_is_open(discourse), GNUNET_NO);
+ break;
+ case GNUNET_CHAT_KIND_CONTACT:
+ break;
+ case GNUNET_CHAT_KIND_DISCOURSE:
+ ck_assert_ptr_nonnull(context);
+ ck_assert_ptr_nonnull(discourse);
+
+ if (GNUNET_YES == GNUNET_CHAT_discourse_is_open(discourse))
+ GNUNET_CHAT_discourse_close(discourse);
+ else
+ GNUNET_CHAT_disconnect(handle);
+ break;
+ default:
+ ck_abort_msg("%d\n", GNUNET_CHAT_message_get_kind(message));
+ ck_abort();
+ break;
+ }
+
+ return GNUNET_YES;
+}
+
+void
+call_gnunet_chat_discourse_open(const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ static struct GNUNET_CHAT_Handle *handle = NULL;
+ handle = GNUNET_CHAT_start(cfg, on_gnunet_chat_discourse_open_msg, &handle);
+
+ ck_assert_ptr_nonnull(handle);
+ ck_assert_int_eq(GNUNET_CHAT_account_create(
+ handle, TEST_OPEN_ID
+ ), GNUNET_OK);
+}
+
+CREATE_GNUNET_TEST(test_gnunet_chat_discourse_open, call_gnunet_chat_discourse_open)
+
+enum GNUNET_GenericReturnValue
+on_gnunet_chat_discourse_write_msg(void *cls,
+ struct GNUNET_CHAT_Context *context,
+ const struct GNUNET_CHAT_Message *message)
+{
+ struct GNUNET_CHAT_Handle *handle = *(
+ (struct GNUNET_CHAT_Handle**) cls
+ );
+
+ ck_assert_ptr_nonnull(handle);
+ ck_assert_ptr_nonnull(message);
+
+ const struct GNUNET_CHAT_Account *account;
+ account = GNUNET_CHAT_message_get_account(message);
+
+ const char *name = GNUNET_CHAT_get_name(handle);
+ struct GNUNET_ShortHashCode discourse_id;
+
+ struct GNUNET_CHAT_Discourse *discourse;
+ discourse = GNUNET_CHAT_message_get_discourse(message);
+
+ switch (GNUNET_CHAT_message_get_kind(message))
+ {
+ case GNUNET_CHAT_KIND_WARNING:
+ ck_abort_msg("%s\n", GNUNET_CHAT_message_get_text(message));
+ break;
+ case GNUNET_CHAT_KIND_REFRESH:
+ break;
+ case GNUNET_CHAT_KIND_LOGIN:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+ ck_assert_ptr_nonnull(name);
+ ck_assert_str_eq(name, TEST_WRITE_ID);
+
+ GNUNET_CHAT_group_create(handle, TEST_WRITE_GROUP);
+ break;
+ case GNUNET_CHAT_KIND_LOGOUT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+ ck_assert_int_eq(GNUNET_CHAT_account_delete(
+ handle,
+ TEST_WRITE_ID
+ ), GNUNET_OK);
+ break;
+ case GNUNET_CHAT_KIND_CREATED_ACCOUNT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+
+ GNUNET_CHAT_connect(handle, account);
+ break;
+ case GNUNET_CHAT_KIND_DELETED_ACCOUNT:
+ ck_assert_ptr_null(context);
+ ck_assert_ptr_nonnull(account);
+
+ GNUNET_CHAT_stop(handle);
+ break;
+ case GNUNET_CHAT_KIND_UPDATE_ACCOUNT:
+ break;
+ case GNUNET_CHAT_KIND_UPDATE_CONTEXT:
+ break;
+ case GNUNET_CHAT_KIND_JOIN:
+ ck_assert_ptr_nonnull(context);
+ ck_assert_ptr_null(discourse);
+
+ GNUNET_memcpy(
+ &discourse_id,
+ TEST_WRITE_DISCOURSE,
+ sizeof(discourse_id)
+ );
+
+ discourse = GNUNET_CHAT_context_open_discourse(
+ context,
+ &discourse_id
+ );
+
+ ck_assert_ptr_nonnull(discourse);
+ ck_assert_int_eq(GNUNET_CHAT_discourse_is_open(discourse), GNUNET_NO);
+ break;
+ case GNUNET_CHAT_KIND_CONTACT:
+ break;
+ case GNUNET_CHAT_KIND_DISCOURSE:
+ ck_assert_ptr_nonnull(context);
+ ck_assert_ptr_nonnull(discourse);
+
+ GNUNET_memcpy(
+ &discourse_id,
+ TEST_WRITE_DISCOURSE,
+ sizeof(discourse_id)
+ );
+
+ if (GNUNET_YES == GNUNET_CHAT_discourse_is_open(discourse))
+ ck_assert_int_eq(
+ GNUNET_CHAT_discourse_write(
+ discourse,
+ (const char*) &discourse_id,
+ sizeof(discourse_id)
+ ),
+ GNUNET_OK
+ );
+ else
+ GNUNET_CHAT_disconnect(handle);
+ break;
+ case GNUNET_CHAT_KIND_DATA:
+ ck_assert_ptr_nonnull(context);
+ ck_assert_ptr_nonnull(discourse);
+
+ ck_assert_uint_eq(
+ GNUNET_CHAT_message_available(message),
+ sizeof(discourse_id)
+ );
+
+ ck_assert_int_eq(
+ GNUNET_CHAT_message_read(
+ message,
+ (char*) &discourse_id,
+ sizeof(discourse_id)
+ ),
+ GNUNET_OK
+ );
+
+ ck_assert_mem_eq(
+ &discourse_id,
+ TEST_WRITE_DISCOURSE,
+ sizeof(discourse_id)
+ );
+
+ GNUNET_CHAT_discourse_close(discourse);
+ break;
+ default:
+ ck_abort_msg("%d\n", GNUNET_CHAT_message_get_kind(message));
+ ck_abort();
+ break;
+ }
+
+ return GNUNET_YES;
+}
+
+void
+call_gnunet_chat_discourse_write(const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ static struct GNUNET_CHAT_Handle *handle = NULL;
+ handle = GNUNET_CHAT_start(cfg, on_gnunet_chat_discourse_write_msg, &handle);
+
+ ck_assert_ptr_nonnull(handle);
+ ck_assert_int_eq(GNUNET_CHAT_account_create(
+ handle, TEST_WRITE_ID
+ ), GNUNET_OK);
+}
+
+CREATE_GNUNET_TEST(test_gnunet_chat_discourse_write, call_gnunet_chat_discourse_write)
+
+START_SUITE(handle_suite, "Handle")
+ADD_TEST_TO_SUITE(test_gnunet_chat_discourse_open, "Open/Close")
+ADD_TEST_TO_SUITE(test_gnunet_chat_discourse_write, "Talk")
+END_SUITE
+
+MAIN_SUITE(handle_suite, CK_NORMAL)